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