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