diff options
Diffstat (limited to 'python/helpers/pydev/pydev_ipython_console_011.py')
-rw-r--r-- | python/helpers/pydev/pydev_ipython_console_011.py | 422 |
1 files changed, 364 insertions, 58 deletions
diff --git a/python/helpers/pydev/pydev_ipython_console_011.py b/python/helpers/pydev/pydev_ipython_console_011.py index 198d40f07ec7..54458e7c4af5 100644 --- a/python/helpers/pydev/pydev_ipython_console_011.py +++ b/python/helpers/pydev/pydev_ipython_console_011.py @@ -1,23 +1,295 @@ +# TODO that would make IPython integration better +# - show output other times then when enter was pressed +# - support proper exit to allow IPython to cleanup (e.g. temp files created with %edit) +# - support Ctrl-D (Ctrl-Z on Windows) +# - use IPython (numbered) prompts in PyDev +# - better integration of IPython and PyDev completions +# - some of the semantics on handling the code completion are not correct: +# eg: Start a line with % and then type c should give %cd as a completion by it doesn't +# however type %c and request completions and %cd is given as an option +# eg: Completing a magic when user typed it without the leading % causes the % to be inserted +# to the left of what should be the first colon. +"""Interface to TerminalInteractiveShell for PyDev Interactive Console frontend + for IPython 0.11 to 1.0+. +""" + +from __future__ import print_function + +import os +import codeop + +from IPython.core.error import UsageError +from IPython.core.inputsplitter import IPythonInputSplitter +from IPython.core.completer import IPCompleter +from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC +from IPython.core.usage import default_banner_parts +from IPython.utils.strdispatch import StrDispatch +import IPython.core.release as IPythonRelease try: from IPython.terminal.interactiveshell import TerminalInteractiveShell except ImportError: + # Versions of IPython [0.11,1.0) had an extra hierarchy level from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell -from IPython.utils import io -import sys -import codeop, re -original_stdout = sys.stdout -original_stderr = sys.stderr +from IPython.utils.traitlets import CBool, Unicode from IPython.core import release +from pydev_imports import xmlrpclib + +pydev_banner_parts = [ + '\n', + 'PyDev -- Python IDE for Eclipse\n', # TODO can we get a version number in here? + 'For help on using PyDev\'s Console see http://pydev.org/manual_adv_interactive_console.html\n', +] + +default_pydev_banner_parts = default_banner_parts + pydev_banner_parts + +default_pydev_banner = ''.join(default_pydev_banner_parts) + +def show_in_pager(self, strng): + """ Run a string through pager """ + # On PyDev we just output the string, there are scroll bars in the console + # to handle "paging". This is the same behaviour as when TERM==dump (see + # page.py) + print(strng) + +def create_editor_hook(pydev_host, pydev_client_port): + def call_editor(self, filename, line=0, wait=True): + """ Open an editor in PyDev """ + if line is None: + line = 0 + + # Make sure to send an absolution path because unlike most editor hooks + # we don't launch a process. This is more like what happens in the zmqshell + filename = os.path.abspath(filename) + + # Tell PyDev to open the editor + server = xmlrpclib.Server('http://%s:%s' % (pydev_host, pydev_client_port)) + server.IPythonEditor(filename, str(line)) + + if wait: + try: + raw_input("Press Enter when done editing:") + except NameError: + input("Press Enter when done editing:") + return call_editor + + + +class PyDevIPCompleter(IPCompleter): + + def __init__(self, *args, **kwargs): + """ Create a Completer that reuses the advanced completion support of PyDev + in addition to the completion support provided by IPython """ + IPCompleter.__init__(self, *args, **kwargs) + # Use PyDev for python matches, see getCompletions below + self.matchers.remove(self.python_matches) + +class PyDevTerminalInteractiveShell(TerminalInteractiveShell): + banner1 = Unicode(default_pydev_banner, config=True, + help="""The part of the banner to be printed before the profile""" + ) + + # TODO term_title: (can PyDev's title be changed???, see terminal.py for where to inject code, in particular set_term_title as used by %cd) + # for now, just disable term_title + term_title = CBool(False) + + # Note in version 0.11 there is no guard in the IPython code about displaying a + # warning, so with 0.11 you get: + # WARNING: Readline services not available or not loaded. + # WARNING: The auto-indent feature requires the readline library + # Disable readline, readline type code is all handled by PyDev (on Java side) + readline_use = CBool(False) + # autoindent has no meaning in PyDev (PyDev always handles that on the Java side), + # and attempting to enable it will print a warning in the absence of readline. + autoindent = CBool(False) + # Force console to not give warning about color scheme choice and default to NoColor. + # TODO It would be nice to enable colors in PyDev but: + # - The PyDev Console (Eclipse Console) does not support the full range of colors, so the + # effect isn't as nice anyway at the command line + # - If done, the color scheme should default to LightBG, but actually be dependent on + # any settings the user has (such as if a dark theme is in use, then Linux is probably + # a better theme). + colors_force = CBool(True) + colors = Unicode("NoColor") + + # In the PyDev Console, GUI control is done via hookable XML-RPC server + @staticmethod + def enable_gui(gui=None, app=None): + """Switch amongst GUI input hooks by name. + """ + # Deferred import + from pydev_ipython.inputhook import enable_gui as real_enable_gui + try: + return real_enable_gui(gui, app) + except ValueError as e: + raise UsageError("%s" % e) + + #------------------------------------------------------------------------- + # Things related to hooks + #------------------------------------------------------------------------- + + def init_hooks(self): + super(PyDevTerminalInteractiveShell, self).init_hooks() + self.set_hook('show_in_pager', show_in_pager) + + #------------------------------------------------------------------------- + # Things related to exceptions + #------------------------------------------------------------------------- + + def showtraceback(self, exc_tuple=None, filename=None, tb_offset=None, + exception_only=False): + # IPython does a lot of clever stuff with Exceptions. However mostly + # it is related to IPython running in a terminal instead of an IDE. + # (e.g. it prints out snippets of code around the stack trace) + # PyDev does a lot of clever stuff too, so leave exception handling + # with default print_exc that PyDev can parse and do its clever stuff + # with (e.g. it puts links back to the original source code) + import traceback;traceback.print_exc() + + + #------------------------------------------------------------------------- + # Things related to text completion + #------------------------------------------------------------------------- + + # The way to construct an IPCompleter changed in most versions, + # so we have a custom, per version implementation of the construction + + def _new_completer_011(self): + return PyDevIPCompleter(self, + self.user_ns, + self.user_global_ns, + self.readline_omit__names, + self.alias_manager.alias_table, + self.has_readline) + + + def _new_completer_012(self): + completer = PyDevIPCompleter(shell=self, + namespace=self.user_ns, + global_namespace=self.user_global_ns, + alias_table=self.alias_manager.alias_table, + use_readline=self.has_readline, + config=self.config, + ) + self.configurables.append(completer) + return completer + + + def _new_completer_100(self): + completer = PyDevIPCompleter(shell=self, + namespace=self.user_ns, + global_namespace=self.user_global_ns, + alias_table=self.alias_manager.alias_table, + use_readline=self.has_readline, + parent=self, + ) + self.configurables.append(completer) + return completer + + def _new_completer_200(self): + # As of writing this, IPython 2.0.0 is in dev mode so subject to change + completer = PyDevIPCompleter(shell=self, + namespace=self.user_ns, + global_namespace=self.user_global_ns, + use_readline=self.has_readline, + parent=self, + ) + self.configurables.append(completer) + return completer + + + + def init_completer(self): + """Initialize the completion machinery. + + This creates a completer that provides the completions that are + IPython specific. We use this to supplement PyDev's core code + completions. + """ + # PyDev uses its own completer and custom hooks so that it uses + # most completions from PyDev's core completer which provides + # extra information. + # See getCompletions for where the two sets of results are merged + + from IPython.core.completerlib import magic_run_completer, cd_completer + try: + from IPython.core.completerlib import reset_completer + except ImportError: + # reset_completer was added for rel-0.13 + reset_completer = None + + if IPythonRelease._version_major >= 2: + self.Completer = self._new_completer_200() + elif IPythonRelease._version_major >= 1: + self.Completer = self._new_completer_100() + elif IPythonRelease._version_minor >= 12: + self.Completer = self._new_completer_012() + else: + self.Completer = self._new_completer_011() + + # Add custom completers to the basic ones built into IPCompleter + sdisp = self.strdispatchers.get('complete_command', StrDispatch()) + self.strdispatchers['complete_command'] = sdisp + self.Completer.custom_completers = sdisp + + self.set_hook('complete_command', magic_run_completer, str_key='%run') + self.set_hook('complete_command', cd_completer, str_key='%cd') + if reset_completer: + self.set_hook('complete_command', reset_completer, str_key='%reset') + + # Only configure readline if we truly are using readline. IPython can + # do tab-completion over the network, in GUIs, etc, where readline + # itself may be absent + if self.has_readline: + self.set_readline_completer() + + + #------------------------------------------------------------------------- + # Things related to aliases + #------------------------------------------------------------------------- + + def init_alias(self): + # InteractiveShell defines alias's we want, but TerminalInteractiveShell defines + # ones we don't. So don't use super and instead go right to InteractiveShell + InteractiveShell.init_alias(self) + + #------------------------------------------------------------------------- + # Things related to exiting + #------------------------------------------------------------------------- + def ask_exit(self): + """ Ask the shell to exit. Can be overiden and used as a callback. """ + # TODO PyDev's console does not have support from the Python side to exit + # the console. If user forces the exit (with sys.exit()) then the console + # simply reports errors. e.g.: + # >>> import sys + # >>> sys.exit() + # Failed to create input stream: Connection refused + # >>> + # Console already exited with value: 0 while waiting for an answer. + # Error stream: + # Output stream: + # >>> + # + # Alternatively if you use the non-IPython shell this is what happens + # >>> exit() + # <type 'exceptions.SystemExit'>:None + # >>> + # <type 'exceptions.SystemExit'>:None + # >>> + # + super(PyDevTerminalInteractiveShell, self).ask_exit() + print('To exit the PyDev Console, terminate the console within Eclipse.') + + #------------------------------------------------------------------------- + # Things related to magics + #------------------------------------------------------------------------- + + def init_magics(self): + super(PyDevTerminalInteractiveShell, self).init_magics() + # TODO Any additional magics for PyDev? + +InteractiveShellABC.register(PyDevTerminalInteractiveShell) # @UndefinedVariable -#======================================================================================================================= -# _showtraceback -#======================================================================================================================= -def _showtraceback(*args, **kwargs): - import traceback;traceback.print_exc() - - - #======================================================================================================================= # PyDevFrontEnd #======================================================================================================================= @@ -25,49 +297,23 @@ class PyDevFrontEnd: version = release.__version__ - - def __init__(self, *args, **kwargs): - #Initialization based on: from IPython.testing.globalipapp import start_ipython - - self._curr_exec_line = 0 - # Store certain global objects that IPython modifies - _displayhook = sys.displayhook - _excepthook = sys.excepthook - - class ClosablePyDevTerminalInteractiveShell(TerminalInteractiveShell): - '''Override ask_exit() method for correct exit, exit(), etc. handling.''' - def ask_exit(self): - sys.exit() + def __init__(self, pydev_host, pydev_client_port, *args, **kwarg): # Create and initialize our IPython instance. - shell = ClosablePyDevTerminalInteractiveShell.instance() - - shell.showtraceback = _showtraceback - # IPython is ready, now clean up some global state... - - # Deactivate the various python system hooks added by ipython for - # interactive convenience so we don't confuse the doctest system - sys.displayhook = _displayhook - sys.excepthook = _excepthook - - # So that ipython magics and aliases can be doctested (they work by making - # a call into a global _ip object). Also make the top-level get_ipython - # now return this without recursively calling here again. - get_ipython = shell.get_ipython - try: - import __builtin__ - except: - import builtins as __builtin__ - __builtin__._ip = shell - __builtin__.get_ipython = get_ipython + self.ipython = PyDevTerminalInteractiveShell.instance() + + # Back channel to PyDev to open editors (in the future other + # info may go back this way. This is the same channel that is + # used to get stdin, see StdIn in pydev_console_utils) + self.ipython.set_hook('editor', create_editor_hook(pydev_host, pydev_client_port)) - # We want to print to stdout/err as usual. - io.stdout = original_stdout - io.stderr = original_stderr + # Display the IPython banner, this has version info and + # help info + self.ipython.show_banner() + self._curr_exec_line = 0 self._curr_exec_lines = [] - self.ipython = shell def update(self, globals, locals): @@ -95,7 +341,7 @@ class PyDevFrontEnd: def is_complete(self, string): #Based on IPython 0.10.1 - + if string in ('', '\n'): # Prefiltering, eg through ipython0, may return an empty # string although some operations have been accomplished. We @@ -109,20 +355,64 @@ class PyDevFrontEnd: # This should probably be done in a different place (like # maybe 'prefilter_input' method? For now, this works. clean_string = string.rstrip('\n') - if not clean_string.endswith('\\'): clean_string += '\n\n' - is_complete = codeop.compile_command(clean_string, - "<string>", "exec") + if not clean_string.endswith('\\'): + clean_string += '\n\n' + + is_complete = codeop.compile_command( + clean_string, + "<string>", + "exec" + ) except Exception: # XXX: Hack: return True so that the # code gets executed and the error captured. is_complete = True return is_complete - - + + + def getCompletions(self, text, act_tok): + # Get completions from IPython and from PyDev and merge the results + # IPython only gives context free list of completions, while PyDev + # gives detailed information about completions. + try: + TYPE_IPYTHON = '11' + TYPE_IPYTHON_MAGIC = '12' + _line, ipython_completions = self.complete(text) + + from _pydev_completer import Completer + completer = Completer(self.getNamespace(), None) + ret = completer.complete(act_tok) + append = ret.append + ip = self.ipython + pydev_completions = set([f[0] for f in ret]) + for ipython_completion in ipython_completions: + + #PyCharm was not expecting completions with '%'... + #Could be fixed in the backend, but it's probably better + #fixing it at PyCharm. + #if ipython_completion.startswith('%'): + # ipython_completion = ipython_completion[1:] + + if ipython_completion not in pydev_completions: + pydev_completions.add(ipython_completion) + inf = ip.object_inspect(ipython_completion) + if inf['type_name'] == 'Magic function': + pydev_type = TYPE_IPYTHON_MAGIC + else: + pydev_type = TYPE_IPYTHON + pydev_doc = inf['docstring'] + if pydev_doc is None: + pydev_doc = '' + append((ipython_completion, pydev_doc, '', pydev_type)) + return ret + except: + import traceback;traceback.print_exc() + return [] + + def getNamespace(self): return self.ipython.user_ns - def addExec(self, line): if self._curr_exec_lines: self._curr_exec_lines.append(line) @@ -155,5 +445,21 @@ class PyDevFrontEnd: return self.ipython.automagic def get_greeting_msg(self): - return 'PyDev console: using IPython %s' % self.version + return 'PyDev console: using IPython %s\n' % self.version + +# If we have succeeded in importing this module, then monkey patch inputhook +# in IPython to redirect to PyDev's version. This is essential to make +# %gui in 0.11 work (0.12+ fixes it by calling self.enable_gui, which is implemented +# above, instead of inputhook.enable_gui). +# See testGui (test_pydev_ipython_011.TestRunningCode) which fails on 0.11 without +# this patch +import IPython.lib.inputhook +import pydev_ipython.inputhook +IPython.lib.inputhook.enable_gui = pydev_ipython.inputhook.enable_gui +# In addition to enable_gui, make all publics in pydev_ipython.inputhook replace +# the IPython versions. This enables the examples in IPython's examples/lib/gui-* +# to operate properly because those examples don't use %gui magic and instead +# rely on using the inputhooks directly. +for name in pydev_ipython.inputhook.__all__: + setattr(IPython.lib.inputhook, name, getattr(pydev_ipython.inputhook, name)) |