##// END OF EJS Templates
color: for the sake of "less -R", default to ansi in auto mode (issue2792)...
Brodie Rao -
r14769:9adce4b3 stable
parent child Browse files
Show More
@@ -1,500 +1,506 b''
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
21 This extension modifies the status and resolve commands to add color
22 to their output to reflect file status, the qseries command to add
22 to their output to reflect file status, the qseries command to add
23 color to reflect patch status (applied, unapplied, missing), and to
23 color to reflect patch status (applied, unapplied, missing), and to
24 diff-related commands to highlight additions, removals, diff headers,
24 diff-related commands to highlight additions, removals, diff headers,
25 and trailing whitespace.
25 and trailing 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. By default, the terminfo database is used to find the
28 also available. By default, the terminfo database is used to find the
29 terminal codes used to change color and effect. If terminfo is not
29 terminal codes used to change color and effect. If terminfo is not
30 available, then effects are rendered with the ECMA-48 SGR control
30 available, then effects are rendered with the ECMA-48 SGR control
31 function (aka ANSI escape codes).
31 function (aka ANSI escape codes).
32
32
33 Default effects may be overridden from your configuration file::
33 Default effects may be overridden from your configuration file::
34
34
35 [color]
35 [color]
36 status.modified = blue bold underline red_background
36 status.modified = blue bold underline red_background
37 status.added = green bold
37 status.added = green bold
38 status.removed = red bold blue_background
38 status.removed = red bold blue_background
39 status.deleted = cyan bold underline
39 status.deleted = cyan bold underline
40 status.unknown = magenta bold underline
40 status.unknown = magenta bold underline
41 status.ignored = black bold
41 status.ignored = black bold
42
42
43 # 'none' turns off all effects
43 # 'none' turns off all effects
44 status.clean = none
44 status.clean = none
45 status.copied = none
45 status.copied = none
46
46
47 qseries.applied = blue bold underline
47 qseries.applied = blue bold underline
48 qseries.unapplied = black bold
48 qseries.unapplied = black bold
49 qseries.missing = red bold
49 qseries.missing = red bold
50
50
51 diff.diffline = bold
51 diff.diffline = bold
52 diff.extended = cyan bold
52 diff.extended = cyan bold
53 diff.file_a = red bold
53 diff.file_a = red bold
54 diff.file_b = green bold
54 diff.file_b = green bold
55 diff.hunk = magenta
55 diff.hunk = magenta
56 diff.deleted = red
56 diff.deleted = red
57 diff.inserted = green
57 diff.inserted = green
58 diff.changed = white
58 diff.changed = white
59 diff.trailingwhitespace = bold red_background
59 diff.trailingwhitespace = bold red_background
60
60
61 resolve.unresolved = red bold
61 resolve.unresolved = red bold
62 resolve.resolved = green bold
62 resolve.resolved = green bold
63
63
64 bookmarks.current = green
64 bookmarks.current = green
65
65
66 branches.active = none
66 branches.active = none
67 branches.closed = black bold
67 branches.closed = black bold
68 branches.current = green
68 branches.current = green
69 branches.inactive = none
69 branches.inactive = none
70
70
71 The available effects in terminfo mode are 'blink', 'bold', 'dim',
71 The available effects in terminfo mode are 'blink', 'bold', 'dim',
72 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
72 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
73 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
73 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
74 'underline'. How each is rendered depends on the terminal emulator.
74 'underline'. How each is rendered depends on the terminal emulator.
75 Some may not be available for a given terminal type, and will be
75 Some may not be available for a given terminal type, and will be
76 silently ignored.
76 silently ignored.
77
77
78 Note that on some systems, terminfo mode may cause problems when using
79 color with the pager extension and less -R. less with the -R option
80 will only display ECMA-48 color codes, and terminfo mode may sometimes
81 emit codes that less doesn't understand. You can work around this by
82 either using ansi mode (or auto mode), or by using less -r (which will
83 pass through all terminal control codes, not just color control
84 codes).
85
78 Because there are only eight standard colors, this module allows you
86 Because there are only eight standard colors, this module allows you
79 to define color names for other color slots which might be available
87 to define color names for other color slots which might be available
80 for your terminal type, assuming terminfo mode. For instance::
88 for your terminal type, assuming terminfo mode. For instance::
81
89
82 color.brightblue = 12
90 color.brightblue = 12
83 color.pink = 207
91 color.pink = 207
84 color.orange = 202
92 color.orange = 202
85
93
86 to set 'brightblue' to color slot 12 (useful for 16 color terminals
94 to set 'brightblue' to color slot 12 (useful for 16 color terminals
87 that have brighter colors defined in the upper eight) and, 'pink' and
95 that have brighter colors defined in the upper eight) and, 'pink' and
88 'orange' to colors in 256-color xterm's default color cube. These
96 'orange' to colors in 256-color xterm's default color cube. These
89 defined colors may then be used as any of the pre-defined eight,
97 defined colors may then be used as any of the pre-defined eight,
90 including appending '_background' to set the background to that color.
98 including appending '_background' to set the background to that color.
91
99
92 The color extension will try to detect whether to use terminfo, ANSI
100 By default, the color extension will use ANSI mode (or win32 mode on
93 codes or Win32 console APIs, unless it is made explicit; e.g.::
101 Windows) if it detects a terminal. To override auto mode (to enable
102 terminfo mode, for example), set the following configuration option::
94
103
95 [color]
104 [color]
96 mode = ansi
105 mode = terminfo
97
106
98 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
107 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
99 disable color.
108 disable color.
100
101 '''
109 '''
102
110
103 import os
111 import os
104
112
105 from mercurial import commands, dispatch, extensions, ui as uimod, util
113 from mercurial import commands, dispatch, extensions, ui as uimod, util
106 from mercurial.i18n import _
114 from mercurial.i18n import _
107
115
108 # start and stop parameters for effects
116 # start and stop parameters for effects
109 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
117 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
110 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
118 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
111 'italic': 3, 'underline': 4, 'inverse': 7,
119 'italic': 3, 'underline': 4, 'inverse': 7,
112 'black_background': 40, 'red_background': 41,
120 'black_background': 40, 'red_background': 41,
113 'green_background': 42, 'yellow_background': 43,
121 'green_background': 42, 'yellow_background': 43,
114 'blue_background': 44, 'purple_background': 45,
122 'blue_background': 44, 'purple_background': 45,
115 'cyan_background': 46, 'white_background': 47}
123 'cyan_background': 46, 'white_background': 47}
116
124
117 def _terminfosetup(ui, mode):
125 def _terminfosetup(ui, mode):
118 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
126 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
119
127
120 global _terminfo_params
128 global _terminfo_params
121 # If we failed to load curses, we go ahead and return.
129 # If we failed to load curses, we go ahead and return.
122 if not _terminfo_params:
130 if not _terminfo_params:
123 return
131 return
124 # Otherwise, see what the config file says.
132 # Otherwise, see what the config file says.
125 if mode not in ('auto', 'terminfo'):
133 if mode not in ('auto', 'terminfo'):
126 return
134 return
127
135
128 _terminfo_params.update((key[6:], (False, int(val)))
136 _terminfo_params.update((key[6:], (False, int(val)))
129 for key, val in ui.configitems('color')
137 for key, val in ui.configitems('color')
130 if key.startswith('color.'))
138 if key.startswith('color.'))
131
139
132 try:
140 try:
133 curses.setupterm()
141 curses.setupterm()
134 except curses.error, e:
142 except curses.error, e:
135 _terminfo_params = {}
143 _terminfo_params = {}
136 return
144 return
137
145
138 for key, (b, e) in _terminfo_params.items():
146 for key, (b, e) in _terminfo_params.items():
139 if not b:
147 if not b:
140 continue
148 continue
141 if not curses.tigetstr(e):
149 if not curses.tigetstr(e):
142 # Most terminals don't support dim, invis, etc, so don't be
150 # Most terminals don't support dim, invis, etc, so don't be
143 # noisy and use ui.debug().
151 # noisy and use ui.debug().
144 ui.debug("no terminfo entry for %s\n" % e)
152 ui.debug("no terminfo entry for %s\n" % e)
145 del _terminfo_params[key]
153 del _terminfo_params[key]
146 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
154 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
147 # Only warn about missing terminfo entries if we explicitly asked for
155 # Only warn about missing terminfo entries if we explicitly asked for
148 # terminfo mode.
156 # terminfo mode.
149 if mode == "terminfo":
157 if mode == "terminfo":
150 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
158 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
151 "ECMA-48 color\n"))
159 "ECMA-48 color\n"))
152 _terminfo_params = {}
160 _terminfo_params = {}
153
161
154 def _modesetup(ui, opts):
162 def _modesetup(ui, opts):
155 global _terminfo_params
163 global _terminfo_params
156
164
157 coloropt = opts['color']
165 coloropt = opts['color']
158 auto = coloropt == 'auto'
166 auto = coloropt == 'auto'
159 always = not auto and util.parsebool(coloropt)
167 always = not auto and util.parsebool(coloropt)
160 if not always and not auto:
168 if not always and not auto:
161 return None
169 return None
162
170
163 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
171 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
164
172
165 mode = ui.config('color', 'mode', 'auto')
173 mode = ui.config('color', 'mode', 'auto')
166 realmode = mode
174 realmode = mode
167 if mode == 'auto':
175 if mode == 'auto':
168 if os.name == 'nt' and 'TERM' not in os.environ:
176 if os.name == 'nt' and 'TERM' not in os.environ:
169 # looks line a cmd.exe console, use win32 API or nothing
177 # looks line a cmd.exe console, use win32 API or nothing
170 realmode = 'win32'
178 realmode = 'win32'
171 elif not formatted:
179 else:
172 realmode = 'ansi'
180 realmode = 'ansi'
173 else:
174 realmode = 'terminfo'
175
181
176 if realmode == 'win32':
182 if realmode == 'win32':
177 if not w32effects:
183 if not w32effects:
178 if mode == 'win32':
184 if mode == 'win32':
179 # only warn if color.mode is explicitly set to win32
185 # only warn if color.mode is explicitly set to win32
180 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
186 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
181 return None
187 return None
182 _effects.update(w32effects)
188 _effects.update(w32effects)
183 elif realmode == 'ansi':
189 elif realmode == 'ansi':
184 _terminfo_params = {}
190 _terminfo_params = {}
185 elif realmode == 'terminfo':
191 elif realmode == 'terminfo':
186 _terminfosetup(ui, mode)
192 _terminfosetup(ui, mode)
187 if not _terminfo_params:
193 if not _terminfo_params:
188 if mode == 'terminfo':
194 if mode == 'terminfo':
189 ## FIXME Shouldn't we return None in this case too?
195 ## FIXME Shouldn't we return None in this case too?
190 # only warn if color.mode is explicitly set to win32
196 # only warn if color.mode is explicitly set to win32
191 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
197 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
192 realmode = 'ansi'
198 realmode = 'ansi'
193 else:
199 else:
194 return None
200 return None
195
201
196 if always or (auto and formatted):
202 if always or (auto and formatted):
197 return realmode
203 return realmode
198 return None
204 return None
199
205
200 try:
206 try:
201 import curses
207 import curses
202 # Mapping from effect name to terminfo attribute name or color number.
208 # Mapping from effect name to terminfo attribute name or color number.
203 # This will also force-load the curses module.
209 # This will also force-load the curses module.
204 _terminfo_params = {'none': (True, 'sgr0'),
210 _terminfo_params = {'none': (True, 'sgr0'),
205 'standout': (True, 'smso'),
211 'standout': (True, 'smso'),
206 'underline': (True, 'smul'),
212 'underline': (True, 'smul'),
207 'reverse': (True, 'rev'),
213 'reverse': (True, 'rev'),
208 'inverse': (True, 'rev'),
214 'inverse': (True, 'rev'),
209 'blink': (True, 'blink'),
215 'blink': (True, 'blink'),
210 'dim': (True, 'dim'),
216 'dim': (True, 'dim'),
211 'bold': (True, 'bold'),
217 'bold': (True, 'bold'),
212 'invisible': (True, 'invis'),
218 'invisible': (True, 'invis'),
213 'italic': (True, 'sitm'),
219 'italic': (True, 'sitm'),
214 'black': (False, curses.COLOR_BLACK),
220 'black': (False, curses.COLOR_BLACK),
215 'red': (False, curses.COLOR_RED),
221 'red': (False, curses.COLOR_RED),
216 'green': (False, curses.COLOR_GREEN),
222 'green': (False, curses.COLOR_GREEN),
217 'yellow': (False, curses.COLOR_YELLOW),
223 'yellow': (False, curses.COLOR_YELLOW),
218 'blue': (False, curses.COLOR_BLUE),
224 'blue': (False, curses.COLOR_BLUE),
219 'magenta': (False, curses.COLOR_MAGENTA),
225 'magenta': (False, curses.COLOR_MAGENTA),
220 'cyan': (False, curses.COLOR_CYAN),
226 'cyan': (False, curses.COLOR_CYAN),
221 'white': (False, curses.COLOR_WHITE)}
227 'white': (False, curses.COLOR_WHITE)}
222 except ImportError:
228 except ImportError:
223 _terminfo_params = False
229 _terminfo_params = False
224
230
225 _styles = {'grep.match': 'red bold',
231 _styles = {'grep.match': 'red bold',
226 'bookmarks.current': 'green',
232 'bookmarks.current': 'green',
227 'branches.active': 'none',
233 'branches.active': 'none',
228 'branches.closed': 'black bold',
234 'branches.closed': 'black bold',
229 'branches.current': 'green',
235 'branches.current': 'green',
230 'branches.inactive': 'none',
236 'branches.inactive': 'none',
231 'diff.changed': 'white',
237 'diff.changed': 'white',
232 'diff.deleted': 'red',
238 'diff.deleted': 'red',
233 'diff.diffline': 'bold',
239 'diff.diffline': 'bold',
234 'diff.extended': 'cyan bold',
240 'diff.extended': 'cyan bold',
235 'diff.file_a': 'red bold',
241 'diff.file_a': 'red bold',
236 'diff.file_b': 'green bold',
242 'diff.file_b': 'green bold',
237 'diff.hunk': 'magenta',
243 'diff.hunk': 'magenta',
238 'diff.inserted': 'green',
244 'diff.inserted': 'green',
239 'diff.trailingwhitespace': 'bold red_background',
245 'diff.trailingwhitespace': 'bold red_background',
240 'diffstat.deleted': 'red',
246 'diffstat.deleted': 'red',
241 'diffstat.inserted': 'green',
247 'diffstat.inserted': 'green',
242 'ui.prompt': 'yellow',
248 'ui.prompt': 'yellow',
243 'log.changeset': 'yellow',
249 'log.changeset': 'yellow',
244 'resolve.resolved': 'green bold',
250 'resolve.resolved': 'green bold',
245 'resolve.unresolved': 'red bold',
251 'resolve.unresolved': 'red bold',
246 'status.added': 'green bold',
252 'status.added': 'green bold',
247 'status.clean': 'none',
253 'status.clean': 'none',
248 'status.copied': 'none',
254 'status.copied': 'none',
249 'status.deleted': 'cyan bold underline',
255 'status.deleted': 'cyan bold underline',
250 'status.ignored': 'black bold',
256 'status.ignored': 'black bold',
251 'status.modified': 'blue bold',
257 'status.modified': 'blue bold',
252 'status.removed': 'red bold',
258 'status.removed': 'red bold',
253 'status.unknown': 'magenta bold underline'}
259 'status.unknown': 'magenta bold underline'}
254
260
255
261
256 def _effect_str(effect):
262 def _effect_str(effect):
257 '''Helper function for render_effects().'''
263 '''Helper function for render_effects().'''
258
264
259 bg = False
265 bg = False
260 if effect.endswith('_background'):
266 if effect.endswith('_background'):
261 bg = True
267 bg = True
262 effect = effect[:-11]
268 effect = effect[:-11]
263 attr, val = _terminfo_params[effect]
269 attr, val = _terminfo_params[effect]
264 if attr:
270 if attr:
265 return curses.tigetstr(val)
271 return curses.tigetstr(val)
266 elif bg:
272 elif bg:
267 return curses.tparm(curses.tigetstr('setab'), val)
273 return curses.tparm(curses.tigetstr('setab'), val)
268 else:
274 else:
269 return curses.tparm(curses.tigetstr('setaf'), val)
275 return curses.tparm(curses.tigetstr('setaf'), val)
270
276
271 def render_effects(text, effects):
277 def render_effects(text, effects):
272 'Wrap text in commands to turn on each effect.'
278 'Wrap text in commands to turn on each effect.'
273 if not text:
279 if not text:
274 return text
280 return text
275 if not _terminfo_params:
281 if not _terminfo_params:
276 start = [str(_effects[e]) for e in ['none'] + effects.split()]
282 start = [str(_effects[e]) for e in ['none'] + effects.split()]
277 start = '\033[' + ';'.join(start) + 'm'
283 start = '\033[' + ';'.join(start) + 'm'
278 stop = '\033[' + str(_effects['none']) + 'm'
284 stop = '\033[' + str(_effects['none']) + 'm'
279 else:
285 else:
280 start = ''.join(_effect_str(effect)
286 start = ''.join(_effect_str(effect)
281 for effect in ['none'] + effects.split())
287 for effect in ['none'] + effects.split())
282 stop = _effect_str('none')
288 stop = _effect_str('none')
283 return ''.join([start, text, stop])
289 return ''.join([start, text, stop])
284
290
285 def extstyles():
291 def extstyles():
286 for name, ext in extensions.extensions():
292 for name, ext in extensions.extensions():
287 _styles.update(getattr(ext, 'colortable', {}))
293 _styles.update(getattr(ext, 'colortable', {}))
288
294
289 def configstyles(ui):
295 def configstyles(ui):
290 for status, cfgeffects in ui.configitems('color'):
296 for status, cfgeffects in ui.configitems('color'):
291 if '.' not in status or status.startswith('color.'):
297 if '.' not in status or status.startswith('color.'):
292 continue
298 continue
293 cfgeffects = ui.configlist('color', status)
299 cfgeffects = ui.configlist('color', status)
294 if cfgeffects:
300 if cfgeffects:
295 good = []
301 good = []
296 for e in cfgeffects:
302 for e in cfgeffects:
297 if not _terminfo_params and e in _effects:
303 if not _terminfo_params and e in _effects:
298 good.append(e)
304 good.append(e)
299 elif e in _terminfo_params or e[:-11] in _terminfo_params:
305 elif e in _terminfo_params or e[:-11] in _terminfo_params:
300 good.append(e)
306 good.append(e)
301 else:
307 else:
302 ui.warn(_("ignoring unknown color/effect %r "
308 ui.warn(_("ignoring unknown color/effect %r "
303 "(configured in color.%s)\n")
309 "(configured in color.%s)\n")
304 % (e, status))
310 % (e, status))
305 _styles[status] = ' '.join(good)
311 _styles[status] = ' '.join(good)
306
312
307 class colorui(uimod.ui):
313 class colorui(uimod.ui):
308 def popbuffer(self, labeled=False):
314 def popbuffer(self, labeled=False):
309 if labeled:
315 if labeled:
310 return ''.join(self.label(a, label) for a, label
316 return ''.join(self.label(a, label) for a, label
311 in self._buffers.pop())
317 in self._buffers.pop())
312 return ''.join(a for a, label in self._buffers.pop())
318 return ''.join(a for a, label in self._buffers.pop())
313
319
314 _colormode = 'ansi'
320 _colormode = 'ansi'
315 def write(self, *args, **opts):
321 def write(self, *args, **opts):
316 label = opts.get('label', '')
322 label = opts.get('label', '')
317 if self._buffers:
323 if self._buffers:
318 self._buffers[-1].extend([(str(a), label) for a in args])
324 self._buffers[-1].extend([(str(a), label) for a in args])
319 elif self._colormode == 'win32':
325 elif self._colormode == 'win32':
320 for a in args:
326 for a in args:
321 win32print(a, super(colorui, self).write, **opts)
327 win32print(a, super(colorui, self).write, **opts)
322 else:
328 else:
323 return super(colorui, self).write(
329 return super(colorui, self).write(
324 *[self.label(str(a), label) for a in args], **opts)
330 *[self.label(str(a), label) for a in args], **opts)
325
331
326 def write_err(self, *args, **opts):
332 def write_err(self, *args, **opts):
327 label = opts.get('label', '')
333 label = opts.get('label', '')
328 if self._colormode == 'win32':
334 if self._colormode == 'win32':
329 for a in args:
335 for a in args:
330 win32print(a, super(colorui, self).write_err, **opts)
336 win32print(a, super(colorui, self).write_err, **opts)
331 else:
337 else:
332 return super(colorui, self).write_err(
338 return super(colorui, self).write_err(
333 *[self.label(str(a), label) for a in args], **opts)
339 *[self.label(str(a), label) for a in args], **opts)
334
340
335 def label(self, msg, label):
341 def label(self, msg, label):
336 effects = []
342 effects = []
337 for l in label.split():
343 for l in label.split():
338 s = _styles.get(l, '')
344 s = _styles.get(l, '')
339 if s:
345 if s:
340 effects.append(s)
346 effects.append(s)
341 effects = ' '.join(effects)
347 effects = ' '.join(effects)
342 if effects:
348 if effects:
343 return '\n'.join([render_effects(s, effects)
349 return '\n'.join([render_effects(s, effects)
344 for s in msg.split('\n')])
350 for s in msg.split('\n')])
345 return msg
351 return msg
346
352
347
353
348 def uisetup(ui):
354 def uisetup(ui):
349 global _terminfo_params
355 global _terminfo_params
350 if ui.plain():
356 if ui.plain():
351 return
357 return
352 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
358 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
353 mode = _modesetup(ui_, opts)
359 mode = _modesetup(ui_, opts)
354 if mode:
360 if mode:
355 colorui._colormode = mode
361 colorui._colormode = mode
356 if not issubclass(ui_.__class__, colorui):
362 if not issubclass(ui_.__class__, colorui):
357 colorui.__bases__ = (ui_.__class__,)
363 colorui.__bases__ = (ui_.__class__,)
358 ui_.__class__ = colorui
364 ui_.__class__ = colorui
359 extstyles()
365 extstyles()
360 configstyles(ui_)
366 configstyles(ui_)
361 return orig(ui_, opts, cmd, cmdfunc)
367 return orig(ui_, opts, cmd, cmdfunc)
362 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
368 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
363
369
364 def extsetup(ui):
370 def extsetup(ui):
365 commands.globalopts.append(
371 commands.globalopts.append(
366 ('', 'color', 'auto',
372 ('', 'color', 'auto',
367 # i18n: 'always', 'auto', and 'never' are keywords and should
373 # i18n: 'always', 'auto', and 'never' are keywords and should
368 # not be translated
374 # not be translated
369 _("when to colorize (boolean, always, auto, or never)"),
375 _("when to colorize (boolean, always, auto, or never)"),
370 _('TYPE')))
376 _('TYPE')))
371
377
372 if os.name != 'nt':
378 if os.name != 'nt':
373 w32effects = None
379 w32effects = None
374 else:
380 else:
375 import re, ctypes
381 import re, ctypes
376
382
377 _kernel32 = ctypes.windll.kernel32
383 _kernel32 = ctypes.windll.kernel32
378
384
379 _WORD = ctypes.c_ushort
385 _WORD = ctypes.c_ushort
380
386
381 _INVALID_HANDLE_VALUE = -1
387 _INVALID_HANDLE_VALUE = -1
382
388
383 class _COORD(ctypes.Structure):
389 class _COORD(ctypes.Structure):
384 _fields_ = [('X', ctypes.c_short),
390 _fields_ = [('X', ctypes.c_short),
385 ('Y', ctypes.c_short)]
391 ('Y', ctypes.c_short)]
386
392
387 class _SMALL_RECT(ctypes.Structure):
393 class _SMALL_RECT(ctypes.Structure):
388 _fields_ = [('Left', ctypes.c_short),
394 _fields_ = [('Left', ctypes.c_short),
389 ('Top', ctypes.c_short),
395 ('Top', ctypes.c_short),
390 ('Right', ctypes.c_short),
396 ('Right', ctypes.c_short),
391 ('Bottom', ctypes.c_short)]
397 ('Bottom', ctypes.c_short)]
392
398
393 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
399 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
394 _fields_ = [('dwSize', _COORD),
400 _fields_ = [('dwSize', _COORD),
395 ('dwCursorPosition', _COORD),
401 ('dwCursorPosition', _COORD),
396 ('wAttributes', _WORD),
402 ('wAttributes', _WORD),
397 ('srWindow', _SMALL_RECT),
403 ('srWindow', _SMALL_RECT),
398 ('dwMaximumWindowSize', _COORD)]
404 ('dwMaximumWindowSize', _COORD)]
399
405
400 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
406 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
401 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
407 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
402
408
403 _FOREGROUND_BLUE = 0x0001
409 _FOREGROUND_BLUE = 0x0001
404 _FOREGROUND_GREEN = 0x0002
410 _FOREGROUND_GREEN = 0x0002
405 _FOREGROUND_RED = 0x0004
411 _FOREGROUND_RED = 0x0004
406 _FOREGROUND_INTENSITY = 0x0008
412 _FOREGROUND_INTENSITY = 0x0008
407
413
408 _BACKGROUND_BLUE = 0x0010
414 _BACKGROUND_BLUE = 0x0010
409 _BACKGROUND_GREEN = 0x0020
415 _BACKGROUND_GREEN = 0x0020
410 _BACKGROUND_RED = 0x0040
416 _BACKGROUND_RED = 0x0040
411 _BACKGROUND_INTENSITY = 0x0080
417 _BACKGROUND_INTENSITY = 0x0080
412
418
413 _COMMON_LVB_REVERSE_VIDEO = 0x4000
419 _COMMON_LVB_REVERSE_VIDEO = 0x4000
414 _COMMON_LVB_UNDERSCORE = 0x8000
420 _COMMON_LVB_UNDERSCORE = 0x8000
415
421
416 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
422 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
417 w32effects = {
423 w32effects = {
418 'none': -1,
424 'none': -1,
419 'black': 0,
425 'black': 0,
420 'red': _FOREGROUND_RED,
426 'red': _FOREGROUND_RED,
421 'green': _FOREGROUND_GREEN,
427 'green': _FOREGROUND_GREEN,
422 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
428 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
423 'blue': _FOREGROUND_BLUE,
429 'blue': _FOREGROUND_BLUE,
424 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
430 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
425 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
431 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
426 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
432 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
427 'bold': _FOREGROUND_INTENSITY,
433 'bold': _FOREGROUND_INTENSITY,
428 'black_background': 0x100, # unused value > 0x0f
434 'black_background': 0x100, # unused value > 0x0f
429 'red_background': _BACKGROUND_RED,
435 'red_background': _BACKGROUND_RED,
430 'green_background': _BACKGROUND_GREEN,
436 'green_background': _BACKGROUND_GREEN,
431 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
437 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
432 'blue_background': _BACKGROUND_BLUE,
438 'blue_background': _BACKGROUND_BLUE,
433 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
439 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
434 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
440 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
435 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
441 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
436 _BACKGROUND_BLUE),
442 _BACKGROUND_BLUE),
437 'bold_background': _BACKGROUND_INTENSITY,
443 'bold_background': _BACKGROUND_INTENSITY,
438 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
444 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
439 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
445 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
440 }
446 }
441
447
442 passthrough = set([_FOREGROUND_INTENSITY,
448 passthrough = set([_FOREGROUND_INTENSITY,
443 _BACKGROUND_INTENSITY,
449 _BACKGROUND_INTENSITY,
444 _COMMON_LVB_UNDERSCORE,
450 _COMMON_LVB_UNDERSCORE,
445 _COMMON_LVB_REVERSE_VIDEO])
451 _COMMON_LVB_REVERSE_VIDEO])
446
452
447 stdout = _kernel32.GetStdHandle(
453 stdout = _kernel32.GetStdHandle(
448 _STD_OUTPUT_HANDLE) # don't close the handle returned
454 _STD_OUTPUT_HANDLE) # don't close the handle returned
449 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
455 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
450 w32effects = None
456 w32effects = None
451 else:
457 else:
452 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
458 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
453 if not _kernel32.GetConsoleScreenBufferInfo(
459 if not _kernel32.GetConsoleScreenBufferInfo(
454 stdout, ctypes.byref(csbi)):
460 stdout, ctypes.byref(csbi)):
455 # stdout may not support GetConsoleScreenBufferInfo()
461 # stdout may not support GetConsoleScreenBufferInfo()
456 # when called from subprocess or redirected
462 # when called from subprocess or redirected
457 w32effects = None
463 w32effects = None
458 else:
464 else:
459 origattr = csbi.wAttributes
465 origattr = csbi.wAttributes
460 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
466 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
461 re.MULTILINE | re.DOTALL)
467 re.MULTILINE | re.DOTALL)
462
468
463 def win32print(text, orig, **opts):
469 def win32print(text, orig, **opts):
464 label = opts.get('label', '')
470 label = opts.get('label', '')
465 attr = origattr
471 attr = origattr
466
472
467 def mapcolor(val, attr):
473 def mapcolor(val, attr):
468 if val == -1:
474 if val == -1:
469 return origattr
475 return origattr
470 elif val in passthrough:
476 elif val in passthrough:
471 return attr | val
477 return attr | val
472 elif val > 0x0f:
478 elif val > 0x0f:
473 return (val & 0x70) | (attr & 0x8f)
479 return (val & 0x70) | (attr & 0x8f)
474 else:
480 else:
475 return (val & 0x07) | (attr & 0xf8)
481 return (val & 0x07) | (attr & 0xf8)
476
482
477 # determine console attributes based on labels
483 # determine console attributes based on labels
478 for l in label.split():
484 for l in label.split():
479 style = _styles.get(l, '')
485 style = _styles.get(l, '')
480 for effect in style.split():
486 for effect in style.split():
481 attr = mapcolor(w32effects[effect], attr)
487 attr = mapcolor(w32effects[effect], attr)
482
488
483 # hack to ensure regexp finds data
489 # hack to ensure regexp finds data
484 if not text.startswith('\033['):
490 if not text.startswith('\033['):
485 text = '\033[m' + text
491 text = '\033[m' + text
486
492
487 # Look for ANSI-like codes embedded in text
493 # Look for ANSI-like codes embedded in text
488 m = re.match(ansire, text)
494 m = re.match(ansire, text)
489
495
490 try:
496 try:
491 while m:
497 while m:
492 for sattr in m.group(1).split(';'):
498 for sattr in m.group(1).split(';'):
493 if sattr:
499 if sattr:
494 attr = mapcolor(int(sattr), attr)
500 attr = mapcolor(int(sattr), attr)
495 _kernel32.SetConsoleTextAttribute(stdout, attr)
501 _kernel32.SetConsoleTextAttribute(stdout, attr)
496 orig(m.group(2), **opts)
502 orig(m.group(2), **opts)
497 m = re.match(ansire, m.group(3))
503 m = re.match(ansire, m.group(3))
498 finally:
504 finally:
499 # Explicity reset original attributes
505 # Explicity reset original attributes
500 _kernel32.SetConsoleTextAttribute(stdout, origattr)
506 _kernel32.SetConsoleTextAttribute(stdout, origattr)
General Comments 0
You need to be logged in to leave comments. Login now