From b99e42a72e0ad45faa1c0e9f1d19028c8538b031 2008-03-16 14:43:35 From: Ville M. Vainio Date: 2008-03-16 14:43:35 Subject: [PATCH] add ipy_vimserver.py extension, and ipy.vim to doc/examples. Both contributed by Erich Heine --- diff --git a/IPython/Extensions/ipy_vimserver.py b/IPython/Extensions/ipy_vimserver.py new file mode 100644 index 0000000..f23b712 --- /dev/null +++ b/IPython/Extensions/ipy_vimserver.py @@ -0,0 +1,239 @@ +""" 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! + +""" + +import IPython.ipapi +#import ipythonhooks +import socket, select +import os, threading, subprocess +import re + +ERRCONDS = select.POLLHUP|select.POLLERR +SERVER = None +ip = IPython.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 IPython.ipapi.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.expose_magic('vim', vim) + diff --git a/doc/examples/ipy.vim b/doc/examples/ipy.vim new file mode 100644 index 0000000..cb05cf5 --- /dev/null +++ b/doc/examples/ipy.vim @@ -0,0 +1,67 @@ +if !exists("$IPY_SESSION") + finish +endif + +" set up the python interpreter within vim, to have all the right modules +" imported, as well as certain useful globals set +python import socket +python import os +python import vim +python IPYSERVER = None + +python << EOF +# do we have a connection to the ipython instance? +def check_server(): + global IPYSERVER + if IPYSERVER: + return True + else: + return False + +# connect to the ipython server, if we need to +def connect(): + global IPYSERVER + if check_server(): + return + try: + IPYSERVER = socket.socket(socket.AF_UNIX) + IPYSERVER.connect(os.environ.get('IPY_SERVER')) + except: + IPYSERVER = None + +def disconnect(): + if IPYSERVER: + IPYSERVER.close() + +def send(cmd): + x = 0 + while True: + x += IPYSERVER.send(cmd) + if x < len(cmd): + cmd = cmd[x:] + else: + break + +def run_this_file(): + if check_server(): + send('run %s' % (vim.current.buffer.name,)) + else: + raise Exception, "Not connected to an IPython server" +EOF + +fun! toggle_send_on_save() + if exists("s:ssos") && s:ssos == 1 + let s:ssos = 0 + au! BufWritePost *.py :py run_this_file() + echo "Autosend Off" + else + let s:ssos = 1 + au BufWritePost *.py :py run_this_file() + echo "Autowsend On" + endif +endfun + +map :python run_this_file() +imap a +map :call toggle_send_on_save() +py connect()