/
ipy_vimserver.py
245 lines (192 loc) · 7.82 KB
/
ipy_vimserver.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
""" 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)