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