##// END OF EJS Templates
color: simplify & improve colorization of qseries...
Dan Villiom Podlaski Christiansen -
r9311:e37e9904 default
parent child Browse files
Show More
@@ -1,289 +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 import itertools
62
63
63 from mercurial import cmdutil, commands, extensions, error
64 from mercurial import cmdutil, commands, extensions, error
64 from mercurial.i18n import _
65 from mercurial.i18n import _
65
66
66 # start and stop parameters for effects
67 # start and stop parameters for effects
67 _effect_params = {'none': 0,
68 _effect_params = {'none': 0,
68 'black': 30,
69 'black': 30,
69 'red': 31,
70 'red': 31,
70 'green': 32,
71 'green': 32,
71 'yellow': 33,
72 'yellow': 33,
72 'blue': 34,
73 'blue': 34,
73 'magenta': 35,
74 'magenta': 35,
74 'cyan': 36,
75 'cyan': 36,
75 'white': 37,
76 'white': 37,
76 'bold': 1,
77 'bold': 1,
77 'italic': 3,
78 'italic': 3,
78 'underline': 4,
79 'underline': 4,
79 'inverse': 7,
80 'inverse': 7,
80 'black_background': 40,
81 'black_background': 40,
81 'red_background': 41,
82 'red_background': 41,
82 'green_background': 42,
83 'green_background': 42,
83 'yellow_background': 43,
84 'yellow_background': 43,
84 'blue_background': 44,
85 'blue_background': 44,
85 'purple_background': 45,
86 'purple_background': 45,
86 'cyan_background': 46,
87 'cyan_background': 46,
87 'white_background': 47}
88 'white_background': 47}
88
89
89 def render_effects(text, effects):
90 def render_effects(text, effects):
90 'Wrap text in commands to turn on each effect.'
91 'Wrap text in commands to turn on each effect.'
91 start = [str(_effect_params[e]) for e in ['none'] + effects]
92 start = [str(_effect_params[e]) for e in ['none'] + effects]
92 start = '\033[' + ';'.join(start) + 'm'
93 start = '\033[' + ';'.join(start) + 'm'
93 stop = '\033[' + str(_effect_params['none']) + 'm'
94 stop = '\033[' + str(_effect_params['none']) + 'm'
94 return ''.join([start, text, stop])
95 return ''.join([start, text, stop])
95
96
96 def colorstatus(orig, ui, repo, *pats, **opts):
97 def colorstatus(orig, ui, repo, *pats, **opts):
97 '''run the status command with colored output'''
98 '''run the status command with colored output'''
98
99
99 delimiter = opts['print0'] and '\0' or '\n'
100 delimiter = opts['print0'] and '\0' or '\n'
100
101
101 nostatus = opts.get('no_status')
102 nostatus = opts.get('no_status')
102 opts['no_status'] = False
103 opts['no_status'] = False
103 # run status and capture its output
104 # run status and capture its output
104 ui.pushbuffer()
105 ui.pushbuffer()
105 retval = orig(ui, repo, *pats, **opts)
106 retval = orig(ui, repo, *pats, **opts)
106 # filter out empty strings
107 # filter out empty strings
107 lines_with_status = [ line for line in ui.popbuffer().split(delimiter) if line ]
108 lines_with_status = [ line for line in ui.popbuffer().split(delimiter) if line ]
108
109
109 if nostatus:
110 if nostatus:
110 lines = [l[2:] for l in lines_with_status]
111 lines = [l[2:] for l in lines_with_status]
111 else:
112 else:
112 lines = lines_with_status
113 lines = lines_with_status
113
114
114 # apply color to output and display it
115 # apply color to output and display it
115 for i in xrange(len(lines)):
116 for i in xrange(len(lines)):
116 status = _status_abbreviations[lines_with_status[i][0]]
117 status = _status_abbreviations[lines_with_status[i][0]]
117 effects = _status_effects[status]
118 effects = _status_effects[status]
118 if effects:
119 if effects:
119 lines[i] = render_effects(lines[i], effects)
120 lines[i] = render_effects(lines[i], effects)
120 ui.write(lines[i] + delimiter)
121 ui.write(lines[i] + delimiter)
121 return retval
122 return retval
122
123
123 _status_abbreviations = { 'M': 'modified',
124 _status_abbreviations = { 'M': 'modified',
124 'A': 'added',
125 'A': 'added',
125 'R': 'removed',
126 'R': 'removed',
126 '!': 'deleted',
127 '!': 'deleted',
127 '?': 'unknown',
128 '?': 'unknown',
128 'I': 'ignored',
129 'I': 'ignored',
129 'C': 'clean',
130 'C': 'clean',
130 ' ': 'copied', }
131 ' ': 'copied', }
131
132
132 _status_effects = { 'modified': ['blue', 'bold'],
133 _status_effects = { 'modified': ['blue', 'bold'],
133 'added': ['green', 'bold'],
134 'added': ['green', 'bold'],
134 'removed': ['red', 'bold'],
135 'removed': ['red', 'bold'],
135 'deleted': ['cyan', 'bold', 'underline'],
136 'deleted': ['cyan', 'bold', 'underline'],
136 'unknown': ['magenta', 'bold', 'underline'],
137 'unknown': ['magenta', 'bold', 'underline'],
137 'ignored': ['black', 'bold'],
138 'ignored': ['black', 'bold'],
138 'clean': ['none'],
139 'clean': ['none'],
139 'copied': ['none'], }
140 'copied': ['none'], }
140
141
141 def colorqseries(orig, ui, repo, *dummy, **opts):
142 def colorqseries(orig, ui, repo, *dummy, **opts):
142 '''run the qseries command with colored output'''
143 '''run the qseries command with colored output'''
143 ui.pushbuffer()
144 ui.pushbuffer()
144 retval = orig(ui, repo, **opts)
145 retval = orig(ui, repo, **opts)
145 patches = ui.popbuffer().splitlines()
146 patchlines = ui.popbuffer().splitlines()
146 for patch in patches:
147 patchnames = repo.mq.series
147 patchname = patch
148 if opts['summary']:
149 patchname = patchname.split(': ', 1)[0]
150 if ui.verbose:
151 patchname = patchname.lstrip().split(' ', 2)[-1]
152
148
149 for patch, patchname in itertools.izip(patchlines, patchnames):
153 if opts['missing']:
150 if opts['missing']:
154 effects = _patch_effects['missing']
151 effects = _patch_effects['missing']
155 # Determine if patch is applied.
152 # Determine if patch is applied.
156 elif [ applied for applied in repo.mq.applied
153 elif [ applied for applied in repo.mq.applied
157 if patchname == applied.name ]:
154 if patchname == applied.name ]:
158 effects = _patch_effects['applied']
155 effects = _patch_effects['applied']
159 else:
156 else:
160 effects = _patch_effects['unapplied']
157 effects = _patch_effects['unapplied']
161
158
162 patch = patch.replace(patchname, render_effects(patchname, effects), 1)
159 patch = patch.replace(patchname, render_effects(patchname, effects), 1)
163 ui.write(patch + '\n')
160 ui.write(patch + '\n')
164 return retval
161 return retval
165
162
166 _patch_effects = { 'applied': ['blue', 'bold', 'underline'],
163 _patch_effects = { 'applied': ['blue', 'bold', 'underline'],
167 'missing': ['red', 'bold'],
164 'missing': ['red', 'bold'],
168 'unapplied': ['black', 'bold'], }
165 'unapplied': ['black', 'bold'], }
169
166
170 def colorwrap(orig, s):
167 def colorwrap(orig, s):
171 '''wrap ui.write for colored diff output'''
168 '''wrap ui.write for colored diff output'''
172 lines = s.split('\n')
169 lines = s.split('\n')
173 for i, line in enumerate(lines):
170 for i, line in enumerate(lines):
174 stripline = line
171 stripline = line
175 if line and line[0] in '+-':
172 if line and line[0] in '+-':
176 # highlight trailing whitespace, but only in changed lines
173 # highlight trailing whitespace, but only in changed lines
177 stripline = line.rstrip()
174 stripline = line.rstrip()
178 for prefix, style in _diff_prefixes:
175 for prefix, style in _diff_prefixes:
179 if stripline.startswith(prefix):
176 if stripline.startswith(prefix):
180 lines[i] = render_effects(stripline, _diff_effects[style])
177 lines[i] = render_effects(stripline, _diff_effects[style])
181 break
178 break
182 if line != stripline:
179 if line != stripline:
183 lines[i] += render_effects(
180 lines[i] += render_effects(
184 line[len(stripline):], _diff_effects['trailingwhitespace'])
181 line[len(stripline):], _diff_effects['trailingwhitespace'])
185 orig('\n'.join(lines))
182 orig('\n'.join(lines))
186
183
187 def colorshowpatch(orig, self, node):
184 def colorshowpatch(orig, self, node):
188 '''wrap cmdutil.changeset_printer.showpatch with colored output'''
185 '''wrap cmdutil.changeset_printer.showpatch with colored output'''
189 oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap)
186 oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap)
190 try:
187 try:
191 orig(self, node)
188 orig(self, node)
192 finally:
189 finally:
193 self.ui.write = oldwrite
190 self.ui.write = oldwrite
194
191
195 def colordiff(orig, ui, repo, *pats, **opts):
192 def colordiff(orig, ui, repo, *pats, **opts):
196 '''run the diff command with colored output'''
193 '''run the diff command with colored output'''
197 oldwrite = extensions.wrapfunction(ui, 'write', colorwrap)
194 oldwrite = extensions.wrapfunction(ui, 'write', colorwrap)
198 try:
195 try:
199 orig(ui, repo, *pats, **opts)
196 orig(ui, repo, *pats, **opts)
200 finally:
197 finally:
201 ui.write = oldwrite
198 ui.write = oldwrite
202
199
203 _diff_prefixes = [('diff', 'diffline'),
200 _diff_prefixes = [('diff', 'diffline'),
204 ('copy', 'extended'),
201 ('copy', 'extended'),
205 ('rename', 'extended'),
202 ('rename', 'extended'),
206 ('old', 'extended'),
203 ('old', 'extended'),
207 ('new', 'extended'),
204 ('new', 'extended'),
208 ('deleted', 'extended'),
205 ('deleted', 'extended'),
209 ('---', 'file_a'),
206 ('---', 'file_a'),
210 ('+++', 'file_b'),
207 ('+++', 'file_b'),
211 ('@', 'hunk'),
208 ('@', 'hunk'),
212 ('-', 'deleted'),
209 ('-', 'deleted'),
213 ('+', 'inserted')]
210 ('+', 'inserted')]
214
211
215 _diff_effects = {'diffline': ['bold'],
212 _diff_effects = {'diffline': ['bold'],
216 'extended': ['cyan', 'bold'],
213 'extended': ['cyan', 'bold'],
217 'file_a': ['red', 'bold'],
214 'file_a': ['red', 'bold'],
218 'file_b': ['green', 'bold'],
215 'file_b': ['green', 'bold'],
219 'hunk': ['magenta'],
216 'hunk': ['magenta'],
220 'deleted': ['red'],
217 'deleted': ['red'],
221 'inserted': ['green'],
218 'inserted': ['green'],
222 'changed': ['white'],
219 'changed': ['white'],
223 'trailingwhitespace': ['bold', 'red_background']}
220 'trailingwhitespace': ['bold', 'red_background']}
224
221
225 _ui = None
222 _ui = None
226
223
227 def uisetup(ui):
224 def uisetup(ui):
228 '''Initialize the extension.'''
225 '''Initialize the extension.'''
229 global _ui
226 global _ui
230 _ui = ui
227 _ui = ui
231 _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects)
228 _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects)
232 _setupcmd(ui, 'incoming', commands.table, None, _diff_effects)
229 _setupcmd(ui, 'incoming', commands.table, None, _diff_effects)
233 _setupcmd(ui, 'log', commands.table, None, _diff_effects)
230 _setupcmd(ui, 'log', commands.table, None, _diff_effects)
234 _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects)
231 _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects)
235 _setupcmd(ui, 'tip', commands.table, None, _diff_effects)
232 _setupcmd(ui, 'tip', commands.table, None, _diff_effects)
236 _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
233 _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
237
234
238 def extsetup():
235 def extsetup():
239 try:
236 try:
240 mq = extensions.find('mq')
237 mq = extensions.find('mq')
241 try:
238 try:
242 # If we are loaded after mq, we must wrap commands.table
239 # If we are loaded after mq, we must wrap commands.table
243 _setupcmd(_ui, 'qdiff', commands.table, colordiff, _diff_effects)
240 _setupcmd(_ui, 'qdiff', commands.table, colordiff, _diff_effects)
244 _setupcmd(_ui, 'qseries', commands.table, colorqseries, _patch_effects)
241 _setupcmd(_ui, 'qseries', commands.table, colorqseries, _patch_effects)
245 except error.UnknownCommand:
242 except error.UnknownCommand:
246 # Otherwise we wrap mq.cmdtable
243 # Otherwise we wrap mq.cmdtable
247 _setupcmd(_ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects)
244 _setupcmd(_ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects)
248 _setupcmd(_ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects)
245 _setupcmd(_ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects)
249 except KeyError:
246 except KeyError:
250 # The mq extension is not enabled
247 # The mq extension is not enabled
251 pass
248 pass
252
249
253 def _setupcmd(ui, cmd, table, func, effectsmap):
250 def _setupcmd(ui, cmd, table, func, effectsmap):
254 '''patch in command to command table and load effect map'''
251 '''patch in command to command table and load effect map'''
255 def nocolor(orig, *args, **opts):
252 def nocolor(orig, *args, **opts):
256
253
257 if (opts['no_color'] or opts['color'] == 'never' or
254 if (opts['no_color'] or opts['color'] == 'never' or
258 (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb'
255 (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb'
259 or not sys.__stdout__.isatty()))):
256 or not sys.__stdout__.isatty()))):
260 return orig(*args, **opts)
257 return orig(*args, **opts)
261
258
262 oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer,
259 oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer,
263 'showpatch', colorshowpatch)
260 'showpatch', colorshowpatch)
264 try:
261 try:
265 if func is not None:
262 if func is not None:
266 return func(orig, *args, **opts)
263 return func(orig, *args, **opts)
267 return orig(*args, **opts)
264 return orig(*args, **opts)
268 finally:
265 finally:
269 cmdutil.changeset_printer.showpatch = oldshowpatch
266 cmdutil.changeset_printer.showpatch = oldshowpatch
270
267
271 entry = extensions.wrapcommand(table, cmd, nocolor)
268 entry = extensions.wrapcommand(table, cmd, nocolor)
272 entry[1].extend([
269 entry[1].extend([
273 ('', 'color', 'auto', _("when to colorize (always, auto, or never)")),
270 ('', 'color', 'auto', _("when to colorize (always, auto, or never)")),
274 ('', 'no-color', None, _("don't colorize output")),
271 ('', 'no-color', None, _("don't colorize output")),
275 ])
272 ])
276
273
277 for status in effectsmap:
274 for status in effectsmap:
278 configkey = cmd + '.' + status
275 configkey = cmd + '.' + status
279 effects = ui.configlist('color', configkey)
276 effects = ui.configlist('color', configkey)
280 if effects:
277 if effects:
281 good = []
278 good = []
282 for e in effects:
279 for e in effects:
283 if e in _effect_params:
280 if e in _effect_params:
284 good.append(e)
281 good.append(e)
285 else:
282 else:
286 ui.warn(_("ignoring unknown color/effect %r "
283 ui.warn(_("ignoring unknown color/effect %r "
287 "(configured in color.%s)\n")
284 "(configured in color.%s)\n")
288 % (e, configkey))
285 % (e, configkey))
289 effectsmap[status] = good
286 effectsmap[status] = good
General Comments 0
You need to be logged in to leave comments. Login now