##// END OF EJS Templates
color: bring back colors with pager...
Yuya Nishihara -
r11207:1d714c15 default
parent child Browse files
Show More
@@ -1,287 +1,291
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 and resolve commands to add color to their
22 22 output 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 resolve.unresolved = red bold
61 61 resolve.resolved = green bold
62 62
63 63 bookmarks.current = green
64 64
65 65 The color extension will try to detect whether to use ANSI codes or
66 66 Win32 console APIs, unless it is made explicit::
67 67
68 68 [color]
69 69 mode = ansi
70 70
71 71 Any value other than 'ansi', 'win32', or 'auto' will disable color.
72 72
73 73 '''
74 74
75 75 import os, sys
76 76
77 77 from mercurial import commands, dispatch, extensions
78 78 from mercurial.i18n import _
79 79
80 80 # start and stop parameters for effects
81 81 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
82 82 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
83 83 'italic': 3, 'underline': 4, 'inverse': 7,
84 84 'black_background': 40, 'red_background': 41,
85 85 'green_background': 42, 'yellow_background': 43,
86 86 'blue_background': 44, 'purple_background': 45,
87 87 'cyan_background': 46, 'white_background': 47}
88 88
89 89 _styles = {'grep.match': 'red bold',
90 90 'diff.changed': 'white',
91 91 'diff.deleted': 'red',
92 92 'diff.diffline': 'bold',
93 93 'diff.extended': 'cyan bold',
94 94 'diff.file_a': 'red bold',
95 95 'diff.file_b': 'green bold',
96 96 'diff.hunk': 'magenta',
97 97 'diff.inserted': 'green',
98 98 'diff.trailingwhitespace': 'bold red_background',
99 99 'diffstat.deleted': 'red',
100 100 'diffstat.inserted': 'green',
101 101 'log.changeset': 'yellow',
102 102 'resolve.resolved': 'green bold',
103 103 'resolve.unresolved': 'red bold',
104 104 'status.added': 'green bold',
105 105 'status.clean': 'none',
106 106 'status.copied': 'none',
107 107 'status.deleted': 'cyan bold underline',
108 108 'status.ignored': 'black bold',
109 109 'status.modified': 'blue bold',
110 110 'status.removed': 'red bold',
111 111 'status.unknown': 'magenta bold underline'}
112 112
113 113
114 114 def render_effects(text, effects):
115 115 'Wrap text in commands to turn on each effect.'
116 116 if not text:
117 117 return text
118 118 start = [str(_effects[e]) for e in ['none'] + effects.split()]
119 119 start = '\033[' + ';'.join(start) + 'm'
120 120 stop = '\033[' + str(_effects['none']) + 'm'
121 121 return ''.join([start, text, stop])
122 122
123 123 def extstyles():
124 124 for name, ext in extensions.extensions():
125 125 _styles.update(getattr(ext, 'colortable', {}))
126 126
127 127 def configstyles(ui):
128 128 for status, cfgeffects in ui.configitems('color'):
129 129 if '.' not in status:
130 130 continue
131 131 cfgeffects = ui.configlist('color', status)
132 132 if cfgeffects:
133 133 good = []
134 134 for e in cfgeffects:
135 135 if e in _effects:
136 136 good.append(e)
137 137 else:
138 138 ui.warn(_("ignoring unknown color/effect %r "
139 139 "(configured in color.%s)\n")
140 140 % (e, status))
141 141 _styles[status] = ' '.join(good)
142 142
143 143 _buffers = None
144 144 def style(msg, label):
145 145 effects = []
146 146 for l in label.split():
147 147 s = _styles.get(l, '')
148 148 if s:
149 149 effects.append(s)
150 150 effects = ''.join(effects)
151 151 if effects:
152 152 return '\n'.join([render_effects(s, effects)
153 153 for s in msg.split('\n')])
154 154 return msg
155 155
156 156 def popbuffer(orig, labeled=False):
157 157 global _buffers
158 158 if labeled:
159 159 return ''.join(style(a, label) for a, label in _buffers.pop())
160 160 return ''.join(a for a, label in _buffers.pop())
161 161
162 162 mode = 'ansi'
163 163 def write(orig, *args, **opts):
164 164 label = opts.get('label', '')
165 165 global _buffers
166 166 if _buffers:
167 167 _buffers[-1].extend([(str(a), label) for a in args])
168 168 elif mode == 'win32':
169 169 for a in args:
170 170 win32print(a, orig, **opts)
171 171 else:
172 172 return orig(*[style(str(a), label) for a in args], **opts)
173 173
174 174 def write_err(orig, *args, **opts):
175 175 label = opts.get('label', '')
176 176 if mode == 'win32':
177 177 for a in args:
178 178 win32print(a, orig, **opts)
179 179 else:
180 180 return orig(*[style(str(a), label) for a in args], **opts)
181 181
182 182 def uisetup(ui):
183 183 if ui.plain():
184 184 return
185 185 global mode
186 186 mode = ui.config('color', 'mode', 'auto')
187 187 if mode == 'auto':
188 188 if os.name == 'nt' and 'TERM' not in os.environ:
189 189 # looks line a cmd.exe console, use win32 API or nothing
190 190 mode = w32effects and 'win32' or 'none'
191 191 else:
192 192 mode = 'ansi'
193 193 if mode == 'win32':
194 194 if w32effects is None:
195 195 # only warn if color.mode is explicitly set to win32
196 196 ui.warn(_('win32console not found, please install pywin32\n'))
197 197 return
198 198 _effects.update(w32effects)
199 199 elif mode != 'ansi':
200 200 return
201
202 # check isatty() before anything else changes it (like pager)
203 isatty = sys.__stdout__.isatty()
204
201 205 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
202 206 if (opts['color'] == 'always' or
203 207 (opts['color'] == 'auto' and (os.environ.get('TERM') != 'dumb'
204 and sys.__stdout__.isatty()))):
208 and isatty))):
205 209 global _buffers
206 210 _buffers = ui_._buffers
207 211 extensions.wrapfunction(ui_, 'popbuffer', popbuffer)
208 212 extensions.wrapfunction(ui_, 'write', write)
209 213 extensions.wrapfunction(ui_, 'write_err', write_err)
210 214 ui_.label = style
211 215 extstyles()
212 216 configstyles(ui)
213 217 return orig(ui_, opts, cmd, cmdfunc)
214 218 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
215 219
216 220 commands.globalopts.append(('', 'color', 'auto',
217 221 _("when to colorize (always, auto, or never)")))
218 222
219 223 try:
220 224 import re, pywintypes
221 225 from win32console import *
222 226
223 227 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
224 228 w32effects = {
225 229 'none': 0,
226 230 'black': 0,
227 231 'red': FOREGROUND_RED,
228 232 'green': FOREGROUND_GREEN,
229 233 'yellow': FOREGROUND_RED | FOREGROUND_GREEN,
230 234 'blue': FOREGROUND_BLUE,
231 235 'magenta': FOREGROUND_BLUE | FOREGROUND_RED,
232 236 'cyan': FOREGROUND_BLUE | FOREGROUND_GREEN,
233 237 'white': FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
234 238 'bold': FOREGROUND_INTENSITY,
235 239 'black_background': 0,
236 240 'red_background': BACKGROUND_RED,
237 241 'green_background': BACKGROUND_GREEN,
238 242 'yellow_background': BACKGROUND_RED | BACKGROUND_GREEN,
239 243 'blue_background': BACKGROUND_BLUE,
240 244 'purple_background': BACKGROUND_BLUE | BACKGROUND_RED,
241 245 'cyan_background': BACKGROUND_BLUE | BACKGROUND_GREEN,
242 246 'white_background': BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE,
243 247 'bold_background': BACKGROUND_INTENSITY,
244 248 'underline': COMMON_LVB_UNDERSCORE, # double-byte charsets only
245 249 'inverse': COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
246 250 }
247 251
248 252 stdout = GetStdHandle(STD_OUTPUT_HANDLE)
249 253 try:
250 254 origattr = stdout.GetConsoleScreenBufferInfo()['Attributes']
251 255 except pywintypes.error:
252 256 # stdout may be defined but not support
253 257 # GetConsoleScreenBufferInfo(), when called from subprocess or
254 258 # redirected.
255 259 raise ImportError()
256 260 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)', re.MULTILINE | re.DOTALL)
257 261
258 262 def win32print(text, orig, **opts):
259 263 label = opts.get('label', '')
260 264 attr = 0
261 265
262 266 # determine console attributes based on labels
263 267 for l in label.split():
264 268 style = _styles.get(l, '')
265 269 for effect in style.split():
266 270 attr |= w32effects[effect]
267 271
268 272 # hack to ensure regexp finds data
269 273 if not text.startswith('\033['):
270 274 text = '\033[m' + text
271 275
272 276 # Look for ANSI-like codes embedded in text
273 277 m = re.match(ansire, text)
274 278 while m:
275 279 for sattr in m.group(1).split(';'):
276 280 if sattr:
277 281 val = int(sattr)
278 282 attr = val and attr|val or 0
279 283 stdout.SetConsoleTextAttribute(attr or origattr)
280 284 orig(m.group(2), **opts)
281 285 m = re.match(ansire, m.group(3))
282 286
283 287 # Explicity reset original attributes
284 288 stdout.SetConsoleTextAttribute(origattr)
285 289
286 290 except ImportError:
287 291 w32effects = None
General Comments 0
You need to be logged in to leave comments. Login now