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