##// END OF EJS Templates
Consolidate color scheme localisations.
Matthias Bussonnier -
Show More
@@ -1,450 +1,416 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Classes for handling input/output prompts.
2 """Classes for handling input/output prompts.
3
3
4 Authors:
4 Authors:
5
5
6 * Fernando Perez
6 * Fernando Perez
7 * Brian Granger
7 * Brian Granger
8 * Thomas Kluyver
8 * Thomas Kluyver
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
13 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import os
23 import os
24 import re
24 import re
25 import socket
25 import socket
26 import sys
26 import sys
27 import time
27 import time
28
28
29 from string import Formatter
29 from string import Formatter
30
30
31 from traitlets.config.configurable import Configurable
31 from traitlets.config.configurable import Configurable
32 from IPython.core import release
32 from IPython.core import release
33 from IPython.utils import coloransi, py3compat
33 from IPython.utils import coloransi, py3compat
34 from traitlets import (Unicode, Instance, Dict, Bool, Int)
34 from traitlets import (Unicode, Instance, Dict, Bool, Int)
35
35
36 from IPython.utils.PyColorize import LightBGColors, LinuxColors, NoColor
37
36 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
37 # Color schemes for prompts
39 # Color schemes for prompts
38 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
39
41
40 InputColors = coloransi.InputTermColors # just a shorthand
42 InputColors = coloransi.InputTermColors # just a shorthand
41 Colors = coloransi.TermColors # just a shorthand
43 Colors = coloransi.TermColors # just a shorthand
42
44
43 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
45 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
44
46
45 PColNoColors = coloransi.ColorScheme(
46 'NoColor',
47 in_prompt = InputColors.NoColor, # Input prompt
48 in_number = InputColors.NoColor, # Input prompt number
49 in_prompt2 = InputColors.NoColor, # Continuation prompt
50 in_normal = InputColors.NoColor, # color off (usu. Colors.Normal)
51
52 out_prompt = Colors.NoColor, # Output prompt
53 out_number = Colors.NoColor, # Output prompt number
54
55 normal = Colors.NoColor # color off (usu. Colors.Normal)
56 )
57
58 # make some schemes as instances so we can copy them for modification easily:
59 PColLinux = coloransi.ColorScheme(
60 'Linux',
61 in_prompt = InputColors.Green,
62 in_number = InputColors.LightGreen,
63 in_prompt2 = InputColors.Green,
64 in_normal = InputColors.Normal, # color off (usu. Colors.Normal)
65
66 out_prompt = Colors.Red,
67 out_number = Colors.LightRed,
68
69 normal = Colors.Normal
70 )
71
72 # Slightly modified Linux for light backgrounds
73 PColLightBG = PColLinux.copy('LightBG')
74
75 PColLightBG.colors.update(
76 in_prompt = InputColors.Blue,
77 in_number = InputColors.LightBlue,
78 in_prompt2 = InputColors.Blue
79 )
80
81 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
82 # Utilities
48 # Utilities
83 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
84
50
85 class LazyEvaluate(object):
51 class LazyEvaluate(object):
86 """This is used for formatting strings with values that need to be updated
52 """This is used for formatting strings with values that need to be updated
87 at that time, such as the current time or working directory."""
53 at that time, such as the current time or working directory."""
88 def __init__(self, func, *args, **kwargs):
54 def __init__(self, func, *args, **kwargs):
89 self.func = func
55 self.func = func
90 self.args = args
56 self.args = args
91 self.kwargs = kwargs
57 self.kwargs = kwargs
92
58
93 def __call__(self, **kwargs):
59 def __call__(self, **kwargs):
94 self.kwargs.update(kwargs)
60 self.kwargs.update(kwargs)
95 return self.func(*self.args, **self.kwargs)
61 return self.func(*self.args, **self.kwargs)
96
62
97 def __str__(self):
63 def __str__(self):
98 return str(self())
64 return str(self())
99
65
100 def __unicode__(self):
66 def __unicode__(self):
101 return py3compat.unicode_type(self())
67 return py3compat.unicode_type(self())
102
68
103 def __format__(self, format_spec):
69 def __format__(self, format_spec):
104 return format(self(), format_spec)
70 return format(self(), format_spec)
105
71
106 def multiple_replace(dict, text):
72 def multiple_replace(dict, text):
107 """ Replace in 'text' all occurences of any key in the given
73 """ Replace in 'text' all occurrences of any key in the given
108 dictionary by its corresponding value. Returns the new string."""
74 dictionary by its corresponding value. Returns the new string."""
109
75
110 # Function by Xavier Defrang, originally found at:
76 # Function by Xavier Defrang, originally found at:
111 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
77 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
112
78
113 # Create a regular expression from the dictionary keys
79 # Create a regular expression from the dictionary keys
114 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
80 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
115 # For each match, look-up corresponding value in dictionary
81 # For each match, look-up corresponding value in dictionary
116 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
82 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
117
83
118 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
119 # Special characters that can be used in prompt templates, mainly bash-like
85 # Special characters that can be used in prompt templates, mainly bash-like
120 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
121
87
122 # If $HOME isn't defined (Windows), make it an absurd string so that it can
88 # If $HOME isn't defined (Windows), make it an absurd string so that it can
123 # never be expanded out into '~'. Basically anything which can never be a
89 # never be expanded out into '~'. Basically anything which can never be a
124 # reasonable directory name will do, we just want the $HOME -> '~' operation
90 # reasonable directory name will do, we just want the $HOME -> '~' operation
125 # to become a no-op. We pre-compute $HOME here so it's not done on every
91 # to become a no-op. We pre-compute $HOME here so it's not done on every
126 # prompt call.
92 # prompt call.
127
93
128 # FIXME:
94 # FIXME:
129
95
130 # - This should be turned into a class which does proper namespace management,
96 # - This should be turned into a class which does proper namespace management,
131 # since the prompt specials need to be evaluated in a certain namespace.
97 # since the prompt specials need to be evaluated in a certain namespace.
132 # Currently it's just globals, which need to be managed manually by code
98 # Currently it's just globals, which need to be managed manually by code
133 # below.
99 # below.
134
100
135 # - I also need to split up the color schemes from the prompt specials
101 # - I also need to split up the color schemes from the prompt specials
136 # somehow. I don't have a clean design for that quite yet.
102 # somehow. I don't have a clean design for that quite yet.
137
103
138 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
104 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
139
105
140 # This is needed on FreeBSD, and maybe other systems which symlink /home to
106 # This is needed on FreeBSD, and maybe other systems which symlink /home to
141 # /usr/home, but retain the $HOME variable as pointing to /home
107 # /usr/home, but retain the $HOME variable as pointing to /home
142 HOME = os.path.realpath(HOME)
108 HOME = os.path.realpath(HOME)
143
109
144 # We precompute a few more strings here for the prompt_specials, which are
110 # We precompute a few more strings here for the prompt_specials, which are
145 # fixed once ipython starts. This reduces the runtime overhead of computing
111 # fixed once ipython starts. This reduces the runtime overhead of computing
146 # prompt strings.
112 # prompt strings.
147 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
113 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
148 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
114 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
149 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
115 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
150
116
151 # IronPython doesn't currently have os.getuid() even if
117 # IronPython doesn't currently have os.getuid() even if
152 # os.name == 'posix'; 2/8/2014
118 # os.name == 'posix'; 2/8/2014
153 ROOT_SYMBOL = "#" if (os.name=='nt' or sys.platform=='cli' or os.getuid()==0) else "$"
119 ROOT_SYMBOL = "#" if (os.name=='nt' or sys.platform=='cli' or os.getuid()==0) else "$"
154
120
155 prompt_abbreviations = {
121 prompt_abbreviations = {
156 # Prompt/history count
122 # Prompt/history count
157 '%n' : '{color.number}' '{count}' '{color.prompt}',
123 '%n' : '{color.number}' '{count}' '{color.prompt}',
158 r'\#': '{color.number}' '{count}' '{color.prompt}',
124 r'\#': '{color.number}' '{count}' '{color.prompt}',
159 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
125 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
160 # can get numbers displayed in whatever color they want.
126 # can get numbers displayed in whatever color they want.
161 r'\N': '{count}',
127 r'\N': '{count}',
162
128
163 # Prompt/history count, with the actual digits replaced by dots or
129 # Prompt/history count, with the actual digits replaced by dots or
164 # spaces. Used mainly in continuation prompts (prompt_in2).
130 # spaces. Used mainly in continuation prompts (prompt_in2).
165 r'\D': '{dots}',
131 r'\D': '{dots}',
166 r'\S': '{spaces}',
132 r'\S': '{spaces}',
167
133
168 # Current time
134 # Current time
169 r'\T' : '{time}',
135 r'\T' : '{time}',
170 # Current working directory
136 # Current working directory
171 r'\w': '{cwd}',
137 r'\w': '{cwd}',
172 # Basename of current working directory.
138 # Basename of current working directory.
173 # (use os.sep to make this portable across OSes)
139 # (use os.sep to make this portable across OSes)
174 r'\W' : '{cwd_last}',
140 r'\W' : '{cwd_last}',
175 # These X<N> are an extension to the normal bash prompts. They return
141 # These X<N> are an extension to the normal bash prompts. They return
176 # N terms of the path, after replacing $HOME with '~'
142 # N terms of the path, after replacing $HOME with '~'
177 r'\X0': '{cwd_x[0]}',
143 r'\X0': '{cwd_x[0]}',
178 r'\X1': '{cwd_x[1]}',
144 r'\X1': '{cwd_x[1]}',
179 r'\X2': '{cwd_x[2]}',
145 r'\X2': '{cwd_x[2]}',
180 r'\X3': '{cwd_x[3]}',
146 r'\X3': '{cwd_x[3]}',
181 r'\X4': '{cwd_x[4]}',
147 r'\X4': '{cwd_x[4]}',
182 r'\X5': '{cwd_x[5]}',
148 r'\X5': '{cwd_x[5]}',
183 # Y<N> are similar to X<N>, but they show '~' if it's the directory
149 # Y<N> are similar to X<N>, but they show '~' if it's the directory
184 # N+1 in the list. Somewhat like %cN in tcsh.
150 # N+1 in the list. Somewhat like %cN in tcsh.
185 r'\Y0': '{cwd_y[0]}',
151 r'\Y0': '{cwd_y[0]}',
186 r'\Y1': '{cwd_y[1]}',
152 r'\Y1': '{cwd_y[1]}',
187 r'\Y2': '{cwd_y[2]}',
153 r'\Y2': '{cwd_y[2]}',
188 r'\Y3': '{cwd_y[3]}',
154 r'\Y3': '{cwd_y[3]}',
189 r'\Y4': '{cwd_y[4]}',
155 r'\Y4': '{cwd_y[4]}',
190 r'\Y5': '{cwd_y[5]}',
156 r'\Y5': '{cwd_y[5]}',
191 # Hostname up to first .
157 # Hostname up to first .
192 r'\h': HOSTNAME_SHORT,
158 r'\h': HOSTNAME_SHORT,
193 # Full hostname
159 # Full hostname
194 r'\H': HOSTNAME,
160 r'\H': HOSTNAME,
195 # Username of current user
161 # Username of current user
196 r'\u': USER,
162 r'\u': USER,
197 # Escaped '\'
163 # Escaped '\'
198 '\\\\': '\\',
164 '\\\\': '\\',
199 # Newline
165 # Newline
200 r'\n': '\n',
166 r'\n': '\n',
201 # Carriage return
167 # Carriage return
202 r'\r': '\r',
168 r'\r': '\r',
203 # Release version
169 # Release version
204 r'\v': release.version,
170 r'\v': release.version,
205 # Root symbol ($ or #)
171 # Root symbol ($ or #)
206 r'\$': ROOT_SYMBOL,
172 r'\$': ROOT_SYMBOL,
207 }
173 }
208
174
209 #-----------------------------------------------------------------------------
175 #-----------------------------------------------------------------------------
210 # More utilities
176 # More utilities
211 #-----------------------------------------------------------------------------
177 #-----------------------------------------------------------------------------
212
178
213 def cwd_filt(depth):
179 def cwd_filt(depth):
214 """Return the last depth elements of the current working directory.
180 """Return the last depth elements of the current working directory.
215
181
216 $HOME is always replaced with '~'.
182 $HOME is always replaced with '~'.
217 If depth==0, the full path is returned."""
183 If depth==0, the full path is returned."""
218
184
219 cwd = py3compat.getcwd().replace(HOME,"~")
185 cwd = py3compat.getcwd().replace(HOME,"~")
220 out = os.sep.join(cwd.split(os.sep)[-depth:])
186 out = os.sep.join(cwd.split(os.sep)[-depth:])
221 return out or os.sep
187 return out or os.sep
222
188
223 def cwd_filt2(depth):
189 def cwd_filt2(depth):
224 """Return the last depth elements of the current working directory.
190 """Return the last depth elements of the current working directory.
225
191
226 $HOME is always replaced with '~'.
192 $HOME is always replaced with '~'.
227 If depth==0, the full path is returned."""
193 If depth==0, the full path is returned."""
228
194
229 full_cwd = py3compat.getcwd()
195 full_cwd = py3compat.getcwd()
230 cwd = full_cwd.replace(HOME,"~").split(os.sep)
196 cwd = full_cwd.replace(HOME,"~").split(os.sep)
231 if '~' in cwd and len(cwd) == depth+1:
197 if '~' in cwd and len(cwd) == depth+1:
232 depth += 1
198 depth += 1
233 drivepart = ''
199 drivepart = ''
234 if sys.platform == 'win32' and len(cwd) > depth:
200 if sys.platform == 'win32' and len(cwd) > depth:
235 drivepart = os.path.splitdrive(full_cwd)[0]
201 drivepart = os.path.splitdrive(full_cwd)[0]
236 out = drivepart + '/'.join(cwd[-depth:])
202 out = drivepart + '/'.join(cwd[-depth:])
237
203
238 return out or os.sep
204 return out or os.sep
239
205
240 #-----------------------------------------------------------------------------
206 #-----------------------------------------------------------------------------
241 # Prompt classes
207 # Prompt classes
242 #-----------------------------------------------------------------------------
208 #-----------------------------------------------------------------------------
243
209
244 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
210 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
245 'cwd': LazyEvaluate(py3compat.getcwd),
211 'cwd': LazyEvaluate(py3compat.getcwd),
246 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
212 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
247 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
213 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
248 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
214 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
249 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
215 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
250 }
216 }
251
217
252 def _lenlastline(s):
218 def _lenlastline(s):
253 """Get the length of the last line. More intelligent than
219 """Get the length of the last line. More intelligent than
254 len(s.splitlines()[-1]).
220 len(s.splitlines()[-1]).
255 """
221 """
256 if not s or s.endswith(('\n', '\r')):
222 if not s or s.endswith(('\n', '\r')):
257 return 0
223 return 0
258 return len(s.splitlines()[-1])
224 return len(s.splitlines()[-1])
259
225
260
226
261 invisible_chars_re = re.compile('\001[^\001\002]*\002')
227 invisible_chars_re = re.compile('\001[^\001\002]*\002')
262 def _invisible_characters(s):
228 def _invisible_characters(s):
263 """
229 """
264 Get the number of invisible ANSI characters in s. Invisible characters
230 Get the number of invisible ANSI characters in s. Invisible characters
265 must be delimited by \001 and \002.
231 must be delimited by \001 and \002.
266 """
232 """
267 return _lenlastline(s) - _lenlastline(invisible_chars_re.sub('', s))
233 return _lenlastline(s) - _lenlastline(invisible_chars_re.sub('', s))
268
234
269 class UserNSFormatter(Formatter):
235 class UserNSFormatter(Formatter):
270 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
236 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
271 def __init__(self, shell):
237 def __init__(self, shell):
272 self.shell = shell
238 self.shell = shell
273
239
274 def get_value(self, key, args, kwargs):
240 def get_value(self, key, args, kwargs):
275 # try regular formatting first:
241 # try regular formatting first:
276 try:
242 try:
277 return Formatter.get_value(self, key, args, kwargs)
243 return Formatter.get_value(self, key, args, kwargs)
278 except Exception:
244 except Exception:
279 pass
245 pass
280 # next, look in user_ns and builtins:
246 # next, look in user_ns and builtins:
281 for container in (self.shell.user_ns, __builtins__):
247 for container in (self.shell.user_ns, __builtins__):
282 if key in container:
248 if key in container:
283 return container[key]
249 return container[key]
284 # nothing found, put error message in its place
250 # nothing found, put error message in its place
285 return "<ERROR: '%s' not found>" % key
251 return "<ERROR: '%s' not found>" % key
286
252
287
253
288 class PromptManager(Configurable):
254 class PromptManager(Configurable):
289 """This is the primary interface for producing IPython's prompts."""
255 """This is the primary interface for producing IPython's prompts."""
290 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
256 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
291
257
292 color_scheme_table = Instance(coloransi.ColorSchemeTable, allow_none=True)
258 color_scheme_table = Instance(coloransi.ColorSchemeTable, allow_none=True)
293 color_scheme = Unicode('Linux', config=True)
259 color_scheme = Unicode('Linux', config=True)
294 def _color_scheme_changed(self, name, new_value):
260 def _color_scheme_changed(self, name, new_value):
295 self.color_scheme_table.set_active_scheme(new_value)
261 self.color_scheme_table.set_active_scheme(new_value)
296 for pname in ['in', 'in2', 'out', 'rewrite']:
262 for pname in ['in', 'in2', 'out', 'rewrite']:
297 # We need to recalculate the number of invisible characters
263 # We need to recalculate the number of invisible characters
298 self.update_prompt(pname)
264 self.update_prompt(pname)
299
265
300 lazy_evaluate_fields = Dict(help="""
266 lazy_evaluate_fields = Dict(help="""
301 This maps field names used in the prompt templates to functions which
267 This maps field names used in the prompt templates to functions which
302 will be called when the prompt is rendered. This allows us to include
268 will be called when the prompt is rendered. This allows us to include
303 things like the current time in the prompts. Functions are only called
269 things like the current time in the prompts. Functions are only called
304 if they are used in the prompt.
270 if they are used in the prompt.
305 """)
271 """)
306 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
272 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
307
273
308 in_template = Unicode('In [\\#]: ', config=True,
274 in_template = Unicode('In [\\#]: ', config=True,
309 help="Input prompt. '\\#' will be transformed to the prompt number")
275 help="Input prompt. '\\#' will be transformed to the prompt number")
310 in2_template = Unicode(' .\\D.: ', config=True,
276 in2_template = Unicode(' .\\D.: ', config=True,
311 help="Continuation prompt.")
277 help="Continuation prompt.")
312 out_template = Unicode('Out[\\#]: ', config=True,
278 out_template = Unicode('Out[\\#]: ', config=True,
313 help="Output prompt. '\\#' will be transformed to the prompt number")
279 help="Output prompt. '\\#' will be transformed to the prompt number")
314
280
315 justify = Bool(True, config=True, help="""
281 justify = Bool(True, config=True, help="""
316 If True (default), each prompt will be right-aligned with the
282 If True (default), each prompt will be right-aligned with the
317 preceding one.
283 preceding one.
318 """)
284 """)
319
285
320 # We actually store the expanded templates here:
286 # We actually store the expanded templates here:
321 templates = Dict()
287 templates = Dict()
322
288
323 # The number of characters in the last prompt rendered, not including
289 # The number of characters in the last prompt rendered, not including
324 # colour characters.
290 # colour characters.
325 width = Int()
291 width = Int()
326 txtwidth = Int() # Not including right-justification
292 txtwidth = Int() # Not including right-justification
327
293
328 # The number of characters in each prompt which don't contribute to width
294 # The number of characters in each prompt which don't contribute to width
329 invisible_chars = Dict()
295 invisible_chars = Dict()
330 def _invisible_chars_default(self):
296 def _invisible_chars_default(self):
331 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
297 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
332
298
333 def __init__(self, shell, **kwargs):
299 def __init__(self, shell, **kwargs):
334 super(PromptManager, self).__init__(shell=shell, **kwargs)
300 super(PromptManager, self).__init__(shell=shell, **kwargs)
335
301
336 # Prepare colour scheme table
302 # Prepare colour scheme table
337 self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors,
303 self.color_scheme_table = coloransi.ColorSchemeTable([NoColor,
338 PColLinux, PColLightBG], self.color_scheme)
304 LinuxColors, LightBGColors], self.color_scheme)
339
305
340 self._formatter = UserNSFormatter(shell)
306 self._formatter = UserNSFormatter(shell)
341 # Prepare templates & numbers of invisible characters
307 # Prepare templates & numbers of invisible characters
342 self.update_prompt('in', self.in_template)
308 self.update_prompt('in', self.in_template)
343 self.update_prompt('in2', self.in2_template)
309 self.update_prompt('in2', self.in2_template)
344 self.update_prompt('out', self.out_template)
310 self.update_prompt('out', self.out_template)
345 self.update_prompt('rewrite')
311 self.update_prompt('rewrite')
346 self.on_trait_change(self._update_prompt_trait, ['in_template',
312 self.on_trait_change(self._update_prompt_trait, ['in_template',
347 'in2_template', 'out_template'])
313 'in2_template', 'out_template'])
348
314
349 def update_prompt(self, name, new_template=None):
315 def update_prompt(self, name, new_template=None):
350 """This is called when a prompt template is updated. It processes
316 """This is called when a prompt template is updated. It processes
351 abbreviations used in the prompt template (like \#) and calculates how
317 abbreviations used in the prompt template (like \#) and calculates how
352 many invisible characters (ANSI colour escapes) the resulting prompt
318 many invisible characters (ANSI colour escapes) the resulting prompt
353 contains.
319 contains.
354
320
355 It is also called for each prompt on changing the colour scheme. In both
321 It is also called for each prompt on changing the colour scheme. In both
356 cases, traitlets should take care of calling this automatically.
322 cases, traitlets should take care of calling this automatically.
357 """
323 """
358 if new_template is not None:
324 if new_template is not None:
359 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
325 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
360 # We count invisible characters (colour escapes) on the last line of the
326 # We count invisible characters (colour escapes) on the last line of the
361 # prompt, to calculate the width for lining up subsequent prompts.
327 # prompt, to calculate the width for lining up subsequent prompts.
362 invis_chars = _invisible_characters(self._render(name, color=True))
328 invis_chars = _invisible_characters(self._render(name, color=True))
363 self.invisible_chars[name] = invis_chars
329 self.invisible_chars[name] = invis_chars
364
330
365 def _update_prompt_trait(self, traitname, new_template):
331 def _update_prompt_trait(self, traitname, new_template):
366 name = traitname[:-9] # Cut off '_template'
332 name = traitname[:-9] # Cut off '_template'
367 self.update_prompt(name, new_template)
333 self.update_prompt(name, new_template)
368
334
369 def _render(self, name, color=True, **kwargs):
335 def _render(self, name, color=True, **kwargs):
370 """Render but don't justify, or update the width or txtwidth attributes.
336 """Render but don't justify, or update the width or txtwidth attributes.
371 """
337 """
372 if name == 'rewrite':
338 if name == 'rewrite':
373 return self._render_rewrite(color=color)
339 return self._render_rewrite(color=color)
374
340
375 if color:
341 if color:
376 scheme = self.color_scheme_table.active_colors
342 scheme = self.color_scheme_table.active_colors
377 if name=='out':
343 if name=='out':
378 colors = color_lists['normal']
344 colors = color_lists['normal']
379 colors.number, colors.prompt, colors.normal = \
345 colors.number, colors.prompt, colors.normal = \
380 scheme.out_number, scheme.out_prompt, scheme.normal
346 scheme.out_number, scheme.out_prompt, scheme.normal
381 else:
347 else:
382 colors = color_lists['inp']
348 colors = color_lists['inp']
383 colors.number, colors.prompt, colors.normal = \
349 colors.number, colors.prompt, colors.normal = \
384 scheme.in_number, scheme.in_prompt, scheme.in_normal
350 scheme.in_number, scheme.in_prompt, scheme.normal
385 if name=='in2':
351 if name=='in2':
386 colors.prompt = scheme.in_prompt2
352 colors.prompt = scheme.in_prompt2
387 else:
353 else:
388 # No color
354 # No color
389 colors = color_lists['nocolor']
355 colors = color_lists['nocolor']
390 colors.number, colors.prompt, colors.normal = '', '', ''
356 colors.number, colors.prompt, colors.normal = '', '', ''
391
357
392 count = self.shell.execution_count # Shorthand
358 count = self.shell.execution_count # Shorthand
393 # Build the dictionary to be passed to string formatting
359 # Build the dictionary to be passed to string formatting
394 fmtargs = dict(color=colors, count=count,
360 fmtargs = dict(color=colors, count=count,
395 dots="."*len(str(count)), spaces=" "*len(str(count)),
361 dots="."*len(str(count)), spaces=" "*len(str(count)),
396 width=self.width, txtwidth=self.txtwidth)
362 width=self.width, txtwidth=self.txtwidth)
397 fmtargs.update(self.lazy_evaluate_fields)
363 fmtargs.update(self.lazy_evaluate_fields)
398 fmtargs.update(kwargs)
364 fmtargs.update(kwargs)
399
365
400 # Prepare the prompt
366 # Prepare the prompt
401 prompt = colors.prompt + self.templates[name] + colors.normal
367 prompt = colors.prompt + self.templates[name] + colors.normal
402
368
403 # Fill in required fields
369 # Fill in required fields
404 return self._formatter.format(prompt, **fmtargs)
370 return self._formatter.format(prompt, **fmtargs)
405
371
406 def _render_rewrite(self, color=True):
372 def _render_rewrite(self, color=True):
407 """Render the ---> rewrite prompt."""
373 """Render the ---> rewrite prompt."""
408 if color:
374 if color:
409 scheme = self.color_scheme_table.active_colors
375 scheme = self.color_scheme_table.active_colors
410 # We need a non-input version of these escapes
376 # We need a non-input version of these escapes
411 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
377 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
412 color_normal = scheme.normal
378 color_normal = scheme.normal
413 else:
379 else:
414 color_prompt, color_normal = '', ''
380 color_prompt, color_normal = '', ''
415
381
416 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
382 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
417
383
418 def render(self, name, color=True, just=None, **kwargs):
384 def render(self, name, color=True, just=None, **kwargs):
419 """
385 """
420 Render the selected prompt.
386 Render the selected prompt.
421
387
422 Parameters
388 Parameters
423 ----------
389 ----------
424 name : str
390 name : str
425 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
391 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
426 color : bool
392 color : bool
427 If True (default), include ANSI escape sequences for a coloured prompt.
393 If True (default), include ANSI escape sequences for a coloured prompt.
428 just : bool
394 just : bool
429 If True, justify the prompt to the width of the last prompt. The
395 If True, justify the prompt to the width of the last prompt. The
430 default is stored in self.justify.
396 default is stored in self.justify.
431 **kwargs :
397 **kwargs :
432 Additional arguments will be passed to the string formatting operation,
398 Additional arguments will be passed to the string formatting operation,
433 so they can override the values that would otherwise fill in the
399 so they can override the values that would otherwise fill in the
434 template.
400 template.
435
401
436 Returns
402 Returns
437 -------
403 -------
438 A string containing the rendered prompt.
404 A string containing the rendered prompt.
439 """
405 """
440 res = self._render(name, color=color, **kwargs)
406 res = self._render(name, color=color, **kwargs)
441
407
442 # Handle justification of prompt
408 # Handle justification of prompt
443 invis_chars = self.invisible_chars[name] if color else 0
409 invis_chars = self.invisible_chars[name] if color else 0
444 self.txtwidth = _lenlastline(res) - invis_chars
410 self.txtwidth = _lenlastline(res) - invis_chars
445 just = self.justify if (just is None) else just
411 just = self.justify if (just is None) else just
446 # If the prompt spans more than one line, don't try to justify it:
412 # If the prompt spans more than one line, don't try to justify it:
447 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
413 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
448 res = res.rjust(self.width + invis_chars)
414 res = res.rjust(self.width + invis_chars)
449 self.width = _lenlastline(res) - invis_chars
415 self.width = _lenlastline(res) - invis_chars
450 return res
416 return res
@@ -1,315 +1,336 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Class and program to colorize python source code for ANSI terminals.
3 Class and program to colorize python source code for ANSI terminals.
4
4
5 Based on an HTML code highlighter by Jurgen Hermann found at:
5 Based on an HTML code highlighter by Jurgen Hermann found at:
6 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52298
6 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52298
7
7
8 Modifications by Fernando Perez (fperez@colorado.edu).
8 Modifications by Fernando Perez (fperez@colorado.edu).
9
9
10 Information on the original HTML highlighter follows:
10 Information on the original HTML highlighter follows:
11
11
12 MoinMoin - Python Source Parser
12 MoinMoin - Python Source Parser
13
13
14 Title: Colorize Python source using the built-in tokenizer
14 Title: Colorize Python source using the built-in tokenizer
15
15
16 Submitter: Jurgen Hermann
16 Submitter: Jurgen Hermann
17 Last Updated:2001/04/06
17 Last Updated:2001/04/06
18
18
19 Version no:1.2
19 Version no:1.2
20
20
21 Description:
21 Description:
22
22
23 This code is part of MoinMoin (http://moin.sourceforge.net/) and converts
23 This code is part of MoinMoin (http://moin.sourceforge.net/) and converts
24 Python source code to HTML markup, rendering comments, keywords,
24 Python source code to HTML markup, rendering comments, keywords,
25 operators, numeric and string literals in different colors.
25 operators, numeric and string literals in different colors.
26
26
27 It shows how to use the built-in keyword, token and tokenize modules to
27 It shows how to use the built-in keyword, token and tokenize modules to
28 scan Python source code and re-emit it with no changes to its original
28 scan Python source code and re-emit it with no changes to its original
29 formatting (which is the hard part).
29 formatting (which is the hard part).
30 """
30 """
31 from __future__ import print_function
31 from __future__ import print_function
32 from __future__ import absolute_import
32 from __future__ import absolute_import
33 from __future__ import unicode_literals
33 from __future__ import unicode_literals
34
34
35 __all__ = ['ANSICodeColors','Parser']
35 __all__ = ['ANSICodeColors','Parser']
36
36
37 _scheme_default = 'Linux'
37 _scheme_default = 'Linux'
38
38
39
39
40 # Imports
40 # Imports
41 import keyword
41 import keyword
42 import os
42 import os
43 import sys
43 import sys
44 import token
44 import token
45 import tokenize
45 import tokenize
46
46
47 try:
47 try:
48 generate_tokens = tokenize.generate_tokens
48 generate_tokens = tokenize.generate_tokens
49 except AttributeError:
49 except AttributeError:
50 # Python 3. Note that we use the undocumented _tokenize because it expects
50 # Python 3. Note that we use the undocumented _tokenize because it expects
51 # strings, not bytes. See also Python issue #9969.
51 # strings, not bytes. See also Python issue #9969.
52 generate_tokens = tokenize._tokenize
52 generate_tokens = tokenize._tokenize
53
53
54 from IPython.utils.coloransi import *
54 from IPython.utils.coloransi import *
55 from IPython.utils.py3compat import PY3
55 from IPython.utils.py3compat import PY3
56
56
57 if PY3:
57 if PY3:
58 from io import StringIO
58 from io import StringIO
59 else:
59 else:
60 from StringIO import StringIO
60 from StringIO import StringIO
61
61
62 #############################################################################
62 #############################################################################
63 ### Python Source Parser (does Hilighting)
63 ### Python Source Parser (does Hilighting)
64 #############################################################################
64 #############################################################################
65
65
66 _KEYWORD = token.NT_OFFSET + 1
66 _KEYWORD = token.NT_OFFSET + 1
67 _TEXT = token.NT_OFFSET + 2
67 _TEXT = token.NT_OFFSET + 2
68
68
69 #****************************************************************************
69 #****************************************************************************
70 # Builtin color schemes
70 # Builtin color schemes
71
71
72 Colors = TermColors # just a shorthand
72 Colors = TermColors # just a shorthand
73
73
74 # Build a few color schemes
74 # Build a few color schemes
75 NoColor = ColorScheme(
75 NoColor = ColorScheme(
76 'NoColor',{
76 'NoColor',{
77 token.NUMBER : Colors.NoColor,
77 token.NUMBER : Colors.NoColor,
78 token.OP : Colors.NoColor,
78 token.OP : Colors.NoColor,
79 token.STRING : Colors.NoColor,
79 token.STRING : Colors.NoColor,
80 tokenize.COMMENT : Colors.NoColor,
80 tokenize.COMMENT : Colors.NoColor,
81 token.NAME : Colors.NoColor,
81 token.NAME : Colors.NoColor,
82 token.ERRORTOKEN : Colors.NoColor,
82 token.ERRORTOKEN : Colors.NoColor,
83
83
84 _KEYWORD : Colors.NoColor,
84 _KEYWORD : Colors.NoColor,
85 _TEXT : Colors.NoColor,
85 _TEXT : Colors.NoColor,
86
86
87 'normal' : Colors.NoColor # color off (usu. Colors.Normal)
87 'in_prompt' : InputTermColors.NoColor, # Input prompt
88 'in_number' : InputTermColors.NoColor, # Input prompt number
89 'in_prompt2' : InputTermColors.NoColor, # Continuation prompt
90
91 'out_prompt' : InputTermColors.NoColor, # Output prompt
92 'out_number' : InputTermColors.NoColor, # Output prompt number
93
94 'normal' : InputTermColors.NoColor # color off (usu. Colors.Normal)
88 } )
95 } )
89
96
90 LinuxColors = ColorScheme(
97 LinuxColors = ColorScheme(
91 'Linux',{
98 'Linux',{
92 token.NUMBER : Colors.LightCyan,
99 token.NUMBER : Colors.LightCyan,
93 token.OP : Colors.Yellow,
100 token.OP : Colors.Yellow,
94 token.STRING : Colors.LightBlue,
101 token.STRING : Colors.LightBlue,
95 tokenize.COMMENT : Colors.LightRed,
102 tokenize.COMMENT : Colors.LightRed,
96 token.NAME : Colors.Normal,
103 token.NAME : Colors.Normal,
97 token.ERRORTOKEN : Colors.Red,
104 token.ERRORTOKEN : Colors.Red,
98
105
99 _KEYWORD : Colors.LightGreen,
106 _KEYWORD : Colors.LightGreen,
100 _TEXT : Colors.Yellow,
107 _TEXT : Colors.Yellow,
101
108
102 'normal' : Colors.Normal # color off (usu. Colors.Normal)
109 'in_prompt' : InputTermColors.Green,
110 'in_number' : InputTermColors.LightGreen,
111 'in_prompt2' : InputTermColors.Green,
112
113 'out_prompt' : InputTermColors.Red,
114 'out_number' : InputTermColors.LightRed,
115
116 'normal' : InputTermColors.Normal # color off (usu. Colors.Normal)
103 } )
117 } )
104
118
105 LightBGColors = ColorScheme(
119 LightBGColors = ColorScheme(
106 'LightBG',{
120 'LightBG',{
107 token.NUMBER : Colors.Cyan,
121 token.NUMBER : Colors.Cyan,
108 token.OP : Colors.Blue,
122 token.OP : Colors.Blue,
109 token.STRING : Colors.Blue,
123 token.STRING : Colors.Blue,
110 tokenize.COMMENT : Colors.Red,
124 tokenize.COMMENT : Colors.Red,
111 token.NAME : Colors.Normal,
125 token.NAME : Colors.Normal,
112 token.ERRORTOKEN : Colors.Red,
126 token.ERRORTOKEN : Colors.Red,
113
127
114 _KEYWORD : Colors.Green,
128 _KEYWORD : Colors.Green,
115 _TEXT : Colors.Blue,
129 _TEXT : Colors.Blue,
116
130
117 'normal' : Colors.Normal # color off (usu. Colors.Normal)
131 'in_prompt' : InputTermColors.Blue,
132 'in_number' : InputTermColors.LightBlue,
133 'in_prompt2' : InputTermColors.Blue,
134
135 'out_prompt' : InputTermColors.Red,
136 'out_number' : InputTermColors.LightRed,
137
138 'normal' : InputTermColors.Normal # color off (usu. Colors.Normal)
118 } )
139 } )
119
140
120 # Build table of color schemes (needed by the parser)
141 # Build table of color schemes (needed by the parser)
121 ANSICodeColors = ColorSchemeTable([NoColor,LinuxColors,LightBGColors],
142 ANSICodeColors = ColorSchemeTable([NoColor,LinuxColors,LightBGColors],
122 _scheme_default)
143 _scheme_default)
123
144
124 class Parser:
145 class Parser:
125 """ Format colored Python source.
146 """ Format colored Python source.
126 """
147 """
127
148
128 def __init__(self, color_table=None,out = sys.stdout):
149 def __init__(self, color_table=None,out = sys.stdout):
129 """ Create a parser with a specified color table and output channel.
150 """ Create a parser with a specified color table and output channel.
130
151
131 Call format() to process code.
152 Call format() to process code.
132 """
153 """
133 self.color_table = color_table and color_table or ANSICodeColors
154 self.color_table = color_table and color_table or ANSICodeColors
134 self.out = out
155 self.out = out
135
156
136 def format(self, raw, out = None, scheme = ''):
157 def format(self, raw, out = None, scheme = ''):
137 return self.format2(raw, out, scheme)[0]
158 return self.format2(raw, out, scheme)[0]
138
159
139 def format2(self, raw, out = None, scheme = ''):
160 def format2(self, raw, out = None, scheme = ''):
140 """ Parse and send the colored source.
161 """ Parse and send the colored source.
141
162
142 If out and scheme are not specified, the defaults (given to
163 If out and scheme are not specified, the defaults (given to
143 constructor) are used.
164 constructor) are used.
144
165
145 out should be a file-type object. Optionally, out can be given as the
166 out should be a file-type object. Optionally, out can be given as the
146 string 'str' and the parser will automatically return the output in a
167 string 'str' and the parser will automatically return the output in a
147 string."""
168 string."""
148
169
149 string_output = 0
170 string_output = 0
150 if out == 'str' or self.out == 'str' or \
171 if out == 'str' or self.out == 'str' or \
151 isinstance(self.out,StringIO):
172 isinstance(self.out,StringIO):
152 # XXX - I don't really like this state handling logic, but at this
173 # XXX - I don't really like this state handling logic, but at this
153 # point I don't want to make major changes, so adding the
174 # point I don't want to make major changes, so adding the
154 # isinstance() check is the simplest I can do to ensure correct
175 # isinstance() check is the simplest I can do to ensure correct
155 # behavior.
176 # behavior.
156 out_old = self.out
177 out_old = self.out
157 self.out = StringIO()
178 self.out = StringIO()
158 string_output = 1
179 string_output = 1
159 elif out is not None:
180 elif out is not None:
160 self.out = out
181 self.out = out
161
182
162 # Fast return of the unmodified input for NoColor scheme
183 # Fast return of the unmodified input for NoColor scheme
163 if scheme == 'NoColor':
184 if scheme == 'NoColor':
164 error = False
185 error = False
165 self.out.write(raw)
186 self.out.write(raw)
166 if string_output:
187 if string_output:
167 return raw,error
188 return raw,error
168 else:
189 else:
169 return None,error
190 return None,error
170
191
171 # local shorthands
192 # local shorthands
172 colors = self.color_table[scheme].colors
193 colors = self.color_table[scheme].colors
173 self.colors = colors # put in object so __call__ sees it
194 self.colors = colors # put in object so __call__ sees it
174
195
175 # Remove trailing whitespace and normalize tabs
196 # Remove trailing whitespace and normalize tabs
176 self.raw = raw.expandtabs().rstrip()
197 self.raw = raw.expandtabs().rstrip()
177
198
178 # store line offsets in self.lines
199 # store line offsets in self.lines
179 self.lines = [0, 0]
200 self.lines = [0, 0]
180 pos = 0
201 pos = 0
181 raw_find = self.raw.find
202 raw_find = self.raw.find
182 lines_append = self.lines.append
203 lines_append = self.lines.append
183 while 1:
204 while 1:
184 pos = raw_find('\n', pos) + 1
205 pos = raw_find('\n', pos) + 1
185 if not pos: break
206 if not pos: break
186 lines_append(pos)
207 lines_append(pos)
187 lines_append(len(self.raw))
208 lines_append(len(self.raw))
188
209
189 # parse the source and write it
210 # parse the source and write it
190 self.pos = 0
211 self.pos = 0
191 text = StringIO(self.raw)
212 text = StringIO(self.raw)
192
213
193 error = False
214 error = False
194 try:
215 try:
195 for atoken in generate_tokens(text.readline):
216 for atoken in generate_tokens(text.readline):
196 self(*atoken)
217 self(*atoken)
197 except tokenize.TokenError as ex:
218 except tokenize.TokenError as ex:
198 msg = ex.args[0]
219 msg = ex.args[0]
199 line = ex.args[1][0]
220 line = ex.args[1][0]
200 self.out.write("%s\n\n*** ERROR: %s%s%s\n" %
221 self.out.write("%s\n\n*** ERROR: %s%s%s\n" %
201 (colors[token.ERRORTOKEN],
222 (colors[token.ERRORTOKEN],
202 msg, self.raw[self.lines[line]:],
223 msg, self.raw[self.lines[line]:],
203 colors.normal)
224 colors.normal)
204 )
225 )
205 error = True
226 error = True
206 self.out.write(colors.normal+'\n')
227 self.out.write(colors.normal+'\n')
207 if string_output:
228 if string_output:
208 output = self.out.getvalue()
229 output = self.out.getvalue()
209 self.out = out_old
230 self.out = out_old
210 return (output, error)
231 return (output, error)
211 return (None, error)
232 return (None, error)
212
233
213 def __call__(self, toktype, toktext, start_pos, end_pos, line):
234 def __call__(self, toktype, toktext, start_pos, end_pos, line):
214 """ Token handler, with syntax highlighting."""
235 """ Token handler, with syntax highlighting."""
215 (srow,scol) = start_pos
236 (srow,scol) = start_pos
216 (erow,ecol) = end_pos
237 (erow,ecol) = end_pos
217 colors = self.colors
238 colors = self.colors
218 owrite = self.out.write
239 owrite = self.out.write
219
240
220 # line separator, so this works across platforms
241 # line separator, so this works across platforms
221 linesep = os.linesep
242 linesep = os.linesep
222
243
223 # calculate new positions
244 # calculate new positions
224 oldpos = self.pos
245 oldpos = self.pos
225 newpos = self.lines[srow] + scol
246 newpos = self.lines[srow] + scol
226 self.pos = newpos + len(toktext)
247 self.pos = newpos + len(toktext)
227
248
228 # send the original whitespace, if needed
249 # send the original whitespace, if needed
229 if newpos > oldpos:
250 if newpos > oldpos:
230 owrite(self.raw[oldpos:newpos])
251 owrite(self.raw[oldpos:newpos])
231
252
232 # skip indenting tokens
253 # skip indenting tokens
233 if toktype in [token.INDENT, token.DEDENT]:
254 if toktype in [token.INDENT, token.DEDENT]:
234 self.pos = newpos
255 self.pos = newpos
235 return
256 return
236
257
237 # map token type to a color group
258 # map token type to a color group
238 if token.LPAR <= toktype and toktype <= token.OP:
259 if token.LPAR <= toktype and toktype <= token.OP:
239 toktype = token.OP
260 toktype = token.OP
240 elif toktype == token.NAME and keyword.iskeyword(toktext):
261 elif toktype == token.NAME and keyword.iskeyword(toktext):
241 toktype = _KEYWORD
262 toktype = _KEYWORD
242 color = colors.get(toktype, colors[_TEXT])
263 color = colors.get(toktype, colors[_TEXT])
243
264
244 #print '<%s>' % toktext, # dbg
265 #print '<%s>' % toktext, # dbg
245
266
246 # Triple quoted strings must be handled carefully so that backtracking
267 # Triple quoted strings must be handled carefully so that backtracking
247 # in pagers works correctly. We need color terminators on _each_ line.
268 # in pagers works correctly. We need color terminators on _each_ line.
248 if linesep in toktext:
269 if linesep in toktext:
249 toktext = toktext.replace(linesep, '%s%s%s' %
270 toktext = toktext.replace(linesep, '%s%s%s' %
250 (colors.normal,linesep,color))
271 (colors.normal,linesep,color))
251
272
252 # send text
273 # send text
253 owrite('%s%s%s' % (color,toktext,colors.normal))
274 owrite('%s%s%s' % (color,toktext,colors.normal))
254
275
255 def main(argv=None):
276 def main(argv=None):
256 """Run as a command-line script: colorize a python file or stdin using ANSI
277 """Run as a command-line script: colorize a python file or stdin using ANSI
257 color escapes and print to stdout.
278 color escapes and print to stdout.
258
279
259 Inputs:
280 Inputs:
260
281
261 - argv(None): a list of strings like sys.argv[1:] giving the command-line
282 - argv(None): a list of strings like sys.argv[1:] giving the command-line
262 arguments. If None, use sys.argv[1:].
283 arguments. If None, use sys.argv[1:].
263 """
284 """
264
285
265 usage_msg = """%prog [options] [filename]
286 usage_msg = """%prog [options] [filename]
266
287
267 Colorize a python file or stdin using ANSI color escapes and print to stdout.
288 Colorize a python file or stdin using ANSI color escapes and print to stdout.
268 If no filename is given, or if filename is -, read standard input."""
289 If no filename is given, or if filename is -, read standard input."""
269
290
270 import optparse
291 import optparse
271 parser = optparse.OptionParser(usage=usage_msg)
292 parser = optparse.OptionParser(usage=usage_msg)
272 newopt = parser.add_option
293 newopt = parser.add_option
273 newopt('-s','--scheme',metavar='NAME',dest='scheme_name',action='store',
294 newopt('-s','--scheme',metavar='NAME',dest='scheme_name',action='store',
274 choices=['Linux','LightBG','NoColor'],default=_scheme_default,
295 choices=['Linux','LightBG','NoColor'],default=_scheme_default,
275 help="give the color scheme to use. Currently only 'Linux'\
296 help="give the color scheme to use. Currently only 'Linux'\
276 (default) and 'LightBG' and 'NoColor' are implemented (give without\
297 (default) and 'LightBG' and 'NoColor' are implemented (give without\
277 quotes)")
298 quotes)")
278
299
279 opts,args = parser.parse_args(argv)
300 opts,args = parser.parse_args(argv)
280
301
281 if len(args) > 1:
302 if len(args) > 1:
282 parser.error("you must give at most one filename.")
303 parser.error("you must give at most one filename.")
283
304
284 if len(args) == 0:
305 if len(args) == 0:
285 fname = '-' # no filename given; setup to read from stdin
306 fname = '-' # no filename given; setup to read from stdin
286 else:
307 else:
287 fname = args[0]
308 fname = args[0]
288
309
289 if fname == '-':
310 if fname == '-':
290 stream = sys.stdin
311 stream = sys.stdin
291 else:
312 else:
292 try:
313 try:
293 stream = open(fname)
314 stream = open(fname)
294 except IOError as msg:
315 except IOError as msg:
295 print(msg, file=sys.stderr)
316 print(msg, file=sys.stderr)
296 sys.exit(1)
317 sys.exit(1)
297
318
298 parser = Parser()
319 parser = Parser()
299
320
300 # we need nested try blocks because pre-2.5 python doesn't support unified
321 # we need nested try blocks because pre-2.5 python doesn't support unified
301 # try-except-finally
322 # try-except-finally
302 try:
323 try:
303 try:
324 try:
304 # write colorized version to stdout
325 # write colorized version to stdout
305 parser.format(stream.read(),scheme=opts.scheme_name)
326 parser.format(stream.read(),scheme=opts.scheme_name)
306 except IOError as msg:
327 except IOError as msg:
307 # if user reads through a pager and quits, don't print traceback
328 # if user reads through a pager and quits, don't print traceback
308 if msg.args != (32,'Broken pipe'):
329 if msg.args != (32,'Broken pipe'):
309 raise
330 raise
310 finally:
331 finally:
311 if stream is not sys.stdin:
332 if stream is not sys.stdin:
312 stream.close() # in case a non-handled exception happened above
333 stream.close() # in case a non-handled exception happened above
313
334
314 if __name__ == "__main__":
335 if __name__ == "__main__":
315 main()
336 main()
@@ -1,187 +1,187 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tools for coloring text in ANSI terminals.
2 """Tools for coloring text in ANSI terminals.
3 """
3 """
4
4
5 #*****************************************************************************
5 #*****************************************************************************
6 # Copyright (C) 2002-2006 Fernando Perez. <fperez@colorado.edu>
6 # Copyright (C) 2002-2006 Fernando Perez. <fperez@colorado.edu>
7 #
7 #
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
9 # the file COPYING, distributed as part of this software.
10 #*****************************************************************************
10 #*****************************************************************************
11
11
12 __all__ = ['TermColors','InputTermColors','ColorScheme','ColorSchemeTable']
12 __all__ = ['TermColors','InputTermColors','ColorScheme','ColorSchemeTable']
13
13
14 import os
14 import os
15
15
16 from IPython.utils.ipstruct import Struct
16 from IPython.utils.ipstruct import Struct
17
17
18 color_templates = (
18 color_templates = (
19 # Dark colors
19 # Dark colors
20 ("Black" , "0;30"),
20 ("Black" , "0;30"),
21 ("Red" , "0;31"),
21 ("Red" , "0;31"),
22 ("Green" , "0;32"),
22 ("Green" , "0;32"),
23 ("Brown" , "0;33"),
23 ("Brown" , "0;33"),
24 ("Blue" , "0;34"),
24 ("Blue" , "0;34"),
25 ("Purple" , "0;35"),
25 ("Purple" , "0;35"),
26 ("Cyan" , "0;36"),
26 ("Cyan" , "0;36"),
27 ("LightGray" , "0;37"),
27 ("LightGray" , "0;37"),
28 # Light colors
28 # Light colors
29 ("DarkGray" , "1;30"),
29 ("DarkGray" , "1;30"),
30 ("LightRed" , "1;31"),
30 ("LightRed" , "1;31"),
31 ("LightGreen" , "1;32"),
31 ("LightGreen" , "1;32"),
32 ("Yellow" , "1;33"),
32 ("Yellow" , "1;33"),
33 ("LightBlue" , "1;34"),
33 ("LightBlue" , "1;34"),
34 ("LightPurple" , "1;35"),
34 ("LightPurple" , "1;35"),
35 ("LightCyan" , "1;36"),
35 ("LightCyan" , "1;36"),
36 ("White" , "1;37"),
36 ("White" , "1;37"),
37 # Blinking colors. Probably should not be used in anything serious.
37 # Blinking colors. Probably should not be used in anything serious.
38 ("BlinkBlack" , "5;30"),
38 ("BlinkBlack" , "5;30"),
39 ("BlinkRed" , "5;31"),
39 ("BlinkRed" , "5;31"),
40 ("BlinkGreen" , "5;32"),
40 ("BlinkGreen" , "5;32"),
41 ("BlinkYellow" , "5;33"),
41 ("BlinkYellow" , "5;33"),
42 ("BlinkBlue" , "5;34"),
42 ("BlinkBlue" , "5;34"),
43 ("BlinkPurple" , "5;35"),
43 ("BlinkPurple" , "5;35"),
44 ("BlinkCyan" , "5;36"),
44 ("BlinkCyan" , "5;36"),
45 ("BlinkLightGray", "5;37"),
45 ("BlinkLightGray", "5;37"),
46 )
46 )
47
47
48 def make_color_table(in_class):
48 def make_color_table(in_class):
49 """Build a set of color attributes in a class.
49 """Build a set of color attributes in a class.
50
50
51 Helper function for building the :class:`TermColors` and
51 Helper function for building the :class:`TermColors` and
52 :class`InputTermColors`.
52 :class`InputTermColors`.
53 """
53 """
54 for name,value in color_templates:
54 for name,value in color_templates:
55 setattr(in_class,name,in_class._base % value)
55 setattr(in_class,name,in_class._base % value)
56
56
57 class TermColors:
57 class TermColors:
58 """Color escape sequences.
58 """Color escape sequences.
59
59
60 This class defines the escape sequences for all the standard (ANSI?)
60 This class defines the escape sequences for all the standard (ANSI?)
61 colors in terminals. Also defines a NoColor escape which is just the null
61 colors in terminals. Also defines a NoColor escape which is just the null
62 string, suitable for defining 'dummy' color schemes in terminals which get
62 string, suitable for defining 'dummy' color schemes in terminals which get
63 confused by color escapes.
63 confused by color escapes.
64
64
65 This class should be used as a mixin for building color schemes."""
65 This class should be used as a mixin for building color schemes."""
66
66
67 NoColor = '' # for color schemes in color-less terminals.
67 NoColor = '' # for color schemes in color-less terminals.
68 Normal = '\033[0m' # Reset normal coloring
68 Normal = '\033[0m' # Reset normal coloring
69 _base = '\033[%sm' # Template for all other colors
69 _base = '\033[%sm' # Template for all other colors
70
70
71 # Build the actual color table as a set of class attributes:
71 # Build the actual color table as a set of class attributes:
72 make_color_table(TermColors)
72 make_color_table(TermColors)
73
73
74 class InputTermColors:
74 class InputTermColors:
75 """Color escape sequences for input prompts.
75 """Color escape sequences for input prompts.
76
76
77 This class is similar to TermColors, but the escapes are wrapped in \001
77 This class is similar to TermColors, but the escapes are wrapped in \001
78 and \002 so that readline can properly know the length of each line and
78 and \002 so that readline can properly know the length of each line and
79 can wrap lines accordingly. Use this class for any colored text which
79 can wrap lines accordingly. Use this class for any colored text which
80 needs to be used in input prompts, such as in calls to raw_input().
80 needs to be used in input prompts, such as in calls to raw_input().
81
81
82 This class defines the escape sequences for all the standard (ANSI?)
82 This class defines the escape sequences for all the standard (ANSI?)
83 colors in terminals. Also defines a NoColor escape which is just the null
83 colors in terminals. Also defines a NoColor escape which is just the null
84 string, suitable for defining 'dummy' color schemes in terminals which get
84 string, suitable for defining 'dummy' color schemes in terminals which get
85 confused by color escapes.
85 confused by color escapes.
86
86
87 This class should be used as a mixin for building color schemes."""
87 This class should be used as a mixin for building color schemes."""
88
88
89 NoColor = '' # for color schemes in color-less terminals.
89 NoColor = '' # for color schemes in color-less terminals.
90
90
91 if os.name == 'nt' and os.environ.get('TERM','dumb') == 'emacs':
91 if os.name == 'nt' and os.environ.get('TERM','dumb') == 'emacs':
92 # (X)emacs on W32 gets confused with \001 and \002 so we remove them
92 # (X)emacs on W32 gets confused with \001 and \002 so we remove them
93 Normal = '\033[0m' # Reset normal coloring
93 Normal = '\033[0m' # Reset normal coloring
94 _base = '\033[%sm' # Template for all other colors
94 _base = '\033[%sm' # Template for all other colors
95 else:
95 else:
96 Normal = '\001\033[0m\002' # Reset normal coloring
96 Normal = '\001\033[0m\002' # Reset normal coloring
97 _base = '\001\033[%sm\002' # Template for all other colors
97 _base = '\001\033[%sm\002' # Template for all other colors
98
98
99 # Build the actual color table as a set of class attributes:
99 # Build the actual color table as a set of class attributes:
100 make_color_table(InputTermColors)
100 make_color_table(InputTermColors)
101
101
102 class NoColors:
102 class NoColors:
103 """This defines all the same names as the colour classes, but maps them to
103 """This defines all the same names as the colour classes, but maps them to
104 empty strings, so it can easily be substituted to turn off colours."""
104 empty strings, so it can easily be substituted to turn off colours."""
105 NoColor = ''
105 NoColor = ''
106 Normal = ''
106 Normal = ''
107
107
108 for name, value in color_templates:
108 for name, value in color_templates:
109 setattr(NoColors, name, '')
109 setattr(NoColors, name, '')
110
110
111 class ColorScheme:
111 class ColorScheme:
112 """Generic color scheme class. Just a name and a Struct."""
112 """Generic color scheme class. Just a name and a Struct."""
113 def __init__(self,__scheme_name_,colordict=None,**colormap):
113 def __init__(self,__scheme_name_,colordict=None,**colormap):
114 self.name = __scheme_name_
114 self.name = __scheme_name_
115 if colordict is None:
115 if colordict is None:
116 self.colors = Struct(**colormap)
116 self.colors = Struct(**colormap)
117 else:
117 else:
118 self.colors = Struct(colordict)
118 self.colors = Struct(colordict)
119
119
120 def copy(self,name=None):
120 def copy(self,name=None):
121 """Return a full copy of the object, optionally renaming it."""
121 """Return a full copy of the object, optionally renaming it."""
122 if name is None:
122 if name is None:
123 name = self.name
123 name = self.name
124 return ColorScheme(name, self.colors.dict())
124 return ColorScheme(name, self.colors.dict())
125
125
126 class ColorSchemeTable(dict):
126 class ColorSchemeTable(dict):
127 """General class to handle tables of color schemes.
127 """General class to handle tables of color schemes.
128
128
129 It's basically a dict of color schemes with a couple of shorthand
129 It's basically a dict of color schemes with a couple of shorthand
130 attributes and some convenient methods.
130 attributes and some convenient methods.
131
131
132 active_scheme_name -> obvious
132 active_scheme_name -> obvious
133 active_colors -> actual color table of the active scheme"""
133 active_colors -> actual color table of the active scheme"""
134
134
135 def __init__(self,scheme_list=None,default_scheme=''):
135 def __init__(self, scheme_list=None, default_scheme=''):
136 """Create a table of color schemes.
136 """Create a table of color schemes.
137
137
138 The table can be created empty and manually filled or it can be
138 The table can be created empty and manually filled or it can be
139 created with a list of valid color schemes AND the specification for
139 created with a list of valid color schemes AND the specification for
140 the default active scheme.
140 the default active scheme.
141 """
141 """
142
142
143 # create object attributes to be set later
143 # create object attributes to be set later
144 self.active_scheme_name = ''
144 self.active_scheme_name = ''
145 self.active_colors = None
145 self.active_colors = None
146
146
147 if scheme_list:
147 if scheme_list:
148 if default_scheme == '':
148 if default_scheme == '':
149 raise ValueError('you must specify the default color scheme')
149 raise ValueError('you must specify the default color scheme')
150 for scheme in scheme_list:
150 for scheme in scheme_list:
151 self.add_scheme(scheme)
151 self.add_scheme(scheme)
152 self.set_active_scheme(default_scheme)
152 self.set_active_scheme(default_scheme)
153
153
154 def copy(self):
154 def copy(self):
155 """Return full copy of object"""
155 """Return full copy of object"""
156 return ColorSchemeTable(self.values(),self.active_scheme_name)
156 return ColorSchemeTable(self.values(),self.active_scheme_name)
157
157
158 def add_scheme(self,new_scheme):
158 def add_scheme(self,new_scheme):
159 """Add a new color scheme to the table."""
159 """Add a new color scheme to the table."""
160 if not isinstance(new_scheme,ColorScheme):
160 if not isinstance(new_scheme,ColorScheme):
161 raise ValueError('ColorSchemeTable only accepts ColorScheme instances')
161 raise ValueError('ColorSchemeTable only accepts ColorScheme instances')
162 self[new_scheme.name] = new_scheme
162 self[new_scheme.name] = new_scheme
163
163
164 def set_active_scheme(self,scheme,case_sensitive=0):
164 def set_active_scheme(self,scheme,case_sensitive=0):
165 """Set the currently active scheme.
165 """Set the currently active scheme.
166
166
167 Names are by default compared in a case-insensitive way, but this can
167 Names are by default compared in a case-insensitive way, but this can
168 be changed by setting the parameter case_sensitive to true."""
168 be changed by setting the parameter case_sensitive to true."""
169
169
170 scheme_names = list(self.keys())
170 scheme_names = list(self.keys())
171 if case_sensitive:
171 if case_sensitive:
172 valid_schemes = scheme_names
172 valid_schemes = scheme_names
173 scheme_test = scheme
173 scheme_test = scheme
174 else:
174 else:
175 valid_schemes = [s.lower() for s in scheme_names]
175 valid_schemes = [s.lower() for s in scheme_names]
176 scheme_test = scheme.lower()
176 scheme_test = scheme.lower()
177 try:
177 try:
178 scheme_idx = valid_schemes.index(scheme_test)
178 scheme_idx = valid_schemes.index(scheme_test)
179 except ValueError:
179 except ValueError:
180 raise ValueError('Unrecognized color scheme: ' + scheme + \
180 raise ValueError('Unrecognized color scheme: ' + scheme + \
181 '\nValid schemes: '+str(scheme_names).replace("'', ",''))
181 '\nValid schemes: '+str(scheme_names).replace("'', ",''))
182 else:
182 else:
183 active = scheme_names[scheme_idx]
183 active = scheme_names[scheme_idx]
184 self.active_scheme_name = active
184 self.active_scheme_name = active
185 self.active_colors = self[active].colors
185 self.active_colors = self[active].colors
186 # Now allow using '' as an index for the current active scheme
186 # Now allow using '' as an index for the current active scheme
187 self[''] = self[active]
187 self[''] = self[active]
General Comments 0
You need to be logged in to leave comments. Login now