##// END OF EJS Templates
color: reset win32 console color in a finally block
Idan Kamara -
r13919:67f20625 default
parent child Browse files
Show More
@@ -1,361 +1,363 b''
1 1 # color.py color output for the status and qseries commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 4 #
5 5 # This program is free software; you can redistribute it and/or modify it
6 6 # under the terms of the GNU General Public License as published by the
7 7 # Free Software Foundation; either version 2 of the License, or (at your
8 8 # option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful, but
11 11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 13 # Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License along
16 16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 18
19 19 '''colorize output from some commands
20 20
21 21 This extension modifies the status and resolve commands to add color
22 22 to their output to reflect file status, the qseries command to add
23 23 color to reflect patch status (applied, unapplied, missing), and to
24 24 diff-related commands to highlight additions, removals, diff headers,
25 25 and trailing whitespace.
26 26
27 27 Other effects in addition to color, like bold and underlined text, are
28 28 also available. Effects are rendered with the ECMA-48 SGR control
29 29 function (aka ANSI escape codes).
30 30
31 31 Default effects may be overridden from your configuration file::
32 32
33 33 [color]
34 34 status.modified = blue bold underline red_background
35 35 status.added = green bold
36 36 status.removed = red bold blue_background
37 37 status.deleted = cyan bold underline
38 38 status.unknown = magenta bold underline
39 39 status.ignored = black bold
40 40
41 41 # 'none' turns off all effects
42 42 status.clean = none
43 43 status.copied = none
44 44
45 45 qseries.applied = blue bold underline
46 46 qseries.unapplied = black bold
47 47 qseries.missing = red bold
48 48
49 49 diff.diffline = bold
50 50 diff.extended = cyan bold
51 51 diff.file_a = red bold
52 52 diff.file_b = green bold
53 53 diff.hunk = magenta
54 54 diff.deleted = red
55 55 diff.inserted = green
56 56 diff.changed = white
57 57 diff.trailingwhitespace = bold red_background
58 58
59 59 resolve.unresolved = red bold
60 60 resolve.resolved = green bold
61 61
62 62 bookmarks.current = green
63 63
64 64 branches.active = none
65 65 branches.closed = black bold
66 66 branches.current = green
67 67 branches.inactive = none
68 68
69 69 The color extension will try to detect whether to use ANSI codes or
70 70 Win32 console APIs, unless it is made explicit::
71 71
72 72 [color]
73 73 mode = ansi
74 74
75 75 Any value other than 'ansi', 'win32', or 'auto' will disable color.
76 76
77 77 '''
78 78
79 79 import os
80 80
81 81 from mercurial import commands, dispatch, extensions, ui as uimod, util
82 82 from mercurial.i18n import _
83 83
84 84 # start and stop parameters for effects
85 85 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
86 86 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
87 87 'italic': 3, 'underline': 4, 'inverse': 7,
88 88 'black_background': 40, 'red_background': 41,
89 89 'green_background': 42, 'yellow_background': 43,
90 90 'blue_background': 44, 'purple_background': 45,
91 91 'cyan_background': 46, 'white_background': 47}
92 92
93 93 _styles = {'grep.match': 'red bold',
94 94 'bookmarks.current': 'green',
95 95 'branches.active': 'none',
96 96 'branches.closed': 'black bold',
97 97 'branches.current': 'green',
98 98 'branches.inactive': 'none',
99 99 'diff.changed': 'white',
100 100 'diff.deleted': 'red',
101 101 'diff.diffline': 'bold',
102 102 'diff.extended': 'cyan bold',
103 103 'diff.file_a': 'red bold',
104 104 'diff.file_b': 'green bold',
105 105 'diff.hunk': 'magenta',
106 106 'diff.inserted': 'green',
107 107 'diff.trailingwhitespace': 'bold red_background',
108 108 'diffstat.deleted': 'red',
109 109 'diffstat.inserted': 'green',
110 110 'ui.prompt': 'yellow',
111 111 'log.changeset': 'yellow',
112 112 'resolve.resolved': 'green bold',
113 113 'resolve.unresolved': 'red bold',
114 114 'status.added': 'green bold',
115 115 'status.clean': 'none',
116 116 'status.copied': 'none',
117 117 'status.deleted': 'cyan bold underline',
118 118 'status.ignored': 'black bold',
119 119 'status.modified': 'blue bold',
120 120 'status.removed': 'red bold',
121 121 'status.unknown': 'magenta bold underline'}
122 122
123 123
124 124 def render_effects(text, effects):
125 125 'Wrap text in commands to turn on each effect.'
126 126 if not text:
127 127 return text
128 128 start = [str(_effects[e]) for e in ['none'] + effects.split()]
129 129 start = '\033[' + ';'.join(start) + 'm'
130 130 stop = '\033[' + str(_effects['none']) + 'm'
131 131 return ''.join([start, text, stop])
132 132
133 133 def extstyles():
134 134 for name, ext in extensions.extensions():
135 135 _styles.update(getattr(ext, 'colortable', {}))
136 136
137 137 def configstyles(ui):
138 138 for status, cfgeffects in ui.configitems('color'):
139 139 if '.' not in status:
140 140 continue
141 141 cfgeffects = ui.configlist('color', status)
142 142 if cfgeffects:
143 143 good = []
144 144 for e in cfgeffects:
145 145 if e in _effects:
146 146 good.append(e)
147 147 else:
148 148 ui.warn(_("ignoring unknown color/effect %r "
149 149 "(configured in color.%s)\n")
150 150 % (e, status))
151 151 _styles[status] = ' '.join(good)
152 152
153 153 class colorui(uimod.ui):
154 154 def popbuffer(self, labeled=False):
155 155 if labeled:
156 156 return ''.join(self.label(a, label) for a, label
157 157 in self._buffers.pop())
158 158 return ''.join(a for a, label in self._buffers.pop())
159 159
160 160 _colormode = 'ansi'
161 161 def write(self, *args, **opts):
162 162 label = opts.get('label', '')
163 163 if self._buffers:
164 164 self._buffers[-1].extend([(str(a), label) for a in args])
165 165 elif self._colormode == 'win32':
166 166 for a in args:
167 167 win32print(a, super(colorui, self).write, **opts)
168 168 else:
169 169 return super(colorui, self).write(
170 170 *[self.label(str(a), label) for a in args], **opts)
171 171
172 172 def write_err(self, *args, **opts):
173 173 label = opts.get('label', '')
174 174 if self._colormode == 'win32':
175 175 for a in args:
176 176 win32print(a, super(colorui, self).write_err, **opts)
177 177 else:
178 178 return super(colorui, self).write_err(
179 179 *[self.label(str(a), label) for a in args], **opts)
180 180
181 181 def label(self, msg, label):
182 182 effects = []
183 183 for l in label.split():
184 184 s = _styles.get(l, '')
185 185 if s:
186 186 effects.append(s)
187 187 effects = ''.join(effects)
188 188 if effects:
189 189 return '\n'.join([render_effects(s, effects)
190 190 for s in msg.split('\n')])
191 191 return msg
192 192
193 193
194 194 def uisetup(ui):
195 195 if ui.plain():
196 196 return
197 197 mode = ui.config('color', 'mode', 'auto')
198 198 if mode == 'auto':
199 199 if os.name == 'nt' and 'TERM' not in os.environ:
200 200 # looks line a cmd.exe console, use win32 API or nothing
201 201 mode = w32effects and 'win32' or 'none'
202 202 else:
203 203 mode = 'ansi'
204 204 if mode == 'win32':
205 205 if w32effects is None:
206 206 # only warn if color.mode is explicitly set to win32
207 207 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
208 208 return
209 209 _effects.update(w32effects)
210 210 elif mode != 'ansi':
211 211 return
212 212 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
213 213 coloropt = opts['color']
214 214 auto = coloropt == 'auto'
215 215 always = util.parsebool(coloropt)
216 216 if (always or
217 217 (always is None and
218 218 (auto and (os.environ.get('TERM') != 'dumb' and ui_.formatted())))):
219 219 colorui._colormode = mode
220 220 colorui.__bases__ = (ui_.__class__,)
221 221 ui_.__class__ = colorui
222 222 extstyles()
223 223 configstyles(ui_)
224 224 return orig(ui_, opts, cmd, cmdfunc)
225 225 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
226 226
227 227 def extsetup(ui):
228 228 commands.globalopts.append(
229 229 ('', 'color', 'auto',
230 230 # i18n: 'always', 'auto', and 'never' are keywords and should
231 231 # not be translated
232 232 _("when to colorize (boolean, always, auto, or never)"),
233 233 _('TYPE')))
234 234
235 235 if os.name != 'nt':
236 236 w32effects = None
237 237 else:
238 238 import re, ctypes
239 239
240 240 _kernel32 = ctypes.windll.kernel32
241 241
242 242 _WORD = ctypes.c_ushort
243 243
244 244 _INVALID_HANDLE_VALUE = -1
245 245
246 246 class _COORD(ctypes.Structure):
247 247 _fields_ = [('X', ctypes.c_short),
248 248 ('Y', ctypes.c_short)]
249 249
250 250 class _SMALL_RECT(ctypes.Structure):
251 251 _fields_ = [('Left', ctypes.c_short),
252 252 ('Top', ctypes.c_short),
253 253 ('Right', ctypes.c_short),
254 254 ('Bottom', ctypes.c_short)]
255 255
256 256 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
257 257 _fields_ = [('dwSize', _COORD),
258 258 ('dwCursorPosition', _COORD),
259 259 ('wAttributes', _WORD),
260 260 ('srWindow', _SMALL_RECT),
261 261 ('dwMaximumWindowSize', _COORD)]
262 262
263 263 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
264 264 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
265 265
266 266 _FOREGROUND_BLUE = 0x0001
267 267 _FOREGROUND_GREEN = 0x0002
268 268 _FOREGROUND_RED = 0x0004
269 269 _FOREGROUND_INTENSITY = 0x0008
270 270
271 271 _BACKGROUND_BLUE = 0x0010
272 272 _BACKGROUND_GREEN = 0x0020
273 273 _BACKGROUND_RED = 0x0040
274 274 _BACKGROUND_INTENSITY = 0x0080
275 275
276 276 _COMMON_LVB_REVERSE_VIDEO = 0x4000
277 277 _COMMON_LVB_UNDERSCORE = 0x8000
278 278
279 279 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
280 280 w32effects = {
281 281 'none': -1,
282 282 'black': 0,
283 283 'red': _FOREGROUND_RED,
284 284 'green': _FOREGROUND_GREEN,
285 285 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
286 286 'blue': _FOREGROUND_BLUE,
287 287 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
288 288 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
289 289 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
290 290 'bold': _FOREGROUND_INTENSITY,
291 291 'black_background': 0x100, # unused value > 0x0f
292 292 'red_background': _BACKGROUND_RED,
293 293 'green_background': _BACKGROUND_GREEN,
294 294 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
295 295 'blue_background': _BACKGROUND_BLUE,
296 296 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
297 297 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
298 298 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
299 299 _BACKGROUND_BLUE),
300 300 'bold_background': _BACKGROUND_INTENSITY,
301 301 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
302 302 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
303 303 }
304 304
305 305 passthrough = set([_FOREGROUND_INTENSITY,
306 306 _BACKGROUND_INTENSITY,
307 307 _COMMON_LVB_UNDERSCORE,
308 308 _COMMON_LVB_REVERSE_VIDEO])
309 309
310 310 stdout = _kernel32.GetStdHandle(
311 311 _STD_OUTPUT_HANDLE) # don't close the handle returned
312 312 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
313 313 w32effects = None
314 314 else:
315 315 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
316 316 if not _kernel32.GetConsoleScreenBufferInfo(
317 317 stdout, ctypes.byref(csbi)):
318 318 # stdout may not support GetConsoleScreenBufferInfo()
319 319 # when called from subprocess or redirected
320 320 w32effects = None
321 321 else:
322 322 origattr = csbi.wAttributes
323 323 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
324 324 re.MULTILINE | re.DOTALL)
325 325
326 326 def win32print(text, orig, **opts):
327 327 label = opts.get('label', '')
328 328 attr = origattr
329 329
330 330 def mapcolor(val, attr):
331 331 if val == -1:
332 332 return origattr
333 333 elif val in passthrough:
334 334 return attr | val
335 335 elif val > 0x0f:
336 336 return (val & 0x70) | (attr & 0x8f)
337 337 else:
338 338 return (val & 0x07) | (attr & 0xf8)
339 339
340 340 # determine console attributes based on labels
341 341 for l in label.split():
342 342 style = _styles.get(l, '')
343 343 for effect in style.split():
344 344 attr = mapcolor(w32effects[effect], attr)
345 345
346 346 # hack to ensure regexp finds data
347 347 if not text.startswith('\033['):
348 348 text = '\033[m' + text
349 349
350 350 # Look for ANSI-like codes embedded in text
351 351 m = re.match(ansire, text)
352
353 try:
352 354 while m:
353 355 for sattr in m.group(1).split(';'):
354 356 if sattr:
355 357 attr = mapcolor(int(sattr), attr)
356 358 _kernel32.SetConsoleTextAttribute(stdout, attr)
357 359 orig(m.group(2), **opts)
358 360 m = re.match(ansire, m.group(3))
359
361 finally:
360 362 # Explicity reset original attributes
361 363 _kernel32.SetConsoleTextAttribute(stdout, origattr)
General Comments 0
You need to be logged in to leave comments. Login now