ipy_vimserver.py
239 lines
| 7.6 KiB
| text/x-python
|
PythonLexer
Ville M. Vainio
|
r1076 | """ Integration with gvim, by Erich Heine | ||
Provides a %vim magic command, and reuses the same vim session. Uses | ||||
unix domain sockets for communication between vim and IPython. ipy.vim is | ||||
available in doc/examples of the IPython distribution. | ||||
Slightly touched up email announcement (and description how to use it) by | ||||
Erich Heine is here: | ||||
Ive recently been playing with ipython, and like it quite a bit. I did | ||||
however discover a bit of frustration, namely with editor interaction. | ||||
I am a gvim user, and using the command edit on a new file causes | ||||
ipython to try and run that file as soon as the text editor opens | ||||
up. The -x command of course fixes this, but its still a bit annoying, | ||||
switching windows to do a run file, then back to the text | ||||
editor. Being a heavy tab user in gvim, another annoyance is not being | ||||
able to specify weather a new tab is how I choose to open the file. | ||||
Not being one to shirk my open source duties (and seeing this as a | ||||
good excuse to poke around ipython internals), Ive created a script | ||||
for having gvim and ipython work very nicely together. Ive attached | ||||
both to this email (hoping of course that the mailing list allows such | ||||
things). | ||||
There are 2 files: | ||||
ipy_vimserver.py -- this file contains the ipython stuff | ||||
ipy.vim -- this file contains the gvim stuff | ||||
In combination they allow for a few functionalities: | ||||
#1. the vim magic command. This is a fancy wrapper around the edit | ||||
magic, that allows for a new option, -t, which opens the text in a new | ||||
gvim tab. Otherwise it works the same as edit -x. (it internally | ||||
calls edit -x). This magic command also juggles vim server management, | ||||
so when it is called when there is not a gvim running, it creates a | ||||
new gvim instance, named after the ipython session name. Once such a | ||||
gvim instance is running, it will be used for subsequent uses of the | ||||
vim command. | ||||
#2. ipython - gvim interaction. Once a file has been opened with the | ||||
vim magic (and a session set up, see below), pressing the F5 key in | ||||
vim will cause the calling ipython instance to execute run | ||||
filename.py. (if you typo like I do, this is very useful) | ||||
#3. ipython server - this is a thread wich listens on a unix domain | ||||
socket, and runs commands sent to that socket. | ||||
Note, this only works on POSIX systems, that allow for AF_UNIX type | ||||
sockets. It has only been tested on linux (a fairly recent debian | ||||
testing distro). | ||||
To install it put, the ipserver.py in your favorite locaion for | ||||
sourcing ipython scripts. I put the ipy.vim in | ||||
~/.vim/after/ftplugin/python/. | ||||
To use (this can be scripted im sure, but i usually have 2 or 3 | ||||
ipythons and corresponding gvims open): | ||||
import ipy_vimserver | ||||
ipy_vimserver.setup('sessionname') | ||||
(Editors note - you can probably add these to your ipy_user_conf.py) | ||||
Then use ipython as you normally would, until you need to edit | ||||
something. Instead of edit, use the vim magic. Thats it! | ||||
""" | ||||
Brian Granger
|
r2027 | from IPython.core import ipapi | ||
Ville M. Vainio
|
r1076 | import socket, select | ||
import os, threading, subprocess | ||||
import re | ||||
ERRCONDS = select.POLLHUP|select.POLLERR | ||||
SERVER = None | ||||
Brian Granger
|
r2027 | ip = ipapi.get() | ||
Ville M. Vainio
|
r1076 | |||
# this listens to a unix domain socket in a separate thread, so that comms | ||||
# between a vim instance and ipython can happen in a fun and productive way | ||||
class IpyServer(threading.Thread): | ||||
def __init__(self, sname): | ||||
super(IpyServer, self).__init__() | ||||
self.keep_running = True | ||||
self.__sname = sname | ||||
self.socket = socket.socket(socket.AF_UNIX) | ||||
self.poller = select.poll() | ||||
self.current_conns = dict() | ||||
self.setDaemon(True) | ||||
def listen(self): | ||||
self.socket.bind(self.__sname) | ||||
self.socket.listen(1) | ||||
def __handle_error(self, socket): | ||||
if socket == self.socket.fileno(): | ||||
self.keep_running = False | ||||
for a in self.current_conns.values(): | ||||
a.close() | ||||
return False | ||||
else: | ||||
y = self.current_conns[socket] | ||||
del self.current_conns[socket] | ||||
y.close() | ||||
self.poller.unregister(socket) | ||||
def serve_me(self): | ||||
self.listen() | ||||
self.poller.register(self.socket,select.POLLIN|ERRCONDS) | ||||
while self.keep_running: | ||||
try: | ||||
avail = self.poller.poll(1) | ||||
except: | ||||
continue | ||||
if not avail: continue | ||||
for sock, conds in avail: | ||||
if conds & (ERRCONDS): | ||||
if self.__handle_error(sock): continue | ||||
else: break | ||||
if sock == self.socket.fileno(): | ||||
y = self.socket.accept()[0] | ||||
self.poller.register(y, select.POLLIN|ERRCONDS) | ||||
self.current_conns[y.fileno()] = y | ||||
else: y = self.current_conns.get(sock) | ||||
self.handle_request(y) | ||||
os.remove(self.__sname) | ||||
run = serve_me | ||||
def stop(self): | ||||
self.keep_running = False | ||||
def handle_request(self,sock): | ||||
sock.settimeout(1) | ||||
while self.keep_running: | ||||
try: | ||||
x = sock.recv(4096) | ||||
except socket.timeout: | ||||
pass | ||||
else: | ||||
break | ||||
self.do_it(x) | ||||
def do_it(self, data): | ||||
data = data.split('\n') | ||||
cmds = list() | ||||
for line in data: | ||||
cmds.append(line) | ||||
ip.runlines(cmds) | ||||
# try to help ensure that the unix domain socket is cleaned up proper | ||||
def shutdown_server(self): | ||||
if SERVER: | ||||
SERVER.stop() | ||||
SERVER.join(3) | ||||
Brian Granger
|
r2027 | raise ipapi.TryNext | ||
Ville M. Vainio
|
r1076 | |||
ip.set_hook('shutdown_hook', shutdown_server, 10) | ||||
# this fun function exists to make setup easier for all, and makes the | ||||
# vimhook function ready for instance specific communication | ||||
def setup(sessionname='',socketdir=os.path.expanduser('~/.ipython/')): | ||||
global SERVER | ||||
if sessionname: | ||||
session = sessionname | ||||
elif os.environ.get('IPY_SESSION'): | ||||
session = os.environ.get('IPY_SESSION') | ||||
else: | ||||
session = 'IPYS' | ||||
vimhook.vimserver=session | ||||
vimhook.ipyserver = os.path.join(socketdir, session) | ||||
if not SERVER: | ||||
SERVER = IpyServer(vimhook.ipyserver) | ||||
SERVER.start() | ||||
# calls gvim, with all ops happening on the correct gvim instance for this | ||||
# ipython instance. it then calls edit -x (since gvim will return right away) | ||||
# things of note: it sets up a special environment, so that the ipy.vim script | ||||
# can connect back to the ipython instance and do fun things, like run the file | ||||
def vimhook(self, fname, line): | ||||
env = os.environ.copy() | ||||
vserver = vimhook.vimserver.upper() | ||||
check = subprocess.Popen('gvim --serverlist', stdout = subprocess.PIPE, | ||||
shell=True) | ||||
check.wait() | ||||
cval = [l for l in check.stdout.readlines() if vserver in l] | ||||
if cval: | ||||
vimargs = '--remote%s' % (vimhook.extras,) | ||||
else: | ||||
vimargs = '' | ||||
vimhook.extras = '' | ||||
env['IPY_SESSION'] = vimhook.vimserver | ||||
env['IPY_SERVER'] = vimhook.ipyserver | ||||
if line is None: line = '' | ||||
else: line = '+' + line | ||||
vim_cmd = 'gvim --servername %s %s %s %s' % (vimhook.vimserver, vimargs, | ||||
line, fname) | ||||
subprocess.call(vim_cmd, env=env, shell=True) | ||||
#default values to keep it sane... | ||||
vimhook.vimserver = '' | ||||
vimhook.ipyserver = '' | ||||
ip.set_hook('editor',vimhook) | ||||
# this is set up so more vim specific commands can be added, instead of just | ||||
# the current -t. all thats required is a compiled regex, a call to do_arg(pat) | ||||
# and the logic to deal with the new feature | ||||
newtab = re.compile(r'-t(?:\s|$)') | ||||
def vim(self, argstr): | ||||
def do_arg(pat, rarg): | ||||
x = len(pat.findall(argstr)) | ||||
if x: | ||||
a = pat.sub('',argstr) | ||||
return rarg, a | ||||
else: return '', argstr | ||||
t, argstr = do_arg(newtab, '-tab') | ||||
vimhook.extras = t | ||||
argstr = 'edit -x ' + argstr | ||||
ip.magic(argstr) | ||||
ip.expose_magic('vim', vim) | ||||