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