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