" Part of Vim filetype plugin for Clojure " Language: Clojure " Maintainer: Meikel Brandmeyer let s:save_cpo = &cpo set cpo&vim function! vimclojure#SynIdName() return synIDattr(synID(line("."), col("."), 0), "name") endfunction function! vimclojure#WithSaved(closure) let v = a:closure.get(a:closure.tosafe) let r = a:closure.f() call a:closure.set(a:closure.tosafe, v) return r endfunction function! vimclojure#WithSavedPosition(closure) let a:closure['tosafe'] = "." let a:closure['get'] = function("getpos") let a:closure['set'] = function("setpos") return vimclojure#WithSaved(a:closure) endfunction function! vimclojure#WithSavedRegister(closure) let a:closure['get'] = function("getreg") let a:closure['set'] = function("setreg") return vimclojure#WithSaved(a:closure) endfunction function! vimclojure#Yank(r, how) let closure = {'tosafe': a:r, 'yank': a:how} function closure.f() dict silent execute self.yank return getreg(self.tosafe) endfunction return vimclojure#WithSavedRegister(closure) endfunction function! vimclojure#EscapePathForOption(path) let path = fnameescape(a:path) " Hardcore escapeing of whitespace... let path = substitute(path, '\', '\\\\', 'g') let path = substitute(path, '\ ', '\\ ', 'g') return path endfunction function! vimclojure#AddPathToOption(path, option) let path = vimclojure#EscapePathForOption(a:path) execute "setlocal " . a:option . "+=" . path endfunction function! vimclojure#AddCompletions(ns) let completions = split(globpath(&rtp, "ftplugin/clojure/completions-" . a:ns . ".txt"), '\n') if completions != [] call vimclojure#AddPathToOption('k' . completions[0], 'complete') endif endfunction " Nailgun part: function! vimclojure#ExtractSexpr(toplevel) let closure = { "flag" : (a:toplevel ? "r" : "") } function closure.f() dict if searchpairpos('(', '', ')', 'bW' . self.flag, \ 'vimclojure#SynIdName() !~ "clojureParen\\d"') != [0, 0] return vimclojure#Yank('l', 'normal! "ly%') end return "" endfunction return vimclojure#WithSavedPosition(closure) endfunction function! vimclojure#BufferName() let file = expand("%") if file == "" let file = "UNNAMED" endif return file endfunction " Key mappings and Plugs function! vimclojure#MakePlug(mode, plug, f) execute a:mode . "noremap Clojure" . a:plug \ . " :call " . a:f . "" endfunction function! vimclojure#MapPlug(mode, keys, plug) if !hasmapto("Clojure" . a:plug) execute a:mode . "map " . a:keys \ . " Clojure" . a:plug endif endfunction " A Buffer... let vimclojure#Buffer = {} function! vimclojure#Buffer.goHere() dict execute "buffer! " . self._buffer endfunction function! vimclojure#Buffer.resize() dict call self.goHere() let size = line("$") if size < 3 let size = 3 endif execute "resize " . size endfunction function! vimclojure#Buffer.showText(text) dict call self.goHere() if type(a:text) == type("") let text = split(a:text, '\n') else let text = a:text endif call append(line("$"), text) endfunction function! vimclojure#Buffer.close() dict execute "bdelete! " . self._buffer endfunction " The transient buffer, used to display results. let vimclojure#PreviewWindow = copy(vimclojure#Buffer) function! vimclojure#PreviewWindow.New() dict pclose! execute &previewheight . "new" set previewwindow set winfixheight setlocal noswapfile setlocal buftype=nofile setlocal bufhidden=wipe let leader = exists("g:maplocalleader") ? g:maplocalleader : "\\" call append(0, "; Use " . leader . "p to close this buffer!") return copy(self) endfunction function! vimclojure#PreviewWindow.goHere() dict wincmd P endfunction function! vimclojure#PreviewWindow.close() dict pclose endfunction " Nails if !exists("vimclojure#NailgunClient") let vimclojure#NailgunClient = "ng" endif function! vimclojure#ExecuteNailWithInput(nail, input, ...) if type(a:input) == type("") let input = split(a:input, '\n', 1) else let input = a:input endif let inputfile = tempname() try call writefile(input, inputfile) let cmdline = [g:vimclojure#NailgunClient, \ "de.kotka.vimclojure.nails." . a:nail] \ + map(copy(a:000), 'shellescape(v:val)') let cmd = join(cmdline, " ") . " <" . inputfile let result = system(cmd) if v:shell_error echoerr "Couldn't execute Nail! " \ . substitute(result, '\n\(\t\?\)', ' ', 'g') endif finally call delete(inputfile) endtry return substitute(result, '\n$', '', '') endfunction function! vimclojure#ExecuteNail(nail, ...) return call(function("vimclojure#ExecuteNailWithInput"), [a:nail, ""] + a:000) endfunction function! vimclojure#FilterNail(nail, rngStart, rngEnd, ...) let cmdline = [g:vimclojure#NailgunClient, \ "de.kotka.vimclojure.nails." . a:nail] \ + map(copy(a:000), 'shellescape(v:val)') let cmd = a:rngStart . "," . a:rngEnd . "!" . join(cmdline, " ") silent execute cmd endfunction function! vimclojure#DocLookup(word) let docs = vimclojure#ExecuteNailWithInput("DocLookup", a:word, \ "-n", b:vimclojure_namespace) let resultBuffer = g:vimclojure#PreviewWindow.New() call resultBuffer.showText(docs) wincmd p endfunction function! vimclojure#FindDoc() let pattern = input("Pattern to look for: ") let result = vimclojure#ExecuteNailWithInput("FindDoc", pattern) let resultBuffer = g:vimclojure#PreviewWindow.New() call resultBuffer.showText(result) wincmd p endfunction let s:DefaultJavadocPaths = { \ "java" : "http://java.sun.com/javase/6/docs/api/", \ "org/apache/commons/beanutils" : "http://commons.apache.org/beanutils/api/", \ "org/apache/commons/chain" : "http://commons.apache.org/chain/api-release/", \ "org/apache/commons/cli" : "http://commons.apache.org/cli/api-release/", \ "org/apache/commons/codec" : "http://commons.apache.org/codec/api-release/", \ "org/apache/commons/collections" : "http://commons.apache.org/collections/api-release/", \ "org/apache/commons/logging" : "http://commons.apache.org/logging/apidocs/", \ "org/apache/commons/mail" : "http://commons.apache.org/email/api-release/", \ "org/apache/commons/io" : "http://commons.apache.org/io/api-release/" \ } if !exists("vimclojure#JavadocPathMap") let vimclojure#JavadocPathMap = {} endif for k in keys(s:DefaultJavadocPaths) if !has_key(vimclojure#JavadocPathMap, k) let vimclojure#JavadocPathMap[k] = s:DefaultJavadocPaths[k] endif endfor if !exists("vimclojure#Browser") if has("win32") || has("win64") let vimclojure#Browser = "start" elseif has("mac") let vimclojure#Browser = "open" else let vimclojure#Browser = "firefox -new-window" endif endif function! vimclojure#JavadocLookup(word) let word = substitute(a:word, "\\.$", "", "") let path = vimclojure#ExecuteNailWithInput("JavadocPath", word, \ "-n", b:vimclojure_namespace) let match = "" for pattern in keys(g:vimclojure#JavadocPathMap) if path =~ "^" . pattern && len(match) < len(pattern) let match = pattern endif endfor if match == "" throw "No matching Javadoc URL found for " . path endif let url = g:vimclojure#JavadocPathMap[match] . path call system(join([g:vimclojure#Browser, url], " ")) endfunction function! vimclojure#MetaLookup(word) let docs = vimclojure#ExecuteNailWithInput("MetaLookup", a:word, \ "-n", b:vimclojure_namespace) let resultBuffer = g:vimclojure#PreviewWindow.New() call resultBuffer.showText(docs) setfiletype clojure wincmd p endfunction function! vimclojure#SourceLookup(word) let source = vimclojure#ExecuteNailWithInput("SourceLookup", a:word, \ "-n", b:vimclojure_namespace) let resultBuffer = g:vimclojure#PreviewWindow.New() call resultBuffer.showText(source) setfiletype clojure wincmd p endfunction function! vimclojure#GotoSource(word) let result = vimclojure#ExecuteNailWithInput("SourceLocation", a:word, \ "-n", b:vimclojure_namespace) execute "let pos = " . result if !filereadable(pos.file) let file = findfile(pos.file) if file == "" echoerr pos.file . " not found in 'path'" return endif let pos.file = file endif execute "edit " . pos.file execute pos.line endfunction " Evaluators function! vimclojure#MacroExpand(firstOnly) let sexp = vimclojure#ExtractSexpr(0) let ns = b:vimclojure_namespace let resultBuffer = g:vimclojure#PreviewWindow.New() let cmd = ["MacroExpand", sexp, "-n", ns] if a:firstOnly let cmd = cmd + [ "-o" ] endif let result = call(function("vimclojure#ExecuteNailWithInput"), cmd) call resultBuffer.showText(result) setfiletype clojure wincmd p endfunction function! vimclojure#RequireFile(all) let ns = b:vimclojure_namespace let all = a:all ? "-all" : "" let resultBuffer = g:vimclojure#PreviewWindow.New() let require = "(require :reload" . all . " :verbose '". ns. ")" let result = vimclojure#ExecuteNailWithInput("Repl", require, "-r") call resultBuffer.showText(result) setfiletype clojure wincmd p endfunction function! vimclojure#EvalFile() let content = getbufline(bufnr("%"), 1, line("$")) let file = vimclojure#BufferName() let ns = b:vimclojure_namespace let result = vimclojure#ExecuteNailWithInput("Repl", content, \ "-r", "-n", ns, "-f", file) let resultBuffer = g:vimclojure#PreviewWindow.New() call resultBuffer.showText(result) setfiletype clojure wincmd p endfunction function! vimclojure#EvalLine() let theLine = line(".") let content = getline(theLine) let file = vimclojure#BufferName() let ns = b:vimclojure_namespace let result = vimclojure#ExecuteNailWithInput("Repl", content, \ "-r", "-n", ns, "-f", file, "-l", theLine) let resultBuffer = g:vimclojure#PreviewWindow.New() call resultBuffer.showText(result) setfiletype clojure wincmd p endfunction function! vimclojure#EvalBlock() range let file = vimclojure#BufferName() let ns = b:vimclojure_namespace let content = getbufline(bufnr("%"), a:firstline, a:lastline) let result = vimclojure#ExecuteNailWithInput("Repl", content, \ "-r", "-n", ns, "-f", file, "-l", a:firstline - 1) let resultBuffer = g:vimclojure#PreviewWindow.New() call resultBuffer.showText(result) setfiletype clojure wincmd p endfunction function! vimclojure#EvalToplevel() let file = vimclojure#BufferName() let ns = b:vimclojure_namespace let pos = searchpairpos('(', '', ')', 'bWnr', \ 'vimclojure#SynIdName() !~ "clojureParen\\d"') if pos == [0, 0] throw "Error: Not in toplevel expression!" endif let expr = vimclojure#ExtractSexpr(1) let result = vimclojure#ExecuteNailWithInput("Repl", expr, \ "-r", "-n", ns, "-f", file, "-l", pos[0] - 1) let resultBuffer = g:vimclojure#PreviewWindow.New() call resultBuffer.showText(result) setfiletype clojure wincmd p endfunction function! vimclojure#EvalParagraph() let file = vimclojure#BufferName() let ns = b:vimclojure_namespace let startPosition = line(".") let closure = {} function! closure.f() dict normal! } return line(".") endfunction let endPosition = vimclojure#WithSavedPosition(closure) let content = getbufline(bufnr("%"), startPosition, endPosition) let result = vimclojure#ExecuteNailWithInput("Repl", content, \ "-r", "-n", ns, "-f", file, "-l", startPosition - 1) let resultBuffer = g:vimclojure#PreviewWindow.New() call resultBuffer.showText(result) setfiletype clojure wincmd p endfunction " The Repl let vimclojure#Repl = copy(vimclojure#Buffer) let vimclojure#Repl._prompt = "Clojure=>" let vimclojure#Repl._history = [] let vimclojure#Repl._historyDepth = 0 let vimclojure#Repl._replCommands = [ ",close", ",st", ",ct" ] function! vimclojure#Repl.New() dict let instance = copy(self) new setlocal buftype=nofile setlocal noswapfile if !hasmapto("ClojureReplEnterHook") imap ClojureReplEnterHook endif if !hasmapto("ClojureReplUpHistory") imap ClojureReplUpHistory endif if !hasmapto("ClojureReplDownHistory") imap ClojureReplDownHistory endif call append(line("$"), ["Clojure", self._prompt . " "]) let instance._id = vimclojure#ExecuteNail("Repl", "-s") call vimclojure#ExecuteNailWithInput("Repl", \ "(require 'clojure.contrib.stacktrace)", "-r", \ "-i", instance._id) let instance._buffer = bufnr("%") let b:vimclojure_repl = instance setfiletype clojure normal! G startinsert! endfunction function! vimclojure#Repl.isReplCommand(cmd) dict for candidate in self._replCommands if candidate == a:cmd return 1 endif endfor return 0 endfunction function! vimclojure#Repl.doReplCommand(cmd) dict if a:cmd == ",close" call vimclojure#ExecuteNail("Repl", "-S", "-i", self._id) call self.close() stopinsert elseif a:cmd == ",st" let result = vimclojure#ExecuteNailWithInput("Repl", \ "(clojure.contrib.stacktrace/print-stack-trace *e)", "-r", \ "-i", self._id) call self.showText(result) call self.showText(self._prompt . " ") normal! G startinsert! elseif a:cmd == ",ct" let result = vimclojure#ExecuteNailWithInput("Repl", \ "(clojure.contrib.stacktrace/print-cause-trace *e)", "-r", \ "-i", self._id) call self.showText(result) call self.showText(self._prompt . " ") normal! G startinsert! endif endfunction function! vimclojure#Repl.showPrompt() dict call self.showText(self._prompt . " ") normal! G startinsert! endfunction function! vimclojure#Repl.getCommand() dict let ln = line("$") while getline(ln) !~ "^" . self._prompt && ln > 0 let ln = ln - 1 endwhile " Special Case: User deleted Prompt by accident. Insert a new one. if ln == 0 call self.showPrompt() return "" endif let cmd = vimclojure#Yank("l", ln . "," . line("$") . "yank l") let cmd = substitute(cmd, "^" . self._prompt . "\\s*", "", "") let cmd = substitute(cmd, "\n$", "", "") return cmd endfunction function! vimclojure#Repl.enterHook() dict let cmd = self.getCommand() " Special Case: Showed prompt (or user just hit enter). if cmd == "" return endif if self.isReplCommand(cmd) call self.doReplCommand(cmd) return endif let result = vimclojure#ExecuteNailWithInput("CheckSyntax", cmd) if result == "false" execute "normal! GA\x" normal! ==x startinsert! else let result = vimclojure#ExecuteNailWithInput("Repl", cmd, \ "-r", "-i", self._id) call self.showText(result) let self._historyDepth = 0 let self._history = [cmd] + self._history call self.showPrompt() endif endfunction function! vimclojure#Repl.upHistory() dict let histLen = len(self._history) let histDepth = self._historyDepth if histLen > 0 && histLen > histDepth let cmd = self._history[histDepth] let self._historyDepth = histDepth + 1 call self.deleteLast() call self.showText(self._prompt . " " . cmd) endif normal! G$ endfunction function! vimclojure#Repl.downHistory() dict let histLen = len(self._history) let histDepth = self._historyDepth if histDepth > 0 && histLen > 0 let self._historyDepth = histDepth - 1 let cmd = self._history[self._historyDepth] call self.deleteLast() call self.showText(self._prompt . " " . cmd) elseif histDepth == 0 call self.deleteLast() call self.showText(self._prompt . " ") endif normal! G$ endfunction function! vimclojure#Repl.deleteLast() dict normal! G while getline("$") !~ self._prompt normal! dd endwhile normal! dd endfunction " Highlighting function! vimclojure#ColorNamespace(highlights) for [category, words] in items(a:highlights) if words != [] execute "syntax keyword clojure" . category . " " . join(words, " ") endif endfor endfunction " Omni Completion function! vimclojure#OmniCompletion(findstart, base) if a:findstart == 1 let line = getline(".") let start = col(".") - 1 while start > 0 && line[start - 1] =~ '\w\|-\|\.\|+\|*\|/' let start -= 1 endwhile return start else let slash = stridx(a:base, '/') if slash > -1 let prefix = strpart(a:base, 0, slash) let base = strpart(a:base, slash + 1) else let prefix = "" let base = a:base endif let completions = vimclojure#ExecuteNail("Complete", \ "-n", b:vimclojure_namespace, \ "-p", prefix, "-b", base) execute "let result = " . completions return result endif endfunction function! vimclojure#InitBuffer() if exists("g:clj_want_gorilla") && g:clj_want_gorilla == 1 if !exists("b:vimclojure_namespace") " Get the namespace of the buffer. if &previewwindow let b:vimclojure_namespace = "user" else try let content = getbufline(bufnr("%"), 1, line("$")) let b:vimclojure_namespace = \ vimclojure#ExecuteNailWithInput( \ "NamespaceOfFile", content) catch /.*/ endtry endif endif endif endfunction " Epilog let &cpo = s:save_cpo