##// END OF EJS Templates
color: fix TypeError with auto mode on win32 when colors aren't available (issue2871)...
Brodie Rao -
r14768:55db12e5 stable
parent child Browse files
Show More
@@ -1,499 +1,500
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, mode):
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 if mode not in ('auto', 'terminfo'):
125 if mode not in ('auto', 'terminfo'):
126 return
126 return
127
127
128 _terminfo_params.update((key[6:], (False, int(val)))
128 _terminfo_params.update((key[6:], (False, int(val)))
129 for key, val in ui.configitems('color')
129 for key, val in ui.configitems('color')
130 if key.startswith('color.'))
130 if key.startswith('color.'))
131
131
132 try:
132 try:
133 curses.setupterm()
133 curses.setupterm()
134 except curses.error, e:
134 except curses.error, e:
135 _terminfo_params = {}
135 _terminfo_params = {}
136 return
136 return
137
137
138 for key, (b, e) in _terminfo_params.items():
138 for key, (b, e) in _terminfo_params.items():
139 if not b:
139 if not b:
140 continue
140 continue
141 if not curses.tigetstr(e):
141 if not curses.tigetstr(e):
142 # 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
143 # noisy and use ui.debug().
143 # noisy and use ui.debug().
144 ui.debug("no terminfo entry for %s\n" % e)
144 ui.debug("no terminfo entry for %s\n" % e)
145 del _terminfo_params[key]
145 del _terminfo_params[key]
146 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
146 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
147 # Only warn about missing terminfo entries if we explicitly asked for
147 # Only warn about missing terminfo entries if we explicitly asked for
148 # terminfo mode.
148 # terminfo mode.
149 if mode == "terminfo":
149 if mode == "terminfo":
150 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
150 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
151 "ECMA-48 color\n"))
151 "ECMA-48 color\n"))
152 _terminfo_params = {}
152 _terminfo_params = {}
153
153
154 def _modesetup(ui, opts):
154 def _modesetup(ui, opts):
155 global _terminfo_params
155 global _terminfo_params
156
156
157 coloropt = opts['color']
157 coloropt = opts['color']
158 auto = coloropt == 'auto'
158 auto = coloropt == 'auto'
159 always = not auto and util.parsebool(coloropt)
159 always = not auto and util.parsebool(coloropt)
160 if not always and not auto:
160 if not always and not auto:
161 return None
161 return None
162
162
163 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
163 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
164
164
165 mode = ui.config('color', 'mode', 'auto')
165 mode = ui.config('color', 'mode', 'auto')
166 realmode = mode
166 realmode = mode
167 if mode == 'auto':
167 if mode == 'auto':
168 if os.name == 'nt' and 'TERM' not in os.environ:
168 if os.name == 'nt' and 'TERM' not in os.environ:
169 # looks line a cmd.exe console, use win32 API or nothing
169 # looks line a cmd.exe console, use win32 API or nothing
170 realmode = 'win32'
170 realmode = 'win32'
171 elif not formatted:
171 elif not formatted:
172 realmode = 'ansi'
172 realmode = 'ansi'
173 else:
173 else:
174 realmode = 'terminfo'
174 realmode = 'terminfo'
175
175
176 if realmode == 'win32':
176 if realmode == 'win32':
177 if not w32effects and mode == 'win32':
177 if not w32effects:
178 # only warn if color.mode is explicitly set to win32
178 if mode == 'win32':
179 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
179 # only warn if color.mode is explicitly set to win32
180 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
180 return None
181 return None
181 _effects.update(w32effects)
182 _effects.update(w32effects)
182 elif realmode == 'ansi':
183 elif realmode == 'ansi':
183 _terminfo_params = {}
184 _terminfo_params = {}
184 elif realmode == 'terminfo':
185 elif realmode == 'terminfo':
185 _terminfosetup(ui, mode)
186 _terminfosetup(ui, mode)
186 if not _terminfo_params:
187 if not _terminfo_params:
187 if mode == 'terminfo':
188 if mode == 'terminfo':
188 ## FIXME Shouldn't we return None in this case too?
189 ## FIXME Shouldn't we return None in this case too?
189 # only warn if color.mode is explicitly set to win32
190 # only warn if color.mode is explicitly set to win32
190 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
191 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
191 realmode = 'ansi'
192 realmode = 'ansi'
192 else:
193 else:
193 return None
194 return None
194
195
195 if always or (auto and formatted):
196 if always or (auto and formatted):
196 return realmode
197 return realmode
197 return None
198 return None
198
199
199 try:
200 try:
200 import curses
201 import curses
201 # Mapping from effect name to terminfo attribute name or color number.
202 # Mapping from effect name to terminfo attribute name or color number.
202 # This will also force-load the curses module.
203 # This will also force-load the curses module.
203 _terminfo_params = {'none': (True, 'sgr0'),
204 _terminfo_params = {'none': (True, 'sgr0'),
204 'standout': (True, 'smso'),
205 'standout': (True, 'smso'),
205 'underline': (True, 'smul'),
206 'underline': (True, 'smul'),
206 'reverse': (True, 'rev'),
207 'reverse': (True, 'rev'),
207 'inverse': (True, 'rev'),
208 'inverse': (True, 'rev'),
208 'blink': (True, 'blink'),
209 'blink': (True, 'blink'),
209 'dim': (True, 'dim'),
210 'dim': (True, 'dim'),
210 'bold': (True, 'bold'),
211 'bold': (True, 'bold'),
211 'invisible': (True, 'invis'),
212 'invisible': (True, 'invis'),
212 'italic': (True, 'sitm'),
213 'italic': (True, 'sitm'),
213 'black': (False, curses.COLOR_BLACK),
214 'black': (False, curses.COLOR_BLACK),
214 'red': (False, curses.COLOR_RED),
215 'red': (False, curses.COLOR_RED),
215 'green': (False, curses.COLOR_GREEN),
216 'green': (False, curses.COLOR_GREEN),
216 'yellow': (False, curses.COLOR_YELLOW),
217 'yellow': (False, curses.COLOR_YELLOW),
217 'blue': (False, curses.COLOR_BLUE),
218 'blue': (False, curses.COLOR_BLUE),
218 'magenta': (False, curses.COLOR_MAGENTA),
219 'magenta': (False, curses.COLOR_MAGENTA),
219 'cyan': (False, curses.COLOR_CYAN),
220 'cyan': (False, curses.COLOR_CYAN),
220 'white': (False, curses.COLOR_WHITE)}
221 'white': (False, curses.COLOR_WHITE)}
221 except ImportError:
222 except ImportError:
222 _terminfo_params = False
223 _terminfo_params = False
223
224
224 _styles = {'grep.match': 'red bold',
225 _styles = {'grep.match': 'red bold',
225 'bookmarks.current': 'green',
226 'bookmarks.current': 'green',
226 'branches.active': 'none',
227 'branches.active': 'none',
227 'branches.closed': 'black bold',
228 'branches.closed': 'black bold',
228 'branches.current': 'green',
229 'branches.current': 'green',
229 'branches.inactive': 'none',
230 'branches.inactive': 'none',
230 'diff.changed': 'white',
231 'diff.changed': 'white',
231 'diff.deleted': 'red',
232 'diff.deleted': 'red',
232 'diff.diffline': 'bold',
233 'diff.diffline': 'bold',
233 'diff.extended': 'cyan bold',
234 'diff.extended': 'cyan bold',
234 'diff.file_a': 'red bold',
235 'diff.file_a': 'red bold',
235 'diff.file_b': 'green bold',
236 'diff.file_b': 'green bold',
236 'diff.hunk': 'magenta',
237 'diff.hunk': 'magenta',
237 'diff.inserted': 'green',
238 'diff.inserted': 'green',
238 'diff.trailingwhitespace': 'bold red_background',
239 'diff.trailingwhitespace': 'bold red_background',
239 'diffstat.deleted': 'red',
240 'diffstat.deleted': 'red',
240 'diffstat.inserted': 'green',
241 'diffstat.inserted': 'green',
241 'ui.prompt': 'yellow',
242 'ui.prompt': 'yellow',
242 'log.changeset': 'yellow',
243 'log.changeset': 'yellow',
243 'resolve.resolved': 'green bold',
244 'resolve.resolved': 'green bold',
244 'resolve.unresolved': 'red bold',
245 'resolve.unresolved': 'red bold',
245 'status.added': 'green bold',
246 'status.added': 'green bold',
246 'status.clean': 'none',
247 'status.clean': 'none',
247 'status.copied': 'none',
248 'status.copied': 'none',
248 'status.deleted': 'cyan bold underline',
249 'status.deleted': 'cyan bold underline',
249 'status.ignored': 'black bold',
250 'status.ignored': 'black bold',
250 'status.modified': 'blue bold',
251 'status.modified': 'blue bold',
251 'status.removed': 'red bold',
252 'status.removed': 'red bold',
252 'status.unknown': 'magenta bold underline'}
253 'status.unknown': 'magenta bold underline'}
253
254
254
255
255 def _effect_str(effect):
256 def _effect_str(effect):
256 '''Helper function for render_effects().'''
257 '''Helper function for render_effects().'''
257
258
258 bg = False
259 bg = False
259 if effect.endswith('_background'):
260 if effect.endswith('_background'):
260 bg = True
261 bg = True
261 effect = effect[:-11]
262 effect = effect[:-11]
262 attr, val = _terminfo_params[effect]
263 attr, val = _terminfo_params[effect]
263 if attr:
264 if attr:
264 return curses.tigetstr(val)
265 return curses.tigetstr(val)
265 elif bg:
266 elif bg:
266 return curses.tparm(curses.tigetstr('setab'), val)
267 return curses.tparm(curses.tigetstr('setab'), val)
267 else:
268 else:
268 return curses.tparm(curses.tigetstr('setaf'), val)
269 return curses.tparm(curses.tigetstr('setaf'), val)
269
270
270 def render_effects(text, effects):
271 def render_effects(text, effects):
271 'Wrap text in commands to turn on each effect.'
272 'Wrap text in commands to turn on each effect.'
272 if not text:
273 if not text:
273 return text
274 return text
274 if not _terminfo_params:
275 if not _terminfo_params:
275 start = [str(_effects[e]) for e in ['none'] + effects.split()]
276 start = [str(_effects[e]) for e in ['none'] + effects.split()]
276 start = '\033[' + ';'.join(start) + 'm'
277 start = '\033[' + ';'.join(start) + 'm'
277 stop = '\033[' + str(_effects['none']) + 'm'
278 stop = '\033[' + str(_effects['none']) + 'm'
278 else:
279 else:
279 start = ''.join(_effect_str(effect)
280 start = ''.join(_effect_str(effect)
280 for effect in ['none'] + effects.split())
281 for effect in ['none'] + effects.split())
281 stop = _effect_str('none')
282 stop = _effect_str('none')
282 return ''.join([start, text, stop])
283 return ''.join([start, text, stop])
283
284
284 def extstyles():
285 def extstyles():
285 for name, ext in extensions.extensions():
286 for name, ext in extensions.extensions():
286 _styles.update(getattr(ext, 'colortable', {}))
287 _styles.update(getattr(ext, 'colortable', {}))
287
288
288 def configstyles(ui):
289 def configstyles(ui):
289 for status, cfgeffects in ui.configitems('color'):
290 for status, cfgeffects in ui.configitems('color'):
290 if '.' not in status or status.startswith('color.'):
291 if '.' not in status or status.startswith('color.'):
291 continue
292 continue
292 cfgeffects = ui.configlist('color', status)
293 cfgeffects = ui.configlist('color', status)
293 if cfgeffects:
294 if cfgeffects:
294 good = []
295 good = []
295 for e in cfgeffects:
296 for e in cfgeffects:
296 if not _terminfo_params and e in _effects:
297 if not _terminfo_params and e in _effects:
297 good.append(e)
298 good.append(e)
298 elif e in _terminfo_params or e[:-11] in _terminfo_params:
299 elif e in _terminfo_params or e[:-11] in _terminfo_params:
299 good.append(e)
300 good.append(e)
300 else:
301 else:
301 ui.warn(_("ignoring unknown color/effect %r "
302 ui.warn(_("ignoring unknown color/effect %r "
302 "(configured in color.%s)\n")
303 "(configured in color.%s)\n")
303 % (e, status))
304 % (e, status))
304 _styles[status] = ' '.join(good)
305 _styles[status] = ' '.join(good)
305
306
306 class colorui(uimod.ui):
307 class colorui(uimod.ui):
307 def popbuffer(self, labeled=False):
308 def popbuffer(self, labeled=False):
308 if labeled:
309 if labeled:
309 return ''.join(self.label(a, label) for a, label
310 return ''.join(self.label(a, label) for a, label
310 in self._buffers.pop())
311 in self._buffers.pop())
311 return ''.join(a for a, label in self._buffers.pop())
312 return ''.join(a for a, label in self._buffers.pop())
312
313
313 _colormode = 'ansi'
314 _colormode = 'ansi'
314 def write(self, *args, **opts):
315 def write(self, *args, **opts):
315 label = opts.get('label', '')
316 label = opts.get('label', '')
316 if self._buffers:
317 if self._buffers:
317 self._buffers[-1].extend([(str(a), label) for a in args])
318 self._buffers[-1].extend([(str(a), label) for a in args])
318 elif self._colormode == 'win32':
319 elif self._colormode == 'win32':
319 for a in args:
320 for a in args:
320 win32print(a, super(colorui, self).write, **opts)
321 win32print(a, super(colorui, self).write, **opts)
321 else:
322 else:
322 return super(colorui, self).write(
323 return super(colorui, self).write(
323 *[self.label(str(a), label) for a in args], **opts)
324 *[self.label(str(a), label) for a in args], **opts)
324
325
325 def write_err(self, *args, **opts):
326 def write_err(self, *args, **opts):
326 label = opts.get('label', '')
327 label = opts.get('label', '')
327 if self._colormode == 'win32':
328 if self._colormode == 'win32':
328 for a in args:
329 for a in args:
329 win32print(a, super(colorui, self).write_err, **opts)
330 win32print(a, super(colorui, self).write_err, **opts)
330 else:
331 else:
331 return super(colorui, self).write_err(
332 return super(colorui, self).write_err(
332 *[self.label(str(a), label) for a in args], **opts)
333 *[self.label(str(a), label) for a in args], **opts)
333
334
334 def label(self, msg, label):
335 def label(self, msg, label):
335 effects = []
336 effects = []
336 for l in label.split():
337 for l in label.split():
337 s = _styles.get(l, '')
338 s = _styles.get(l, '')
338 if s:
339 if s:
339 effects.append(s)
340 effects.append(s)
340 effects = ' '.join(effects)
341 effects = ' '.join(effects)
341 if effects:
342 if effects:
342 return '\n'.join([render_effects(s, effects)
343 return '\n'.join([render_effects(s, effects)
343 for s in msg.split('\n')])
344 for s in msg.split('\n')])
344 return msg
345 return msg
345
346
346
347
347 def uisetup(ui):
348 def uisetup(ui):
348 global _terminfo_params
349 global _terminfo_params
349 if ui.plain():
350 if ui.plain():
350 return
351 return
351 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
352 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
352 mode = _modesetup(ui_, opts)
353 mode = _modesetup(ui_, opts)
353 if mode:
354 if mode:
354 colorui._colormode = mode
355 colorui._colormode = mode
355 if not issubclass(ui_.__class__, colorui):
356 if not issubclass(ui_.__class__, colorui):
356 colorui.__bases__ = (ui_.__class__,)
357 colorui.__bases__ = (ui_.__class__,)
357 ui_.__class__ = colorui
358 ui_.__class__ = colorui
358 extstyles()
359 extstyles()
359 configstyles(ui_)
360 configstyles(ui_)
360 return orig(ui_, opts, cmd, cmdfunc)
361 return orig(ui_, opts, cmd, cmdfunc)
361 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
362 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
362
363
363 def extsetup(ui):
364 def extsetup(ui):
364 commands.globalopts.append(
365 commands.globalopts.append(
365 ('', 'color', 'auto',
366 ('', 'color', 'auto',
366 # i18n: 'always', 'auto', and 'never' are keywords and should
367 # i18n: 'always', 'auto', and 'never' are keywords and should
367 # not be translated
368 # not be translated
368 _("when to colorize (boolean, always, auto, or never)"),
369 _("when to colorize (boolean, always, auto, or never)"),
369 _('TYPE')))
370 _('TYPE')))
370
371
371 if os.name != 'nt':
372 if os.name != 'nt':
372 w32effects = None
373 w32effects = None
373 else:
374 else:
374 import re, ctypes
375 import re, ctypes
375
376
376 _kernel32 = ctypes.windll.kernel32
377 _kernel32 = ctypes.windll.kernel32
377
378
378 _WORD = ctypes.c_ushort
379 _WORD = ctypes.c_ushort
379
380
380 _INVALID_HANDLE_VALUE = -1
381 _INVALID_HANDLE_VALUE = -1
381
382
382 class _COORD(ctypes.Structure):
383 class _COORD(ctypes.Structure):
383 _fields_ = [('X', ctypes.c_short),
384 _fields_ = [('X', ctypes.c_short),
384 ('Y', ctypes.c_short)]
385 ('Y', ctypes.c_short)]
385
386
386 class _SMALL_RECT(ctypes.Structure):
387 class _SMALL_RECT(ctypes.Structure):
387 _fields_ = [('Left', ctypes.c_short),
388 _fields_ = [('Left', ctypes.c_short),
388 ('Top', ctypes.c_short),
389 ('Top', ctypes.c_short),
389 ('Right', ctypes.c_short),
390 ('Right', ctypes.c_short),
390 ('Bottom', ctypes.c_short)]
391 ('Bottom', ctypes.c_short)]
391
392
392 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
393 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
393 _fields_ = [('dwSize', _COORD),
394 _fields_ = [('dwSize', _COORD),
394 ('dwCursorPosition', _COORD),
395 ('dwCursorPosition', _COORD),
395 ('wAttributes', _WORD),
396 ('wAttributes', _WORD),
396 ('srWindow', _SMALL_RECT),
397 ('srWindow', _SMALL_RECT),
397 ('dwMaximumWindowSize', _COORD)]
398 ('dwMaximumWindowSize', _COORD)]
398
399
399 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
400 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
400 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
401 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
401
402
402 _FOREGROUND_BLUE = 0x0001
403 _FOREGROUND_BLUE = 0x0001
403 _FOREGROUND_GREEN = 0x0002
404 _FOREGROUND_GREEN = 0x0002
404 _FOREGROUND_RED = 0x0004
405 _FOREGROUND_RED = 0x0004
405 _FOREGROUND_INTENSITY = 0x0008
406 _FOREGROUND_INTENSITY = 0x0008
406
407
407 _BACKGROUND_BLUE = 0x0010
408 _BACKGROUND_BLUE = 0x0010
408 _BACKGROUND_GREEN = 0x0020
409 _BACKGROUND_GREEN = 0x0020
409 _BACKGROUND_RED = 0x0040
410 _BACKGROUND_RED = 0x0040
410 _BACKGROUND_INTENSITY = 0x0080
411 _BACKGROUND_INTENSITY = 0x0080
411
412
412 _COMMON_LVB_REVERSE_VIDEO = 0x4000
413 _COMMON_LVB_REVERSE_VIDEO = 0x4000
413 _COMMON_LVB_UNDERSCORE = 0x8000
414 _COMMON_LVB_UNDERSCORE = 0x8000
414
415
415 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
416 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
416 w32effects = {
417 w32effects = {
417 'none': -1,
418 'none': -1,
418 'black': 0,
419 'black': 0,
419 'red': _FOREGROUND_RED,
420 'red': _FOREGROUND_RED,
420 'green': _FOREGROUND_GREEN,
421 'green': _FOREGROUND_GREEN,
421 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
422 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
422 'blue': _FOREGROUND_BLUE,
423 'blue': _FOREGROUND_BLUE,
423 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
424 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
424 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
425 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
425 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
426 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
426 'bold': _FOREGROUND_INTENSITY,
427 'bold': _FOREGROUND_INTENSITY,
427 'black_background': 0x100, # unused value > 0x0f
428 'black_background': 0x100, # unused value > 0x0f
428 'red_background': _BACKGROUND_RED,
429 'red_background': _BACKGROUND_RED,
429 'green_background': _BACKGROUND_GREEN,
430 'green_background': _BACKGROUND_GREEN,
430 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
431 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
431 'blue_background': _BACKGROUND_BLUE,
432 'blue_background': _BACKGROUND_BLUE,
432 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
433 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
433 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
434 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
434 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
435 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
435 _BACKGROUND_BLUE),
436 _BACKGROUND_BLUE),
436 'bold_background': _BACKGROUND_INTENSITY,
437 'bold_background': _BACKGROUND_INTENSITY,
437 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
438 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
438 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
439 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
439 }
440 }
440
441
441 passthrough = set([_FOREGROUND_INTENSITY,
442 passthrough = set([_FOREGROUND_INTENSITY,
442 _BACKGROUND_INTENSITY,
443 _BACKGROUND_INTENSITY,
443 _COMMON_LVB_UNDERSCORE,
444 _COMMON_LVB_UNDERSCORE,
444 _COMMON_LVB_REVERSE_VIDEO])
445 _COMMON_LVB_REVERSE_VIDEO])
445
446
446 stdout = _kernel32.GetStdHandle(
447 stdout = _kernel32.GetStdHandle(
447 _STD_OUTPUT_HANDLE) # don't close the handle returned
448 _STD_OUTPUT_HANDLE) # don't close the handle returned
448 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
449 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
449 w32effects = None
450 w32effects = None
450 else:
451 else:
451 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
452 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
452 if not _kernel32.GetConsoleScreenBufferInfo(
453 if not _kernel32.GetConsoleScreenBufferInfo(
453 stdout, ctypes.byref(csbi)):
454 stdout, ctypes.byref(csbi)):
454 # stdout may not support GetConsoleScreenBufferInfo()
455 # stdout may not support GetConsoleScreenBufferInfo()
455 # when called from subprocess or redirected
456 # when called from subprocess or redirected
456 w32effects = None
457 w32effects = None
457 else:
458 else:
458 origattr = csbi.wAttributes
459 origattr = csbi.wAttributes
459 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
460 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
460 re.MULTILINE | re.DOTALL)
461 re.MULTILINE | re.DOTALL)
461
462
462 def win32print(text, orig, **opts):
463 def win32print(text, orig, **opts):
463 label = opts.get('label', '')
464 label = opts.get('label', '')
464 attr = origattr
465 attr = origattr
465
466
466 def mapcolor(val, attr):
467 def mapcolor(val, attr):
467 if val == -1:
468 if val == -1:
468 return origattr
469 return origattr
469 elif val in passthrough:
470 elif val in passthrough:
470 return attr | val
471 return attr | val
471 elif val > 0x0f:
472 elif val > 0x0f:
472 return (val & 0x70) | (attr & 0x8f)
473 return (val & 0x70) | (attr & 0x8f)
473 else:
474 else:
474 return (val & 0x07) | (attr & 0xf8)
475 return (val & 0x07) | (attr & 0xf8)
475
476
476 # determine console attributes based on labels
477 # determine console attributes based on labels
477 for l in label.split():
478 for l in label.split():
478 style = _styles.get(l, '')
479 style = _styles.get(l, '')
479 for effect in style.split():
480 for effect in style.split():
480 attr = mapcolor(w32effects[effect], attr)
481 attr = mapcolor(w32effects[effect], attr)
481
482
482 # hack to ensure regexp finds data
483 # hack to ensure regexp finds data
483 if not text.startswith('\033['):
484 if not text.startswith('\033['):
484 text = '\033[m' + text
485 text = '\033[m' + text
485
486
486 # Look for ANSI-like codes embedded in text
487 # Look for ANSI-like codes embedded in text
487 m = re.match(ansire, text)
488 m = re.match(ansire, text)
488
489
489 try:
490 try:
490 while m:
491 while m:
491 for sattr in m.group(1).split(';'):
492 for sattr in m.group(1).split(';'):
492 if sattr:
493 if sattr:
493 attr = mapcolor(int(sattr), attr)
494 attr = mapcolor(int(sattr), attr)
494 _kernel32.SetConsoleTextAttribute(stdout, attr)
495 _kernel32.SetConsoleTextAttribute(stdout, attr)
495 orig(m.group(2), **opts)
496 orig(m.group(2), **opts)
496 m = re.match(ansire, m.group(3))
497 m = re.match(ansire, m.group(3))
497 finally:
498 finally:
498 # Explicity reset original attributes
499 # Explicity reset original attributes
499 _kernel32.SetConsoleTextAttribute(stdout, origattr)
500 _kernel32.SetConsoleTextAttribute(stdout, origattr)
General Comments 0
You need to be logged in to leave comments. Login now