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