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