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