##// END OF EJS Templates
Complete reorganization of InteractiveShell....
Complete reorganization of InteractiveShell. I have created a new subclass of InteractiveShell for the old terminal frontend. All frontendy type things have been moved to this subclass.

File last commit:

r2761:7b6e19f0
r2761:7b6e19f0
Show More
interactiveshell.py
541 lines | 20.2 KiB | text/x-python | PythonLexer
# -*- coding: utf-8 -*-
"""Subclass of InteractiveShell for terminal based frontends."""
#-----------------------------------------------------------------------------
# Copyright (C) 2001 Janko Hauser <jhauser@zscout.de>
# Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
# Copyright (C) 2008-2010 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
import __builtin__
import bdb
from contextlib import nested
import os
import re
import sys
from IPython.core.error import TryNext
from IPython.core.usage import interactive_usage, default_banner
from IPython.core.inputlist import InputList
from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
from IPython.lib.inputhook import enable_gui
from IPython.lib.pylabtools import pylab_activate
from IPython.utils.terminal import toggle_set_term_title, set_term_title
from IPython.utils.process import abbrev_cwd
from IPython.utils.warn import warn
from IPython.utils.text import num_ini_spaces
from IPython.utils.traitlets import Int, Str, CBool
#-----------------------------------------------------------------------------
# Utilities
#-----------------------------------------------------------------------------
def get_default_editor():
try:
ed = os.environ['EDITOR']
except KeyError:
if os.name == 'posix':
ed = 'vi' # the only one guaranteed to be there!
else:
ed = 'notepad' # same in Windows!
return ed
# store the builtin raw_input globally, and use this always, in case user code
# overwrites it (like wx.py.PyShell does)
raw_input_original = raw_input
class SeparateStr(Str):
"""A Str subclass to validate separate_in, separate_out, etc.
This is a Str based trait that converts '0'->'' and '\\n'->'\n'.
"""
def validate(self, obj, value):
if value == '0': value = ''
value = value.replace('\\n','\n')
return super(SeparateStr, self).validate(obj, value)
#-----------------------------------------------------------------------------
# Main class
#-----------------------------------------------------------------------------
class TerminalInteractiveShell(InteractiveShell):
autoedit_syntax = CBool(False, config=True)
autoindent = CBool(True, config=True)
banner = Str('')
banner1 = Str(default_banner, config=True)
banner2 = Str('', config=True)
confirm_exit = CBool(True, config=True)
# This display_banner only controls whether or not self.show_banner()
# is called when mainloop/interact are called. The default is False
# because for the terminal based application, the banner behavior
# is controlled by Global.display_banner, which IPythonApp looks at
# to determine if *it* should call show_banner() by hand or not.
display_banner = CBool(False) # This isn't configurable!
embedded = CBool(False)
embedded_active = CBool(False)
editor = Str(get_default_editor(), config=True)
exit_now = CBool(False)
pager = Str('less', config=True)
screen_length = Int(0, config=True)
# Use custom TraitTypes that convert '0'->'' and '\\n'->'\n'
separate_in = SeparateStr('\n', config=True)
separate_out = SeparateStr('', config=True)
separate_out2 = SeparateStr('', config=True)
term_title = CBool(False, config=True)
def __init__(self, config=None, ipython_dir=None, user_ns=None,
user_global_ns=None, custom_exceptions=((),None),
usage=None, banner1=None, banner2=None,
display_banner=None):
super(TerminalInteractiveShell, self).__init__(
config=config, ipython_dir=ipython_dir, user_ns=user_ns,
user_global_ns=user_global_ns, custom_exceptions=custom_exceptions
)
self.init_term_title()
self.init_usage(usage)
self.init_banner(banner1, banner2, display_banner)
#-------------------------------------------------------------------------
# Things related to the terminal
#-------------------------------------------------------------------------
@property
def usable_screen_length(self):
if self.screen_length == 0:
return 0
else:
num_lines_bot = self.separate_in.count('\n')+1
return self.screen_length - num_lines_bot
def init_term_title(self):
# Enable or disable the terminal title.
if self.term_title:
toggle_set_term_title(True)
set_term_title('IPython: ' + abbrev_cwd())
else:
toggle_set_term_title(False)
#-------------------------------------------------------------------------
# Things related to the banner and usage
#-------------------------------------------------------------------------
def _banner1_changed(self):
self.compute_banner()
def _banner2_changed(self):
self.compute_banner()
def _term_title_changed(self, name, new_value):
self.init_term_title()
def init_banner(self, banner1, banner2, display_banner):
if banner1 is not None:
self.banner1 = banner1
if banner2 is not None:
self.banner2 = banner2
if display_banner is not None:
self.display_banner = display_banner
self.compute_banner()
def show_banner(self, banner=None):
if banner is None:
banner = self.banner
self.write(banner)
def compute_banner(self):
self.banner = self.banner1 + '\n'
if self.profile:
self.banner += '\nIPython profile: %s\n' % self.profile
if self.banner2:
self.banner += '\n' + self.banner2 + '\n'
def init_usage(self, usage=None):
if usage is None:
self.usage = interactive_usage
else:
self.usage = usage
#-------------------------------------------------------------------------
# Mainloop and code execution logic
#-------------------------------------------------------------------------
def mainloop(self, display_banner=None):
"""Start the mainloop.
If an optional banner argument is given, it will override the
internally created default banner.
"""
with nested(self.builtin_trap, self.display_trap):
# if you run stuff with -c <cmd>, raw hist is not updated
# ensure that it's in sync
if len(self.input_hist) != len (self.input_hist_raw):
self.input_hist_raw = InputList(self.input_hist)
while 1:
try:
self.interact(display_banner=display_banner)
#self.interact_with_readline()
# XXX for testing of a readline-decoupled repl loop, call
# interact_with_readline above
break
except KeyboardInterrupt:
# this should not be necessary, but KeyboardInterrupt
# handling seems rather unpredictable...
self.write("\nKeyboardInterrupt in interact()\n")
def interact(self, display_banner=None):
"""Closely emulate the interactive Python console."""
# batch run -> do not interact
if self.exit_now:
return
if display_banner is None:
display_banner = self.display_banner
if display_banner:
self.show_banner()
more = 0
# Mark activity in the builtins
__builtin__.__dict__['__IPYTHON__active'] += 1
if self.has_readline:
self.readline_startup_hook(self.pre_readline)
# exit_now is set by a call to %Exit or %Quit, through the
# ask_exit callback.
while not self.exit_now:
self.hooks.pre_prompt_hook()
if more:
try:
prompt = self.hooks.generate_prompt(True)
except:
self.showtraceback()
if self.autoindent:
self.rl_do_indent = True
else:
try:
prompt = self.hooks.generate_prompt(False)
except:
self.showtraceback()
try:
line = self.raw_input(prompt, more)
if self.exit_now:
# quick exit on sys.std[in|out] close
break
if self.autoindent:
self.rl_do_indent = False
except KeyboardInterrupt:
#double-guard against keyboardinterrupts during kbdint handling
try:
self.write('\nKeyboardInterrupt\n')
self.resetbuffer()
# keep cache in sync with the prompt counter:
self.outputcache.prompt_count -= 1
if self.autoindent:
self.indent_current_nsp = 0
more = 0
except KeyboardInterrupt:
pass
except EOFError:
if self.autoindent:
self.rl_do_indent = False
if self.has_readline:
self.readline_startup_hook(None)
self.write('\n')
self.exit()
except bdb.BdbQuit:
warn('The Python debugger has exited with a BdbQuit exception.\n'
'Because of how pdb handles the stack, it is impossible\n'
'for IPython to properly format this particular exception.\n'
'IPython will resume normal operation.')
except:
# exceptions here are VERY RARE, but they can be triggered
# asynchronously by signal handlers, for example.
self.showtraceback()
else:
more = self.push_line(line)
if (self.SyntaxTB.last_syntax_error and
self.autoedit_syntax):
self.edit_syntax_error()
# We are off again...
__builtin__.__dict__['__IPYTHON__active'] -= 1
# Turn off the exit flag, so the mainloop can be restarted if desired
self.exit_now = False
def raw_input(self,prompt='',continue_prompt=False):
"""Write a prompt and read a line.
The returned line does not include the trailing newline.
When the user enters the EOF key sequence, EOFError is raised.
Optional inputs:
- prompt(''): a string to be printed to prompt the user.
- continue_prompt(False): whether this line is the first one or a
continuation in a sequence of inputs.
"""
# growl.notify("raw_input: ", "prompt = %r\ncontinue_prompt = %s" % (prompt, continue_prompt))
# Code run by the user may have modified the readline completer state.
# We must ensure that our completer is back in place.
if self.has_readline:
self.set_completer()
try:
line = raw_input_original(prompt).decode(self.stdin_encoding)
except ValueError:
warn("\n********\nYou or a %run:ed script called sys.stdin.close()"
" or sys.stdout.close()!\nExiting IPython!")
self.ask_exit()
return ""
# Try to be reasonably smart about not re-indenting pasted input more
# than necessary. We do this by trimming out the auto-indent initial
# spaces, if the user's actual input started itself with whitespace.
#debugx('self.buffer[-1]')
if self.autoindent:
if num_ini_spaces(line) > self.indent_current_nsp:
line = line[self.indent_current_nsp:]
self.indent_current_nsp = 0
# store the unfiltered input before the user has any chance to modify
# it.
if line.strip():
if continue_prompt:
self.input_hist_raw[-1] += '%s\n' % line
if self.has_readline and self.readline_use:
try:
histlen = self.readline.get_current_history_length()
if histlen > 1:
newhist = self.input_hist_raw[-1].rstrip()
self.readline.remove_history_item(histlen-1)
self.readline.replace_history_item(histlen-2,
newhist.encode(self.stdin_encoding))
except AttributeError:
pass # re{move,place}_history_item are new in 2.4.
else:
self.input_hist_raw.append('%s\n' % line)
# only entries starting at first column go to shadow history
if line.lstrip() == line:
self.shadowhist.add(line.strip())
elif not continue_prompt:
self.input_hist_raw.append('\n')
try:
lineout = self.prefilter_manager.prefilter_lines(line,continue_prompt)
except:
# blanket except, in case a user-defined prefilter crashes, so it
# can't take all of ipython with it.
self.showtraceback()
return ''
else:
return lineout
# TODO: The following three methods are an early attempt to refactor
# the main code execution logic. We don't use them, but they may be
# helpful when we refactor the code execution logic further.
# def interact_prompt(self):
# """ Print the prompt (in read-eval-print loop)
#
# Provided for those who want to implement their own read-eval-print loop (e.g. GUIs), not
# used in standard IPython flow.
# """
# if self.more:
# try:
# prompt = self.hooks.generate_prompt(True)
# except:
# self.showtraceback()
# if self.autoindent:
# self.rl_do_indent = True
#
# else:
# try:
# prompt = self.hooks.generate_prompt(False)
# except:
# self.showtraceback()
# self.write(prompt)
#
# def interact_handle_input(self,line):
# """ Handle the input line (in read-eval-print loop)
#
# Provided for those who want to implement their own read-eval-print loop (e.g. GUIs), not
# used in standard IPython flow.
# """
# if line.lstrip() == line:
# self.shadowhist.add(line.strip())
# lineout = self.prefilter_manager.prefilter_lines(line,self.more)
#
# if line.strip():
# if self.more:
# self.input_hist_raw[-1] += '%s\n' % line
# else:
# self.input_hist_raw.append('%s\n' % line)
#
#
# self.more = self.push_line(lineout)
# if (self.SyntaxTB.last_syntax_error and
# self.autoedit_syntax):
# self.edit_syntax_error()
#
# def interact_with_readline(self):
# """ Demo of using interact_handle_input, interact_prompt
#
# This is the main read-eval-print loop. If you need to implement your own (e.g. for GUI),
# it should work like this.
# """
# self.readline_startup_hook(self.pre_readline)
# while not self.exit_now:
# self.interact_prompt()
# if self.more:
# self.rl_do_indent = True
# else:
# self.rl_do_indent = False
# line = raw_input_original().decode(self.stdin_encoding)
# self.interact_handle_input(line)
#-------------------------------------------------------------------------
# Methods to support auto-editing of SyntaxErrors.
#-------------------------------------------------------------------------
def edit_syntax_error(self):
"""The bottom half of the syntax error handler called in the main loop.
Loop until syntax error is fixed or user cancels.
"""
while self.SyntaxTB.last_syntax_error:
# copy and clear last_syntax_error
err = self.SyntaxTB.clear_err_state()
if not self._should_recompile(err):
return
try:
# may set last_syntax_error again if a SyntaxError is raised
self.safe_execfile(err.filename,self.user_ns)
except:
self.showtraceback()
else:
try:
f = file(err.filename)
try:
# This should be inside a display_trap block and I
# think it is.
sys.displayhook(f.read())
finally:
f.close()
except:
self.showtraceback()
def _should_recompile(self,e):
"""Utility routine for edit_syntax_error"""
if e.filename in ('<ipython console>','<input>','<string>',
'<console>','<BackgroundJob compilation>',
None):
return False
try:
if (self.autoedit_syntax and
not self.ask_yes_no('Return to editor to correct syntax error? '
'[Y/n] ','y')):
return False
except EOFError:
return False
def int0(x):
try:
return int(x)
except TypeError:
return 0
# always pass integer line and offset values to editor hook
try:
self.hooks.fix_error_editor(e.filename,
int0(e.lineno),int0(e.offset),e.msg)
except TryNext:
warn('Could not open editor')
return False
return True
#-------------------------------------------------------------------------
# Things related to GUI support and pylab
#-------------------------------------------------------------------------
def enable_pylab(self, gui=None):
"""Activate pylab support at runtime.
This turns on support for matplotlib, preloads into the interactive
namespace all of numpy and pylab, and configures IPython to correcdtly
interact with the GUI event loop. The GUI backend to be used can be
optionally selected with the optional :param:`gui` argument.
Parameters
----------
gui : optional, string
If given, dictates the choice of matplotlib GUI backend to use
(should be one of IPython's supported backends, 'tk', 'qt', 'wx' or
'gtk'), otherwise we use the default chosen by matplotlib (as
dictated by the matplotlib build-time options plus the user's
matplotlibrc configuration file).
"""
# We want to prevent the loading of pylab to pollute the user's
# namespace as shown by the %who* magics, so we execute the activation
# code in an empty namespace, and we update *both* user_ns and
# user_ns_hidden with this information.
ns = {}
gui = pylab_activate(ns, gui)
self.user_ns.update(ns)
self.user_ns_hidden.update(ns)
# Now we must activate the gui pylab wants to use, and fix %run to take
# plot updates into account
enable_gui(gui)
self.magic_run = self._pylab_magic_run
#-------------------------------------------------------------------------
# Things related to exiting
#-------------------------------------------------------------------------
def ask_exit(self):
""" Ask the shell to exit. Can be overiden and used as a callback. """
self.exit_now = True
def exit(self):
"""Handle interactive exit.
This method calls the ask_exit callback."""
if self.confirm_exit:
if self.ask_yes_no('Do you really want to exit ([y]/n)?','y'):
self.ask_exit()
else:
self.ask_exit()
InteractiveShellABC.register(TerminalInteractiveShell)