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