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