##// END OF EJS Templates
color: minor simplification of some terminfo setup code...
Pierre-Yves David -
r31069:cf2bc379 default
parent child Browse files
Show More
@@ -1,434 +1,435
1 1 # color.py color output for Mercurial 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 The color extension colorizes output from several Mercurial commands.
11 11 For example, the diff command shows additions in green and deletions
12 12 in red, while the status command shows modified files in magenta. Many
13 13 other commands have analogous colors. It is possible to customize
14 14 these colors.
15 15
16 16 Effects
17 17 -------
18 18
19 19 Other effects in addition to color, like bold and underlined text, are
20 20 also available. By default, the terminfo database is used to find the
21 21 terminal codes used to change color and effect. If terminfo is not
22 22 available, then effects are rendered with the ECMA-48 SGR control
23 23 function (aka ANSI escape codes).
24 24
25 25 The available effects in terminfo mode are 'blink', 'bold', 'dim',
26 26 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
27 27 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
28 28 'underline'. How each is rendered depends on the terminal emulator.
29 29 Some may not be available for a given terminal type, and will be
30 30 silently ignored.
31 31
32 32 If the terminfo entry for your terminal is missing codes for an effect
33 33 or has the wrong codes, you can add or override those codes in your
34 34 configuration::
35 35
36 36 [color]
37 37 terminfo.dim = \E[2m
38 38
39 39 where '\E' is substituted with an escape character.
40 40
41 41 Labels
42 42 ------
43 43
44 44 Text receives color effects depending on the labels that it has. Many
45 45 default Mercurial commands emit labelled text. You can also define
46 46 your own labels in templates using the label function, see :hg:`help
47 47 templates`. A single portion of text may have more than one label. In
48 48 that case, effects given to the last label will override any other
49 49 effects. This includes the special "none" effect, which nullifies
50 50 other effects.
51 51
52 52 Labels are normally invisible. In order to see these labels and their
53 53 position in the text, use the global --color=debug option. The same
54 54 anchor text may be associated to multiple labels, e.g.
55 55
56 56 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
57 57
58 58 The following are the default effects for some default labels. Default
59 59 effects may be overridden from your configuration file::
60 60
61 61 [color]
62 62 status.modified = blue bold underline red_background
63 63 status.added = green bold
64 64 status.removed = red bold blue_background
65 65 status.deleted = cyan bold underline
66 66 status.unknown = magenta bold underline
67 67 status.ignored = black bold
68 68
69 69 # 'none' turns off all effects
70 70 status.clean = none
71 71 status.copied = none
72 72
73 73 qseries.applied = blue bold underline
74 74 qseries.unapplied = black bold
75 75 qseries.missing = red bold
76 76
77 77 diff.diffline = bold
78 78 diff.extended = cyan bold
79 79 diff.file_a = red bold
80 80 diff.file_b = green bold
81 81 diff.hunk = magenta
82 82 diff.deleted = red
83 83 diff.inserted = green
84 84 diff.changed = white
85 85 diff.tab =
86 86 diff.trailingwhitespace = bold red_background
87 87
88 88 # Blank so it inherits the style of the surrounding label
89 89 changeset.public =
90 90 changeset.draft =
91 91 changeset.secret =
92 92
93 93 resolve.unresolved = red bold
94 94 resolve.resolved = green bold
95 95
96 96 bookmarks.active = green
97 97
98 98 branches.active = none
99 99 branches.closed = black bold
100 100 branches.current = green
101 101 branches.inactive = none
102 102
103 103 tags.normal = green
104 104 tags.local = black bold
105 105
106 106 rebase.rebased = blue
107 107 rebase.remaining = red bold
108 108
109 109 shelve.age = cyan
110 110 shelve.newest = green bold
111 111 shelve.name = blue bold
112 112
113 113 histedit.remaining = red bold
114 114
115 115 Custom colors
116 116 -------------
117 117
118 118 Because there are only eight standard colors, this module allows you
119 119 to define color names for other color slots which might be available
120 120 for your terminal type, assuming terminfo mode. For instance::
121 121
122 122 color.brightblue = 12
123 123 color.pink = 207
124 124 color.orange = 202
125 125
126 126 to set 'brightblue' to color slot 12 (useful for 16 color terminals
127 127 that have brighter colors defined in the upper eight) and, 'pink' and
128 128 'orange' to colors in 256-color xterm's default color cube. These
129 129 defined colors may then be used as any of the pre-defined eight,
130 130 including appending '_background' to set the background to that color.
131 131
132 132 Modes
133 133 -----
134 134
135 135 By default, the color extension will use ANSI mode (or win32 mode on
136 136 Windows) if it detects a terminal. To override auto mode (to enable
137 137 terminfo mode, for example), set the following configuration option::
138 138
139 139 [color]
140 140 mode = terminfo
141 141
142 142 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
143 143 disable color.
144 144
145 145 Note that on some systems, terminfo mode may cause problems when using
146 146 color with the pager extension and less -R. less with the -R option
147 147 will only display ECMA-48 color codes, and terminfo mode may sometimes
148 148 emit codes that less doesn't understand. You can work around this by
149 149 either using ansi mode (or auto mode), or by using less -r (which will
150 150 pass through all terminal control codes, not just color control
151 151 codes).
152 152
153 153 On some systems (such as MSYS in Windows), the terminal may support
154 154 a different color mode than the pager (activated via the "pager"
155 155 extension). It is possible to define separate modes depending on whether
156 156 the pager is active::
157 157
158 158 [color]
159 159 mode = auto
160 160 pagermode = ansi
161 161
162 162 If ``pagermode`` is not defined, the ``mode`` will be used.
163 163 '''
164 164
165 165 from __future__ import absolute_import
166 166
167 167 try:
168 168 import curses
169 169 curses.COLOR_BLACK # force import
170 170 except ImportError:
171 171 curses = None
172 172
173 173 from mercurial.i18n import _
174 174 from mercurial import (
175 175 cmdutil,
176 176 color,
177 177 commands,
178 178 dispatch,
179 179 encoding,
180 180 extensions,
181 181 pycompat,
182 182 subrepo,
183 183 ui as uimod,
184 184 util,
185 185 )
186 186
187 187 cmdtable = {}
188 188 command = cmdutil.command(cmdtable)
189 189 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
190 190 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
191 191 # be specifying the version(s) of Mercurial they are tested with, or
192 192 # leave the attribute unspecified.
193 193 testedwith = 'ships-with-hg-core'
194 194
195 195 def _terminfosetup(ui, mode):
196 196 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
197 197
198 198 # If we failed to load curses, we go ahead and return.
199 199 if curses is None:
200 200 return
201 201 # Otherwise, see what the config file says.
202 202 if mode not in ('auto', 'terminfo'):
203 203 return
204 204
205 color._terminfo_params.update((key[6:], (False, int(val), ''))
206 for key, val in ui.configitems('color')
207 if key.startswith('color.'))
208 color._terminfo_params.update((key[9:],
209 (True, '', val.replace('\\E', '\x1b')))
210 for key, val in ui.configitems('color')
211 if key.startswith('terminfo.'))
205 for key, val in ui.configitems('color'):
206 if key.startswith('color.'):
207 newval = (False, int(val), '')
208 color._terminfo_params[key[6:]] = newval
212 209
210 for key, val in ui.configitems('color'):
211 if key.startswith('terminfo.'):
212 newval = (True, '', val.replace('\\E', '\x1b'))
213 color._terminfo_params[key[9:]] = newval
213 214 try:
214 215 curses.setupterm()
215 216 except curses.error as e:
216 217 color._terminfo_params.clear()
217 218 return
218 219
219 220 for key, (b, e, c) in color._terminfo_params.items():
220 221 if not b:
221 222 continue
222 223 if not c and not curses.tigetstr(e):
223 224 # Most terminals don't support dim, invis, etc, so don't be
224 225 # noisy and use ui.debug().
225 226 ui.debug("no terminfo entry for %s\n" % e)
226 227 del color._terminfo_params[key]
227 228 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
228 229 # Only warn about missing terminfo entries if we explicitly asked for
229 230 # terminfo mode.
230 231 if mode == "terminfo":
231 232 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
232 233 "ECMA-48 color\n"))
233 234 color._terminfo_params.clear()
234 235
235 236 def _modesetup(ui, coloropt):
236 237 if coloropt == 'debug':
237 238 return 'debug'
238 239
239 240 auto = (coloropt == 'auto')
240 241 always = not auto and util.parsebool(coloropt)
241 242 if not always and not auto:
242 243 return None
243 244
244 245 formatted = (always or (encoding.environ.get('TERM') != 'dumb'
245 246 and ui.formatted()))
246 247
247 248 mode = ui.config('color', 'mode', 'auto')
248 249
249 250 # If pager is active, color.pagermode overrides color.mode.
250 251 if getattr(ui, 'pageractive', False):
251 252 mode = ui.config('color', 'pagermode', mode)
252 253
253 254 realmode = mode
254 255 if mode == 'auto':
255 256 if pycompat.osname == 'nt':
256 257 term = encoding.environ.get('TERM')
257 258 # TERM won't be defined in a vanilla cmd.exe environment.
258 259
259 260 # UNIX-like environments on Windows such as Cygwin and MSYS will
260 261 # set TERM. They appear to make a best effort attempt at setting it
261 262 # to something appropriate. However, not all environments with TERM
262 263 # defined support ANSI. Since "ansi" could result in terminal
263 264 # gibberish, we error on the side of selecting "win32". However, if
264 265 # w32effects is not defined, we almost certainly don't support
265 266 # "win32", so don't even try.
266 267 if (term and 'xterm' in term) or not color.w32effects:
267 268 realmode = 'ansi'
268 269 else:
269 270 realmode = 'win32'
270 271 else:
271 272 realmode = 'ansi'
272 273
273 274 def modewarn():
274 275 # only warn if color.mode was explicitly set and we're in
275 276 # a formatted terminal
276 277 if mode == realmode and ui.formatted():
277 278 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
278 279
279 280 if realmode == 'win32':
280 281 color._terminfo_params.clear()
281 282 if not color.w32effects:
282 283 modewarn()
283 284 return None
284 285 color._effects.update(color.w32effects)
285 286 elif realmode == 'ansi':
286 287 color._terminfo_params.clear()
287 288 elif realmode == 'terminfo':
288 289 _terminfosetup(ui, mode)
289 290 if not color._terminfo_params:
290 291 ## FIXME Shouldn't we return None in this case too?
291 292 modewarn()
292 293 realmode = 'ansi'
293 294 else:
294 295 return None
295 296
296 297 if always or (auto and formatted):
297 298 return realmode
298 299 return None
299 300
300 301 class colorui(uimod.ui):
301 302 _colormode = 'ansi'
302 303 def write(self, *args, **opts):
303 304 if self._colormode is None:
304 305 return super(colorui, self).write(*args, **opts)
305 306
306 307 label = opts.get('label', '')
307 308 if self._buffers and not opts.get('prompt', False):
308 309 if self._bufferapplylabels:
309 310 self._buffers[-1].extend(self.label(a, label) for a in args)
310 311 else:
311 312 self._buffers[-1].extend(args)
312 313 elif self._colormode == 'win32':
313 314 for a in args:
314 315 color.win32print(a, super(colorui, self).write, **opts)
315 316 else:
316 317 return super(colorui, self).write(
317 318 *[self.label(a, label) for a in args], **opts)
318 319
319 320 def write_err(self, *args, **opts):
320 321 if self._colormode is None:
321 322 return super(colorui, self).write_err(*args, **opts)
322 323
323 324 label = opts.get('label', '')
324 325 if self._bufferstates and self._bufferstates[-1][0]:
325 326 return self.write(*args, **opts)
326 327 if self._colormode == 'win32':
327 328 for a in args:
328 329 color.win32print(a, super(colorui, self).write_err, **opts)
329 330 else:
330 331 return super(colorui, self).write_err(
331 332 *[self.label(a, label) for a in args], **opts)
332 333
333 334 def showlabel(self, msg, label):
334 335 if label and msg:
335 336 if msg[-1] == '\n':
336 337 return "[%s|%s]\n" % (label, msg[:-1])
337 338 else:
338 339 return "[%s|%s]" % (label, msg)
339 340 else:
340 341 return msg
341 342
342 343 def label(self, msg, label):
343 344 if self._colormode is None:
344 345 return super(colorui, self).label(msg, label)
345 346
346 347 if self._colormode == 'debug':
347 348 return self.showlabel(msg, label)
348 349
349 350 effects = []
350 351 for l in label.split():
351 352 s = color._styles.get(l, '')
352 353 if s:
353 354 effects.append(s)
354 355 elif color.valideffect(l):
355 356 effects.append(l)
356 357 effects = ' '.join(effects)
357 358 if effects:
358 359 return '\n'.join([color._render_effects(line, effects)
359 360 for line in msg.split('\n')])
360 361 return msg
361 362
362 363 def uisetup(ui):
363 364 if ui.plain():
364 365 return
365 366 if not isinstance(ui, colorui):
366 367 colorui.__bases__ = (ui.__class__,)
367 368 ui.__class__ = colorui
368 369 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
369 370 mode = _modesetup(ui_, opts['color'])
370 371 colorui._colormode = mode
371 372 if mode and mode != 'debug':
372 373 color.configstyles(ui_)
373 374 return orig(ui_, opts, cmd, cmdfunc)
374 375 def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None):
375 376 if gitsub.ui._colormode and len(commands) and commands[0] == "diff":
376 377 # insert the argument in the front,
377 378 # the end of git diff arguments is used for paths
378 379 commands.insert(1, '--color')
379 380 return orig(gitsub, commands, env, stream, cwd)
380 381 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
381 382 extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
382 383
383 384 def extsetup(ui):
384 385 commands.globalopts.append(
385 386 ('', 'color', 'auto',
386 387 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
387 388 # and should not be translated
388 389 _("when to colorize (boolean, always, auto, never, or debug)"),
389 390 _('TYPE')))
390 391
391 392 @command('debugcolor',
392 393 [('', 'style', None, _('show all configured styles'))],
393 394 'hg debugcolor')
394 395 def debugcolor(ui, repo, **opts):
395 396 """show available color, effects or style"""
396 397 ui.write(('color mode: %s\n') % ui._colormode)
397 398 if opts.get('style'):
398 399 return _debugdisplaystyle(ui)
399 400 else:
400 401 return _debugdisplaycolor(ui)
401 402
402 403 def _debugdisplaycolor(ui):
403 404 oldstyle = color._styles.copy()
404 405 try:
405 406 color._styles.clear()
406 407 for effect in color._effects.keys():
407 408 color._styles[effect] = effect
408 409 if color._terminfo_params:
409 410 for k, v in ui.configitems('color'):
410 411 if k.startswith('color.'):
411 412 color._styles[k] = k[6:]
412 413 elif k.startswith('terminfo.'):
413 414 color._styles[k] = k[9:]
414 415 ui.write(_('available colors:\n'))
415 416 # sort label with a '_' after the other to group '_background' entry.
416 417 items = sorted(color._styles.items(),
417 418 key=lambda i: ('_' in i[0], i[0], i[1]))
418 419 for colorname, label in items:
419 420 ui.write(('%s\n') % colorname, label=label)
420 421 finally:
421 422 color._styles.clear()
422 423 color._styles.update(oldstyle)
423 424
424 425 def _debugdisplaystyle(ui):
425 426 ui.write(_('available style:\n'))
426 427 width = max(len(s) for s in color._styles)
427 428 for label, effects in sorted(color._styles.items()):
428 429 ui.write('%s' % label, label=label)
429 430 if effects:
430 431 # 50
431 432 ui.write(': ')
432 433 ui.write(' ' * (max(0, width - len(label))))
433 434 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
434 435 ui.write('\n')
General Comments 0
You need to be logged in to leave comments. Login now