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