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