diff --git a/IPython/Magic.py b/IPython/Magic.py index e45adf9..c123baf 100644 --- a/IPython/Magic.py +++ b/IPython/Magic.py @@ -3210,6 +3210,61 @@ Defaulting color scheme to 'NoColor'""" page(self.shell.pycolorize(cont), screen_lines=self.shell.rc.screen_length) + def _rerun_pasted(self): + """ Rerun a previously pasted command. + """ + b = self.user_ns.get('pasted_block', None) + if b is None: + raise UsageError('No previous pasted block available') + print "Re-executing '%s...' (%d chars)"% (b.split('\n',1)[0], len(b)) + exec b in self.user_ns + + def _get_pasted_lines(self, sentinel): + """ Yield pasted lines until the user enters the given sentinel value. + """ + from IPython import iplib + print "Pasting code; enter '%s' alone on the line to stop." % sentinel + while True: + l = iplib.raw_input_original(':') + if l == sentinel: + return + else: + yield l + + def _strip_pasted_lines_for_code(self, raw_lines): + """ Strip non-code parts of a sequence of lines to return a block of + code. + """ + # Regular expressions that declare text we strip from the input: + strip_re = [r'^\s*In \[\d+\]:', # IPython input prompt + r'^\s*(\s?>)+', # Python input prompt + r'^\s*\.{3,}', # Continuation prompts + r'^\++', + ] + + strip_from_start = map(re.compile,strip_re) + + lines = [] + for l in raw_lines: + for pat in strip_from_start: + l = pat.sub('',l) + lines.append(l) + + block = "\n".join(lines) + '\n' + #print "block:\n",block + return block + + def _execute_block(self, block, par): + """ Execute a block, or store it in a variable, per the user's request. + """ + if not par: + b = textwrap.dedent(block) + self.user_ns['pasted_block'] = b + exec b in self.user_ns + else: + self.user_ns[par] = SList(block.splitlines()) + print "Block assigned to '%s'" % par + def magic_cpaste(self, parameter_s=''): """Allows you to paste & execute a pre-formatted code block from clipboard. @@ -3239,46 +3294,47 @@ Defaulting color scheme to 'NoColor'""" opts,args = self.parse_options(parameter_s,'rs:',mode='string') par = args.strip() if opts.has_key('r'): - b = self.user_ns.get('pasted_block', None) - if b is None: - raise UsageError('No previous pasted block available') - print "Re-executing '%s...' (%d chars)"% (b.split('\n',1)[0], len(b)) - exec b in self.user_ns + self._rerun_pasted() return sentinel = opts.get('s','--') - # Regular expressions that declare text we strip from the input: - strip_re = [r'^\s*In \[\d+\]:', # IPython input prompt - r'^\s*(\s?>)+', # Python input prompt - r'^\s*\.{3,}', # Continuation prompts - r'^\++', - ] + block = self._strip_pasted_lines_for_code( + self._get_pasted_lines(sentinel)) - strip_from_start = map(re.compile,strip_re) + self._execute_block(block, par) + + def magic_paste(self, parameter_s=''): + """Allows you to paste & execute a pre-formatted code block from clipboard. - from IPython import iplib - lines = [] - print "Pasting code; enter '%s' alone on the line to stop." % sentinel - while 1: - l = iplib.raw_input_original(':') - if l ==sentinel: - break - - for pat in strip_from_start: - l = pat.sub('',l) - lines.append(l) - - block = "\n".join(lines) + '\n' - #print "block:\n",block - if not par: - b = textwrap.dedent(block) - self.user_ns['pasted_block'] = b - exec b in self.user_ns - else: - self.user_ns[par] = SList(block.splitlines()) - print "Block assigned to '%s'" % par - + The text is pulled directly from the clipboard without user + intervention. + + The block is dedented prior to execution to enable execution of method + definitions. '>' and '+' characters at the beginning of a line are + ignored, to allow pasting directly from e-mails, diff files and + doctests (the '...' continuation prompt is also stripped). The + executed block is also assigned to variable named 'pasted_block' for + later editing with '%edit pasted_block'. + + You can also pass a variable name as an argument, e.g. '%paste foo'. + This assigns the pasted block to variable 'foo' as string, without + dedenting or executing it (preceding >>> and + is still stripped) + + '%paste -r' re-executes the block previously entered by cpaste. + + IPython statements (magics, shell escapes) are not supported (yet). + """ + opts,args = self.parse_options(parameter_s,'r:',mode='string') + par = args.strip() + if opts.has_key('r'): + self._rerun_pasted() + return + + text = self.shell.hooks.clipboard_get() + block = self._strip_pasted_lines_for_code(text.splitlines()) + self._execute_block(block, par) + def magic_quickref(self,arg): """ Show a quick reference sheet """ import IPython.usage diff --git a/IPython/clipboard.py b/IPython/clipboard.py new file mode 100644 index 0000000..6607f6e --- /dev/null +++ b/IPython/clipboard.py @@ -0,0 +1,56 @@ +""" Utilities for accessing the platform's clipboard. +""" + +import subprocess +import sys + +from IPython.ipapi import TryNext + + +def win32_clipboard_get(): + """ Get the current clipboard's text on Windows. + + Requires Mark Hammond's pywin32 extensions. + """ + try: + import win32clipboard + except ImportError: + message = ("Getting text from the clipboard requires the pywin32 " + "extensions: http://sourceforge.net/projects/pywin32/") + raise TryNext(message) + win32clipboard.OpenClipboard() + text = win32clipboard.GetClipboardData(win32clipboard.CF_TEXT) + # FIXME: convert \r\n to \n? + win32clipboard.CloseClipboard() + return text + +def osx_clipboard_get(): + """ Get the clipboard's text on OS X. + """ + p = subprocess.Popen(['pbpaste', '-Prefer', 'ascii'], + stdout=subprocess.PIPE) + text, stderr = p.communicate() + # Text comes in with old Mac \r line endings. Change them to \n. + text = text.replace('\r', '\n') + return text + +def tkinter_clipboard_get(): + """ Get the clipboard's text using Tkinter. + + This is the default on systems that are not Windows or OS X. It may + interfere with other UI toolkits and should be replaced with an + implementation that uses that toolkit. + """ + try: + import Tkinter + except ImportError: + message = ("Getting text from the clipboard on this platform " + "requires Tkinter.") + raise TryNext(message) + root = Tkinter.Tk() + root.withdraw() + text = root.clipboard_get() + root.destroy() + return text + + diff --git a/IPython/hooks.py b/IPython/hooks.py index fdbea8e..e8245ba 100644 --- a/IPython/hooks.py +++ b/IPython/hooks.py @@ -49,6 +49,7 @@ __license__ = Release.license __version__ = Release.version import os,bisect +import sys from genutils import Term,shell from pprint import PrettyPrinter @@ -58,7 +59,8 @@ from pprint import PrettyPrinter __all__ = ['editor', 'fix_error_editor', 'synchronize_with_editor', 'result_display', 'input_prefilter', 'shutdown_hook', 'late_startup_hook', 'generate_prompt', 'generate_output_prompt','shell_hook', - 'show_in_pager','pre_prompt_hook', 'pre_runcode_hook'] + 'show_in_pager','pre_prompt_hook', 'pre_runcode_hook', + 'clipboard_get'] # vds: << pformat = PrettyPrinter().pformat @@ -248,5 +250,20 @@ def pre_prompt_hook(self): def pre_runcode_hook(self): """ Executed before running the (prefiltered) code in IPython """ return None - +def clipboard_get(self): + """ Get text from the clipboard. + """ + from IPython.clipboard import (osx_clipboard_get, tkinter_clipboard_get, + win32_clipboard_get) + if sys.platform == 'win32': + chain = [win32_clipboard_get, tkinter_clipboard_get] + elif sys.platform == 'darwin': + chain = [osx_clipboard_get, tkinter_clipboard_get] + else: + chain = [tkinter_clipboard_get] + dispatcher = CommandChainDispatcher() + for func in chain: + dispatcher.add(func) + text = dispatcher() + return text