From 2073263c079554cc38383430377cdd99253e9030 Mon Sep 17 00:00:00 2001 From: Tim Pope Date: Tue, 7 Jan 2014 00:16:36 -0500 Subject: [PATCH] Generalize interface between Vim and Python --- autoload/nrepl/fireplace_connection.vim | 73 ++++----------- python/nrepl_fireplace.py | 115 +++++++++++++++++++----- 2 files changed, 108 insertions(+), 80 deletions(-) diff --git a/autoload/nrepl/fireplace_connection.vim b/autoload/nrepl/fireplace_connection.vim index 36462a5..1b8e3ac 100644 --- a/autoload/nrepl/fireplace_connection.vim +++ b/autoload/nrepl/fireplace_connection.vim @@ -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 diff --git a/python/nrepl_fireplace.py b/python/nrepl_fireplace.py index 202ff45..ae74fe3 100644 --- a/python/nrepl_fireplace.py +++ b/python/nrepl_fireplace.py @@ -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: + 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: - sys.stdout.write(repl_send(host, int(port), payload, noop)) + 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)