Generalize interface between Vim and Python

This commit is contained in:
Tim Pope 2014-01-07 00:16:36 -05:00
parent 1b2e58db97
commit 2073263c07
2 changed files with 108 additions and 80 deletions

View File

@ -28,43 +28,6 @@ function! nrepl#fireplace_connection#bencode(value) abort
endif
endfunction
function! nrepl#fireplace_connection#bdecode(value) abort
return s:bdecode({'pos': 0, 'value': a:value})
endfunction
function! s:bdecode(state) abort
let value = a:state.value
if value[a:state.pos] =~# '\d'
let pos = a:state.pos
let length = matchstr(value[pos : -1], '^\d\+')
let a:state.pos += strlen(length) + length + 1
return value[pos+strlen(length)+1 : pos+strlen(length)+length]
elseif value[a:state.pos] ==# 'i'
let int = matchstr(value[a:state.pos+1:-1], '[^e]*')
let a:state.pos += 2 + strlen(int)
return str2nr(int)
elseif value[a:state.pos] ==# 'l'
let values = []
let a:state.pos += 1
while value[a:state.pos] !=# 'e' && value[a:state.pos] !=# ''
call add(values, s:bdecode(a:state))
endwhile
let a:state.pos += 1
return values
elseif value[a:state.pos] ==# 'd'
let values = {}
let a:state.pos += 1
while value[a:state.pos] !=# 'e' && value[a:state.pos] !=# ''
let key = s:bdecode(a:state)
let values[key] = s:bdecode(a:state)
endwhile
let a:state.pos += 1
return values
else
throw 'bencode parse error: '.string(a:state)
endif
endfunction
" }}}1
function! s:shellesc(arg) abort
@ -198,20 +161,26 @@ function! s:extract_last_stacktrace(nrepl) abort
return stacktrace
endfunction
function! s:nrepl_call(payload) dict abort
function! s:nrepl_dispatch(command, ...) dict abort
let in = 'python'
\ . ' ' . s:shellesc(s:python_dir.'/nrepl_fireplace.py')
\ . ' ' . s:shellesc(a:command)
\ . ' ' . s:shellesc(self.host)
\ . ' ' . s:shellesc(self.port)
\ . ' ' . s:shellesc(nrepl#fireplace_connection#bencode(a:payload))
\ . ' ' . join(map(copy(a:000), 's:shellesc(v:val)'), ' ')
let out = system(in)
if !v:shell_error
return nrepl#fireplace_connection#bdecode('l'.out.'e')
return eval(out)
endif
throw 'nREPL: '.out
endfunction
function! s:nrepl_call(payload) dict abort
return self.dispatch('call', nrepl#fireplace_connection#bencode(a:payload))
endfunction
let s:nrepl = {
\ 'dispatch': s:function('s:nrepl_dispatch'),
\ 'call': s:function('s:nrepl_call'),
\ 'eval': s:function('s:nrepl_eval'),
\ 'path': s:function('s:nrepl_path'),
@ -222,7 +191,7 @@ if !has('python')
endif
if !exists('s:python')
exe 'python sys.path.insert(0, "'.s:python_dir.'")'
exe 'python sys.path.insert(0, "'.escape(s:python_dir, '\"').'")'
let s:python = 1
python import nrepl_fireplace
else
@ -232,33 +201,23 @@ endif
python << EOF
import vim
def fireplace_string_encode(input):
str_list = []
for c in input:
if (000 <= ord(c) and ord(c) <= 037) or c == '"' or c == "\\":
str_list.append("\\{0:03o}".format(ord(c)))
else:
str_list.append(c)
return '"' + ''.join(str_list) + '"'
def fireplace_let(var, value):
return vim.command('let ' + var + " = " + fireplace_string_encode(value))
return vim.command('let ' + var + ' = ' + nrepl_fireplace.vim_encode(value))
def fireplace_check():
vim.eval('getchar(1)')
def fireplace_repl_interact():
def fireplace_repl_dispatch(command, *args):
try:
fireplace_let('out', nrepl_fireplace.repl_send(vim.eval('self.host'), int(vim.eval('self.port')), vim.eval('payload'), fireplace_check))
fireplace_let('out', nrepl_fireplace.dispatch(command, vim.eval('self.host'), vim.eval('self.port'), fireplace_check, *args))
except Exception, e:
fireplace_let('err', str(e))
EOF
function! s:nrepl_call(payload) dict abort
let payload = nrepl#fireplace_connection#bencode(a:payload)
python fireplace_repl_interact()
function! s:nrepl_dispatch(command, ...) dict abort
python fireplace_repl_dispatch(vim.eval('a:command'), *vim.eval('a:000'))
if !exists('err')
return nrepl#fireplace_connection#bdecode('l'.out.'e')
return out
endif
throw 'nREPL Connection Error: '.err
endfunction

View File

@ -3,33 +3,102 @@ import select
import socket
import re
def repl_send(host, port, payload, callback):
buffer = ''
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(8)
try:
s.connect((host, port))
s.setblocking(1)
s.sendall(payload)
while True:
while len(select.select([s], [], [], 0.1)[0]) == 0:
callback()
body = s.recv(8192)
if re.search("=> $", body) != None:
raise Exception("not an nREPL server: upgrade to Leiningen 2")
buffer += body
if re.search('6:statusl(5:error|14:session-closed)?4:done', body):
break
return buffer
finally:
s.close()
def noop():
pass
def main(host, port, payload):
def vim_encode(data):
if isinstance(data, list):
return "[" + ",".join([vim_encode(x) for x in data]) + "]"
elif isinstance(data, dict):
return "{" + ",".join([vim_encode(x)+":"+vim_encode(y) for x,y in data.items()]) + "}"
elif isinstance(data, str):
str_list = []
for c in data:
if (000 <= ord(c) and ord(c) <= 037) or c == '"' or c == "\\":
str_list.append("\\{0:03o}".format(ord(c)))
else:
str_list.append(c)
return '"' + ''.join(str_list) + '"'
elif isinstance(data, int):
return str(data)
else:
raise TypeError("can't encode a " + type(data).__name__)
def bdecode(f, char=None):
if char == None:
char = f.read(1)
if char == 'l':
l = []
while True:
char = f.read(1)
if char == 'e':
return l
l.append(bdecode(f, char))
elif char == 'd':
d = {}
while True:
char = f.read(1)
if char == 'e':
return d
key = bdecode(f, char)
d[key] = bdecode(f)
elif char == 'i':
i = 0
while True:
char = f.read(1)
if char == 'e':
return i
i = 10 * i + int(char)
else:
i = int(char)
while True:
char = f.read(1)
if char == ':':
return f.read(i)
i = 10 * i + int(char)
class Connection:
def __init__(self, host, port, poll=noop):
self.poll = poll
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(8)
s.connect((host, int(port)))
s.setblocking(1)
self.socket = s
def close(self):
return self.socket.close()
def send(self, payload):
self.socket.sendall(payload)
return ''
def receive(self, char=None):
f = self.socket.makefile()
try:
sys.stdout.write(repl_send(host, int(port), payload, noop))
return bdecode(f)
finally:
f.close()
def call(self, payload):
self.send(payload)
responses = []
while True:
responses.append(self.receive())
if 'status' in responses[-1] and 'done' in responses[-1]['status']:
return responses
def dispatch(command, host, port, poll, *args):
conn = Connection(host, port, poll)
try:
return getattr(conn, command)(*args)
finally:
conn.close()
def main(command, host, port, *args):
try:
sys.stdout.write(vim_encode(dispatch(command, host, port, noop, *args)))
except Exception, e:
print(e)
exit(1)