/plugins/hg4idea/testData/bin/hgext/color.py

http://github.com/JetBrains/intellij-community · Python · 545 lines · 505 code · 9 blank · 31 comment · 5 complexity · 71802a66b1827222216eb22bf0e94f5d MD5 · raw file

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