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