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