##// END OF EJS Templates
color: update description of the extension...
Jordi Gutiérrez Hermoso -
r22771:d6274291 default
parent child Browse files
Show More
@@ -1,628 +1,628 b''
1 # color.py color output for the status and qseries commands
1 # color.py color output for Mercurial commands
2 #
2 #
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''colorize output from some commands
8 '''colorize output from some commands
9
9
10 This extension modifies the status and resolve commands to add color
10 The color extension colorizes output from several Mercurial commands.
11 to their output to reflect file status, the qseries command to add
11 For example, the diff command shows additions in green and deletions
12 color to reflect patch status (applied, unapplied, missing), and to
12 in red, while the status command shows modified files in magenta. Many
13 diff-related commands to highlight additions, removals, diff headers,
13 other commands have analogous colors. It is possible to customize
14 tabs, and trailing whitespace.
14 these colors.
15
15
16 Other effects in addition to color, like bold and underlined text, are
16 Other effects in addition to color, like bold and underlined text, are
17 also available. By default, the terminfo database is used to find the
17 also available. By default, the terminfo database is used to find the
18 terminal codes used to change color and effect. If terminfo is not
18 terminal codes used to change color and effect. If terminfo is not
19 available, then effects are rendered with the ECMA-48 SGR control
19 available, then effects are rendered with the ECMA-48 SGR control
20 function (aka ANSI escape codes).
20 function (aka ANSI escape codes).
21
21
22 Text receives color effects depending on the labels that it has. Many
22 Text receives color effects depending on the labels that it has. Many
23 default Mercurial commands emit labelled text. You can also define
23 default Mercurial commands emit labelled text. You can also define
24 your own labels in templates using the label function, see :hg:`help
24 your own labels in templates using the label function, see :hg:`help
25 templates`. A single portion of text may have more than one label. In
25 templates`. A single portion of text may have more than one label. In
26 that case, effects given to the last label will override any other
26 that case, effects given to the last label will override any other
27 effects. This includes the special "none" effect, which nullifies
27 effects. This includes the special "none" effect, which nullifies
28 other effects.
28 other effects.
29
29
30 Labels are normally invisible. In order to see these labels and their
30 Labels are normally invisible. In order to see these labels and their
31 position in the text, use the global --color=debug option. The same
31 position in the text, use the global --color=debug option. The same
32 anchor text may be associated to multiple labels, e.g.
32 anchor text may be associated to multiple labels, e.g.
33
33
34 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
34 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
35
35
36 The following are the default effects for some default labels. Default
36 The following are the default effects for some default labels. Default
37 effects may be overridden from your configuration file::
37 effects may be overridden from your configuration file::
38
38
39 [color]
39 [color]
40 status.modified = blue bold underline red_background
40 status.modified = blue bold underline red_background
41 status.added = green bold
41 status.added = green bold
42 status.removed = red bold blue_background
42 status.removed = red bold blue_background
43 status.deleted = cyan bold underline
43 status.deleted = cyan bold underline
44 status.unknown = magenta bold underline
44 status.unknown = magenta bold underline
45 status.ignored = black bold
45 status.ignored = black bold
46
46
47 # 'none' turns off all effects
47 # 'none' turns off all effects
48 status.clean = none
48 status.clean = none
49 status.copied = none
49 status.copied = none
50
50
51 qseries.applied = blue bold underline
51 qseries.applied = blue bold underline
52 qseries.unapplied = black bold
52 qseries.unapplied = black bold
53 qseries.missing = red bold
53 qseries.missing = red bold
54
54
55 diff.diffline = bold
55 diff.diffline = bold
56 diff.extended = cyan bold
56 diff.extended = cyan bold
57 diff.file_a = red bold
57 diff.file_a = red bold
58 diff.file_b = green bold
58 diff.file_b = green bold
59 diff.hunk = magenta
59 diff.hunk = magenta
60 diff.deleted = red
60 diff.deleted = red
61 diff.inserted = green
61 diff.inserted = green
62 diff.changed = white
62 diff.changed = white
63 diff.tab =
63 diff.tab =
64 diff.trailingwhitespace = bold red_background
64 diff.trailingwhitespace = bold red_background
65
65
66 # Blank so it inherits the style of the surrounding label
66 # Blank so it inherits the style of the surrounding label
67 changeset.public =
67 changeset.public =
68 changeset.draft =
68 changeset.draft =
69 changeset.secret =
69 changeset.secret =
70
70
71 resolve.unresolved = red bold
71 resolve.unresolved = red bold
72 resolve.resolved = green bold
72 resolve.resolved = green bold
73
73
74 bookmarks.current = green
74 bookmarks.current = green
75
75
76 branches.active = none
76 branches.active = none
77 branches.closed = black bold
77 branches.closed = black bold
78 branches.current = green
78 branches.current = green
79 branches.inactive = none
79 branches.inactive = none
80
80
81 tags.normal = green
81 tags.normal = green
82 tags.local = black bold
82 tags.local = black bold
83
83
84 rebase.rebased = blue
84 rebase.rebased = blue
85 rebase.remaining = red bold
85 rebase.remaining = red bold
86
86
87 shelve.age = cyan
87 shelve.age = cyan
88 shelve.newest = green bold
88 shelve.newest = green bold
89 shelve.name = blue bold
89 shelve.name = blue bold
90
90
91 histedit.remaining = red bold
91 histedit.remaining = red bold
92
92
93 The available effects in terminfo mode are 'blink', 'bold', 'dim',
93 The available effects in terminfo mode are 'blink', 'bold', 'dim',
94 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
94 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
95 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
95 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
96 'underline'. How each is rendered depends on the terminal emulator.
96 'underline'. How each is rendered depends on the terminal emulator.
97 Some may not be available for a given terminal type, and will be
97 Some may not be available for a given terminal type, and will be
98 silently ignored.
98 silently ignored.
99
99
100 Note that on some systems, terminfo mode may cause problems when using
100 Note that on some systems, terminfo mode may cause problems when using
101 color with the pager extension and less -R. less with the -R option
101 color with the pager extension and less -R. less with the -R option
102 will only display ECMA-48 color codes, and terminfo mode may sometimes
102 will only display ECMA-48 color codes, and terminfo mode may sometimes
103 emit codes that less doesn't understand. You can work around this by
103 emit codes that less doesn't understand. You can work around this by
104 either using ansi mode (or auto mode), or by using less -r (which will
104 either using ansi mode (or auto mode), or by using less -r (which will
105 pass through all terminal control codes, not just color control
105 pass through all terminal control codes, not just color control
106 codes).
106 codes).
107
107
108 Because there are only eight standard colors, this module allows you
108 Because there are only eight standard colors, this module allows you
109 to define color names for other color slots which might be available
109 to define color names for other color slots which might be available
110 for your terminal type, assuming terminfo mode. For instance::
110 for your terminal type, assuming terminfo mode. For instance::
111
111
112 color.brightblue = 12
112 color.brightblue = 12
113 color.pink = 207
113 color.pink = 207
114 color.orange = 202
114 color.orange = 202
115
115
116 to set 'brightblue' to color slot 12 (useful for 16 color terminals
116 to set 'brightblue' to color slot 12 (useful for 16 color terminals
117 that have brighter colors defined in the upper eight) and, 'pink' and
117 that have brighter colors defined in the upper eight) and, 'pink' and
118 'orange' to colors in 256-color xterm's default color cube. These
118 'orange' to colors in 256-color xterm's default color cube. These
119 defined colors may then be used as any of the pre-defined eight,
119 defined colors may then be used as any of the pre-defined eight,
120 including appending '_background' to set the background to that color.
120 including appending '_background' to set the background to that color.
121
121
122 By default, the color extension will use ANSI mode (or win32 mode on
122 By default, the color extension will use ANSI mode (or win32 mode on
123 Windows) if it detects a terminal. To override auto mode (to enable
123 Windows) if it detects a terminal. To override auto mode (to enable
124 terminfo mode, for example), set the following configuration option::
124 terminfo mode, for example), set the following configuration option::
125
125
126 [color]
126 [color]
127 mode = terminfo
127 mode = terminfo
128
128
129 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
129 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
130 disable color.
130 disable color.
131 '''
131 '''
132
132
133 import os
133 import os
134
134
135 from mercurial import cmdutil, commands, dispatch, extensions, ui as uimod, util
135 from mercurial import cmdutil, commands, dispatch, extensions, ui as uimod, util
136 from mercurial import templater, error
136 from mercurial import templater, error
137 from mercurial.i18n import _
137 from mercurial.i18n import _
138
138
139 cmdtable = {}
139 cmdtable = {}
140 command = cmdutil.command(cmdtable)
140 command = cmdutil.command(cmdtable)
141 testedwith = 'internal'
141 testedwith = 'internal'
142
142
143 # start and stop parameters for effects
143 # start and stop parameters for effects
144 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
144 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
145 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
145 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
146 'italic': 3, 'underline': 4, 'inverse': 7,
146 'italic': 3, 'underline': 4, 'inverse': 7,
147 'black_background': 40, 'red_background': 41,
147 'black_background': 40, 'red_background': 41,
148 'green_background': 42, 'yellow_background': 43,
148 'green_background': 42, 'yellow_background': 43,
149 'blue_background': 44, 'purple_background': 45,
149 'blue_background': 44, 'purple_background': 45,
150 'cyan_background': 46, 'white_background': 47}
150 'cyan_background': 46, 'white_background': 47}
151
151
152 def _terminfosetup(ui, mode):
152 def _terminfosetup(ui, mode):
153 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
153 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
154
154
155 global _terminfo_params
155 global _terminfo_params
156 # If we failed to load curses, we go ahead and return.
156 # If we failed to load curses, we go ahead and return.
157 if not _terminfo_params:
157 if not _terminfo_params:
158 return
158 return
159 # Otherwise, see what the config file says.
159 # Otherwise, see what the config file says.
160 if mode not in ('auto', 'terminfo'):
160 if mode not in ('auto', 'terminfo'):
161 return
161 return
162
162
163 _terminfo_params.update((key[6:], (False, int(val)))
163 _terminfo_params.update((key[6:], (False, int(val)))
164 for key, val in ui.configitems('color')
164 for key, val in ui.configitems('color')
165 if key.startswith('color.'))
165 if key.startswith('color.'))
166
166
167 try:
167 try:
168 curses.setupterm()
168 curses.setupterm()
169 except curses.error, e:
169 except curses.error, e:
170 _terminfo_params = {}
170 _terminfo_params = {}
171 return
171 return
172
172
173 for key, (b, e) in _terminfo_params.items():
173 for key, (b, e) in _terminfo_params.items():
174 if not b:
174 if not b:
175 continue
175 continue
176 if not curses.tigetstr(e):
176 if not curses.tigetstr(e):
177 # Most terminals don't support dim, invis, etc, so don't be
177 # Most terminals don't support dim, invis, etc, so don't be
178 # noisy and use ui.debug().
178 # noisy and use ui.debug().
179 ui.debug("no terminfo entry for %s\n" % e)
179 ui.debug("no terminfo entry for %s\n" % e)
180 del _terminfo_params[key]
180 del _terminfo_params[key]
181 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
181 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
182 # Only warn about missing terminfo entries if we explicitly asked for
182 # Only warn about missing terminfo entries if we explicitly asked for
183 # terminfo mode.
183 # terminfo mode.
184 if mode == "terminfo":
184 if mode == "terminfo":
185 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
185 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
186 "ECMA-48 color\n"))
186 "ECMA-48 color\n"))
187 _terminfo_params = {}
187 _terminfo_params = {}
188
188
189 def _modesetup(ui, coloropt):
189 def _modesetup(ui, coloropt):
190 global _terminfo_params
190 global _terminfo_params
191
191
192 if coloropt == 'debug':
192 if coloropt == 'debug':
193 return 'debug'
193 return 'debug'
194
194
195 auto = (coloropt == 'auto')
195 auto = (coloropt == 'auto')
196 always = not auto and util.parsebool(coloropt)
196 always = not auto and util.parsebool(coloropt)
197 if not always and not auto:
197 if not always and not auto:
198 return None
198 return None
199
199
200 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
200 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
201
201
202 mode = ui.config('color', 'mode', 'auto')
202 mode = ui.config('color', 'mode', 'auto')
203 realmode = mode
203 realmode = mode
204 if mode == 'auto':
204 if mode == 'auto':
205 if os.name == 'nt' and 'TERM' not in os.environ:
205 if os.name == 'nt' and 'TERM' not in os.environ:
206 # looks line a cmd.exe console, use win32 API or nothing
206 # looks line a cmd.exe console, use win32 API or nothing
207 realmode = 'win32'
207 realmode = 'win32'
208 else:
208 else:
209 realmode = 'ansi'
209 realmode = 'ansi'
210
210
211 if realmode == 'win32':
211 if realmode == 'win32':
212 _terminfo_params = {}
212 _terminfo_params = {}
213 if not w32effects:
213 if not w32effects:
214 if mode == 'win32':
214 if mode == 'win32':
215 # only warn if color.mode is explicitly set to win32
215 # only warn if color.mode is explicitly set to win32
216 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
216 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
217 return None
217 return None
218 _effects.update(w32effects)
218 _effects.update(w32effects)
219 elif realmode == 'ansi':
219 elif realmode == 'ansi':
220 _terminfo_params = {}
220 _terminfo_params = {}
221 elif realmode == 'terminfo':
221 elif realmode == 'terminfo':
222 _terminfosetup(ui, mode)
222 _terminfosetup(ui, mode)
223 if not _terminfo_params:
223 if not _terminfo_params:
224 if mode == 'terminfo':
224 if mode == 'terminfo':
225 ## FIXME Shouldn't we return None in this case too?
225 ## FIXME Shouldn't we return None in this case too?
226 # only warn if color.mode is explicitly set to win32
226 # only warn if color.mode is explicitly set to win32
227 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
227 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
228 realmode = 'ansi'
228 realmode = 'ansi'
229 else:
229 else:
230 return None
230 return None
231
231
232 if always or (auto and formatted):
232 if always or (auto and formatted):
233 return realmode
233 return realmode
234 return None
234 return None
235
235
236 try:
236 try:
237 import curses
237 import curses
238 # Mapping from effect name to terminfo attribute name or color number.
238 # Mapping from effect name to terminfo attribute name or color number.
239 # This will also force-load the curses module.
239 # This will also force-load the curses module.
240 _terminfo_params = {'none': (True, 'sgr0'),
240 _terminfo_params = {'none': (True, 'sgr0'),
241 'standout': (True, 'smso'),
241 'standout': (True, 'smso'),
242 'underline': (True, 'smul'),
242 'underline': (True, 'smul'),
243 'reverse': (True, 'rev'),
243 'reverse': (True, 'rev'),
244 'inverse': (True, 'rev'),
244 'inverse': (True, 'rev'),
245 'blink': (True, 'blink'),
245 'blink': (True, 'blink'),
246 'dim': (True, 'dim'),
246 'dim': (True, 'dim'),
247 'bold': (True, 'bold'),
247 'bold': (True, 'bold'),
248 'invisible': (True, 'invis'),
248 'invisible': (True, 'invis'),
249 'italic': (True, 'sitm'),
249 'italic': (True, 'sitm'),
250 'black': (False, curses.COLOR_BLACK),
250 'black': (False, curses.COLOR_BLACK),
251 'red': (False, curses.COLOR_RED),
251 'red': (False, curses.COLOR_RED),
252 'green': (False, curses.COLOR_GREEN),
252 'green': (False, curses.COLOR_GREEN),
253 'yellow': (False, curses.COLOR_YELLOW),
253 'yellow': (False, curses.COLOR_YELLOW),
254 'blue': (False, curses.COLOR_BLUE),
254 'blue': (False, curses.COLOR_BLUE),
255 'magenta': (False, curses.COLOR_MAGENTA),
255 'magenta': (False, curses.COLOR_MAGENTA),
256 'cyan': (False, curses.COLOR_CYAN),
256 'cyan': (False, curses.COLOR_CYAN),
257 'white': (False, curses.COLOR_WHITE)}
257 'white': (False, curses.COLOR_WHITE)}
258 except ImportError:
258 except ImportError:
259 _terminfo_params = {}
259 _terminfo_params = {}
260
260
261 _styles = {'grep.match': 'red bold',
261 _styles = {'grep.match': 'red bold',
262 'grep.linenumber': 'green',
262 'grep.linenumber': 'green',
263 'grep.rev': 'green',
263 'grep.rev': 'green',
264 'grep.change': 'green',
264 'grep.change': 'green',
265 'grep.sep': 'cyan',
265 'grep.sep': 'cyan',
266 'grep.filename': 'magenta',
266 'grep.filename': 'magenta',
267 'grep.user': 'magenta',
267 'grep.user': 'magenta',
268 'grep.date': 'magenta',
268 'grep.date': 'magenta',
269 'bookmarks.current': 'green',
269 'bookmarks.current': 'green',
270 'branches.active': 'none',
270 'branches.active': 'none',
271 'branches.closed': 'black bold',
271 'branches.closed': 'black bold',
272 'branches.current': 'green',
272 'branches.current': 'green',
273 'branches.inactive': 'none',
273 'branches.inactive': 'none',
274 'diff.changed': 'white',
274 'diff.changed': 'white',
275 'diff.deleted': 'red',
275 'diff.deleted': 'red',
276 'diff.diffline': 'bold',
276 'diff.diffline': 'bold',
277 'diff.extended': 'cyan bold',
277 'diff.extended': 'cyan bold',
278 'diff.file_a': 'red bold',
278 'diff.file_a': 'red bold',
279 'diff.file_b': 'green bold',
279 'diff.file_b': 'green bold',
280 'diff.hunk': 'magenta',
280 'diff.hunk': 'magenta',
281 'diff.inserted': 'green',
281 'diff.inserted': 'green',
282 'diff.tab': '',
282 'diff.tab': '',
283 'diff.trailingwhitespace': 'bold red_background',
283 'diff.trailingwhitespace': 'bold red_background',
284 'changeset.public' : '',
284 'changeset.public' : '',
285 'changeset.draft' : '',
285 'changeset.draft' : '',
286 'changeset.secret' : '',
286 'changeset.secret' : '',
287 'diffstat.deleted': 'red',
287 'diffstat.deleted': 'red',
288 'diffstat.inserted': 'green',
288 'diffstat.inserted': 'green',
289 'histedit.remaining': 'red bold',
289 'histedit.remaining': 'red bold',
290 'ui.prompt': 'yellow',
290 'ui.prompt': 'yellow',
291 'log.changeset': 'yellow',
291 'log.changeset': 'yellow',
292 'rebase.rebased': 'blue',
292 'rebase.rebased': 'blue',
293 'rebase.remaining': 'red bold',
293 'rebase.remaining': 'red bold',
294 'resolve.resolved': 'green bold',
294 'resolve.resolved': 'green bold',
295 'resolve.unresolved': 'red bold',
295 'resolve.unresolved': 'red bold',
296 'shelve.age': 'cyan',
296 'shelve.age': 'cyan',
297 'shelve.newest': 'green bold',
297 'shelve.newest': 'green bold',
298 'shelve.name': 'blue bold',
298 'shelve.name': 'blue bold',
299 'status.added': 'green bold',
299 'status.added': 'green bold',
300 'status.clean': 'none',
300 'status.clean': 'none',
301 'status.copied': 'none',
301 'status.copied': 'none',
302 'status.deleted': 'cyan bold underline',
302 'status.deleted': 'cyan bold underline',
303 'status.ignored': 'black bold',
303 'status.ignored': 'black bold',
304 'status.modified': 'blue bold',
304 'status.modified': 'blue bold',
305 'status.removed': 'red bold',
305 'status.removed': 'red bold',
306 'status.unknown': 'magenta bold underline',
306 'status.unknown': 'magenta bold underline',
307 'tags.normal': 'green',
307 'tags.normal': 'green',
308 'tags.local': 'black bold'}
308 'tags.local': 'black bold'}
309
309
310
310
311 def _effect_str(effect):
311 def _effect_str(effect):
312 '''Helper function for render_effects().'''
312 '''Helper function for render_effects().'''
313
313
314 bg = False
314 bg = False
315 if effect.endswith('_background'):
315 if effect.endswith('_background'):
316 bg = True
316 bg = True
317 effect = effect[:-11]
317 effect = effect[:-11]
318 attr, val = _terminfo_params[effect]
318 attr, val = _terminfo_params[effect]
319 if attr:
319 if attr:
320 return curses.tigetstr(val)
320 return curses.tigetstr(val)
321 elif bg:
321 elif bg:
322 return curses.tparm(curses.tigetstr('setab'), val)
322 return curses.tparm(curses.tigetstr('setab'), val)
323 else:
323 else:
324 return curses.tparm(curses.tigetstr('setaf'), val)
324 return curses.tparm(curses.tigetstr('setaf'), val)
325
325
326 def render_effects(text, effects):
326 def render_effects(text, effects):
327 'Wrap text in commands to turn on each effect.'
327 'Wrap text in commands to turn on each effect.'
328 if not text:
328 if not text:
329 return text
329 return text
330 if not _terminfo_params:
330 if not _terminfo_params:
331 start = [str(_effects[e]) for e in ['none'] + effects.split()]
331 start = [str(_effects[e]) for e in ['none'] + effects.split()]
332 start = '\033[' + ';'.join(start) + 'm'
332 start = '\033[' + ';'.join(start) + 'm'
333 stop = '\033[' + str(_effects['none']) + 'm'
333 stop = '\033[' + str(_effects['none']) + 'm'
334 else:
334 else:
335 start = ''.join(_effect_str(effect)
335 start = ''.join(_effect_str(effect)
336 for effect in ['none'] + effects.split())
336 for effect in ['none'] + effects.split())
337 stop = _effect_str('none')
337 stop = _effect_str('none')
338 return ''.join([start, text, stop])
338 return ''.join([start, text, stop])
339
339
340 def extstyles():
340 def extstyles():
341 for name, ext in extensions.extensions():
341 for name, ext in extensions.extensions():
342 _styles.update(getattr(ext, 'colortable', {}))
342 _styles.update(getattr(ext, 'colortable', {}))
343
343
344 def valideffect(effect):
344 def valideffect(effect):
345 'Determine if the effect is valid or not.'
345 'Determine if the effect is valid or not.'
346 good = False
346 good = False
347 if not _terminfo_params and effect in _effects:
347 if not _terminfo_params and effect in _effects:
348 good = True
348 good = True
349 elif effect in _terminfo_params or effect[:-11] in _terminfo_params:
349 elif effect in _terminfo_params or effect[:-11] in _terminfo_params:
350 good = True
350 good = True
351 return good
351 return good
352
352
353 def configstyles(ui):
353 def configstyles(ui):
354 for status, cfgeffects in ui.configitems('color'):
354 for status, cfgeffects in ui.configitems('color'):
355 if '.' not in status or status.startswith('color.'):
355 if '.' not in status or status.startswith('color.'):
356 continue
356 continue
357 cfgeffects = ui.configlist('color', status)
357 cfgeffects = ui.configlist('color', status)
358 if cfgeffects:
358 if cfgeffects:
359 good = []
359 good = []
360 for e in cfgeffects:
360 for e in cfgeffects:
361 if valideffect(e):
361 if valideffect(e):
362 good.append(e)
362 good.append(e)
363 else:
363 else:
364 ui.warn(_("ignoring unknown color/effect %r "
364 ui.warn(_("ignoring unknown color/effect %r "
365 "(configured in color.%s)\n")
365 "(configured in color.%s)\n")
366 % (e, status))
366 % (e, status))
367 _styles[status] = ' '.join(good)
367 _styles[status] = ' '.join(good)
368
368
369 class colorui(uimod.ui):
369 class colorui(uimod.ui):
370 def popbuffer(self, labeled=False):
370 def popbuffer(self, labeled=False):
371 if self._colormode is None:
371 if self._colormode is None:
372 return super(colorui, self).popbuffer(labeled)
372 return super(colorui, self).popbuffer(labeled)
373
373
374 self._bufferstates.pop()
374 self._bufferstates.pop()
375 if labeled:
375 if labeled:
376 return ''.join(self.label(a, label) for a, label
376 return ''.join(self.label(a, label) for a, label
377 in self._buffers.pop())
377 in self._buffers.pop())
378 return ''.join(a for a, label in self._buffers.pop())
378 return ''.join(a for a, label in self._buffers.pop())
379
379
380 _colormode = 'ansi'
380 _colormode = 'ansi'
381 def write(self, *args, **opts):
381 def write(self, *args, **opts):
382 if self._colormode is None:
382 if self._colormode is None:
383 return super(colorui, self).write(*args, **opts)
383 return super(colorui, self).write(*args, **opts)
384
384
385 label = opts.get('label', '')
385 label = opts.get('label', '')
386 if self._buffers:
386 if self._buffers:
387 self._buffers[-1].extend([(str(a), label) for a in args])
387 self._buffers[-1].extend([(str(a), label) for a in args])
388 elif self._colormode == 'win32':
388 elif self._colormode == 'win32':
389 for a in args:
389 for a in args:
390 win32print(a, super(colorui, self).write, **opts)
390 win32print(a, super(colorui, self).write, **opts)
391 else:
391 else:
392 return super(colorui, self).write(
392 return super(colorui, self).write(
393 *[self.label(str(a), label) for a in args], **opts)
393 *[self.label(str(a), label) for a in args], **opts)
394
394
395 def write_err(self, *args, **opts):
395 def write_err(self, *args, **opts):
396 if self._colormode is None:
396 if self._colormode is None:
397 return super(colorui, self).write_err(*args, **opts)
397 return super(colorui, self).write_err(*args, **opts)
398
398
399 label = opts.get('label', '')
399 label = opts.get('label', '')
400 if self._bufferstates and self._bufferstates[-1]:
400 if self._bufferstates and self._bufferstates[-1]:
401 return self.write(*args, **opts)
401 return self.write(*args, **opts)
402 if self._colormode == 'win32':
402 if self._colormode == 'win32':
403 for a in args:
403 for a in args:
404 win32print(a, super(colorui, self).write_err, **opts)
404 win32print(a, super(colorui, self).write_err, **opts)
405 else:
405 else:
406 return super(colorui, self).write_err(
406 return super(colorui, self).write_err(
407 *[self.label(str(a), label) for a in args], **opts)
407 *[self.label(str(a), label) for a in args], **opts)
408
408
409 def showlabel(self, msg, label):
409 def showlabel(self, msg, label):
410 if label and msg:
410 if label and msg:
411 if msg[-1] == '\n':
411 if msg[-1] == '\n':
412 return "[%s|%s]\n" % (label, msg[:-1])
412 return "[%s|%s]\n" % (label, msg[:-1])
413 else:
413 else:
414 return "[%s|%s]" % (label, msg)
414 return "[%s|%s]" % (label, msg)
415 else:
415 else:
416 return msg
416 return msg
417
417
418 def label(self, msg, label):
418 def label(self, msg, label):
419 if self._colormode is None:
419 if self._colormode is None:
420 return super(colorui, self).label(msg, label)
420 return super(colorui, self).label(msg, label)
421
421
422 if self._colormode == 'debug':
422 if self._colormode == 'debug':
423 return self.showlabel(msg, label)
423 return self.showlabel(msg, label)
424
424
425 effects = []
425 effects = []
426 for l in label.split():
426 for l in label.split():
427 s = _styles.get(l, '')
427 s = _styles.get(l, '')
428 if s:
428 if s:
429 effects.append(s)
429 effects.append(s)
430 elif valideffect(l):
430 elif valideffect(l):
431 effects.append(l)
431 effects.append(l)
432 effects = ' '.join(effects)
432 effects = ' '.join(effects)
433 if effects:
433 if effects:
434 return '\n'.join([render_effects(s, effects)
434 return '\n'.join([render_effects(s, effects)
435 for s in msg.split('\n')])
435 for s in msg.split('\n')])
436 return msg
436 return msg
437
437
438 def templatelabel(context, mapping, args):
438 def templatelabel(context, mapping, args):
439 if len(args) != 2:
439 if len(args) != 2:
440 # i18n: "label" is a keyword
440 # i18n: "label" is a keyword
441 raise error.ParseError(_("label expects two arguments"))
441 raise error.ParseError(_("label expects two arguments"))
442
442
443 # add known effects to the mapping so symbols like 'red', 'bold',
443 # add known effects to the mapping so symbols like 'red', 'bold',
444 # etc. don't need to be quoted
444 # etc. don't need to be quoted
445 mapping.update(dict([(k, k) for k in _effects]))
445 mapping.update(dict([(k, k) for k in _effects]))
446
446
447 thing = templater._evalifliteral(args[1], context, mapping)
447 thing = templater._evalifliteral(args[1], context, mapping)
448
448
449 # apparently, repo could be a string that is the favicon?
449 # apparently, repo could be a string that is the favicon?
450 repo = mapping.get('repo', '')
450 repo = mapping.get('repo', '')
451 if isinstance(repo, str):
451 if isinstance(repo, str):
452 return thing
452 return thing
453
453
454 label = templater._evalifliteral(args[0], context, mapping)
454 label = templater._evalifliteral(args[0], context, mapping)
455
455
456 thing = templater.stringify(thing)
456 thing = templater.stringify(thing)
457 label = templater.stringify(label)
457 label = templater.stringify(label)
458
458
459 return repo.ui.label(thing, label)
459 return repo.ui.label(thing, label)
460
460
461 def uisetup(ui):
461 def uisetup(ui):
462 if ui.plain():
462 if ui.plain():
463 return
463 return
464 if not isinstance(ui, colorui):
464 if not isinstance(ui, colorui):
465 colorui.__bases__ = (ui.__class__,)
465 colorui.__bases__ = (ui.__class__,)
466 ui.__class__ = colorui
466 ui.__class__ = colorui
467 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
467 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
468 mode = _modesetup(ui_, opts['color'])
468 mode = _modesetup(ui_, opts['color'])
469 colorui._colormode = mode
469 colorui._colormode = mode
470 if mode and mode != 'debug':
470 if mode and mode != 'debug':
471 extstyles()
471 extstyles()
472 configstyles(ui_)
472 configstyles(ui_)
473 return orig(ui_, opts, cmd, cmdfunc)
473 return orig(ui_, opts, cmd, cmdfunc)
474 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
474 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
475 templater.funcs['label'] = templatelabel
475 templater.funcs['label'] = templatelabel
476
476
477 def extsetup(ui):
477 def extsetup(ui):
478 commands.globalopts.append(
478 commands.globalopts.append(
479 ('', 'color', 'auto',
479 ('', 'color', 'auto',
480 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
480 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
481 # and should not be translated
481 # and should not be translated
482 _("when to colorize (boolean, always, auto, never, or debug)"),
482 _("when to colorize (boolean, always, auto, never, or debug)"),
483 _('TYPE')))
483 _('TYPE')))
484
484
485 @command('debugcolor', [], 'hg debugcolor')
485 @command('debugcolor', [], 'hg debugcolor')
486 def debugcolor(ui, repo, **opts):
486 def debugcolor(ui, repo, **opts):
487 global _styles
487 global _styles
488 _styles = {}
488 _styles = {}
489 for effect in _effects.keys():
489 for effect in _effects.keys():
490 _styles[effect] = effect
490 _styles[effect] = effect
491 ui.write(('color mode: %s\n') % ui._colormode)
491 ui.write(('color mode: %s\n') % ui._colormode)
492 ui.write(_('available colors:\n'))
492 ui.write(_('available colors:\n'))
493 for label, colors in _styles.items():
493 for label, colors in _styles.items():
494 ui.write(('%s\n') % colors, label=label)
494 ui.write(('%s\n') % colors, label=label)
495
495
496 if os.name != 'nt':
496 if os.name != 'nt':
497 w32effects = None
497 w32effects = None
498 else:
498 else:
499 import re, ctypes
499 import re, ctypes
500
500
501 _kernel32 = ctypes.windll.kernel32
501 _kernel32 = ctypes.windll.kernel32
502
502
503 _WORD = ctypes.c_ushort
503 _WORD = ctypes.c_ushort
504
504
505 _INVALID_HANDLE_VALUE = -1
505 _INVALID_HANDLE_VALUE = -1
506
506
507 class _COORD(ctypes.Structure):
507 class _COORD(ctypes.Structure):
508 _fields_ = [('X', ctypes.c_short),
508 _fields_ = [('X', ctypes.c_short),
509 ('Y', ctypes.c_short)]
509 ('Y', ctypes.c_short)]
510
510
511 class _SMALL_RECT(ctypes.Structure):
511 class _SMALL_RECT(ctypes.Structure):
512 _fields_ = [('Left', ctypes.c_short),
512 _fields_ = [('Left', ctypes.c_short),
513 ('Top', ctypes.c_short),
513 ('Top', ctypes.c_short),
514 ('Right', ctypes.c_short),
514 ('Right', ctypes.c_short),
515 ('Bottom', ctypes.c_short)]
515 ('Bottom', ctypes.c_short)]
516
516
517 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
517 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
518 _fields_ = [('dwSize', _COORD),
518 _fields_ = [('dwSize', _COORD),
519 ('dwCursorPosition', _COORD),
519 ('dwCursorPosition', _COORD),
520 ('wAttributes', _WORD),
520 ('wAttributes', _WORD),
521 ('srWindow', _SMALL_RECT),
521 ('srWindow', _SMALL_RECT),
522 ('dwMaximumWindowSize', _COORD)]
522 ('dwMaximumWindowSize', _COORD)]
523
523
524 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
524 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
525 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
525 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
526
526
527 _FOREGROUND_BLUE = 0x0001
527 _FOREGROUND_BLUE = 0x0001
528 _FOREGROUND_GREEN = 0x0002
528 _FOREGROUND_GREEN = 0x0002
529 _FOREGROUND_RED = 0x0004
529 _FOREGROUND_RED = 0x0004
530 _FOREGROUND_INTENSITY = 0x0008
530 _FOREGROUND_INTENSITY = 0x0008
531
531
532 _BACKGROUND_BLUE = 0x0010
532 _BACKGROUND_BLUE = 0x0010
533 _BACKGROUND_GREEN = 0x0020
533 _BACKGROUND_GREEN = 0x0020
534 _BACKGROUND_RED = 0x0040
534 _BACKGROUND_RED = 0x0040
535 _BACKGROUND_INTENSITY = 0x0080
535 _BACKGROUND_INTENSITY = 0x0080
536
536
537 _COMMON_LVB_REVERSE_VIDEO = 0x4000
537 _COMMON_LVB_REVERSE_VIDEO = 0x4000
538 _COMMON_LVB_UNDERSCORE = 0x8000
538 _COMMON_LVB_UNDERSCORE = 0x8000
539
539
540 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
540 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
541 w32effects = {
541 w32effects = {
542 'none': -1,
542 'none': -1,
543 'black': 0,
543 'black': 0,
544 'red': _FOREGROUND_RED,
544 'red': _FOREGROUND_RED,
545 'green': _FOREGROUND_GREEN,
545 'green': _FOREGROUND_GREEN,
546 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
546 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
547 'blue': _FOREGROUND_BLUE,
547 'blue': _FOREGROUND_BLUE,
548 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
548 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
549 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
549 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
550 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
550 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
551 'bold': _FOREGROUND_INTENSITY,
551 'bold': _FOREGROUND_INTENSITY,
552 'black_background': 0x100, # unused value > 0x0f
552 'black_background': 0x100, # unused value > 0x0f
553 'red_background': _BACKGROUND_RED,
553 'red_background': _BACKGROUND_RED,
554 'green_background': _BACKGROUND_GREEN,
554 'green_background': _BACKGROUND_GREEN,
555 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
555 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
556 'blue_background': _BACKGROUND_BLUE,
556 'blue_background': _BACKGROUND_BLUE,
557 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
557 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
558 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
558 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
559 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
559 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
560 _BACKGROUND_BLUE),
560 _BACKGROUND_BLUE),
561 'bold_background': _BACKGROUND_INTENSITY,
561 'bold_background': _BACKGROUND_INTENSITY,
562 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
562 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
563 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
563 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
564 }
564 }
565
565
566 passthrough = set([_FOREGROUND_INTENSITY,
566 passthrough = set([_FOREGROUND_INTENSITY,
567 _BACKGROUND_INTENSITY,
567 _BACKGROUND_INTENSITY,
568 _COMMON_LVB_UNDERSCORE,
568 _COMMON_LVB_UNDERSCORE,
569 _COMMON_LVB_REVERSE_VIDEO])
569 _COMMON_LVB_REVERSE_VIDEO])
570
570
571 stdout = _kernel32.GetStdHandle(
571 stdout = _kernel32.GetStdHandle(
572 _STD_OUTPUT_HANDLE) # don't close the handle returned
572 _STD_OUTPUT_HANDLE) # don't close the handle returned
573 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
573 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
574 w32effects = None
574 w32effects = None
575 else:
575 else:
576 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
576 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
577 if not _kernel32.GetConsoleScreenBufferInfo(
577 if not _kernel32.GetConsoleScreenBufferInfo(
578 stdout, ctypes.byref(csbi)):
578 stdout, ctypes.byref(csbi)):
579 # stdout may not support GetConsoleScreenBufferInfo()
579 # stdout may not support GetConsoleScreenBufferInfo()
580 # when called from subprocess or redirected
580 # when called from subprocess or redirected
581 w32effects = None
581 w32effects = None
582 else:
582 else:
583 origattr = csbi.wAttributes
583 origattr = csbi.wAttributes
584 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
584 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
585 re.MULTILINE | re.DOTALL)
585 re.MULTILINE | re.DOTALL)
586
586
587 def win32print(text, orig, **opts):
587 def win32print(text, orig, **opts):
588 label = opts.get('label', '')
588 label = opts.get('label', '')
589 attr = origattr
589 attr = origattr
590
590
591 def mapcolor(val, attr):
591 def mapcolor(val, attr):
592 if val == -1:
592 if val == -1:
593 return origattr
593 return origattr
594 elif val in passthrough:
594 elif val in passthrough:
595 return attr | val
595 return attr | val
596 elif val > 0x0f:
596 elif val > 0x0f:
597 return (val & 0x70) | (attr & 0x8f)
597 return (val & 0x70) | (attr & 0x8f)
598 else:
598 else:
599 return (val & 0x07) | (attr & 0xf8)
599 return (val & 0x07) | (attr & 0xf8)
600
600
601 # determine console attributes based on labels
601 # determine console attributes based on labels
602 for l in label.split():
602 for l in label.split():
603 style = _styles.get(l, '')
603 style = _styles.get(l, '')
604 for effect in style.split():
604 for effect in style.split():
605 try:
605 try:
606 attr = mapcolor(w32effects[effect], attr)
606 attr = mapcolor(w32effects[effect], attr)
607 except KeyError:
607 except KeyError:
608 # w32effects could not have certain attributes so we skip
608 # w32effects could not have certain attributes so we skip
609 # them if not found
609 # them if not found
610 pass
610 pass
611 # hack to ensure regexp finds data
611 # hack to ensure regexp finds data
612 if not text.startswith('\033['):
612 if not text.startswith('\033['):
613 text = '\033[m' + text
613 text = '\033[m' + text
614
614
615 # Look for ANSI-like codes embedded in text
615 # Look for ANSI-like codes embedded in text
616 m = re.match(ansire, text)
616 m = re.match(ansire, text)
617
617
618 try:
618 try:
619 while m:
619 while m:
620 for sattr in m.group(1).split(';'):
620 for sattr in m.group(1).split(';'):
621 if sattr:
621 if sattr:
622 attr = mapcolor(int(sattr), attr)
622 attr = mapcolor(int(sattr), attr)
623 _kernel32.SetConsoleTextAttribute(stdout, attr)
623 _kernel32.SetConsoleTextAttribute(stdout, attr)
624 orig(m.group(2), **opts)
624 orig(m.group(2), **opts)
625 m = re.match(ansire, m.group(3))
625 m = re.match(ansire, m.group(3))
626 finally:
626 finally:
627 # Explicitly reset original attributes
627 # Explicitly reset original attributes
628 _kernel32.SetConsoleTextAttribute(stdout, origattr)
628 _kernel32.SetConsoleTextAttribute(stdout, origattr)
General Comments 0
You need to be logged in to leave comments. Login now