##// END OF EJS Templates
color: allow multiple args to ui.write()
Kevin Bullock -
r9520:bcc27ee3 default
parent child Browse files
Show More
@@ -1,287 +1,286 b''
1 # color.py color output for the status and qseries commands
1 # color.py color output for the status and qseries 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 program is free software; you can redistribute it and/or modify it
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2 of the License, or (at your
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
8 # option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful, but
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 # Public License for more details.
13 # Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License along
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
18
19 '''colorize output from some commands
19 '''colorize output from some commands
20
20
21 This extension modifies the status command to add color to its output
21 This extension modifies the status command to add color to its output
22 to reflect file status, the qseries command to add color to reflect
22 to reflect file status, the qseries command to add color to reflect
23 patch status (applied, unapplied, missing), and to diff-related
23 patch status (applied, unapplied, missing), and to diff-related
24 commands to highlight additions, removals, diff headers, and trailing
24 commands to highlight additions, removals, diff headers, and trailing
25 whitespace.
25 whitespace.
26
26
27 Other effects in addition to color, like bold and underlined text, are
27 Other effects in addition to color, like bold and underlined text, are
28 also available. Effects are rendered with the ECMA-48 SGR control
28 also available. Effects are rendered with the ECMA-48 SGR control
29 function (aka ANSI escape codes). This module also provides the
29 function (aka ANSI escape codes). This module also provides the
30 render_text function, which can be used to add effects to any text.
30 render_text function, which can be used to add effects to any text.
31
31
32 Default effects may be overridden from the .hgrc file:
32 Default effects may be overridden from the .hgrc file:
33
33
34 [color]
34 [color]
35 status.modified = blue bold underline red_background
35 status.modified = blue bold underline red_background
36 status.added = green bold
36 status.added = green bold
37 status.removed = red bold blue_background
37 status.removed = red bold blue_background
38 status.deleted = cyan bold underline
38 status.deleted = cyan bold underline
39 status.unknown = magenta bold underline
39 status.unknown = magenta bold underline
40 status.ignored = black bold
40 status.ignored = black bold
41
41
42 # 'none' turns off all effects
42 # 'none' turns off all effects
43 status.clean = none
43 status.clean = none
44 status.copied = none
44 status.copied = none
45
45
46 qseries.applied = blue bold underline
46 qseries.applied = blue bold underline
47 qseries.unapplied = black bold
47 qseries.unapplied = black bold
48 qseries.missing = red bold
48 qseries.missing = red bold
49
49
50 diff.diffline = bold
50 diff.diffline = bold
51 diff.extended = cyan bold
51 diff.extended = cyan bold
52 diff.file_a = red bold
52 diff.file_a = red bold
53 diff.file_b = green bold
53 diff.file_b = green bold
54 diff.hunk = magenta
54 diff.hunk = magenta
55 diff.deleted = red
55 diff.deleted = red
56 diff.inserted = green
56 diff.inserted = green
57 diff.changed = white
57 diff.changed = white
58 diff.trailingwhitespace = bold red_background
58 diff.trailingwhitespace = bold red_background
59 '''
59 '''
60
60
61 import os, sys
61 import os, sys
62
62
63 from mercurial import cmdutil, commands, extensions, error
63 from mercurial import cmdutil, commands, extensions, error
64 from mercurial.i18n import _
64 from mercurial.i18n import _
65
65
66 # start and stop parameters for effects
66 # start and stop parameters for effects
67 _effect_params = {'none': 0,
67 _effect_params = {'none': 0,
68 'black': 30,
68 'black': 30,
69 'red': 31,
69 'red': 31,
70 'green': 32,
70 'green': 32,
71 'yellow': 33,
71 'yellow': 33,
72 'blue': 34,
72 'blue': 34,
73 'magenta': 35,
73 'magenta': 35,
74 'cyan': 36,
74 'cyan': 36,
75 'white': 37,
75 'white': 37,
76 'bold': 1,
76 'bold': 1,
77 'italic': 3,
77 'italic': 3,
78 'underline': 4,
78 'underline': 4,
79 'inverse': 7,
79 'inverse': 7,
80 'black_background': 40,
80 'black_background': 40,
81 'red_background': 41,
81 'red_background': 41,
82 'green_background': 42,
82 'green_background': 42,
83 'yellow_background': 43,
83 'yellow_background': 43,
84 'blue_background': 44,
84 'blue_background': 44,
85 'purple_background': 45,
85 'purple_background': 45,
86 'cyan_background': 46,
86 'cyan_background': 46,
87 'white_background': 47}
87 'white_background': 47}
88
88
89 def render_effects(text, effects):
89 def render_effects(text, effects):
90 'Wrap text in commands to turn on each effect.'
90 'Wrap text in commands to turn on each effect.'
91 start = [str(_effect_params[e]) for e in ['none'] + effects]
91 start = [str(_effect_params[e]) for e in ['none'] + effects]
92 start = '\033[' + ';'.join(start) + 'm'
92 start = '\033[' + ';'.join(start) + 'm'
93 stop = '\033[' + str(_effect_params['none']) + 'm'
93 stop = '\033[' + str(_effect_params['none']) + 'm'
94 return ''.join([start, text, stop])
94 return ''.join([start, text, stop])
95
95
96 def colorstatus(orig, ui, repo, *pats, **opts):
96 def colorstatus(orig, ui, repo, *pats, **opts):
97 '''run the status command with colored output'''
97 '''run the status command with colored output'''
98
98
99 delimiter = opts['print0'] and '\0' or '\n'
99 delimiter = opts['print0'] and '\0' or '\n'
100
100
101 nostatus = opts.get('no_status')
101 nostatus = opts.get('no_status')
102 opts['no_status'] = False
102 opts['no_status'] = False
103 # run status and capture its output
103 # run status and capture its output
104 ui.pushbuffer()
104 ui.pushbuffer()
105 retval = orig(ui, repo, *pats, **opts)
105 retval = orig(ui, repo, *pats, **opts)
106 # filter out empty strings
106 # filter out empty strings
107 lines_with_status = [ line for line in ui.popbuffer().split(delimiter) if line ]
107 lines_with_status = [ line for line in ui.popbuffer().split(delimiter) if line ]
108
108
109 if nostatus:
109 if nostatus:
110 lines = [l[2:] for l in lines_with_status]
110 lines = [l[2:] for l in lines_with_status]
111 else:
111 else:
112 lines = lines_with_status
112 lines = lines_with_status
113
113
114 # apply color to output and display it
114 # apply color to output and display it
115 for i in xrange(len(lines)):
115 for i in xrange(len(lines)):
116 status = _status_abbreviations[lines_with_status[i][0]]
116 status = _status_abbreviations[lines_with_status[i][0]]
117 effects = _status_effects[status]
117 effects = _status_effects[status]
118 if effects:
118 if effects:
119 lines[i] = render_effects(lines[i], effects)
119 lines[i] = render_effects(lines[i], effects)
120 ui.write(lines[i] + delimiter)
120 ui.write(lines[i] + delimiter)
121 return retval
121 return retval
122
122
123 _status_abbreviations = { 'M': 'modified',
123 _status_abbreviations = { 'M': 'modified',
124 'A': 'added',
124 'A': 'added',
125 'R': 'removed',
125 'R': 'removed',
126 '!': 'deleted',
126 '!': 'deleted',
127 '?': 'unknown',
127 '?': 'unknown',
128 'I': 'ignored',
128 'I': 'ignored',
129 'C': 'clean',
129 'C': 'clean',
130 ' ': 'copied', }
130 ' ': 'copied', }
131
131
132 _status_effects = { 'modified': ['blue', 'bold'],
132 _status_effects = { 'modified': ['blue', 'bold'],
133 'added': ['green', 'bold'],
133 'added': ['green', 'bold'],
134 'removed': ['red', 'bold'],
134 'removed': ['red', 'bold'],
135 'deleted': ['cyan', 'bold', 'underline'],
135 'deleted': ['cyan', 'bold', 'underline'],
136 'unknown': ['magenta', 'bold', 'underline'],
136 'unknown': ['magenta', 'bold', 'underline'],
137 'ignored': ['black', 'bold'],
137 'ignored': ['black', 'bold'],
138 'clean': ['none'],
138 'clean': ['none'],
139 'copied': ['none'], }
139 'copied': ['none'], }
140
140
141 def colorqseries(orig, ui, repo, *dummy, **opts):
141 def colorqseries(orig, ui, repo, *dummy, **opts):
142 '''run the qseries command with colored output'''
142 '''run the qseries command with colored output'''
143 ui.pushbuffer()
143 ui.pushbuffer()
144 retval = orig(ui, repo, **opts)
144 retval = orig(ui, repo, **opts)
145 patches = ui.popbuffer().splitlines()
145 patches = ui.popbuffer().splitlines()
146 for patch in patches:
146 for patch in patches:
147 patchname = patch
147 patchname = patch
148 if opts['summary']:
148 if opts['summary']:
149 patchname = patchname.split(': ')[0]
149 patchname = patchname.split(': ')[0]
150 if ui.verbose:
150 if ui.verbose:
151 patchname = patchname.split(' ', 2)[-1]
151 patchname = patchname.split(' ', 2)[-1]
152
152
153 if opts['missing']:
153 if opts['missing']:
154 effects = _patch_effects['missing']
154 effects = _patch_effects['missing']
155 # Determine if patch is applied.
155 # Determine if patch is applied.
156 elif [ applied for applied in repo.mq.applied
156 elif [ applied for applied in repo.mq.applied
157 if patchname == applied.name ]:
157 if patchname == applied.name ]:
158 effects = _patch_effects['applied']
158 effects = _patch_effects['applied']
159 else:
159 else:
160 effects = _patch_effects['unapplied']
160 effects = _patch_effects['unapplied']
161 ui.write(render_effects(patch, effects) + '\n')
161 ui.write(render_effects(patch, effects) + '\n')
162 return retval
162 return retval
163
163
164 _patch_effects = { 'applied': ['blue', 'bold', 'underline'],
164 _patch_effects = { 'applied': ['blue', 'bold', 'underline'],
165 'missing': ['red', 'bold'],
165 'missing': ['red', 'bold'],
166 'unapplied': ['black', 'bold'], }
166 'unapplied': ['black', 'bold'], }
167
168 def colorwrap(orig, s):
167 def colorwrap(orig, s):
169 '''wrap ui.write for colored diff output'''
168 '''wrap ui.write for colored diff output'''
170 lines = s.split('\n')
169 lines = s.split('\n')
171 for i, line in enumerate(lines):
170 for i, line in enumerate(lines):
172 stripline = line
171 stripline = line
173 if line and line[0] in '+-':
172 if line and line[0] in '+-':
174 # highlight trailing whitespace, but only in changed lines
173 # highlight trailing whitespace, but only in changed lines
175 stripline = line.rstrip()
174 stripline = line.rstrip()
176 for prefix, style in _diff_prefixes:
175 for prefix, style in _diff_prefixes:
177 if stripline.startswith(prefix):
176 if stripline.startswith(prefix):
178 lines[i] = render_effects(stripline, _diff_effects[style])
177 lines[i] = render_effects(stripline, _diff_effects[style])
179 break
178 break
180 if line != stripline:
179 if line != stripline:
181 lines[i] += render_effects(
180 lines[i] += render_effects(
182 line[len(stripline):], _diff_effects['trailingwhitespace'])
181 line[len(stripline):], _diff_effects['trailingwhitespace'])
183 orig('\n'.join(lines))
182 orig('\n'.join(lines))
184
183
185 def colorshowpatch(orig, self, node):
184 def colorshowpatch(orig, self, node):
186 '''wrap cmdutil.changeset_printer.showpatch with colored output'''
185 '''wrap cmdutil.changeset_printer.showpatch with colored output'''
187 oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap)
186 oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap)
188 try:
187 try:
189 orig(self, node)
188 orig(self, node)
190 finally:
189 finally:
191 self.ui.write = oldwrite
190 self.ui.write = oldwrite
192
191
193 def colordiff(orig, ui, repo, *pats, **opts):
192 def colordiff(orig, ui, repo, *pats, **opts):
194 '''run the diff command with colored output'''
193 '''run the diff command with colored output'''
195 oldwrite = extensions.wrapfunction(ui, 'write', colorwrap)
194 oldwrite = extensions.wrapfunction(ui, 'write', colorwrap)
196 try:
195 try:
197 orig(ui, repo, *pats, **opts)
196 orig(ui, repo, *pats, **opts)
198 finally:
197 finally:
199 ui.write = oldwrite
198 ui.write = oldwrite
200
199
201 _diff_prefixes = [('diff', 'diffline'),
200 _diff_prefixes = [('diff', 'diffline'),
202 ('copy', 'extended'),
201 ('copy', 'extended'),
203 ('rename', 'extended'),
202 ('rename', 'extended'),
204 ('old', 'extended'),
203 ('old', 'extended'),
205 ('new', 'extended'),
204 ('new', 'extended'),
206 ('deleted', 'extended'),
205 ('deleted', 'extended'),
207 ('---', 'file_a'),
206 ('---', 'file_a'),
208 ('+++', 'file_b'),
207 ('+++', 'file_b'),
209 ('@', 'hunk'),
208 ('@', 'hunk'),
210 ('-', 'deleted'),
209 ('-', 'deleted'),
211 ('+', 'inserted')]
210 ('+', 'inserted')]
212
211
213 _diff_effects = {'diffline': ['bold'],
212 _diff_effects = {'diffline': ['bold'],
214 'extended': ['cyan', 'bold'],
213 'extended': ['cyan', 'bold'],
215 'file_a': ['red', 'bold'],
214 'file_a': ['red', 'bold'],
216 'file_b': ['green', 'bold'],
215 'file_b': ['green', 'bold'],
217 'hunk': ['magenta'],
216 'hunk': ['magenta'],
218 'deleted': ['red'],
217 'deleted': ['red'],
219 'inserted': ['green'],
218 'inserted': ['green'],
220 'changed': ['white'],
219 'changed': ['white'],
221 'trailingwhitespace': ['bold', 'red_background']}
220 'trailingwhitespace': ['bold', 'red_background']}
222
221
223 _ui = None
222 _ui = None
224
223
225 def uisetup(ui):
224 def uisetup(ui):
226 '''Initialize the extension.'''
225 '''Initialize the extension.'''
227 global _ui
226 global _ui
228 _ui = ui
227 _ui = ui
229 _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects)
228 _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects)
230 _setupcmd(ui, 'incoming', commands.table, None, _diff_effects)
229 _setupcmd(ui, 'incoming', commands.table, None, _diff_effects)
231 _setupcmd(ui, 'log', commands.table, None, _diff_effects)
230 _setupcmd(ui, 'log', commands.table, None, _diff_effects)
232 _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects)
231 _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects)
233 _setupcmd(ui, 'tip', commands.table, None, _diff_effects)
232 _setupcmd(ui, 'tip', commands.table, None, _diff_effects)
234 _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
233 _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
235
234
236 def extsetup():
235 def extsetup():
237 try:
236 try:
238 mq = extensions.find('mq')
237 mq = extensions.find('mq')
239 try:
238 try:
240 # If we are loaded after mq, we must wrap commands.table
239 # If we are loaded after mq, we must wrap commands.table
241 _setupcmd(_ui, 'qdiff', commands.table, colordiff, _diff_effects)
240 _setupcmd(_ui, 'qdiff', commands.table, colordiff, _diff_effects)
242 _setupcmd(_ui, 'qseries', commands.table, colorqseries, _patch_effects)
241 _setupcmd(_ui, 'qseries', commands.table, colorqseries, _patch_effects)
243 except error.UnknownCommand:
242 except error.UnknownCommand:
244 # Otherwise we wrap mq.cmdtable
243 # Otherwise we wrap mq.cmdtable
245 _setupcmd(_ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects)
244 _setupcmd(_ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects)
246 _setupcmd(_ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects)
245 _setupcmd(_ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects)
247 except KeyError:
246 except KeyError:
248 # The mq extension is not enabled
247 # The mq extension is not enabled
249 pass
248 pass
250
249
251 def _setupcmd(ui, cmd, table, func, effectsmap):
250 def _setupcmd(ui, cmd, table, func, effectsmap):
252 '''patch in command to command table and load effect map'''
251 '''patch in command to command table and load effect map'''
253 def nocolor(orig, *args, **opts):
252 def nocolor(orig, *args, **opts):
254
253
255 if (opts['no_color'] or opts['color'] == 'never' or
254 if (opts['no_color'] or opts['color'] == 'never' or
256 (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb'
255 (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb'
257 or not sys.__stdout__.isatty()))):
256 or not sys.__stdout__.isatty()))):
258 return orig(*args, **opts)
257 return orig(*args, **opts)
259
258
260 oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer,
259 oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer,
261 'showpatch', colorshowpatch)
260 'showpatch', colorshowpatch)
262 try:
261 try:
263 if func is not None:
262 if func is not None:
264 return func(orig, *args, **opts)
263 return func(orig, *args, **opts)
265 return orig(*args, **opts)
264 return orig(*args, **opts)
266 finally:
265 finally:
267 cmdutil.changeset_printer.showpatch = oldshowpatch
266 cmdutil.changeset_printer.showpatch = oldshowpatch
268
267
269 entry = extensions.wrapcommand(table, cmd, nocolor)
268 entry = extensions.wrapcommand(table, cmd, nocolor)
270 entry[1].extend([
269 entry[1].extend([
271 ('', 'color', 'auto', _("when to colorize (always, auto, or never)")),
270 ('', 'color', 'auto', _("when to colorize (always, auto, or never)")),
272 ('', 'no-color', None, _("don't colorize output")),
271 ('', 'no-color', None, _("don't colorize output")),
273 ])
272 ])
274
273
275 for status in effectsmap:
274 for status in effectsmap:
276 configkey = cmd + '.' + status
275 configkey = cmd + '.' + status
277 effects = ui.configlist('color', configkey)
276 effects = ui.configlist('color', configkey)
278 if effects:
277 if effects:
279 good = []
278 good = []
280 for e in effects:
279 for e in effects:
281 if e in _effect_params:
280 if e in _effect_params:
282 good.append(e)
281 good.append(e)
283 else:
282 else:
284 ui.warn(_("ignoring unknown color/effect %r "
283 ui.warn(_("ignoring unknown color/effect %r "
285 "(configured in color.%s)\n")
284 "(configured in color.%s)\n")
286 % (e, configkey))
285 % (e, configkey))
287 effectsmap[status] = good
286 effectsmap[status] = good
General Comments 0
You need to be logged in to leave comments. Login now