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