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