##// END OF EJS Templates
color: support a different color mode when the pager is active...
Gregory Szorc -
r24068:4e02418b default
parent child Browse files
Show More
@@ -1,667 +1,683 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
144 On some systems (such as MSYS in Windows), the terminal may support
145 a different color mode than the pager (activated via the "pager"
146 extension). It is possible to define separate modes depending on whether
147 the pager is active::
148
149 [color]
150 mode = auto
151 pagermode = ansi
152
153 If ``pagermode`` is not defined, the ``mode`` will be used.
143 '''
154 '''
144
155
145 import os
156 import os
146
157
147 from mercurial import cmdutil, commands, dispatch, extensions, subrepo, util
158 from mercurial import cmdutil, commands, dispatch, extensions, subrepo, util
148 from mercurial import ui as uimod
159 from mercurial import ui as uimod
149 from mercurial import templater, error
160 from mercurial import templater, error
150 from mercurial.i18n import _
161 from mercurial.i18n import _
151
162
152 cmdtable = {}
163 cmdtable = {}
153 command = cmdutil.command(cmdtable)
164 command = cmdutil.command(cmdtable)
154 testedwith = 'internal'
165 testedwith = 'internal'
155
166
156 # start and stop parameters for effects
167 # start and stop parameters for effects
157 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
168 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
158 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
169 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
159 'italic': 3, 'underline': 4, 'inverse': 7, 'dim': 2,
170 'italic': 3, 'underline': 4, 'inverse': 7, 'dim': 2,
160 'black_background': 40, 'red_background': 41,
171 'black_background': 40, 'red_background': 41,
161 'green_background': 42, 'yellow_background': 43,
172 'green_background': 42, 'yellow_background': 43,
162 'blue_background': 44, 'purple_background': 45,
173 'blue_background': 44, 'purple_background': 45,
163 'cyan_background': 46, 'white_background': 47}
174 'cyan_background': 46, 'white_background': 47}
164
175
165 def _terminfosetup(ui, mode):
176 def _terminfosetup(ui, mode):
166 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
177 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
167
178
168 global _terminfo_params
179 global _terminfo_params
169 # If we failed to load curses, we go ahead and return.
180 # If we failed to load curses, we go ahead and return.
170 if not _terminfo_params:
181 if not _terminfo_params:
171 return
182 return
172 # Otherwise, see what the config file says.
183 # Otherwise, see what the config file says.
173 if mode not in ('auto', 'terminfo'):
184 if mode not in ('auto', 'terminfo'):
174 return
185 return
175
186
176 _terminfo_params.update((key[6:], (False, int(val)))
187 _terminfo_params.update((key[6:], (False, int(val)))
177 for key, val in ui.configitems('color')
188 for key, val in ui.configitems('color')
178 if key.startswith('color.'))
189 if key.startswith('color.'))
179
190
180 try:
191 try:
181 curses.setupterm()
192 curses.setupterm()
182 except curses.error, e:
193 except curses.error, e:
183 _terminfo_params = {}
194 _terminfo_params = {}
184 return
195 return
185
196
186 for key, (b, e) in _terminfo_params.items():
197 for key, (b, e) in _terminfo_params.items():
187 if not b:
198 if not b:
188 continue
199 continue
189 if not curses.tigetstr(e):
200 if not curses.tigetstr(e):
190 # Most terminals don't support dim, invis, etc, so don't be
201 # Most terminals don't support dim, invis, etc, so don't be
191 # noisy and use ui.debug().
202 # noisy and use ui.debug().
192 ui.debug("no terminfo entry for %s\n" % e)
203 ui.debug("no terminfo entry for %s\n" % e)
193 del _terminfo_params[key]
204 del _terminfo_params[key]
194 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
205 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
195 # Only warn about missing terminfo entries if we explicitly asked for
206 # Only warn about missing terminfo entries if we explicitly asked for
196 # terminfo mode.
207 # terminfo mode.
197 if mode == "terminfo":
208 if mode == "terminfo":
198 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
209 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
199 "ECMA-48 color\n"))
210 "ECMA-48 color\n"))
200 _terminfo_params = {}
211 _terminfo_params = {}
201
212
202 def _modesetup(ui, coloropt):
213 def _modesetup(ui, coloropt):
203 global _terminfo_params
214 global _terminfo_params
204
215
205 if coloropt == 'debug':
216 if coloropt == 'debug':
206 return 'debug'
217 return 'debug'
207
218
208 auto = (coloropt == 'auto')
219 auto = (coloropt == 'auto')
209 always = not auto and util.parsebool(coloropt)
220 always = not auto and util.parsebool(coloropt)
210 if not always and not auto:
221 if not always and not auto:
211 return None
222 return None
212
223
213 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
224 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
214
225
215 mode = ui.config('color', 'mode', 'auto')
226 mode = ui.config('color', 'mode', 'auto')
227
228 # If pager is active, color.pagermode overrides color.mode.
229 if getattr(ui, 'pageractive', False):
230 mode = ui.config('color', 'pagermode', mode)
231
216 realmode = mode
232 realmode = mode
217 if mode == 'auto':
233 if mode == 'auto':
218 if os.name == 'nt':
234 if os.name == 'nt':
219 term = os.environ.get('TERM')
235 term = os.environ.get('TERM')
220 # TERM won't be defined in a vanilla cmd.exe environment.
236 # TERM won't be defined in a vanilla cmd.exe environment.
221 if not term:
237 if not term:
222 realmode = 'win32'
238 realmode = 'win32'
223
239
224 # UNIX-like environments on Windows such as Cygwin and MSYS will
240 # 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
241 # set TERM. They appear to make a best effort attempt at setting it
226 # to something appropriate. However, not all environments with TERM
242 # to something appropriate. However, not all environments with TERM
227 # defined support ANSI. Since "ansi" could result in terminal
243 # defined support ANSI. Since "ansi" could result in terminal
228 # gibberish, we error on the side of selecting "win32". However, if
244 # gibberish, we error on the side of selecting "win32". However, if
229 # w32effects is not defined, we almost certainly don't support
245 # w32effects is not defined, we almost certainly don't support
230 # "win32", so don't even try.
246 # "win32", so don't even try.
231 if 'xterm' in term or not w32effects:
247 if 'xterm' in term or not w32effects:
232 realmode = 'ansi'
248 realmode = 'ansi'
233 else:
249 else:
234 realmode = 'win32'
250 realmode = 'win32'
235 else:
251 else:
236 realmode = 'ansi'
252 realmode = 'ansi'
237
253
238 if realmode == 'win32':
254 if realmode == 'win32':
239 _terminfo_params = {}
255 _terminfo_params = {}
240 if not w32effects:
256 if not w32effects:
241 if mode == 'win32':
257 if mode == 'win32':
242 # only warn if color.mode is explicitly set to win32
258 # only warn if color.mode is explicitly set to win32
243 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
259 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
244 return None
260 return None
245 _effects.update(w32effects)
261 _effects.update(w32effects)
246 elif realmode == 'ansi':
262 elif realmode == 'ansi':
247 _terminfo_params = {}
263 _terminfo_params = {}
248 elif realmode == 'terminfo':
264 elif realmode == 'terminfo':
249 _terminfosetup(ui, mode)
265 _terminfosetup(ui, mode)
250 if not _terminfo_params:
266 if not _terminfo_params:
251 if mode == 'terminfo':
267 if mode == 'terminfo':
252 ## FIXME Shouldn't we return None in this case too?
268 ## FIXME Shouldn't we return None in this case too?
253 # only warn if color.mode is explicitly set to win32
269 # only warn if color.mode is explicitly set to win32
254 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
270 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
255 realmode = 'ansi'
271 realmode = 'ansi'
256 else:
272 else:
257 return None
273 return None
258
274
259 if always or (auto and formatted):
275 if always or (auto and formatted):
260 return realmode
276 return realmode
261 return None
277 return None
262
278
263 try:
279 try:
264 import curses
280 import curses
265 # Mapping from effect name to terminfo attribute name or color number.
281 # Mapping from effect name to terminfo attribute name or color number.
266 # This will also force-load the curses module.
282 # This will also force-load the curses module.
267 _terminfo_params = {'none': (True, 'sgr0'),
283 _terminfo_params = {'none': (True, 'sgr0'),
268 'standout': (True, 'smso'),
284 'standout': (True, 'smso'),
269 'underline': (True, 'smul'),
285 'underline': (True, 'smul'),
270 'reverse': (True, 'rev'),
286 'reverse': (True, 'rev'),
271 'inverse': (True, 'rev'),
287 'inverse': (True, 'rev'),
272 'blink': (True, 'blink'),
288 'blink': (True, 'blink'),
273 'dim': (True, 'dim'),
289 'dim': (True, 'dim'),
274 'bold': (True, 'bold'),
290 'bold': (True, 'bold'),
275 'invisible': (True, 'invis'),
291 'invisible': (True, 'invis'),
276 'italic': (True, 'sitm'),
292 'italic': (True, 'sitm'),
277 'black': (False, curses.COLOR_BLACK),
293 'black': (False, curses.COLOR_BLACK),
278 'red': (False, curses.COLOR_RED),
294 'red': (False, curses.COLOR_RED),
279 'green': (False, curses.COLOR_GREEN),
295 'green': (False, curses.COLOR_GREEN),
280 'yellow': (False, curses.COLOR_YELLOW),
296 'yellow': (False, curses.COLOR_YELLOW),
281 'blue': (False, curses.COLOR_BLUE),
297 'blue': (False, curses.COLOR_BLUE),
282 'magenta': (False, curses.COLOR_MAGENTA),
298 'magenta': (False, curses.COLOR_MAGENTA),
283 'cyan': (False, curses.COLOR_CYAN),
299 'cyan': (False, curses.COLOR_CYAN),
284 'white': (False, curses.COLOR_WHITE)}
300 'white': (False, curses.COLOR_WHITE)}
285 except ImportError:
301 except ImportError:
286 _terminfo_params = {}
302 _terminfo_params = {}
287
303
288 _styles = {'grep.match': 'red bold',
304 _styles = {'grep.match': 'red bold',
289 'grep.linenumber': 'green',
305 'grep.linenumber': 'green',
290 'grep.rev': 'green',
306 'grep.rev': 'green',
291 'grep.change': 'green',
307 'grep.change': 'green',
292 'grep.sep': 'cyan',
308 'grep.sep': 'cyan',
293 'grep.filename': 'magenta',
309 'grep.filename': 'magenta',
294 'grep.user': 'magenta',
310 'grep.user': 'magenta',
295 'grep.date': 'magenta',
311 'grep.date': 'magenta',
296 'bookmarks.current': 'green',
312 'bookmarks.current': 'green',
297 'branches.active': 'none',
313 'branches.active': 'none',
298 'branches.closed': 'black bold',
314 'branches.closed': 'black bold',
299 'branches.current': 'green',
315 'branches.current': 'green',
300 'branches.inactive': 'none',
316 'branches.inactive': 'none',
301 'diff.changed': 'white',
317 'diff.changed': 'white',
302 'diff.deleted': 'red',
318 'diff.deleted': 'red',
303 'diff.diffline': 'bold',
319 'diff.diffline': 'bold',
304 'diff.extended': 'cyan bold',
320 'diff.extended': 'cyan bold',
305 'diff.file_a': 'red bold',
321 'diff.file_a': 'red bold',
306 'diff.file_b': 'green bold',
322 'diff.file_b': 'green bold',
307 'diff.hunk': 'magenta',
323 'diff.hunk': 'magenta',
308 'diff.inserted': 'green',
324 'diff.inserted': 'green',
309 'diff.tab': '',
325 'diff.tab': '',
310 'diff.trailingwhitespace': 'bold red_background',
326 'diff.trailingwhitespace': 'bold red_background',
311 'changeset.public' : '',
327 'changeset.public' : '',
312 'changeset.draft' : '',
328 'changeset.draft' : '',
313 'changeset.secret' : '',
329 'changeset.secret' : '',
314 'diffstat.deleted': 'red',
330 'diffstat.deleted': 'red',
315 'diffstat.inserted': 'green',
331 'diffstat.inserted': 'green',
316 'histedit.remaining': 'red bold',
332 'histedit.remaining': 'red bold',
317 'ui.prompt': 'yellow',
333 'ui.prompt': 'yellow',
318 'log.changeset': 'yellow',
334 'log.changeset': 'yellow',
319 'patchbomb.finalsummary': '',
335 'patchbomb.finalsummary': '',
320 'patchbomb.from': 'magenta',
336 'patchbomb.from': 'magenta',
321 'patchbomb.to': 'cyan',
337 'patchbomb.to': 'cyan',
322 'patchbomb.subject': 'green',
338 'patchbomb.subject': 'green',
323 'patchbomb.diffstats': '',
339 'patchbomb.diffstats': '',
324 'rebase.rebased': 'blue',
340 'rebase.rebased': 'blue',
325 'rebase.remaining': 'red bold',
341 'rebase.remaining': 'red bold',
326 'resolve.resolved': 'green bold',
342 'resolve.resolved': 'green bold',
327 'resolve.unresolved': 'red bold',
343 'resolve.unresolved': 'red bold',
328 'shelve.age': 'cyan',
344 'shelve.age': 'cyan',
329 'shelve.newest': 'green bold',
345 'shelve.newest': 'green bold',
330 'shelve.name': 'blue bold',
346 'shelve.name': 'blue bold',
331 'status.added': 'green bold',
347 'status.added': 'green bold',
332 'status.clean': 'none',
348 'status.clean': 'none',
333 'status.copied': 'none',
349 'status.copied': 'none',
334 'status.deleted': 'cyan bold underline',
350 'status.deleted': 'cyan bold underline',
335 'status.ignored': 'black bold',
351 'status.ignored': 'black bold',
336 'status.modified': 'blue bold',
352 'status.modified': 'blue bold',
337 'status.removed': 'red bold',
353 'status.removed': 'red bold',
338 'status.unknown': 'magenta bold underline',
354 'status.unknown': 'magenta bold underline',
339 'tags.normal': 'green',
355 'tags.normal': 'green',
340 'tags.local': 'black bold'}
356 'tags.local': 'black bold'}
341
357
342
358
343 def _effect_str(effect):
359 def _effect_str(effect):
344 '''Helper function for render_effects().'''
360 '''Helper function for render_effects().'''
345
361
346 bg = False
362 bg = False
347 if effect.endswith('_background'):
363 if effect.endswith('_background'):
348 bg = True
364 bg = True
349 effect = effect[:-11]
365 effect = effect[:-11]
350 attr, val = _terminfo_params[effect]
366 attr, val = _terminfo_params[effect]
351 if attr:
367 if attr:
352 return curses.tigetstr(val)
368 return curses.tigetstr(val)
353 elif bg:
369 elif bg:
354 return curses.tparm(curses.tigetstr('setab'), val)
370 return curses.tparm(curses.tigetstr('setab'), val)
355 else:
371 else:
356 return curses.tparm(curses.tigetstr('setaf'), val)
372 return curses.tparm(curses.tigetstr('setaf'), val)
357
373
358 def render_effects(text, effects):
374 def render_effects(text, effects):
359 'Wrap text in commands to turn on each effect.'
375 'Wrap text in commands to turn on each effect.'
360 if not text:
376 if not text:
361 return text
377 return text
362 if not _terminfo_params:
378 if not _terminfo_params:
363 start = [str(_effects[e]) for e in ['none'] + effects.split()]
379 start = [str(_effects[e]) for e in ['none'] + effects.split()]
364 start = '\033[' + ';'.join(start) + 'm'
380 start = '\033[' + ';'.join(start) + 'm'
365 stop = '\033[' + str(_effects['none']) + 'm'
381 stop = '\033[' + str(_effects['none']) + 'm'
366 else:
382 else:
367 start = ''.join(_effect_str(effect)
383 start = ''.join(_effect_str(effect)
368 for effect in ['none'] + effects.split())
384 for effect in ['none'] + effects.split())
369 stop = _effect_str('none')
385 stop = _effect_str('none')
370 return ''.join([start, text, stop])
386 return ''.join([start, text, stop])
371
387
372 def extstyles():
388 def extstyles():
373 for name, ext in extensions.extensions():
389 for name, ext in extensions.extensions():
374 _styles.update(getattr(ext, 'colortable', {}))
390 _styles.update(getattr(ext, 'colortable', {}))
375
391
376 def valideffect(effect):
392 def valideffect(effect):
377 'Determine if the effect is valid or not.'
393 'Determine if the effect is valid or not.'
378 good = False
394 good = False
379 if not _terminfo_params and effect in _effects:
395 if not _terminfo_params and effect in _effects:
380 good = True
396 good = True
381 elif effect in _terminfo_params or effect[:-11] in _terminfo_params:
397 elif effect in _terminfo_params or effect[:-11] in _terminfo_params:
382 good = True
398 good = True
383 return good
399 return good
384
400
385 def configstyles(ui):
401 def configstyles(ui):
386 for status, cfgeffects in ui.configitems('color'):
402 for status, cfgeffects in ui.configitems('color'):
387 if '.' not in status or status.startswith('color.'):
403 if '.' not in status or status.startswith('color.'):
388 continue
404 continue
389 cfgeffects = ui.configlist('color', status)
405 cfgeffects = ui.configlist('color', status)
390 if cfgeffects:
406 if cfgeffects:
391 good = []
407 good = []
392 for e in cfgeffects:
408 for e in cfgeffects:
393 if valideffect(e):
409 if valideffect(e):
394 good.append(e)
410 good.append(e)
395 else:
411 else:
396 ui.warn(_("ignoring unknown color/effect %r "
412 ui.warn(_("ignoring unknown color/effect %r "
397 "(configured in color.%s)\n")
413 "(configured in color.%s)\n")
398 % (e, status))
414 % (e, status))
399 _styles[status] = ' '.join(good)
415 _styles[status] = ' '.join(good)
400
416
401 class colorui(uimod.ui):
417 class colorui(uimod.ui):
402 def popbuffer(self, labeled=False):
418 def popbuffer(self, labeled=False):
403 if self._colormode is None:
419 if self._colormode is None:
404 return super(colorui, self).popbuffer(labeled)
420 return super(colorui, self).popbuffer(labeled)
405
421
406 self._bufferstates.pop()
422 self._bufferstates.pop()
407 if labeled:
423 if labeled:
408 return ''.join(self.label(a, label) for a, label
424 return ''.join(self.label(a, label) for a, label
409 in self._buffers.pop())
425 in self._buffers.pop())
410 return ''.join(a for a, label in self._buffers.pop())
426 return ''.join(a for a, label in self._buffers.pop())
411
427
412 _colormode = 'ansi'
428 _colormode = 'ansi'
413 def write(self, *args, **opts):
429 def write(self, *args, **opts):
414 if self._colormode is None:
430 if self._colormode is None:
415 return super(colorui, self).write(*args, **opts)
431 return super(colorui, self).write(*args, **opts)
416
432
417 label = opts.get('label', '')
433 label = opts.get('label', '')
418 if self._buffers:
434 if self._buffers:
419 self._buffers[-1].extend([(str(a), label) for a in args])
435 self._buffers[-1].extend([(str(a), label) for a in args])
420 elif self._colormode == 'win32':
436 elif self._colormode == 'win32':
421 for a in args:
437 for a in args:
422 win32print(a, super(colorui, self).write, **opts)
438 win32print(a, super(colorui, self).write, **opts)
423 else:
439 else:
424 return super(colorui, self).write(
440 return super(colorui, self).write(
425 *[self.label(str(a), label) for a in args], **opts)
441 *[self.label(str(a), label) for a in args], **opts)
426
442
427 def write_err(self, *args, **opts):
443 def write_err(self, *args, **opts):
428 if self._colormode is None:
444 if self._colormode is None:
429 return super(colorui, self).write_err(*args, **opts)
445 return super(colorui, self).write_err(*args, **opts)
430
446
431 label = opts.get('label', '')
447 label = opts.get('label', '')
432 if self._bufferstates and self._bufferstates[-1]:
448 if self._bufferstates and self._bufferstates[-1]:
433 return self.write(*args, **opts)
449 return self.write(*args, **opts)
434 if self._colormode == 'win32':
450 if self._colormode == 'win32':
435 for a in args:
451 for a in args:
436 win32print(a, super(colorui, self).write_err, **opts)
452 win32print(a, super(colorui, self).write_err, **opts)
437 else:
453 else:
438 return super(colorui, self).write_err(
454 return super(colorui, self).write_err(
439 *[self.label(str(a), label) for a in args], **opts)
455 *[self.label(str(a), label) for a in args], **opts)
440
456
441 def showlabel(self, msg, label):
457 def showlabel(self, msg, label):
442 if label and msg:
458 if label and msg:
443 if msg[-1] == '\n':
459 if msg[-1] == '\n':
444 return "[%s|%s]\n" % (label, msg[:-1])
460 return "[%s|%s]\n" % (label, msg[:-1])
445 else:
461 else:
446 return "[%s|%s]" % (label, msg)
462 return "[%s|%s]" % (label, msg)
447 else:
463 else:
448 return msg
464 return msg
449
465
450 def label(self, msg, label):
466 def label(self, msg, label):
451 if self._colormode is None:
467 if self._colormode is None:
452 return super(colorui, self).label(msg, label)
468 return super(colorui, self).label(msg, label)
453
469
454 if self._colormode == 'debug':
470 if self._colormode == 'debug':
455 return self.showlabel(msg, label)
471 return self.showlabel(msg, label)
456
472
457 effects = []
473 effects = []
458 for l in label.split():
474 for l in label.split():
459 s = _styles.get(l, '')
475 s = _styles.get(l, '')
460 if s:
476 if s:
461 effects.append(s)
477 effects.append(s)
462 elif valideffect(l):
478 elif valideffect(l):
463 effects.append(l)
479 effects.append(l)
464 effects = ' '.join(effects)
480 effects = ' '.join(effects)
465 if effects:
481 if effects:
466 return '\n'.join([render_effects(s, effects)
482 return '\n'.join([render_effects(s, effects)
467 for s in msg.split('\n')])
483 for s in msg.split('\n')])
468 return msg
484 return msg
469
485
470 def templatelabel(context, mapping, args):
486 def templatelabel(context, mapping, args):
471 if len(args) != 2:
487 if len(args) != 2:
472 # i18n: "label" is a keyword
488 # i18n: "label" is a keyword
473 raise error.ParseError(_("label expects two arguments"))
489 raise error.ParseError(_("label expects two arguments"))
474
490
475 # add known effects to the mapping so symbols like 'red', 'bold',
491 # add known effects to the mapping so symbols like 'red', 'bold',
476 # etc. don't need to be quoted
492 # etc. don't need to be quoted
477 mapping.update(dict([(k, k) for k in _effects]))
493 mapping.update(dict([(k, k) for k in _effects]))
478
494
479 thing = templater._evalifliteral(args[1], context, mapping)
495 thing = templater._evalifliteral(args[1], context, mapping)
480
496
481 # apparently, repo could be a string that is the favicon?
497 # apparently, repo could be a string that is the favicon?
482 repo = mapping.get('repo', '')
498 repo = mapping.get('repo', '')
483 if isinstance(repo, str):
499 if isinstance(repo, str):
484 return thing
500 return thing
485
501
486 label = templater._evalifliteral(args[0], context, mapping)
502 label = templater._evalifliteral(args[0], context, mapping)
487
503
488 thing = templater.stringify(thing)
504 thing = templater.stringify(thing)
489 label = templater.stringify(label)
505 label = templater.stringify(label)
490
506
491 return repo.ui.label(thing, label)
507 return repo.ui.label(thing, label)
492
508
493 def uisetup(ui):
509 def uisetup(ui):
494 if ui.plain():
510 if ui.plain():
495 return
511 return
496 if not isinstance(ui, colorui):
512 if not isinstance(ui, colorui):
497 colorui.__bases__ = (ui.__class__,)
513 colorui.__bases__ = (ui.__class__,)
498 ui.__class__ = colorui
514 ui.__class__ = colorui
499 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
515 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
500 mode = _modesetup(ui_, opts['color'])
516 mode = _modesetup(ui_, opts['color'])
501 colorui._colormode = mode
517 colorui._colormode = mode
502 if mode and mode != 'debug':
518 if mode and mode != 'debug':
503 extstyles()
519 extstyles()
504 configstyles(ui_)
520 configstyles(ui_)
505 return orig(ui_, opts, cmd, cmdfunc)
521 return orig(ui_, opts, cmd, cmdfunc)
506 def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None):
522 def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None):
507 if gitsub.ui._colormode and len(commands) and commands[0] == "diff":
523 if gitsub.ui._colormode and len(commands) and commands[0] == "diff":
508 # insert the argument in the front,
524 # insert the argument in the front,
509 # the end of git diff arguments is used for paths
525 # the end of git diff arguments is used for paths
510 commands.insert(1, '--color')
526 commands.insert(1, '--color')
511 return orig(gitsub, commands, env, stream, cwd)
527 return orig(gitsub, commands, env, stream, cwd)
512 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
528 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
513 extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
529 extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
514 templater.funcs['label'] = templatelabel
530 templater.funcs['label'] = templatelabel
515
531
516 def extsetup(ui):
532 def extsetup(ui):
517 commands.globalopts.append(
533 commands.globalopts.append(
518 ('', 'color', 'auto',
534 ('', 'color', 'auto',
519 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
535 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
520 # and should not be translated
536 # and should not be translated
521 _("when to colorize (boolean, always, auto, never, or debug)"),
537 _("when to colorize (boolean, always, auto, never, or debug)"),
522 _('TYPE')))
538 _('TYPE')))
523
539
524 @command('debugcolor', [], 'hg debugcolor')
540 @command('debugcolor', [], 'hg debugcolor')
525 def debugcolor(ui, repo, **opts):
541 def debugcolor(ui, repo, **opts):
526 global _styles
542 global _styles
527 _styles = {}
543 _styles = {}
528 for effect in _effects.keys():
544 for effect in _effects.keys():
529 _styles[effect] = effect
545 _styles[effect] = effect
530 ui.write(('color mode: %s\n') % ui._colormode)
546 ui.write(('color mode: %s\n') % ui._colormode)
531 ui.write(_('available colors:\n'))
547 ui.write(_('available colors:\n'))
532 for label, colors in _styles.items():
548 for label, colors in _styles.items():
533 ui.write(('%s\n') % colors, label=label)
549 ui.write(('%s\n') % colors, label=label)
534
550
535 if os.name != 'nt':
551 if os.name != 'nt':
536 w32effects = None
552 w32effects = None
537 else:
553 else:
538 import re, ctypes
554 import re, ctypes
539
555
540 _kernel32 = ctypes.windll.kernel32
556 _kernel32 = ctypes.windll.kernel32
541
557
542 _WORD = ctypes.c_ushort
558 _WORD = ctypes.c_ushort
543
559
544 _INVALID_HANDLE_VALUE = -1
560 _INVALID_HANDLE_VALUE = -1
545
561
546 class _COORD(ctypes.Structure):
562 class _COORD(ctypes.Structure):
547 _fields_ = [('X', ctypes.c_short),
563 _fields_ = [('X', ctypes.c_short),
548 ('Y', ctypes.c_short)]
564 ('Y', ctypes.c_short)]
549
565
550 class _SMALL_RECT(ctypes.Structure):
566 class _SMALL_RECT(ctypes.Structure):
551 _fields_ = [('Left', ctypes.c_short),
567 _fields_ = [('Left', ctypes.c_short),
552 ('Top', ctypes.c_short),
568 ('Top', ctypes.c_short),
553 ('Right', ctypes.c_short),
569 ('Right', ctypes.c_short),
554 ('Bottom', ctypes.c_short)]
570 ('Bottom', ctypes.c_short)]
555
571
556 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
572 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
557 _fields_ = [('dwSize', _COORD),
573 _fields_ = [('dwSize', _COORD),
558 ('dwCursorPosition', _COORD),
574 ('dwCursorPosition', _COORD),
559 ('wAttributes', _WORD),
575 ('wAttributes', _WORD),
560 ('srWindow', _SMALL_RECT),
576 ('srWindow', _SMALL_RECT),
561 ('dwMaximumWindowSize', _COORD)]
577 ('dwMaximumWindowSize', _COORD)]
562
578
563 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
579 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
564 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
580 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
565
581
566 _FOREGROUND_BLUE = 0x0001
582 _FOREGROUND_BLUE = 0x0001
567 _FOREGROUND_GREEN = 0x0002
583 _FOREGROUND_GREEN = 0x0002
568 _FOREGROUND_RED = 0x0004
584 _FOREGROUND_RED = 0x0004
569 _FOREGROUND_INTENSITY = 0x0008
585 _FOREGROUND_INTENSITY = 0x0008
570
586
571 _BACKGROUND_BLUE = 0x0010
587 _BACKGROUND_BLUE = 0x0010
572 _BACKGROUND_GREEN = 0x0020
588 _BACKGROUND_GREEN = 0x0020
573 _BACKGROUND_RED = 0x0040
589 _BACKGROUND_RED = 0x0040
574 _BACKGROUND_INTENSITY = 0x0080
590 _BACKGROUND_INTENSITY = 0x0080
575
591
576 _COMMON_LVB_REVERSE_VIDEO = 0x4000
592 _COMMON_LVB_REVERSE_VIDEO = 0x4000
577 _COMMON_LVB_UNDERSCORE = 0x8000
593 _COMMON_LVB_UNDERSCORE = 0x8000
578
594
579 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
595 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
580 w32effects = {
596 w32effects = {
581 'none': -1,
597 'none': -1,
582 'black': 0,
598 'black': 0,
583 'red': _FOREGROUND_RED,
599 'red': _FOREGROUND_RED,
584 'green': _FOREGROUND_GREEN,
600 'green': _FOREGROUND_GREEN,
585 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
601 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
586 'blue': _FOREGROUND_BLUE,
602 'blue': _FOREGROUND_BLUE,
587 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
603 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
588 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
604 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
589 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
605 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
590 'bold': _FOREGROUND_INTENSITY,
606 'bold': _FOREGROUND_INTENSITY,
591 'black_background': 0x100, # unused value > 0x0f
607 'black_background': 0x100, # unused value > 0x0f
592 'red_background': _BACKGROUND_RED,
608 'red_background': _BACKGROUND_RED,
593 'green_background': _BACKGROUND_GREEN,
609 'green_background': _BACKGROUND_GREEN,
594 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
610 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
595 'blue_background': _BACKGROUND_BLUE,
611 'blue_background': _BACKGROUND_BLUE,
596 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
612 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
597 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
613 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
598 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
614 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
599 _BACKGROUND_BLUE),
615 _BACKGROUND_BLUE),
600 'bold_background': _BACKGROUND_INTENSITY,
616 'bold_background': _BACKGROUND_INTENSITY,
601 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
617 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
602 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
618 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
603 }
619 }
604
620
605 passthrough = set([_FOREGROUND_INTENSITY,
621 passthrough = set([_FOREGROUND_INTENSITY,
606 _BACKGROUND_INTENSITY,
622 _BACKGROUND_INTENSITY,
607 _COMMON_LVB_UNDERSCORE,
623 _COMMON_LVB_UNDERSCORE,
608 _COMMON_LVB_REVERSE_VIDEO])
624 _COMMON_LVB_REVERSE_VIDEO])
609
625
610 stdout = _kernel32.GetStdHandle(
626 stdout = _kernel32.GetStdHandle(
611 _STD_OUTPUT_HANDLE) # don't close the handle returned
627 _STD_OUTPUT_HANDLE) # don't close the handle returned
612 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
628 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
613 w32effects = None
629 w32effects = None
614 else:
630 else:
615 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
631 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
616 if not _kernel32.GetConsoleScreenBufferInfo(
632 if not _kernel32.GetConsoleScreenBufferInfo(
617 stdout, ctypes.byref(csbi)):
633 stdout, ctypes.byref(csbi)):
618 # stdout may not support GetConsoleScreenBufferInfo()
634 # stdout may not support GetConsoleScreenBufferInfo()
619 # when called from subprocess or redirected
635 # when called from subprocess or redirected
620 w32effects = None
636 w32effects = None
621 else:
637 else:
622 origattr = csbi.wAttributes
638 origattr = csbi.wAttributes
623 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
639 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
624 re.MULTILINE | re.DOTALL)
640 re.MULTILINE | re.DOTALL)
625
641
626 def win32print(text, orig, **opts):
642 def win32print(text, orig, **opts):
627 label = opts.get('label', '')
643 label = opts.get('label', '')
628 attr = origattr
644 attr = origattr
629
645
630 def mapcolor(val, attr):
646 def mapcolor(val, attr):
631 if val == -1:
647 if val == -1:
632 return origattr
648 return origattr
633 elif val in passthrough:
649 elif val in passthrough:
634 return attr | val
650 return attr | val
635 elif val > 0x0f:
651 elif val > 0x0f:
636 return (val & 0x70) | (attr & 0x8f)
652 return (val & 0x70) | (attr & 0x8f)
637 else:
653 else:
638 return (val & 0x07) | (attr & 0xf8)
654 return (val & 0x07) | (attr & 0xf8)
639
655
640 # determine console attributes based on labels
656 # determine console attributes based on labels
641 for l in label.split():
657 for l in label.split():
642 style = _styles.get(l, '')
658 style = _styles.get(l, '')
643 for effect in style.split():
659 for effect in style.split():
644 try:
660 try:
645 attr = mapcolor(w32effects[effect], attr)
661 attr = mapcolor(w32effects[effect], attr)
646 except KeyError:
662 except KeyError:
647 # w32effects could not have certain attributes so we skip
663 # w32effects could not have certain attributes so we skip
648 # them if not found
664 # them if not found
649 pass
665 pass
650 # hack to ensure regexp finds data
666 # hack to ensure regexp finds data
651 if not text.startswith('\033['):
667 if not text.startswith('\033['):
652 text = '\033[m' + text
668 text = '\033[m' + text
653
669
654 # Look for ANSI-like codes embedded in text
670 # Look for ANSI-like codes embedded in text
655 m = re.match(ansire, text)
671 m = re.match(ansire, text)
656
672
657 try:
673 try:
658 while m:
674 while m:
659 for sattr in m.group(1).split(';'):
675 for sattr in m.group(1).split(';'):
660 if sattr:
676 if sattr:
661 attr = mapcolor(int(sattr), attr)
677 attr = mapcolor(int(sattr), attr)
662 _kernel32.SetConsoleTextAttribute(stdout, attr)
678 _kernel32.SetConsoleTextAttribute(stdout, attr)
663 orig(m.group(2), **opts)
679 orig(m.group(2), **opts)
664 m = re.match(ansire, m.group(3))
680 m = re.match(ansire, m.group(3))
665 finally:
681 finally:
666 # Explicitly reset original attributes
682 # Explicitly reset original attributes
667 _kernel32.SetConsoleTextAttribute(stdout, origattr)
683 _kernel32.SetConsoleTextAttribute(stdout, origattr)
General Comments 0
You need to be logged in to leave comments. Login now