##// END OF EJS Templates
py3: handle keyword arguments correctly in color.py...
Pulkit Goyal -
r35352:a4478f74 default
parent child Browse files
Show More
@@ -1,529 +1,529
1 # utility for color output for Mercurial commands
1 # utility for color output for Mercurial commands
2 #
2 #
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> and other
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> and other
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13
13
14 from . import (
14 from . import (
15 encoding,
15 encoding,
16 pycompat,
16 pycompat,
17 util
17 util
18 )
18 )
19
19
20 try:
20 try:
21 import curses
21 import curses
22 # Mapping from effect name to terminfo attribute name (or raw code) or
22 # Mapping from effect name to terminfo attribute name (or raw code) or
23 # color number. This will also force-load the curses module.
23 # color number. This will also force-load the curses module.
24 _baseterminfoparams = {
24 _baseterminfoparams = {
25 'none': (True, 'sgr0', ''),
25 'none': (True, 'sgr0', ''),
26 'standout': (True, 'smso', ''),
26 'standout': (True, 'smso', ''),
27 'underline': (True, 'smul', ''),
27 'underline': (True, 'smul', ''),
28 'reverse': (True, 'rev', ''),
28 'reverse': (True, 'rev', ''),
29 'inverse': (True, 'rev', ''),
29 'inverse': (True, 'rev', ''),
30 'blink': (True, 'blink', ''),
30 'blink': (True, 'blink', ''),
31 'dim': (True, 'dim', ''),
31 'dim': (True, 'dim', ''),
32 'bold': (True, 'bold', ''),
32 'bold': (True, 'bold', ''),
33 'invisible': (True, 'invis', ''),
33 'invisible': (True, 'invis', ''),
34 'italic': (True, 'sitm', ''),
34 'italic': (True, 'sitm', ''),
35 'black': (False, curses.COLOR_BLACK, ''),
35 'black': (False, curses.COLOR_BLACK, ''),
36 'red': (False, curses.COLOR_RED, ''),
36 'red': (False, curses.COLOR_RED, ''),
37 'green': (False, curses.COLOR_GREEN, ''),
37 'green': (False, curses.COLOR_GREEN, ''),
38 'yellow': (False, curses.COLOR_YELLOW, ''),
38 'yellow': (False, curses.COLOR_YELLOW, ''),
39 'blue': (False, curses.COLOR_BLUE, ''),
39 'blue': (False, curses.COLOR_BLUE, ''),
40 'magenta': (False, curses.COLOR_MAGENTA, ''),
40 'magenta': (False, curses.COLOR_MAGENTA, ''),
41 'cyan': (False, curses.COLOR_CYAN, ''),
41 'cyan': (False, curses.COLOR_CYAN, ''),
42 'white': (False, curses.COLOR_WHITE, ''),
42 'white': (False, curses.COLOR_WHITE, ''),
43 }
43 }
44 except ImportError:
44 except ImportError:
45 curses = None
45 curses = None
46 _baseterminfoparams = {}
46 _baseterminfoparams = {}
47
47
48 # start and stop parameters for effects
48 # start and stop parameters for effects
49 _effects = {
49 _effects = {
50 'none': 0,
50 'none': 0,
51 'black': 30,
51 'black': 30,
52 'red': 31,
52 'red': 31,
53 'green': 32,
53 'green': 32,
54 'yellow': 33,
54 'yellow': 33,
55 'blue': 34,
55 'blue': 34,
56 'magenta': 35,
56 'magenta': 35,
57 'cyan': 36,
57 'cyan': 36,
58 'white': 37,
58 'white': 37,
59 'bold': 1,
59 'bold': 1,
60 'italic': 3,
60 'italic': 3,
61 'underline': 4,
61 'underline': 4,
62 'inverse': 7,
62 'inverse': 7,
63 'dim': 2,
63 'dim': 2,
64 'black_background': 40,
64 'black_background': 40,
65 'red_background': 41,
65 'red_background': 41,
66 'green_background': 42,
66 'green_background': 42,
67 'yellow_background': 43,
67 'yellow_background': 43,
68 'blue_background': 44,
68 'blue_background': 44,
69 'purple_background': 45,
69 'purple_background': 45,
70 'cyan_background': 46,
70 'cyan_background': 46,
71 'white_background': 47,
71 'white_background': 47,
72 }
72 }
73
73
74 _defaultstyles = {
74 _defaultstyles = {
75 'grep.match': 'red bold',
75 'grep.match': 'red bold',
76 'grep.linenumber': 'green',
76 'grep.linenumber': 'green',
77 'grep.rev': 'green',
77 'grep.rev': 'green',
78 'grep.change': 'green',
78 'grep.change': 'green',
79 'grep.sep': 'cyan',
79 'grep.sep': 'cyan',
80 'grep.filename': 'magenta',
80 'grep.filename': 'magenta',
81 'grep.user': 'magenta',
81 'grep.user': 'magenta',
82 'grep.date': 'magenta',
82 'grep.date': 'magenta',
83 'bookmarks.active': 'green',
83 'bookmarks.active': 'green',
84 'branches.active': 'none',
84 'branches.active': 'none',
85 'branches.closed': 'black bold',
85 'branches.closed': 'black bold',
86 'branches.current': 'green',
86 'branches.current': 'green',
87 'branches.inactive': 'none',
87 'branches.inactive': 'none',
88 'diff.changed': 'white',
88 'diff.changed': 'white',
89 'diff.deleted': 'red',
89 'diff.deleted': 'red',
90 'diff.deleted.highlight': 'red bold underline',
90 'diff.deleted.highlight': 'red bold underline',
91 'diff.diffline': 'bold',
91 'diff.diffline': 'bold',
92 'diff.extended': 'cyan bold',
92 'diff.extended': 'cyan bold',
93 'diff.file_a': 'red bold',
93 'diff.file_a': 'red bold',
94 'diff.file_b': 'green bold',
94 'diff.file_b': 'green bold',
95 'diff.hunk': 'magenta',
95 'diff.hunk': 'magenta',
96 'diff.inserted': 'green',
96 'diff.inserted': 'green',
97 'diff.inserted.highlight': 'green bold underline',
97 'diff.inserted.highlight': 'green bold underline',
98 'diff.tab': '',
98 'diff.tab': '',
99 'diff.trailingwhitespace': 'bold red_background',
99 'diff.trailingwhitespace': 'bold red_background',
100 'changeset.public': '',
100 'changeset.public': '',
101 'changeset.draft': '',
101 'changeset.draft': '',
102 'changeset.secret': '',
102 'changeset.secret': '',
103 'diffstat.deleted': 'red',
103 'diffstat.deleted': 'red',
104 'diffstat.inserted': 'green',
104 'diffstat.inserted': 'green',
105 'formatvariant.name.mismatchconfig': 'red',
105 'formatvariant.name.mismatchconfig': 'red',
106 'formatvariant.name.mismatchdefault': 'yellow',
106 'formatvariant.name.mismatchdefault': 'yellow',
107 'formatvariant.name.uptodate': 'green',
107 'formatvariant.name.uptodate': 'green',
108 'formatvariant.repo.mismatchconfig': 'red',
108 'formatvariant.repo.mismatchconfig': 'red',
109 'formatvariant.repo.mismatchdefault': 'yellow',
109 'formatvariant.repo.mismatchdefault': 'yellow',
110 'formatvariant.repo.uptodate': 'green',
110 'formatvariant.repo.uptodate': 'green',
111 'formatvariant.config.special': 'yellow',
111 'formatvariant.config.special': 'yellow',
112 'formatvariant.config.default': 'green',
112 'formatvariant.config.default': 'green',
113 'formatvariant.default': '',
113 'formatvariant.default': '',
114 'histedit.remaining': 'red bold',
114 'histedit.remaining': 'red bold',
115 'ui.prompt': 'yellow',
115 'ui.prompt': 'yellow',
116 'log.changeset': 'yellow',
116 'log.changeset': 'yellow',
117 'patchbomb.finalsummary': '',
117 'patchbomb.finalsummary': '',
118 'patchbomb.from': 'magenta',
118 'patchbomb.from': 'magenta',
119 'patchbomb.to': 'cyan',
119 'patchbomb.to': 'cyan',
120 'patchbomb.subject': 'green',
120 'patchbomb.subject': 'green',
121 'patchbomb.diffstats': '',
121 'patchbomb.diffstats': '',
122 'rebase.rebased': 'blue',
122 'rebase.rebased': 'blue',
123 'rebase.remaining': 'red bold',
123 'rebase.remaining': 'red bold',
124 'resolve.resolved': 'green bold',
124 'resolve.resolved': 'green bold',
125 'resolve.unresolved': 'red bold',
125 'resolve.unresolved': 'red bold',
126 'shelve.age': 'cyan',
126 'shelve.age': 'cyan',
127 'shelve.newest': 'green bold',
127 'shelve.newest': 'green bold',
128 'shelve.name': 'blue bold',
128 'shelve.name': 'blue bold',
129 'status.added': 'green bold',
129 'status.added': 'green bold',
130 'status.clean': 'none',
130 'status.clean': 'none',
131 'status.copied': 'none',
131 'status.copied': 'none',
132 'status.deleted': 'cyan bold underline',
132 'status.deleted': 'cyan bold underline',
133 'status.ignored': 'black bold',
133 'status.ignored': 'black bold',
134 'status.modified': 'blue bold',
134 'status.modified': 'blue bold',
135 'status.removed': 'red bold',
135 'status.removed': 'red bold',
136 'status.unknown': 'magenta bold underline',
136 'status.unknown': 'magenta bold underline',
137 'tags.normal': 'green',
137 'tags.normal': 'green',
138 'tags.local': 'black bold',
138 'tags.local': 'black bold',
139 }
139 }
140
140
141 def loadcolortable(ui, extname, colortable):
141 def loadcolortable(ui, extname, colortable):
142 _defaultstyles.update(colortable)
142 _defaultstyles.update(colortable)
143
143
144 def _terminfosetup(ui, mode, formatted):
144 def _terminfosetup(ui, mode, formatted):
145 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
145 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
146
146
147 # If we failed to load curses, we go ahead and return.
147 # If we failed to load curses, we go ahead and return.
148 if curses is None:
148 if curses is None:
149 return
149 return
150 # Otherwise, see what the config file says.
150 # Otherwise, see what the config file says.
151 if mode not in ('auto', 'terminfo'):
151 if mode not in ('auto', 'terminfo'):
152 return
152 return
153 ui._terminfoparams.update(_baseterminfoparams)
153 ui._terminfoparams.update(_baseterminfoparams)
154
154
155 for key, val in ui.configitems('color'):
155 for key, val in ui.configitems('color'):
156 if key.startswith('color.'):
156 if key.startswith('color.'):
157 newval = (False, int(val), '')
157 newval = (False, int(val), '')
158 ui._terminfoparams[key[6:]] = newval
158 ui._terminfoparams[key[6:]] = newval
159 elif key.startswith('terminfo.'):
159 elif key.startswith('terminfo.'):
160 newval = (True, '', val.replace('\\E', '\x1b'))
160 newval = (True, '', val.replace('\\E', '\x1b'))
161 ui._terminfoparams[key[9:]] = newval
161 ui._terminfoparams[key[9:]] = newval
162 try:
162 try:
163 curses.setupterm()
163 curses.setupterm()
164 except curses.error as e:
164 except curses.error as e:
165 ui._terminfoparams.clear()
165 ui._terminfoparams.clear()
166 return
166 return
167
167
168 for key, (b, e, c) in ui._terminfoparams.items():
168 for key, (b, e, c) in ui._terminfoparams.items():
169 if not b:
169 if not b:
170 continue
170 continue
171 if not c and not curses.tigetstr(e):
171 if not c and not curses.tigetstr(e):
172 # Most terminals don't support dim, invis, etc, so don't be
172 # Most terminals don't support dim, invis, etc, so don't be
173 # noisy and use ui.debug().
173 # noisy and use ui.debug().
174 ui.debug("no terminfo entry for %s\n" % e)
174 ui.debug("no terminfo entry for %s\n" % e)
175 del ui._terminfoparams[key]
175 del ui._terminfoparams[key]
176 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
176 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
177 # Only warn about missing terminfo entries if we explicitly asked for
177 # Only warn about missing terminfo entries if we explicitly asked for
178 # terminfo mode and we're in a formatted terminal.
178 # terminfo mode and we're in a formatted terminal.
179 if mode == "terminfo" and formatted:
179 if mode == "terminfo" and formatted:
180 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
180 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
181 "ECMA-48 color\n"))
181 "ECMA-48 color\n"))
182 ui._terminfoparams.clear()
182 ui._terminfoparams.clear()
183
183
184 def setup(ui):
184 def setup(ui):
185 """configure color on a ui
185 """configure color on a ui
186
186
187 That function both set the colormode for the ui object and read
187 That function both set the colormode for the ui object and read
188 the configuration looking for custom colors and effect definitions."""
188 the configuration looking for custom colors and effect definitions."""
189 mode = _modesetup(ui)
189 mode = _modesetup(ui)
190 ui._colormode = mode
190 ui._colormode = mode
191 if mode and mode != 'debug':
191 if mode and mode != 'debug':
192 configstyles(ui)
192 configstyles(ui)
193
193
194 def _modesetup(ui):
194 def _modesetup(ui):
195 if ui.plain('color'):
195 if ui.plain('color'):
196 return None
196 return None
197 config = ui.config('ui', 'color')
197 config = ui.config('ui', 'color')
198 if config == 'debug':
198 if config == 'debug':
199 return 'debug'
199 return 'debug'
200
200
201 auto = (config == 'auto')
201 auto = (config == 'auto')
202 always = False
202 always = False
203 if not auto and util.parsebool(config):
203 if not auto and util.parsebool(config):
204 # We want the config to behave like a boolean, "on" is actually auto,
204 # We want the config to behave like a boolean, "on" is actually auto,
205 # but "always" value is treated as a special case to reduce confusion.
205 # but "always" value is treated as a special case to reduce confusion.
206 if ui.configsource('ui', 'color') == '--color' or config == 'always':
206 if ui.configsource('ui', 'color') == '--color' or config == 'always':
207 always = True
207 always = True
208 else:
208 else:
209 auto = True
209 auto = True
210
210
211 if not always and not auto:
211 if not always and not auto:
212 return None
212 return None
213
213
214 formatted = (always or (encoding.environ.get('TERM') != 'dumb'
214 formatted = (always or (encoding.environ.get('TERM') != 'dumb'
215 and ui.formatted()))
215 and ui.formatted()))
216
216
217 mode = ui.config('color', 'mode')
217 mode = ui.config('color', 'mode')
218
218
219 # If pager is active, color.pagermode overrides color.mode.
219 # If pager is active, color.pagermode overrides color.mode.
220 if getattr(ui, 'pageractive', False):
220 if getattr(ui, 'pageractive', False):
221 mode = ui.config('color', 'pagermode', mode)
221 mode = ui.config('color', 'pagermode', mode)
222
222
223 realmode = mode
223 realmode = mode
224 if pycompat.iswindows:
224 if pycompat.iswindows:
225 from . import win32
225 from . import win32
226
226
227 term = encoding.environ.get('TERM')
227 term = encoding.environ.get('TERM')
228 # TERM won't be defined in a vanilla cmd.exe environment.
228 # TERM won't be defined in a vanilla cmd.exe environment.
229
229
230 # UNIX-like environments on Windows such as Cygwin and MSYS will
230 # UNIX-like environments on Windows such as Cygwin and MSYS will
231 # set TERM. They appear to make a best effort attempt at setting it
231 # set TERM. They appear to make a best effort attempt at setting it
232 # to something appropriate. However, not all environments with TERM
232 # to something appropriate. However, not all environments with TERM
233 # defined support ANSI.
233 # defined support ANSI.
234 ansienviron = term and 'xterm' in term
234 ansienviron = term and 'xterm' in term
235
235
236 if mode == 'auto':
236 if mode == 'auto':
237 # Since "ansi" could result in terminal gibberish, we error on the
237 # Since "ansi" could result in terminal gibberish, we error on the
238 # side of selecting "win32". However, if w32effects is not defined,
238 # side of selecting "win32". However, if w32effects is not defined,
239 # we almost certainly don't support "win32", so don't even try.
239 # we almost certainly don't support "win32", so don't even try.
240 # w32ffects is not populated when stdout is redirected, so checking
240 # w32ffects is not populated when stdout is redirected, so checking
241 # it first avoids win32 calls in a state known to error out.
241 # it first avoids win32 calls in a state known to error out.
242 if ansienviron or not w32effects or win32.enablevtmode():
242 if ansienviron or not w32effects or win32.enablevtmode():
243 realmode = 'ansi'
243 realmode = 'ansi'
244 else:
244 else:
245 realmode = 'win32'
245 realmode = 'win32'
246 # An empty w32effects is a clue that stdout is redirected, and thus
246 # An empty w32effects is a clue that stdout is redirected, and thus
247 # cannot enable VT mode.
247 # cannot enable VT mode.
248 elif mode == 'ansi' and w32effects and not ansienviron:
248 elif mode == 'ansi' and w32effects and not ansienviron:
249 win32.enablevtmode()
249 win32.enablevtmode()
250 elif mode == 'auto':
250 elif mode == 'auto':
251 realmode = 'ansi'
251 realmode = 'ansi'
252
252
253 def modewarn():
253 def modewarn():
254 # only warn if color.mode was explicitly set and we're in
254 # only warn if color.mode was explicitly set and we're in
255 # a formatted terminal
255 # a formatted terminal
256 if mode == realmode and formatted:
256 if mode == realmode and formatted:
257 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
257 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
258
258
259 if realmode == 'win32':
259 if realmode == 'win32':
260 ui._terminfoparams.clear()
260 ui._terminfoparams.clear()
261 if not w32effects:
261 if not w32effects:
262 modewarn()
262 modewarn()
263 return None
263 return None
264 elif realmode == 'ansi':
264 elif realmode == 'ansi':
265 ui._terminfoparams.clear()
265 ui._terminfoparams.clear()
266 elif realmode == 'terminfo':
266 elif realmode == 'terminfo':
267 _terminfosetup(ui, mode, formatted)
267 _terminfosetup(ui, mode, formatted)
268 if not ui._terminfoparams:
268 if not ui._terminfoparams:
269 ## FIXME Shouldn't we return None in this case too?
269 ## FIXME Shouldn't we return None in this case too?
270 modewarn()
270 modewarn()
271 realmode = 'ansi'
271 realmode = 'ansi'
272 else:
272 else:
273 return None
273 return None
274
274
275 if always or (auto and formatted):
275 if always or (auto and formatted):
276 return realmode
276 return realmode
277 return None
277 return None
278
278
279 def configstyles(ui):
279 def configstyles(ui):
280 ui._styles.update(_defaultstyles)
280 ui._styles.update(_defaultstyles)
281 for status, cfgeffects in ui.configitems('color'):
281 for status, cfgeffects in ui.configitems('color'):
282 if '.' not in status or status.startswith(('color.', 'terminfo.')):
282 if '.' not in status or status.startswith(('color.', 'terminfo.')):
283 continue
283 continue
284 cfgeffects = ui.configlist('color', status)
284 cfgeffects = ui.configlist('color', status)
285 if cfgeffects:
285 if cfgeffects:
286 good = []
286 good = []
287 for e in cfgeffects:
287 for e in cfgeffects:
288 if valideffect(ui, e):
288 if valideffect(ui, e):
289 good.append(e)
289 good.append(e)
290 else:
290 else:
291 ui.warn(_("ignoring unknown color/effect %r "
291 ui.warn(_("ignoring unknown color/effect %r "
292 "(configured in color.%s)\n")
292 "(configured in color.%s)\n")
293 % (e, status))
293 % (e, status))
294 ui._styles[status] = ' '.join(good)
294 ui._styles[status] = ' '.join(good)
295
295
296 def _activeeffects(ui):
296 def _activeeffects(ui):
297 '''Return the effects map for the color mode set on the ui.'''
297 '''Return the effects map for the color mode set on the ui.'''
298 if ui._colormode == 'win32':
298 if ui._colormode == 'win32':
299 return w32effects
299 return w32effects
300 elif ui._colormode is not None:
300 elif ui._colormode is not None:
301 return _effects
301 return _effects
302 return {}
302 return {}
303
303
304 def valideffect(ui, effect):
304 def valideffect(ui, effect):
305 'Determine if the effect is valid or not.'
305 'Determine if the effect is valid or not.'
306 return ((not ui._terminfoparams and effect in _activeeffects(ui))
306 return ((not ui._terminfoparams and effect in _activeeffects(ui))
307 or (effect in ui._terminfoparams
307 or (effect in ui._terminfoparams
308 or effect[:-11] in ui._terminfoparams))
308 or effect[:-11] in ui._terminfoparams))
309
309
310 def _effect_str(ui, effect):
310 def _effect_str(ui, effect):
311 '''Helper function for render_effects().'''
311 '''Helper function for render_effects().'''
312
312
313 bg = False
313 bg = False
314 if effect.endswith('_background'):
314 if effect.endswith('_background'):
315 bg = True
315 bg = True
316 effect = effect[:-11]
316 effect = effect[:-11]
317 try:
317 try:
318 attr, val, termcode = ui._terminfoparams[effect]
318 attr, val, termcode = ui._terminfoparams[effect]
319 except KeyError:
319 except KeyError:
320 return ''
320 return ''
321 if attr:
321 if attr:
322 if termcode:
322 if termcode:
323 return termcode
323 return termcode
324 else:
324 else:
325 return curses.tigetstr(val)
325 return curses.tigetstr(val)
326 elif bg:
326 elif bg:
327 return curses.tparm(curses.tigetstr('setab'), val)
327 return curses.tparm(curses.tigetstr('setab'), val)
328 else:
328 else:
329 return curses.tparm(curses.tigetstr('setaf'), val)
329 return curses.tparm(curses.tigetstr('setaf'), val)
330
330
331 def _mergeeffects(text, start, stop):
331 def _mergeeffects(text, start, stop):
332 """Insert start sequence at every occurrence of stop sequence
332 """Insert start sequence at every occurrence of stop sequence
333
333
334 >>> s = _mergeeffects(b'cyan', b'[C]', b'|')
334 >>> s = _mergeeffects(b'cyan', b'[C]', b'|')
335 >>> s = _mergeeffects(s + b'yellow', b'[Y]', b'|')
335 >>> s = _mergeeffects(s + b'yellow', b'[Y]', b'|')
336 >>> s = _mergeeffects(b'ma' + s + b'genta', b'[M]', b'|')
336 >>> s = _mergeeffects(b'ma' + s + b'genta', b'[M]', b'|')
337 >>> s = _mergeeffects(b'red' + s, b'[R]', b'|')
337 >>> s = _mergeeffects(b'red' + s, b'[R]', b'|')
338 >>> s
338 >>> s
339 '[R]red[M]ma[Y][C]cyan|[R][M][Y]yellow|[R][M]genta|'
339 '[R]red[M]ma[Y][C]cyan|[R][M][Y]yellow|[R][M]genta|'
340 """
340 """
341 parts = []
341 parts = []
342 for t in text.split(stop):
342 for t in text.split(stop):
343 if not t:
343 if not t:
344 continue
344 continue
345 parts.extend([start, t, stop])
345 parts.extend([start, t, stop])
346 return ''.join(parts)
346 return ''.join(parts)
347
347
348 def _render_effects(ui, text, effects):
348 def _render_effects(ui, text, effects):
349 'Wrap text in commands to turn on each effect.'
349 'Wrap text in commands to turn on each effect.'
350 if not text:
350 if not text:
351 return text
351 return text
352 if ui._terminfoparams:
352 if ui._terminfoparams:
353 start = ''.join(_effect_str(ui, effect)
353 start = ''.join(_effect_str(ui, effect)
354 for effect in ['none'] + effects.split())
354 for effect in ['none'] + effects.split())
355 stop = _effect_str(ui, 'none')
355 stop = _effect_str(ui, 'none')
356 else:
356 else:
357 activeeffects = _activeeffects(ui)
357 activeeffects = _activeeffects(ui)
358 start = [pycompat.bytestr(activeeffects[e])
358 start = [pycompat.bytestr(activeeffects[e])
359 for e in ['none'] + effects.split()]
359 for e in ['none'] + effects.split()]
360 start = '\033[' + ';'.join(start) + 'm'
360 start = '\033[' + ';'.join(start) + 'm'
361 stop = '\033[' + pycompat.bytestr(activeeffects['none']) + 'm'
361 stop = '\033[' + pycompat.bytestr(activeeffects['none']) + 'm'
362 return _mergeeffects(text, start, stop)
362 return _mergeeffects(text, start, stop)
363
363
364 _ansieffectre = re.compile(br'\x1b\[[0-9;]*m')
364 _ansieffectre = re.compile(br'\x1b\[[0-9;]*m')
365
365
366 def stripeffects(text):
366 def stripeffects(text):
367 """Strip ANSI control codes which could be inserted by colorlabel()"""
367 """Strip ANSI control codes which could be inserted by colorlabel()"""
368 return _ansieffectre.sub('', text)
368 return _ansieffectre.sub('', text)
369
369
370 def colorlabel(ui, msg, label):
370 def colorlabel(ui, msg, label):
371 """add color control code according to the mode"""
371 """add color control code according to the mode"""
372 if ui._colormode == 'debug':
372 if ui._colormode == 'debug':
373 if label and msg:
373 if label and msg:
374 if msg[-1] == '\n':
374 if msg[-1] == '\n':
375 msg = "[%s|%s]\n" % (label, msg[:-1])
375 msg = "[%s|%s]\n" % (label, msg[:-1])
376 else:
376 else:
377 msg = "[%s|%s]" % (label, msg)
377 msg = "[%s|%s]" % (label, msg)
378 elif ui._colormode is not None:
378 elif ui._colormode is not None:
379 effects = []
379 effects = []
380 for l in label.split():
380 for l in label.split():
381 s = ui._styles.get(l, '')
381 s = ui._styles.get(l, '')
382 if s:
382 if s:
383 effects.append(s)
383 effects.append(s)
384 elif valideffect(ui, l):
384 elif valideffect(ui, l):
385 effects.append(l)
385 effects.append(l)
386 effects = ' '.join(effects)
386 effects = ' '.join(effects)
387 if effects:
387 if effects:
388 msg = '\n'.join([_render_effects(ui, line, effects)
388 msg = '\n'.join([_render_effects(ui, line, effects)
389 for line in msg.split('\n')])
389 for line in msg.split('\n')])
390 return msg
390 return msg
391
391
392 w32effects = None
392 w32effects = None
393 if pycompat.iswindows:
393 if pycompat.iswindows:
394 import ctypes
394 import ctypes
395
395
396 _kernel32 = ctypes.windll.kernel32
396 _kernel32 = ctypes.windll.kernel32
397
397
398 _WORD = ctypes.c_ushort
398 _WORD = ctypes.c_ushort
399
399
400 _INVALID_HANDLE_VALUE = -1
400 _INVALID_HANDLE_VALUE = -1
401
401
402 class _COORD(ctypes.Structure):
402 class _COORD(ctypes.Structure):
403 _fields_ = [('X', ctypes.c_short),
403 _fields_ = [('X', ctypes.c_short),
404 ('Y', ctypes.c_short)]
404 ('Y', ctypes.c_short)]
405
405
406 class _SMALL_RECT(ctypes.Structure):
406 class _SMALL_RECT(ctypes.Structure):
407 _fields_ = [('Left', ctypes.c_short),
407 _fields_ = [('Left', ctypes.c_short),
408 ('Top', ctypes.c_short),
408 ('Top', ctypes.c_short),
409 ('Right', ctypes.c_short),
409 ('Right', ctypes.c_short),
410 ('Bottom', ctypes.c_short)]
410 ('Bottom', ctypes.c_short)]
411
411
412 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
412 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
413 _fields_ = [('dwSize', _COORD),
413 _fields_ = [('dwSize', _COORD),
414 ('dwCursorPosition', _COORD),
414 ('dwCursorPosition', _COORD),
415 ('wAttributes', _WORD),
415 ('wAttributes', _WORD),
416 ('srWindow', _SMALL_RECT),
416 ('srWindow', _SMALL_RECT),
417 ('dwMaximumWindowSize', _COORD)]
417 ('dwMaximumWindowSize', _COORD)]
418
418
419 _STD_OUTPUT_HANDLE = 0xfffffff5 # (DWORD)-11
419 _STD_OUTPUT_HANDLE = 0xfffffff5 # (DWORD)-11
420 _STD_ERROR_HANDLE = 0xfffffff4 # (DWORD)-12
420 _STD_ERROR_HANDLE = 0xfffffff4 # (DWORD)-12
421
421
422 _FOREGROUND_BLUE = 0x0001
422 _FOREGROUND_BLUE = 0x0001
423 _FOREGROUND_GREEN = 0x0002
423 _FOREGROUND_GREEN = 0x0002
424 _FOREGROUND_RED = 0x0004
424 _FOREGROUND_RED = 0x0004
425 _FOREGROUND_INTENSITY = 0x0008
425 _FOREGROUND_INTENSITY = 0x0008
426
426
427 _BACKGROUND_BLUE = 0x0010
427 _BACKGROUND_BLUE = 0x0010
428 _BACKGROUND_GREEN = 0x0020
428 _BACKGROUND_GREEN = 0x0020
429 _BACKGROUND_RED = 0x0040
429 _BACKGROUND_RED = 0x0040
430 _BACKGROUND_INTENSITY = 0x0080
430 _BACKGROUND_INTENSITY = 0x0080
431
431
432 _COMMON_LVB_REVERSE_VIDEO = 0x4000
432 _COMMON_LVB_REVERSE_VIDEO = 0x4000
433 _COMMON_LVB_UNDERSCORE = 0x8000
433 _COMMON_LVB_UNDERSCORE = 0x8000
434
434
435 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
435 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
436 w32effects = {
436 w32effects = {
437 'none': -1,
437 'none': -1,
438 'black': 0,
438 'black': 0,
439 'red': _FOREGROUND_RED,
439 'red': _FOREGROUND_RED,
440 'green': _FOREGROUND_GREEN,
440 'green': _FOREGROUND_GREEN,
441 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
441 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
442 'blue': _FOREGROUND_BLUE,
442 'blue': _FOREGROUND_BLUE,
443 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
443 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
444 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
444 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
445 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
445 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
446 'bold': _FOREGROUND_INTENSITY,
446 'bold': _FOREGROUND_INTENSITY,
447 'black_background': 0x100, # unused value > 0x0f
447 'black_background': 0x100, # unused value > 0x0f
448 'red_background': _BACKGROUND_RED,
448 'red_background': _BACKGROUND_RED,
449 'green_background': _BACKGROUND_GREEN,
449 'green_background': _BACKGROUND_GREEN,
450 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
450 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
451 'blue_background': _BACKGROUND_BLUE,
451 'blue_background': _BACKGROUND_BLUE,
452 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
452 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
453 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
453 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
454 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
454 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
455 _BACKGROUND_BLUE),
455 _BACKGROUND_BLUE),
456 'bold_background': _BACKGROUND_INTENSITY,
456 'bold_background': _BACKGROUND_INTENSITY,
457 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
457 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
458 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
458 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
459 }
459 }
460
460
461 passthrough = {_FOREGROUND_INTENSITY,
461 passthrough = {_FOREGROUND_INTENSITY,
462 _BACKGROUND_INTENSITY,
462 _BACKGROUND_INTENSITY,
463 _COMMON_LVB_UNDERSCORE,
463 _COMMON_LVB_UNDERSCORE,
464 _COMMON_LVB_REVERSE_VIDEO}
464 _COMMON_LVB_REVERSE_VIDEO}
465
465
466 stdout = _kernel32.GetStdHandle(
466 stdout = _kernel32.GetStdHandle(
467 _STD_OUTPUT_HANDLE) # don't close the handle returned
467 _STD_OUTPUT_HANDLE) # don't close the handle returned
468 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
468 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
469 w32effects = None
469 w32effects = None
470 else:
470 else:
471 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
471 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
472 if not _kernel32.GetConsoleScreenBufferInfo(
472 if not _kernel32.GetConsoleScreenBufferInfo(
473 stdout, ctypes.byref(csbi)):
473 stdout, ctypes.byref(csbi)):
474 # stdout may not support GetConsoleScreenBufferInfo()
474 # stdout may not support GetConsoleScreenBufferInfo()
475 # when called from subprocess or redirected
475 # when called from subprocess or redirected
476 w32effects = None
476 w32effects = None
477 else:
477 else:
478 origattr = csbi.wAttributes
478 origattr = csbi.wAttributes
479 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
479 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
480 re.MULTILINE | re.DOTALL)
480 re.MULTILINE | re.DOTALL)
481
481
482 def win32print(ui, writefunc, *msgs, **opts):
482 def win32print(ui, writefunc, *msgs, **opts):
483 for text in msgs:
483 for text in msgs:
484 _win32print(ui, text, writefunc, **opts)
484 _win32print(ui, text, writefunc, **opts)
485
485
486 def _win32print(ui, text, writefunc, **opts):
486 def _win32print(ui, text, writefunc, **opts):
487 label = opts.get('label', '')
487 label = opts.get(r'label', '')
488 attr = origattr
488 attr = origattr
489
489
490 def mapcolor(val, attr):
490 def mapcolor(val, attr):
491 if val == -1:
491 if val == -1:
492 return origattr
492 return origattr
493 elif val in passthrough:
493 elif val in passthrough:
494 return attr | val
494 return attr | val
495 elif val > 0x0f:
495 elif val > 0x0f:
496 return (val & 0x70) | (attr & 0x8f)
496 return (val & 0x70) | (attr & 0x8f)
497 else:
497 else:
498 return (val & 0x07) | (attr & 0xf8)
498 return (val & 0x07) | (attr & 0xf8)
499
499
500 # determine console attributes based on labels
500 # determine console attributes based on labels
501 for l in label.split():
501 for l in label.split():
502 style = ui._styles.get(l, '')
502 style = ui._styles.get(l, '')
503 for effect in style.split():
503 for effect in style.split():
504 try:
504 try:
505 attr = mapcolor(w32effects[effect], attr)
505 attr = mapcolor(w32effects[effect], attr)
506 except KeyError:
506 except KeyError:
507 # w32effects could not have certain attributes so we skip
507 # w32effects could not have certain attributes so we skip
508 # them if not found
508 # them if not found
509 pass
509 pass
510 # hack to ensure regexp finds data
510 # hack to ensure regexp finds data
511 if not text.startswith('\033['):
511 if not text.startswith('\033['):
512 text = '\033[m' + text
512 text = '\033[m' + text
513
513
514 # Look for ANSI-like codes embedded in text
514 # Look for ANSI-like codes embedded in text
515 m = re.match(ansire, text)
515 m = re.match(ansire, text)
516
516
517 try:
517 try:
518 while m:
518 while m:
519 for sattr in m.group(1).split(';'):
519 for sattr in m.group(1).split(';'):
520 if sattr:
520 if sattr:
521 attr = mapcolor(int(sattr), attr)
521 attr = mapcolor(int(sattr), attr)
522 ui.flush()
522 ui.flush()
523 _kernel32.SetConsoleTextAttribute(stdout, attr)
523 _kernel32.SetConsoleTextAttribute(stdout, attr)
524 writefunc(m.group(2), **opts)
524 writefunc(m.group(2), **opts)
525 m = re.match(ansire, m.group(3))
525 m = re.match(ansire, m.group(3))
526 finally:
526 finally:
527 # Explicitly reset original attributes
527 # Explicitly reset original attributes
528 ui.flush()
528 ui.flush()
529 _kernel32.SetConsoleTextAttribute(stdout, origattr)
529 _kernel32.SetConsoleTextAttribute(stdout, origattr)
General Comments 0
You need to be logged in to leave comments. Login now