interactiveshell.py
523 lines
| 19.6 KiB
| text/x-python
|
PythonLexer
Brian Granger
|
r2761 | # -*- 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 | ||||
#----------------------------------------------------------------------------- | ||||
# Main class | ||||
#----------------------------------------------------------------------------- | ||||
class TerminalInteractiveShell(InteractiveShell): | ||||
autoedit_syntax = CBool(False, 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) | ||||
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) | ||||