9ccaea1f2b
In cider-nrepl 0.9.0 there is new extra-metadata option for complete op [1]. It can be used to enrich the candiate results with additional properties like arglists and docstring. This commit adds extra-metadata option to complete call and changes the candidate function to set fields in omnicomplete result so that arglists are shown on omnicomplete menu and docstring is shown in preview window. [1]: https://github.com/clojure-emacs/cider-nrepl/pull/195/files
1545 lines
46 KiB
VimL
1545 lines
46 KiB
VimL
" fireplace.vim - Clojure REPL support
|
|
" Maintainer: Tim Pope <http://tpo.pe/>
|
|
" Version: 1.0
|
|
" GetLatestVimScripts: 4978 1 :AutoInstall: fireplace.vim
|
|
|
|
if exists("g:loaded_fireplace") || v:version < 700 || &cp
|
|
finish
|
|
endif
|
|
let g:loaded_fireplace = 1
|
|
|
|
" Section: File type
|
|
|
|
augroup fireplace_file_type
|
|
autocmd!
|
|
autocmd BufNewFile,BufReadPost *.clj setfiletype clojure
|
|
augroup END
|
|
|
|
" Section: Escaping
|
|
|
|
function! s:str(string) abort
|
|
return '"' . escape(a:string, '"\') . '"'
|
|
endfunction
|
|
|
|
function! s:qsym(symbol) abort
|
|
if a:symbol =~# '^[[:alnum:]?*!+/=<>.:-]\+$'
|
|
return "'".a:symbol
|
|
else
|
|
return '(symbol '.s:str(a:symbol).')'
|
|
endif
|
|
endfunction
|
|
|
|
function! s:to_ns(path) abort
|
|
return tr(substitute(a:path, '\.\w\+$', '', ''), '\/_', '..-')
|
|
endfunction
|
|
|
|
" Section: Completion
|
|
|
|
let s:jar_contents = {}
|
|
|
|
function! fireplace#jar_contents(path) abort
|
|
if !exists('s:zipinfo')
|
|
if executable('zipinfo')
|
|
let s:zipinfo = 'zipinfo -1 '
|
|
elseif executable('python')
|
|
let s:zipinfo = 'python -c '.shellescape('import zipfile, sys; print chr(10).join(zipfile.ZipFile(sys.argv[1]).namelist())').' '
|
|
else
|
|
let s:zipinfo = ''
|
|
endif
|
|
endif
|
|
|
|
if !has_key(s:jar_contents, a:path) && has('python')
|
|
python import vim, zipfile
|
|
python vim.command("let s:jar_contents[a:path] = split('" + "\n".join(zipfile.ZipFile(vim.eval('a:path')).namelist()) + "', \"\n\")")
|
|
elseif !has_key(s:jar_contents, a:path) && !empty(s:zipinfo)
|
|
let s:jar_contents[a:path] = split(system(s:zipinfo.shellescape(a:path)), "\n")
|
|
if v:shell_error
|
|
let s:jar_contents[a:path] = []
|
|
endif
|
|
endif
|
|
|
|
return copy(get(s:jar_contents, a:path, []))
|
|
endfunction
|
|
|
|
function! fireplace#eval_complete(A, L, P) abort
|
|
let prefix = matchstr(a:A, '\%(.* \|^\)\%(#\=[\[{('']\)*')
|
|
let keyword = a:A[strlen(prefix) : -1]
|
|
return sort(map(fireplace#omnicomplete(0, keyword), 'prefix . v:val.word'))
|
|
endfunction
|
|
|
|
function! fireplace#ns_complete(A, L, P) abort
|
|
let matches = []
|
|
for dir in fireplace#path()
|
|
if dir =~# '\.jar$'
|
|
let files = filter(fireplace#jar_contents(dir), 'v:val =~# "\\.clj$"')
|
|
else
|
|
let files = split(glob(dir."/**/*.clj", 1), "\n")
|
|
call map(files, 'v:val[strlen(dir)+1 : -1]')
|
|
endif
|
|
let matches += files
|
|
endfor
|
|
return filter(map(matches, 's:to_ns(v:val)'), 'a:A ==# "" || a:A ==# v:val[0 : strlen(a:A)-1]')
|
|
endfunction
|
|
|
|
let s:short_types = {
|
|
\ 'function': 'f',
|
|
\ 'macro': 'm',
|
|
\ 'var': 'v',
|
|
\ 'special-form': 's',
|
|
\ 'class': 'c'
|
|
\ }
|
|
|
|
function! s:candidate(val) abort
|
|
let type = get(a:val, 'type', '')
|
|
let arglists = get(a:val, 'arglists', [])
|
|
return {
|
|
\ 'word': get(a:val, 'candidate'),
|
|
\ 'kind': get(s:short_types, type, type),
|
|
\ 'info': get(a:val, 'doc', ''),
|
|
\ 'menu': empty(arglists) ? '' : '(' . join(arglists, ' ') . ')'
|
|
\ }
|
|
endfunction
|
|
|
|
function! fireplace#omnicomplete(findstart, base) abort
|
|
if a:findstart
|
|
let line = getline('.')[0 : col('.')-2]
|
|
return col('.') - strlen(matchstr(line, '\k\+$')) - 1
|
|
else
|
|
try
|
|
|
|
if fireplace#op_available('complete')
|
|
let response = fireplace#message({'op': 'complete', 'symbol': a:base, 'extra-metadata': ['arglists', 'doc']})
|
|
let trans = '{"word": (v:val =~# ''[./]'' ? "" : matchstr(a:base, ''^.\+/'')) . v:val}'
|
|
let value = get(response[0], 'value', get(response[0], 'completions'))
|
|
if type(value) == type([])
|
|
if type(get(value, 0)) == type({})
|
|
return map(value, 's:candidate(v:val)')
|
|
elseif type(get(value, 0)) == type([])
|
|
return map(value[0], trans)
|
|
elseif type(get(value, 0)) == type('')
|
|
return map(value, trans)
|
|
else
|
|
return []
|
|
endif
|
|
endif
|
|
endif
|
|
|
|
let omnifier = '(fn [[k v]] (let [{:keys [arglists] :as m} (meta v)]' .
|
|
\ ' {:word k :menu (pr-str (or arglists (symbol ""))) :info (str (when arglists (str arglists "\n")) " " (:doc m)) :kind (if arglists "f" "v")}))'
|
|
|
|
let ns = fireplace#ns()
|
|
|
|
let [aliases, namespaces, maps] = fireplace#evalparse(
|
|
\ '[(ns-aliases '.s:qsym(ns).') (all-ns) '.
|
|
\ '(sort-by :word (map '.omnifier.' (ns-map '.s:qsym(ns).')))]')
|
|
|
|
if a:base =~# '^[^/]*/[^/]*$'
|
|
let ns = matchstr(a:base, '^.*\ze/')
|
|
let prefix = ns . '/'
|
|
let ns = get(aliases, ns, ns)
|
|
let keyword = matchstr(a:base, '.*/\zs.*')
|
|
let results = fireplace#evalparse(
|
|
\ '(sort-by :word (map '.omnifier.' (ns-publics '.s:qsym(ns).')))')
|
|
for r in results
|
|
let r.word = prefix . r.word
|
|
endfor
|
|
else
|
|
let keyword = a:base
|
|
let results = maps + map(sort(keys(aliases) + namespaces), '{"word": v:val."/", "kind": "t", "info": ""}')
|
|
endif
|
|
if type(results) == type([])
|
|
return filter(results, 'a:base ==# "" || a:base ==# v:val.word[0 : strlen(a:base)-1]')
|
|
else
|
|
return []
|
|
endif
|
|
catch /.*/
|
|
return []
|
|
endtry
|
|
endif
|
|
endfunction
|
|
|
|
augroup fireplace_completion
|
|
autocmd!
|
|
autocmd FileType clojure setlocal omnifunc=fireplace#omnicomplete
|
|
augroup END
|
|
|
|
" Section: REPL client
|
|
|
|
let s:repl = {"requires": {}}
|
|
|
|
if !exists('s:repls')
|
|
let s:repls = []
|
|
let s:repl_paths = {}
|
|
let s:repl_portfiles = {}
|
|
endif
|
|
|
|
function! s:repl.user_ns() abort
|
|
return 'user'
|
|
endfunction
|
|
|
|
function! s:repl.path() dict abort
|
|
return self.connection.path()
|
|
endfunction
|
|
|
|
function! s:conn_try(connection, function, ...) abort
|
|
try
|
|
return call(a:connection[a:function], a:000, a:connection)
|
|
catch /^\w\+ Connection Error:/
|
|
call s:unregister_connection(a:connection)
|
|
throw v:exception
|
|
endtry
|
|
endfunction
|
|
|
|
function! s:repl.eval(expr, options) dict abort
|
|
if has_key(a:options, 'ns') && a:options.ns !=# self.user_ns()
|
|
let error = self.preload(a:options.ns)
|
|
if !empty(error)
|
|
return error
|
|
endif
|
|
endif
|
|
return s:conn_try(self.connection, 'eval', a:expr, a:options)
|
|
endfunction
|
|
|
|
function! s:repl.message(payload, ...) dict abort
|
|
if has_key(a:payload, 'ns') && a:payload.ns !=# self.user_ns()
|
|
let ignored_error = self.preload(a:payload.ns)
|
|
endif
|
|
return call('s:conn_try', [self.connection, 'message', a:payload] + a:000, self)
|
|
endfunction
|
|
|
|
function! s:repl.preload(lib) dict abort
|
|
if !empty(a:lib) && a:lib !=# self.user_ns() && !get(self.requires, a:lib)
|
|
let reload = has_key(self.requires, a:lib) ? ' :reload' : ''
|
|
let self.requires[a:lib] = 0
|
|
let clone = s:conn_try(self.connection, 'clone')
|
|
if self.user_ns() ==# 'user'
|
|
let qsym = s:qsym(a:lib)
|
|
let expr = '(when-not (find-ns '.qsym.') (try'
|
|
\ . ' (#''clojure.core/load-one '.qsym.' true true)'
|
|
\ . ' (catch Exception e (when-not (find-ns '.qsym.') (throw e)))))'
|
|
else
|
|
let expr = '(ns '.self.user_ns().' (:require '.a:lib.reload.'))'
|
|
endif
|
|
try
|
|
let result = clone.eval(expr, {'ns': self.user_ns()})
|
|
finally
|
|
call clone.close()
|
|
endtry
|
|
let self.requires[a:lib] = !has_key(result, 'ex')
|
|
if has_key(result, 'ex')
|
|
return result
|
|
endif
|
|
endif
|
|
return {}
|
|
endfunction
|
|
|
|
let s:piggieback = copy(s:repl)
|
|
|
|
function! s:repl.piggieback(arg, ...) abort
|
|
if a:0 && a:1
|
|
if len(self.piggiebacks)
|
|
call remove(self.piggiebacks, 0)
|
|
endif
|
|
return {}
|
|
endif
|
|
|
|
let connection = s:conn_try(self.connection, 'clone')
|
|
if empty(a:arg)
|
|
let arg = ''
|
|
elseif a:arg =~# '^\d\{1,5}$'
|
|
call connection.eval("(require 'cljs.repl.browser)")
|
|
let port = matchstr(a:arg, '^\d\{1,5}$')
|
|
let arg = ' (cljs.repl.browser/repl-env :port '.port.')'
|
|
else
|
|
let arg = ' ' . a:arg
|
|
endif
|
|
let response = connection.eval('(cemerick.piggieback/cljs-repl'.arg.')')
|
|
|
|
if empty(get(response, 'ex'))
|
|
call insert(self.piggiebacks, extend({'connection': connection}, deepcopy(s:piggieback)))
|
|
return {}
|
|
endif
|
|
call connection.close()
|
|
return response
|
|
endfunction
|
|
|
|
function! s:piggieback.user_ns() abort
|
|
return 'cljs.user'
|
|
endfunction
|
|
|
|
function! s:piggieback.eval(expr, options) abort
|
|
let options = copy(a:options)
|
|
if has_key(options, 'file_path')
|
|
call remove(options, 'file_path')
|
|
endif
|
|
return call(s:repl.eval, [a:expr, options], self)
|
|
endfunction
|
|
|
|
function! s:register_connection(conn, ...) abort
|
|
call insert(s:repls, extend({'connection': a:conn, 'piggiebacks': []}, deepcopy(s:repl)))
|
|
if a:0 && a:1 !=# ''
|
|
let s:repl_paths[a:1] = s:repls[0]
|
|
endif
|
|
return s:repls[0]
|
|
endfunction
|
|
|
|
function! s:unregister_connection(conn) abort
|
|
call filter(s:repl_paths, 'v:val.connection.transport isnot# a:conn.transport')
|
|
call filter(s:repls, 'v:val.connection.transport isnot# a:conn.transport')
|
|
call filter(s:repl_portfiles, 'v:val.connection.transport isnot# a:conn.transport')
|
|
endfunction
|
|
|
|
function! fireplace#register_port_file(portfile, ...) abort
|
|
let old = get(s:repl_portfiles, a:portfile, {})
|
|
if has_key(old, 'time') && getftime(a:portfile) !=# old.time
|
|
call s:unregister_connection(old.connection)
|
|
let old = {}
|
|
endif
|
|
if empty(old) && getfsize(a:portfile) > 0
|
|
let port = matchstr(readfile(a:portfile, 'b', 1)[0], '\d\+')
|
|
try
|
|
let conn = fireplace#nrepl_connection#open(port)
|
|
let s:repl_portfiles[a:portfile] = {
|
|
\ 'time': getftime(a:portfile),
|
|
\ 'connection': conn}
|
|
call s:register_connection(conn, a:0 ? a:1 : '')
|
|
return conn
|
|
catch /^nREPL Connection Error:/
|
|
if &verbose
|
|
echohl WarningMSG
|
|
echomsg v:exception
|
|
echohl None
|
|
endif
|
|
return {}
|
|
endtry
|
|
else
|
|
return get(old, 'connection', {})
|
|
endif
|
|
endfunction
|
|
|
|
" Section: :Connect
|
|
|
|
command! -bar -complete=customlist,s:connect_complete -nargs=* FireplaceConnect :exe s:Connect(<f-args>)
|
|
|
|
function! fireplace#input_host_port() abort
|
|
let arg = input('Host> ', 'localhost')
|
|
if arg ==# ''
|
|
return ''
|
|
endif
|
|
echo "\n"
|
|
let arg .= ':' . input('Port> ')
|
|
if arg =~# ':$'
|
|
return ''
|
|
endif
|
|
echo "\n"
|
|
return arg
|
|
endfunction
|
|
|
|
function! s:protos() abort
|
|
return map(split(globpath(&runtimepath, 'autoload/fireplace/*_connection.vim'), "\n"), 'fnamemodify(v:val, ":t")[0:-16]')
|
|
endfunction
|
|
|
|
function! s:connect_complete(A, L, P) abort
|
|
let proto = matchstr(a:A, '\w\+\ze://')
|
|
if proto ==# ''
|
|
let options = map(s:protos(), 'v:val."://"')
|
|
else
|
|
let rest = matchstr(a:A, '://\zs.*')
|
|
try
|
|
let options = fireplace#{proto}_connection#complete(rest)
|
|
catch /^Vim(let):E117/
|
|
let options = ['localhost:']
|
|
endtry
|
|
call map(options, 'proto."://".v:val')
|
|
endif
|
|
if a:A !=# ''
|
|
call filter(options, 'v:val[0 : strlen(a:A)-1] ==# a:A')
|
|
endif
|
|
return options
|
|
endfunction
|
|
|
|
function! s:Connect(...) abort
|
|
if (a:0 ? a:1 : '') =~# '^\w\+://'
|
|
let [proto, arg] = split(a:1, '://')
|
|
elseif (a:0 ? a:1 : '') =~# '^\%([[:alnum:].-]\+:\)\=\d\+$'
|
|
let [proto, arg] = ['nrepl', a:1]
|
|
elseif a:0
|
|
return 'echoerr '.string('Usage: :Connect proto://...')
|
|
else
|
|
let protos = s:protos()
|
|
if empty(protos)
|
|
return 'echoerr '.string('No protocols available')
|
|
endif
|
|
let proto = s:inputlist('Protocol> ', protos)
|
|
if proto ==# ''
|
|
return
|
|
endif
|
|
redraw!
|
|
echo ':Connect'
|
|
echo 'Protocol> '.proto
|
|
let arg = fireplace#{proto}_connection#prompt()
|
|
endif
|
|
try
|
|
let connection = fireplace#{proto}_connection#open(arg)
|
|
catch /.*/
|
|
return 'echoerr '.string(v:exception)
|
|
endtry
|
|
if type(connection) !=# type({}) || empty(connection)
|
|
return ''
|
|
endif
|
|
let client = s:register_connection(connection)
|
|
echo 'Connected to '.proto.'://'.arg
|
|
let path = fnamemodify(exists('b:java_root') ? b:java_root : fnamemodify(expand('%'), ':p:s?.*\zs[\/]src[\/].*??'), ':~')
|
|
let root = a:0 > 1 ? expand(a:2) : input('Scope connection to: ', path, 'dir')
|
|
if root !=# '' && root !=# '-'
|
|
let s:repl_paths[fnamemodify(root, ':p:s?.\zs[\/]$??')] = client
|
|
endif
|
|
return ''
|
|
endfunction
|
|
|
|
function! s:piggieback(arg, remove) abort
|
|
let response = fireplace#platform().piggieback(a:arg, a:remove)
|
|
call s:output_response(response)
|
|
endfunction
|
|
|
|
augroup fireplace_connect
|
|
autocmd!
|
|
autocmd FileType clojure command! -buffer -bar -complete=customlist,s:connect_complete -nargs=*
|
|
\ Connect FireplaceConnect <args>
|
|
autocmd FileType clojure command! -buffer -bang -complete=customlist,fireplace#eval_complete -nargs=*
|
|
\ Piggieback call s:piggieback(<q-args>, <bang>0)
|
|
augroup END
|
|
|
|
" Section: Java runner
|
|
|
|
let s:oneoff_pr = tempname()
|
|
let s:oneoff_ex = tempname()
|
|
let s:oneoff_stk = tempname()
|
|
let s:oneoff_in = tempname()
|
|
let s:oneoff_out = tempname()
|
|
let s:oneoff_err = tempname()
|
|
|
|
function! s:spawning_eval(classpath, expr, ns) abort
|
|
if a:ns !=# '' && a:ns !=# 'user'
|
|
let ns = '(require '.s:qsym(a:ns).') (in-ns '.s:qsym(a:ns).') '
|
|
else
|
|
let ns = ''
|
|
endif
|
|
call writefile([], s:oneoff_pr, 'b')
|
|
call writefile([], s:oneoff_ex, 'b')
|
|
call writefile([], s:oneoff_stk, 'b')
|
|
call writefile(split('(do '.a:expr.')', "\n"), s:oneoff_in, 'b')
|
|
call writefile([], s:oneoff_out, 'b')
|
|
call writefile([], s:oneoff_err, 'b')
|
|
let java_cmd = exists('$JAVA_CMD') ? $JAVA_CMD : 'java'
|
|
let command = java_cmd.' -cp '.shellescape(a:classpath).' clojure.main -e ' .
|
|
\ shellescape(
|
|
\ '(binding [*out* (java.io.FileWriter. '.s:str(s:oneoff_out).')' .
|
|
\ ' *err* (java.io.FileWriter. '.s:str(s:oneoff_err).')]' .
|
|
\ ' (try' .
|
|
\ ' (require ''clojure.repl ''clojure.java.javadoc) '.ns.'(spit '.s:str(s:oneoff_pr).' (pr-str (eval (read-string (slurp '.s:str(s:oneoff_in).')))))' .
|
|
\ ' (catch Exception e' .
|
|
\ ' (spit *err* (.toString e))' .
|
|
\ ' (spit '.s:str(s:oneoff_ex).' (class e))' .
|
|
\ ' (spit '.s:str(s:oneoff_stk).' (apply str (interpose "\n" (.getStackTrace e))))))' .
|
|
\ ' nil)')
|
|
let captured = system(command)
|
|
let result = {}
|
|
let result.value = join(readfile(s:oneoff_pr, 'b'), "\n")
|
|
let result.out = join(readfile(s:oneoff_out, 'b'), "\n")
|
|
let result.err = join(readfile(s:oneoff_err, 'b'), "\n")
|
|
let result.ex = join(readfile(s:oneoff_ex, 'b'), "\n")
|
|
let result.stacktrace = readfile(s:oneoff_stk)
|
|
if empty(result.ex)
|
|
let result.status = ['done']
|
|
else
|
|
let result.status = ['eval-error', 'done']
|
|
endif
|
|
call filter(result, '!empty(v:val)')
|
|
if v:shell_error && get(result, 'ex', '') ==# ''
|
|
throw 'Error running Java: '.get(split(captured, "\n"), -1, '')
|
|
else
|
|
return result
|
|
endif
|
|
endfunction
|
|
|
|
let s:oneoff = {}
|
|
|
|
function! s:oneoff.user_ns() abort
|
|
return 'user'
|
|
endfunction
|
|
|
|
function! s:oneoff.path() dict abort
|
|
return self._path
|
|
endfunction
|
|
|
|
function! s:oneoff.eval(expr, options) dict abort
|
|
if !empty(get(a:options, 'session', 1))
|
|
throw 'Fireplace: no live REPL connection'
|
|
endif
|
|
let result = s:spawning_eval(join(self.path(), has('win32') ? ';' : ':'),
|
|
\ a:expr, get(a:options, 'ns', self.user_ns()))
|
|
if has_key(a:options, 'id')
|
|
let result.id = a:options.id
|
|
endif
|
|
return result
|
|
endfunction
|
|
|
|
function! s:oneoff.message(...) abort
|
|
throw 'Fireplace: no live REPL connection'
|
|
endfunction
|
|
|
|
let s:oneoff.piggieback = s:oneoff.message
|
|
|
|
" Section: Client
|
|
|
|
function! s:buf() abort
|
|
if exists('s:input')
|
|
return s:input
|
|
elseif has_key(s:qffiles, expand('%:p'))
|
|
return s:qffiles[expand('%:p')].buffer
|
|
else
|
|
return '%'
|
|
endif
|
|
endfunction
|
|
|
|
function! s:includes_file(file, path) abort
|
|
let file = substitute(a:file, '\C^zipfile:\(.*\)::', '\1/', '')
|
|
let file = substitute(file, '\C^fugitive:[\/][\/]\(.*\)\.git[\/][\/][^\/]\+[\/]', '\1', '')
|
|
for path in a:path
|
|
if file[0 : len(path)-1] ==? path
|
|
return 1
|
|
endif
|
|
endfor
|
|
endfunction
|
|
|
|
function! s:path_extract(path)
|
|
let path = []
|
|
if a:path =~# '\.jar'
|
|
for elem in split(substitute(a:path, ',$', '', ''), ',')
|
|
if elem ==# ''
|
|
let path += ['.']
|
|
else
|
|
let path += split(glob(substitute(elem, '\\\ze[\\ ,]', '', 'g'), 1), "\n")
|
|
endif
|
|
endfor
|
|
endif
|
|
return path
|
|
endfunction
|
|
|
|
function! fireplace#path(...) abort
|
|
let buf = a:0 ? a:1 : s:buf()
|
|
for repl in s:repls
|
|
if s:includes_file(fnamemodify(bufname(buf), ':p'), repl.path())
|
|
return repl.path()
|
|
endif
|
|
endfor
|
|
return s:path_extract(getbufvar(buf, '&path'))
|
|
endfunction
|
|
|
|
function! fireplace#platform(...) abort
|
|
for [k, v] in items(s:repl_portfiles)
|
|
if getftime(k) != v.time
|
|
call s:unregister_connection(v.connection)
|
|
endif
|
|
endfor
|
|
|
|
let portfile = findfile('.nrepl-port', '.;')
|
|
if !empty(portfile)
|
|
call fireplace#register_port_file(portfile, fnamemodify(portfile, ':p:h'))
|
|
endif
|
|
silent doautocmd User FireplacePreConnect
|
|
|
|
let buf = a:0 ? a:1 : s:buf()
|
|
let root = simplify(fnamemodify(bufname(buf), ':p:s?[\/]$??'))
|
|
let previous = ""
|
|
while root !=# previous
|
|
if has_key(s:repl_paths, root)
|
|
return s:repl_paths[root]
|
|
endif
|
|
let previous = root
|
|
let root = fnamemodify(root, ':h')
|
|
endwhile
|
|
for repl in s:repls
|
|
if s:includes_file(fnamemodify(bufname(buf), ':p'), repl.path())
|
|
return repl
|
|
endif
|
|
endfor
|
|
let path = s:path_extract(getbufvar(buf, '&path'))
|
|
if !empty(path) && fnamemodify(bufname(buf), ':e') =~# '^cljx\=$'
|
|
return extend({'_path': path, 'nr': bufnr(buf)}, s:oneoff)
|
|
endif
|
|
throw 'Fireplace: :Connect to a REPL or install classpath.vim'
|
|
endfunction
|
|
|
|
function! fireplace#client(...) abort
|
|
let buf = a:0 ? a:1 : s:buf()
|
|
let client = fireplace#platform(buf)
|
|
if fnamemodify(bufname(buf), ':e') ==# 'cljs'
|
|
if !has_key(client, 'connection')
|
|
throw 'Fireplace: no live REPL connection'
|
|
endif
|
|
if empty(client.piggiebacks)
|
|
let result = client.piggieback('')
|
|
if has_key(result, 'ex')
|
|
return result
|
|
endif
|
|
endif
|
|
return client.piggiebacks[0]
|
|
endif
|
|
return client
|
|
endfunction
|
|
|
|
function! fireplace#message(payload, ...) abort
|
|
let client = fireplace#client()
|
|
let payload = copy(a:payload)
|
|
if !has_key(payload, 'ns')
|
|
let payload.ns = fireplace#ns()
|
|
elseif empty(payload.ns)
|
|
unlet payload.ns
|
|
endif
|
|
return call(client.message, [payload] + a:000, client)
|
|
endfunction
|
|
|
|
function! fireplace#op_available(op) abort
|
|
try
|
|
let client = fireplace#platform()
|
|
if has_key(client, 'connection')
|
|
return client.connection.has_op(a:op)
|
|
endif
|
|
catch /^Fireplace: :Connect to a REPL/
|
|
endtry
|
|
endfunction
|
|
|
|
function! fireplace#findresource(resource, ...) abort
|
|
if a:resource ==# ''
|
|
return ''
|
|
endif
|
|
let resource = a:resource
|
|
if a:0 > 2 && type(a:3) == type([])
|
|
let suffixes = a:3
|
|
else
|
|
let suffixes = [''] + split(get(a:000, 2, ''), ',')
|
|
endif
|
|
for dir in a:0 ? a:1 : fireplace#path()
|
|
for suffix in suffixes
|
|
if fnamemodify(dir, ':e') ==# 'jar' && index(fireplace#jar_contents(dir), resource . suffix) >= 0
|
|
return 'zipfile:' . dir . '::' . resource . suffix
|
|
elseif filereadable(dir . '/' . resource . suffix)
|
|
return dir . (exists('+shellslash') && !&shellslash ? '\' : '/') . resource . suffix
|
|
endif
|
|
endfor
|
|
endfor
|
|
return ''
|
|
endfunction
|
|
|
|
function! s:output_response(response) abort
|
|
let substitution_pat = '\e\[[0-9;]*m\|\r\|\n$'
|
|
if get(a:response, 'err', '') !=# ''
|
|
echohl ErrorMSG
|
|
echo substitute(a:response.err, substitution_pat, '', 'g')
|
|
echohl NONE
|
|
endif
|
|
if get(a:response, 'out', '') !=# ''
|
|
echo substitute(a:response.out, substitution_pat, '', 'g')
|
|
endif
|
|
endfunction
|
|
|
|
function! s:eval(expr, ...) abort
|
|
let options = a:0 ? copy(a:1) : {}
|
|
let client = fireplace#client()
|
|
if !has_key(options, 'ns')
|
|
let options.ns = fireplace#ns()
|
|
endif
|
|
return client.eval(a:expr, options)
|
|
endfunction
|
|
|
|
function! s:temp_response(response) abort
|
|
let output = []
|
|
if get(a:response, 'err', '') !=# ''
|
|
let output = map(split(a:response.err, "\n"), '";!!".v:val')
|
|
endif
|
|
if get(a:response, 'out', '') !=# ''
|
|
let output = map(split(a:response.out, "\n"), '";".v:val')
|
|
endif
|
|
if has_key(a:response, 'value')
|
|
let output += [a:response.value]
|
|
endif
|
|
let temp = tempname().'.clj'
|
|
call writefile(output, temp)
|
|
return temp
|
|
endfunction
|
|
|
|
if !exists('s:history')
|
|
let s:history = []
|
|
endif
|
|
|
|
if !exists('s:qffiles')
|
|
let s:qffiles = {}
|
|
endif
|
|
|
|
function! s:qfentry(entry) abort
|
|
if !has_key(a:entry, 'tempfile')
|
|
let a:entry.tempfile = s:temp_response(a:entry.response)
|
|
endif
|
|
let s:qffiles[a:entry.tempfile] = a:entry
|
|
return {'filename': a:entry.tempfile, 'text': a:entry.code, 'type': 'E'}
|
|
endfunction
|
|
|
|
function! s:qfhistory() abort
|
|
let list = []
|
|
for entry in reverse(s:history)
|
|
if !has_key(entry, 'tempfile')
|
|
let entry.tempfile = s:temp_response(entry.response)
|
|
endif
|
|
call extend(list, [s:qfentry(entry)])
|
|
endfor
|
|
return list
|
|
endfunction
|
|
|
|
function! fireplace#session_eval(expr, ...) abort
|
|
let response = s:eval(a:expr, a:0 ? a:1 : {})
|
|
|
|
if !empty(get(response, 'value', '')) || !empty(get(response, 'err', ''))
|
|
call insert(s:history, {'buffer': bufnr(''), 'code': a:expr, 'ns': fireplace#ns(), 'response': response})
|
|
endif
|
|
if len(s:history) > &history
|
|
call remove(s:history, &history, -1)
|
|
endif
|
|
|
|
if !empty(get(response, 'stacktrace', []))
|
|
let nr = 0
|
|
if has_key(s:qffiles, expand('%:p'))
|
|
let nr = winbufnr(s:qffiles[expand('%:p')].buffer)
|
|
endif
|
|
if nr != -1
|
|
call setloclist(nr, fireplace#quickfix_for(response.stacktrace))
|
|
endif
|
|
endif
|
|
|
|
call s:output_response(response)
|
|
|
|
if get(response, 'ex', '') !=# ''
|
|
let err = 'Clojure: '.response.ex
|
|
elseif has_key(response, 'value')
|
|
return response.value
|
|
else
|
|
let err = 'fireplace.vim: Something went wrong: '.string(response)
|
|
endif
|
|
throw err
|
|
endfunction
|
|
|
|
function! fireplace#eval(...) abort
|
|
return call('fireplace#session_eval', a:000)
|
|
endfunction
|
|
|
|
function! fireplace#echo_session_eval(expr, ...) abort
|
|
try
|
|
echo fireplace#session_eval(a:expr, a:0 ? a:1 : {})
|
|
catch /^Clojure:/
|
|
catch
|
|
echohl ErrorMSG
|
|
echomsg v:exception
|
|
echohl NONE
|
|
endtry
|
|
return ''
|
|
endfunction
|
|
|
|
function! fireplace#evalprint(expr) abort
|
|
return fireplace#echo_session_eval(a:expr)
|
|
endfunction
|
|
|
|
function! fireplace#macroexpand(fn, form) abort
|
|
return fireplace#evalprint('('.a:fn.' (quote '.a:form.'))')
|
|
endfunction
|
|
|
|
let g:fireplace#reader =
|
|
\ '(symbol ((fn *vimify [x]' .
|
|
\ ' (cond' .
|
|
\ ' (map? x) (str "{" (apply str (interpose ", " (map (fn [[k v]] (str (*vimify k) ": " (*vimify v))) x))) "}")' .
|
|
\ ' (coll? x) (str "[" (apply str (interpose ", " (map *vimify x))) "]")' .
|
|
\ ' (true? x) "1"' .
|
|
\ ' (false? x) "0"' .
|
|
\ ' (number? x) (pr-str x)' .
|
|
\ ' (keyword? x) (pr-str (name x))' .
|
|
\ ' :else (pr-str (str x)))) %s))'
|
|
|
|
function! fireplace#evalparse(expr, ...) abort
|
|
let options = extend({'session': 0}, a:0 ? a:1 : {})
|
|
let response = s:eval(printf(g:fireplace#reader, a:expr), options)
|
|
call s:output_response(response)
|
|
|
|
if get(response, 'ex', '') !=# ''
|
|
let err = 'Clojure: '.response.ex
|
|
elseif has_key(response, 'value')
|
|
return empty(response.value) ? '' : eval(response.value)
|
|
else
|
|
let err = 'fireplace.vim: Something went wrong: '.string(response)
|
|
endif
|
|
throw err
|
|
endfunction
|
|
|
|
function! fireplace#query(expr, ...) abort
|
|
return fireplace#evalparse(a:expr, a:0 ? a:1 : {})
|
|
endfunction
|
|
|
|
" Section: Quickfix
|
|
|
|
function! s:qfmassage(line, path) abort
|
|
let entry = {'text': a:line}
|
|
let match = matchlist(a:line, '\(\S\+\)\s\=(\(\S\+\))')
|
|
if !empty(match)
|
|
let [_, class, file; __] = match
|
|
if file =~# '^NO_SOURCE_FILE:' || file !~# ':'
|
|
let entry.resource = ''
|
|
let entry.lnum = 0
|
|
else
|
|
let truncated = substitute(class, '\.[A-Za-z0-9_]\+\%([$/].*\)$', '', '')
|
|
let entry.resource = tr(truncated, '.', '/').'/'.split(file, ':')[0]
|
|
let entry.lnum = split(file, ':')[-1]
|
|
endif
|
|
let entry.filename = fireplace#findresource(entry.resource, a:path)
|
|
if empty(entry.filename)
|
|
let entry.lnum = 0
|
|
else
|
|
let entry.text = class
|
|
endif
|
|
endif
|
|
return entry
|
|
endfunction
|
|
|
|
function! fireplace#quickfix_for(stacktrace) abort
|
|
let path = fireplace#path()
|
|
return map(copy(a:stacktrace), 's:qfmassage(v:val, path)')
|
|
endfunction
|
|
|
|
function! s:massage_quickfix() abort
|
|
let p = substitute(matchstr(','.&errorformat, ',classpath\zs\%(\\.\|[^\,]\)*'), '\\\ze[\,%]', '', 'g')
|
|
if empty(p)
|
|
return
|
|
endif
|
|
let path = p[0] ==# ',' ? s:path_extract(p[1:-1]) : split(p[1:-1], p[0])
|
|
let qflist = getqflist()
|
|
for entry in qflist
|
|
call extend(entry, s:qfmassage(get(entry, 'text', ''), path))
|
|
endfor
|
|
call setqflist(qflist, 'replace')
|
|
endfunction
|
|
|
|
augroup fireplace_quickfix
|
|
autocmd!
|
|
autocmd QuickFixCmdPost make,cfile,cgetfile call s:massage_quickfix()
|
|
augroup END
|
|
|
|
" Section: Eval
|
|
|
|
let fireplace#skip = 'synIDattr(synID(line("."),col("."),1),"name") =~? "comment\\|string\\|char\\|regexp"'
|
|
|
|
function! s:opfunc(type) abort
|
|
let sel_save = &selection
|
|
let cb_save = &clipboard
|
|
let reg_save = @@
|
|
try
|
|
set selection=inclusive clipboard-=unnamed clipboard-=unnamedplus
|
|
if type(a:type) == type(0)
|
|
let open = '[[{(]'
|
|
let close = '[]})]'
|
|
if getline('.')[col('.')-1] =~# close
|
|
let [line1, col1] = searchpairpos(open, '', close, 'bn', g:fireplace#skip)
|
|
let [line2, col2] = [line('.'), col('.')]
|
|
else
|
|
let [line1, col1] = searchpairpos(open, '', close, 'bcn', g:fireplace#skip)
|
|
let [line2, col2] = searchpairpos(open, '', close, 'n', g:fireplace#skip)
|
|
endif
|
|
while col1 > 1 && getline(line1)[col1-2] =~# '[#''`~@]'
|
|
let col1 -= 1
|
|
endwhile
|
|
call setpos("'[", [0, line1, col1, 0])
|
|
call setpos("']", [0, line2, col2, 0])
|
|
silent exe "normal! `[v`]y"
|
|
elseif a:type =~# '^.$'
|
|
silent exe "normal! `<" . a:type . "`>y"
|
|
elseif a:type ==# 'line'
|
|
silent exe "normal! '[V']y"
|
|
elseif a:type ==# 'block'
|
|
silent exe "normal! `[\<C-V>`]y"
|
|
elseif a:type ==# 'outer'
|
|
call searchpair('(','',')', 'Wbcr', g:fireplace#skip)
|
|
silent exe "normal! vaby"
|
|
else
|
|
silent exe "normal! `[v`]y"
|
|
endif
|
|
redraw
|
|
return repeat("\n", line("'<")-1) . repeat(" ", col("'<")-1) . @@
|
|
finally
|
|
let @@ = reg_save
|
|
let &selection = sel_save
|
|
let &clipboard = cb_save
|
|
endtry
|
|
endfunction
|
|
|
|
function! s:filterop(type) abort
|
|
let reg_save = @@
|
|
let sel_save = &selection
|
|
let cb_save = &clipboard
|
|
try
|
|
set selection=inclusive clipboard-=unnamed clipboard-=unnamedplus
|
|
let expr = s:opfunc(a:type)
|
|
let @@ = fireplace#session_eval(matchstr(expr, '^\n\+').expr).matchstr(expr, '\n\+$')
|
|
if @@ !~# '^\n*$'
|
|
normal! gvp
|
|
endif
|
|
catch /^Clojure:/
|
|
return ''
|
|
finally
|
|
let @@ = reg_save
|
|
let &selection = sel_save
|
|
let &clipboard = cb_save
|
|
endtry
|
|
endfunction
|
|
|
|
function! s:macroexpandop(type) abort
|
|
call fireplace#macroexpand("clojure.walk/macroexpand-all", s:opfunc(a:type))
|
|
endfunction
|
|
|
|
function! s:macroexpand1op(type) abort
|
|
call fireplace#macroexpand("macroexpand-1", s:opfunc(a:type))
|
|
endfunction
|
|
|
|
function! s:printop(type) abort
|
|
let s:todo = s:opfunc(a:type)
|
|
call feedkeys("\<Plug>FireplacePrintLast")
|
|
endfunction
|
|
|
|
function! s:print_last() abort
|
|
call fireplace#echo_session_eval(s:todo, {'file_path': s:buffer_path()})
|
|
return ''
|
|
endfunction
|
|
|
|
function! s:editop(type) abort
|
|
call feedkeys(eval('"\'.&cedit.'"') . "\<Home>", 'n')
|
|
let input = s:input(substitute(substitute(substitute(
|
|
\ s:opfunc(a:type), "\s*;[^\n\"]*\\%(\n\\@=\\|$\\)", '', 'g'),
|
|
\ '\n\+\s*', ' ', 'g'),
|
|
\ '^\s*', '', ''))
|
|
if input !=# ''
|
|
call fireplace#echo_session_eval(input)
|
|
endif
|
|
endfunction
|
|
|
|
function! s:Eval(bang, line1, line2, count, args) abort
|
|
let options = {}
|
|
if a:args !=# ''
|
|
let expr = a:args
|
|
else
|
|
if a:count ==# 0
|
|
let open = '[[{(]'
|
|
let close = '[]})]'
|
|
let [line1, col1] = searchpairpos(open, '', close, 'bcrn', g:fireplace#skip)
|
|
let [line2, col2] = searchpairpos(open, '', close, 'rn', g:fireplace#skip)
|
|
if !line1 && !line2
|
|
let [line1, col1] = searchpairpos(open, '', close, 'brn', g:fireplace#skip)
|
|
let [line2, col2] = searchpairpos(open, '', close, 'crn', g:fireplace#skip)
|
|
endif
|
|
while col1 > 1 && getline(line1)[col1-2] =~# '[#''`~@]'
|
|
let col1 -= 1
|
|
endwhile
|
|
else
|
|
let line1 = a:line1
|
|
let line2 = a:line2
|
|
let col1 = 1
|
|
let col2 = strlen(getline(line2))
|
|
endif
|
|
if !line1 || !line2
|
|
return ''
|
|
endif
|
|
let options.file_path = s:buffer_path()
|
|
let expr = repeat("\n", line1-1).repeat(" ", col1-1)
|
|
if line1 == line2
|
|
let expr .= getline(line1)[col1-1 : col2-1]
|
|
else
|
|
let expr .= getline(line1)[col1-1 : -1] . "\n"
|
|
\ . join(map(getline(line1+1, line2-1), 'v:val . "\n"'))
|
|
\ . getline(line2)[0 : col2-1]
|
|
endif
|
|
if a:bang
|
|
exe line1.','.line2.'delete _'
|
|
endif
|
|
endif
|
|
if a:bang
|
|
try
|
|
let result = fireplace#session_eval(expr, options)
|
|
if a:args !=# ''
|
|
call append(a:line1, result)
|
|
exe a:line1
|
|
else
|
|
call append(a:line1-1, result)
|
|
exe a:line1-1
|
|
endif
|
|
catch /^Clojure:/
|
|
endtry
|
|
else
|
|
call fireplace#echo_session_eval(expr, options)
|
|
endif
|
|
return ''
|
|
endfunction
|
|
|
|
" If we call input() directly inside a try, and the user opens the command
|
|
" line window and tries to switch out of it (such as with ctrl-w), Vim will
|
|
" crash when the command line window closes. Adding an indirect function call
|
|
" works around this.
|
|
function! s:actually_input(...) abort
|
|
return call(function('input'), a:000)
|
|
endfunction
|
|
|
|
function! s:input(default) abort
|
|
if !exists('g:FIREPLACE_HISTORY') || type(g:FIREPLACE_HISTORY) != type([])
|
|
unlet! g:FIREPLACE_HISTORY
|
|
let g:FIREPLACE_HISTORY = []
|
|
endif
|
|
try
|
|
let s:input = bufnr('%')
|
|
let s:oldhist = s:histswap(g:FIREPLACE_HISTORY)
|
|
return s:actually_input(fireplace#ns().'=> ', a:default, 'customlist,fireplace#eval_complete')
|
|
finally
|
|
unlet! s:input
|
|
if exists('s:oldhist')
|
|
let g:FIREPLACE_HISTORY = s:histswap(s:oldhist)
|
|
endif
|
|
endtry
|
|
endfunction
|
|
|
|
function! s:inputclose() abort
|
|
let l = substitute(getcmdline(), '"\%(\\.\|[^"]\)*"\|\\.', '', 'g')
|
|
let open = len(substitute(l, '[^(]', '', 'g'))
|
|
let close = len(substitute(l, '[^)]', '', 'g'))
|
|
if open - close == 1
|
|
return ")\<CR>"
|
|
else
|
|
return ")"
|
|
endif
|
|
endfunction
|
|
|
|
function! s:inputeval() abort
|
|
let input = s:input('')
|
|
redraw
|
|
if input !=# ''
|
|
call fireplace#echo_session_eval(input)
|
|
endif
|
|
return ''
|
|
endfunction
|
|
|
|
function! s:recall() abort
|
|
try
|
|
cnoremap <expr> ) <SID>inputclose()
|
|
let input = s:input('(')
|
|
if input =~# '^(\=$'
|
|
return ''
|
|
else
|
|
return fireplace#session_eval(input)
|
|
endif
|
|
catch /^Clojure:/
|
|
return ''
|
|
finally
|
|
silent! cunmap )
|
|
endtry
|
|
endfunction
|
|
|
|
function! s:histswap(list) abort
|
|
let old = []
|
|
for i in range(1, histnr('@') * (histnr('@') > 0))
|
|
call extend(old, [histget('@', i)])
|
|
endfor
|
|
call histdel('@')
|
|
for entry in a:list
|
|
call histadd('@', entry)
|
|
endfor
|
|
return old
|
|
endfunction
|
|
|
|
nnoremap <silent> <Plug>FireplacePrintLast :exe <SID>print_last()<CR>
|
|
nnoremap <silent> <Plug>FireplacePrint :<C-U>set opfunc=<SID>printop<CR>g@
|
|
xnoremap <silent> <Plug>FireplacePrint :<C-U>call <SID>printop(visualmode())<CR>
|
|
nnoremap <silent> <Plug>FireplaceCountPrint :<C-U>call <SID>printop(v:count)<CR>
|
|
|
|
nnoremap <silent> <Plug>FireplaceFilter :<C-U>set opfunc=<SID>filterop<CR>g@
|
|
xnoremap <silent> <Plug>FireplaceFilter :<C-U>call <SID>filterop(visualmode())<CR>
|
|
nnoremap <silent> <Plug>FireplaceCountFilter :<C-U>call <SID>filterop(v:count)<CR>
|
|
|
|
nnoremap <silent> <Plug>FireplaceMacroExpand :<C-U>set opfunc=<SID>macroexpandop<CR>g@
|
|
xnoremap <silent> <Plug>FireplaceMacroExpand :<C-U>call <SID>macroexpandop(visualmode())<CR>
|
|
nnoremap <silent> <Plug>FireplaceCountMacroExpand :<C-U>call <SID>macroexpandop(v:count)<CR>
|
|
nnoremap <silent> <Plug>Fireplace1MacroExpand :<C-U>set opfunc=<SID>macroexpand1op<CR>g@
|
|
xnoremap <silent> <Plug>Fireplace1MacroExpand :<C-U>call <SID>macroexpand1op(visualmode())<CR>
|
|
nnoremap <silent> <Plug>FireplaceCount1MacroExpand :<C-U>call <SID>macroexpand1op(v:count)<CR>
|
|
|
|
nnoremap <silent> <Plug>FireplaceEdit :<C-U>set opfunc=<SID>editop<CR>g@
|
|
xnoremap <silent> <Plug>FireplaceEdit :<C-U>call <SID>editop(visualmode())<CR>
|
|
nnoremap <silent> <Plug>FireplaceCountEdit :<C-U>call <SID>editop(v:count)<CR>
|
|
|
|
nnoremap <Plug>FireplacePrompt :exe <SID>inputeval()<CR>
|
|
|
|
noremap! <Plug>FireplaceRecall <C-R>=<SID>recall()<CR>
|
|
|
|
function! s:Last(bang, count) abort
|
|
if len(s:history) < a:count
|
|
return 'echoerr "History entry not found"'
|
|
endif
|
|
let history = s:qfhistory()
|
|
let last = s:qfhistory()[a:count-1]
|
|
execute 'pedit '.last.filename
|
|
if !&previewwindow
|
|
let nr = winnr()
|
|
wincmd p
|
|
wincmd P
|
|
endif
|
|
call setloclist(0, history)
|
|
silent exe 'llast '.(len(history)-a:count+1)
|
|
if exists('nr') && a:bang
|
|
wincmd p
|
|
exe nr.'wincmd w'
|
|
endif
|
|
return ''
|
|
endfunction
|
|
|
|
function! s:set_up_eval() abort
|
|
command! -buffer -bang -range=0 -nargs=? -complete=customlist,fireplace#eval_complete Eval :exe s:Eval(<bang>0, <line1>, <line2>, <count>, <q-args>)
|
|
command! -buffer -bang -bar -count=1 Last exe s:Last(<bang>0, <count>)
|
|
|
|
if get(g:, 'fireplace_no_maps') | return | endif
|
|
|
|
nmap <buffer> cp <Plug>FireplacePrint
|
|
nmap <buffer> cpp <Plug>FireplaceCountPrint
|
|
|
|
nmap <buffer> c! <Plug>FireplaceFilter
|
|
nmap <buffer> c!! <Plug>FireplaceCountFilter
|
|
|
|
nmap <buffer> cm <Plug>FireplaceMacroExpand
|
|
nmap <buffer> cmm <Plug>FireplaceCountMacroExpand
|
|
nmap <buffer> c1m <Plug>Fireplace1MacroExpand
|
|
nmap <buffer> c1mm <Plug>FireplaceCount1MacroExpand
|
|
|
|
nmap <buffer> cq <Plug>FireplaceEdit
|
|
nmap <buffer> cqq <Plug>FireplaceCountEdit
|
|
|
|
nmap <buffer> cqp <Plug>FireplacePrompt
|
|
exe 'nmap <buffer> cqc <Plug>FireplacePrompt' . &cedit . 'i'
|
|
|
|
map! <buffer> <C-R>( <Plug>FireplaceRecall
|
|
endfunction
|
|
|
|
function! s:set_up_historical() abort
|
|
setlocal readonly nomodifiable
|
|
nnoremap <buffer><silent>q :bdelete<CR>
|
|
endfunction
|
|
|
|
function! s:cmdwinenter() abort
|
|
setlocal filetype=clojure
|
|
endfunction
|
|
|
|
function! s:cmdwinleave() abort
|
|
setlocal filetype< omnifunc<
|
|
endfunction
|
|
|
|
augroup fireplace_eval
|
|
autocmd!
|
|
autocmd FileType clojure call s:set_up_eval()
|
|
autocmd BufReadPost * if has_key(s:qffiles, expand('<amatch>:p')) |
|
|
\ call s:set_up_historical() |
|
|
\ endif
|
|
autocmd CmdWinEnter @ if exists('s:input') | call s:cmdwinenter() | endif
|
|
autocmd CmdWinLeave @ if exists('s:input') | call s:cmdwinleave() | endif
|
|
augroup END
|
|
|
|
" Section: :Require
|
|
|
|
function! s:Require(bang, echo, ns) abort
|
|
if &autowrite || &autowriteall
|
|
silent! wall
|
|
endif
|
|
if expand('%:e') ==# 'cljs'
|
|
let cmd = '(load-file '.s:str(tr(a:ns ==# '' ? fireplace#ns() : a:ns, '-.', '_/').'.cljs').')'
|
|
else
|
|
let cmd = ('(require '.s:qsym(a:ns ==# '' ? fireplace#ns() : a:ns).' :reload'.(a:bang ? '-all' : '').')')
|
|
endif
|
|
if a:echo
|
|
echo cmd
|
|
endif
|
|
try
|
|
call fireplace#session_eval(cmd, {'ns': fireplace#client().user_ns()})
|
|
return ''
|
|
catch /^Clojure:.*/
|
|
return ''
|
|
endtry
|
|
endfunction
|
|
|
|
function! s:set_up_require() abort
|
|
command! -buffer -bar -bang -complete=customlist,fireplace#ns_complete -nargs=? Require :exe s:Require(<bang>0, 1, <q-args>)
|
|
|
|
if get(g:, 'fireplace_no_maps') | return | endif
|
|
nnoremap <silent><buffer> cpr :if expand('%:e') ==# 'cljs'<Bar>Require<Bar>else<Bar>RunTests<Bar>endif<CR>
|
|
endfunction
|
|
|
|
augroup fireplace_require
|
|
autocmd!
|
|
autocmd FileType clojure call s:set_up_require()
|
|
augroup END
|
|
|
|
" Section: Go to source
|
|
|
|
function! fireplace#info(symbol) abort
|
|
if fireplace#op_available('info')
|
|
let response = fireplace#message({'op': 'info', 'symbol': a:symbol})[0]
|
|
if type(get(response, 'value')) == type({})
|
|
return response.value
|
|
elseif has_key(response, 'file')
|
|
return response
|
|
endif
|
|
endif
|
|
let cmd =
|
|
\ '(if-let [m (meta (resolve ' . s:qsym(a:symbol) .'))]'
|
|
\ . ' {:name (:name m)'
|
|
\ . ' :ns (:ns m)'
|
|
\ . ' :resource (:file m)'
|
|
\ . ' :line (:line m)'
|
|
\ . ' :doc (:doc m)'
|
|
\ . ' :arglists-str (str (:arglists m))}'
|
|
\ . ' {})'
|
|
return fireplace#evalparse(cmd)
|
|
endfunction
|
|
|
|
function! fireplace#source(symbol) abort
|
|
let info = fireplace#info(a:symbol)
|
|
|
|
let file = ''
|
|
if !empty(get(info, 'resource'))
|
|
let file = fireplace#findresource(info.resource)
|
|
elseif get(info, 'file') =~# '^/\|^\w:\\' && filereadable(info.file)
|
|
let file = info.file
|
|
endif
|
|
|
|
if !empty(file) && !empty(get(info, 'line'))
|
|
return '+' . info.line . ' ' . fnameescape(file)
|
|
endif
|
|
return ''
|
|
endfunction
|
|
|
|
function! s:Edit(cmd, keyword) abort
|
|
try
|
|
if a:keyword =~# '^\k\+[/.]$'
|
|
let location = fireplace#findfile(a:keyword[0: -2])
|
|
elseif a:keyword =~# '^\k\+\.[^/.]\+$'
|
|
let location = fireplace#findfile(a:keyword)
|
|
else
|
|
let location = fireplace#source(a:keyword)
|
|
endif
|
|
catch /^Clojure:/
|
|
return ''
|
|
endtry
|
|
if location !=# ''
|
|
if matchstr(location, '^+\d\+ \zs.*') ==# fnameescape(expand('%:p')) && a:cmd ==# 'edit'
|
|
normal! m'
|
|
return matchstr(location, '\d\+')
|
|
else
|
|
return a:cmd.' '.location.'|let &l:path = '.string(&l:path)
|
|
endif
|
|
endif
|
|
let v:errmsg = "Couldn't find source for ".a:keyword
|
|
return 'echoerr v:errmsg'
|
|
endfunction
|
|
|
|
nnoremap <silent> <Plug>FireplaceDjump :<C-U>exe <SID>Edit('edit', expand('<cword>'))<CR>
|
|
nnoremap <silent> <Plug>FireplaceDsplit :<C-U>exe <SID>Edit('split', expand('<cword>'))<CR>
|
|
nnoremap <silent> <Plug>FireplaceDtabjump :<C-U>exe <SID>Edit('tabedit', expand('<cword>'))<CR>
|
|
|
|
function! s:set_up_source() abort
|
|
setlocal define=^\\s*(def\\w*
|
|
command! -bar -buffer -nargs=1 -complete=customlist,fireplace#eval_complete Djump :exe s:Edit('edit', <q-args>)
|
|
command! -bar -buffer -nargs=1 -complete=customlist,fireplace#eval_complete Dsplit :exe s:Edit('split', <q-args>)
|
|
|
|
if get(g:, 'fireplace_no_maps') | return | endif
|
|
nmap <buffer> [<C-D> <Plug>FireplaceDjump
|
|
nmap <buffer> ]<C-D> <Plug>FireplaceDjump
|
|
nmap <buffer> <C-W><C-D> <Plug>FireplaceDsplit
|
|
nmap <buffer> <C-W>d <Plug>FireplaceDsplit
|
|
nmap <buffer> <C-W>gd <Plug>FireplaceDtabjump
|
|
endfunction
|
|
|
|
augroup fireplace_source
|
|
autocmd!
|
|
autocmd FileType clojure call s:set_up_source()
|
|
augroup END
|
|
|
|
" Section: Go to file
|
|
|
|
function! fireplace#findfile(path) abort
|
|
let path = a:path
|
|
if a:path !~# '/'
|
|
let path = tr(a:path, '.-', '/_')
|
|
else
|
|
let path = substitute(a:path, '^/', '', '')
|
|
endif
|
|
let resource = fireplace#findresource(path, fireplace#path(), 0, &suffixesadd)
|
|
if !empty(resource)
|
|
return resource
|
|
elseif fnamemodify(a:path, ':p') ==# a:path && filereadable(a:path)
|
|
return path
|
|
elseif a:path[0] !=# '/' && filereadable(expand('%:h') . '/' . path)
|
|
return expand('%:h') . '/' . path
|
|
endif
|
|
return ''
|
|
endfunction
|
|
|
|
function! s:GF(cmd, file) abort
|
|
if a:file =~# '^\w[[:alnum:]_/]*$' &&
|
|
\ synIDattr(synID(line("."),col("."),1),"name") =~# 'String'
|
|
let file = substitute(expand('%:p'), '[^\/:]*$', '', '').a:file.'.'.expand('%:e')
|
|
elseif a:file =~# '^[^/]*/[^/.]*$' && a:file =~# '^\k\+$'
|
|
let [file, jump] = split(a:file, "/")
|
|
if file !~# '\.'
|
|
try
|
|
let file = fireplace#evalparse('((ns-aliases *ns*) '.s:qsym(file).' '.s:qsym(file).')')
|
|
catch /^Clojure:/
|
|
endtry
|
|
endif
|
|
let file = fireplace#findfile(file)
|
|
else
|
|
let file = fireplace#findfile(a:file)
|
|
endif
|
|
if file ==# ''
|
|
let v:errmsg = "Couldn't find file for ".a:file
|
|
return 'echoerr v:errmsg'
|
|
endif
|
|
return a:cmd .
|
|
\ (exists('jump') ? ' +sil!\ djump\ ' . jump : '') .
|
|
\ ' ' . fnameescape(file) .
|
|
\ '| let &l:path = ' . string(&l:path)
|
|
endfunction
|
|
|
|
nnoremap <silent> <Plug>FireplaceEditFile :<C-U>exe <SID>GF('edit', expand('<cfile>'))<CR>
|
|
nnoremap <silent> <Plug>FireplaceSplitFile :<C-U>exe <SID>GF('split', expand('<cfile>'))<CR>
|
|
nnoremap <silent> <Plug>FireplaceTabeditFile :<C-U>exe <SID>GF('tabedit', expand('<cfile>'))<CR>
|
|
|
|
function! s:set_up_go_to_file() abort
|
|
if expand('%:e') ==# 'cljs'
|
|
setlocal suffixesadd=.cljs,.cljx,.clj,.java
|
|
else
|
|
setlocal suffixesadd=.clj,.cljx,.cljs,.java
|
|
endif
|
|
|
|
if get(g:, 'fireplace_no_maps') | return | endif
|
|
nmap <buffer> gf <Plug>FireplaceEditFile
|
|
nmap <buffer> <C-W>f <Plug>FireplaceSplitFile
|
|
nmap <buffer> <C-W><C-F> <Plug>FireplaceSplitFile
|
|
nmap <buffer> <C-W>gf <Plug>FireplaceTabeditFile
|
|
endfunction
|
|
|
|
augroup fireplace_go_to_file
|
|
autocmd!
|
|
autocmd FileType clojure call s:set_up_go_to_file()
|
|
augroup END
|
|
|
|
" Section: Documentation
|
|
|
|
function! s:buffer_path(...) abort
|
|
let buffer = a:0 ? a:1 : s:buf()
|
|
if getbufvar(buffer, '&buftype') =~# '^no'
|
|
return ''
|
|
endif
|
|
let path = substitute(fnamemodify(bufname(buffer), ':p'), '\C^zipfile:\(.*\)::', '\1/', '')
|
|
for dir in fireplace#path(buffer)
|
|
if dir !=# '' && path[0 : strlen(dir)-1] ==# dir && path[strlen(dir)] =~# '[\/]'
|
|
return path[strlen(dir)+1:-1]
|
|
endif
|
|
endfor
|
|
return ''
|
|
endfunction
|
|
|
|
function! fireplace#ns(...) abort
|
|
let buffer = a:0 ? a:1 : s:buf()
|
|
if !empty(getbufvar(buffer, 'fireplace_ns'))
|
|
return getbufvar(buffer, 'fireplace_ns')
|
|
endif
|
|
let head = getbufline(buffer, 1, 500)
|
|
let blank = '^\s*\%(;.*\)\=$'
|
|
call filter(head, 'v:val !~# blank')
|
|
let keyword_group = '[A-Za-z0-9_?*!+/=<>.-]'
|
|
let lines = join(head[0:49], ' ')
|
|
let lines = substitute(lines, '"\%(\\.\|[^"]\)*"\|\\.', '', 'g')
|
|
let lines = substitute(lines, '\^\={[^{}]*}', '', '')
|
|
let lines = substitute(lines, '\^:'.keyword_group.'\+', '', 'g')
|
|
let ns = matchstr(lines, '\C^(\s*\%(in-ns\s*''\|ns\s\+\)\zs'.keyword_group.'\+\ze')
|
|
if ns !=# ''
|
|
return ns
|
|
endif
|
|
let path = s:buffer_path(buffer)
|
|
return s:to_ns(path ==# '' ? fireplace#client(buffer).user_ns() : path)
|
|
endfunction
|
|
|
|
function! s:Lookup(ns, macro, arg) abort
|
|
try
|
|
let response = s:eval('('.a:ns.'/'.a:macro.' '.a:arg.')', {'session': 0})
|
|
call s:output_response(response)
|
|
catch /^Clojure:/
|
|
catch /.*/
|
|
echohl ErrorMSG
|
|
echo v:exception
|
|
echohl None
|
|
endtry
|
|
return ''
|
|
endfunction
|
|
|
|
function! s:inputlist(label, entries) abort
|
|
let choices = [a:label]
|
|
for i in range(len(a:entries))
|
|
let choices += [printf('%2d. %s', i+1, a:entries[i])]
|
|
endfor
|
|
let choice = inputlist(choices)
|
|
if choice
|
|
return a:entries[choice-1]
|
|
else
|
|
return ''
|
|
endif
|
|
endfunction
|
|
|
|
function! s:Doc(symbol) abort
|
|
let info = fireplace#info(a:symbol)
|
|
if has_key(info, 'ns') && has_key(info, 'name')
|
|
echo info.ns . '/' . info.name
|
|
endif
|
|
if get(info, 'arglists-str', 'nil') !=# 'nil'
|
|
echo info['arglists-str']
|
|
endif
|
|
if !empty(get(info, 'doc', ''))
|
|
echo ' ' . info.doc
|
|
endif
|
|
return ''
|
|
endfunction
|
|
|
|
function! s:K() abort
|
|
let word = expand('<cword>')
|
|
let java_candidate = matchstr(word, '^\%(\w\+\.\)*\u\l[[:alnum:]$]*\ze\%(\.\|\/\w\+\)\=$')
|
|
if java_candidate !=# ''
|
|
return 'Javadoc '.java_candidate
|
|
else
|
|
return 'Doc '.word
|
|
endif
|
|
endfunction
|
|
|
|
nnoremap <Plug>FireplaceK :<C-R>=<SID>K()<CR><CR>
|
|
nnoremap <Plug>FireplaceSource :Source <C-R><C-W><CR>
|
|
|
|
function! s:set_up_doc() abort
|
|
command! -buffer -nargs=1 FindDoc :exe s:Lookup('clojure.repl', 'find-doc', printf('#"%s"', <q-args>))
|
|
command! -buffer -bar -nargs=1 Javadoc :exe s:Lookup('clojure.java.javadoc', 'javadoc', <q-args>)
|
|
command! -buffer -bar -nargs=1 -complete=customlist,fireplace#eval_complete Doc :exe s:Doc(<q-args>)
|
|
command! -buffer -bar -nargs=1 -complete=customlist,fireplace#eval_complete Source :exe s:Lookup('clojure.repl', 'source', <q-args>)
|
|
setlocal keywordprg=:Doc
|
|
|
|
if get(g:, 'fireplace_no_maps') | return | endif
|
|
if empty(mapcheck('K', 'n'))
|
|
nmap <buffer> K <Plug>FireplaceK
|
|
endif
|
|
nmap <buffer> [d <Plug>FireplaceSource
|
|
nmap <buffer> ]d <Plug>FireplaceSource
|
|
endfunction
|
|
|
|
augroup fireplace_doc
|
|
autocmd!
|
|
autocmd FileType clojure call s:set_up_doc()
|
|
augroup END
|
|
|
|
" Section: Tests
|
|
|
|
function! fireplace#capture_test_run(expr, ...) abort
|
|
let expr = '(try'
|
|
\ . ' (require ''clojure.test)'
|
|
\ . ' (binding [clojure.test/report (fn [m]'
|
|
\ . ' (case (:type m)'
|
|
\ . ' (:fail :error)'
|
|
\ . ' (let [{file :file line :line test :name} (meta (last clojure.test/*testing-vars*))]'
|
|
\ . ' (clojure.test/with-test-out'
|
|
\ . ' (println (clojure.string/join "\t" [file line (name (:type m)) test]))'
|
|
\ . ' (when (seq clojure.test/*testing-contexts*) (println (clojure.test/testing-contexts-str)))'
|
|
\ . ' (when-let [message (:message m)] (println message))'
|
|
\ . ' (println "expected:" (pr-str (:expected m)))'
|
|
\ . ' (println " actual:" (pr-str (:actual m)))))'
|
|
\ . ' ((.getRawRoot #''clojure.test/report) m)))]'
|
|
\ . ' ' . (a:0 ? a:1 : '') . a:expr . ')'
|
|
\ . ' (catch Exception e'
|
|
\ . ' (println (str e))'
|
|
\ . ' (println (clojure.string/join "\n" (.getStackTrace e)))))'
|
|
let qflist = []
|
|
let response = s:eval(expr, {'session': 0})
|
|
if !has_key(response, 'out')
|
|
call setqflist(fireplace#quickfix_for(get(response, 'stacktrace', [])))
|
|
return s:output_response(response)
|
|
endif
|
|
for line in split(response.out, "\n")
|
|
if line =~# '\t.*\t.*\t'
|
|
let entry = {'text': line}
|
|
let [resource, lnum, type, name] = split(line, "\t", 1)
|
|
let entry.lnum = lnum
|
|
let entry.type = (type ==# 'fail' ? 'W' : 'E')
|
|
let entry.text = name
|
|
if resource ==# 'NO_SOURCE_FILE'
|
|
let resource = ''
|
|
let entry.lnum = 0
|
|
endif
|
|
let entry.filename = fireplace#findresource(resource, fireplace#path())
|
|
if empty(entry.filename)
|
|
let entry.lnum = 0
|
|
endif
|
|
else
|
|
let entry = s:qfmassage(line, fireplace#path())
|
|
endif
|
|
call add(qflist, entry)
|
|
endfor
|
|
call setqflist(qflist)
|
|
let was_qf = &buftype ==# 'quickfix'
|
|
botright cwindow
|
|
if &buftype ==# 'quickfix' && !was_qf
|
|
wincmd p
|
|
endif
|
|
for winnr in range(1, winnr('$'))
|
|
if getwinvar(winnr, '&buftype') ==# 'quickfix'
|
|
call setwinvar(winnr, 'quickfix_title', a:expr)
|
|
return
|
|
endif
|
|
endfor
|
|
endfunction
|
|
|
|
function! s:RunTests(bang, ...) abort
|
|
if &autowrite || &autowriteall
|
|
silent! wall
|
|
endif
|
|
let reqs = map(copy(a:000), '"''".v:val')
|
|
let pre = '(clojure.core/require '.join(empty(a:000) ? ["'".fireplace#ns()] : reqs, ' ').' :reload) '
|
|
let expr = join(['(clojure.test/run-tests'] + reqs, ' ').')'
|
|
call fireplace#capture_test_run(expr, pre)
|
|
echo expr
|
|
endfunction
|
|
|
|
function! s:RunAllTests(bang, ...) abort
|
|
if a:0
|
|
let expr = '(clojure.test/run-all-tests #"'.join(a:000, '|').'")'
|
|
else
|
|
let expr = '(clojure.test/run-all-tests)'
|
|
endif
|
|
call fireplace#capture_test_run(expr)
|
|
echo expr
|
|
endfunction
|
|
|
|
function! s:set_up_tests() abort
|
|
command! -buffer -bar -bang -nargs=*
|
|
\ -complete=customlist,fireplace#ns_complete RunTests
|
|
\ call s:RunTests(<bang>0, <f-args>)
|
|
command! -buffer -bang -nargs=* RunAllTests
|
|
\ call s:RunAllTests(<bang>0, <f-args>)
|
|
endfunction
|
|
|
|
augroup fireplace_tests
|
|
autocmd!
|
|
autocmd FileType clojure call s:set_up_tests()
|
|
augroup END
|