##// END OF EJS Templates
merge with stable
Sune Foldager -
r12278:c4c2ba55 merge default
parent child Browse files
Show More
@@ -1,302 +1,316
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 your configuration 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, util
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 coloropt = opts['color']
213 213 auto = coloropt == 'auto'
214 214 always = util.parsebool(coloropt)
215 215 if (always or
216 216 (always is None and
217 217 (auto and (os.environ.get('TERM') != 'dumb' and ui_.formatted())))):
218 218 colorui._colormode = mode
219 219 colorui.__bases__ = (ui_.__class__,)
220 220 ui_.__class__ = colorui
221 221 extstyles()
222 222 configstyles(ui_)
223 223 return orig(ui_, opts, cmd, cmdfunc)
224 224 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
225 225
226 226 commands.globalopts.append(
227 227 ('', 'color', 'auto',
228 228 _("when to colorize (boolean, always, auto, or never)"),
229 229 _('TYPE')))
230 230
231 231 try:
232 232 import re, pywintypes, win32console as win32c
233 233
234 234 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
235 235 w32effects = {
236 'none': 0,
236 'none': -1,
237 237 'black': 0,
238 238 'red': win32c.FOREGROUND_RED,
239 239 'green': win32c.FOREGROUND_GREEN,
240 240 'yellow': win32c.FOREGROUND_RED | win32c.FOREGROUND_GREEN,
241 241 'blue': win32c.FOREGROUND_BLUE,
242 242 'magenta': win32c.FOREGROUND_BLUE | win32c.FOREGROUND_RED,
243 243 'cyan': win32c.FOREGROUND_BLUE | win32c.FOREGROUND_GREEN,
244 244 'white': (win32c.FOREGROUND_RED | win32c.FOREGROUND_GREEN |
245 245 win32c.FOREGROUND_BLUE),
246 246 'bold': win32c.FOREGROUND_INTENSITY,
247 'black_background': 0,
247 'black_background': 0x100, # unused value > 0x0f
248 248 'red_background': win32c.BACKGROUND_RED,
249 249 'green_background': win32c.BACKGROUND_GREEN,
250 250 'yellow_background': win32c.BACKGROUND_RED | win32c.BACKGROUND_GREEN,
251 251 'blue_background': win32c.BACKGROUND_BLUE,
252 252 'purple_background': win32c.BACKGROUND_BLUE | win32c.BACKGROUND_RED,
253 253 'cyan_background': win32c.BACKGROUND_BLUE | win32c.BACKGROUND_GREEN,
254 254 'white_background': (win32c.BACKGROUND_RED | win32c.BACKGROUND_GREEN |
255 255 win32c.BACKGROUND_BLUE),
256 256 'bold_background': win32c.BACKGROUND_INTENSITY,
257 257 'underline': win32c.COMMON_LVB_UNDERSCORE, # double-byte charsets only
258 258 'inverse': win32c.COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
259 259 }
260 260
261 passthrough = set([win32c.FOREGROUND_INTENSITY,
262 win32c.BACKGROUND_INTENSITY,
263 win32c.COMMON_LVB_UNDERSCORE,
264 win32c.COMMON_LVB_REVERSE_VIDEO])
265
261 266 try:
262 267 stdout = win32c.GetStdHandle(win32c.STD_OUTPUT_HANDLE)
263 268 if stdout is None:
264 269 raise ImportError()
265 270 origattr = stdout.GetConsoleScreenBufferInfo()['Attributes']
266 271 except pywintypes.error:
267 272 # stdout may be defined but not support
268 273 # GetConsoleScreenBufferInfo(), when called from subprocess or
269 274 # redirected.
270 275 raise ImportError()
271 276 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)', re.MULTILINE | re.DOTALL)
272 277
273 278 def win32print(text, orig, **opts):
274 279 label = opts.get('label', '')
275 attr = 0
280 attr = origattr
281
282 def mapcolor(val, attr):
283 if val == -1:
284 return origattr
285 elif val in passthrough:
286 return attr | val
287 elif val > 0x0f:
288 return (val & 0x70) | (attr & 0x8f)
289 else:
290 return (val & 0x07) | (attr & 0xf8)
276 291
277 292 # determine console attributes based on labels
278 293 for l in label.split():
279 294 style = _styles.get(l, '')
280 295 for effect in style.split():
281 attr |= w32effects[effect]
296 attr = mapcolor(w32effects[effect], attr)
282 297
283 298 # hack to ensure regexp finds data
284 299 if not text.startswith('\033['):
285 300 text = '\033[m' + text
286 301
287 302 # Look for ANSI-like codes embedded in text
288 303 m = re.match(ansire, text)
289 304 while m:
290 305 for sattr in m.group(1).split(';'):
291 306 if sattr:
292 val = int(sattr)
293 attr = val and attr|val or 0
294 stdout.SetConsoleTextAttribute(attr or origattr)
307 attr = mapcolor(int(sattr), attr)
308 stdout.SetConsoleTextAttribute(attr)
295 309 orig(m.group(2), **opts)
296 310 m = re.match(ansire, m.group(3))
297 311
298 312 # Explicity reset original attributes
299 313 stdout.SetConsoleTextAttribute(origattr)
300 314
301 315 except ImportError:
302 316 w32effects = None
General Comments 0
You need to be logged in to leave comments. Login now