vim-fireplace/autoload/fireplace/nrepl.vim
Joshua Davey 5866d0017a Add support for boot 2.0 and up
Boot's built-in repl task adds a fake.class.path System property which
refers back to the original user files (as opposed to the temporary
files it uses to actually do builds). We should prefer that to anything
else when the property is set.

Fixes #194.
2015-02-15 13:45:21 -05:00

259 lines
8.0 KiB
VimL

" Location: autoload/nrepl/fireplace.vim
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 client.describe = client.process({'op': 'describe', 'verbose?': 1})
if get(client.describe.versions.nrepl, 'major', -1) == 0 &&
\ client.describe.versions.nrepl.minor < 2
throw 'nREPL: 0.2.0 or higher required'
endif
" Handle boot, which sets a fake.class.path entry
let response = client.process({'op': 'eval', 'code':
\ '[(System/getProperty "path.separator") (System/getProperty "fake.class.path")]', 'session': ''})
let cpath = response.value[-1][5:-2]
if cpath !=# 'nil'
let cpath = eval(cpath)
if !empty(cpath)
let client._path = split(cpath, response.value[-1][2])
endif
endif
if !has_key(client, '_path') && client.has_op('classpath')
let response = client.message({'op': 'classpath'})[0]
if type(get(response, 'classpath')) == type([])
let client._path = response.classpath
endif
endif
if !has_key(client, '_path')
let response = client.process({'op': 'eval', 'code':
\ '[(System/getProperty "path.separator") (System/getProperty "java.class.path")]', 'session': ''})
let client._path = split(eval(response.value[-1][5:-2]), response.value[-1][2])
endif
let g:fireplace_nrepl_sessions[client.session] = client
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'}, 'ignore')
catch
finally
unlet self.session
endtry
endif
call self.transport.close()
return self
endfunction
function! s:nrepl_clone() dict abort
let client = copy(self)
if has_key(self, 'session')
let client.session = client.process({'op': 'clone'})['new-session']
let g:fireplace_nrepl_sessions[client.session] = client
endif
return client
endfunction
function! s:nrepl_path() dict abort
return self._path
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, 'id')
let msg.id = options.id
else
let msg.id = fireplace#nrepl#next_id()
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
try
let response = self.process(msg)
finally
if !exists('response')
let session = get(msg, 'session', self.session)
if !empty(session)
call self.message({'op': 'interrupt', 'session': session, 'interrupt-id': msg.id}, 'ignore')
endif
throw 'Clojure: Interrupt'
endif
endtry
if has_key(response, 'ns') && empty(get(options, 'ns'))
let self.ns = response.ns
endif
if has_key(response, 'ex') && !empty(get(msg, 'session', 1))
let response.stacktrace = s:extract_last_stacktrace(self, get(msg, 'session', self.session))
endif
if has_key(response, 'value')
let response.value = response.value[-1]
endif
return response
endfunction
function! s:extract_last_stacktrace(nrepl, session) abort
if a:nrepl.has_op('stacktrace')
let stacktrace = filter(a:nrepl.message({'op': 'stacktrace', 'session': a:session}), 'has_key(v:val, "file")')
if !empty(stacktrace)
return map(stacktrace, 'v:val.class.".".v:val.method."(".v:val.file.":".v:val.line.")"')
endif
endif
let format_st = '(symbol (str "\n\b" (apply str (interleave (repeat "\n") (map str (.getStackTrace *e)))) "\n\b\n"))'
let response = a:nrepl.process({'op': 'eval', 'code': '['.format_st.' *3 *2 *1]', 'ns': 'user', 'session': a:session})
try
let stacktrace = split(get(split(response.value[0], "\n\b\n"), 1, ""), "\n")
catch
throw string(response)
endtry
call a:nrepl.message({'op': 'eval', 'code': '(*1 1)', 'ns': 'user', 'session': a:session})
call a:nrepl.message({'op': 'eval', 'code': '(*2 2)', 'ns': 'user', 'session': a:session})
call a:nrepl.message({'op': 'eval', 'code': '(*3 3)', 'ns': 'user', 'session': a: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, 'ns', 1))
unlet msg.ns
endif
if empty(get(msg, 'session', 1))
unlet msg.session
elseif !has_key(msg, 'session')
let msg.session = self.session
endif
return msg
endfunction
function! fireplace#nrepl#callback(body, type, callback) abort
try
let response = {'body': a:body, 'type': a:type}
if has_key(g:fireplace_nrepl_sessions, get(a:body, 'session'))
let response.session = g:fireplace_nrepl_sessions[a:body.session]
endif
call call(a:callback[0], [response] + a:callback[1:-1])
catch
endtry
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
function! s:nrepl_has_op(op) dict abort
return has_key(self.describe.ops, a:op)
endfunction
let s:nrepl = {
\ 'close': s:function('s:nrepl_close'),
\ 'clone': s:function('s:nrepl_clone'),
\ 'prepare': s:function('s:nrepl_prepare'),
\ 'call': s:function('s:nrepl_call'),
\ 'message': s:function('s:nrepl_message'),
\ 'eval': s:function('s:nrepl_eval'),
\ 'has_op': s:function('s:nrepl_has_op'),
\ 'path': s:function('s:nrepl_path'),
\ 'process': s:function('s:nrepl_process')}