diff --git a/IPython/core/displayhook.py b/IPython/core/displayhook.py index 269ae18..ad3cc27 100644 --- a/IPython/core/displayhook.py +++ b/IPython/core/displayhook.py @@ -113,7 +113,7 @@ class DisplayHook(Configurable): """ # Use write, not print which adds an extra space. sys.stdout.write(self.shell.separate_out) - outprompt = self.shell.prompt_manager.render('out') + outprompt = 'Out[{}]: '.format(self.shell.execution_count) if self.do_full_cache: sys.stdout.write(outprompt) @@ -149,6 +149,9 @@ class DisplayHook(Configurable): """ return self.shell.display_formatter.format(result) + # This can be set to True by the write_output_prompt method in a subclass + prompt_end_newline = False + def write_format_data(self, format_dict, md_dict=None): """Write the format data dict to the frontend. @@ -179,8 +182,7 @@ class DisplayHook(Configurable): # because the expansion may add ANSI escapes that will interfere # with our ability to determine whether or not we should add # a newline. - prompt_template = self.shell.prompt_manager.out_template - if prompt_template and not prompt_template.endswith('\n'): + if not self.prompt_end_newline: # But avoid extraneous empty lines. result_repr = '\n' + result_repr diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index d081dfd..7c20eed 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -56,7 +56,6 @@ from IPython.core.macro import Macro from IPython.core.payload import PayloadManager from IPython.core.prefilter import PrefilterManager from IPython.core.profiledir import ProfileDir -from IPython.core.prompts import PromptManager from IPython.core.usage import default_banner from IPython.testing.skipdoctest import skip_doctest_py2, skip_doctest from IPython.utils import PyColorize @@ -664,8 +663,6 @@ class InteractiveShell(SingletonConfigurable): io.stderr = io.IOStream(sys.stderr) def init_prompts(self): - self.prompt_manager = PromptManager(shell=self, parent=self) - self.configurables.append(self.prompt_manager) # Set system prompts, so that scripts can decide if they are running # interactively. sys.ps1 = 'In : ' @@ -2339,16 +2336,9 @@ class InteractiveShell(SingletonConfigurable): """ if not self.show_rewritten_input: return - - rw = self.prompt_manager.render('rewrite') + cmd - try: - # plain ascii works better w/ pyreadline, on some machines, so - # we use it and only print uncolored rewrite if we have unicode - rw = str(rw) - print(rw) - except UnicodeEncodeError: - print("------> " + cmd) + # This is overridden in TerminalInteractiveShell to use fancy prompts + print("------> " + cmd) #------------------------------------------------------------------------- # Things related to extracting values/expressions from kernel and user_ns @@ -3233,6 +3223,11 @@ class InteractiveShell(SingletonConfigurable): self.restore_sys_module_state() + # Overridden in terminal subclass to change prompts + def switch_doctest_mode(self, mode): + pass + + class InteractiveShellABC(with_metaclass(abc.ABCMeta, object)): """An abstract base class for InteractiveShell.""" diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index b42aab9..818a90c 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -356,14 +356,6 @@ Defaulting color scheme to 'NoColor'""" # Will remove this check after switching to prompt_toolkit new_scheme = 'NoColor' - # Set prompt colors - try: - shell.prompt_manager.color_scheme = new_scheme - except: - color_switch_err('prompt') - else: - shell.colors = \ - shell.prompt_manager.color_scheme_table.active_scheme_name # Set exception colors try: shell.InteractiveTB.set_colors(scheme = new_scheme) @@ -435,7 +427,6 @@ Defaulting color scheme to 'NoColor'""" # Shorthands shell = self.shell - pm = shell.prompt_manager meta = shell.meta disp_formatter = self.shell.display_formatter ptformatter = disp_formatter.formatters['text/plain'] @@ -450,23 +441,17 @@ Defaulting color scheme to 'NoColor'""" save_dstore('xmode',shell.InteractiveTB.mode) save_dstore('rc_separate_out',shell.separate_out) save_dstore('rc_separate_out2',shell.separate_out2) - save_dstore('rc_prompts_pad_left',pm.justify) save_dstore('rc_separate_in',shell.separate_in) save_dstore('rc_active_types',disp_formatter.active_types) - save_dstore('prompt_templates',(pm.in_template, pm.in2_template, pm.out_template)) if not mode: # turn on - pm.in_template = '>>> ' - pm.in2_template = '... ' - pm.out_template = '' # Prompt separators like plain python shell.separate_in = '' shell.separate_out = '' shell.separate_out2 = '' - pm.justify = False ptformatter.pprint = False disp_formatter.active_types = ['text/plain'] @@ -474,22 +459,22 @@ Defaulting color scheme to 'NoColor'""" shell.magic('xmode Plain') else: # turn off - pm.in_template, pm.in2_template, pm.out_template = dstore.prompt_templates - shell.separate_in = dstore.rc_separate_in shell.separate_out = dstore.rc_separate_out shell.separate_out2 = dstore.rc_separate_out2 - pm.justify = dstore.rc_prompts_pad_left - ptformatter.pprint = dstore.rc_pprint disp_formatter.active_types = dstore.rc_active_types shell.magic('xmode ' + dstore.xmode) + # mode here is the state before we switch; switch_doctest_mode takes + # the mode we're switching to. + shell.switch_doctest_mode(not mode) + # Store new mode and inform - dstore.mode = bool(1-int(mode)) + dstore.mode = bool(not mode) mode_label = ['OFF','ON'][dstore.mode] print('Doctest mode is:', mode_label) diff --git a/IPython/core/prompts.py b/IPython/core/prompts.py index 0f42557..7802bc5 100644 --- a/IPython/core/prompts.py +++ b/IPython/core/prompts.py @@ -1,37 +1,8 @@ # -*- coding: utf-8 -*- -"""Classes for handling input/output prompts.""" +"""Being removed +""" -# Copyright (c) 2001-2007 Fernando Perez -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import os -import re -import socket -import sys -import time - -from string import Formatter - -from traitlets.config.configurable import Configurable -from IPython.core import release -from IPython.utils import coloransi, py3compat -from traitlets import Unicode, Instance, Dict, Bool, Int, observe, default - -from IPython.utils.PyColorize import LightBGColors, LinuxColors, NoColor - -#----------------------------------------------------------------------------- -# 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()) - -#----------------------------------------------------------------------------- -# Utilities -#----------------------------------------------------------------------------- +from IPython.utils import py3compat class LazyEvaluate(object): """This is used for formatting strings with values that need to be updated @@ -53,360 +24,3 @@ class LazyEvaluate(object): def __format__(self, format_spec): return format(self(), format_spec) - -def multiple_replace(dict, text): - """ Replace in 'text' all occurrences 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 or - # spaces. Used mainly in continuation prompts (prompt_in2). - r'\D': '{dots}', - r'\S': '{spaces}', - - # 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 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 are similar to X, 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]) - - -invisible_chars_re = re.compile('\001[^\001\002]*\002') -def _invisible_characters(s): - """ - Get the number of invisible ANSI characters in s. Invisible characters - must be delimited by \001 and \002. - """ - return _lenlastline(s) - _lenlastline(invisible_chars_re.sub('', s)) - -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 "" % key - - -class PromptManager(Configurable): - """This is the primary interface for producing IPython's prompts.""" - shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) - - color_scheme_table = Instance(coloransi.ColorSchemeTable, allow_none=True) - color_scheme = Unicode('Linux').tag(config=True) - - @observe('color_scheme') - def _color_scheme_changed(self, change): - self.color_scheme_table.set_active_scheme(change['new']) - 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. - """) - - in_template = Unicode('In [\\#]: ', - help="Input prompt. '\\#' will be transformed to the prompt number" - ).tag(config=True) - in2_template = Unicode(' .\\D.: ', - help="Continuation prompt.").tag(config=True) - out_template = Unicode('Out[\\#]: ', - help="Output prompt. '\\#' will be transformed to the prompt number" - ).tag(config=True) - - @default('lazy_evaluate_fields') - def _lazy_evaluate_fields_default(self): - return lazily_evaluate.copy() - - justify = Bool(True, help=""" - If True (default), each prompt will be right-aligned with the - preceding one. - """).tag(config=True) - - # 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() - - @default('invisible_chars') - 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([NoColor, - LinuxColors, LightBGColors], 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.observe(self._update_prompt_trait, - names=['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 = _invisible_characters(self._render(name, color=True)) - self.invisible_chars[name] = invis_chars - - def _update_prompt_trait(self, changes): - traitname = changes['name'] - new_template = changes['new'] - 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)), spaces=" "*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 diff --git a/IPython/core/tests/test_prompts.py b/IPython/core/tests/test_prompts.py index 024ebaa..6595dab 100644 --- a/IPython/core/tests/test_prompts.py +++ b/IPython/core/tests/test_prompts.py @@ -3,75 +3,14 @@ import unittest -import os - -from IPython.testing import tools as tt, decorators as dec -from IPython.core.prompts import PromptManager, LazyEvaluate, _invisible_characters +from IPython.core.prompts import LazyEvaluate from IPython.testing.globalipapp import get_ipython -from IPython.utils.tempdir import TemporaryWorkingDirectory -from IPython.utils import py3compat from IPython.utils.py3compat import unicode_type ip = get_ipython() class PromptTests(unittest.TestCase): - def setUp(self): - self.pm = PromptManager(shell=ip, config=ip.config) - - def test_multiline_prompt(self): - self.pm.in_template = "[In]\n>>>" - self.pm.render('in') - self.assertEqual(self.pm.width, 3) - self.assertEqual(self.pm.txtwidth, 3) - - self.pm.in_template = '[In]\n' - self.pm.render('in') - self.assertEqual(self.pm.width, 0) - self.assertEqual(self.pm.txtwidth, 0) - - def test_translate_abbreviations(self): - def do_translate(template): - self.pm.in_template = template - return self.pm.templates['in'] - - pairs = [(r'%n>', '{color.number}{count}{color.prompt}>'), - (r'\T', '{time}'), - (r'\n', '\n') - ] - - tt.check_pairs(do_translate, pairs) - - def test_user_ns(self): - self.pm.color_scheme = 'NoColor' - ip.ex("foo='bar'") - self.pm.in_template = "In [{foo}]" - prompt = self.pm.render('in') - self.assertEqual(prompt, u'In [bar]') - - def test_builtins(self): - self.pm.color_scheme = 'NoColor' - self.pm.in_template = "In [{int}]" - prompt = self.pm.render('in') - self.assertEqual(prompt, u"In [%r]" % int) - - def test_undefined(self): - self.pm.color_scheme = 'NoColor' - self.pm.in_template = "In [{foo_dne}]" - prompt = self.pm.render('in') - self.assertEqual(prompt, u"In []") - - def test_render(self): - self.pm.in_template = r'\#>' - self.assertEqual(self.pm.render('in',color=False), '%d>' % ip.execution_count) - - @dec.onlyif_unicode_paths - def test_render_unicode_cwd(self): - with TemporaryWorkingDirectory(u'ünicødé'): - self.pm.in_template = r'\w [\#]' - p = self.pm.render('in', color=False) - self.assertEqual(p, u"%s [%i]" % (py3compat.getcwd(), ip.execution_count)) - def test_lazy_eval_unicode(self): u = u'ünicødé' lz = LazyEvaluate(lambda : u) @@ -95,35 +34,4 @@ class PromptTests(unittest.TestCase): self.assertEqual(unicode_type(lz), unicode_type(f)) self.assertEqual(format(lz), str(f)) self.assertEqual(format(lz, '.1'), '0.5') - - @dec.skip_win32 - def test_cwd_x(self): - self.pm.in_template = r"\X0" - save = py3compat.getcwd() - os.chdir(os.path.expanduser('~')) - p = self.pm.render('in', color=False) - try: - self.assertEqual(p, '~') - finally: - os.chdir(save) - - def test_invisible_chars(self): - self.assertEqual(_invisible_characters('abc'), 0) - self.assertEqual(_invisible_characters('\001\033[1;37m\002'), 9) - # Sequences must be between \001 and \002 to be counted - self.assertEqual(_invisible_characters('\033[1;37m'), 0) - # Test custom escape sequences - self.assertEqual(_invisible_characters('\001\033]133;A\a\002'), 10) - - def test_width(self): - default_in = '\x01\x1b]133;A\x07\x02In [1]: \x01\x1b]133;B\x07\x02' - self.pm.in_template = default_in - self.pm.render('in') - self.assertEqual(self.pm.width, 8) - self.assertEqual(self.pm.txtwidth, 8) - # Test custom escape sequences - self.pm.in_template = '\001\033]133;A\a\002' + default_in + '\001\033]133;B\a\002' - self.pm.render('in') - self.assertEqual(self.pm.width, 8) - self.assertEqual(self.pm.txtwidth, 8) diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py index 5e64fb2..e3ade57 100644 --- a/IPython/core/tests/test_run.py +++ b/IPython/core/tests/test_run.py @@ -205,13 +205,6 @@ class TestMagicRunPass(tt.TempFileMixin): _ip = get_ipython() self.run_tmpfile() nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys)) - - def test_prompts(self): - """Test that prompts correctly generate after %run""" - self.run_tmpfile() - _ip = get_ipython() - p2 = _ip.prompt_manager.render('in2').strip() - nt.assert_equal(p2[:3], '...') def test_run_profile( self ): """Test that the option -p, which invokes the profiler, do not diff --git a/IPython/core/tests/test_shellapp.py b/IPython/core/tests/test_shellapp.py index 6e2e31b..8e15743 100644 --- a/IPython/core/tests/test_shellapp.py +++ b/IPython/core/tests/test_shellapp.py @@ -52,10 +52,9 @@ class TestFileToRun(unittest.TestCase, tt.TempFileMixin): src = "True\n" self.mktmp(src) - out = 'In [1]: False\n\nIn [2]:' - err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None - tt.ipexec_validate(self.fname, out, err, options=['-i'], + out, err = tt.ipexec(self.fname, options=['-i'], commands=['"__file__" in globals()', 'exit()']) + self.assertIn("False", out) @dec.skip_win32 @dec.skipif(PY3) @@ -64,7 +63,6 @@ class TestFileToRun(unittest.TestCase, tt.TempFileMixin): src = "from __future__ import division\n" self.mktmp(src) - out = 'In [1]: float\n\nIn [2]:' - err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None - tt.ipexec_validate(self.fname, out, err, options=['-i'], + out, err = tt.ipexec(self.fname, options=['-i'], commands=['type(1/2)', 'exit()']) + self.assertIn('float', out) diff --git a/IPython/sphinxext/ipython_directive.py b/IPython/sphinxext/ipython_directive.py index eb1219f..7c13cd4 100644 --- a/IPython/sphinxext/ipython_directive.py +++ b/IPython/sphinxext/ipython_directive.py @@ -898,7 +898,6 @@ class IPythonDirective(Directive): if not self.state.document.current_source in self.seen_docs: self.shell.IP.history_manager.reset() self.shell.IP.execution_count = 1 - self.shell.IP.prompt_manager.width = 0 self.seen_docs.add(self.state.document.current_source) # and attach to shell so we don't have to pass them around diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 85f17af..1c1eaef 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -614,7 +614,7 @@ class TerminalInteractiveShell(InteractiveShell): self.hooks.pre_prompt_hook() if more: try: - prompt = self.prompt_manager.render('in2') + prompt = ' ...: ' except: self.showtraceback() if self.autoindent: @@ -622,7 +622,7 @@ class TerminalInteractiveShell(InteractiveShell): else: try: - prompt = self.separate_in + self.prompt_manager.render('in') + prompt = self.separate_in + 'In [{}]: '.format(self.execution_count) except: self.showtraceback() try: diff --git a/IPython/terminal/ipapp.py b/IPython/terminal/ipapp.py index b39eb52..d0470a1 100755 --- a/IPython/terminal/ipapp.py +++ b/IPython/terminal/ipapp.py @@ -24,7 +24,6 @@ from IPython.core.completer import IPCompleter from IPython.core.crashhandler import CrashHandler from IPython.core.formatters import PlainTextFormatter from IPython.core.history import HistoryManager -from IPython.core.prompts import PromptManager from IPython.core.application import ( ProfileDir, BaseIPythonApplication, base_flags, base_aliases ) @@ -195,7 +194,6 @@ class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp): InteractiveShellApp, # ShellApp comes before TerminalApp, because self.__class__, # it will also affect subclasses (e.g. QtConsole) TerminalInteractiveShell, - PromptManager, HistoryManager, ProfileDir, PlainTextFormatter, diff --git a/IPython/terminal/prompts.py b/IPython/terminal/prompts.py new file mode 100644 index 0000000..9547128 --- /dev/null +++ b/IPython/terminal/prompts.py @@ -0,0 +1,73 @@ +"""Terminal input and output prompts.""" +from __future__ import print_function + +from pygments.token import Token +import sys + +from IPython.core.displayhook import DisplayHook + +class Prompts(object): + def __init__(self, shell): + self.shell = shell + + def in_prompt_tokens(self, cli=None): + return [ + (Token.Prompt, 'In ['), + (Token.PromptNum, str(self.shell.execution_count)), + (Token.Prompt, ']: '), + ] + + def _width(self): + in_tokens = self.in_prompt_tokens() + return sum(len(s) for (t, s) in in_tokens) + + def continuation_prompt_tokens(self, cli=None, width=None): + if width is None: + width = self._width() + return [ + (Token.Prompt, (' ' * (width - 5)) + '...: '), + ] + + def rewrite_prompt_tokens(self): + width = self._width() + return [ + (Token.Prompt, ('-' * (width - 2)) + '> '), + ] + + def out_prompt_tokens(self): + return [ + (Token.OutPrompt, 'Out['), + (Token.OutPromptNum, str(self.shell.execution_count)), + (Token.OutPrompt, ']: '), + ] + +class ClassicPrompts(Prompts): + def in_prompt_tokens(self, cli=None): + return [ + (Token.Prompt, '>>> '), + ] + + def continuation_prompt_tokens(self, cli=None, width=None): + return [ + (Token.Prompt, '... ') + ] + + def rewrite_prompt_tokens(self): + return [] + + def out_prompt_tokens(self): + return [] + +class RichPromptDisplayHook(DisplayHook): + """Subclass of base display hook using coloured prompt""" + def write_output_prompt(self): + sys.stdout.write(self.shell.separate_out) + self.prompt_end_newline = False + if self.do_full_cache: + tokens = self.shell.prompts.out_prompt_tokens() + if tokens and tokens[-1][1].endswith('\n'): + self.prompt_end_newline = True + if self.shell.pt_cli: + self.shell.pt_cli.print_tokens(tokens) + else: + print(*(s for t, s in tokens), sep='') diff --git a/IPython/terminal/ptshell.py b/IPython/terminal/ptshell.py index cb63634..3c34d7c 100644 --- a/IPython/terminal/ptshell.py +++ b/IPython/terminal/ptshell.py @@ -11,7 +11,7 @@ from IPython.core.interactiveshell import InteractiveShell from IPython.utils.py3compat import cast_unicode_py2, input from IPython.utils.terminal import toggle_set_term_title, set_term_title from IPython.utils.process import abbrev_cwd -from traitlets import Bool, Unicode, Dict, Integer, observe +from traitlets import Bool, Unicode, Dict, Integer, observe, Instance from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode from prompt_toolkit.filters import HasFocus, HasSelection, Condition, ViInsertMode, EmacsInsertMode, IsDone @@ -29,6 +29,7 @@ from pygments.token import Token from .debugger import TerminalPdb, Pdb from .pt_inputhooks import get_inputhook_func from .interactiveshell import get_default_editor, TerminalMagics +from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook from .ptutils import IPythonPTCompleter, IPythonPTLexer _use_simple_prompt = 'IPY_TEST_SIMPLE_PROMPT' in os.environ or not sys.stdin.isatty() @@ -98,7 +99,19 @@ class TerminalInteractiveShell(InteractiveShell): editor = Unicode(get_default_editor(), help="Set the editor used by IPython (default to $EDITOR/vi/notepad)." ).tag(config=True) - + + prompts = Instance(Prompts) + + def _prompts_default(self): + return Prompts(self) + + @observe('prompts') + def _(self, change): + self._update_layout() + + def _displayhook_class_default(self): + return RichPromptDisplayHook + term_title = Bool(True, help="Automatically set the terminal title" ).tag(config=True) @@ -120,18 +133,6 @@ class TerminalInteractiveShell(InteractiveShell): else: toggle_set_term_title(False) - def get_prompt_tokens(self, cli): - return [ - (Token.Prompt, 'In ['), - (Token.PromptNum, str(self.execution_count)), - (Token.Prompt, ']: '), - ] - - def get_continuation_tokens(self, cli, width): - return [ - (Token.Prompt, (' ' * (width - 5)) + '...: '), - ] - def init_prompt_toolkit_cli(self): if self.simple_prompt: # Fall back to plain non-interactive output for tests. @@ -234,6 +235,8 @@ class TerminalInteractiveShell(InteractiveShell): style_overrides = { Token.Prompt: '#009900', Token.PromptNum: '#00ff00 bold', + Token.OutPrompt: '#990000', + Token.OutPromptNum: '#ff0000 bold', } if name == 'default': style_cls = get_style_by_name('default') @@ -261,8 +264,8 @@ class TerminalInteractiveShell(InteractiveShell): return { 'lexer':IPythonPTLexer(), 'reserve_space_for_menu':self.space_for_menu, - 'get_prompt_tokens':self.get_prompt_tokens, - 'get_continuation_tokens':self.get_continuation_tokens, + 'get_prompt_tokens':self.prompts.in_prompt_tokens, + 'get_continuation_tokens':self.prompts.continuation_prompt_tokens, 'multiline':True, 'display_completions_in_columns': self.display_completions_in_columns, @@ -439,6 +442,29 @@ class TerminalInteractiveShell(InteractiveShell): # work correctly. system = InteractiveShell.system_raw + def auto_rewrite_input(self, cmd): + """Overridden from the parent class to use fancy rewriting prompt""" + if not self.show_rewritten_input: + return + + tokens = self.prompts.rewrite_prompt_tokens() + if self.pt_cli: + self.pt_cli.print_tokens(tokens) + print(cmd) + else: + prompt = ''.join(s for t, s in tokens) + print(prompt, cmd, sep='') + + _prompts_before = None + def switch_doctest_mode(self, mode): + """Switch prompts to classic for %doctest_mode""" + if mode: + self._prompts_before = self.prompts + self.prompts = ClassicPrompts(self) + elif self._prompts_before: + self.prompts = self._prompts_before + self._prompts_before = None + if __name__ == '__main__': TerminalInteractiveShell.instance().interact() diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index 73f4ee7..bf62f30 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -202,13 +202,7 @@ def ipexec(fname, options=None, commands=()): """ if options is None: options = [] - # For these subprocess calls, eliminate all prompt printing so we only see - # output from script execution - prompt_opts = [ '--PromptManager.in_template=""', - '--PromptManager.in2_template=""', - '--PromptManager.out_template=""' - ] - cmdargs = default_argv() + prompt_opts + options + cmdargs = default_argv() + options test_dir = os.path.dirname(__file__) diff --git a/docs/source/config/details.rst b/docs/source/config/details.rst index c8b01c7..3f7414b 100644 --- a/docs/source/config/details.rst +++ b/docs/source/config/details.rst @@ -2,52 +2,6 @@ Specific config details ======================= -Prompts -======= - -In the terminal, the format of the input and output prompts can be -customised. This does not currently affect other frontends. - -The following codes in the prompt string will be substituted into the -prompt string: - -====== =================================== ===================================================== -Short Long Notes -====== =================================== ===================================================== -%n,\\# {color.number}{count}{color.prompt} history counter with bolding -\\N {count} history counter without bolding -\\D {dots} series of dots the same width as the history counter -\\T {time} current time -\\w {cwd} current working directory -\\W {cwd_last} basename of CWD -\\Xn {cwd_x[n]} Show the last n terms of the CWD. n=0 means show all. -\\Yn {cwd_y[n]} Like \Xn, but show '~' for $HOME -\\h hostname, up to the first '.' -\\H full hostname -\\u username (from the $USER environment variable) -\\v IPython version -\\$ root symbol ("$" for normal user or "#" for root) -``\\`` escaped '\\' -\\n newline -\\r carriage return -n/a {color.} set terminal colour - see below for list of names -====== =================================== ===================================================== - -Available colour names are: Black, BlinkBlack, BlinkBlue, BlinkCyan, -BlinkGreen, BlinkLightGray, BlinkPurple, BlinkRed, BlinkYellow, Blue, -Brown, Cyan, DarkGray, Green, LightBlue, LightCyan, LightGray, LightGreen, -LightPurple, LightRed, Purple, Red, White, Yellow. The selected colour -scheme also defines the names *prompt* and *number*. Finally, the name -*normal* resets the terminal to its default colour. - -So, this config:: - - c.PromptManager.in_template = "{color.LightGreen}{time}{color.Yellow} \u{color.normal}>>>" - -will produce input prompts with the time in light green, your username -in yellow, and a ``>>>`` prompt in the default terminal colour. - - .. _termcolour: Terminal Colors @@ -84,27 +38,10 @@ These have shown problems: extensions. Once Gary's readline library is installed, the normal WinXP/2k command prompt works perfectly. -Currently the following color schemes are available: - - * NoColor: uses no color escapes at all (all escapes are empty '' '' - strings). This 'scheme' is thus fully safe to use in any terminal. - * Linux: works well in Linux console type environments: dark - background with light fonts. It uses bright colors for - information, so it is difficult to read if you have a light - colored background. - * LightBG: the basic colors are similar to those in the Linux scheme - but darker. It is easy to read in terminals with light backgrounds. - IPython uses colors for two main groups of things: prompts and tracebacks which are directly printed to the terminal, and the object introspection system which passes large sets of data through a pager. -If you are seeing garbage sequences in your terminal and no colour, you -may need to disable colours: run ``%colors NoColor`` inside IPython, or -add this to a config file:: - - c.InteractiveShell.colors = 'NoColor' - Colors in the pager ------------------- diff --git a/docs/source/config/intro.rst b/docs/source/config/intro.rst index f91e701..d3ab800 100644 --- a/docs/source/config/intro.rst +++ b/docs/source/config/intro.rst @@ -73,11 +73,6 @@ Example config file c.InteractiveShell.editor = 'nano' c.InteractiveShell.xmode = 'Context' - c.PromptManager.in_template = 'In [\#]: ' - c.PromptManager.in2_template = ' .\D.: ' - c.PromptManager.out_template = 'Out[\#]: ' - c.PromptManager.justify = True - c.PrefilterManager.multi_line_specials = True c.AliasManager.user_aliases = [ diff --git a/docs/source/interactive/shell.rst b/docs/source/interactive/shell.rst index 4ba0f54..5724efb 100644 --- a/docs/source/interactive/shell.rst +++ b/docs/source/interactive/shell.rst @@ -63,20 +63,46 @@ switching to any of them. Type ``cd?`` for more details. Prompt customization ==================== -Here are some prompt configurations you can try out interactively by using the -``%config`` magic:: - - %config PromptManager.in_template = r'{color.LightGreen}\u@\h{color.LightBlue}[{color.LightCyan}\Y1{color.LightBlue}]{color.Green}|\#> ' - %config PromptManager.in2_template = r'{color.Green}|{color.LightGreen}\D{color.Green}> ' - %config PromptManager.out_template = r'<\#> ' - - -You can change the prompt configuration to your liking permanently by editing -``ipython_config.py``:: - - c.PromptManager.in_template = r'{color.LightGreen}\u@\h{color.LightBlue}[{color.LightCyan}\Y1{color.LightBlue}]{color.Green}|\#> ' - c.PromptManager.in2_template = r'{color.Green}|{color.LightGreen}\D{color.Green}> ' - c.PromptManager.out_template = r'<\#> ' +Starting at IPython 5.0 the prompt customisation is done by subclassing :class:`IPython.terminal.prompts.Prompt`. + +The custom ``Prompt`` receive the current IPython shell instance as first +argument, which by default is stored as the ``shell`` attribute of the object. +The class can implement optional methods for each of the available prompt types: + + - ``in_prompt_tokens(self, cli=None)``, input prompt , default to ``In [1]`` + - ``continuation_prompt_tokens(self, cli=None, width=None)``, continuation prompt for multi lines (default `...:`) + - ``rewrite_prompt_tokens(self)`` + - ``out_prompt_tokens(self)`` + +Each of these methods should return a list of `(TokenType, Token)` pairs. See documentation of `prompt_toolkit` and/or `Pygments`. + +Here is an example of Prompt class that will insert the current working directory in front of a prompt: + + +.. codeblock:: python + + from IPython.terminal.prompts import Prompts, Token + import os + + class MyPrompt(Prompts): + + def in_prompt_tokens(self, cli=None): + return [(Token, os.getcwd()), + (Token.Prompt, ' >>>')] + +To set the new prompt, assign it to the `prompts` attribute of the IPython shell: + +.. codeblock:: python + + In[2]: ip = get_ipython() + ...: ip.prompts = MyPrompt(ip) + + ~/ >>> # it works + + +See ``IPython/example/utils/cwd_prompt.py`` for an example of how to write an +extensions that customise prompts. + Read more about the :ref:`configuration system ` for details on how to find ``ipython_config.py``. diff --git a/examples/utils/cwd_prompt.py b/examples/utils/cwd_prompt.py new file mode 100644 index 0000000..1d1b03a --- /dev/null +++ b/examples/utils/cwd_prompt.py @@ -0,0 +1,26 @@ +"""This is an example that shows how to create new prompts for IPython +""" + +from IPython.terminal.prompts import Prompts, Token +import os + +class MyPrompt(Prompts): + + def in_prompt_tokens(self, cli=None): + return [(Token, os.getcwd()), + (Token.Prompt, '>>>')] + +def load_ipython_extension(shell): + new_prompts = MyPrompt(shell) + new_prompts.old_prompts = shell.prompts + shell.prompts = new_prompts + +def unload_ipython_extension(shell): + if not hasattr(shell.prompts, 'old_prompts'): + print("cannot unload") + else: + shell.prompts = shell.prompts.old_prompts + + + +