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