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