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