From e90aef2ac73319a74db3395849ef903fb2211618 Mon Sep 17 00:00:00 2001 From: Tim Pope Date: Fri, 10 Jan 2014 18:39:40 -0500 Subject: [PATCH] Separate nREPL transport and session concerns The transport is still doing too much, but it's a good start. --- autoload/fireplace/nrepl.vim | 199 ++++++++++++++++++++++++ autoload/nrepl/fireplace_connection.vim | 189 ++-------------------- 2 files changed, 216 insertions(+), 172 deletions(-) create mode 100644 autoload/fireplace/nrepl.vim diff --git a/autoload/fireplace/nrepl.vim b/autoload/fireplace/nrepl.vim new file mode 100644 index 0000000..8444304 --- /dev/null +++ b/autoload/fireplace/nrepl.vim @@ -0,0 +1,199 @@ +" Fireplace nREPL session + +if exists("g:autoloaded_fireplace_nrepl") + finish +endif +let g:autoloaded_fireplace_nrepl = 1 + +function! s:function(name) abort + return function(substitute(a:name,'^s:',matchstr(expand(''), '\d\+_'),'')) +endfunction + +if !exists('s:id') + let s:vim_id = localtime() + let s:id = 0 +endif +function! fireplace#nrepl#next_id() abort + let s:id += 1 + return 'fireplace-'.hostname().'-'.s:vim_id.'-'.s:id +endfunction + +if !exists('g:fireplace_nrepl_sessions') + let g:fireplace_nrepl_sessions = {} +endif + +augroup fireplace_nrepl_connection + autocmd! + autocmd VimLeave * for s:session in values(g:fireplace_nrepl_sessions) + \ | call s:session.close() + \ | endfor +augroup END + +function! fireplace#nrepl#for(transport) abort + let client = copy(s:nrepl) + let client.transport = a:transport + let client.session = client.process({'op': 'clone', 'session': 0})['new-session'] + let response = client.process({'op': 'eval', 'code': + \ '(do (println "success") (symbol (str (System/getProperty "path.separator") (System/getProperty "java.class.path"))))'}) + let client._path = response.value[-1] + if has_key(response, 'out') + let g:fireplace_nrepl_sessions[client.session] = client + else + unlet client.session + endif + return client +endfunction + +function! s:nrepl_close() dict abort + if has_key(self, 'session') + try + unlet! g:fireplace_nrepl_sessions[self.session] + call self.message({'op': 'close'}) + catch + finally + unlet self.session + endtry + endif + call self.transport.close() + return self +endfunction + +function! s:nrepl_path() dict abort + return split(self._path[1:-1], self._path[0]) +endfunction + +function! fireplace#nrepl#combine(responses) + let combined = {'status': [], 'session': []} + for response in a:responses + for key in keys(response) + if key ==# 'id' || key ==# 'ns' + let combined[key] = response[key] + elseif key ==# 'value' + let combined.value = extend(get(combined, 'value', []), [response.value]) + elseif key ==# 'status' + for entry in response[key] + if index(combined[key], entry) < 0 + call extend(combined[key], [entry]) + endif + endfor + elseif key ==# 'session' + if index(combined[key], response[key]) < 0 + call extend(combined[key], [response[key]]) + endif + elseif type(response[key]) == type('') + let combined[key] = get(combined, key, '') . response[key] + else + let combined[key] = response[key] + endif + endfor + endfor + return combined +endfunction + +function! s:nrepl_process(msg) dict abort + let combined = fireplace#nrepl#combine(self.message(a:msg)) + if index(combined.status, 'error') < 0 + return combined + endif + throw 'nREPL: ' . tr(combined.status[0], '-', ' ') +endfunction + +function! s:nrepl_eval(expr, ...) dict abort + let msg = {"op": "eval"} + let msg.code = a:expr + let options = a:0 ? a:1 : {} + if has_key(options, 'ns') + let msg.ns = options.ns + elseif has_key(self, 'ns') + let msg.ns = self.ns + endif + if has_key(options, 'session') + let msg.session = options.session + endif + if has_key(options, 'file_path') + let msg.op = 'load-file' + let msg['file-path'] = options.file_path + let msg['file-name'] = fnamemodify(options.file_path, ':t') + if has_key(msg, 'ns') + let msg.file = "(in-ns '".msg.ns.") ".msg.code + call remove(msg, 'ns') + else + let msg.file = msg.code + endif + call remove(msg, 'code') + endif + let response = self.process(msg) + if has_key(response, 'ns') && !a:0 + let self.ns = response.ns + endif + + if has_key(response, 'ex') && !empty(get(msg, 'session', 1)) + let response.stacktrace = s:extract_last_stacktrace(self) + endif + + if has_key(response, 'value') + let response.value = response.value[-1] + endif + return response +endfunction + +function! s:extract_last_stacktrace(nrepl) abort + let format_st = '(clojure.core/symbol (clojure.core/str "\n\b" (clojure.core/apply clojure.core/str (clojure.core/interleave (clojure.core/repeat "\n") (clojure.core/map clojure.core/str (.getStackTrace *e)))) "\n\b\n"))' + let stacktrace = split(get(split(a:nrepl.process({'op': 'eval', 'code': '['.format_st.' *3 *2 *1]', 'session': a:nrepl.session}).value[0], "\n\b\n"), 1, ""), "\n") + call a:nrepl.message({'op': 'eval', 'code': '(nth *1 1)', 'session': a:nrepl.session}) + call a:nrepl.message({'op': 'eval', 'code': '(nth *2 2)', 'session': a:nrepl.session}) + call a:nrepl.message({'op': 'eval', 'code': '(nth *3 3)', 'session': a:nrepl.session}) + return stacktrace +endfunction + +let s:keepalive = tempname() +call writefile([getpid()], s:keepalive) + +function! s:nrepl_prepare(msg) dict abort + let msg = copy(a:msg) + if !has_key(msg, 'id') + let msg.id = fireplace#nrepl#next_id() + endif + if empty(get(msg, 'session', 1)) + unlet msg.session + elseif !has_key(self, 'session') + if &verbose + echohl WarningMSG + echo "nREPL: server has bug preventing session support" + echohl None + endif + unlet! msg.session + elseif !has_key(msg, 'session') + let msg.session = self.session + endif + return msg +endfunction + +function! fireplace#nrepl#callback(body, type, fn) + let response = {'body': a:body, 'type': a:type} + if has_key(a:body, 'session') + let response.session = g:fireplace_nrepl_sessions[a:body.session] + endif + call call(a:fn, [response]) +endfunction + +function! s:nrepl_call(msg, ...) dict abort + let terms = a:0 ? a:1 : ['done'] + let sels = a:0 > 1 ? a:2 : {} + return call(self.transport.call, [a:msg, terms, sels] + a:000[2:-1], self.transport) +endfunction + +function! s:nrepl_message(msg, ...) dict abort + let msg = self.prepare(a:msg) + let sel = {'id': msg.id} + return call(self.call, [msg, ['done'], sel] + a:000, self) +endfunction + +let s:nrepl = { + \ 'close': s:function('s:nrepl_close'), + \ 'prepare': s:function('s:nrepl_prepare'), + \ 'call': s:function('s:nrepl_call'), + \ 'message': s:function('s:nrepl_message'), + \ 'eval': s:function('s:nrepl_eval'), + \ 'path': s:function('s:nrepl_path'), + \ 'process': s:function('s:nrepl_process')} diff --git a/autoload/nrepl/fireplace_connection.vim b/autoload/nrepl/fireplace_connection.vim index 05f1894..d73af2a 100644 --- a/autoload/nrepl/fireplace_connection.vim +++ b/autoload/nrepl/fireplace_connection.vim @@ -58,17 +58,6 @@ function! nrepl#fireplace_connection#prompt() abort return fireplace#input_host_port() endfunction -if !exists('g:nrepl_fireplace_sessions') - let g:nrepl_fireplace_sessions = {} -endif - -augroup nrepl_fireplace_connection - autocmd! - autocmd VimLeave * for s:session in values(g:nrepl_fireplace_sessions) - \ | call s:session.close() - \ | endfor -augroup END - function! nrepl#fireplace_connection#open(arg) abort if a:arg =~# '^\d\+$' let host = 'localhost' @@ -79,121 +68,20 @@ function! nrepl#fireplace_connection#open(arg) abort else throw "nREPL: Couldn't find [host:]port in " . a:arg endif - let client = deepcopy(s:nrepl) - let client.host = host - let client.port = port - let client.session = client.process({'op': 'clone', 'session': 0})['new-session'] - let response = client.process({'op': 'eval', 'code': - \ '(do (println "success") (symbol (str (System/getProperty "path.separator") (System/getProperty "java.class.path"))))'}) - let client._path = response.value[-1] - if has_key(response, 'out') - let g:nrepl_fireplace_sessions[client.session] = client - else - unlet client.session - endif - return client + let transport = deepcopy(s:nrepl_transport) + let transport.host = host + let transport.port = port + return fireplace#nrepl#for(transport) endfunction -function! s:nrepl_close() dict abort - if has_key(self, 'session') - try - unlet! g:nrepl_fireplace_sessions[self.session] - call self.message({'op': 'close'}) - catch // - finally - unlet self.session - endtry - endif +function! s:nrepl_transport_close() dict abort return self endfunction -function! s:nrepl_path() dict abort - return split(self._path[1:-1], self._path[0]) -endfunction - -function! s:nrepl_process(responses) dict abort - let combined = {'status': [], 'session': []} - for response in self.message(a:responses) - for key in keys(response) - if key ==# 'id' || key ==# 'ns' - let combined[key] = response[key] - elseif key ==# 'value' - let combined.value = extend(get(combined, 'value', []), [response.value]) - elseif key ==# 'status' - for entry in response[key] - if index(combined[key], entry) < 0 - call extend(combined[key], [entry]) - endif - endfor - elseif key ==# 'session' - if index(combined[key], response[key]) < 0 - call extend(combined[key], [response[key]]) - endif - elseif type(response[key]) == type('') - let combined[key] = get(combined, key, '') . response[key] - else - let combined[key] = response[key] - endif - endfor - endfor - if index(combined.status, 'error') >= 0 - throw 'nREPL: ' . tr(combined.status[0], '-', ' ') - endif - return combined -endfunction - -function! s:nrepl_eval(expr, ...) dict abort - let msg = {"op": "eval"} - let msg.code = a:expr - let options = a:0 ? a:1 : {} - if has_key(options, 'ns') - let msg.ns = options.ns - elseif has_key(self, 'ns') - let msg.ns = self.ns - endif - if has_key(options, 'session') - let msg.session = options.session - endif - if has_key(options, 'file_path') - let msg.op = 'load-file' - let msg['file-path'] = options.file_path - let msg['file-name'] = fnamemodify(options.file_path, ':t') - if has_key(msg, 'ns') - let msg.file = "(in-ns '".msg.ns.") ".msg.code - call remove(msg, 'ns') - else - let msg.file = msg.code - endif - call remove(msg, 'code') - endif - let response = self.process(msg) - if has_key(response, 'ns') && !a:0 - let self.ns = response.ns - endif - - if has_key(response, 'ex') && !empty(get(msg, 'session', 1)) - let response.stacktrace = s:extract_last_stacktrace(self) - endif - - if has_key(response, 'value') - let response.value = response.value[-1] - endif - return response -endfunction - -function! s:extract_last_stacktrace(nrepl) abort - let format_st = '(clojure.core/symbol (clojure.core/str "\n\b" (clojure.core/apply clojure.core/str (clojure.core/interleave (clojure.core/repeat "\n") (clojure.core/map clojure.core/str (.getStackTrace *e)))) "\n\b\n"))' - let stacktrace = split(get(split(a:nrepl.process({'op': 'eval', 'code': '['.format_st.' *3 *2 *1]', 'session': a:nrepl.session}).value[0], "\n\b\n"), 1, ""), "\n") - call a:nrepl.message({'op': 'eval', 'code': '(nth *1 1)', 'session': a:nrepl.session}) - call a:nrepl.message({'op': 'eval', 'code': '(nth *2 2)', 'session': a:nrepl.session}) - call a:nrepl.message({'op': 'eval', 'code': '(nth *3 3)', 'session': a:nrepl.session}) - return stacktrace -endfunction - let s:keepalive = tempname() call writefile([getpid()], s:keepalive) -function! s:nrepl_command(cmd, args) dict abort +function! s:nrepl_transport_command(cmd, args) dict abort return 'python' \ . ' ' . s:shellesc(s:python_dir.'/nrepl_fireplace.py') \ . ' ' . s:shellesc(self.host) @@ -203,7 +91,7 @@ function! s:nrepl_command(cmd, args) dict abort \ . ' ' . join(map(copy(a:args), 's:shellesc(nrepl#fireplace_connection#bencode(v:val))'), ' ') endfunction -function! s:nrepl_dispatch(cmd, ...) dict abort +function! s:nrepl_transport_dispatch(cmd, ...) dict abort let in = self.command(a:cmd, a:000) let out = system(in) if !v:shell_error @@ -212,62 +100,21 @@ function! s:nrepl_dispatch(cmd, ...) dict abort throw 'nREPL: '.out endfunction -function! s:nrepl_prepare(msg) dict abort - let msg = copy(a:msg) - if !has_key(msg, 'id') - let msg.id = s:id() - endif - if empty(get(msg, 'session', 1)) - unlet msg.session - elseif !has_key(self, 'session') - if &verbose - echohl WarningMSG - echo "nREPL: server has bug preventing session support" - echohl None - endif - unlet! msg.session - elseif !has_key(msg, 'session') - let msg.session = self.session - endif - return msg -endfunction - -function! nrepl#fireplace_connection#callback(body, fn) - let response = {'body': a:body} - if has_key(a:body, 'session') - let response.session = g:nrepl_fireplace_sessions[a:body.session] - endif - call call(a:fn, [response]) -endfunction - -function! s:nrepl_call(msg, ...) dict abort +function! s:nrepl_transport_call(msg, terms, sels, ...) dict abort let payload = nrepl#fireplace_connection#bencode(a:msg) - let terms = a:0 ? a:1 : ['done'] - let sels = a:0 > 1 ? a:2 : {} - let response = self.dispatch('call', payload, terms, sels) - if a:0 > 2 - return map(response, 'nrepl#fireplace_connection#callback(v:val, a:3)') + let response = self.dispatch('call', payload, a:terms, a:sels) + if a:0 + return map(response, 'fireplace#nrepl#callback(v:val, "synchronous", a:1)') else return response endif endfunction -function! s:nrepl_message(msg, ...) dict abort - let msg = self.prepare(a:msg) - let sel = {'id': msg.id} - return call(self.call, [msg, ['done'], sel] + a:000, self) -endfunction - -let s:nrepl = { - \ 'close': s:function('s:nrepl_close'), - \ 'command': s:function('s:nrepl_command'), - \ 'dispatch': s:function('s:nrepl_dispatch'), - \ 'prepare': s:function('s:nrepl_prepare'), - \ 'call': s:function('s:nrepl_call'), - \ 'message': s:function('s:nrepl_message'), - \ 'eval': s:function('s:nrepl_eval'), - \ 'path': s:function('s:nrepl_path'), - \ 'process': s:function('s:nrepl_process')} +let s:nrepl_transport = { + \ 'close': s:function('s:nrepl_transport_close'), + \ 'command': s:function('s:nrepl_transport_command'), + \ 'dispatch': s:function('s:nrepl_transport_dispatch'), + \ 'call': s:function('s:nrepl_transport_call')} if !has('python') || $FIREPLACE_NO_IF_PYTHON finish @@ -297,12 +144,10 @@ def fireplace_repl_dispatch(command, *args): fireplace_let('err', str(e)) EOF -function! s:nrepl_dispatch(command, ...) dict abort +function! s:nrepl_transport_dispatch(command, ...) dict abort python fireplace_repl_dispatch(vim.eval('a:command'), *vim.eval('a:000')) if !exists('err') return out endif throw 'nREPL Connection Error: '.err endfunction - -" vim:set et sw=2: