##// END OF EJS Templates
py3: handle keyword arguments correctly in color.py...
Pulkit Goyal -
r35352:a4478f74 default
parent child Browse files
Show More
@@ -1,529 +1,529
1 1 # utility for color output for Mercurial commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> and other
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 from __future__ import absolute_import
9 9
10 10 import re
11 11
12 12 from .i18n import _
13 13
14 14 from . import (
15 15 encoding,
16 16 pycompat,
17 17 util
18 18 )
19 19
20 20 try:
21 21 import curses
22 22 # Mapping from effect name to terminfo attribute name (or raw code) or
23 23 # color number. This will also force-load the curses module.
24 24 _baseterminfoparams = {
25 25 'none': (True, 'sgr0', ''),
26 26 'standout': (True, 'smso', ''),
27 27 'underline': (True, 'smul', ''),
28 28 'reverse': (True, 'rev', ''),
29 29 'inverse': (True, 'rev', ''),
30 30 'blink': (True, 'blink', ''),
31 31 'dim': (True, 'dim', ''),
32 32 'bold': (True, 'bold', ''),
33 33 'invisible': (True, 'invis', ''),
34 34 'italic': (True, 'sitm', ''),
35 35 'black': (False, curses.COLOR_BLACK, ''),
36 36 'red': (False, curses.COLOR_RED, ''),
37 37 'green': (False, curses.COLOR_GREEN, ''),
38 38 'yellow': (False, curses.COLOR_YELLOW, ''),
39 39 'blue': (False, curses.COLOR_BLUE, ''),
40 40 'magenta': (False, curses.COLOR_MAGENTA, ''),
41 41 'cyan': (False, curses.COLOR_CYAN, ''),
42 42 'white': (False, curses.COLOR_WHITE, ''),
43 43 }
44 44 except ImportError:
45 45 curses = None
46 46 _baseterminfoparams = {}
47 47
48 48 # start and stop parameters for effects
49 49 _effects = {
50 50 'none': 0,
51 51 'black': 30,
52 52 'red': 31,
53 53 'green': 32,
54 54 'yellow': 33,
55 55 'blue': 34,
56 56 'magenta': 35,
57 57 'cyan': 36,
58 58 'white': 37,
59 59 'bold': 1,
60 60 'italic': 3,
61 61 'underline': 4,
62 62 'inverse': 7,
63 63 'dim': 2,
64 64 'black_background': 40,
65 65 'red_background': 41,
66 66 'green_background': 42,
67 67 'yellow_background': 43,
68 68 'blue_background': 44,
69 69 'purple_background': 45,
70 70 'cyan_background': 46,
71 71 'white_background': 47,
72 72 }
73 73
74 74 _defaultstyles = {
75 75 'grep.match': 'red bold',
76 76 'grep.linenumber': 'green',
77 77 'grep.rev': 'green',
78 78 'grep.change': 'green',
79 79 'grep.sep': 'cyan',
80 80 'grep.filename': 'magenta',
81 81 'grep.user': 'magenta',
82 82 'grep.date': 'magenta',
83 83 'bookmarks.active': 'green',
84 84 'branches.active': 'none',
85 85 'branches.closed': 'black bold',
86 86 'branches.current': 'green',
87 87 'branches.inactive': 'none',
88 88 'diff.changed': 'white',
89 89 'diff.deleted': 'red',
90 90 'diff.deleted.highlight': 'red bold underline',
91 91 'diff.diffline': 'bold',
92 92 'diff.extended': 'cyan bold',
93 93 'diff.file_a': 'red bold',
94 94 'diff.file_b': 'green bold',
95 95 'diff.hunk': 'magenta',
96 96 'diff.inserted': 'green',
97 97 'diff.inserted.highlight': 'green bold underline',
98 98 'diff.tab': '',
99 99 'diff.trailingwhitespace': 'bold red_background',
100 100 'changeset.public': '',
101 101 'changeset.draft': '',
102 102 'changeset.secret': '',
103 103 'diffstat.deleted': 'red',
104 104 'diffstat.inserted': 'green',
105 105 'formatvariant.name.mismatchconfig': 'red',
106 106 'formatvariant.name.mismatchdefault': 'yellow',
107 107 'formatvariant.name.uptodate': 'green',
108 108 'formatvariant.repo.mismatchconfig': 'red',
109 109 'formatvariant.repo.mismatchdefault': 'yellow',
110 110 'formatvariant.repo.uptodate': 'green',
111 111 'formatvariant.config.special': 'yellow',
112 112 'formatvariant.config.default': 'green',
113 113 'formatvariant.default': '',
114 114 'histedit.remaining': 'red bold',
115 115 'ui.prompt': 'yellow',
116 116 'log.changeset': 'yellow',
117 117 'patchbomb.finalsummary': '',
118 118 'patchbomb.from': 'magenta',
119 119 'patchbomb.to': 'cyan',
120 120 'patchbomb.subject': 'green',
121 121 'patchbomb.diffstats': '',
122 122 'rebase.rebased': 'blue',
123 123 'rebase.remaining': 'red bold',
124 124 'resolve.resolved': 'green bold',
125 125 'resolve.unresolved': 'red bold',
126 126 'shelve.age': 'cyan',
127 127 'shelve.newest': 'green bold',
128 128 'shelve.name': 'blue bold',
129 129 'status.added': 'green bold',
130 130 'status.clean': 'none',
131 131 'status.copied': 'none',
132 132 'status.deleted': 'cyan bold underline',
133 133 'status.ignored': 'black bold',
134 134 'status.modified': 'blue bold',
135 135 'status.removed': 'red bold',
136 136 'status.unknown': 'magenta bold underline',
137 137 'tags.normal': 'green',
138 138 'tags.local': 'black bold',
139 139 }
140 140
141 141 def loadcolortable(ui, extname, colortable):
142 142 _defaultstyles.update(colortable)
143 143
144 144 def _terminfosetup(ui, mode, formatted):
145 145 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
146 146
147 147 # If we failed to load curses, we go ahead and return.
148 148 if curses is None:
149 149 return
150 150 # Otherwise, see what the config file says.
151 151 if mode not in ('auto', 'terminfo'):
152 152 return
153 153 ui._terminfoparams.update(_baseterminfoparams)
154 154
155 155 for key, val in ui.configitems('color'):
156 156 if key.startswith('color.'):
157 157 newval = (False, int(val), '')
158 158 ui._terminfoparams[key[6:]] = newval
159 159 elif key.startswith('terminfo.'):
160 160 newval = (True, '', val.replace('\\E', '\x1b'))
161 161 ui._terminfoparams[key[9:]] = newval
162 162 try:
163 163 curses.setupterm()
164 164 except curses.error as e:
165 165 ui._terminfoparams.clear()
166 166 return
167 167
168 168 for key, (b, e, c) in ui._terminfoparams.items():
169 169 if not b:
170 170 continue
171 171 if not c and not curses.tigetstr(e):
172 172 # Most terminals don't support dim, invis, etc, so don't be
173 173 # noisy and use ui.debug().
174 174 ui.debug("no terminfo entry for %s\n" % e)
175 175 del ui._terminfoparams[key]
176 176 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
177 177 # Only warn about missing terminfo entries if we explicitly asked for
178 178 # terminfo mode and we're in a formatted terminal.
179 179 if mode == "terminfo" and formatted:
180 180 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
181 181 "ECMA-48 color\n"))
182 182 ui._terminfoparams.clear()
183 183
184 184 def setup(ui):
185 185 """configure color on a ui
186 186
187 187 That function both set the colormode for the ui object and read
188 188 the configuration looking for custom colors and effect definitions."""
189 189 mode = _modesetup(ui)
190 190 ui._colormode = mode
191 191 if mode and mode != 'debug':
192 192 configstyles(ui)
193 193
194 194 def _modesetup(ui):
195 195 if ui.plain('color'):
196 196 return None
197 197 config = ui.config('ui', 'color')
198 198 if config == 'debug':
199 199 return 'debug'
200 200
201 201 auto = (config == 'auto')
202 202 always = False
203 203 if not auto and util.parsebool(config):
204 204 # We want the config to behave like a boolean, "on" is actually auto,
205 205 # but "always" value is treated as a special case to reduce confusion.
206 206 if ui.configsource('ui', 'color') == '--color' or config == 'always':
207 207 always = True
208 208 else:
209 209 auto = True
210 210
211 211 if not always and not auto:
212 212 return None
213 213
214 214 formatted = (always or (encoding.environ.get('TERM') != 'dumb'
215 215 and ui.formatted()))
216 216
217 217 mode = ui.config('color', 'mode')
218 218
219 219 # If pager is active, color.pagermode overrides color.mode.
220 220 if getattr(ui, 'pageractive', False):
221 221 mode = ui.config('color', 'pagermode', mode)
222 222
223 223 realmode = mode
224 224 if pycompat.iswindows:
225 225 from . import win32
226 226
227 227 term = encoding.environ.get('TERM')
228 228 # TERM won't be defined in a vanilla cmd.exe environment.
229 229
230 230 # UNIX-like environments on Windows such as Cygwin and MSYS will
231 231 # set TERM. They appear to make a best effort attempt at setting it
232 232 # to something appropriate. However, not all environments with TERM
233 233 # defined support ANSI.
234 234 ansienviron = term and 'xterm' in term
235 235
236 236 if mode == 'auto':
237 237 # Since "ansi" could result in terminal gibberish, we error on the
238 238 # side of selecting "win32". However, if w32effects is not defined,
239 239 # we almost certainly don't support "win32", so don't even try.
240 240 # w32ffects is not populated when stdout is redirected, so checking
241 241 # it first avoids win32 calls in a state known to error out.
242 242 if ansienviron or not w32effects or win32.enablevtmode():
243 243 realmode = 'ansi'
244 244 else:
245 245 realmode = 'win32'
246 246 # An empty w32effects is a clue that stdout is redirected, and thus
247 247 # cannot enable VT mode.
248 248 elif mode == 'ansi' and w32effects and not ansienviron:
249 249 win32.enablevtmode()
250 250 elif mode == 'auto':
251 251 realmode = 'ansi'
252 252
253 253 def modewarn():
254 254 # only warn if color.mode was explicitly set and we're in
255 255 # a formatted terminal
256 256 if mode == realmode and formatted:
257 257 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
258 258
259 259 if realmode == 'win32':
260 260 ui._terminfoparams.clear()
261 261 if not w32effects:
262 262 modewarn()
263 263 return None
264 264 elif realmode == 'ansi':
265 265 ui._terminfoparams.clear()
266 266 elif realmode == 'terminfo':
267 267 _terminfosetup(ui, mode, formatted)
268 268 if not ui._terminfoparams:
269 269 ## FIXME Shouldn't we return None in this case too?
270 270 modewarn()
271 271 realmode = 'ansi'
272 272 else:
273 273 return None
274 274
275 275 if always or (auto and formatted):
276 276 return realmode
277 277 return None
278 278
279 279 def configstyles(ui):
280 280 ui._styles.update(_defaultstyles)
281 281 for status, cfgeffects in ui.configitems('color'):
282 282 if '.' not in status or status.startswith(('color.', 'terminfo.')):
283 283 continue
284 284 cfgeffects = ui.configlist('color', status)
285 285 if cfgeffects:
286 286 good = []
287 287 for e in cfgeffects:
288 288 if valideffect(ui, e):
289 289 good.append(e)
290 290 else:
291 291 ui.warn(_("ignoring unknown color/effect %r "
292 292 "(configured in color.%s)\n")
293 293 % (e, status))
294 294 ui._styles[status] = ' '.join(good)
295 295
296 296 def _activeeffects(ui):
297 297 '''Return the effects map for the color mode set on the ui.'''
298 298 if ui._colormode == 'win32':
299 299 return w32effects
300 300 elif ui._colormode is not None:
301 301 return _effects
302 302 return {}
303 303
304 304 def valideffect(ui, effect):
305 305 'Determine if the effect is valid or not.'
306 306 return ((not ui._terminfoparams and effect in _activeeffects(ui))
307 307 or (effect in ui._terminfoparams
308 308 or effect[:-11] in ui._terminfoparams))
309 309
310 310 def _effect_str(ui, effect):
311 311 '''Helper function for render_effects().'''
312 312
313 313 bg = False
314 314 if effect.endswith('_background'):
315 315 bg = True
316 316 effect = effect[:-11]
317 317 try:
318 318 attr, val, termcode = ui._terminfoparams[effect]
319 319 except KeyError:
320 320 return ''
321 321 if attr:
322 322 if termcode:
323 323 return termcode
324 324 else:
325 325 return curses.tigetstr(val)
326 326 elif bg:
327 327 return curses.tparm(curses.tigetstr('setab'), val)
328 328 else:
329 329 return curses.tparm(curses.tigetstr('setaf'), val)
330 330
331 331 def _mergeeffects(text, start, stop):
332 332 """Insert start sequence at every occurrence of stop sequence
333 333
334 334 >>> s = _mergeeffects(b'cyan', b'[C]', b'|')
335 335 >>> s = _mergeeffects(s + b'yellow', b'[Y]', b'|')
336 336 >>> s = _mergeeffects(b'ma' + s + b'genta', b'[M]', b'|')
337 337 >>> s = _mergeeffects(b'red' + s, b'[R]', b'|')
338 338 >>> s
339 339 '[R]red[M]ma[Y][C]cyan|[R][M][Y]yellow|[R][M]genta|'
340 340 """
341 341 parts = []
342 342 for t in text.split(stop):
343 343 if not t:
344 344 continue
345 345 parts.extend([start, t, stop])
346 346 return ''.join(parts)
347 347
348 348 def _render_effects(ui, text, effects):
349 349 'Wrap text in commands to turn on each effect.'
350 350 if not text:
351 351 return text
352 352 if ui._terminfoparams:
353 353 start = ''.join(_effect_str(ui, effect)
354 354 for effect in ['none'] + effects.split())
355 355 stop = _effect_str(ui, 'none')
356 356 else:
357 357 activeeffects = _activeeffects(ui)
358 358 start = [pycompat.bytestr(activeeffects[e])
359 359 for e in ['none'] + effects.split()]
360 360 start = '\033[' + ';'.join(start) + 'm'
361 361 stop = '\033[' + pycompat.bytestr(activeeffects['none']) + 'm'
362 362 return _mergeeffects(text, start, stop)
363 363
364 364 _ansieffectre = re.compile(br'\x1b\[[0-9;]*m')
365 365
366 366 def stripeffects(text):
367 367 """Strip ANSI control codes which could be inserted by colorlabel()"""
368 368 return _ansieffectre.sub('', text)
369 369
370 370 def colorlabel(ui, msg, label):
371 371 """add color control code according to the mode"""
372 372 if ui._colormode == 'debug':
373 373 if label and msg:
374 374 if msg[-1] == '\n':
375 375 msg = "[%s|%s]\n" % (label, msg[:-1])
376 376 else:
377 377 msg = "[%s|%s]" % (label, msg)
378 378 elif ui._colormode is not None:
379 379 effects = []
380 380 for l in label.split():
381 381 s = ui._styles.get(l, '')
382 382 if s:
383 383 effects.append(s)
384 384 elif valideffect(ui, l):
385 385 effects.append(l)
386 386 effects = ' '.join(effects)
387 387 if effects:
388 388 msg = '\n'.join([_render_effects(ui, line, effects)
389 389 for line in msg.split('\n')])
390 390 return msg
391 391
392 392 w32effects = None
393 393 if pycompat.iswindows:
394 394 import ctypes
395 395
396 396 _kernel32 = ctypes.windll.kernel32
397 397
398 398 _WORD = ctypes.c_ushort
399 399
400 400 _INVALID_HANDLE_VALUE = -1
401 401
402 402 class _COORD(ctypes.Structure):
403 403 _fields_ = [('X', ctypes.c_short),
404 404 ('Y', ctypes.c_short)]
405 405
406 406 class _SMALL_RECT(ctypes.Structure):
407 407 _fields_ = [('Left', ctypes.c_short),
408 408 ('Top', ctypes.c_short),
409 409 ('Right', ctypes.c_short),
410 410 ('Bottom', ctypes.c_short)]
411 411
412 412 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
413 413 _fields_ = [('dwSize', _COORD),
414 414 ('dwCursorPosition', _COORD),
415 415 ('wAttributes', _WORD),
416 416 ('srWindow', _SMALL_RECT),
417 417 ('dwMaximumWindowSize', _COORD)]
418 418
419 419 _STD_OUTPUT_HANDLE = 0xfffffff5 # (DWORD)-11
420 420 _STD_ERROR_HANDLE = 0xfffffff4 # (DWORD)-12
421 421
422 422 _FOREGROUND_BLUE = 0x0001
423 423 _FOREGROUND_GREEN = 0x0002
424 424 _FOREGROUND_RED = 0x0004
425 425 _FOREGROUND_INTENSITY = 0x0008
426 426
427 427 _BACKGROUND_BLUE = 0x0010
428 428 _BACKGROUND_GREEN = 0x0020
429 429 _BACKGROUND_RED = 0x0040
430 430 _BACKGROUND_INTENSITY = 0x0080
431 431
432 432 _COMMON_LVB_REVERSE_VIDEO = 0x4000
433 433 _COMMON_LVB_UNDERSCORE = 0x8000
434 434
435 435 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
436 436 w32effects = {
437 437 'none': -1,
438 438 'black': 0,
439 439 'red': _FOREGROUND_RED,
440 440 'green': _FOREGROUND_GREEN,
441 441 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
442 442 'blue': _FOREGROUND_BLUE,
443 443 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
444 444 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
445 445 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
446 446 'bold': _FOREGROUND_INTENSITY,
447 447 'black_background': 0x100, # unused value > 0x0f
448 448 'red_background': _BACKGROUND_RED,
449 449 'green_background': _BACKGROUND_GREEN,
450 450 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
451 451 'blue_background': _BACKGROUND_BLUE,
452 452 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
453 453 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
454 454 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
455 455 _BACKGROUND_BLUE),
456 456 'bold_background': _BACKGROUND_INTENSITY,
457 457 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
458 458 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
459 459 }
460 460
461 461 passthrough = {_FOREGROUND_INTENSITY,
462 462 _BACKGROUND_INTENSITY,
463 463 _COMMON_LVB_UNDERSCORE,
464 464 _COMMON_LVB_REVERSE_VIDEO}
465 465
466 466 stdout = _kernel32.GetStdHandle(
467 467 _STD_OUTPUT_HANDLE) # don't close the handle returned
468 468 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
469 469 w32effects = None
470 470 else:
471 471 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
472 472 if not _kernel32.GetConsoleScreenBufferInfo(
473 473 stdout, ctypes.byref(csbi)):
474 474 # stdout may not support GetConsoleScreenBufferInfo()
475 475 # when called from subprocess or redirected
476 476 w32effects = None
477 477 else:
478 478 origattr = csbi.wAttributes
479 479 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
480 480 re.MULTILINE | re.DOTALL)
481 481
482 482 def win32print(ui, writefunc, *msgs, **opts):
483 483 for text in msgs:
484 484 _win32print(ui, text, writefunc, **opts)
485 485
486 486 def _win32print(ui, text, writefunc, **opts):
487 label = opts.get('label', '')
487 label = opts.get(r'label', '')
488 488 attr = origattr
489 489
490 490 def mapcolor(val, attr):
491 491 if val == -1:
492 492 return origattr
493 493 elif val in passthrough:
494 494 return attr | val
495 495 elif val > 0x0f:
496 496 return (val & 0x70) | (attr & 0x8f)
497 497 else:
498 498 return (val & 0x07) | (attr & 0xf8)
499 499
500 500 # determine console attributes based on labels
501 501 for l in label.split():
502 502 style = ui._styles.get(l, '')
503 503 for effect in style.split():
504 504 try:
505 505 attr = mapcolor(w32effects[effect], attr)
506 506 except KeyError:
507 507 # w32effects could not have certain attributes so we skip
508 508 # them if not found
509 509 pass
510 510 # hack to ensure regexp finds data
511 511 if not text.startswith('\033['):
512 512 text = '\033[m' + text
513 513
514 514 # Look for ANSI-like codes embedded in text
515 515 m = re.match(ansire, text)
516 516
517 517 try:
518 518 while m:
519 519 for sattr in m.group(1).split(';'):
520 520 if sattr:
521 521 attr = mapcolor(int(sattr), attr)
522 522 ui.flush()
523 523 _kernel32.SetConsoleTextAttribute(stdout, attr)
524 524 writefunc(m.group(2), **opts)
525 525 m = re.match(ansire, m.group(3))
526 526 finally:
527 527 # Explicitly reset original attributes
528 528 ui.flush()
529 529 _kernel32.SetConsoleTextAttribute(stdout, origattr)
General Comments 0
You need to be logged in to leave comments. Login now