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