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