##// END OF EJS Templates
Add \S prompt char for whitespace as wide as the count (similar to \D)
Wouter Bolsterlee -
Show More
@@ -1,449 +1,450 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 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Color schemes for prompts
37 # Color schemes for prompts
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 InputColors = coloransi.InputTermColors # just a shorthand
40 InputColors = coloransi.InputTermColors # just a shorthand
41 Colors = coloransi.TermColors # just a shorthand
41 Colors = coloransi.TermColors # just a shorthand
42
42
43 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
43 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
44
44
45 PColNoColors = coloransi.ColorScheme(
45 PColNoColors = coloransi.ColorScheme(
46 'NoColor',
46 'NoColor',
47 in_prompt = InputColors.NoColor, # Input prompt
47 in_prompt = InputColors.NoColor, # Input prompt
48 in_number = InputColors.NoColor, # Input prompt number
48 in_number = InputColors.NoColor, # Input prompt number
49 in_prompt2 = InputColors.NoColor, # Continuation prompt
49 in_prompt2 = InputColors.NoColor, # Continuation prompt
50 in_normal = InputColors.NoColor, # color off (usu. Colors.Normal)
50 in_normal = InputColors.NoColor, # color off (usu. Colors.Normal)
51
51
52 out_prompt = Colors.NoColor, # Output prompt
52 out_prompt = Colors.NoColor, # Output prompt
53 out_number = Colors.NoColor, # Output prompt number
53 out_number = Colors.NoColor, # Output prompt number
54
54
55 normal = Colors.NoColor # color off (usu. Colors.Normal)
55 normal = Colors.NoColor # color off (usu. Colors.Normal)
56 )
56 )
57
57
58 # make some schemes as instances so we can copy them for modification easily:
58 # make some schemes as instances so we can copy them for modification easily:
59 PColLinux = coloransi.ColorScheme(
59 PColLinux = coloransi.ColorScheme(
60 'Linux',
60 'Linux',
61 in_prompt = InputColors.Green,
61 in_prompt = InputColors.Green,
62 in_number = InputColors.LightGreen,
62 in_number = InputColors.LightGreen,
63 in_prompt2 = InputColors.Green,
63 in_prompt2 = InputColors.Green,
64 in_normal = InputColors.Normal, # color off (usu. Colors.Normal)
64 in_normal = InputColors.Normal, # color off (usu. Colors.Normal)
65
65
66 out_prompt = Colors.Red,
66 out_prompt = Colors.Red,
67 out_number = Colors.LightRed,
67 out_number = Colors.LightRed,
68
68
69 normal = Colors.Normal
69 normal = Colors.Normal
70 )
70 )
71
71
72 # Slightly modified Linux for light backgrounds
72 # Slightly modified Linux for light backgrounds
73 PColLightBG = PColLinux.copy('LightBG')
73 PColLightBG = PColLinux.copy('LightBG')
74
74
75 PColLightBG.colors.update(
75 PColLightBG.colors.update(
76 in_prompt = InputColors.Blue,
76 in_prompt = InputColors.Blue,
77 in_number = InputColors.LightBlue,
77 in_number = InputColors.LightBlue,
78 in_prompt2 = InputColors.Blue
78 in_prompt2 = InputColors.Blue
79 )
79 )
80
80
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82 # Utilities
82 # Utilities
83 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
84
84
85 class LazyEvaluate(object):
85 class LazyEvaluate(object):
86 """This is used for formatting strings with values that need to be updated
86 """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."""
87 at that time, such as the current time or working directory."""
88 def __init__(self, func, *args, **kwargs):
88 def __init__(self, func, *args, **kwargs):
89 self.func = func
89 self.func = func
90 self.args = args
90 self.args = args
91 self.kwargs = kwargs
91 self.kwargs = kwargs
92
92
93 def __call__(self, **kwargs):
93 def __call__(self, **kwargs):
94 self.kwargs.update(kwargs)
94 self.kwargs.update(kwargs)
95 return self.func(*self.args, **self.kwargs)
95 return self.func(*self.args, **self.kwargs)
96
96
97 def __str__(self):
97 def __str__(self):
98 return str(self())
98 return str(self())
99
99
100 def __unicode__(self):
100 def __unicode__(self):
101 return py3compat.unicode_type(self())
101 return py3compat.unicode_type(self())
102
102
103 def __format__(self, format_spec):
103 def __format__(self, format_spec):
104 return format(self(), format_spec)
104 return format(self(), format_spec)
105
105
106 def multiple_replace(dict, text):
106 def multiple_replace(dict, text):
107 """ Replace in 'text' all occurences of any key in the given
107 """ Replace in 'text' all occurences of any key in the given
108 dictionary by its corresponding value. Returns the new string."""
108 dictionary by its corresponding value. Returns the new string."""
109
109
110 # Function by Xavier Defrang, originally found at:
110 # Function by Xavier Defrang, originally found at:
111 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
111 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
112
112
113 # Create a regular expression from the dictionary keys
113 # Create a regular expression from the dictionary keys
114 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
114 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
115 # For each match, look-up corresponding value in dictionary
115 # For each match, look-up corresponding value in dictionary
116 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
116 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
117
117
118 #-----------------------------------------------------------------------------
118 #-----------------------------------------------------------------------------
119 # Special characters that can be used in prompt templates, mainly bash-like
119 # Special characters that can be used in prompt templates, mainly bash-like
120 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
121
121
122 # If $HOME isn't defined (Windows), make it an absurd string so that it can
122 # 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
123 # never be expanded out into '~'. Basically anything which can never be a
124 # reasonable directory name will do, we just want the $HOME -> '~' operation
124 # 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
125 # to become a no-op. We pre-compute $HOME here so it's not done on every
126 # prompt call.
126 # prompt call.
127
127
128 # FIXME:
128 # FIXME:
129
129
130 # - This should be turned into a class which does proper namespace management,
130 # - 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.
131 # 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
132 # Currently it's just globals, which need to be managed manually by code
133 # below.
133 # below.
134
134
135 # - I also need to split up the color schemes from the prompt specials
135 # - 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.
136 # somehow. I don't have a clean design for that quite yet.
137
137
138 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
138 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
139
139
140 # This is needed on FreeBSD, and maybe other systems which symlink /home to
140 # 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
141 # /usr/home, but retain the $HOME variable as pointing to /home
142 HOME = os.path.realpath(HOME)
142 HOME = os.path.realpath(HOME)
143
143
144 # We precompute a few more strings here for the prompt_specials, which are
144 # 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
145 # fixed once ipython starts. This reduces the runtime overhead of computing
146 # prompt strings.
146 # prompt strings.
147 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
147 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
148 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
148 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
149 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
149 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
150
150
151 # IronPython doesn't currently have os.getuid() even if
151 # IronPython doesn't currently have os.getuid() even if
152 # os.name == 'posix'; 2/8/2014
152 # os.name == 'posix'; 2/8/2014
153 ROOT_SYMBOL = "#" if (os.name=='nt' or sys.platform=='cli' or os.getuid()==0) else "$"
153 ROOT_SYMBOL = "#" if (os.name=='nt' or sys.platform=='cli' or os.getuid()==0) else "$"
154
154
155 prompt_abbreviations = {
155 prompt_abbreviations = {
156 # Prompt/history count
156 # Prompt/history count
157 '%n' : '{color.number}' '{count}' '{color.prompt}',
157 '%n' : '{color.number}' '{count}' '{color.prompt}',
158 r'\#': '{color.number}' '{count}' '{color.prompt}',
158 r'\#': '{color.number}' '{count}' '{color.prompt}',
159 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
159 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
160 # can get numbers displayed in whatever color they want.
160 # can get numbers displayed in whatever color they want.
161 r'\N': '{count}',
161 r'\N': '{count}',
162
162
163 # Prompt/history count, with the actual digits replaced by dots. Used
163 # Prompt/history count, with the actual digits replaced by dots or
164 # mainly in continuation prompts (prompt_in2)
164 # spaces. Used mainly in continuation prompts (prompt_in2).
165 r'\D': '{dots}',
165 r'\D': '{dots}',
166 r'\S': '{spaces}',
166
167
167 # Current time
168 # Current time
168 r'\T' : '{time}',
169 r'\T' : '{time}',
169 # Current working directory
170 # Current working directory
170 r'\w': '{cwd}',
171 r'\w': '{cwd}',
171 # Basename of current working directory.
172 # Basename of current working directory.
172 # (use os.sep to make this portable across OSes)
173 # (use os.sep to make this portable across OSes)
173 r'\W' : '{cwd_last}',
174 r'\W' : '{cwd_last}',
174 # These X<N> are an extension to the normal bash prompts. They return
175 # These X<N> are an extension to the normal bash prompts. They return
175 # N terms of the path, after replacing $HOME with '~'
176 # N terms of the path, after replacing $HOME with '~'
176 r'\X0': '{cwd_x[0]}',
177 r'\X0': '{cwd_x[0]}',
177 r'\X1': '{cwd_x[1]}',
178 r'\X1': '{cwd_x[1]}',
178 r'\X2': '{cwd_x[2]}',
179 r'\X2': '{cwd_x[2]}',
179 r'\X3': '{cwd_x[3]}',
180 r'\X3': '{cwd_x[3]}',
180 r'\X4': '{cwd_x[4]}',
181 r'\X4': '{cwd_x[4]}',
181 r'\X5': '{cwd_x[5]}',
182 r'\X5': '{cwd_x[5]}',
182 # Y<N> are similar to X<N>, but they show '~' if it's the directory
183 # Y<N> are similar to X<N>, but they show '~' if it's the directory
183 # N+1 in the list. Somewhat like %cN in tcsh.
184 # N+1 in the list. Somewhat like %cN in tcsh.
184 r'\Y0': '{cwd_y[0]}',
185 r'\Y0': '{cwd_y[0]}',
185 r'\Y1': '{cwd_y[1]}',
186 r'\Y1': '{cwd_y[1]}',
186 r'\Y2': '{cwd_y[2]}',
187 r'\Y2': '{cwd_y[2]}',
187 r'\Y3': '{cwd_y[3]}',
188 r'\Y3': '{cwd_y[3]}',
188 r'\Y4': '{cwd_y[4]}',
189 r'\Y4': '{cwd_y[4]}',
189 r'\Y5': '{cwd_y[5]}',
190 r'\Y5': '{cwd_y[5]}',
190 # Hostname up to first .
191 # Hostname up to first .
191 r'\h': HOSTNAME_SHORT,
192 r'\h': HOSTNAME_SHORT,
192 # Full hostname
193 # Full hostname
193 r'\H': HOSTNAME,
194 r'\H': HOSTNAME,
194 # Username of current user
195 # Username of current user
195 r'\u': USER,
196 r'\u': USER,
196 # Escaped '\'
197 # Escaped '\'
197 '\\\\': '\\',
198 '\\\\': '\\',
198 # Newline
199 # Newline
199 r'\n': '\n',
200 r'\n': '\n',
200 # Carriage return
201 # Carriage return
201 r'\r': '\r',
202 r'\r': '\r',
202 # Release version
203 # Release version
203 r'\v': release.version,
204 r'\v': release.version,
204 # Root symbol ($ or #)
205 # Root symbol ($ or #)
205 r'\$': ROOT_SYMBOL,
206 r'\$': ROOT_SYMBOL,
206 }
207 }
207
208
208 #-----------------------------------------------------------------------------
209 #-----------------------------------------------------------------------------
209 # More utilities
210 # More utilities
210 #-----------------------------------------------------------------------------
211 #-----------------------------------------------------------------------------
211
212
212 def cwd_filt(depth):
213 def cwd_filt(depth):
213 """Return the last depth elements of the current working directory.
214 """Return the last depth elements of the current working directory.
214
215
215 $HOME is always replaced with '~'.
216 $HOME is always replaced with '~'.
216 If depth==0, the full path is returned."""
217 If depth==0, the full path is returned."""
217
218
218 cwd = py3compat.getcwd().replace(HOME,"~")
219 cwd = py3compat.getcwd().replace(HOME,"~")
219 out = os.sep.join(cwd.split(os.sep)[-depth:])
220 out = os.sep.join(cwd.split(os.sep)[-depth:])
220 return out or os.sep
221 return out or os.sep
221
222
222 def cwd_filt2(depth):
223 def cwd_filt2(depth):
223 """Return the last depth elements of the current working directory.
224 """Return the last depth elements of the current working directory.
224
225
225 $HOME is always replaced with '~'.
226 $HOME is always replaced with '~'.
226 If depth==0, the full path is returned."""
227 If depth==0, the full path is returned."""
227
228
228 full_cwd = py3compat.getcwd()
229 full_cwd = py3compat.getcwd()
229 cwd = full_cwd.replace(HOME,"~").split(os.sep)
230 cwd = full_cwd.replace(HOME,"~").split(os.sep)
230 if '~' in cwd and len(cwd) == depth+1:
231 if '~' in cwd and len(cwd) == depth+1:
231 depth += 1
232 depth += 1
232 drivepart = ''
233 drivepart = ''
233 if sys.platform == 'win32' and len(cwd) > depth:
234 if sys.platform == 'win32' and len(cwd) > depth:
234 drivepart = os.path.splitdrive(full_cwd)[0]
235 drivepart = os.path.splitdrive(full_cwd)[0]
235 out = drivepart + '/'.join(cwd[-depth:])
236 out = drivepart + '/'.join(cwd[-depth:])
236
237
237 return out or os.sep
238 return out or os.sep
238
239
239 #-----------------------------------------------------------------------------
240 #-----------------------------------------------------------------------------
240 # Prompt classes
241 # Prompt classes
241 #-----------------------------------------------------------------------------
242 #-----------------------------------------------------------------------------
242
243
243 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
244 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
244 'cwd': LazyEvaluate(py3compat.getcwd),
245 'cwd': LazyEvaluate(py3compat.getcwd),
245 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
246 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
246 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
247 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
247 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
248 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
248 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
249 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
249 }
250 }
250
251
251 def _lenlastline(s):
252 def _lenlastline(s):
252 """Get the length of the last line. More intelligent than
253 """Get the length of the last line. More intelligent than
253 len(s.splitlines()[-1]).
254 len(s.splitlines()[-1]).
254 """
255 """
255 if not s or s.endswith(('\n', '\r')):
256 if not s or s.endswith(('\n', '\r')):
256 return 0
257 return 0
257 return len(s.splitlines()[-1])
258 return len(s.splitlines()[-1])
258
259
259
260
260 invisible_chars_re = re.compile('\001[^\001\002]*\002')
261 invisible_chars_re = re.compile('\001[^\001\002]*\002')
261 def _invisible_characters(s):
262 def _invisible_characters(s):
262 """
263 """
263 Get the number of invisible ANSI characters in s. Invisible characters
264 Get the number of invisible ANSI characters in s. Invisible characters
264 must be delimited by \001 and \002.
265 must be delimited by \001 and \002.
265 """
266 """
266 return _lenlastline(s) - _lenlastline(invisible_chars_re.sub('', s))
267 return _lenlastline(s) - _lenlastline(invisible_chars_re.sub('', s))
267
268
268 class UserNSFormatter(Formatter):
269 class UserNSFormatter(Formatter):
269 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
270 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
270 def __init__(self, shell):
271 def __init__(self, shell):
271 self.shell = shell
272 self.shell = shell
272
273
273 def get_value(self, key, args, kwargs):
274 def get_value(self, key, args, kwargs):
274 # try regular formatting first:
275 # try regular formatting first:
275 try:
276 try:
276 return Formatter.get_value(self, key, args, kwargs)
277 return Formatter.get_value(self, key, args, kwargs)
277 except Exception:
278 except Exception:
278 pass
279 pass
279 # next, look in user_ns and builtins:
280 # next, look in user_ns and builtins:
280 for container in (self.shell.user_ns, __builtins__):
281 for container in (self.shell.user_ns, __builtins__):
281 if key in container:
282 if key in container:
282 return container[key]
283 return container[key]
283 # nothing found, put error message in its place
284 # nothing found, put error message in its place
284 return "<ERROR: '%s' not found>" % key
285 return "<ERROR: '%s' not found>" % key
285
286
286
287
287 class PromptManager(Configurable):
288 class PromptManager(Configurable):
288 """This is the primary interface for producing IPython's prompts."""
289 """This is the primary interface for producing IPython's prompts."""
289 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
290 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
290
291
291 color_scheme_table = Instance(coloransi.ColorSchemeTable, allow_none=True)
292 color_scheme_table = Instance(coloransi.ColorSchemeTable, allow_none=True)
292 color_scheme = Unicode('Linux', config=True)
293 color_scheme = Unicode('Linux', config=True)
293 def _color_scheme_changed(self, name, new_value):
294 def _color_scheme_changed(self, name, new_value):
294 self.color_scheme_table.set_active_scheme(new_value)
295 self.color_scheme_table.set_active_scheme(new_value)
295 for pname in ['in', 'in2', 'out', 'rewrite']:
296 for pname in ['in', 'in2', 'out', 'rewrite']:
296 # We need to recalculate the number of invisible characters
297 # We need to recalculate the number of invisible characters
297 self.update_prompt(pname)
298 self.update_prompt(pname)
298
299
299 lazy_evaluate_fields = Dict(help="""
300 lazy_evaluate_fields = Dict(help="""
300 This maps field names used in the prompt templates to functions which
301 This maps field names used in the prompt templates to functions which
301 will be called when the prompt is rendered. This allows us to include
302 will be called when the prompt is rendered. This allows us to include
302 things like the current time in the prompts. Functions are only called
303 things like the current time in the prompts. Functions are only called
303 if they are used in the prompt.
304 if they are used in the prompt.
304 """)
305 """)
305 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
306 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
306
307
307 in_template = Unicode('In [\\#]: ', config=True,
308 in_template = Unicode('In [\\#]: ', config=True,
308 help="Input prompt. '\\#' will be transformed to the prompt number")
309 help="Input prompt. '\\#' will be transformed to the prompt number")
309 in2_template = Unicode(' .\\D.: ', config=True,
310 in2_template = Unicode(' .\\D.: ', config=True,
310 help="Continuation prompt.")
311 help="Continuation prompt.")
311 out_template = Unicode('Out[\\#]: ', config=True,
312 out_template = Unicode('Out[\\#]: ', config=True,
312 help="Output prompt. '\\#' will be transformed to the prompt number")
313 help="Output prompt. '\\#' will be transformed to the prompt number")
313
314
314 justify = Bool(True, config=True, help="""
315 justify = Bool(True, config=True, help="""
315 If True (default), each prompt will be right-aligned with the
316 If True (default), each prompt will be right-aligned with the
316 preceding one.
317 preceding one.
317 """)
318 """)
318
319
319 # We actually store the expanded templates here:
320 # We actually store the expanded templates here:
320 templates = Dict()
321 templates = Dict()
321
322
322 # The number of characters in the last prompt rendered, not including
323 # The number of characters in the last prompt rendered, not including
323 # colour characters.
324 # colour characters.
324 width = Int()
325 width = Int()
325 txtwidth = Int() # Not including right-justification
326 txtwidth = Int() # Not including right-justification
326
327
327 # The number of characters in each prompt which don't contribute to width
328 # The number of characters in each prompt which don't contribute to width
328 invisible_chars = Dict()
329 invisible_chars = Dict()
329 def _invisible_chars_default(self):
330 def _invisible_chars_default(self):
330 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
331 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
331
332
332 def __init__(self, shell, **kwargs):
333 def __init__(self, shell, **kwargs):
333 super(PromptManager, self).__init__(shell=shell, **kwargs)
334 super(PromptManager, self).__init__(shell=shell, **kwargs)
334
335
335 # Prepare colour scheme table
336 # Prepare colour scheme table
336 self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors,
337 self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors,
337 PColLinux, PColLightBG], self.color_scheme)
338 PColLinux, PColLightBG], self.color_scheme)
338
339
339 self._formatter = UserNSFormatter(shell)
340 self._formatter = UserNSFormatter(shell)
340 # Prepare templates & numbers of invisible characters
341 # Prepare templates & numbers of invisible characters
341 self.update_prompt('in', self.in_template)
342 self.update_prompt('in', self.in_template)
342 self.update_prompt('in2', self.in2_template)
343 self.update_prompt('in2', self.in2_template)
343 self.update_prompt('out', self.out_template)
344 self.update_prompt('out', self.out_template)
344 self.update_prompt('rewrite')
345 self.update_prompt('rewrite')
345 self.on_trait_change(self._update_prompt_trait, ['in_template',
346 self.on_trait_change(self._update_prompt_trait, ['in_template',
346 'in2_template', 'out_template'])
347 'in2_template', 'out_template'])
347
348
348 def update_prompt(self, name, new_template=None):
349 def update_prompt(self, name, new_template=None):
349 """This is called when a prompt template is updated. It processes
350 """This is called when a prompt template is updated. It processes
350 abbreviations used in the prompt template (like \#) and calculates how
351 abbreviations used in the prompt template (like \#) and calculates how
351 many invisible characters (ANSI colour escapes) the resulting prompt
352 many invisible characters (ANSI colour escapes) the resulting prompt
352 contains.
353 contains.
353
354
354 It is also called for each prompt on changing the colour scheme. In both
355 It is also called for each prompt on changing the colour scheme. In both
355 cases, traitlets should take care of calling this automatically.
356 cases, traitlets should take care of calling this automatically.
356 """
357 """
357 if new_template is not None:
358 if new_template is not None:
358 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
359 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
359 # We count invisible characters (colour escapes) on the last line of the
360 # We count invisible characters (colour escapes) on the last line of the
360 # prompt, to calculate the width for lining up subsequent prompts.
361 # prompt, to calculate the width for lining up subsequent prompts.
361 invis_chars = _invisible_characters(self._render(name, color=True))
362 invis_chars = _invisible_characters(self._render(name, color=True))
362 self.invisible_chars[name] = invis_chars
363 self.invisible_chars[name] = invis_chars
363
364
364 def _update_prompt_trait(self, traitname, new_template):
365 def _update_prompt_trait(self, traitname, new_template):
365 name = traitname[:-9] # Cut off '_template'
366 name = traitname[:-9] # Cut off '_template'
366 self.update_prompt(name, new_template)
367 self.update_prompt(name, new_template)
367
368
368 def _render(self, name, color=True, **kwargs):
369 def _render(self, name, color=True, **kwargs):
369 """Render but don't justify, or update the width or txtwidth attributes.
370 """Render but don't justify, or update the width or txtwidth attributes.
370 """
371 """
371 if name == 'rewrite':
372 if name == 'rewrite':
372 return self._render_rewrite(color=color)
373 return self._render_rewrite(color=color)
373
374
374 if color:
375 if color:
375 scheme = self.color_scheme_table.active_colors
376 scheme = self.color_scheme_table.active_colors
376 if name=='out':
377 if name=='out':
377 colors = color_lists['normal']
378 colors = color_lists['normal']
378 colors.number, colors.prompt, colors.normal = \
379 colors.number, colors.prompt, colors.normal = \
379 scheme.out_number, scheme.out_prompt, scheme.normal
380 scheme.out_number, scheme.out_prompt, scheme.normal
380 else:
381 else:
381 colors = color_lists['inp']
382 colors = color_lists['inp']
382 colors.number, colors.prompt, colors.normal = \
383 colors.number, colors.prompt, colors.normal = \
383 scheme.in_number, scheme.in_prompt, scheme.in_normal
384 scheme.in_number, scheme.in_prompt, scheme.in_normal
384 if name=='in2':
385 if name=='in2':
385 colors.prompt = scheme.in_prompt2
386 colors.prompt = scheme.in_prompt2
386 else:
387 else:
387 # No color
388 # No color
388 colors = color_lists['nocolor']
389 colors = color_lists['nocolor']
389 colors.number, colors.prompt, colors.normal = '', '', ''
390 colors.number, colors.prompt, colors.normal = '', '', ''
390
391
391 count = self.shell.execution_count # Shorthand
392 count = self.shell.execution_count # Shorthand
392 # Build the dictionary to be passed to string formatting
393 # Build the dictionary to be passed to string formatting
393 fmtargs = dict(color=colors, count=count,
394 fmtargs = dict(color=colors, count=count,
394 dots="."*len(str(count)),
395 dots="."*len(str(count)), spaces=" "*len(str(count)),
395 width=self.width, txtwidth=self.txtwidth )
396 width=self.width, txtwidth=self.txtwidth)
396 fmtargs.update(self.lazy_evaluate_fields)
397 fmtargs.update(self.lazy_evaluate_fields)
397 fmtargs.update(kwargs)
398 fmtargs.update(kwargs)
398
399
399 # Prepare the prompt
400 # Prepare the prompt
400 prompt = colors.prompt + self.templates[name] + colors.normal
401 prompt = colors.prompt + self.templates[name] + colors.normal
401
402
402 # Fill in required fields
403 # Fill in required fields
403 return self._formatter.format(prompt, **fmtargs)
404 return self._formatter.format(prompt, **fmtargs)
404
405
405 def _render_rewrite(self, color=True):
406 def _render_rewrite(self, color=True):
406 """Render the ---> rewrite prompt."""
407 """Render the ---> rewrite prompt."""
407 if color:
408 if color:
408 scheme = self.color_scheme_table.active_colors
409 scheme = self.color_scheme_table.active_colors
409 # We need a non-input version of these escapes
410 # We need a non-input version of these escapes
410 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
411 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
411 color_normal = scheme.normal
412 color_normal = scheme.normal
412 else:
413 else:
413 color_prompt, color_normal = '', ''
414 color_prompt, color_normal = '', ''
414
415
415 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
416 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
416
417
417 def render(self, name, color=True, just=None, **kwargs):
418 def render(self, name, color=True, just=None, **kwargs):
418 """
419 """
419 Render the selected prompt.
420 Render the selected prompt.
420
421
421 Parameters
422 Parameters
422 ----------
423 ----------
423 name : str
424 name : str
424 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
425 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
425 color : bool
426 color : bool
426 If True (default), include ANSI escape sequences for a coloured prompt.
427 If True (default), include ANSI escape sequences for a coloured prompt.
427 just : bool
428 just : bool
428 If True, justify the prompt to the width of the last prompt. The
429 If True, justify the prompt to the width of the last prompt. The
429 default is stored in self.justify.
430 default is stored in self.justify.
430 **kwargs :
431 **kwargs :
431 Additional arguments will be passed to the string formatting operation,
432 Additional arguments will be passed to the string formatting operation,
432 so they can override the values that would otherwise fill in the
433 so they can override the values that would otherwise fill in the
433 template.
434 template.
434
435
435 Returns
436 Returns
436 -------
437 -------
437 A string containing the rendered prompt.
438 A string containing the rendered prompt.
438 """
439 """
439 res = self._render(name, color=color, **kwargs)
440 res = self._render(name, color=color, **kwargs)
440
441
441 # Handle justification of prompt
442 # Handle justification of prompt
442 invis_chars = self.invisible_chars[name] if color else 0
443 invis_chars = self.invisible_chars[name] if color else 0
443 self.txtwidth = _lenlastline(res) - invis_chars
444 self.txtwidth = _lenlastline(res) - invis_chars
444 just = self.justify if (just is None) else just
445 just = self.justify if (just is None) else just
445 # If the prompt spans more than one line, don't try to justify it:
446 # If the prompt spans more than one line, don't try to justify it:
446 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
447 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
447 res = res.rjust(self.width + invis_chars)
448 res = res.rjust(self.width + invis_chars)
448 self.width = _lenlastline(res) - invis_chars
449 self.width = _lenlastline(res) - invis_chars
449 return res
450 return res
General Comments 0
You need to be logged in to leave comments. Login now