Separate nREPL transport and session concerns
The transport is still doing too much, but it's a good start.
This commit is contained in:
parent
fc0ab71587
commit
e90aef2ac7
199
autoload/fireplace/nrepl.vim
Normal file
199
autoload/fireplace/nrepl.vim
Normal file
@ -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('<sfile>'), '<SNR>\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')}
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user