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