Skip to content

Instantly share code, notes, and snippets.

@wilywampa
Created June 26, 2017 01:40
Show Gist options
  • Save wilywampa/c72d8fe4ea697cb224a6f648e01936f3 to your computer and use it in GitHub Desktop.
Save wilywampa/c72d8fe4ea697cb224a6f648e01936f3 to your computer and use it in GitHub Desktop.
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