" fireplace.vim - Clojure REPL tease " Maintainer: Tim Pope if exists("g:loaded_fireplace") || v:version < 700 || &cp finish endif let g:loaded_fireplace = 1 " File type {{{1 augroup fireplace_file_type autocmd! autocmd BufNewFile,BufReadPost *.clj setfiletype clojure augroup END " }}}1 " Escaping {{{1 function! s:str(string) return '"' . escape(a:string, '"\') . '"' endfunction function! s:qsym(symbol) 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 " }}}1 " Completion {{{1 let s:jar_contents = {} function! fireplace#jar_contents(path) abort if !exists('s:zipinfo') let s:zipinfo = executable('zipinfo') endif if !has_key(s:jar_contents, a:path) && s:zipinfo let s:jar_contents[a:path] = split(system('zipinfo -1 '.shellescape(a:path)), "\n") if v:shell_error return [] 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#client().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 function! fireplace#omnicomplete(findstart, base) abort if a:findstart let line = getline('.')[0 : col('.')-2] return col('.') - strlen(matchstr(line, '\k\+$')) - 1 else try let omnifier = '(fn [[k v]] (let [m (meta v)]' . \ ' {:word k :menu (pr-str (:arglists m (symbol ""))) :info (str " " (:doc m)) :kind (if (:arglists m) "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 " }}}1 " REPL client {{{1 let s:repl = {"requires": {}} if !exists('s:repls') let s:repls = [] let s:repl_paths = {} endif function! s:repl.path() dict abort return self.connection.path() endfunction function! s:repl.eval(expr, options) dict abort try let result = self.connection.eval(a:expr, a:options) catch /^\w\+ Connection Error:/ call filter(s:repl_paths, 'v:val isnot self') call filter(s:repls, 'v:val isnot self') throw v:exception endtry return result endfunction function! s:repl.require(lib) dict abort if a:lib !~# '^\%(user\)\=$' && !get(self.requires, a:lib, 0) let reload = has_key(self.requires, a:lib) ? ' :reload' : '' let self.requires[a:lib] = 0 let result = self.eval('(doto '.s:qsym(a:lib).' (require'.reload.') the-ns)', {'ns': 'user', 'session': 0}) let self.requires[a:lib] = !has_key(result, 'ex') if has_key(result, 'ex') return result.err endif endif return '' endfunction function! s:repl.includes_file(file) dict abort let file = substitute(a:file, '\C^zipfile:\(.*\)::', '\1/', '') for path in self.path() if file[0 : len(path)-1] ==? path return 1 endif endfor endfunction function! s:register_connection(conn, ...) call insert(s:repls, extend({'connection': a:conn}, deepcopy(s:repl))) if a:0 && a:1 !=# '' let s:repl_paths[a:1] = s:repls[0] endif return s:repls[0] endfunction " }}}1 " :Connect {{{1 command! -bar -complete=customlist,s:connect_complete -nargs=+ FireplaceConnect :exe s:Connect() function! fireplace#input_host_port() 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() return map(split(globpath(&runtimepath, 'autoload/*/fireplace_connection.vim'), "\n"), 'fnamemodify(v:val, ":h:t")') endfunction function! s:connect_complete(A, L, P) 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 = {proto}#fireplace_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(arg, ...) if a:arg =~# '^\w\+://' let [proto, arg] = split(a:arg, '://') elseif a:arg !=# '' 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 = {proto}#fireplace_connection#prompt() endif try let connection = {proto}#fireplace_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 ? expand(a:1) : input('Scope connection to: ', path, 'dir') if root !=# '' && root !=# '-' let s:repl_paths[fnamemodify(root, ':p:s?.\zs[\/]$??')] = client endif return '' endfunction augroup fireplace_connect autocmd! autocmd FileType clojure command! -bar -complete=customlist,s:connect_complete -nargs=+ Connect :FireplaceConnect augroup END " }}}1 " Java runner {{{1 let s:oneoff = {} function! s:oneoff.path() dict abort return classpath#split(self.classpath) endfunction 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:oneoff.eval(expr, options) dict abort if &verbose && get(options, 'session', 1) echohl WarningMSG echomsg "No REPL found. Running java clojure.main ..." echohl None endif if a:options.ns !=# '' && a:options.ns !=# 'user' let ns = '(require '.s:qsym(a:options.ns).') (in-ns '.s:qsym(a:options.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(self.classpath).' clojure.main -e ' . \ shellescape( \ '(clojure.core/binding [*out* (java.io.FileWriter. '.s:str(s:oneoff_out).')' . \ ' *err* (java.io.FileWriter. '.s:str(s:oneoff_err).')]' . \ ' (try' . \ ' (clojure.core/require ''clojure.repl) '.ns.'(clojure.core/spit '.s:str(s:oneoff_pr).' (clojure.core/pr-str (clojure.core/eval (clojure.core/read-string (clojure.core/slurp '.s:str(s:oneoff_in).')))))' . \ ' (catch Exception e' . \ ' (clojure.core/spit *err* (.toString e))' . \ ' (clojure.core/spit '.s:str(s:oneoff_ex).' (clojure.core/class e))' . \ ' (clojure.core/spit '.s:str(s:oneoff_stk).' (clojure.core/apply clojure.core/str (clojure.core/interpose "\n" (.getStackTrace e))))))' . \ ' nil)') let wtf = 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) call filter(result, '!empty(v:val)') if v:shell_error && get(result, 'ex', '') ==# '' throw 'Error running Clojure: '.wtf else return result endif endfunction function! s:oneoff.require(symbol) return '' endfunction " }}}1 " Client {{{1 function! s:client() abort silent doautocmd User FireplacePreConnect if exists('s:input') let buf = s:input elseif has_key(s:qffiles, expand('%:p')) let buf = s:qffiles[expand('%:p')].buffer else let buf = '%' endif 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 return fireplace#local_client(1) endfunction function! fireplace#client() abort return s:client() endfunction function! fireplace#local_client(...) if !a:0 silent doautocmd User FireplacePreConnect endif if exists('s:input') let buf = s:input elseif has_key(s:qffiles, expand('%:p')) let buf = s:qffiles[expand('%:p')].buffer else let buf = '%' endif for repl in s:repls if repl.includes_file(fnamemodify(bufname(buf), ':p')) return repl endif endfor if exists('*classpath#from_vim') let cp = classpath#from_vim(getbufvar(buf, '&path')) return extend({'classpath': cp}, s:oneoff) endif throw ':Connect to a REPL or install classpath.vim to evaluate code' endfunction function! fireplace#findresource(resource) abort if a:resource ==# '' return '' endif try let path = fireplace#local_client().path() catch /^:Connect/ return '' endtry let file = findfile(a:resource, escape(join(path, ','), ' ')) if !empty(file) return file endif for jar in path if fnamemodify(jar, ':e') ==# 'jar' && index(fireplace#jar_contents(jar), a:resource) >= 0 return 'zipfile:' . jar . '::' . a:resource endif endfor return '' endfunction function! fireplace#quickfix_for(stacktrace) abort let qflist = [] for line in a:stacktrace let entry = {'text': line} let match = matchlist(line, '\(.*\)(\(.*\))') 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 qflist += [entry] endif endfor let paths = map(copy(qflist), 'fireplace#findresource(v:val.resource)') let i = 0 for i in range(len(qflist)) if !empty(paths[i]) let qflist[i].filename = paths[i] else call remove(qflist[i], 'lnum') endif endfor return qflist endfunction function! s:output_response(response) abort if get(a:response, 'err', '') !=# '' echohl ErrorMSG echo substitute(a:response.err, '\r\|\n$', '', 'g') echohl NONE endif if get(a:response, 'out', '') !=# '' echo substitute(a:response.out, '\r\|\n$', '', 'g') endif endfunction function! s:eval(expr, ...) abort let options = a:0 ? copy(a:1) : {} let client = get(options, 'client', s:client()) if !has_key(options, 'ns') if fireplace#ns() !~# '^\%(user\)$' let error = client.require(fireplace#ns()) if !empty(error) echohl ErrorMSG echo error echohl NONE throw "Clojure: couldn't require " . fireplace#ns() endif endif 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, '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 let g:list = list return list endfunction function! fireplace#session_eval(expr) abort let response = s:eval(a:expr, {'session': 1}) if !empty(get(response, 'value', '')) 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)) lopen wincmd p 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(expr) abort return fireplace#session_eval(a:expr) endfunction function! fireplace#echo_session_eval(expr) abort try echo fireplace#session_eval(a:expr) catch /^Clojure:/ endtry return '' endfunction function! fireplace#evalprint(expr) abort return fireplace#echo_session_eval(a:expr) 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 " }}}1 " Eval {{{1 let fireplace#skip = 'synIDattr(synID(line("."),col("."),1),"name") =~? "comment\\|string\\|char"' 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 a:type =~ '^\d\+$' silent exe 'normal! ^v'.a:type.'$hy' 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! `[\`]y" elseif a:type ==# 'outer' call searchpair('(','',')', 'Wbcr', g:fireplace#skip) silent exe "normal! vaby" else silent exe "normal! `[v`]y" endif redraw return @@ finally let @@ = reg_save let &selection = sel_save let &clipboard = cb_save endtry endfunction function! s:filterop(type) abort let reg_save = @@ try let expr = s:opfunc(a:type) let @@ = matchstr(expr, '^\n\+').fireplace#session_eval(expr).matchstr(expr, '\n\+$') if @@ !~# '^\n*$' normal! gvp endif catch /^Clojure:/ return '' finally let @@ = reg_save endtry endfunction function! s:printop(type) abort let s:todo = s:opfunc(a:type) call feedkeys("\FireplacePrintLast") endfunction function! s:print_last() abort call fireplace#echo_session_eval(s:todo) return '' endfunction function! s:editop(type) abort call feedkeys(&cedit . "\", 'n') let input = s:input(substitute(substitute(s:opfunc(a:type), "\s*;[^\n]*", '', 'g'), '\n\+\s*', ' ', 'g')) if input !=# '' call fireplace#echo_session_eval(input) endif endfunction function! s:Eval(bang, line1, line2, count, args) abort if a:args !=# '' let expr = a:args else if a:count ==# 0 normal! ^ let line1 = searchpair('(','',')', 'bcrn', g:fireplace#skip) let line2 = searchpair('(','',')', 'rn', g:fireplace#skip) else let line1 = a:line1 let line2 = a:line2 endif if !line1 || !line2 return '' endif let expr = join(getline(line1, line2), "\n") if a:bang exe line1.','.line2.'delete _' endif endif if a:bang try let result = fireplace#session_eval(expr) 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) 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(...) 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 ")\" 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 ) 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 FireplacePrintLast :exe print_last() nnoremap FireplacePrint :set opfunc=printopg@ xnoremap FireplacePrint :call printop(visualmode()) nnoremap FireplaceFilter :set opfunc=filteropg@ xnoremap FireplaceFilter :call filterop(visualmode()) nnoremap FireplaceEdit :set opfunc=editopg@ xnoremap FireplaceEdit :call editop(visualmode()) nnoremap FireplacePrompt :exe inputeval() noremap! FireplaceRecall =recall() 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:setup_eval() abort command! -buffer -bang -range=0 -nargs=? -complete=customlist,fireplace#eval_complete Eval :exe s:Eval(0, , , , ) command! -buffer -bang -bar -count=1 Last exe s:Last(0, ) nmap cp FireplacePrint nmap cpp FireplacePrintab nmap c! FireplaceFilter nmap c!! FireplaceFilterab nmap cq FireplaceEdit nmap cqq FireplaceEditab nmap cqp FireplacePrompt exe 'nmap cqc FireplacePrompt' . &cedit . 'i' map! ( FireplaceRecall endfunction function! s:setup_historical() setlocal readonly nomodifiable nnoremap q :bdelete endfunction function! s:cmdwinenter() setlocal filetype=clojure endfunction function! s:cmdwinleave() setlocal filetype< omnifunc< endfunction augroup fireplace_eval autocmd! autocmd FileType clojure call s:setup_eval() autocmd BufReadPost * if has_key(s:qffiles, expand(':p')) | \ call s:setup_historical() | \ endif autocmd CmdWinEnter @ if exists('s:input') | call s:cmdwinenter() | endif autocmd CmdWinLeave @ if exists('s:input') | call s:cmdwinleave() | endif augroup END " }}}1 " :Require {{{1 function! s:Require(bang, ns) let cmd = ('(clojure.core/require '.s:qsym(a:ns ==# '' ? fireplace#ns() : a:ns).' :reload'.(a:bang ? '-all' : '').')') echo cmd try call fireplace#session_eval(cmd) return '' catch /^Clojure:.*/ return '' endtry endfunction function! s:setup_require() command! -buffer -bar -bang -complete=customlist,fireplace#ns_complete -nargs=? Require :exe s:Require(0, ) nnoremap cpr :Require endfunction augroup fireplace_require autocmd! autocmd FileType clojure call s:setup_require() augroup END " }}}1 " Go to source {{{1 function! s:decode_url(url) abort let url = a:url let url = substitute(url, '^\%(jar:\)\=file:\zs/\ze\w:/', '', '') let url = substitute(url, '^file:', '', '') let url = substitute(url, '^jar:\(.*\)!/', 'zip\1::', '') let url = substitute(url, '%\(\x\x\)', '\=eval(''"\x''.submatch(1).''"'')', 'g') return url endfunction function! fireplace#source(symbol) abort let options = {'client': fireplace#local_client(), 'session': 0} let cmd = \ '(when-let [v (resolve ' . s:qsym(a:symbol) .')]' . \ ' (when-let [filepath (:file (meta v))]' . \ ' (when-let [url (.getResource (clojure.lang.RT/baseLoader) filepath)]' . \ ' [(str url)' . \ ' (:line (meta v))])))' let result = fireplace#evalparse(cmd, options) if type(result) == type([]) return '+' . result[1] . ' ' . fnameescape(s:decode_url(result[0])) else return '' endif 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.*') ==# expand('%:p') && a:cmd ==# 'edit' 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 FireplaceDjump :exe Edit('edit', expand('')) nnoremap FireplaceDsplit :exe Edit('split', expand('')) nnoremap FireplaceDtabjump :exe Edit('tabedit', expand('')) augroup fireplace_source autocmd! autocmd FileType clojure setlocal includeexpr=tr(v:fname,'.-','/_') autocmd FileType clojure setlocal suffixesadd=.clj,.java autocmd FileType clojure setlocal define=^\\s*(def\\w* autocmd FileType clojure command! -bar -buffer -nargs=1 -complete=customlist,fireplace#eval_complete Djump :exe s:Edit('edit', ) autocmd FileType clojure command! -bar -buffer -nargs=1 -complete=customlist,fireplace#eval_complete Dsplit :exe s:Edit('split', ) autocmd FileType clojure nmap [ FireplaceDjump autocmd FileType clojure nmap ] FireplaceDjump autocmd FileType clojure nmap FireplaceDsplit autocmd FileType clojure nmap d FireplaceDsplit autocmd FileType clojure nmap gd FireplaceDtabjump augroup END " }}}1 " Go to file {{{1 function! fireplace#findfile(path) abort let options = {'client': fireplace#local_client(), 'session': 0} let cmd = \ '(symbol' . \ ' (or' . \ ' (when-let [url (.getResource (clojure.lang.RT/baseLoader) %s)]' . \ ' (str url))' . \ ' ""))' let path = a:path if path !~# '[/.]' && path =~# '^\k\+$' let aliascmd = printf(cmd, \ '(if-let [ns ((ns-aliases *ns*) '.s:qsym(path).')]' . \ ' (str (.replace (.replace (str (ns-name ns)) "-" "_") "." "/") ".clj")' . \ ' "'.path.'.clj")') let result = get(split(s:eval(aliascmd, options).value, "\n"), 0, '') else if path !~# '/' let path = tr(path, '.-', '/_') endif if path !~# '\.\w\+$' let path .= '.clj' endif let response = s:eval(printf(cmd, s:str(path)), options) let result = get(split(get(response, 'value', ''), "\n"), 0, '') endif let result = s:decode_url(result) if result ==# '' return fireplace#findresource(path) else return result endif endfunction function! s:GF(cmd, file) abort if a:file =~# '^[^/]*/[^/.]*$' && a:file =~# '^\k\+$' let [file, jump] = split(a:file, "/") else let file = a:file endif try let file = fireplace#findfile(file) catch /^Clojure:/ return '' endtry 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 augroup fireplace_go_to_file autocmd! autocmd FileType clojure nnoremap gf :exe GF('edit', expand('')) autocmd FileType clojure nnoremap f :exe GF('split', expand('')) autocmd FileType clojure nnoremap :exe GF('split', expand('')) autocmd FileType clojure nnoremap gf :exe GF('tabedit', expand('')) augroup END " }}}1 " Documentation {{{1 function! s:buffer_path(...) abort let buffer = a:0 ? a:1 : exists('s:input') ? s:input : '%' if getbufvar(buffer, '&buftype') =~# '^no' return '' endif let path = substitute(fnamemodify(bufname(buffer), ':p'), '\C^zipfile:\(.*\)::', '\1/', '') if exists('*classpath#from_vim') for dir in classpath#split(classpath#from_vim(getbufvar(buffer, '&path'))) if dir !=# '' && path[0 : strlen(dir)-1] ==# dir return path[strlen(dir)+1:-1] endif endfor endif return '' endfunction function! fireplace#ns() abort let lnum = 1 while lnum < line('$') && getline(lnum) =~# '^\s*\%(;.*\)\=$' let lnum += 1 endwhile let keyword_group = '[A-Za-z0-9_?*!+/=<>.-]' let lines = join(getline(lnum, lnum+50), ' ') 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 if has_key(s:qffiles, expand('%:p')) return s:qffiles[expand('%:p')].ns endif let path = s:buffer_path() return s:to_ns(path ==# '' ? 'user' : path) endfunction function! s:Lookup(ns, macro, arg) abort " doc is in clojure.core in older Clojure versions try call fireplace#session_eval("(clojure.core/require '".a:ns.") (clojure.core/eval (clojure.core/list (if (ns-resolve 'clojure.core '".a:macro.") 'clojure.core/".a:macro." '".a:ns.'/'.a:macro.") '".a:arg.'))') catch /^Clojure:/ catch /.*/ echohl ErrorMSG echo v:exception echohl None endtry return '' endfunction function! s:inputlist(label, entries) 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:Apropos(pattern) abort if a:pattern =~# '^#\="' let pattern = a:pattern elseif a:pattern =~# '^^' let pattern = '#"' . a:pattern . '"' else let pattern = '"' . a:pattern . '"' endif let matches = fireplace#evalparse('(clojure.repl/apropos '.pattern.')') if empty(matches) return '' endif let choice = s:inputlist('Look up docs for:', matches) if choice !=# '' return 'echo "\n"|Doc '.choice else return '' endif endfunction function! s:K() let word = expand('') let java_candidate = matchstr(word, '^\%(\w\+\.\)*\u\l\w*\ze\%(\.\|\/\w\+\)\=$') if java_candidate !=# '' return 'Javadoc '.java_candidate else return 'Doc '.word endif endfunction nnoremap FireplaceK :=K() nnoremap FireplaceSource :Source augroup fireplace_doc autocmd! autocmd FileType clojure nmap K FireplaceK autocmd FileType clojure nmap [d FireplaceSource autocmd FileType clojure nmap ]d FireplaceSource autocmd FileType clojure command! -buffer -nargs=1 Apropos :exe s:Apropos() autocmd FileType clojure command! -buffer -nargs=1 FindDoc :exe s:Lookup('clojure.repl', 'find-doc', printf('#"%s"', )) autocmd FileType clojure command! -buffer -bar -nargs=1 Javadoc :exe s:Lookup('clojure.java.javadoc', 'javadoc', ) autocmd FileType clojure command! -buffer -bar -nargs=1 -complete=customlist,fireplace#eval_complete Doc :exe s:Lookup('clojure.repl', 'doc', ) autocmd FileType clojure command! -buffer -bar -nargs=1 -complete=customlist,fireplace#eval_complete Source :exe s:Lookup('clojure.repl', 'source', ) augroup END " }}}1 " Alternate {{{1 augroup fireplace_alternate autocmd! autocmd FileType clojure command! -buffer -bar -bang A :exe s:Alternate('edit') autocmd FileType clojure command! -buffer -bar AS :exe s:Alternate('split') autocmd FileType clojure command! -buffer -bar AV :exe s:Alternate('vsplit') autocmd FileType clojure command! -buffer -bar AT :exe s:Alternate('tabedit') augroup END function! s:alternates() abort let ns = fireplace#ns() if ns =~# '-test$' let alt = [ns[0:-6]] elseif ns =~# '\.test\.' let alt = [substitute(ns, '\.test\.', '.', '')] elseif ns =~# '-spec$' let alt = [ns[0:-6], ns . '-test'] else let alt = [ns . '-test', substitute(ns, '\.', '.test.', ''), ns . '-spec'] endif return map(alt, 'tr(v:val, ".-", "/_") . ".clj"') endfunction function! s:Alternate(cmd) abort let alternates = s:alternates() for file in alternates let path = fireplace#findresource(file) if !empty(path) return a:cmd . ' ' . fnameescape(path) endif endfor return 'echoerr '.string("Couldn't find " . alternates[0] . " in class path") endfunction " }}}1 " Leiningen {{{1 function! s:hunt(start, anchor, pattern) abort let root = simplify(fnamemodify(a:start, ':p:s?[\/]$??')) if !isdirectory(fnamemodify(root, ':h')) return '' endif let previous = "" while root !=# previous if filereadable(root . '/' . a:anchor) && join(readfile(root . '/' . a:anchor, '', 50)) =~# a:pattern return root endif let previous = root let root = fnamemodify(root, ':h') endwhile return '' endfunction if !exists('s:leiningen_repl_ports') let s:leiningen_repl_ports = {} endif function! s:leiningen_connect() if !exists('b:leiningen_root') return endif let portfile = b:leiningen_root . '/target/repl-port' if getfsize(portfile) > 0 && getftime(portfile) !=# get(s:leiningen_repl_ports, b:leiningen_root, -1) let port = matchstr(readfile(portfile, 'b', 1)[0], '\d\+') let s:leiningen_repl_ports[b:leiningen_root] = getftime(portfile) try call s:register_connection(nrepl#fireplace_connection#open(port), b:leiningen_root) catch /^nREPL Connection Error:/ endtry endif endfunction function! s:leiningen_init() abort if !exists('b:leiningen_root') let root = s:hunt(expand('%:p'), 'project.clj', '(\s*defproject') if root !=# '' let b:leiningen_root = root endif endif if !exists('b:leiningen_root') return endif let b:java_root = b:leiningen_root setlocal makeprg=lein efm=%+G call s:leiningen_connect() endfunction augroup fireplace_leiningen autocmd! autocmd User FireplacePreConnect call s:leiningen_connect() autocmd FileType clojure call s:leiningen_init() augroup END " }}}1 " vim:set et sw=2: