##// END OF EJS Templates
Merge branch 'issue1290'
Merge branch 'issue1290'

File last commit:

r4872:34c10438
r5914:f2084ede merge
Show More
ipy_vimserver.py
245 lines | 7.8 KiB | text/x-python | PythonLexer
""" 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!
"""
from IPython.core import ipapi
from IPython.core.error import TryNext
import socket, select
import os, threading, subprocess
import re
try:
ERRCONDS = select.POLLHUP|select.POLLERR
except AttributeError:
raise ImportError("Vim server not supported on this platform - select "
"missing necessary POLLHUP/POLLERR functionality")
SERVER = None
ip = ipapi.get()
# 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)
raise TryNext
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.define_magic('vim', vim)