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