##// END OF EJS Templates
color: fallback and test label as an effect...
Sean Farley -
r20993:a8db48e9 default
parent child Browse files
Show More
@@ -1,577 +1,579
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 valideffect(effect):
314 def valideffect(effect):
315 'Determine if the effect is valid or not.'
315 'Determine if the effect is valid or not.'
316 good = False
316 good = False
317 if not _terminfo_params and effect in _effects:
317 if not _terminfo_params and effect in _effects:
318 good = True
318 good = True
319 elif effect in _terminfo_params or effect[:-11] in _terminfo_params:
319 elif effect in _terminfo_params or effect[:-11] in _terminfo_params:
320 good = True
320 good = True
321 return good
321 return good
322
322
323 def configstyles(ui):
323 def configstyles(ui):
324 for status, cfgeffects in ui.configitems('color'):
324 for status, cfgeffects in ui.configitems('color'):
325 if '.' not in status or status.startswith('color.'):
325 if '.' not in status or status.startswith('color.'):
326 continue
326 continue
327 cfgeffects = ui.configlist('color', status)
327 cfgeffects = ui.configlist('color', status)
328 if cfgeffects:
328 if cfgeffects:
329 good = []
329 good = []
330 for e in cfgeffects:
330 for e in cfgeffects:
331 if valideffect(e):
331 if valideffect(e):
332 good.append(e)
332 good.append(e)
333 else:
333 else:
334 ui.warn(_("ignoring unknown color/effect %r "
334 ui.warn(_("ignoring unknown color/effect %r "
335 "(configured in color.%s)\n")
335 "(configured in color.%s)\n")
336 % (e, status))
336 % (e, status))
337 _styles[status] = ' '.join(good)
337 _styles[status] = ' '.join(good)
338
338
339 class colorui(uimod.ui):
339 class colorui(uimod.ui):
340 def popbuffer(self, labeled=False):
340 def popbuffer(self, labeled=False):
341 if self._colormode is None:
341 if self._colormode is None:
342 return super(colorui, self).popbuffer(labeled)
342 return super(colorui, self).popbuffer(labeled)
343
343
344 if labeled:
344 if labeled:
345 return ''.join(self.label(a, label) for a, label
345 return ''.join(self.label(a, label) for a, label
346 in self._buffers.pop())
346 in self._buffers.pop())
347 return ''.join(a for a, label in self._buffers.pop())
347 return ''.join(a for a, label in self._buffers.pop())
348
348
349 _colormode = 'ansi'
349 _colormode = 'ansi'
350 def write(self, *args, **opts):
350 def write(self, *args, **opts):
351 if self._colormode is None:
351 if self._colormode is None:
352 return super(colorui, self).write(*args, **opts)
352 return super(colorui, self).write(*args, **opts)
353
353
354 label = opts.get('label', '')
354 label = opts.get('label', '')
355 if self._buffers:
355 if self._buffers:
356 self._buffers[-1].extend([(str(a), label) for a in args])
356 self._buffers[-1].extend([(str(a), label) for a in args])
357 elif self._colormode == 'win32':
357 elif self._colormode == 'win32':
358 for a in args:
358 for a in args:
359 win32print(a, super(colorui, self).write, **opts)
359 win32print(a, super(colorui, self).write, **opts)
360 else:
360 else:
361 return super(colorui, self).write(
361 return super(colorui, self).write(
362 *[self.label(str(a), label) for a in args], **opts)
362 *[self.label(str(a), label) for a in args], **opts)
363
363
364 def write_err(self, *args, **opts):
364 def write_err(self, *args, **opts):
365 if self._colormode is None:
365 if self._colormode is None:
366 return super(colorui, self).write_err(*args, **opts)
366 return super(colorui, self).write_err(*args, **opts)
367
367
368 label = opts.get('label', '')
368 label = opts.get('label', '')
369 if self._colormode == 'win32':
369 if self._colormode == 'win32':
370 for a in args:
370 for a in args:
371 win32print(a, super(colorui, self).write_err, **opts)
371 win32print(a, super(colorui, self).write_err, **opts)
372 else:
372 else:
373 return super(colorui, self).write_err(
373 return super(colorui, self).write_err(
374 *[self.label(str(a), label) for a in args], **opts)
374 *[self.label(str(a), label) for a in args], **opts)
375
375
376 def label(self, msg, label):
376 def label(self, msg, label):
377 if self._colormode is None:
377 if self._colormode is None:
378 return super(colorui, self).label(msg, label)
378 return super(colorui, self).label(msg, label)
379
379
380 effects = []
380 effects = []
381 for l in label.split():
381 for l in label.split():
382 s = _styles.get(l, '')
382 s = _styles.get(l, '')
383 if s:
383 if s:
384 effects.append(s)
384 effects.append(s)
385 elif valideffect(l):
386 effects.append(l)
385 effects = ' '.join(effects)
387 effects = ' '.join(effects)
386 if effects:
388 if effects:
387 return '\n'.join([render_effects(s, effects)
389 return '\n'.join([render_effects(s, effects)
388 for s in msg.split('\n')])
390 for s in msg.split('\n')])
389 return msg
391 return msg
390
392
391 def templatelabel(context, mapping, args):
393 def templatelabel(context, mapping, args):
392 if len(args) != 2:
394 if len(args) != 2:
393 # i18n: "label" is a keyword
395 # i18n: "label" is a keyword
394 raise error.ParseError(_("label expects two arguments"))
396 raise error.ParseError(_("label expects two arguments"))
395
397
396 thing = templater._evalifliteral(args[1], context, mapping)
398 thing = templater._evalifliteral(args[1], context, mapping)
397
399
398 # apparently, repo could be a string that is the favicon?
400 # apparently, repo could be a string that is the favicon?
399 repo = mapping.get('repo', '')
401 repo = mapping.get('repo', '')
400 if isinstance(repo, str):
402 if isinstance(repo, str):
401 return thing
403 return thing
402
404
403 label = templater._evalifliteral(args[0], context, mapping)
405 label = templater._evalifliteral(args[0], context, mapping)
404
406
405 thing = templater.stringify(thing)
407 thing = templater.stringify(thing)
406 label = templater.stringify(label)
408 label = templater.stringify(label)
407
409
408 return repo.ui.label(thing, label)
410 return repo.ui.label(thing, label)
409
411
410 def uisetup(ui):
412 def uisetup(ui):
411 if ui.plain():
413 if ui.plain():
412 return
414 return
413 if not isinstance(ui, colorui):
415 if not isinstance(ui, colorui):
414 colorui.__bases__ = (ui.__class__,)
416 colorui.__bases__ = (ui.__class__,)
415 ui.__class__ = colorui
417 ui.__class__ = colorui
416 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
418 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
417 mode = _modesetup(ui_, opts['color'])
419 mode = _modesetup(ui_, opts['color'])
418 colorui._colormode = mode
420 colorui._colormode = mode
419 if mode:
421 if mode:
420 extstyles()
422 extstyles()
421 configstyles(ui_)
423 configstyles(ui_)
422 return orig(ui_, opts, cmd, cmdfunc)
424 return orig(ui_, opts, cmd, cmdfunc)
423 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
425 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
424 templater.funcs['label'] = templatelabel
426 templater.funcs['label'] = templatelabel
425
427
426 def extsetup(ui):
428 def extsetup(ui):
427 commands.globalopts.append(
429 commands.globalopts.append(
428 ('', 'color', 'auto',
430 ('', 'color', 'auto',
429 # i18n: 'always', 'auto', and 'never' are keywords and should
431 # i18n: 'always', 'auto', and 'never' are keywords and should
430 # not be translated
432 # not be translated
431 _("when to colorize (boolean, always, auto, or never)"),
433 _("when to colorize (boolean, always, auto, or never)"),
432 _('TYPE')))
434 _('TYPE')))
433
435
434 def debugcolor(ui, repo, **opts):
436 def debugcolor(ui, repo, **opts):
435 global _styles
437 global _styles
436 _styles = {}
438 _styles = {}
437 for effect in _effects.keys():
439 for effect in _effects.keys():
438 _styles[effect] = effect
440 _styles[effect] = effect
439 ui.write(('colormode: %s\n') % ui._colormode)
441 ui.write(('colormode: %s\n') % ui._colormode)
440 ui.write(_('available colors:\n'))
442 ui.write(_('available colors:\n'))
441 for label, colors in _styles.items():
443 for label, colors in _styles.items():
442 ui.write(('%s\n') % colors, label=label)
444 ui.write(('%s\n') % colors, label=label)
443
445
444 if os.name != 'nt':
446 if os.name != 'nt':
445 w32effects = None
447 w32effects = None
446 else:
448 else:
447 import re, ctypes
449 import re, ctypes
448
450
449 _kernel32 = ctypes.windll.kernel32
451 _kernel32 = ctypes.windll.kernel32
450
452
451 _WORD = ctypes.c_ushort
453 _WORD = ctypes.c_ushort
452
454
453 _INVALID_HANDLE_VALUE = -1
455 _INVALID_HANDLE_VALUE = -1
454
456
455 class _COORD(ctypes.Structure):
457 class _COORD(ctypes.Structure):
456 _fields_ = [('X', ctypes.c_short),
458 _fields_ = [('X', ctypes.c_short),
457 ('Y', ctypes.c_short)]
459 ('Y', ctypes.c_short)]
458
460
459 class _SMALL_RECT(ctypes.Structure):
461 class _SMALL_RECT(ctypes.Structure):
460 _fields_ = [('Left', ctypes.c_short),
462 _fields_ = [('Left', ctypes.c_short),
461 ('Top', ctypes.c_short),
463 ('Top', ctypes.c_short),
462 ('Right', ctypes.c_short),
464 ('Right', ctypes.c_short),
463 ('Bottom', ctypes.c_short)]
465 ('Bottom', ctypes.c_short)]
464
466
465 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
467 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
466 _fields_ = [('dwSize', _COORD),
468 _fields_ = [('dwSize', _COORD),
467 ('dwCursorPosition', _COORD),
469 ('dwCursorPosition', _COORD),
468 ('wAttributes', _WORD),
470 ('wAttributes', _WORD),
469 ('srWindow', _SMALL_RECT),
471 ('srWindow', _SMALL_RECT),
470 ('dwMaximumWindowSize', _COORD)]
472 ('dwMaximumWindowSize', _COORD)]
471
473
472 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
474 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
473 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
475 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
474
476
475 _FOREGROUND_BLUE = 0x0001
477 _FOREGROUND_BLUE = 0x0001
476 _FOREGROUND_GREEN = 0x0002
478 _FOREGROUND_GREEN = 0x0002
477 _FOREGROUND_RED = 0x0004
479 _FOREGROUND_RED = 0x0004
478 _FOREGROUND_INTENSITY = 0x0008
480 _FOREGROUND_INTENSITY = 0x0008
479
481
480 _BACKGROUND_BLUE = 0x0010
482 _BACKGROUND_BLUE = 0x0010
481 _BACKGROUND_GREEN = 0x0020
483 _BACKGROUND_GREEN = 0x0020
482 _BACKGROUND_RED = 0x0040
484 _BACKGROUND_RED = 0x0040
483 _BACKGROUND_INTENSITY = 0x0080
485 _BACKGROUND_INTENSITY = 0x0080
484
486
485 _COMMON_LVB_REVERSE_VIDEO = 0x4000
487 _COMMON_LVB_REVERSE_VIDEO = 0x4000
486 _COMMON_LVB_UNDERSCORE = 0x8000
488 _COMMON_LVB_UNDERSCORE = 0x8000
487
489
488 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
490 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
489 w32effects = {
491 w32effects = {
490 'none': -1,
492 'none': -1,
491 'black': 0,
493 'black': 0,
492 'red': _FOREGROUND_RED,
494 'red': _FOREGROUND_RED,
493 'green': _FOREGROUND_GREEN,
495 'green': _FOREGROUND_GREEN,
494 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
496 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
495 'blue': _FOREGROUND_BLUE,
497 'blue': _FOREGROUND_BLUE,
496 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
498 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
497 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
499 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
498 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
500 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
499 'bold': _FOREGROUND_INTENSITY,
501 'bold': _FOREGROUND_INTENSITY,
500 'black_background': 0x100, # unused value > 0x0f
502 'black_background': 0x100, # unused value > 0x0f
501 'red_background': _BACKGROUND_RED,
503 'red_background': _BACKGROUND_RED,
502 'green_background': _BACKGROUND_GREEN,
504 'green_background': _BACKGROUND_GREEN,
503 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
505 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
504 'blue_background': _BACKGROUND_BLUE,
506 'blue_background': _BACKGROUND_BLUE,
505 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
507 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
506 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
508 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
507 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
509 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
508 _BACKGROUND_BLUE),
510 _BACKGROUND_BLUE),
509 'bold_background': _BACKGROUND_INTENSITY,
511 'bold_background': _BACKGROUND_INTENSITY,
510 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
512 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
511 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
513 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
512 }
514 }
513
515
514 passthrough = set([_FOREGROUND_INTENSITY,
516 passthrough = set([_FOREGROUND_INTENSITY,
515 _BACKGROUND_INTENSITY,
517 _BACKGROUND_INTENSITY,
516 _COMMON_LVB_UNDERSCORE,
518 _COMMON_LVB_UNDERSCORE,
517 _COMMON_LVB_REVERSE_VIDEO])
519 _COMMON_LVB_REVERSE_VIDEO])
518
520
519 stdout = _kernel32.GetStdHandle(
521 stdout = _kernel32.GetStdHandle(
520 _STD_OUTPUT_HANDLE) # don't close the handle returned
522 _STD_OUTPUT_HANDLE) # don't close the handle returned
521 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
523 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
522 w32effects = None
524 w32effects = None
523 else:
525 else:
524 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
526 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
525 if not _kernel32.GetConsoleScreenBufferInfo(
527 if not _kernel32.GetConsoleScreenBufferInfo(
526 stdout, ctypes.byref(csbi)):
528 stdout, ctypes.byref(csbi)):
527 # stdout may not support GetConsoleScreenBufferInfo()
529 # stdout may not support GetConsoleScreenBufferInfo()
528 # when called from subprocess or redirected
530 # when called from subprocess or redirected
529 w32effects = None
531 w32effects = None
530 else:
532 else:
531 origattr = csbi.wAttributes
533 origattr = csbi.wAttributes
532 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
534 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
533 re.MULTILINE | re.DOTALL)
535 re.MULTILINE | re.DOTALL)
534
536
535 def win32print(text, orig, **opts):
537 def win32print(text, orig, **opts):
536 label = opts.get('label', '')
538 label = opts.get('label', '')
537 attr = origattr
539 attr = origattr
538
540
539 def mapcolor(val, attr):
541 def mapcolor(val, attr):
540 if val == -1:
542 if val == -1:
541 return origattr
543 return origattr
542 elif val in passthrough:
544 elif val in passthrough:
543 return attr | val
545 return attr | val
544 elif val > 0x0f:
546 elif val > 0x0f:
545 return (val & 0x70) | (attr & 0x8f)
547 return (val & 0x70) | (attr & 0x8f)
546 else:
548 else:
547 return (val & 0x07) | (attr & 0xf8)
549 return (val & 0x07) | (attr & 0xf8)
548
550
549 # determine console attributes based on labels
551 # determine console attributes based on labels
550 for l in label.split():
552 for l in label.split():
551 style = _styles.get(l, '')
553 style = _styles.get(l, '')
552 for effect in style.split():
554 for effect in style.split():
553 attr = mapcolor(w32effects[effect], attr)
555 attr = mapcolor(w32effects[effect], attr)
554
556
555 # hack to ensure regexp finds data
557 # hack to ensure regexp finds data
556 if not text.startswith('\033['):
558 if not text.startswith('\033['):
557 text = '\033[m' + text
559 text = '\033[m' + text
558
560
559 # Look for ANSI-like codes embedded in text
561 # Look for ANSI-like codes embedded in text
560 m = re.match(ansire, text)
562 m = re.match(ansire, text)
561
563
562 try:
564 try:
563 while m:
565 while m:
564 for sattr in m.group(1).split(';'):
566 for sattr in m.group(1).split(';'):
565 if sattr:
567 if sattr:
566 attr = mapcolor(int(sattr), attr)
568 attr = mapcolor(int(sattr), attr)
567 _kernel32.SetConsoleTextAttribute(stdout, attr)
569 _kernel32.SetConsoleTextAttribute(stdout, attr)
568 orig(m.group(2), **opts)
570 orig(m.group(2), **opts)
569 m = re.match(ansire, m.group(3))
571 m = re.match(ansire, m.group(3))
570 finally:
572 finally:
571 # Explicitly reset original attributes
573 # Explicitly reset original attributes
572 _kernel32.SetConsoleTextAttribute(stdout, origattr)
574 _kernel32.SetConsoleTextAttribute(stdout, origattr)
573
575
574 cmdtable = {
576 cmdtable = {
575 'debugcolor':
577 'debugcolor':
576 (debugcolor, [], ('hg debugcolor'))
578 (debugcolor, [], ('hg debugcolor'))
577 }
579 }
General Comments 0
You need to be logged in to leave comments. Login now