Created
June 26, 2017 01:40
-
-
Save wilywampa/c72d8fe4ea697cb224a6f648e01936f3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import greenlet | |
import logging | |
import neovim | |
import os | |
from functools import partial | |
from jupyter_client import KernelManager | |
from jupyter_client.consoleapp import JupyterConsoleApp | |
from jupyter_client.threaded import ThreadedKernelClient | |
from jupyter_core.application import JupyterApp | |
logger = logging.getLogger(__name__) | |
error, debug, info, warn = ( | |
logger.error, logger.debug, logger.info, logger.warn,) | |
if 'NVIM_IPY_DEBUG_FILE' in os.environ: | |
logfile = os.environ['NVIM_IPY_DEBUG_FILE'].strip() | |
logger.addHandler(logging.FileHandler(logfile, 'w')) | |
logger.level = logging.DEBUG | |
class RedirectingKernelManager(KernelManager): | |
def _launch_kernel(self, cmd, **b): | |
# stdout is used to communicate with nvim, redirect it somewhere else | |
self._null = open("/dev/null", "wb", 0) | |
b['stdout'] = self._null.fileno() | |
b['stderr'] = self._null.fileno() | |
return super(RedirectingKernelManager, self)._launch_kernel(cmd, **b) | |
class JupyterVimApp(JupyterApp, JupyterConsoleApp): | |
# don't use blocking client; we override call_handlers below | |
kernel_client_class = ThreadedKernelClient | |
kernel_manager_class = RedirectingKernelManager | |
aliases = JupyterConsoleApp.aliases # this the way? | |
flags = JupyterConsoleApp.flags | |
def init_kernel_client(self): | |
# TODO: cleanup this (by subclassing kernel_clint or something) | |
if self.kernel_manager is not None: | |
self.kernel_client = self.kernel_manager.client() | |
else: | |
self.kernel_client = self.kernel_client_class( | |
session=self.session, | |
ip=self.ip, | |
transport=self.transport, | |
shell_port=self.shell_port, | |
iopub_port=self.iopub_port, | |
hb_port=self.hb_port, | |
connection_file=self.connection_file, | |
parent=self, | |
) | |
self.kernel_client.shell_channel.call_handlers = self.target.on_shell_msg | |
self.kernel_client.hb_channel.call_handlers = self.target.on_hb_msg | |
self.kernel_client.start_channels() | |
def initialize(self, target, argv): | |
self.target = target | |
super(JupyterVimApp, self).initialize(argv) | |
JupyterConsoleApp.initialize(self, argv) | |
class Async(object): | |
"""Wrapper that defers all method calls on a plugin object to the event | |
loop, given that the object has vim attribute""" | |
def __init__(self, wraps): | |
self.wraps = wraps | |
def __getattr__(self, name): | |
return partial(self.wraps.vim.async_call, getattr(self.wraps, name)) | |
@neovim.plugin | |
@neovim.encoding(True) | |
class IPythonPlugin(object): | |
def __init__(self, vim): | |
self.vim = vim | |
self.has_connection = False | |
self.pending_shell_msgs = {} | |
def connect(self, argv): | |
self.ip_app = JupyterVimApp.instance() | |
self.ip_app.initialize(Async(self), argv) | |
self.ip_app.start() | |
self.kc = self.ip_app.kernel_client | |
self.km = self.ip_app.kernel_manager | |
self.has_connection = True | |
def handle(self, msg_id, handler): | |
self.pending_shell_msgs[msg_id] = handler | |
def waitfor(self, msg_id, retval=None): | |
# FIXME: add some kind of timeout | |
gr = greenlet.getcurrent() | |
self.handle(msg_id, gr) | |
return gr.parent.switch(retval) | |
def ignore(self, msg_id): | |
self.handle(msg_id, None) | |
@neovim.function("IPyConnect", sync=True) | |
def ipy_connect(self, args): | |
# 'connect' waits for kernelinfo, and so must be async | |
Async(self).connect(args) | |
def on_shell_msg(self, m): | |
self.last_msg = m | |
debug('shell %s: %r', m['msg_type'], m['content']) | |
msg_id = m['parent_header']['msg_id'] | |
try: | |
handler = self.pending_shell_msgs.pop(msg_id) | |
except KeyError: | |
debug('unexpected shell msg: %r', m) | |
return | |
if isinstance(handler, greenlet.greenlet): | |
handler.parent = greenlet.getcurrent() | |
handler.switch(m) | |
elif handler is not None: | |
handler(m) | |
# this gets called when heartbeat is lost | |
def on_hb_msg(self, time_since): | |
self.has_connection = False |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment