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