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