##// END OF EJS Templates
color: fix TypeError with auto mode on win32 when colors aren't available (issue2871)...
Brodie Rao -
r14768:55db12e5 stable
parent child Browse files
Show More
@@ -1,499 +1,500
1 1 # color.py color output for the status and qseries commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 4 #
5 5 # This program is free software; you can redistribute it and/or modify it
6 6 # under the terms of the GNU General Public License as published by the
7 7 # Free Software Foundation; either version 2 of the License, or (at your
8 8 # option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful, but
11 11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 13 # Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License along
16 16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 18
19 19 '''colorize output from some commands
20 20
21 21 This extension modifies the status and resolve commands to add color
22 22 to their output to reflect file status, the qseries command to add
23 23 color to reflect patch status (applied, unapplied, missing), and to
24 24 diff-related commands to highlight additions, removals, diff headers,
25 25 and trailing whitespace.
26 26
27 27 Other effects in addition to color, like bold and underlined text, are
28 28 also available. By default, the terminfo database is used to find the
29 29 terminal codes used to change color and effect. If terminfo is not
30 30 available, then effects are rendered with the ECMA-48 SGR control
31 31 function (aka ANSI escape codes).
32 32
33 33 Default effects may be overridden from your configuration file::
34 34
35 35 [color]
36 36 status.modified = blue bold underline red_background
37 37 status.added = green bold
38 38 status.removed = red bold blue_background
39 39 status.deleted = cyan bold underline
40 40 status.unknown = magenta bold underline
41 41 status.ignored = black bold
42 42
43 43 # 'none' turns off all effects
44 44 status.clean = none
45 45 status.copied = none
46 46
47 47 qseries.applied = blue bold underline
48 48 qseries.unapplied = black bold
49 49 qseries.missing = red bold
50 50
51 51 diff.diffline = bold
52 52 diff.extended = cyan bold
53 53 diff.file_a = red bold
54 54 diff.file_b = green bold
55 55 diff.hunk = magenta
56 56 diff.deleted = red
57 57 diff.inserted = green
58 58 diff.changed = white
59 59 diff.trailingwhitespace = bold red_background
60 60
61 61 resolve.unresolved = red bold
62 62 resolve.resolved = green bold
63 63
64 64 bookmarks.current = green
65 65
66 66 branches.active = none
67 67 branches.closed = black bold
68 68 branches.current = green
69 69 branches.inactive = none
70 70
71 71 The available effects in terminfo mode are 'blink', 'bold', 'dim',
72 72 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
73 73 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
74 74 'underline'. How each is rendered depends on the terminal emulator.
75 75 Some may not be available for a given terminal type, and will be
76 76 silently ignored.
77 77
78 78 Because there are only eight standard colors, this module allows you
79 79 to define color names for other color slots which might be available
80 80 for your terminal type, assuming terminfo mode. For instance::
81 81
82 82 color.brightblue = 12
83 83 color.pink = 207
84 84 color.orange = 202
85 85
86 86 to set 'brightblue' to color slot 12 (useful for 16 color terminals
87 87 that have brighter colors defined in the upper eight) and, 'pink' and
88 88 'orange' to colors in 256-color xterm's default color cube. These
89 89 defined colors may then be used as any of the pre-defined eight,
90 90 including appending '_background' to set the background to that color.
91 91
92 92 The color extension will try to detect whether to use terminfo, ANSI
93 93 codes or Win32 console APIs, unless it is made explicit; e.g.::
94 94
95 95 [color]
96 96 mode = ansi
97 97
98 98 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
99 99 disable color.
100 100
101 101 '''
102 102
103 103 import os
104 104
105 105 from mercurial import commands, dispatch, extensions, ui as uimod, util
106 106 from mercurial.i18n import _
107 107
108 108 # start and stop parameters for effects
109 109 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
110 110 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
111 111 'italic': 3, 'underline': 4, 'inverse': 7,
112 112 'black_background': 40, 'red_background': 41,
113 113 'green_background': 42, 'yellow_background': 43,
114 114 'blue_background': 44, 'purple_background': 45,
115 115 'cyan_background': 46, 'white_background': 47}
116 116
117 117 def _terminfosetup(ui, mode):
118 118 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
119 119
120 120 global _terminfo_params
121 121 # If we failed to load curses, we go ahead and return.
122 122 if not _terminfo_params:
123 123 return
124 124 # Otherwise, see what the config file says.
125 125 if mode not in ('auto', 'terminfo'):
126 126 return
127 127
128 128 _terminfo_params.update((key[6:], (False, int(val)))
129 129 for key, val in ui.configitems('color')
130 130 if key.startswith('color.'))
131 131
132 132 try:
133 133 curses.setupterm()
134 134 except curses.error, e:
135 135 _terminfo_params = {}
136 136 return
137 137
138 138 for key, (b, e) in _terminfo_params.items():
139 139 if not b:
140 140 continue
141 141 if not curses.tigetstr(e):
142 142 # Most terminals don't support dim, invis, etc, so don't be
143 143 # noisy and use ui.debug().
144 144 ui.debug("no terminfo entry for %s\n" % e)
145 145 del _terminfo_params[key]
146 146 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
147 147 # Only warn about missing terminfo entries if we explicitly asked for
148 148 # terminfo mode.
149 149 if mode == "terminfo":
150 150 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
151 151 "ECMA-48 color\n"))
152 152 _terminfo_params = {}
153 153
154 154 def _modesetup(ui, opts):
155 155 global _terminfo_params
156 156
157 157 coloropt = opts['color']
158 158 auto = coloropt == 'auto'
159 159 always = not auto and util.parsebool(coloropt)
160 160 if not always and not auto:
161 161 return None
162 162
163 163 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
164 164
165 165 mode = ui.config('color', 'mode', 'auto')
166 166 realmode = mode
167 167 if mode == 'auto':
168 168 if os.name == 'nt' and 'TERM' not in os.environ:
169 169 # looks line a cmd.exe console, use win32 API or nothing
170 170 realmode = 'win32'
171 171 elif not formatted:
172 172 realmode = 'ansi'
173 173 else:
174 174 realmode = 'terminfo'
175 175
176 176 if realmode == 'win32':
177 if not w32effects and mode == 'win32':
177 if not w32effects:
178 if mode == 'win32':
178 179 # only warn if color.mode is explicitly set to win32
179 180 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
180 181 return None
181 182 _effects.update(w32effects)
182 183 elif realmode == 'ansi':
183 184 _terminfo_params = {}
184 185 elif realmode == 'terminfo':
185 186 _terminfosetup(ui, mode)
186 187 if not _terminfo_params:
187 188 if mode == 'terminfo':
188 189 ## FIXME Shouldn't we return None in this case too?
189 190 # only warn if color.mode is explicitly set to win32
190 191 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
191 192 realmode = 'ansi'
192 193 else:
193 194 return None
194 195
195 196 if always or (auto and formatted):
196 197 return realmode
197 198 return None
198 199
199 200 try:
200 201 import curses
201 202 # Mapping from effect name to terminfo attribute name or color number.
202 203 # This will also force-load the curses module.
203 204 _terminfo_params = {'none': (True, 'sgr0'),
204 205 'standout': (True, 'smso'),
205 206 'underline': (True, 'smul'),
206 207 'reverse': (True, 'rev'),
207 208 'inverse': (True, 'rev'),
208 209 'blink': (True, 'blink'),
209 210 'dim': (True, 'dim'),
210 211 'bold': (True, 'bold'),
211 212 'invisible': (True, 'invis'),
212 213 'italic': (True, 'sitm'),
213 214 'black': (False, curses.COLOR_BLACK),
214 215 'red': (False, curses.COLOR_RED),
215 216 'green': (False, curses.COLOR_GREEN),
216 217 'yellow': (False, curses.COLOR_YELLOW),
217 218 'blue': (False, curses.COLOR_BLUE),
218 219 'magenta': (False, curses.COLOR_MAGENTA),
219 220 'cyan': (False, curses.COLOR_CYAN),
220 221 'white': (False, curses.COLOR_WHITE)}
221 222 except ImportError:
222 223 _terminfo_params = False
223 224
224 225 _styles = {'grep.match': 'red bold',
225 226 'bookmarks.current': 'green',
226 227 'branches.active': 'none',
227 228 'branches.closed': 'black bold',
228 229 'branches.current': 'green',
229 230 'branches.inactive': 'none',
230 231 'diff.changed': 'white',
231 232 'diff.deleted': 'red',
232 233 'diff.diffline': 'bold',
233 234 'diff.extended': 'cyan bold',
234 235 'diff.file_a': 'red bold',
235 236 'diff.file_b': 'green bold',
236 237 'diff.hunk': 'magenta',
237 238 'diff.inserted': 'green',
238 239 'diff.trailingwhitespace': 'bold red_background',
239 240 'diffstat.deleted': 'red',
240 241 'diffstat.inserted': 'green',
241 242 'ui.prompt': 'yellow',
242 243 'log.changeset': 'yellow',
243 244 'resolve.resolved': 'green bold',
244 245 'resolve.unresolved': 'red bold',
245 246 'status.added': 'green bold',
246 247 'status.clean': 'none',
247 248 'status.copied': 'none',
248 249 'status.deleted': 'cyan bold underline',
249 250 'status.ignored': 'black bold',
250 251 'status.modified': 'blue bold',
251 252 'status.removed': 'red bold',
252 253 'status.unknown': 'magenta bold underline'}
253 254
254 255
255 256 def _effect_str(effect):
256 257 '''Helper function for render_effects().'''
257 258
258 259 bg = False
259 260 if effect.endswith('_background'):
260 261 bg = True
261 262 effect = effect[:-11]
262 263 attr, val = _terminfo_params[effect]
263 264 if attr:
264 265 return curses.tigetstr(val)
265 266 elif bg:
266 267 return curses.tparm(curses.tigetstr('setab'), val)
267 268 else:
268 269 return curses.tparm(curses.tigetstr('setaf'), val)
269 270
270 271 def render_effects(text, effects):
271 272 'Wrap text in commands to turn on each effect.'
272 273 if not text:
273 274 return text
274 275 if not _terminfo_params:
275 276 start = [str(_effects[e]) for e in ['none'] + effects.split()]
276 277 start = '\033[' + ';'.join(start) + 'm'
277 278 stop = '\033[' + str(_effects['none']) + 'm'
278 279 else:
279 280 start = ''.join(_effect_str(effect)
280 281 for effect in ['none'] + effects.split())
281 282 stop = _effect_str('none')
282 283 return ''.join([start, text, stop])
283 284
284 285 def extstyles():
285 286 for name, ext in extensions.extensions():
286 287 _styles.update(getattr(ext, 'colortable', {}))
287 288
288 289 def configstyles(ui):
289 290 for status, cfgeffects in ui.configitems('color'):
290 291 if '.' not in status or status.startswith('color.'):
291 292 continue
292 293 cfgeffects = ui.configlist('color', status)
293 294 if cfgeffects:
294 295 good = []
295 296 for e in cfgeffects:
296 297 if not _terminfo_params and e in _effects:
297 298 good.append(e)
298 299 elif e in _terminfo_params or e[:-11] in _terminfo_params:
299 300 good.append(e)
300 301 else:
301 302 ui.warn(_("ignoring unknown color/effect %r "
302 303 "(configured in color.%s)\n")
303 304 % (e, status))
304 305 _styles[status] = ' '.join(good)
305 306
306 307 class colorui(uimod.ui):
307 308 def popbuffer(self, labeled=False):
308 309 if labeled:
309 310 return ''.join(self.label(a, label) for a, label
310 311 in self._buffers.pop())
311 312 return ''.join(a for a, label in self._buffers.pop())
312 313
313 314 _colormode = 'ansi'
314 315 def write(self, *args, **opts):
315 316 label = opts.get('label', '')
316 317 if self._buffers:
317 318 self._buffers[-1].extend([(str(a), label) for a in args])
318 319 elif self._colormode == 'win32':
319 320 for a in args:
320 321 win32print(a, super(colorui, self).write, **opts)
321 322 else:
322 323 return super(colorui, self).write(
323 324 *[self.label(str(a), label) for a in args], **opts)
324 325
325 326 def write_err(self, *args, **opts):
326 327 label = opts.get('label', '')
327 328 if self._colormode == 'win32':
328 329 for a in args:
329 330 win32print(a, super(colorui, self).write_err, **opts)
330 331 else:
331 332 return super(colorui, self).write_err(
332 333 *[self.label(str(a), label) for a in args], **opts)
333 334
334 335 def label(self, msg, label):
335 336 effects = []
336 337 for l in label.split():
337 338 s = _styles.get(l, '')
338 339 if s:
339 340 effects.append(s)
340 341 effects = ' '.join(effects)
341 342 if effects:
342 343 return '\n'.join([render_effects(s, effects)
343 344 for s in msg.split('\n')])
344 345 return msg
345 346
346 347
347 348 def uisetup(ui):
348 349 global _terminfo_params
349 350 if ui.plain():
350 351 return
351 352 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
352 353 mode = _modesetup(ui_, opts)
353 354 if mode:
354 355 colorui._colormode = mode
355 356 if not issubclass(ui_.__class__, colorui):
356 357 colorui.__bases__ = (ui_.__class__,)
357 358 ui_.__class__ = colorui
358 359 extstyles()
359 360 configstyles(ui_)
360 361 return orig(ui_, opts, cmd, cmdfunc)
361 362 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
362 363
363 364 def extsetup(ui):
364 365 commands.globalopts.append(
365 366 ('', 'color', 'auto',
366 367 # i18n: 'always', 'auto', and 'never' are keywords and should
367 368 # not be translated
368 369 _("when to colorize (boolean, always, auto, or never)"),
369 370 _('TYPE')))
370 371
371 372 if os.name != 'nt':
372 373 w32effects = None
373 374 else:
374 375 import re, ctypes
375 376
376 377 _kernel32 = ctypes.windll.kernel32
377 378
378 379 _WORD = ctypes.c_ushort
379 380
380 381 _INVALID_HANDLE_VALUE = -1
381 382
382 383 class _COORD(ctypes.Structure):
383 384 _fields_ = [('X', ctypes.c_short),
384 385 ('Y', ctypes.c_short)]
385 386
386 387 class _SMALL_RECT(ctypes.Structure):
387 388 _fields_ = [('Left', ctypes.c_short),
388 389 ('Top', ctypes.c_short),
389 390 ('Right', ctypes.c_short),
390 391 ('Bottom', ctypes.c_short)]
391 392
392 393 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
393 394 _fields_ = [('dwSize', _COORD),
394 395 ('dwCursorPosition', _COORD),
395 396 ('wAttributes', _WORD),
396 397 ('srWindow', _SMALL_RECT),
397 398 ('dwMaximumWindowSize', _COORD)]
398 399
399 400 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
400 401 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
401 402
402 403 _FOREGROUND_BLUE = 0x0001
403 404 _FOREGROUND_GREEN = 0x0002
404 405 _FOREGROUND_RED = 0x0004
405 406 _FOREGROUND_INTENSITY = 0x0008
406 407
407 408 _BACKGROUND_BLUE = 0x0010
408 409 _BACKGROUND_GREEN = 0x0020
409 410 _BACKGROUND_RED = 0x0040
410 411 _BACKGROUND_INTENSITY = 0x0080
411 412
412 413 _COMMON_LVB_REVERSE_VIDEO = 0x4000
413 414 _COMMON_LVB_UNDERSCORE = 0x8000
414 415
415 416 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
416 417 w32effects = {
417 418 'none': -1,
418 419 'black': 0,
419 420 'red': _FOREGROUND_RED,
420 421 'green': _FOREGROUND_GREEN,
421 422 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
422 423 'blue': _FOREGROUND_BLUE,
423 424 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
424 425 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
425 426 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
426 427 'bold': _FOREGROUND_INTENSITY,
427 428 'black_background': 0x100, # unused value > 0x0f
428 429 'red_background': _BACKGROUND_RED,
429 430 'green_background': _BACKGROUND_GREEN,
430 431 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
431 432 'blue_background': _BACKGROUND_BLUE,
432 433 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
433 434 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
434 435 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
435 436 _BACKGROUND_BLUE),
436 437 'bold_background': _BACKGROUND_INTENSITY,
437 438 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
438 439 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
439 440 }
440 441
441 442 passthrough = set([_FOREGROUND_INTENSITY,
442 443 _BACKGROUND_INTENSITY,
443 444 _COMMON_LVB_UNDERSCORE,
444 445 _COMMON_LVB_REVERSE_VIDEO])
445 446
446 447 stdout = _kernel32.GetStdHandle(
447 448 _STD_OUTPUT_HANDLE) # don't close the handle returned
448 449 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
449 450 w32effects = None
450 451 else:
451 452 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
452 453 if not _kernel32.GetConsoleScreenBufferInfo(
453 454 stdout, ctypes.byref(csbi)):
454 455 # stdout may not support GetConsoleScreenBufferInfo()
455 456 # when called from subprocess or redirected
456 457 w32effects = None
457 458 else:
458 459 origattr = csbi.wAttributes
459 460 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
460 461 re.MULTILINE | re.DOTALL)
461 462
462 463 def win32print(text, orig, **opts):
463 464 label = opts.get('label', '')
464 465 attr = origattr
465 466
466 467 def mapcolor(val, attr):
467 468 if val == -1:
468 469 return origattr
469 470 elif val in passthrough:
470 471 return attr | val
471 472 elif val > 0x0f:
472 473 return (val & 0x70) | (attr & 0x8f)
473 474 else:
474 475 return (val & 0x07) | (attr & 0xf8)
475 476
476 477 # determine console attributes based on labels
477 478 for l in label.split():
478 479 style = _styles.get(l, '')
479 480 for effect in style.split():
480 481 attr = mapcolor(w32effects[effect], attr)
481 482
482 483 # hack to ensure regexp finds data
483 484 if not text.startswith('\033['):
484 485 text = '\033[m' + text
485 486
486 487 # Look for ANSI-like codes embedded in text
487 488 m = re.match(ansire, text)
488 489
489 490 try:
490 491 while m:
491 492 for sattr in m.group(1).split(';'):
492 493 if sattr:
493 494 attr = mapcolor(int(sattr), attr)
494 495 _kernel32.SetConsoleTextAttribute(stdout, attr)
495 496 orig(m.group(2), **opts)
496 497 m = re.match(ansire, m.group(3))
497 498 finally:
498 499 # Explicity reset original attributes
499 500 _kernel32.SetConsoleTextAttribute(stdout, origattr)
General Comments 0
You need to be logged in to leave comments. Login now