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