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