You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
505 lines
20 KiB
Python
505 lines
20 KiB
Python
# 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 sys
|
|
import codeop
|
|
import traceback
|
|
|
|
from IPython.core.error import UsageError
|
|
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
|
|
from IPython.terminal.interactiveshell import TerminalInteractiveShell
|
|
|
|
try:
|
|
from traitlets import CBool, Unicode
|
|
except ImportError:
|
|
from IPython.utils.traitlets import CBool, Unicode
|
|
from IPython.core import release
|
|
|
|
from _pydev_bundle.pydev_imports import xmlrpclib
|
|
|
|
default_pydev_banner_parts = default_banner_parts
|
|
|
|
default_pydev_banner = "".join(default_pydev_banner_parts)
|
|
|
|
|
|
def show_in_pager(self, strng, *args, **kwargs):
|
|
"""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)
|
|
# for compatibility with mime-bundle form:
|
|
if isinstance(strng, dict):
|
|
strng = strng.get("text/plain", strng)
|
|
print(strng)
|
|
|
|
|
|
def create_editor_hook(pydev_host, pydev_client_port):
|
|
def call_editor(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)
|
|
|
|
# import sys
|
|
# sys.__stderr__.write('Calling editor at: %s:%s\n' % (pydev_host, pydev_client_port))
|
|
|
|
# Tell PyDev to open the editor
|
|
server = xmlrpclib.Server("http://%s:%s" % (pydev_host, pydev_client_port))
|
|
server.IPythonEditor(filename, str(line))
|
|
|
|
if wait:
|
|
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
|
|
if self.python_matches in self.matchers:
|
|
# `self.python_matches` matches attributes or global python names
|
|
self.matchers.remove(self.python_matches)
|
|
|
|
|
|
class PyDevIPCompleter6(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)
|
|
|
|
@property
|
|
def matchers(self):
|
|
"""All active matcher routines for completion"""
|
|
# To remove python_matches we now have to override it as it's now a property in the superclass.
|
|
return [
|
|
self.file_matches,
|
|
self.magic_matches,
|
|
self.python_func_kw_matches,
|
|
self.dict_key_matches,
|
|
]
|
|
|
|
@matchers.setter
|
|
def matchers(self, value):
|
|
# To stop the init in IPCompleter raising an AttributeError we now have to specify a setter as it's now a property in the superclass.
|
|
return
|
|
|
|
|
|
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")
|
|
# Since IPython 5 the terminal interface is not compatible with Emacs `inferior-shell` and
|
|
# the `simple_prompt` flag is needed
|
|
simple_prompt = CBool(True)
|
|
|
|
# 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_history(self):
|
|
# Disable history so that we don't have an additional thread for that
|
|
# (and we don't use the history anyways).
|
|
self.config.HistoryManager.enabled = False
|
|
super(PyDevTerminalInteractiveShell, self).init_history()
|
|
|
|
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, *args, **kwargs):
|
|
# 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)
|
|
try:
|
|
if exc_tuple is None:
|
|
etype, value, tb = sys.exc_info()
|
|
else:
|
|
etype, value, tb = exc_tuple
|
|
except ValueError:
|
|
return
|
|
|
|
if tb is not None:
|
|
traceback.print_exception(etype, value, tb)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# 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_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,
|
|
)
|
|
return completer
|
|
|
|
def _new_completer_234(self):
|
|
# correct for IPython versions 2.x, 3.x, 4.x
|
|
completer = PyDevIPCompleter(
|
|
shell=self,
|
|
namespace=self.user_ns,
|
|
global_namespace=self.user_global_ns,
|
|
use_readline=self.has_readline,
|
|
parent=self,
|
|
)
|
|
return completer
|
|
|
|
def _new_completer_500(self):
|
|
completer = PyDevIPCompleter(
|
|
shell=self, namespace=self.user_ns, global_namespace=self.user_global_ns, use_readline=False, parent=self
|
|
)
|
|
return completer
|
|
|
|
def _new_completer_600(self):
|
|
completer = PyDevIPCompleter6(
|
|
shell=self, namespace=self.user_ns, global_namespace=self.user_global_ns, use_readline=False, parent=self
|
|
)
|
|
return completer
|
|
|
|
def add_completer_hooks(self):
|
|
from IPython.core.completerlib import module_completer, 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
|
|
self.configurables.append(self.Completer)
|
|
|
|
# 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", module_completer, str_key="import")
|
|
self.set_hook("complete_command", module_completer, str_key="from")
|
|
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")
|
|
|
|
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
|
|
|
|
if IPythonRelease._version_major >= 6:
|
|
self.Completer = self._new_completer_600()
|
|
elif IPythonRelease._version_major >= 5:
|
|
self.Completer = self._new_completer_500()
|
|
elif IPythonRelease._version_major >= 2:
|
|
self.Completer = self._new_completer_234()
|
|
elif IPythonRelease._version_major >= 1:
|
|
self.Completer = self._new_completer_100()
|
|
|
|
if hasattr(self.Completer, "use_jedi"):
|
|
self.Completer.use_jedi = False
|
|
|
|
self.add_completer_hooks()
|
|
|
|
if IPythonRelease._version_major <= 3:
|
|
# 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 IDE.")
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Things related to magics
|
|
# -------------------------------------------------------------------------
|
|
|
|
def init_magics(self):
|
|
super(PyDevTerminalInteractiveShell, self).init_magics()
|
|
# TODO Any additional magics for PyDev?
|
|
|
|
|
|
InteractiveShellABC.register(PyDevTerminalInteractiveShell) # @UndefinedVariable
|
|
|
|
|
|
# =======================================================================================================================
|
|
# _PyDevFrontEnd
|
|
# =======================================================================================================================
|
|
class _PyDevFrontEnd:
|
|
version = release.__version__
|
|
|
|
def __init__(self):
|
|
# Create and initialize our IPython instance.
|
|
if hasattr(PyDevTerminalInteractiveShell, "_instance") and PyDevTerminalInteractiveShell._instance is not None:
|
|
self.ipython = PyDevTerminalInteractiveShell._instance
|
|
else:
|
|
self.ipython = PyDevTerminalInteractiveShell.instance()
|
|
|
|
self._curr_exec_line = 0
|
|
self._curr_exec_lines = []
|
|
|
|
def show_banner(self):
|
|
self.ipython.show_banner()
|
|
|
|
def update(self, globals, locals):
|
|
ns = self.ipython.user_ns
|
|
|
|
for key, value in list(ns.items()):
|
|
if key not in locals:
|
|
locals[key] = value
|
|
|
|
self.ipython.user_global_ns.clear()
|
|
self.ipython.user_global_ns.update(globals)
|
|
self.ipython.user_ns = locals
|
|
|
|
if hasattr(self.ipython, "history_manager") and hasattr(self.ipython.history_manager, "save_thread"):
|
|
self.ipython.history_manager.save_thread.pydev_do_not_trace = True # don't trace ipython history saving thread
|
|
|
|
def complete(self, string):
|
|
try:
|
|
if string:
|
|
return self.ipython.complete(None, line=string, cursor_pos=string.__len__())
|
|
else:
|
|
return self.ipython.complete(string, string, 0)
|
|
except:
|
|
# Silence completer exceptions
|
|
pass
|
|
|
|
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
|
|
# thus want to consider an empty string as a complete
|
|
# statement.
|
|
return True
|
|
else:
|
|
try:
|
|
# Add line returns here, to make sure that the statement is
|
|
# complete (except if '\' was used).
|
|
# 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")
|
|
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_bundle._pydev_completer import Completer
|
|
|
|
completer = Completer(self.get_namespace(), 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 get_namespace(self):
|
|
return self.ipython.user_ns
|
|
|
|
def clear_buffer(self):
|
|
del self._curr_exec_lines[:]
|
|
|
|
def add_exec(self, line):
|
|
if self._curr_exec_lines:
|
|
self._curr_exec_lines.append(line)
|
|
|
|
buf = "\n".join(self._curr_exec_lines)
|
|
|
|
if self.is_complete(buf):
|
|
self._curr_exec_line += 1
|
|
self.ipython.run_cell(buf)
|
|
del self._curr_exec_lines[:]
|
|
return False # execute complete (no more)
|
|
|
|
return True # needs more
|
|
else:
|
|
if not self.is_complete(line):
|
|
# Did not execute
|
|
self._curr_exec_lines.append(line)
|
|
return True # needs more
|
|
else:
|
|
self._curr_exec_line += 1
|
|
self.ipython.run_cell(line, store_history=True)
|
|
# hist = self.ipython.history_manager.output_hist_reprs
|
|
# rep = hist.get(self._curr_exec_line, None)
|
|
# if rep is not None:
|
|
# print(rep)
|
|
return False # execute complete (no more)
|
|
|
|
def is_automagic(self):
|
|
return self.ipython.automagic
|
|
|
|
def get_greeting_msg(self):
|
|
return "PyDev console: using IPython %s\n" % self.version
|
|
|
|
|
|
class _PyDevFrontEndContainer:
|
|
_instance = None
|
|
_last_host_port = None
|
|
|
|
|
|
def get_pydev_frontend(pydev_host, pydev_client_port):
|
|
if _PyDevFrontEndContainer._instance is None:
|
|
_PyDevFrontEndContainer._instance = _PyDevFrontEnd()
|
|
|
|
if _PyDevFrontEndContainer._last_host_port != (pydev_host, pydev_client_port):
|
|
_PyDevFrontEndContainer._last_host_port = pydev_host, pydev_client_port
|
|
|
|
# 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)
|
|
_PyDevFrontEndContainer._instance.ipython.hooks["editor"] = create_editor_hook(pydev_host, pydev_client_port)
|
|
|
|
# Note: setting the callback directly because setting it with set_hook would actually create a chain instead
|
|
# of ovewriting at each new call).
|
|
# _PyDevFrontEndContainer._instance.ipython.set_hook('editor', create_editor_hook(pydev_host, pydev_client_port))
|
|
|
|
return _PyDevFrontEndContainer._instance
|