##// END OF EJS Templates
color: be more conservative about setting ANSI mode on Windows (BC)...
Gregory Szorc -
r24028:a78888d9 default
parent child Browse files
Show More
@@ -1,653 +1,667 b''
1 # color.py color output for Mercurial commands
1 # color.py color output for Mercurial commands
2 #
2 #
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 #
4 #
5 # This 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 '''colorize output from some commands
8 '''colorize output from some commands
9
9
10 The color extension colorizes output from several Mercurial commands.
10 The color extension colorizes output from several Mercurial commands.
11 For example, the diff command shows additions in green and deletions
11 For example, the diff command shows additions in green and deletions
12 in red, while the status command shows modified files in magenta. Many
12 in red, while the status command shows modified files in magenta. Many
13 other commands have analogous colors. It is possible to customize
13 other commands have analogous colors. It is possible to customize
14 these colors.
14 these colors.
15
15
16 Effects
16 Effects
17 -------
17 -------
18
18
19 Other effects in addition to color, like bold and underlined text, are
19 Other effects in addition to color, like bold and underlined text, are
20 also available. By default, the terminfo database is used to find the
20 also available. By default, the terminfo database is used to find the
21 terminal codes used to change color and effect. If terminfo is not
21 terminal codes used to change color and effect. If terminfo is not
22 available, then effects are rendered with the ECMA-48 SGR control
22 available, then effects are rendered with the ECMA-48 SGR control
23 function (aka ANSI escape codes).
23 function (aka ANSI escape codes).
24
24
25 The available effects in terminfo mode are 'blink', 'bold', 'dim',
25 The available effects in terminfo mode are 'blink', 'bold', 'dim',
26 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
26 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
27 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
27 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
28 'underline'. How each is rendered depends on the terminal emulator.
28 'underline'. How each is rendered depends on the terminal emulator.
29 Some may not be available for a given terminal type, and will be
29 Some may not be available for a given terminal type, and will be
30 silently ignored.
30 silently ignored.
31
31
32 Labels
32 Labels
33 ------
33 ------
34
34
35 Text receives color effects depending on the labels that it has. Many
35 Text receives color effects depending on the labels that it has. Many
36 default Mercurial commands emit labelled text. You can also define
36 default Mercurial commands emit labelled text. You can also define
37 your own labels in templates using the label function, see :hg:`help
37 your own labels in templates using the label function, see :hg:`help
38 templates`. A single portion of text may have more than one label. In
38 templates`. A single portion of text may have more than one label. In
39 that case, effects given to the last label will override any other
39 that case, effects given to the last label will override any other
40 effects. This includes the special "none" effect, which nullifies
40 effects. This includes the special "none" effect, which nullifies
41 other effects.
41 other effects.
42
42
43 Labels are normally invisible. In order to see these labels and their
43 Labels are normally invisible. In order to see these labels and their
44 position in the text, use the global --color=debug option. The same
44 position in the text, use the global --color=debug option. The same
45 anchor text may be associated to multiple labels, e.g.
45 anchor text may be associated to multiple labels, e.g.
46
46
47 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
47 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
48
48
49 The following are the default effects for some default labels. Default
49 The following are the default effects for some default labels. Default
50 effects may be overridden from your configuration file::
50 effects may be overridden from your configuration file::
51
51
52 [color]
52 [color]
53 status.modified = blue bold underline red_background
53 status.modified = blue bold underline red_background
54 status.added = green bold
54 status.added = green bold
55 status.removed = red bold blue_background
55 status.removed = red bold blue_background
56 status.deleted = cyan bold underline
56 status.deleted = cyan bold underline
57 status.unknown = magenta bold underline
57 status.unknown = magenta bold underline
58 status.ignored = black bold
58 status.ignored = black bold
59
59
60 # 'none' turns off all effects
60 # 'none' turns off all effects
61 status.clean = none
61 status.clean = none
62 status.copied = none
62 status.copied = none
63
63
64 qseries.applied = blue bold underline
64 qseries.applied = blue bold underline
65 qseries.unapplied = black bold
65 qseries.unapplied = black bold
66 qseries.missing = red bold
66 qseries.missing = red bold
67
67
68 diff.diffline = bold
68 diff.diffline = bold
69 diff.extended = cyan bold
69 diff.extended = cyan bold
70 diff.file_a = red bold
70 diff.file_a = red bold
71 diff.file_b = green bold
71 diff.file_b = green bold
72 diff.hunk = magenta
72 diff.hunk = magenta
73 diff.deleted = red
73 diff.deleted = red
74 diff.inserted = green
74 diff.inserted = green
75 diff.changed = white
75 diff.changed = white
76 diff.tab =
76 diff.tab =
77 diff.trailingwhitespace = bold red_background
77 diff.trailingwhitespace = bold red_background
78
78
79 # Blank so it inherits the style of the surrounding label
79 # Blank so it inherits the style of the surrounding label
80 changeset.public =
80 changeset.public =
81 changeset.draft =
81 changeset.draft =
82 changeset.secret =
82 changeset.secret =
83
83
84 resolve.unresolved = red bold
84 resolve.unresolved = red bold
85 resolve.resolved = green bold
85 resolve.resolved = green bold
86
86
87 bookmarks.current = green
87 bookmarks.current = green
88
88
89 branches.active = none
89 branches.active = none
90 branches.closed = black bold
90 branches.closed = black bold
91 branches.current = green
91 branches.current = green
92 branches.inactive = none
92 branches.inactive = none
93
93
94 tags.normal = green
94 tags.normal = green
95 tags.local = black bold
95 tags.local = black bold
96
96
97 rebase.rebased = blue
97 rebase.rebased = blue
98 rebase.remaining = red bold
98 rebase.remaining = red bold
99
99
100 shelve.age = cyan
100 shelve.age = cyan
101 shelve.newest = green bold
101 shelve.newest = green bold
102 shelve.name = blue bold
102 shelve.name = blue bold
103
103
104 histedit.remaining = red bold
104 histedit.remaining = red bold
105
105
106 Custom colors
106 Custom colors
107 -------------
107 -------------
108
108
109 Because there are only eight standard colors, this module allows you
109 Because there are only eight standard colors, this module allows you
110 to define color names for other color slots which might be available
110 to define color names for other color slots which might be available
111 for your terminal type, assuming terminfo mode. For instance::
111 for your terminal type, assuming terminfo mode. For instance::
112
112
113 color.brightblue = 12
113 color.brightblue = 12
114 color.pink = 207
114 color.pink = 207
115 color.orange = 202
115 color.orange = 202
116
116
117 to set 'brightblue' to color slot 12 (useful for 16 color terminals
117 to set 'brightblue' to color slot 12 (useful for 16 color terminals
118 that have brighter colors defined in the upper eight) and, 'pink' and
118 that have brighter colors defined in the upper eight) and, 'pink' and
119 'orange' to colors in 256-color xterm's default color cube. These
119 'orange' to colors in 256-color xterm's default color cube. These
120 defined colors may then be used as any of the pre-defined eight,
120 defined colors may then be used as any of the pre-defined eight,
121 including appending '_background' to set the background to that color.
121 including appending '_background' to set the background to that color.
122
122
123 Modes
123 Modes
124 -----
124 -----
125
125
126 By default, the color extension will use ANSI mode (or win32 mode on
126 By default, the color extension will use ANSI mode (or win32 mode on
127 Windows) if it detects a terminal. To override auto mode (to enable
127 Windows) if it detects a terminal. To override auto mode (to enable
128 terminfo mode, for example), set the following configuration option::
128 terminfo mode, for example), set the following configuration option::
129
129
130 [color]
130 [color]
131 mode = terminfo
131 mode = terminfo
132
132
133 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
133 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
134 disable color.
134 disable color.
135
135
136 Note that on some systems, terminfo mode may cause problems when using
136 Note that on some systems, terminfo mode may cause problems when using
137 color with the pager extension and less -R. less with the -R option
137 color with the pager extension and less -R. less with the -R option
138 will only display ECMA-48 color codes, and terminfo mode may sometimes
138 will only display ECMA-48 color codes, and terminfo mode may sometimes
139 emit codes that less doesn't understand. You can work around this by
139 emit codes that less doesn't understand. You can work around this by
140 either using ansi mode (or auto mode), or by using less -r (which will
140 either using ansi mode (or auto mode), or by using less -r (which will
141 pass through all terminal control codes, not just color control
141 pass through all terminal control codes, not just color control
142 codes).
142 codes).
143 '''
143 '''
144
144
145 import os
145 import os
146
146
147 from mercurial import cmdutil, commands, dispatch, extensions, subrepo, util
147 from mercurial import cmdutil, commands, dispatch, extensions, subrepo, util
148 from mercurial import ui as uimod
148 from mercurial import ui as uimod
149 from mercurial import templater, error
149 from mercurial import templater, error
150 from mercurial.i18n import _
150 from mercurial.i18n import _
151
151
152 cmdtable = {}
152 cmdtable = {}
153 command = cmdutil.command(cmdtable)
153 command = cmdutil.command(cmdtable)
154 testedwith = 'internal'
154 testedwith = 'internal'
155
155
156 # start and stop parameters for effects
156 # start and stop parameters for effects
157 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
157 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
158 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
158 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
159 'italic': 3, 'underline': 4, 'inverse': 7, 'dim': 2,
159 'italic': 3, 'underline': 4, 'inverse': 7, 'dim': 2,
160 'black_background': 40, 'red_background': 41,
160 'black_background': 40, 'red_background': 41,
161 'green_background': 42, 'yellow_background': 43,
161 'green_background': 42, 'yellow_background': 43,
162 'blue_background': 44, 'purple_background': 45,
162 'blue_background': 44, 'purple_background': 45,
163 'cyan_background': 46, 'white_background': 47}
163 'cyan_background': 46, 'white_background': 47}
164
164
165 def _terminfosetup(ui, mode):
165 def _terminfosetup(ui, mode):
166 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
166 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
167
167
168 global _terminfo_params
168 global _terminfo_params
169 # If we failed to load curses, we go ahead and return.
169 # If we failed to load curses, we go ahead and return.
170 if not _terminfo_params:
170 if not _terminfo_params:
171 return
171 return
172 # Otherwise, see what the config file says.
172 # Otherwise, see what the config file says.
173 if mode not in ('auto', 'terminfo'):
173 if mode not in ('auto', 'terminfo'):
174 return
174 return
175
175
176 _terminfo_params.update((key[6:], (False, int(val)))
176 _terminfo_params.update((key[6:], (False, int(val)))
177 for key, val in ui.configitems('color')
177 for key, val in ui.configitems('color')
178 if key.startswith('color.'))
178 if key.startswith('color.'))
179
179
180 try:
180 try:
181 curses.setupterm()
181 curses.setupterm()
182 except curses.error, e:
182 except curses.error, e:
183 _terminfo_params = {}
183 _terminfo_params = {}
184 return
184 return
185
185
186 for key, (b, e) in _terminfo_params.items():
186 for key, (b, e) in _terminfo_params.items():
187 if not b:
187 if not b:
188 continue
188 continue
189 if not curses.tigetstr(e):
189 if not curses.tigetstr(e):
190 # Most terminals don't support dim, invis, etc, so don't be
190 # Most terminals don't support dim, invis, etc, so don't be
191 # noisy and use ui.debug().
191 # noisy and use ui.debug().
192 ui.debug("no terminfo entry for %s\n" % e)
192 ui.debug("no terminfo entry for %s\n" % e)
193 del _terminfo_params[key]
193 del _terminfo_params[key]
194 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
194 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
195 # Only warn about missing terminfo entries if we explicitly asked for
195 # Only warn about missing terminfo entries if we explicitly asked for
196 # terminfo mode.
196 # terminfo mode.
197 if mode == "terminfo":
197 if mode == "terminfo":
198 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
198 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
199 "ECMA-48 color\n"))
199 "ECMA-48 color\n"))
200 _terminfo_params = {}
200 _terminfo_params = {}
201
201
202 def _modesetup(ui, coloropt):
202 def _modesetup(ui, coloropt):
203 global _terminfo_params
203 global _terminfo_params
204
204
205 if coloropt == 'debug':
205 if coloropt == 'debug':
206 return 'debug'
206 return 'debug'
207
207
208 auto = (coloropt == 'auto')
208 auto = (coloropt == 'auto')
209 always = not auto and util.parsebool(coloropt)
209 always = not auto and util.parsebool(coloropt)
210 if not always and not auto:
210 if not always and not auto:
211 return None
211 return None
212
212
213 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
213 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
214
214
215 mode = ui.config('color', 'mode', 'auto')
215 mode = ui.config('color', 'mode', 'auto')
216 realmode = mode
216 realmode = mode
217 if mode == 'auto':
217 if mode == 'auto':
218 if os.name == 'nt' and 'TERM' not in os.environ:
218 if os.name == 'nt':
219 # looks line a cmd.exe console, use win32 API or nothing
219 term = os.environ.get('TERM')
220 realmode = 'win32'
220 # TERM won't be defined in a vanilla cmd.exe environment.
221 if not term:
222 realmode = 'win32'
223
224 # UNIX-like environments on Windows such as Cygwin and MSYS will
225 # set TERM. They appear to make a best effort attempt at setting it
226 # to something appropriate. However, not all environments with TERM
227 # defined support ANSI. Since "ansi" could result in terminal
228 # gibberish, we error on the side of selecting "win32". However, if
229 # w32effects is not defined, we almost certainly don't support
230 # "win32", so don't even try.
231 if 'xterm' in term or not w32effects:
232 realmode = 'ansi'
233 else:
234 realmode = 'win32'
221 else:
235 else:
222 realmode = 'ansi'
236 realmode = 'ansi'
223
237
224 if realmode == 'win32':
238 if realmode == 'win32':
225 _terminfo_params = {}
239 _terminfo_params = {}
226 if not w32effects:
240 if not w32effects:
227 if mode == 'win32':
241 if mode == 'win32':
228 # only warn if color.mode is explicitly set to win32
242 # only warn if color.mode is explicitly set to win32
229 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
243 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
230 return None
244 return None
231 _effects.update(w32effects)
245 _effects.update(w32effects)
232 elif realmode == 'ansi':
246 elif realmode == 'ansi':
233 _terminfo_params = {}
247 _terminfo_params = {}
234 elif realmode == 'terminfo':
248 elif realmode == 'terminfo':
235 _terminfosetup(ui, mode)
249 _terminfosetup(ui, mode)
236 if not _terminfo_params:
250 if not _terminfo_params:
237 if mode == 'terminfo':
251 if mode == 'terminfo':
238 ## FIXME Shouldn't we return None in this case too?
252 ## FIXME Shouldn't we return None in this case too?
239 # only warn if color.mode is explicitly set to win32
253 # only warn if color.mode is explicitly set to win32
240 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
254 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
241 realmode = 'ansi'
255 realmode = 'ansi'
242 else:
256 else:
243 return None
257 return None
244
258
245 if always or (auto and formatted):
259 if always or (auto and formatted):
246 return realmode
260 return realmode
247 return None
261 return None
248
262
249 try:
263 try:
250 import curses
264 import curses
251 # Mapping from effect name to terminfo attribute name or color number.
265 # Mapping from effect name to terminfo attribute name or color number.
252 # This will also force-load the curses module.
266 # This will also force-load the curses module.
253 _terminfo_params = {'none': (True, 'sgr0'),
267 _terminfo_params = {'none': (True, 'sgr0'),
254 'standout': (True, 'smso'),
268 'standout': (True, 'smso'),
255 'underline': (True, 'smul'),
269 'underline': (True, 'smul'),
256 'reverse': (True, 'rev'),
270 'reverse': (True, 'rev'),
257 'inverse': (True, 'rev'),
271 'inverse': (True, 'rev'),
258 'blink': (True, 'blink'),
272 'blink': (True, 'blink'),
259 'dim': (True, 'dim'),
273 'dim': (True, 'dim'),
260 'bold': (True, 'bold'),
274 'bold': (True, 'bold'),
261 'invisible': (True, 'invis'),
275 'invisible': (True, 'invis'),
262 'italic': (True, 'sitm'),
276 'italic': (True, 'sitm'),
263 'black': (False, curses.COLOR_BLACK),
277 'black': (False, curses.COLOR_BLACK),
264 'red': (False, curses.COLOR_RED),
278 'red': (False, curses.COLOR_RED),
265 'green': (False, curses.COLOR_GREEN),
279 'green': (False, curses.COLOR_GREEN),
266 'yellow': (False, curses.COLOR_YELLOW),
280 'yellow': (False, curses.COLOR_YELLOW),
267 'blue': (False, curses.COLOR_BLUE),
281 'blue': (False, curses.COLOR_BLUE),
268 'magenta': (False, curses.COLOR_MAGENTA),
282 'magenta': (False, curses.COLOR_MAGENTA),
269 'cyan': (False, curses.COLOR_CYAN),
283 'cyan': (False, curses.COLOR_CYAN),
270 'white': (False, curses.COLOR_WHITE)}
284 'white': (False, curses.COLOR_WHITE)}
271 except ImportError:
285 except ImportError:
272 _terminfo_params = {}
286 _terminfo_params = {}
273
287
274 _styles = {'grep.match': 'red bold',
288 _styles = {'grep.match': 'red bold',
275 'grep.linenumber': 'green',
289 'grep.linenumber': 'green',
276 'grep.rev': 'green',
290 'grep.rev': 'green',
277 'grep.change': 'green',
291 'grep.change': 'green',
278 'grep.sep': 'cyan',
292 'grep.sep': 'cyan',
279 'grep.filename': 'magenta',
293 'grep.filename': 'magenta',
280 'grep.user': 'magenta',
294 'grep.user': 'magenta',
281 'grep.date': 'magenta',
295 'grep.date': 'magenta',
282 'bookmarks.current': 'green',
296 'bookmarks.current': 'green',
283 'branches.active': 'none',
297 'branches.active': 'none',
284 'branches.closed': 'black bold',
298 'branches.closed': 'black bold',
285 'branches.current': 'green',
299 'branches.current': 'green',
286 'branches.inactive': 'none',
300 'branches.inactive': 'none',
287 'diff.changed': 'white',
301 'diff.changed': 'white',
288 'diff.deleted': 'red',
302 'diff.deleted': 'red',
289 'diff.diffline': 'bold',
303 'diff.diffline': 'bold',
290 'diff.extended': 'cyan bold',
304 'diff.extended': 'cyan bold',
291 'diff.file_a': 'red bold',
305 'diff.file_a': 'red bold',
292 'diff.file_b': 'green bold',
306 'diff.file_b': 'green bold',
293 'diff.hunk': 'magenta',
307 'diff.hunk': 'magenta',
294 'diff.inserted': 'green',
308 'diff.inserted': 'green',
295 'diff.tab': '',
309 'diff.tab': '',
296 'diff.trailingwhitespace': 'bold red_background',
310 'diff.trailingwhitespace': 'bold red_background',
297 'changeset.public' : '',
311 'changeset.public' : '',
298 'changeset.draft' : '',
312 'changeset.draft' : '',
299 'changeset.secret' : '',
313 'changeset.secret' : '',
300 'diffstat.deleted': 'red',
314 'diffstat.deleted': 'red',
301 'diffstat.inserted': 'green',
315 'diffstat.inserted': 'green',
302 'histedit.remaining': 'red bold',
316 'histedit.remaining': 'red bold',
303 'ui.prompt': 'yellow',
317 'ui.prompt': 'yellow',
304 'log.changeset': 'yellow',
318 'log.changeset': 'yellow',
305 'patchbomb.finalsummary': '',
319 'patchbomb.finalsummary': '',
306 'patchbomb.from': 'magenta',
320 'patchbomb.from': 'magenta',
307 'patchbomb.to': 'cyan',
321 'patchbomb.to': 'cyan',
308 'patchbomb.subject': 'green',
322 'patchbomb.subject': 'green',
309 'patchbomb.diffstats': '',
323 'patchbomb.diffstats': '',
310 'rebase.rebased': 'blue',
324 'rebase.rebased': 'blue',
311 'rebase.remaining': 'red bold',
325 'rebase.remaining': 'red bold',
312 'resolve.resolved': 'green bold',
326 'resolve.resolved': 'green bold',
313 'resolve.unresolved': 'red bold',
327 'resolve.unresolved': 'red bold',
314 'shelve.age': 'cyan',
328 'shelve.age': 'cyan',
315 'shelve.newest': 'green bold',
329 'shelve.newest': 'green bold',
316 'shelve.name': 'blue bold',
330 'shelve.name': 'blue bold',
317 'status.added': 'green bold',
331 'status.added': 'green bold',
318 'status.clean': 'none',
332 'status.clean': 'none',
319 'status.copied': 'none',
333 'status.copied': 'none',
320 'status.deleted': 'cyan bold underline',
334 'status.deleted': 'cyan bold underline',
321 'status.ignored': 'black bold',
335 'status.ignored': 'black bold',
322 'status.modified': 'blue bold',
336 'status.modified': 'blue bold',
323 'status.removed': 'red bold',
337 'status.removed': 'red bold',
324 'status.unknown': 'magenta bold underline',
338 'status.unknown': 'magenta bold underline',
325 'tags.normal': 'green',
339 'tags.normal': 'green',
326 'tags.local': 'black bold'}
340 'tags.local': 'black bold'}
327
341
328
342
329 def _effect_str(effect):
343 def _effect_str(effect):
330 '''Helper function for render_effects().'''
344 '''Helper function for render_effects().'''
331
345
332 bg = False
346 bg = False
333 if effect.endswith('_background'):
347 if effect.endswith('_background'):
334 bg = True
348 bg = True
335 effect = effect[:-11]
349 effect = effect[:-11]
336 attr, val = _terminfo_params[effect]
350 attr, val = _terminfo_params[effect]
337 if attr:
351 if attr:
338 return curses.tigetstr(val)
352 return curses.tigetstr(val)
339 elif bg:
353 elif bg:
340 return curses.tparm(curses.tigetstr('setab'), val)
354 return curses.tparm(curses.tigetstr('setab'), val)
341 else:
355 else:
342 return curses.tparm(curses.tigetstr('setaf'), val)
356 return curses.tparm(curses.tigetstr('setaf'), val)
343
357
344 def render_effects(text, effects):
358 def render_effects(text, effects):
345 'Wrap text in commands to turn on each effect.'
359 'Wrap text in commands to turn on each effect.'
346 if not text:
360 if not text:
347 return text
361 return text
348 if not _terminfo_params:
362 if not _terminfo_params:
349 start = [str(_effects[e]) for e in ['none'] + effects.split()]
363 start = [str(_effects[e]) for e in ['none'] + effects.split()]
350 start = '\033[' + ';'.join(start) + 'm'
364 start = '\033[' + ';'.join(start) + 'm'
351 stop = '\033[' + str(_effects['none']) + 'm'
365 stop = '\033[' + str(_effects['none']) + 'm'
352 else:
366 else:
353 start = ''.join(_effect_str(effect)
367 start = ''.join(_effect_str(effect)
354 for effect in ['none'] + effects.split())
368 for effect in ['none'] + effects.split())
355 stop = _effect_str('none')
369 stop = _effect_str('none')
356 return ''.join([start, text, stop])
370 return ''.join([start, text, stop])
357
371
358 def extstyles():
372 def extstyles():
359 for name, ext in extensions.extensions():
373 for name, ext in extensions.extensions():
360 _styles.update(getattr(ext, 'colortable', {}))
374 _styles.update(getattr(ext, 'colortable', {}))
361
375
362 def valideffect(effect):
376 def valideffect(effect):
363 'Determine if the effect is valid or not.'
377 'Determine if the effect is valid or not.'
364 good = False
378 good = False
365 if not _terminfo_params and effect in _effects:
379 if not _terminfo_params and effect in _effects:
366 good = True
380 good = True
367 elif effect in _terminfo_params or effect[:-11] in _terminfo_params:
381 elif effect in _terminfo_params or effect[:-11] in _terminfo_params:
368 good = True
382 good = True
369 return good
383 return good
370
384
371 def configstyles(ui):
385 def configstyles(ui):
372 for status, cfgeffects in ui.configitems('color'):
386 for status, cfgeffects in ui.configitems('color'):
373 if '.' not in status or status.startswith('color.'):
387 if '.' not in status or status.startswith('color.'):
374 continue
388 continue
375 cfgeffects = ui.configlist('color', status)
389 cfgeffects = ui.configlist('color', status)
376 if cfgeffects:
390 if cfgeffects:
377 good = []
391 good = []
378 for e in cfgeffects:
392 for e in cfgeffects:
379 if valideffect(e):
393 if valideffect(e):
380 good.append(e)
394 good.append(e)
381 else:
395 else:
382 ui.warn(_("ignoring unknown color/effect %r "
396 ui.warn(_("ignoring unknown color/effect %r "
383 "(configured in color.%s)\n")
397 "(configured in color.%s)\n")
384 % (e, status))
398 % (e, status))
385 _styles[status] = ' '.join(good)
399 _styles[status] = ' '.join(good)
386
400
387 class colorui(uimod.ui):
401 class colorui(uimod.ui):
388 def popbuffer(self, labeled=False):
402 def popbuffer(self, labeled=False):
389 if self._colormode is None:
403 if self._colormode is None:
390 return super(colorui, self).popbuffer(labeled)
404 return super(colorui, self).popbuffer(labeled)
391
405
392 self._bufferstates.pop()
406 self._bufferstates.pop()
393 if labeled:
407 if labeled:
394 return ''.join(self.label(a, label) for a, label
408 return ''.join(self.label(a, label) for a, label
395 in self._buffers.pop())
409 in self._buffers.pop())
396 return ''.join(a for a, label in self._buffers.pop())
410 return ''.join(a for a, label in self._buffers.pop())
397
411
398 _colormode = 'ansi'
412 _colormode = 'ansi'
399 def write(self, *args, **opts):
413 def write(self, *args, **opts):
400 if self._colormode is None:
414 if self._colormode is None:
401 return super(colorui, self).write(*args, **opts)
415 return super(colorui, self).write(*args, **opts)
402
416
403 label = opts.get('label', '')
417 label = opts.get('label', '')
404 if self._buffers:
418 if self._buffers:
405 self._buffers[-1].extend([(str(a), label) for a in args])
419 self._buffers[-1].extend([(str(a), label) for a in args])
406 elif self._colormode == 'win32':
420 elif self._colormode == 'win32':
407 for a in args:
421 for a in args:
408 win32print(a, super(colorui, self).write, **opts)
422 win32print(a, super(colorui, self).write, **opts)
409 else:
423 else:
410 return super(colorui, self).write(
424 return super(colorui, self).write(
411 *[self.label(str(a), label) for a in args], **opts)
425 *[self.label(str(a), label) for a in args], **opts)
412
426
413 def write_err(self, *args, **opts):
427 def write_err(self, *args, **opts):
414 if self._colormode is None:
428 if self._colormode is None:
415 return super(colorui, self).write_err(*args, **opts)
429 return super(colorui, self).write_err(*args, **opts)
416
430
417 label = opts.get('label', '')
431 label = opts.get('label', '')
418 if self._bufferstates and self._bufferstates[-1]:
432 if self._bufferstates and self._bufferstates[-1]:
419 return self.write(*args, **opts)
433 return self.write(*args, **opts)
420 if self._colormode == 'win32':
434 if self._colormode == 'win32':
421 for a in args:
435 for a in args:
422 win32print(a, super(colorui, self).write_err, **opts)
436 win32print(a, super(colorui, self).write_err, **opts)
423 else:
437 else:
424 return super(colorui, self).write_err(
438 return super(colorui, self).write_err(
425 *[self.label(str(a), label) for a in args], **opts)
439 *[self.label(str(a), label) for a in args], **opts)
426
440
427 def showlabel(self, msg, label):
441 def showlabel(self, msg, label):
428 if label and msg:
442 if label and msg:
429 if msg[-1] == '\n':
443 if msg[-1] == '\n':
430 return "[%s|%s]\n" % (label, msg[:-1])
444 return "[%s|%s]\n" % (label, msg[:-1])
431 else:
445 else:
432 return "[%s|%s]" % (label, msg)
446 return "[%s|%s]" % (label, msg)
433 else:
447 else:
434 return msg
448 return msg
435
449
436 def label(self, msg, label):
450 def label(self, msg, label):
437 if self._colormode is None:
451 if self._colormode is None:
438 return super(colorui, self).label(msg, label)
452 return super(colorui, self).label(msg, label)
439
453
440 if self._colormode == 'debug':
454 if self._colormode == 'debug':
441 return self.showlabel(msg, label)
455 return self.showlabel(msg, label)
442
456
443 effects = []
457 effects = []
444 for l in label.split():
458 for l in label.split():
445 s = _styles.get(l, '')
459 s = _styles.get(l, '')
446 if s:
460 if s:
447 effects.append(s)
461 effects.append(s)
448 elif valideffect(l):
462 elif valideffect(l):
449 effects.append(l)
463 effects.append(l)
450 effects = ' '.join(effects)
464 effects = ' '.join(effects)
451 if effects:
465 if effects:
452 return '\n'.join([render_effects(s, effects)
466 return '\n'.join([render_effects(s, effects)
453 for s in msg.split('\n')])
467 for s in msg.split('\n')])
454 return msg
468 return msg
455
469
456 def templatelabel(context, mapping, args):
470 def templatelabel(context, mapping, args):
457 if len(args) != 2:
471 if len(args) != 2:
458 # i18n: "label" is a keyword
472 # i18n: "label" is a keyword
459 raise error.ParseError(_("label expects two arguments"))
473 raise error.ParseError(_("label expects two arguments"))
460
474
461 # add known effects to the mapping so symbols like 'red', 'bold',
475 # add known effects to the mapping so symbols like 'red', 'bold',
462 # etc. don't need to be quoted
476 # etc. don't need to be quoted
463 mapping.update(dict([(k, k) for k in _effects]))
477 mapping.update(dict([(k, k) for k in _effects]))
464
478
465 thing = templater._evalifliteral(args[1], context, mapping)
479 thing = templater._evalifliteral(args[1], context, mapping)
466
480
467 # apparently, repo could be a string that is the favicon?
481 # apparently, repo could be a string that is the favicon?
468 repo = mapping.get('repo', '')
482 repo = mapping.get('repo', '')
469 if isinstance(repo, str):
483 if isinstance(repo, str):
470 return thing
484 return thing
471
485
472 label = templater._evalifliteral(args[0], context, mapping)
486 label = templater._evalifliteral(args[0], context, mapping)
473
487
474 thing = templater.stringify(thing)
488 thing = templater.stringify(thing)
475 label = templater.stringify(label)
489 label = templater.stringify(label)
476
490
477 return repo.ui.label(thing, label)
491 return repo.ui.label(thing, label)
478
492
479 def uisetup(ui):
493 def uisetup(ui):
480 if ui.plain():
494 if ui.plain():
481 return
495 return
482 if not isinstance(ui, colorui):
496 if not isinstance(ui, colorui):
483 colorui.__bases__ = (ui.__class__,)
497 colorui.__bases__ = (ui.__class__,)
484 ui.__class__ = colorui
498 ui.__class__ = colorui
485 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
499 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
486 mode = _modesetup(ui_, opts['color'])
500 mode = _modesetup(ui_, opts['color'])
487 colorui._colormode = mode
501 colorui._colormode = mode
488 if mode and mode != 'debug':
502 if mode and mode != 'debug':
489 extstyles()
503 extstyles()
490 configstyles(ui_)
504 configstyles(ui_)
491 return orig(ui_, opts, cmd, cmdfunc)
505 return orig(ui_, opts, cmd, cmdfunc)
492 def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None):
506 def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None):
493 if gitsub.ui._colormode and len(commands) and commands[0] == "diff":
507 if gitsub.ui._colormode and len(commands) and commands[0] == "diff":
494 # insert the argument in the front,
508 # insert the argument in the front,
495 # the end of git diff arguments is used for paths
509 # the end of git diff arguments is used for paths
496 commands.insert(1, '--color')
510 commands.insert(1, '--color')
497 return orig(gitsub, commands, env, stream, cwd)
511 return orig(gitsub, commands, env, stream, cwd)
498 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
512 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
499 extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
513 extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
500 templater.funcs['label'] = templatelabel
514 templater.funcs['label'] = templatelabel
501
515
502 def extsetup(ui):
516 def extsetup(ui):
503 commands.globalopts.append(
517 commands.globalopts.append(
504 ('', 'color', 'auto',
518 ('', 'color', 'auto',
505 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
519 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
506 # and should not be translated
520 # and should not be translated
507 _("when to colorize (boolean, always, auto, never, or debug)"),
521 _("when to colorize (boolean, always, auto, never, or debug)"),
508 _('TYPE')))
522 _('TYPE')))
509
523
510 @command('debugcolor', [], 'hg debugcolor')
524 @command('debugcolor', [], 'hg debugcolor')
511 def debugcolor(ui, repo, **opts):
525 def debugcolor(ui, repo, **opts):
512 global _styles
526 global _styles
513 _styles = {}
527 _styles = {}
514 for effect in _effects.keys():
528 for effect in _effects.keys():
515 _styles[effect] = effect
529 _styles[effect] = effect
516 ui.write(('color mode: %s\n') % ui._colormode)
530 ui.write(('color mode: %s\n') % ui._colormode)
517 ui.write(_('available colors:\n'))
531 ui.write(_('available colors:\n'))
518 for label, colors in _styles.items():
532 for label, colors in _styles.items():
519 ui.write(('%s\n') % colors, label=label)
533 ui.write(('%s\n') % colors, label=label)
520
534
521 if os.name != 'nt':
535 if os.name != 'nt':
522 w32effects = None
536 w32effects = None
523 else:
537 else:
524 import re, ctypes
538 import re, ctypes
525
539
526 _kernel32 = ctypes.windll.kernel32
540 _kernel32 = ctypes.windll.kernel32
527
541
528 _WORD = ctypes.c_ushort
542 _WORD = ctypes.c_ushort
529
543
530 _INVALID_HANDLE_VALUE = -1
544 _INVALID_HANDLE_VALUE = -1
531
545
532 class _COORD(ctypes.Structure):
546 class _COORD(ctypes.Structure):
533 _fields_ = [('X', ctypes.c_short),
547 _fields_ = [('X', ctypes.c_short),
534 ('Y', ctypes.c_short)]
548 ('Y', ctypes.c_short)]
535
549
536 class _SMALL_RECT(ctypes.Structure):
550 class _SMALL_RECT(ctypes.Structure):
537 _fields_ = [('Left', ctypes.c_short),
551 _fields_ = [('Left', ctypes.c_short),
538 ('Top', ctypes.c_short),
552 ('Top', ctypes.c_short),
539 ('Right', ctypes.c_short),
553 ('Right', ctypes.c_short),
540 ('Bottom', ctypes.c_short)]
554 ('Bottom', ctypes.c_short)]
541
555
542 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
556 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
543 _fields_ = [('dwSize', _COORD),
557 _fields_ = [('dwSize', _COORD),
544 ('dwCursorPosition', _COORD),
558 ('dwCursorPosition', _COORD),
545 ('wAttributes', _WORD),
559 ('wAttributes', _WORD),
546 ('srWindow', _SMALL_RECT),
560 ('srWindow', _SMALL_RECT),
547 ('dwMaximumWindowSize', _COORD)]
561 ('dwMaximumWindowSize', _COORD)]
548
562
549 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
563 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
550 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
564 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
551
565
552 _FOREGROUND_BLUE = 0x0001
566 _FOREGROUND_BLUE = 0x0001
553 _FOREGROUND_GREEN = 0x0002
567 _FOREGROUND_GREEN = 0x0002
554 _FOREGROUND_RED = 0x0004
568 _FOREGROUND_RED = 0x0004
555 _FOREGROUND_INTENSITY = 0x0008
569 _FOREGROUND_INTENSITY = 0x0008
556
570
557 _BACKGROUND_BLUE = 0x0010
571 _BACKGROUND_BLUE = 0x0010
558 _BACKGROUND_GREEN = 0x0020
572 _BACKGROUND_GREEN = 0x0020
559 _BACKGROUND_RED = 0x0040
573 _BACKGROUND_RED = 0x0040
560 _BACKGROUND_INTENSITY = 0x0080
574 _BACKGROUND_INTENSITY = 0x0080
561
575
562 _COMMON_LVB_REVERSE_VIDEO = 0x4000
576 _COMMON_LVB_REVERSE_VIDEO = 0x4000
563 _COMMON_LVB_UNDERSCORE = 0x8000
577 _COMMON_LVB_UNDERSCORE = 0x8000
564
578
565 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
579 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
566 w32effects = {
580 w32effects = {
567 'none': -1,
581 'none': -1,
568 'black': 0,
582 'black': 0,
569 'red': _FOREGROUND_RED,
583 'red': _FOREGROUND_RED,
570 'green': _FOREGROUND_GREEN,
584 'green': _FOREGROUND_GREEN,
571 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
585 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
572 'blue': _FOREGROUND_BLUE,
586 'blue': _FOREGROUND_BLUE,
573 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
587 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
574 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
588 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
575 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
589 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
576 'bold': _FOREGROUND_INTENSITY,
590 'bold': _FOREGROUND_INTENSITY,
577 'black_background': 0x100, # unused value > 0x0f
591 'black_background': 0x100, # unused value > 0x0f
578 'red_background': _BACKGROUND_RED,
592 'red_background': _BACKGROUND_RED,
579 'green_background': _BACKGROUND_GREEN,
593 'green_background': _BACKGROUND_GREEN,
580 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
594 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
581 'blue_background': _BACKGROUND_BLUE,
595 'blue_background': _BACKGROUND_BLUE,
582 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
596 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
583 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
597 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
584 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
598 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
585 _BACKGROUND_BLUE),
599 _BACKGROUND_BLUE),
586 'bold_background': _BACKGROUND_INTENSITY,
600 'bold_background': _BACKGROUND_INTENSITY,
587 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
601 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
588 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
602 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
589 }
603 }
590
604
591 passthrough = set([_FOREGROUND_INTENSITY,
605 passthrough = set([_FOREGROUND_INTENSITY,
592 _BACKGROUND_INTENSITY,
606 _BACKGROUND_INTENSITY,
593 _COMMON_LVB_UNDERSCORE,
607 _COMMON_LVB_UNDERSCORE,
594 _COMMON_LVB_REVERSE_VIDEO])
608 _COMMON_LVB_REVERSE_VIDEO])
595
609
596 stdout = _kernel32.GetStdHandle(
610 stdout = _kernel32.GetStdHandle(
597 _STD_OUTPUT_HANDLE) # don't close the handle returned
611 _STD_OUTPUT_HANDLE) # don't close the handle returned
598 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
612 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
599 w32effects = None
613 w32effects = None
600 else:
614 else:
601 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
615 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
602 if not _kernel32.GetConsoleScreenBufferInfo(
616 if not _kernel32.GetConsoleScreenBufferInfo(
603 stdout, ctypes.byref(csbi)):
617 stdout, ctypes.byref(csbi)):
604 # stdout may not support GetConsoleScreenBufferInfo()
618 # stdout may not support GetConsoleScreenBufferInfo()
605 # when called from subprocess or redirected
619 # when called from subprocess or redirected
606 w32effects = None
620 w32effects = None
607 else:
621 else:
608 origattr = csbi.wAttributes
622 origattr = csbi.wAttributes
609 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
623 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
610 re.MULTILINE | re.DOTALL)
624 re.MULTILINE | re.DOTALL)
611
625
612 def win32print(text, orig, **opts):
626 def win32print(text, orig, **opts):
613 label = opts.get('label', '')
627 label = opts.get('label', '')
614 attr = origattr
628 attr = origattr
615
629
616 def mapcolor(val, attr):
630 def mapcolor(val, attr):
617 if val == -1:
631 if val == -1:
618 return origattr
632 return origattr
619 elif val in passthrough:
633 elif val in passthrough:
620 return attr | val
634 return attr | val
621 elif val > 0x0f:
635 elif val > 0x0f:
622 return (val & 0x70) | (attr & 0x8f)
636 return (val & 0x70) | (attr & 0x8f)
623 else:
637 else:
624 return (val & 0x07) | (attr & 0xf8)
638 return (val & 0x07) | (attr & 0xf8)
625
639
626 # determine console attributes based on labels
640 # determine console attributes based on labels
627 for l in label.split():
641 for l in label.split():
628 style = _styles.get(l, '')
642 style = _styles.get(l, '')
629 for effect in style.split():
643 for effect in style.split():
630 try:
644 try:
631 attr = mapcolor(w32effects[effect], attr)
645 attr = mapcolor(w32effects[effect], attr)
632 except KeyError:
646 except KeyError:
633 # w32effects could not have certain attributes so we skip
647 # w32effects could not have certain attributes so we skip
634 # them if not found
648 # them if not found
635 pass
649 pass
636 # hack to ensure regexp finds data
650 # hack to ensure regexp finds data
637 if not text.startswith('\033['):
651 if not text.startswith('\033['):
638 text = '\033[m' + text
652 text = '\033[m' + text
639
653
640 # Look for ANSI-like codes embedded in text
654 # Look for ANSI-like codes embedded in text
641 m = re.match(ansire, text)
655 m = re.match(ansire, text)
642
656
643 try:
657 try:
644 while m:
658 while m:
645 for sattr in m.group(1).split(';'):
659 for sattr in m.group(1).split(';'):
646 if sattr:
660 if sattr:
647 attr = mapcolor(int(sattr), attr)
661 attr = mapcolor(int(sattr), attr)
648 _kernel32.SetConsoleTextAttribute(stdout, attr)
662 _kernel32.SetConsoleTextAttribute(stdout, attr)
649 orig(m.group(2), **opts)
663 orig(m.group(2), **opts)
650 m = re.match(ansire, m.group(3))
664 m = re.match(ansire, m.group(3))
651 finally:
665 finally:
652 # Explicitly reset original attributes
666 # Explicitly reset original attributes
653 _kernel32.SetConsoleTextAttribute(stdout, origattr)
667 _kernel32.SetConsoleTextAttribute(stdout, origattr)
General Comments 0
You need to be logged in to leave comments. Login now