" 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) 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 " }}}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.user_ns() abort return 'user' endfunction 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.message(...) dict abort try let result = call(self.connection.message, a:000, self.connection) 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 !empty(a:lib) && a:lib !=# self.user_ns() && !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': self.user_ns(), '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/', '') let file = substitute(file, '\C^fugitive:[\/][\/]\(.*\)\.git[\/][\/][^\/]\+[\/]', '\1', '') for path in self.path() if file[0 : len(path)-1] ==? path return 1 endif endfor endfunction function! s:register_connection(conn, ...) abort 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() 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, ":h:t")') 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 = {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(...) abort if (a:0 ? a:1 : '') =~# '^\w\+://' let [proto, arg] = split(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 = {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 > 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 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.user_ns() abort return 'user' endfunction function! s:oneoff.eval(expr, options) dict abort if &verbose && !empty(get(a:options, 'session', 1)) echohl WarningMSG echomsg "No REPL found. Running java clojure.main ..." echohl None endif if a:options.ns !=# '' && a:options.ns !=# self.user_ns() 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) abort return '' endfunction function! s:oneoff.message(symbol) abort throw 'No live REPL connection' endfunction " }}}1 " Client {{{1 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:client(...) abort 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 return fireplace#local_client(buf, 1) endfunction function! fireplace#client(...) abort return a:0 ? s:client(a:1) : s:client() endfunction function! fireplace#local_client(...) abort if a:0 < 2 silent doautocmd User FireplacePreConnect endif let buf = a:0 ? a:1 : s:buf() for repl in s:repls if repl.includes_file(fnamemodify(bufname(buf), ':p')) return repl endif endfor if exists('*classpath#from_vim') && expand('%:e') ==# 'clj' let cp = classpath#from_vim(getbufvar(buf, '&path')) return extend({'classpath': cp, 'nr': bufnr(buf)}, s:oneoff) endif throw ':Connect to a REPL or install classpath.vim to evaluate code' endfunction function! fireplace#message(payload, ...) let client = s:client() let payload = copy(a:payload) if !has_key(payload, 'ns') let payload.ns = fireplace#ns() if fireplace#ns() !=# client.user_ns() let error = client.require(fireplace#ns()) if !empty(error) echohl ErrorMSG echo error echohl NONE throw "Clojure: couldn't require " . fireplace#ns() endif endif endif return call(client.message, [payload] + a:000, client) endfunction function! fireplace#findresource(resource, ...) abort if a:resource ==# '' return '' endif try let path = a:0 ? a:1 : 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() !=# client.user_ns() 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, '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 let g:list = list 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(expr, ...) abort return fireplace#eval(a:expr, extend({'session': 0}, a:0 ? a:1 : {})) endfunction function! fireplace#echo_session_eval(expr, ...) abort try echo fireplace#session_eval(a:expr, a:0 ? a:1 : {}) catch /^Clojure:/ 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 " }}}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 =~# '^.$' 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:macroexpandop(type) abort call fireplace#macroexpand("clojure.walk/macroexpand-all", s:opfunc(a:type)) endfunction function! s:macroexpand1op(type) abort call fireplace#macroexpand("clojure.core/macroexpand-1", s:opfunc(a:type)) 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 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#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 ")\" 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 FireplaceCountPrint :Eval nnoremap FireplaceFilter :set opfunc=filteropg@ xnoremap FireplaceFilter :call filterop(visualmode()) nnoremap FireplaceMacroExpand :set opfunc=macroexpandopg@ xnoremap FireplaceMacroExpand :call macroexpandop(visualmode()) nnoremap FireplaceMacroExpand1 :set opfunc=macroexpand1opg@ xnoremap FireplaceMacroExpand1 :call macroexpand1op(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 FireplaceCountPrint nmap c! FireplaceFilter nmap c!! FireplaceFilterab nmap cm FireplaceMacroExpand nmap cmm FireplaceMacroExpandab nmap c1m FireplaceMacroExpand1 nmap c1mm FireplaceMacroExpand1ab nmap cq FireplaceEdit nmap cqq FireplaceEditab nmap cqp FireplacePrompt exe 'nmap cqc FireplacePrompt' . &cedit . 'i' map! ( FireplaceRecall endfunction function! s:setup_historical() abort setlocal readonly nomodifiable nnoremap q :bdelete 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: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) abort 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() abort 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 : s:buf() 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 buffer = a:0 ? a:1 : s:buf() if 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 " 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) 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: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() abort 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! fireplace#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 = fireplace#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:portfile() abort if !exists('b:leiningen_root') return '' endif let root = b:leiningen_root let portfiles = [root.'/target/repl-port', root.'/target/repl/repl-port', root.'/.nrepl-port'] for f in portfiles if filereadable(f) return f endif endfor return '' endfunction function! s:leiningen_connect() abort let portfile = s:portfile() if empty(portfile) return endif 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:/ if &verbose echohl WarningMSG echomsg v:exception echohl None endif 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: