From 9b31561855df278cfccbfc29f19d46f5c370cf6c 2010-08-20 19:16:22 From: epatters Date: 2010-08-20 19:16:22 Subject: [PATCH] Merge branch 'newkernel' of git://github.com/ellisonbg/ipython into qtfrontend --- diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index e5a4a79..711b2f9 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -1901,9 +1901,6 @@ class InteractiveShell(Configurable, Magic): - 1: an error occurred. """ - # Clear the payload before executing new code. - self.payload_manager.clear_payload() - # Set our own excepthook in case the user code tries to call it # directly, so that the IPython crash handler doesn't get triggered old_excepthook,sys.excepthook = sys.excepthook, self.excepthook diff --git a/IPython/core/magic.py b/IPython/core/magic.py index d5ef5d4..aff9523 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -52,7 +52,7 @@ from IPython.core.error import TryNext from IPython.core.error import UsageError from IPython.core.fakemodule import FakeModule from IPython.core.macro import Macro -from IPython.core.page import page +from IPython.core import page from IPython.core.prefilter import ESC_MAGIC from IPython.lib.pylabtools import mpl_runner from IPython.lib.inputhook import enable_gui @@ -514,7 +514,7 @@ Currently the magic system has the following functions:\n""" (' '+mesc).join(self.lsmagic()), Magic.auto_status[self.shell.automagic] ) ) - page(outmsg,screen_lines=self.shell.usable_screen_length) + page.page(outmsg,screen_lines=self.shell.usable_screen_length) def magic_autoindent(self, parameter_s = ''): @@ -656,7 +656,7 @@ Currently the magic system has the following functions:\n""" info = self._ofind(oname) if info['found']: txt = (raw and str or pformat)( info['obj'] ) - page(txt) + page.page(txt) else: print 'Object `%s` not found' % oname @@ -727,7 +727,7 @@ Currently the magic system has the following functions:\n""" except IOError,msg: print msg return - page(self.shell.inspector.format(file(filename).read())) + page.page(self.shell.inspector.format(file(filename).read())) def _inspect(self,meth,oname,namespaces=None,**kw): """Generic interface to the inspector system. @@ -1520,7 +1520,7 @@ Currently the magic system has the following functions:\n""" output = stdout_trap.getvalue() output = output.rstrip() - page(output,screen_lines=self.shell.usable_screen_length) + page.page(output,screen_lines=self.shell.usable_screen_length) print sys_exit, dump_file = opts.D[0] @@ -3256,7 +3256,7 @@ Defaulting color scheme to 'NoColor'""" print "Error: no such file or variable" return - page(self.shell.pycolorize(cont), + page.page(self.shell.pycolorize(cont), screen_lines=self.shell.usable_screen_length) def _rerun_pasted(self): @@ -3413,7 +3413,7 @@ Defaulting color scheme to 'NoColor'""" import IPython.core.usage qr = IPython.core.usage.quick_reference + self.magic_magic('-brief') - page(qr) + page.page(qr) def magic_doctest_mode(self,parameter_s=''): """Toggle doctest mode on and off. diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py index 723acae..5d19236 100644 --- a/IPython/core/oinspect.py +++ b/IPython/core/oinspect.py @@ -27,7 +27,7 @@ import sys import types # IPython's own -from IPython.core.page import page +from IPython.core import page from IPython.external.Itpl import itpl from IPython.utils import PyColorize import IPython.utils.io @@ -281,7 +281,7 @@ class Inspector: if output is None: self.noinfo('documentation',oname) return - page(output) + page.page(output) def psource(self,obj,oname=''): """Print the source code for an object.""" @@ -293,7 +293,7 @@ class Inspector: except: self.noinfo('source',oname) else: - page(self.format(src)) + page.page(self.format(src)) def pfile(self,obj,oname=''): """Show the whole file where an object was defined.""" @@ -325,7 +325,7 @@ class Inspector: # Print only text files, not extension binaries. Note that # getsourcelines returns lineno with 1-offset and page() uses # 0-offset, so we must adjust. - page(self.format(open(ofile).read()),lineno-1) + page.page(self.format(open(ofile).read()),lineno-1) def pinfo(self,obj,oname='',formatter=None,info=None,detail_level=0): """Show detailed information about an object. @@ -548,7 +548,7 @@ class Inspector: # Finally send to printer/pager output = out.getvalue() if output: - page(output) + page.page(output) # end pinfo def psearch(self,pattern,ns_table,ns_search=[], @@ -606,4 +606,4 @@ class Inspector: search_result.extend(tmp_res) search_result.sort() - page('\n'.join(search_result)) + page.page('\n'.join(search_result)) diff --git a/IPython/core/payloadpage.py b/IPython/core/payloadpage.py new file mode 100644 index 0000000..2ec73e2 --- /dev/null +++ b/IPython/core/payloadpage.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +A payload based version of page. + +Authors: + +* Brian Granger +* Fernando Perez +""" + +#----------------------------------------------------------------------------- +# 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 +#----------------------------------------------------------------------------- + +from IPython.core.interactiveshell import InteractiveShell + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +def page(strng, start=0, screen_lines=0, pager_cmd=None): + """Print a string, piping through a pager. + + This version ignores the screen_lines and pager_cmd arguments and uses + IPython's payload system instead. + """ + + # Some routines may auto-compute start offsets incorrectly and pass a + # negative value. Offset to 0 for robustness. + start = max(0, start) + shell = InteractiveShell.instance() + payload = dict( + source='IPython.zmq.page.page', + data=strng, + start_line_number=start + ) + shell.payload_manager.write_payload(payload) + +def install_payload_page(): + """Install this version of page as IPython.core.page.page.""" + from IPython.core import page as corepage + corepage.page = page diff --git a/IPython/core/prefilter.py b/IPython/core/prefilter.py index 3c11767..45ac5ad 100755 --- a/IPython/core/prefilter.py +++ b/IPython/core/prefilter.py @@ -33,7 +33,7 @@ from IPython.core.alias import AliasManager from IPython.core.autocall import IPyAutocall from IPython.config.configurable import Configurable from IPython.core.splitinput import split_user_input -from IPython.core.page import page +from IPython.core import page from IPython.utils.traitlets import List, Int, Any, Str, CBool, Bool, Instance import IPython.utils.io @@ -960,7 +960,7 @@ class HelpHandler(PrefilterHandler): #print 'line:<%r>' % line # dbg self.shell.magic_pinfo(line) else: - page(self.shell.usage, screen_lines=self.shell.usable_screen_length) + page.page(self.shell.usage, screen_lines=self.shell.usable_screen_length) return '' # Empty string is needed here! except: raise diff --git a/IPython/deathrow/GnuplotInteractive.py b/IPython/deathrow/GnuplotInteractive.py index ab837d7..b528428 100644 --- a/IPython/deathrow/GnuplotInteractive.py +++ b/IPython/deathrow/GnuplotInteractive.py @@ -17,7 +17,7 @@ __all__ = ['Gnuplot','gp','gp_new','plot','plot2','splot','replot', import IPython.GnuplotRuntime as GRun from IPython.utils.genutils import warn -from IPython.core.page import page +from IPython.core import page # Set global names for interactive use Gnuplot = GRun.Gnuplot diff --git a/IPython/frontend/qt/console/rich_ipython_widget.py b/IPython/frontend/qt/console/rich_ipython_widget.py index 9003af7..66a5e68 100644 --- a/IPython/frontend/qt/console/rich_ipython_widget.py +++ b/IPython/frontend/qt/console/rich_ipython_widget.py @@ -1,3 +1,5 @@ +import os + # System library imports from PyQt4 import QtCore, QtGui @@ -61,7 +63,7 @@ class RichIPythonWidget(IPythonWidget): """ payload = msg['content']['payload'] for item in payload: - if item['type'] == 'plot': + if item['source'] == 'IPython.zmq.pylab.backend_payload.add_plot_payload': if item['format'] == 'svg': svg = item['data'] try: @@ -78,6 +80,28 @@ class RichIPythonWidget(IPythonWidget): else: # Add other plot formats here! pass + elif item['source'] == 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic': + # TODO: I have implmented the logic for TextMate on the Mac. + # But, we need to allow payload handlers on the non-rich + # text IPython widget as well. Furthermore, we should probably + # move these handlers to separate methods. But, we need to + # be very careful to process the payload list in order. Thus, + # we will probably need a _handle_payload method of the + # base class that dispatches to the separate handler methods + # for each payload source. If a particular subclass doesn't + # have a handler for a payload source, it should at least + # print a nice message. + filename = item['filename'] + line_number = item['line_number'] + if line_number is None: + cmd = 'mate %s' % filename + else: + cmd = 'mate -l %s %s' % (line_number, filename) + os.system(cmd) + elif item['source'] == 'IPython.zmq.page.page': + # TODO: This is probably a good place to start, but Evan can + # add better paging capabilities. + self._append_plain_text(item['data']) else: # Add other payload types here! pass diff --git a/IPython/zmq/pylab/backend_payload.py b/IPython/zmq/pylab/backend_payload.py index 8ad92d5..74d4de6 100644 --- a/IPython/zmq/pylab/backend_payload.py +++ b/IPython/zmq/pylab/backend_payload.py @@ -19,5 +19,8 @@ def add_plot_payload(format, data, metadata={}): metadata : dict, optional [default empty] Allows for specification of additional information about the plot data. """ - payload = dict(type='plot', format=format, data=data, metadata=metadata) + payload = dict( + source='IPython.zmq.pylab.backend_payload.add_plot_payload', + format=format, data=data, metadata=metadata + ) InteractiveShell.instance().payload_manager.write_payload(payload) diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py index fb7b88f..6aaae9e 100644 --- a/IPython/zmq/zmqshell.py +++ b/IPython/zmq/zmqshell.py @@ -1,3 +1,5 @@ +import inspect +import re import sys from subprocess import Popen, PIPE @@ -5,8 +7,17 @@ from IPython.core.interactiveshell import ( InteractiveShell, InteractiveShellABC ) from IPython.core.displayhook import DisplayHook +from IPython.core.macro import Macro +from IPython.utils.path import get_py_filename +from IPython.utils.text import StringTypes from IPython.utils.traitlets import Instance, Type, Dict +from IPython.utils.warn import warn from IPython.zmq.session import extract_header +from IPython.core.payloadpage import install_payload_page + + +# Install the payload version of page. +install_payload_page() class ZMQDisplayHook(DisplayHook): @@ -66,6 +77,282 @@ class ZMQInteractiveShell(InteractiveShell): Term = IPython.utils.io.IOTerm() IPython.utils.io.Term = Term + def magic_edit(self,parameter_s='',last_call=['','']): + """Bring up an editor and execute the resulting code. + + Usage: + %edit [options] [args] + + %edit runs IPython's editor hook. The default version of this hook is + set to call the __IPYTHON__.rc.editor command. This is read from your + environment variable $EDITOR. If this isn't found, it will default to + vi under Linux/Unix and to notepad under Windows. See the end of this + docstring for how to change the editor hook. + + You can also set the value of this editor via the command line option + '-editor' or in your ipythonrc file. This is useful if you wish to use + specifically for IPython an editor different from your typical default + (and for Windows users who typically don't set environment variables). + + This command allows you to conveniently edit multi-line code right in + your IPython session. + + If called without arguments, %edit opens up an empty editor with a + temporary file and will execute the contents of this file when you + close it (don't forget to save it!). + + + Options: + + -n : open the editor at a specified line number. By default, + the IPython editor hook uses the unix syntax 'editor +N filename', but + you can configure this by providing your own modified hook if your + favorite editor supports line-number specifications with a different + syntax. + + -p: this will call the editor with the same data as the previous time + it was used, regardless of how long ago (in your current session) it + was. + + -r: use 'raw' input. This option only applies to input taken from the + user's history. By default, the 'processed' history is used, so that + magics are loaded in their transformed version to valid Python. If + this option is given, the raw input as typed as the command line is + used instead. When you exit the editor, it will be executed by + IPython's own processor. + + -x: do not execute the edited code immediately upon exit. This is + mainly useful if you are editing programs which need to be called with + command line arguments, which you can then do using %run. + + + Arguments: + + If arguments are given, the following possibilites exist: + + - The arguments are numbers or pairs of colon-separated numbers (like + 1 4:8 9). These are interpreted as lines of previous input to be + loaded into the editor. The syntax is the same of the %macro command. + + - If the argument doesn't start with a number, it is evaluated as a + variable and its contents loaded into the editor. You can thus edit + any string which contains python code (including the result of + previous edits). + + - If the argument is the name of an object (other than a string), + IPython will try to locate the file where it was defined and open the + editor at the point where it is defined. You can use `%edit function` + to load an editor exactly at the point where 'function' is defined, + edit it and have the file be executed automatically. + + If the object is a macro (see %macro for details), this opens up your + specified editor with a temporary file containing the macro's data. + Upon exit, the macro is reloaded with the contents of the file. + + Note: opening at an exact line is only supported under Unix, and some + editors (like kedit and gedit up to Gnome 2.8) do not understand the + '+NUMBER' parameter necessary for this feature. Good editors like + (X)Emacs, vi, jed, pico and joe all do. + + - If the argument is not found as a variable, IPython will look for a + file with that name (adding .py if necessary) and load it into the + editor. It will execute its contents with execfile() when you exit, + loading any code in the file into your interactive namespace. + + After executing your code, %edit will return as output the code you + typed in the editor (except when it was an existing file). This way + you can reload the code in further invocations of %edit as a variable, + via _ or Out[], where is the prompt number of + the output. + + Note that %edit is also available through the alias %ed. + + This is an example of creating a simple function inside the editor and + then modifying it. First, start up the editor: + + In [1]: ed + Editing... done. Executing edited code... + Out[1]: 'def foo():n print "foo() was defined in an editing session"n' + + We can then call the function foo(): + + In [2]: foo() + foo() was defined in an editing session + + Now we edit foo. IPython automatically loads the editor with the + (temporary) file where foo() was previously defined: + + In [3]: ed foo + Editing... done. Executing edited code... + + And if we call foo() again we get the modified version: + + In [4]: foo() + foo() has now been changed! + + Here is an example of how to edit a code snippet successive + times. First we call the editor: + + In [5]: ed + Editing... done. Executing edited code... + hello + Out[5]: "print 'hello'n" + + Now we call it again with the previous output (stored in _): + + In [6]: ed _ + Editing... done. Executing edited code... + hello world + Out[6]: "print 'hello world'n" + + Now we call it with the output #8 (stored in _8, also as Out[8]): + + In [7]: ed _8 + Editing... done. Executing edited code... + hello again + Out[7]: "print 'hello again'n" + + + Changing the default editor hook: + + If you wish to write your own editor hook, you can put it in a + configuration file which you load at startup time. The default hook + is defined in the IPython.core.hooks module, and you can use that as a + starting example for further modifications. That file also has + general instructions on how to set a new hook for use once you've + defined it.""" + + # FIXME: This function has become a convoluted mess. It needs a + # ground-up rewrite with clean, simple logic. + + def make_filename(arg): + "Make a filename from the given args" + try: + filename = get_py_filename(arg) + except IOError: + if args.endswith('.py'): + filename = arg + else: + filename = None + return filename + + # custom exceptions + class DataIsObject(Exception): pass + + opts,args = self.parse_options(parameter_s,'prn:') + # Set a few locals from the options for convenience: + opts_p = opts.has_key('p') + opts_r = opts.has_key('r') + + # Default line number value + lineno = opts.get('n',None) + + if opts_p: + args = '_%s' % last_call[0] + if not self.shell.user_ns.has_key(args): + args = last_call[1] + + # use last_call to remember the state of the previous call, but don't + # let it be clobbered by successive '-p' calls. + try: + last_call[0] = self.shell.displayhook.prompt_count + if not opts_p: + last_call[1] = parameter_s + except: + pass + + # by default this is done with temp files, except when the given + # arg is a filename + use_temp = 1 + + if re.match(r'\d',args): + # Mode where user specifies ranges of lines, like in %macro. + # This means that you can't edit files whose names begin with + # numbers this way. Tough. + ranges = args.split() + data = ''.join(self.extract_input_slices(ranges,opts_r)) + elif args.endswith('.py'): + filename = make_filename(args) + data = '' + use_temp = 0 + elif args: + try: + # Load the parameter given as a variable. If not a string, + # process it as an object instead (below) + + #print '*** args',args,'type',type(args) # dbg + data = eval(args,self.shell.user_ns) + if not type(data) in StringTypes: + raise DataIsObject + + except (NameError,SyntaxError): + # given argument is not a variable, try as a filename + filename = make_filename(args) + if filename is None: + warn("Argument given (%s) can't be found as a variable " + "or as a filename." % args) + return + + data = '' + use_temp = 0 + except DataIsObject: + + # macros have a special edit function + if isinstance(data,Macro): + self._edit_macro(args,data) + return + + # For objects, try to edit the file where they are defined + try: + filename = inspect.getabsfile(data) + if 'fakemodule' in filename.lower() and inspect.isclass(data): + # class created by %edit? Try to find source + # by looking for method definitions instead, the + # __module__ in those classes is FakeModule. + attrs = [getattr(data, aname) for aname in dir(data)] + for attr in attrs: + if not inspect.ismethod(attr): + continue + filename = inspect.getabsfile(attr) + if filename and 'fakemodule' not in filename.lower(): + # change the attribute to be the edit target instead + data = attr + break + + datafile = 1 + except TypeError: + filename = make_filename(args) + datafile = 1 + warn('Could not find file where `%s` is defined.\n' + 'Opening a file named `%s`' % (args,filename)) + # Now, make sure we can actually read the source (if it was in + # a temp file it's gone by now). + if datafile: + try: + if lineno is None: + lineno = inspect.getsourcelines(data)[1] + except IOError: + filename = make_filename(args) + if filename is None: + warn('The file `%s` where `%s` was defined cannot ' + 'be read.' % (filename,data)) + return + use_temp = 0 + else: + data = '' + + if use_temp: + filename = self.shell.mktempfile(data) + print 'IPython will make a temporary file named:',filename + + payload = { + 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic', + 'filename' : filename, + 'line_number' : lineno + } + self.payload_manager.write_payload(payload) + + InteractiveShellABC.register(ZMQInteractiveShell)