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