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