##// END OF EJS Templates
color: handle more Windows console errors...
Steve Borho -
r12065:a8b1cb0b default
parent child Browse files
Show More
@@ -1,296 +1,298 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 to their
22 22 output to reflect file status, the qseries command to add color to reflect
23 23 patch status (applied, unapplied, missing), and to diff-related
24 24 commands to highlight additions, removals, diff headers, and trailing
25 25 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). This module also provides the
30 30 render_text function, which can be used to add effects to any text.
31 31
32 32 Default effects may be overridden from the .hgrc file::
33 33
34 34 [color]
35 35 status.modified = blue bold underline red_background
36 36 status.added = green bold
37 37 status.removed = red bold blue_background
38 38 status.deleted = cyan bold underline
39 39 status.unknown = magenta bold underline
40 40 status.ignored = black bold
41 41
42 42 # 'none' turns off all effects
43 43 status.clean = none
44 44 status.copied = none
45 45
46 46 qseries.applied = blue bold underline
47 47 qseries.unapplied = black bold
48 48 qseries.missing = red bold
49 49
50 50 diff.diffline = bold
51 51 diff.extended = cyan bold
52 52 diff.file_a = red bold
53 53 diff.file_b = green bold
54 54 diff.hunk = magenta
55 55 diff.deleted = red
56 56 diff.inserted = green
57 57 diff.changed = white
58 58 diff.trailingwhitespace = bold red_background
59 59
60 60 resolve.unresolved = red bold
61 61 resolve.resolved = green bold
62 62
63 63 bookmarks.current = green
64 64
65 65 branches.active = none
66 66 branches.closed = black bold
67 67 branches.current = green
68 68 branches.inactive = none
69 69
70 70 The color extension will try to detect whether to use ANSI codes or
71 71 Win32 console APIs, unless it is made explicit::
72 72
73 73 [color]
74 74 mode = ansi
75 75
76 76 Any value other than 'ansi', 'win32', or 'auto' will disable color.
77 77
78 78 '''
79 79
80 80 import os
81 81
82 82 from mercurial import commands, dispatch, extensions, ui as uimod
83 83 from mercurial.i18n import _
84 84
85 85 # start and stop parameters for effects
86 86 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
87 87 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
88 88 'italic': 3, 'underline': 4, 'inverse': 7,
89 89 'black_background': 40, 'red_background': 41,
90 90 'green_background': 42, 'yellow_background': 43,
91 91 'blue_background': 44, 'purple_background': 45,
92 92 'cyan_background': 46, 'white_background': 47}
93 93
94 94 _styles = {'grep.match': 'red bold',
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 'log.changeset': 'yellow',
111 111 'resolve.resolved': 'green bold',
112 112 'resolve.unresolved': 'red bold',
113 113 'status.added': 'green bold',
114 114 'status.clean': 'none',
115 115 'status.copied': 'none',
116 116 'status.deleted': 'cyan bold underline',
117 117 'status.ignored': 'black bold',
118 118 'status.modified': 'blue bold',
119 119 'status.removed': 'red bold',
120 120 'status.unknown': 'magenta bold underline'}
121 121
122 122
123 123 def render_effects(text, effects):
124 124 'Wrap text in commands to turn on each effect.'
125 125 if not text:
126 126 return text
127 127 start = [str(_effects[e]) for e in ['none'] + effects.split()]
128 128 start = '\033[' + ';'.join(start) + 'm'
129 129 stop = '\033[' + str(_effects['none']) + 'm'
130 130 return ''.join([start, text, stop])
131 131
132 132 def extstyles():
133 133 for name, ext in extensions.extensions():
134 134 _styles.update(getattr(ext, 'colortable', {}))
135 135
136 136 def configstyles(ui):
137 137 for status, cfgeffects in ui.configitems('color'):
138 138 if '.' not in status:
139 139 continue
140 140 cfgeffects = ui.configlist('color', status)
141 141 if cfgeffects:
142 142 good = []
143 143 for e in cfgeffects:
144 144 if e in _effects:
145 145 good.append(e)
146 146 else:
147 147 ui.warn(_("ignoring unknown color/effect %r "
148 148 "(configured in color.%s)\n")
149 149 % (e, status))
150 150 _styles[status] = ' '.join(good)
151 151
152 152 class colorui(uimod.ui):
153 153 def popbuffer(self, labeled=False):
154 154 if labeled:
155 155 return ''.join(self.label(a, label) for a, label
156 156 in self._buffers.pop())
157 157 return ''.join(a for a, label in self._buffers.pop())
158 158
159 159 _colormode = 'ansi'
160 160 def write(self, *args, **opts):
161 161 label = opts.get('label', '')
162 162 if self._buffers:
163 163 self._buffers[-1].extend([(str(a), label) for a in args])
164 164 elif self._colormode == 'win32':
165 165 for a in args:
166 166 win32print(a, super(colorui, self).write, **opts)
167 167 else:
168 168 return super(colorui, self).write(
169 169 *[self.label(str(a), label) for a in args], **opts)
170 170
171 171 def write_err(self, *args, **opts):
172 172 label = opts.get('label', '')
173 173 if self._colormode == 'win32':
174 174 for a in args:
175 175 win32print(a, super(colorui, self).write_err, **opts)
176 176 else:
177 177 return super(colorui, self).write_err(
178 178 *[self.label(str(a), label) for a in args], **opts)
179 179
180 180 def label(self, msg, label):
181 181 effects = []
182 182 for l in label.split():
183 183 s = _styles.get(l, '')
184 184 if s:
185 185 effects.append(s)
186 186 effects = ''.join(effects)
187 187 if effects:
188 188 return '\n'.join([render_effects(s, effects)
189 189 for s in msg.split('\n')])
190 190 return msg
191 191
192 192
193 193 def uisetup(ui):
194 194 if ui.plain():
195 195 return
196 196 mode = ui.config('color', 'mode', 'auto')
197 197 if mode == 'auto':
198 198 if os.name == 'nt' and 'TERM' not in os.environ:
199 199 # looks line a cmd.exe console, use win32 API or nothing
200 200 mode = w32effects and 'win32' or 'none'
201 201 else:
202 202 mode = 'ansi'
203 203 if mode == 'win32':
204 204 if w32effects is None:
205 205 # only warn if color.mode is explicitly set to win32
206 206 ui.warn(_('win32console not found, please install pywin32\n'))
207 207 return
208 208 _effects.update(w32effects)
209 209 elif mode != 'ansi':
210 210 return
211 211 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
212 212 if (opts['color'] == 'always' or
213 213 (opts['color'] == 'auto' and (os.environ.get('TERM') != 'dumb'
214 214 and ui_.formatted()))):
215 215 colorui._colormode = mode
216 216 colorui.__bases__ = (ui_.__class__,)
217 217 ui_.__class__ = colorui
218 218 extstyles()
219 219 configstyles(ui_)
220 220 return orig(ui_, opts, cmd, cmdfunc)
221 221 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
222 222
223 223 commands.globalopts.append(('', 'color', 'auto',
224 224 _("when to colorize (always, auto, or never)"),
225 225 _('TYPE')))
226 226
227 227 try:
228 228 import re, pywintypes, win32console as win32c
229 229
230 230 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
231 231 w32effects = {
232 232 'none': 0,
233 233 'black': 0,
234 234 'red': win32c.FOREGROUND_RED,
235 235 'green': win32c.FOREGROUND_GREEN,
236 236 'yellow': win32c.FOREGROUND_RED | win32c.FOREGROUND_GREEN,
237 237 'blue': win32c.FOREGROUND_BLUE,
238 238 'magenta': win32c.FOREGROUND_BLUE | win32c.FOREGROUND_RED,
239 239 'cyan': win32c.FOREGROUND_BLUE | win32c.FOREGROUND_GREEN,
240 240 'white': (win32c.FOREGROUND_RED | win32c.FOREGROUND_GREEN |
241 241 win32c.FOREGROUND_BLUE),
242 242 'bold': win32c.FOREGROUND_INTENSITY,
243 243 'black_background': 0,
244 244 'red_background': win32c.BACKGROUND_RED,
245 245 'green_background': win32c.BACKGROUND_GREEN,
246 246 'yellow_background': win32c.BACKGROUND_RED | win32c.BACKGROUND_GREEN,
247 247 'blue_background': win32c.BACKGROUND_BLUE,
248 248 'purple_background': win32c.BACKGROUND_BLUE | win32c.BACKGROUND_RED,
249 249 'cyan_background': win32c.BACKGROUND_BLUE | win32c.BACKGROUND_GREEN,
250 250 'white_background': (win32c.BACKGROUND_RED | win32c.BACKGROUND_GREEN |
251 251 win32c.BACKGROUND_BLUE),
252 252 'bold_background': win32c.BACKGROUND_INTENSITY,
253 253 'underline': win32c.COMMON_LVB_UNDERSCORE, # double-byte charsets only
254 254 'inverse': win32c.COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
255 255 }
256 256
257 try:
257 258 stdout = win32c.GetStdHandle(win32c.STD_OUTPUT_HANDLE)
258 try:
259 if stdout is None:
260 raise ImportError()
259 261 origattr = stdout.GetConsoleScreenBufferInfo()['Attributes']
260 262 except pywintypes.error:
261 263 # stdout may be defined but not support
262 264 # GetConsoleScreenBufferInfo(), when called from subprocess or
263 265 # redirected.
264 266 raise ImportError()
265 267 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)', re.MULTILINE | re.DOTALL)
266 268
267 269 def win32print(text, orig, **opts):
268 270 label = opts.get('label', '')
269 271 attr = 0
270 272
271 273 # determine console attributes based on labels
272 274 for l in label.split():
273 275 style = _styles.get(l, '')
274 276 for effect in style.split():
275 277 attr |= w32effects[effect]
276 278
277 279 # hack to ensure regexp finds data
278 280 if not text.startswith('\033['):
279 281 text = '\033[m' + text
280 282
281 283 # Look for ANSI-like codes embedded in text
282 284 m = re.match(ansire, text)
283 285 while m:
284 286 for sattr in m.group(1).split(';'):
285 287 if sattr:
286 288 val = int(sattr)
287 289 attr = val and attr|val or 0
288 290 stdout.SetConsoleTextAttribute(attr or origattr)
289 291 orig(m.group(2), **opts)
290 292 m = re.match(ansire, m.group(3))
291 293
292 294 # Explicity reset original attributes
293 295 stdout.SetConsoleTextAttribute(origattr)
294 296
295 297 except ImportError:
296 298 w32effects = None
General Comments 0
You need to be logged in to leave comments. Login now