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