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