##// END OF EJS Templates
fix another FreeBSD $HOME symlink issue
Paul Ivanov -
Show More
@@ -1,435 +1,439 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 IPython.config.configurable import Configurable
31 from IPython.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 IPython.utils.traitlets import (Unicode, Instance, Dict, Bool, Int)
34 from IPython.utils.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 unicode(self())
101 return unicode(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
141 # /usr/home, but retain the $HOME variable as pointing to /home
142 HOME = os.path.realpath(HOME)
143
140 # 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
141 # fixed once ipython starts. This reduces the runtime overhead of computing
145 # fixed once ipython starts. This reduces the runtime overhead of computing
142 # prompt strings.
146 # prompt strings.
143 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
147 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
144 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
148 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
145 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
149 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
146 ROOT_SYMBOL = "#" if (os.name=='nt' or os.getuid()==0) else "$"
150 ROOT_SYMBOL = "#" if (os.name=='nt' or os.getuid()==0) else "$"
147
151
148 prompt_abbreviations = {
152 prompt_abbreviations = {
149 # Prompt/history count
153 # Prompt/history count
150 '%n' : '{color.number}' '{count}' '{color.prompt}',
154 '%n' : '{color.number}' '{count}' '{color.prompt}',
151 r'\#': '{color.number}' '{count}' '{color.prompt}',
155 r'\#': '{color.number}' '{count}' '{color.prompt}',
152 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
156 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
153 # can get numbers displayed in whatever color they want.
157 # can get numbers displayed in whatever color they want.
154 r'\N': '{count}',
158 r'\N': '{count}',
155
159
156 # Prompt/history count, with the actual digits replaced by dots. Used
160 # Prompt/history count, with the actual digits replaced by dots. Used
157 # mainly in continuation prompts (prompt_in2)
161 # mainly in continuation prompts (prompt_in2)
158 r'\D': '{dots}',
162 r'\D': '{dots}',
159
163
160 # Current time
164 # Current time
161 r'\T' : '{time}',
165 r'\T' : '{time}',
162 # Current working directory
166 # Current working directory
163 r'\w': '{cwd}',
167 r'\w': '{cwd}',
164 # Basename of current working directory.
168 # Basename of current working directory.
165 # (use os.sep to make this portable across OSes)
169 # (use os.sep to make this portable across OSes)
166 r'\W' : '{cwd_last}',
170 r'\W' : '{cwd_last}',
167 # These X<N> are an extension to the normal bash prompts. They return
171 # These X<N> are an extension to the normal bash prompts. They return
168 # N terms of the path, after replacing $HOME with '~'
172 # N terms of the path, after replacing $HOME with '~'
169 r'\X0': '{cwd_x[0]}',
173 r'\X0': '{cwd_x[0]}',
170 r'\X1': '{cwd_x[1]}',
174 r'\X1': '{cwd_x[1]}',
171 r'\X2': '{cwd_x[2]}',
175 r'\X2': '{cwd_x[2]}',
172 r'\X3': '{cwd_x[3]}',
176 r'\X3': '{cwd_x[3]}',
173 r'\X4': '{cwd_x[4]}',
177 r'\X4': '{cwd_x[4]}',
174 r'\X5': '{cwd_x[5]}',
178 r'\X5': '{cwd_x[5]}',
175 # Y<N> are similar to X<N>, but they show '~' if it's the directory
179 # Y<N> are similar to X<N>, but they show '~' if it's the directory
176 # N+1 in the list. Somewhat like %cN in tcsh.
180 # N+1 in the list. Somewhat like %cN in tcsh.
177 r'\Y0': '{cwd_y[0]}',
181 r'\Y0': '{cwd_y[0]}',
178 r'\Y1': '{cwd_y[1]}',
182 r'\Y1': '{cwd_y[1]}',
179 r'\Y2': '{cwd_y[2]}',
183 r'\Y2': '{cwd_y[2]}',
180 r'\Y3': '{cwd_y[3]}',
184 r'\Y3': '{cwd_y[3]}',
181 r'\Y4': '{cwd_y[4]}',
185 r'\Y4': '{cwd_y[4]}',
182 r'\Y5': '{cwd_y[5]}',
186 r'\Y5': '{cwd_y[5]}',
183 # Hostname up to first .
187 # Hostname up to first .
184 r'\h': HOSTNAME_SHORT,
188 r'\h': HOSTNAME_SHORT,
185 # Full hostname
189 # Full hostname
186 r'\H': HOSTNAME,
190 r'\H': HOSTNAME,
187 # Username of current user
191 # Username of current user
188 r'\u': USER,
192 r'\u': USER,
189 # Escaped '\'
193 # Escaped '\'
190 '\\\\': '\\',
194 '\\\\': '\\',
191 # Newline
195 # Newline
192 r'\n': '\n',
196 r'\n': '\n',
193 # Carriage return
197 # Carriage return
194 r'\r': '\r',
198 r'\r': '\r',
195 # Release version
199 # Release version
196 r'\v': release.version,
200 r'\v': release.version,
197 # Root symbol ($ or #)
201 # Root symbol ($ or #)
198 r'\$': ROOT_SYMBOL,
202 r'\$': ROOT_SYMBOL,
199 }
203 }
200
204
201 #-----------------------------------------------------------------------------
205 #-----------------------------------------------------------------------------
202 # More utilities
206 # More utilities
203 #-----------------------------------------------------------------------------
207 #-----------------------------------------------------------------------------
204
208
205 def cwd_filt(depth):
209 def cwd_filt(depth):
206 """Return the last depth elements of the current working directory.
210 """Return the last depth elements of the current working directory.
207
211
208 $HOME is always replaced with '~'.
212 $HOME is always replaced with '~'.
209 If depth==0, the full path is returned."""
213 If depth==0, the full path is returned."""
210
214
211 cwd = os.getcwdu().replace(HOME,"~")
215 cwd = os.getcwdu().replace(HOME,"~")
212 out = os.sep.join(cwd.split(os.sep)[-depth:])
216 out = os.sep.join(cwd.split(os.sep)[-depth:])
213 return out or os.sep
217 return out or os.sep
214
218
215 def cwd_filt2(depth):
219 def cwd_filt2(depth):
216 """Return the last depth elements of the current working directory.
220 """Return the last depth elements of the current working directory.
217
221
218 $HOME is always replaced with '~'.
222 $HOME is always replaced with '~'.
219 If depth==0, the full path is returned."""
223 If depth==0, the full path is returned."""
220
224
221 full_cwd = os.getcwdu()
225 full_cwd = os.getcwdu()
222 cwd = full_cwd.replace(HOME,"~").split(os.sep)
226 cwd = full_cwd.replace(HOME,"~").split(os.sep)
223 if '~' in cwd and len(cwd) == depth+1:
227 if '~' in cwd and len(cwd) == depth+1:
224 depth += 1
228 depth += 1
225 drivepart = ''
229 drivepart = ''
226 if sys.platform == 'win32' and len(cwd) > depth:
230 if sys.platform == 'win32' and len(cwd) > depth:
227 drivepart = os.path.splitdrive(full_cwd)[0]
231 drivepart = os.path.splitdrive(full_cwd)[0]
228 out = drivepart + '/'.join(cwd[-depth:])
232 out = drivepart + '/'.join(cwd[-depth:])
229
233
230 return out or os.sep
234 return out or os.sep
231
235
232 #-----------------------------------------------------------------------------
236 #-----------------------------------------------------------------------------
233 # Prompt classes
237 # Prompt classes
234 #-----------------------------------------------------------------------------
238 #-----------------------------------------------------------------------------
235
239
236 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
240 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
237 'cwd': LazyEvaluate(os.getcwdu),
241 'cwd': LazyEvaluate(os.getcwdu),
238 'cwd_last': LazyEvaluate(lambda: os.getcwdu().split(os.sep)[-1]),
242 'cwd_last': LazyEvaluate(lambda: os.getcwdu().split(os.sep)[-1]),
239 'cwd_x': [LazyEvaluate(lambda: os.getcwdu().replace(HOME,"~"))] +\
243 'cwd_x': [LazyEvaluate(lambda: os.getcwdu().replace(HOME,"~"))] +\
240 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
244 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
241 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
245 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
242 }
246 }
243
247
244 def _lenlastline(s):
248 def _lenlastline(s):
245 """Get the length of the last line. More intelligent than
249 """Get the length of the last line. More intelligent than
246 len(s.splitlines()[-1]).
250 len(s.splitlines()[-1]).
247 """
251 """
248 if not s or s.endswith(('\n', '\r')):
252 if not s or s.endswith(('\n', '\r')):
249 return 0
253 return 0
250 return len(s.splitlines()[-1])
254 return len(s.splitlines()[-1])
251
255
252
256
253 class UserNSFormatter(Formatter):
257 class UserNSFormatter(Formatter):
254 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
258 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
255 def __init__(self, shell):
259 def __init__(self, shell):
256 self.shell = shell
260 self.shell = shell
257
261
258 def get_value(self, key, args, kwargs):
262 def get_value(self, key, args, kwargs):
259 # try regular formatting first:
263 # try regular formatting first:
260 try:
264 try:
261 return Formatter.get_value(self, key, args, kwargs)
265 return Formatter.get_value(self, key, args, kwargs)
262 except Exception:
266 except Exception:
263 pass
267 pass
264 # next, look in user_ns and builtins:
268 # next, look in user_ns and builtins:
265 for container in (self.shell.user_ns, __builtins__):
269 for container in (self.shell.user_ns, __builtins__):
266 if key in container:
270 if key in container:
267 return container[key]
271 return container[key]
268 # nothing found, put error message in its place
272 # nothing found, put error message in its place
269 return "<ERROR: '%s' not found>" % key
273 return "<ERROR: '%s' not found>" % key
270
274
271
275
272 class PromptManager(Configurable):
276 class PromptManager(Configurable):
273 """This is the primary interface for producing IPython's prompts."""
277 """This is the primary interface for producing IPython's prompts."""
274 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
278 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
275
279
276 color_scheme_table = Instance(coloransi.ColorSchemeTable)
280 color_scheme_table = Instance(coloransi.ColorSchemeTable)
277 color_scheme = Unicode('Linux', config=True)
281 color_scheme = Unicode('Linux', config=True)
278 def _color_scheme_changed(self, name, new_value):
282 def _color_scheme_changed(self, name, new_value):
279 self.color_scheme_table.set_active_scheme(new_value)
283 self.color_scheme_table.set_active_scheme(new_value)
280 for pname in ['in', 'in2', 'out', 'rewrite']:
284 for pname in ['in', 'in2', 'out', 'rewrite']:
281 # We need to recalculate the number of invisible characters
285 # We need to recalculate the number of invisible characters
282 self.update_prompt(pname)
286 self.update_prompt(pname)
283
287
284 lazy_evaluate_fields = Dict(help="""
288 lazy_evaluate_fields = Dict(help="""
285 This maps field names used in the prompt templates to functions which
289 This maps field names used in the prompt templates to functions which
286 will be called when the prompt is rendered. This allows us to include
290 will be called when the prompt is rendered. This allows us to include
287 things like the current time in the prompts. Functions are only called
291 things like the current time in the prompts. Functions are only called
288 if they are used in the prompt.
292 if they are used in the prompt.
289 """)
293 """)
290 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
294 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
291
295
292 in_template = Unicode('In [\\#]: ', config=True,
296 in_template = Unicode('In [\\#]: ', config=True,
293 help="Input prompt. '\\#' will be transformed to the prompt number")
297 help="Input prompt. '\\#' will be transformed to the prompt number")
294 in2_template = Unicode(' .\\D.: ', config=True,
298 in2_template = Unicode(' .\\D.: ', config=True,
295 help="Continuation prompt.")
299 help="Continuation prompt.")
296 out_template = Unicode('Out[\\#]: ', config=True,
300 out_template = Unicode('Out[\\#]: ', config=True,
297 help="Output prompt. '\\#' will be transformed to the prompt number")
301 help="Output prompt. '\\#' will be transformed to the prompt number")
298
302
299 justify = Bool(True, config=True, help="""
303 justify = Bool(True, config=True, help="""
300 If True (default), each prompt will be right-aligned with the
304 If True (default), each prompt will be right-aligned with the
301 preceding one.
305 preceding one.
302 """)
306 """)
303
307
304 # We actually store the expanded templates here:
308 # We actually store the expanded templates here:
305 templates = Dict()
309 templates = Dict()
306
310
307 # The number of characters in the last prompt rendered, not including
311 # The number of characters in the last prompt rendered, not including
308 # colour characters.
312 # colour characters.
309 width = Int()
313 width = Int()
310 txtwidth = Int() # Not including right-justification
314 txtwidth = Int() # Not including right-justification
311
315
312 # The number of characters in each prompt which don't contribute to width
316 # The number of characters in each prompt which don't contribute to width
313 invisible_chars = Dict()
317 invisible_chars = Dict()
314 def _invisible_chars_default(self):
318 def _invisible_chars_default(self):
315 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
319 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
316
320
317 def __init__(self, shell, config=None):
321 def __init__(self, shell, config=None):
318 super(PromptManager, self).__init__(shell=shell, config=config)
322 super(PromptManager, self).__init__(shell=shell, config=config)
319
323
320 # Prepare colour scheme table
324 # Prepare colour scheme table
321 self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors,
325 self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors,
322 PColLinux, PColLightBG], self.color_scheme)
326 PColLinux, PColLightBG], self.color_scheme)
323
327
324 self._formatter = UserNSFormatter(shell)
328 self._formatter = UserNSFormatter(shell)
325 # Prepare templates & numbers of invisible characters
329 # Prepare templates & numbers of invisible characters
326 self.update_prompt('in', self.in_template)
330 self.update_prompt('in', self.in_template)
327 self.update_prompt('in2', self.in2_template)
331 self.update_prompt('in2', self.in2_template)
328 self.update_prompt('out', self.out_template)
332 self.update_prompt('out', self.out_template)
329 self.update_prompt('rewrite')
333 self.update_prompt('rewrite')
330 self.on_trait_change(self._update_prompt_trait, ['in_template',
334 self.on_trait_change(self._update_prompt_trait, ['in_template',
331 'in2_template', 'out_template'])
335 'in2_template', 'out_template'])
332
336
333 def update_prompt(self, name, new_template=None):
337 def update_prompt(self, name, new_template=None):
334 """This is called when a prompt template is updated. It processes
338 """This is called when a prompt template is updated. It processes
335 abbreviations used in the prompt template (like \#) and calculates how
339 abbreviations used in the prompt template (like \#) and calculates how
336 many invisible characters (ANSI colour escapes) the resulting prompt
340 many invisible characters (ANSI colour escapes) the resulting prompt
337 contains.
341 contains.
338
342
339 It is also called for each prompt on changing the colour scheme. In both
343 It is also called for each prompt on changing the colour scheme. In both
340 cases, traitlets should take care of calling this automatically.
344 cases, traitlets should take care of calling this automatically.
341 """
345 """
342 if new_template is not None:
346 if new_template is not None:
343 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
347 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
344 # We count invisible characters (colour escapes) on the last line of the
348 # We count invisible characters (colour escapes) on the last line of the
345 # prompt, to calculate the width for lining up subsequent prompts.
349 # prompt, to calculate the width for lining up subsequent prompts.
346 invis_chars = _lenlastline(self._render(name, color=True)) - \
350 invis_chars = _lenlastline(self._render(name, color=True)) - \
347 _lenlastline(self._render(name, color=False))
351 _lenlastline(self._render(name, color=False))
348 self.invisible_chars[name] = invis_chars
352 self.invisible_chars[name] = invis_chars
349
353
350 def _update_prompt_trait(self, traitname, new_template):
354 def _update_prompt_trait(self, traitname, new_template):
351 name = traitname[:-9] # Cut off '_template'
355 name = traitname[:-9] # Cut off '_template'
352 self.update_prompt(name, new_template)
356 self.update_prompt(name, new_template)
353
357
354 def _render(self, name, color=True, **kwargs):
358 def _render(self, name, color=True, **kwargs):
355 """Render but don't justify, or update the width or txtwidth attributes.
359 """Render but don't justify, or update the width or txtwidth attributes.
356 """
360 """
357 if name == 'rewrite':
361 if name == 'rewrite':
358 return self._render_rewrite(color=color)
362 return self._render_rewrite(color=color)
359
363
360 if color:
364 if color:
361 scheme = self.color_scheme_table.active_colors
365 scheme = self.color_scheme_table.active_colors
362 if name=='out':
366 if name=='out':
363 colors = color_lists['normal']
367 colors = color_lists['normal']
364 colors.number, colors.prompt, colors.normal = \
368 colors.number, colors.prompt, colors.normal = \
365 scheme.out_number, scheme.out_prompt, scheme.normal
369 scheme.out_number, scheme.out_prompt, scheme.normal
366 else:
370 else:
367 colors = color_lists['inp']
371 colors = color_lists['inp']
368 colors.number, colors.prompt, colors.normal = \
372 colors.number, colors.prompt, colors.normal = \
369 scheme.in_number, scheme.in_prompt, scheme.in_normal
373 scheme.in_number, scheme.in_prompt, scheme.in_normal
370 if name=='in2':
374 if name=='in2':
371 colors.prompt = scheme.in_prompt2
375 colors.prompt = scheme.in_prompt2
372 else:
376 else:
373 # No color
377 # No color
374 colors = color_lists['nocolor']
378 colors = color_lists['nocolor']
375 colors.number, colors.prompt, colors.normal = '', '', ''
379 colors.number, colors.prompt, colors.normal = '', '', ''
376
380
377 count = self.shell.execution_count # Shorthand
381 count = self.shell.execution_count # Shorthand
378 # Build the dictionary to be passed to string formatting
382 # Build the dictionary to be passed to string formatting
379 fmtargs = dict(color=colors, count=count,
383 fmtargs = dict(color=colors, count=count,
380 dots="."*len(str(count)),
384 dots="."*len(str(count)),
381 width=self.width, txtwidth=self.txtwidth )
385 width=self.width, txtwidth=self.txtwidth )
382 fmtargs.update(self.lazy_evaluate_fields)
386 fmtargs.update(self.lazy_evaluate_fields)
383 fmtargs.update(kwargs)
387 fmtargs.update(kwargs)
384
388
385 # Prepare the prompt
389 # Prepare the prompt
386 prompt = colors.prompt + self.templates[name] + colors.normal
390 prompt = colors.prompt + self.templates[name] + colors.normal
387
391
388 # Fill in required fields
392 # Fill in required fields
389 return self._formatter.format(prompt, **fmtargs)
393 return self._formatter.format(prompt, **fmtargs)
390
394
391 def _render_rewrite(self, color=True):
395 def _render_rewrite(self, color=True):
392 """Render the ---> rewrite prompt."""
396 """Render the ---> rewrite prompt."""
393 if color:
397 if color:
394 scheme = self.color_scheme_table.active_colors
398 scheme = self.color_scheme_table.active_colors
395 # We need a non-input version of these escapes
399 # We need a non-input version of these escapes
396 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
400 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
397 color_normal = scheme.normal
401 color_normal = scheme.normal
398 else:
402 else:
399 color_prompt, color_normal = '', ''
403 color_prompt, color_normal = '', ''
400
404
401 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
405 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
402
406
403 def render(self, name, color=True, just=None, **kwargs):
407 def render(self, name, color=True, just=None, **kwargs):
404 """
408 """
405 Render the selected prompt.
409 Render the selected prompt.
406
410
407 Parameters
411 Parameters
408 ----------
412 ----------
409 name : str
413 name : str
410 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
414 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
411 color : bool
415 color : bool
412 If True (default), include ANSI escape sequences for a coloured prompt.
416 If True (default), include ANSI escape sequences for a coloured prompt.
413 just : bool
417 just : bool
414 If True, justify the prompt to the width of the last prompt. The
418 If True, justify the prompt to the width of the last prompt. The
415 default is stored in self.justify.
419 default is stored in self.justify.
416 **kwargs :
420 **kwargs :
417 Additional arguments will be passed to the string formatting operation,
421 Additional arguments will be passed to the string formatting operation,
418 so they can override the values that would otherwise fill in the
422 so they can override the values that would otherwise fill in the
419 template.
423 template.
420
424
421 Returns
425 Returns
422 -------
426 -------
423 A string containing the rendered prompt.
427 A string containing the rendered prompt.
424 """
428 """
425 res = self._render(name, color=color, **kwargs)
429 res = self._render(name, color=color, **kwargs)
426
430
427 # Handle justification of prompt
431 # Handle justification of prompt
428 invis_chars = self.invisible_chars[name] if color else 0
432 invis_chars = self.invisible_chars[name] if color else 0
429 self.txtwidth = _lenlastline(res) - invis_chars
433 self.txtwidth = _lenlastline(res) - invis_chars
430 just = self.justify if (just is None) else just
434 just = self.justify if (just is None) else just
431 # If the prompt spans more than one line, don't try to justify it:
435 # If the prompt spans more than one line, don't try to justify it:
432 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
436 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
433 res = res.rjust(self.width + invis_chars)
437 res = res.rjust(self.width + invis_chars)
434 self.width = _lenlastline(res) - invis_chars
438 self.width = _lenlastline(res) - invis_chars
435 return res
439 return res
General Comments 0
You need to be logged in to leave comments. Login now