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