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