Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit ff9e3b5cd7a2051a9b8b7797eaa324391295bf43 0 parents
Tim Pope authored
1  .gitignore
@@ -0,0 +1 @@
+/doc/tags
140 README.markdown
@@ -0,0 +1,140 @@
+# foreplay.vim
+
+There's a REPL in foreplay, but you probably wouldn't have noticed if I hadn't
+told you. Such is the way with foreplay.vim. By the way, this plugin is for
+Clojure.
+
+## Installation
+
+You'll need a set of Clojure runtime files.
+[VimClojure](http://www.vim.org/scripts/script.php?script_id=2501) is popular.
+You don't need to bother with the interactive setup.
+
+If you don't have a preferred installation method, I recommend
+installing [pathogen.vim](https://github.com/tpope/vim-pathogen), and
+then simply copy and paste:
+
+ cd ~/.vim/bundle
+ git clone git://github.com/tpope/vim-foreplay.git
+ git clone git://github.com/vim-scripts/VimClojure.git
+
+Once help tags have been generated, you can view the manual with
+`:help foreplay`.
+
+## Features
+
+This list isn't exhaustive; see the `:help` for details. Any snark resembling
+actual plugins is purely coincidental.
+
+### Transparent setup
+
+Foreplay.vim talks to nREPL. With Leiningen 2, it connects automatically
+based on `target/repl-port`, otherwise it's just a `:Connect` away. You can
+connect to multiple instances of nREPL for different projects, and it will
+use the right one automatically.
+
+You don't need a custom built nailgun client. You don't need a lein plugin.
+You don't need to run some specialized server in a separate terminal, then
+kill and restart it each time you switch projects because the port is hard
+coded.
+
+Oh, and if you don't have an nREPL connection, it falls back to using
+`java clojure.main`, using a class path based on your Leiningen or Maven
+config. It's a bit slow, but a two second delay its vastly preferable to
+being forced out of my flow for a single command, in my book.
+
+### Not quite a REPL
+
+You know that one plugin that provides a REPL in a split window and works
+absolutely flawlessly, never breaking just because you did something innocuous
+like backspace through part of the prompt? No? Such a shame, you really
+would have liked it.
+
+I've taken a different approach in foreplay.vim. `cq` (Think "Clojure
+Quasi-REPL") is the prefix for a set of commands that bring up a *command-line
+window* — the same thing you get when you hit `q:` — but set up for Clojure
+code.
+
+`cqq` prepopulates the command-line window with the expression under the
+cursor. `cqc` gives you a blank line in insert mode.
+
+### Evaluating from the buffer
+
+Standard stuff here. `:Eval` evaluates a range (`:%Eval` gets the whole
+file), `:Require` requires a namespace with `:reload` (`:Require!` does
+`:reload-all`), either the current buffer or a given argument. There's a `cp`
+operator that evaluates a given motion (`cpp` for the expression under the
+cursor).
+
+### Navigating and Comprehending
+
+I'm new to Clojure, so stuff that helps me understand code is a top priority.
+
+* `:Source`, `:Doc`, `:FindDoc`, and `:Apropros`, which map to the underlying
+ `clojure.repl` macro (with tab complete, of course).
+
+* `K` is mapped to look up the symbol under the cursor with `doc`.
+
+* `[d` is mapped to look up the symbol under the cursor with `source`.
+
+* `[<C-D>` jumps to the definition of a symbol (even if it's inside a jar
+ file).
+
+* `gf`, everybody's favorite "go to file" command, works on namespaces.
+
+Where possible, I favor enhancing built-ins over inventing a bunch of
+`<Leader>` maps.
+
+### Omnicomplete
+
+Because why not? It works in the quasi-REPL too.
+
+### FAQ
+
+> Why does it take so long for Vim to startup?
+
+The short answer is because the JVM is slow.
+
+The first time you load a Clojure file from any given project, foreplay.vim
+sets about trying to determine your class path, leveraging either
+`lein classpath` or `mvn dependency:build-classpath`. This takes a couple of
+seconds or so in the best case scenario, and potentially much longer if it
+decides to hit the network. (I don't understand why "tell me the class path"
+requires hitting the network, but what do I know?)
+
+Because the class path is oh-so-expensive to retrieve, foreplay.vim caches it
+in `g:CLASSPATH_CACHE`. By default, this disappears when you exit Vim, but
+you can save it across sessions in `.viminfo` with this handy option:
+
+ set viminfo+=!
+
+The cache is expired when the timestamp on `project.clj` or `pom.xml` changes.
+
+## Contributing
+
+More than any other plugin, I'm in over my head here. I tried to do my
+homework, but you don't learn best practices overnight. Please, open
+[GitHub issues][] for bug reports and feature requests. Even better than a
+feature request is just to tell me the pain you're experiencing, and perhaps
+some ideas for what might eliminate it. I know Vimscript; you know Clojure.
+Let's synergize.
+
+I'm a stickler for [commit messages][], so if you send me a pull request
+request with so much as superfluous period in the subject line, I will
+reject it, then TP your house.
+
+[GitHub issues]: http://github.com/tpope/vim-foreplay/issues
+[commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
+
+## Self-Promotion
+
+Like foreplay.vim? Follow the repository on
+[GitHub](https://github.com/tpope/vim-foreplay). And if
+you're feeling especially charitable, follow [tpope](http://tpo.pe/) on
+[Twitter](http://twitter.com/tpope) and
+[GitHub](https://github.com/tpope).
+
+## License
+
+Copyright © Tim Pope. Distributed under the same terms as Vim itself.
+See `:help license`.
126 autoload/classpath.vim
@@ -0,0 +1,126 @@
+" classpath.vim - Manipulate the Java class path
+" Maintainer: Tim Pope <http://tpo.pe>
+
+if exists("g:autoloaded_classpath")
+ finish
+endif
+let g:autoloaded_classpath = 1
+
+function! classpath#separator() abort
+ return has('win32') ? ';' : ':'
+endfunction
+
+function! classpath#file_separator() abort
+ return exists('shellslash') && !&shellslash ? '\' : '/'
+endfunction
+
+function! classpath#split(cp) abort
+ return split(a:cp, classpath#separator())
+endfunction
+
+function! classpath#to_vim(cp) abort
+ let path = []
+ for elem in classpath#split(a:cp)
+ let path += [elem ==# '.' ? '' : elem]
+ endfor
+ if a:cp =~# '\(^\|:\)\.$'
+ let path += ['']
+ endif
+ return join(map(path, 'escape(v:val, ", ")'), ',')
+endfunction
+
+function! classpath#from_vim(path) abort
+ if a:path =~# '^,\=$'
+ return '.'
+ endif
+ let path = []
+ for elem in split(substitute(a:path, ',$', '', ''), ',')
+ if elem ==# ''
+ let path += ['.']
+ else
+ let path += split(glob(substitute(elem, '\\\ze[\\ ,]', '', 'g')), "\n")
+ endif
+ endfor
+ return join(path, classpath#separator())
+endfunction
+
+function! classpath#detect(...) abort
+ let sep = classpath#file_separator()
+
+ let buffer = a:0 ? a:1 : '%'
+ let default = $CLASSPATH ==# '' ? ',' : classpath#to_vim($CLASSPATH)
+ let root = getbufvar(buffer, 'java_root')
+ if root ==# ''
+ let root = simplify(fnamemodify(bufname(buffer), ':p:s?[\/]$??'))
+ endif
+
+ if root =~# '^zipfile:'
+ return default
+ endif
+
+ let previous = ""
+ while root !=# previous
+ if isdirectory(root . '/src')
+ if filereadable(root . '/project.clj')
+ let file = 'project.clj'
+ let cmd = 'lein classpath'
+ let pattern = "[^\n]*\\ze\n*$"
+ let default = join(map(['test', 'src', 'dev-resources', 'resources', 'target'.sep.'classes'], 'escape(root . sep . v:val, ", ")'), ',')
+ let base = ''
+ break
+ endif
+ if filereadable(root . '/pom.xml')
+ let file = 'pom.xml'
+ let cmd = 'mvn dependency:build-classpath'
+ let pattern = '\%(^\|\n\)\zs[^[].\{-\}\ze\n'
+ let base = escape(root.sep.'src'.sep.'*'.sep.'*', ', ') . ','
+ let default = base . default
+ break
+ endif
+ endif
+ let previous = root
+ let root = fnamemodify(root, ':h')
+ endwhile
+
+ if !exists('file')
+ if a:0 > 1 && a:2 ==# 'keep'
+ return ''
+ else
+ return default
+ endif
+ endif
+
+ if !exists('g:CLASSPATH_CACHE') || type(g:CLASSPATH_CACHE) != type({})
+ unlet! g:CLASSPATH_CACHE
+ let g:CLASSPATH_CACHE = {}
+ endif
+
+ let [when, last, path] = split(get(g:CLASSPATH_CACHE, root, "-1\t-1\t."), "\t")
+ let disk = getftime(root . sep . file)
+ if last ==# disk
+ return path
+ else
+ try
+ if &verbose
+ echomsg 'Determining class path with '.cmd.' ...'
+ endif
+ let out = system('cd ' . shellescape(root) . ' && ' . cmd)
+ catch /^Vim:Interrupt/
+ return default
+ endtry
+ let match = matchstr(out, pattern)
+ if !v:shell_error && exists('out') && out !=# ''
+ let path = base . classpath#to_vim(match)
+ let g:CLASSPATH_CACHE[root] = localtime() . "\t" . disk . "\t" . path
+ return path
+ else
+ echohl WarningMSG
+ echomsg "Couldn't determine class path."
+ echohl NONE
+ echo out
+ return default
+ endif
+ endif
+endfunction
+
+" vim:set et sw=2:
212 autoload/nrepl/foreplay_connection.vim
@@ -0,0 +1,212 @@
+" nrepl/foreplay_connection.vim
+" Maintainer: Tim Pope <http://tpo.pe/>
+
+if exists("g:autoloaded_nrepl_foreplay_connection") || &cp
+ finish
+endif
+let g:autoloaded_nrepl_foreplay_connection = 1
+
+function! s:function(name) abort
+ return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
+endfunction
+
+" Bencode {{{1
+
+function! nrepl#foreplay_connection#bencode(value) abort
+ if type(a:value) == type(0)
+ return 'i'.a:value.'e'
+ elseif type(a:value) == type('')
+ return strlen(a:value).':'.a:value
+ elseif type(a:value) == type([])
+ return 'l'.join(map(a:value,'nrepl#foreplay_connection#bencode(v:val)'),'').'e'
+ elseif type(a:value) == type({})
+ return 'd'.join(values(map(a:value,'nrepl#foreplay_connection#bencode(v:key).nrepl#foreplay_connection#bencode(v:val)')),'').'e'
+ else
+ throw "Can't bencode ".string(a:value)
+ endif
+endfunction
+
+function! nrepl#foreplay_connection#bdecode(value) abort
+ return s:bdecode({'pos': 0, 'value': a:value})
+endfunction
+
+function! s:bdecode(state) abort
+ let value = a:state.value
+ if value[a:state.pos] =~# '\d'
+ let pos = a:state.pos
+ let length = matchstr(value[pos : -1], '^\d\+')
+ let a:state.pos += strlen(length) + length + 1
+ return value[pos+strlen(length)+1 : pos+strlen(length)+length]
+ elseif value[a:state.pos] ==# 'i'
+ let int = matchstr(value[a:state.pos+1:-1], '[^e]*')
+ let a:state.pos += 2 + strlen(int)
+ return str2nr(int)
+ elseif value[a:state.pos] ==# 'l'
+ let values = []
+ let a:state.pos += 1
+ while value[a:state.pos] !=# 'e' && value[a:state.pos] !=# ''
+ call add(values, s:bdecode(a:state))
+ endwhile
+ let a:state.pos += 1
+ return values
+ elseif value[a:state.pos] ==# 'd'
+ let values = {}
+ let a:state.pos += 1
+ while value[a:state.pos] !=# 'e' && value[a:state.pos] !=# ''
+ let key = s:bdecode(a:state)
+ let values[key] = s:bdecode(a:state)
+ endwhile
+ let a:state.pos += 1
+ return values
+ else
+ throw 'bencode parse error: '.string(a:state)
+ endif
+endfunction
+
+" }}}1
+
+function! s:shellesc(arg) abort
+ if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
+ return a:arg
+ elseif &shell =~# 'cmd'
+ return '"'.substitute(substitute(a:arg, '"', '""', 'g'), '%', '"%"', 'g').'"'
+ else
+ let escaped = shellescape(a:arg)
+ if &shell =~# 'sh' && &shell !~# 'csh'
+ return substitute(escaped, '\\\n', '\n', 'g')
+ else
+ return escaped
+ endif
+ endif
+endfunction
+
+function! nrepl#foreplay_connection#prompt() abort
+ return foreplay#input_host_port()
+endfunction
+
+function! nrepl#foreplay_connection#open(arg) abort
+ if a:arg =~# '^\d\+$'
+ let host = 'localhost'
+ let port = a:arg
+ elseif a:arg =~# ':\d\+$'
+ let host = matchstr(a:arg, '.*\ze:')
+ let port = matchstr(a:arg, ':\zs.*')
+ else
+ throw "nREPL: Couldn't find [host:]port in " . a:arg
+ endif
+ let client = deepcopy(s:nrepl)
+ let client.host = host
+ let client.port = port
+ let client._path = client.eval('(symbol (str (System/getProperty "path.separator") (System/getProperty "java.class.path")))')
+ return client
+endfunction
+
+function! s:nrepl_path() dict abort
+ return split(self._path[1:-1], self._path[0])
+endfunction
+
+function! s:nrepl_process(payload) dict abort
+ let parsed = self.call(a:payload)
+ let result = {}
+ for packet in parsed
+ if has_key(packet, 'out')
+ echo substitute(packet.out, '\n$', '', '')
+ endif
+ if has_key(packet, 'err')
+ echohl ErrorMSG
+ echo substitute(packet.err, '\n$', '', '')
+ echohl NONE
+ endif
+ if has_key(packet, 'ex') || has_key(packet, 'value')
+ let result = packet
+ endif
+ endfor
+ return result
+endfunction
+
+function! s:nrepl_eval(expr, ...) dict abort
+ let payload = {"op": "eval", "code": a:expr}
+ if a:0
+ let payload.ns = a:1
+ elseif has_key(self, 'ns')
+ let payload.ns = self.ns
+ endif
+ let packet = self.process(payload)
+ if has_key(packet, 'value')
+ if !a:0
+ let self.ns = packet.ns
+ endif
+ return packet.value
+ elseif has_key(packet, 'ex')
+ let err = 'Clojure: '.packet.ex
+ else
+ let err = 'nREPL: '.string(packet)
+ endif
+ throw err
+endfunction
+
+function! s:nrepl_call(payload) dict abort
+ let in = 'ruby -rsocket -e '.s:shellesc(
+ \ 'begin;' .
+ \ 'TCPSocket.open(%(' . self.host . '), ' . self.port . ') {|s|' .
+ \ 's.write(ARGV.first); loop {' .
+ \ 'body = s.readpartial(8192); print body;' .
+ \ 'break if body.include?(%(6:statusl4:done)) }};' .
+ \ 'rescue; abort $!.to_s;' .
+ \ 'end') . ' ' .
+ \ s:shellesc(nrepl#foreplay_connection#bencode(a:payload))
+ let out = system(in)
+ if !v:shell_error
+ return nrepl#foreplay_connection#bdecode('l'.out.'e')
+ endif
+ throw 'nREPL: '.split(out, "\n")[0]
+endfunction
+
+let s:nrepl = {
+ \ 'call': s:function('s:nrepl_call'),
+ \ 'eval': s:function('s:nrepl_eval'),
+ \ 'path': s:function('s:nrepl_path'),
+ \ 'process': s:function('s:nrepl_process')}
+
+if !has('ruby')
+ finish
+endif
+
+ruby <<
+require 'timeout'
+require 'socket'
+def VIM.string_encode(str)
+ '"' + str.gsub(/[\000-\037"\\]/) { |x| "\\%03o" % (x.respond_to?(:ord) ? x.ord : x[0]) } + '"'
+end
+def VIM.let(var, value)
+ VIM.command("let #{var} = #{string_encode(value)}")
+end
+.
+
+function! s:nrepl_call(payload) dict abort
+ let payload = nrepl#foreplay_connection#bencode(a:payload)
+ ruby <<
+ begin
+ buffer = ''
+ Timeout.timeout(16) do
+ TCPSocket.open(VIM.evaluate('self.host'), VIM.evaluate('self.port').to_i) do |s|
+ s.write(VIM.evaluate('payload'))
+ loop do
+ body = s.readpartial(8192)
+ buffer << body
+ break if body.include?("6:statusl4:done")
+ end
+ VIM.let('out', buffer)
+ end
+ end
+ rescue
+ VIM.let('err', $!.to_s)
+ end
+.
+ if !exists('err')
+ return nrepl#foreplay_connection#bdecode('l'.out.'e')
+ endif
+ throw 'nREPL: '.err
+endfunction
+
+" vim:set et sw=2:
152 doc/foreplay.txt
@@ -0,0 +1,152 @@
+*foreplay.txt* Clojure REPL tease
+
+Author: Tim Pope <http://tpo.pe/>
+License: Same terms as Vim itself (see |license|)
+
+This plugin is only available if 'compatible' is not set.
+
+You need Clojure runtime files to use this plugin. Try VimClojure. You don't
+need the interactive stuff.
+
+CLASSPATH *foreplay-classpath*
+
+Upon loading a Clojure buffer, the 'path' option is automatically set to your
+class path. The class path is found by `lein classpath`, `mvn
+dependency:build-classpath`, or failing both of those, $CLASSPATH.
+
+Finding the class path can be slow. To cache it across multiple invocations
+of Vim, try
+
+ set viminfo+=!
+
+LEININGEN *foreplay-leiningen*
+
+Leiningen support is currently bare bones. If a Leiningen project is found,
+'makeprg' will be set to "lein".
+
+Leiningen 2.x writes to target/repl-port when `lein repl` is invoked. If this
+file is found, an nREPL connection will be established automatically.
+
+CONNECTING TO A REPL *foreplay-connect*
+
+ *foreplay-:Connect*
+:Connect {proto}://{host}:{port}
+ Connect to a REPL server.
+
+:Connect Interactively prompt for the options to connect to a
+ REPL server.
+
+The REPL is used for the commands below. If no REPL is found for the current
+buffer, java (or $JAVA_CMD) is invoked directly (using 'path' as the class
+path), which can be quite slow depending on your setup.
+
+The only adapter shipped with foreplay.vim is for nREPL. You need Ruby
+installed and either |if_ruby| or the ruby command in your PATH.
+
+DOCUMENTATION *foreplay-documentation*
+
+ *foreplay-:Doc*
+:Doc {symbol} Show the docs for the given symbol.
+
+ *foreplay-K*
+K Look up docs for keyword under cursor.
+
+ *foreplay-:FindDoc*
+:FindDoc {arg} Wrapper around (clojure.repl/find-doc ...).
+
+ *foreplay-:Apropos*
+:Apropos {arg} Wrapper around (clojure.repl/apropos ...).
+
+ *foreplay-:Source*
+:Source {symbol} Show the source for the given symbol.
+
+ *foreplay-[d*
+[d Show source for keyword under cursor.
+]d
+
+NAVIGATING *foreplay-navigating*
+
+These commands will never use a remote REPL, only a local one, as file paths
+on a remote server wouldn't be very useful locally.
+
+ *foreplay-[_CTRL-D*
+[<C-D> Jump to the source of the keyword under the cursor.
+]<C-D>
+
+ *foreplay-CTRL-W_CTRL-D*
+<C-W><C-D> Jump to the source of the keyword under the cursor in
+<C-W>d a split.
+
+ *foreplay-gf*
+gf Go to the file for the namespace under the cursor.
+
+ *foreplay-:Djump*
+:Djump {symbol} Jump to the definition for the given symbol.
+
+ *foreplay-:Dsplit*
+:Dsplit {symbol} Jump to the definition for the given symbol in a
+ split.
+
+EVALUATING CODE *foreplay-eval*
+
+All code is evaluated in the namespace of the current file, requiring it if
+necessary. If the current file sits outside the class path (project.clj, for
+example), the user namespace is used instead.
+
+ *foreplay-:Require*
+:Require [ns] Require :reload the given/current namespace.
+
+ *foreplay-:Require!*
+:Require! [ns] Require :reload-all the given/current namespace.
+
+ *foreplay-:Eval*
+:Eval Eval/print the outermost expression for the current
+ line.
+
+:{range}Eval Eval/print the given range.
+
+:Eval {expr} Eval/print the given expression.
+
+ *foreplay-:Eval!*
+:[range]Eval! Eval the given range or outermost expression and
+ replace it with its result.
+
+:[range]Eval! {expr} Eval the given expression and insert it after
+ the given range or current line.
+
+ *foreplay-cp*
+cp{motion} Eval/print the code indicated by {motion}.
+
+cpp Eval/print the inner-most expr at the cursor.
+
+ *foreplay-c!*
+c!{motion} Eval/replace the code indicated by {motion}.
+
+c!! Eval/replace the inner-most expr at the cusror.
+
+ *foreplay-cqp*
+cqp Bring up a prompt for code to eval/print.
+
+ *foreplay-cqc*
+cqc Bring up a |command-line-window| for code to
+ eval/print. Equivalent to cqp<C-F>i.
+
+ *foreplay-cq*
+cq{motion} Bring up a |command-line-window| with text indicated
+ by {motion} prepopulated.
+
+And insert mode:
+
+ *foreplay-i_CTRL-R_(*
+<C-R>( Evaluate the given expression and insert the result.
+
+There's omnicomplete on |CTRL-X_CTRL-O|, which works in Clojure buffers and
+in the |command-line-window|, and tab complete at the cqp prompt.
+
+ABOUT *foreplay-about*
+
+Grab the latest version or report a bug on GitHub:
+
+http://github.com/tpope/vim-foreplay
+
+ vim:tw=78:et:ft=help:norl:
894 plugin/foreplay.vim
@@ -0,0 +1,894 @@
+" foreplay.vim - Clojure REPL tease
+" Maintainer: Tim Pope <http://tpo.pe>
+
+if exists("g:loaded_foreplay") || v:version < 700 || &cp
+ finish
+endif
+let g:loaded_foreplay = 1
+
+" File type {{{1
+
+augroup foreplay_file_type
+ autocmd!
+ autocmd BufNewFile,BufReadPost *.clj setfiletype clojure
+ autocmd FileType clojure let &l:path = classpath#detect()
+augroup END
+
+" }}}1
+" Shell escaping {{{1
+
+function! foreplay#shellesc(arg) abort
+ if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
+ return a:arg
+ elseif &shell =~# 'cmd'
+ return '"'.substitute(substitute(a:arg, '"', '""', 'g'), '%', '"%"', 'g').'"'
+ else
+ let escaped = shellescape(a:arg)
+ if &shell =~# 'sh' && &shell !~# 'csh'
+ return substitute(escaped, '\\\n', '\n', 'g')
+ else
+ return escaped
+ endif
+ endif
+endfunction
+
+" }}}1
+" Completion {{{1
+
+function! foreplay#eval_complete(A, L, P) abort
+ let prefix = matchstr(a:A, '\%(.* \|^\)\%(#\=[\[{('']\)*')
+ let keyword = a:A[strlen(prefix) : -1]
+ return sort(map(foreplay#omnicomplete(0, keyword), 'prefix . v:val.word'))
+endfunction
+
+function! foreplay#ns_complete(A, L, P) abort
+ let matches = []
+ for pattern in split(&path, ',')
+ for dir in split(glob(pattern), "\n")
+ if dir =~# '\.jar$' && executable('zipinfo')
+ let files = split(system('zipinfo -1 '.shellescape(dir).' "*.clj"'), "\n")
+ if v:shell_error
+ let files = []
+ endif
+ else
+ let files = split(glob(dir."/**/*.clj"), "\n")
+ call map(files, 'v:val[strlen(dir)+1 : -1]')
+ endif
+ let matches += files
+ endfor
+ endfor
+ return filter(map(matches, 's:tons(v:val)'), 'a:A ==# "" || a:A ==# v:val[0 : strlen(a:A)-1]')
+endfunction
+
+function! foreplay#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 = foreplay#ns()
+
+ let [aliases, namespaces, maps] = foreplay#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 = foreplay#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 foreplay_completion
+ autocmd!
+ autocmd FileType clojure setlocal omnifunc=foreplay#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:qsym(symbol)
+ if a:symbol =~# '^[[:alnum:]?*!+/=<>.:-]\+$'
+ return "'".a:symbol
+ else
+ return '(symbol "'.escape(a:symbol, '"').'")'
+ endif
+endfunction
+
+function! s:repl.eval(expr, ns) dict abort
+ try
+ let result = self.connection.eval(a:expr, a:ns)
+ catch /^\w\+: Connection/
+ 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\)\=$' || has_key(self.requires, a:lib)
+ return ''
+ endif
+ let result = self.eval('(require '.s:qsym(a:lib).')', 'user')
+ let self.requires[a:lib] = 1
+ return result
+endfunction
+
+function! s:repl.includes_file(file) dict abort
+ let file = substitute(a:file, '\C^zipfile:\(.*\)::', '\1/', '')
+ for path in self.connection.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=? ForeplayConnect :exe s:Connect(<q-args>)
+
+function! foreplay#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/*/foreplay_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}#foreplay_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}#foreplay_connection#prompt()
+ endif
+ try
+ let connection = {proto}#foreplay_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 = exists('b:java_root') ? b:java_root : fnamemodify(expand('%'), ':p:s?.*\zs[\/]src[\/].*??')
+ let root = input('Path to root of project: ', path, 'dir')
+ if root !=# ''
+ let s:repls[root] = client
+ endif
+ echo "\n"
+ return ''
+endfunction
+
+augroup foreplay_connect
+ autocmd!
+ autocmd FileType clojure command! -bar -complete=customlist,s:connect_complete -nargs=? Connect :ForeplayConnect <args>
+augroup END
+
+" }}}1
+" Java runner {{{1
+
+if !exists('g:java_cmd')
+ let g:java_cmd = exists('$JAVA_CMD') ? $JAVA_CMD : 'java'
+endif
+
+let s:oneoff = {}
+
+let s:oneoff_pr = tempname()
+let s:oneoff_ex = tempname()
+let s:oneoff_in = tempname()
+let s:oneoff_out = tempname()
+let s:oneoff_err = tempname()
+
+function! s:oneoff.eval(expr, ns) dict abort
+ if &verbose
+ echohl WarningMSG
+ echomsg "No REPL found. Running java clojure.main ..."
+ echohl None
+ endif
+ 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(split('(do '.a:expr.')', "\n"), s:oneoff_in, 'b')
+ call writefile([], s:oneoff_out, 'b')
+ call writefile([], s:oneoff_err, 'b')
+ let command = g:java_cmd.' -cp '.shellescape(self.classpath).' clojure.main -e ' .
+ \ foreplay#shellesc(
+ \ '(binding [*out* (java.io.FileWriter. "'.s:oneoff_out.'")' .
+ \ ' *err* (java.io.FileWriter. "'.s:oneoff_err.'")]' .
+ \ ' (try' .
+ \ ' (require ''clojure.repl) '.ns.'(spit "'.s:oneoff_pr.'" (pr-str (eval (read-string (slurp "'.s:oneoff_in.'")))))' .
+ \ ' (catch Exception e' .
+ \ ' (spit *err* (.toString e))' .
+ \ ' (spit "'.s:oneoff_ex.'" (class e))))' .
+ \ ' nil)')
+ let wtf = system(command)
+ let pr = join(readfile(s:oneoff_pr, 'b'), "\n")
+ let out = join(readfile(s:oneoff_out, 'b'), "\n")
+ let err = join(readfile(s:oneoff_err, 'b'), "\n")
+ let ex = join(readfile(s:oneoff_err, 'b'), "\n")
+ if v:shell_error && ex ==# ''
+ throw 'Error running Clojure: '.wtf
+ else
+ if err !=# ''
+ echohl ErrorMSG
+ echo substitute(err, '\n$', '', '')
+ echohl None
+ endif
+ if out !=# ''
+ echo substitute(out, "\n$", '', '')
+ endif
+ if v:shell_error
+ throw 'Clojure: '.ex
+ else
+ return pr
+ endif
+ endif
+endfunction
+
+function! s:oneoff.require(symbol)
+ return ''
+endfunction
+
+" }}}1
+" Client {{{1
+
+function! s:client() abort
+ silent doautocmd User ForeplayPreConnect
+ let buf = exists('s:input') ? s:input : '%'
+ 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 foreplay#local_client(1)
+endfunction
+
+function! foreplay#client() abort
+ return s:client()
+endfunction
+
+function! foreplay#local_client(...)
+ if !a:0
+ silent doautocmd User ForeplayPreConnect
+ endif
+ let buf = exists('s:input') ? s:input : '%'
+ for repl in s:repls
+ if repl.includes_file(fnamemodify(bufname(buf), ':p'))
+ return repl
+ endif
+ endfor
+ let cp = classpath#from_vim(getbufvar(buf, '&path'))
+ return extend({'classpath': cp}, s:oneoff)
+endfunction
+
+function! foreplay#eval(expr, ...) abort
+ let c = s:client()
+ if !a:0
+ call c.require(foreplay#ns())
+ endif
+ return c.eval(a:expr, a:0 ? a:1 : foreplay#ns())
+endfunction
+
+function! foreplay#evalparse(expr) abort
+ let body = foreplay#eval(
+ \ '(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))) "]")' .
+ \ ' (number? x) (pr-str x)' .
+ \ ' (keyword? x) (pr-str (name x))' .
+ \ ' :else (pr-str (str x)))) '.a:expr.'))',
+ \ a:0 ? a:1 : foreplay#ns())
+ if body ==# ''
+ return ''
+ else
+ return eval(body)
+ endif
+endfunction
+
+" }}}1
+" Eval {{{1
+
+let foreplay#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! `[\<C-V>`]y"
+ elseif a:type ==# 'outer'
+ call searchpair('(','',')', 'Wbcr', g:foreplay#skip)
+ silent exe "normal! vaby"
+ else
+ silent exe "normal! `[v`]y"
+ endif
+ 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\+').foreplay#eval(expr, foreplay#ns()).matchstr(expr, '\n\+$')
+ if @@ !~# '^\n*$'
+ normal! gvp
+ endif
+ catch /^Clojure:/
+ return ''
+ finally
+ let @@ = reg_save
+ endtry
+endfunction
+
+function! s:printop(type) abort
+ try
+ echo foreplay#eval(s:opfunc(a:type))
+ catch /^Clojure:/
+ return ''
+ endtry
+endfunction
+
+function! s:editop(type) abort
+ call feedkeys(&cedit . "\<Home>", 'n')
+ let input = s:input(substitute(s:opfunc(a:type), '\n\s*', ' ', 'g'))
+ try
+ if input !=# ''
+ echo foreplay#eval(input)
+ endif
+ catch /^Clojure:/
+ return ''
+ endtry
+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:foreplay#skip)
+ let line2 = searchpair('(','',')', 'n', g:foreplay#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
+ try
+ let result = foreplay#eval(expr)
+ if a:bang
+ if a:args !=# ''
+ call append(a:line1, result)
+ exe a:line1
+ else
+ call append(a:line1-1, result)
+ exe a:line1-1
+ endif
+ else
+ echo result
+ endif
+ catch /^Clojure:/
+ endtry
+ 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:FOREPLAY_HISTORY')
+ let g:FOREPLAY_HISTORY = []
+ endif
+ try
+ let s:input = bufnr('%')
+ let s:oldhist = s:histswap(g:FOREPLAY_HISTORY)
+ return s:actually_input(foreplay#ns().'=> ', a:default, 'customlist,foreplay#eval_complete')
+ finally
+ unlet! s:input
+ if exists('s:oldhist')
+ let g:FOREPLAY_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 ==# ''
+ return ''
+ else
+ try
+ echo foreplay#eval(input)
+ return ''
+ catch /^Clojure:/
+ return ''
+ catch
+ return 'echoerr '.string(v:exception)
+ endtry
+ endif
+endfunction
+
+function! s:recall() abort
+ try
+ cnoremap <expr> ) <SID>inputclose()
+ let input = s:input('(')
+ if input =~# '^(\=$'
+ return ''
+ else
+ return foreplay#eval(input)
+ endif
+ catch /^Clojure:/
+ return ''
+ finally
+ silent! cunmap )
+ endtry
+endfunction
+
+function! s:histswap(list)
+ 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>ForeplacePrint :<C-U>set opfunc=<SID>printop<CR>g@
+xnoremap <silent> <Plug>ForeplacePrint :<C-U>call <SID>printop(visualmode())<CR>
+
+nnoremap <silent> <Plug>ForeplaceFilter :<C-U>set opfunc=<SID>filterop<CR>g@
+xnoremap <silent> <Plug>ForeplaceFilter :<C-U>call <SID>filterop(visualmode())<CR>
+
+nnoremap <silent> <Plug>ForeplaceEdit :<C-U>set opfunc=<SID>editop<CR>g@
+xnoremap <silent> <Plug>ForeplaceEdit :<C-U>call <SID>editop(visualmode())<CR>
+
+nnoremap <Plug>ForeplacePrompt :exe <SID>inputeval()<CR>
+
+noremap! <Plug>ForeplaceRecall <C-R>=<SID>recall()<CR>
+
+function! s:eval_setup() abort
+ command! -buffer -bang -range=0 -nargs=? -complete=customlist,foreplay#eval_complete Eval :exe s:Eval(<bang>0, <line1>, <line2>, <count>, <q-args>)
+
+ nmap <buffer> cp <Plug>ForeplacePrint
+ xmap <buffer> cp <Plug>ForeplacePrint
+ nmap <buffer> cpp <Plug>ForeplacePrintab
+
+ nmap <buffer> c! <Plug>ForeplaceFilter
+ xmap <buffer> c! <Plug>ForeplaceFilter
+ nmap <buffer> c!! <Plug>ForeplaceFilterab
+
+ nmap <buffer> cq <Plug>ForeplaceEdit
+ nmap <buffer> cqq <Plug>ForeplaceEditab
+
+ nmap <buffer> cqp <Plug>ForeplacePrompt
+ exe 'nmap <buffer> cqc <Plug>ForeplacePrompt' . &cedit . 'i'
+
+ map! <buffer> <C-R>( <Plug>ForeplaceRecall
+endfunction
+
+function! s:cmdwinenter()
+ setlocal filetype=clojure
+endfunction
+
+function! s:cmdwinleave()
+ setlocal filetype< omnifunc<
+endfunction
+
+augroup foreplay_eval
+ autocmd!
+ autocmd FileType clojure call s:eval_setup()
+ 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 = ('(require '.s:qsym(a:ns ==# '' ? foreplay#ns() : a:ns).' :reload'.(a:bang ? '-all' : '').')')
+ echo cmd
+ try
+ call foreplay#eval(cmd)
+ return ''
+ catch /^Clojure:.*/
+ return ''
+ endtry
+endfunction
+
+augroup foreplay_require
+ autocmd!
+ autocmd FileType clojure command! -buffer -bar -bang -complete=customlist,foreplay#ns_complete -nargs=? Require :exe s:Require(<bang>0, <q-args>)
+augroup END
+
+" }}}1
+" Go to source {{{1
+
+function! foreplay#source(symbol) abort
+ let c = foreplay#local_client()
+ call c.require(foreplay#ns())
+ 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)]' .
+ \ ' (symbol (str (str "+" (:line (meta v))) " "' .
+ \ ' (if (= "jar" (.getProtocol url))' .
+ \ ' (str "zip" (.replaceFirst (.getFile url) "!/" "::"))' .
+ \ ' (.getFile url)))))))'
+ let result = get(split(c.eval(cmd, foreplay#ns()), "\n"), 0, '')
+ return result ==# 'nil' ? '' : result
+endfunction
+
+function! s:Edit(cmd, keyword) abort
+ if a:keyword =~# '^\k\+/$'
+ let location = foreplay#findfile(a:keyword[0: -2])
+ elseif a:keyword =~# '^\k\+\.[^/.]\+$'
+ let location = foreplay#findfile(a:keyword)
+ else
+ let location = foreplay#source(a:keyword)
+ endif
+ 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.': '.location
+ return 'echoerr v:errmsg'
+endfunction
+
+augroup foreplay_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,foreplay#eval_complete Djump :exe s:Edit('edit', <q-args>)
+ autocmd FileType clojure command! -bar -buffer -nargs=1 -complete=customlist,foreplay#eval_complete Dsplit :exe s:Edit('split', <q-args>)
+ autocmd FileType clojure nnoremap <silent><buffer> [<C-D> :<C-U>exe <SID>Edit('edit', expand('<cword>'))<CR>
+ autocmd FileType clojure nnoremap <silent><buffer> ]<C-D> :<C-U>exe <SID>Edit('edit', expand('<cword>'))<CR>
+ autocmd FileType clojure nnoremap <silent><buffer> <C-W><C-D> :<C-U>exe <SID>Edit('split', expand('<cword>'))<CR>
+ autocmd FileType clojure nnoremap <silent><buffer> <C-W>d :<C-U>exe <SID>Edit('split', expand('<cword>'))<CR>
+ autocmd FileType clojure nnoremap <silent><buffer> <C-W>gd :<C-U>exe <SID>Edit('tabedit', expand('<cword>'))<CR>
+augroup END
+
+" }}}1
+" Go to file {{{1
+
+function! foreplay#findfile(path) abort
+ let c = foreplay#local_client()
+ call c.require(foreplay#ns())
+
+ let cmd =
+ \ '(symbol' .
+ \ ' (or' .
+ \ ' (when-let [url (.getResource (clojure.lang.RT/baseLoader) %s)]' .
+ \ ' (if (= "jar" (.getProtocol url))' .
+ \ ' (str "zip" (.replaceFirst (.getFile url) "!/" "::"))' .
+ \ ' (.getFile 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(c.eval(aliascmd, foreplay#ns()), "\n"), 0, '')
+ else
+ if path !~# '/'
+ let path = tr(path, '.-', '/_')
+ endif
+ if path !~# '\.\w\+$'
+ let path .= '.clj'
+ endif
+
+ let result = get(split(c.eval(printf(cmd, '"'.escape(path, '"').'"'), foreplay#ns()), "\n"), 0, '')
+
+ endif
+ if result ==# ''
+ return findfile(path, &l: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
+ let file = foreplay#findfile(file)
+ 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 foreplay_go_to_file
+ autocmd!
+ autocmd FileType clojure nnoremap <silent><buffer> gf :<C-U>exe <SID>GF('edit', expand('<cfile>'))<CR>
+ autocmd FileType clojure nnoremap <silent><buffer> <C-W>f :<C-U>exe <SID>GF('split', expand('<cfile>'))<CR>
+ autocmd FileType clojure nnoremap <silent><buffer> <C-W><C-F> :<C-U>exe <SID>GF('split', expand('<cfile>'))<CR>
+ autocmd FileType clojure nnoremap <silent><buffer> <C-W>gf :<C-U>exe <SID>GF('tabedit', expand('<cfile>'))<CR>
+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/', '')
+ 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
+ return ''
+endfunction
+
+function! s:tons(path) abort
+ return tr(substitute(a:path, '\.\w\+$', '', ''), '\/_', '..-')
+endfunction
+
+function! foreplay#ns() abort
+ return s:tons(s:buffer_path())
+endfunction
+
+function! s:Lookup(macro, arg) abort
+ " doc is in clojure.core in older Clojure versions
+ try
+ call foreplay#eval("(eval (list (if (ns-resolve 'clojure.core '".a:macro.") '".a:macro." 'clojure.repl/".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 = foreplay#evalparse('(apropos '.pattern.')')
+ if empty(matches)
+ return ''
+ endif
+ let choice = s:inputlist('Lookup docs for:', matches)
+ if choice !=# ''
+ return 'echo "\n"|Doc '.choice
+ else
+ return ''
+ endif
+endfunction
+
+augroup foreplay_doc
+ autocmd!
+ autocmd FileType clojure nnoremap <buffer> K :Doc <C-R><C-W><CR>
+ autocmd FileType clojure nnoremap <buffer> [d :Source <C-R><C-W><CR>
+ autocmd FileType clojure nnoremap <buffer> ]d :Source <C-R><C-W><CR>
+ autocmd FileType clojure command! -buffer -nargs=1 FindDoc :exe s:Lookup('find-doc', printf('#"%s"', <q-args>))
+ autocmd FileType clojure command! -buffer -bar -nargs=1 -complete=customlist,foreplay#eval_complete Doc :exe s:Lookup('doc', <q-args>)
+ autocmd FileType clojure command! -buffer -bar -nargs=1 -complete=customlist,foreplay#eval_complete Source :exe s:Lookup('source', <q-args>)
+ autocmd FileType clojure command! -buffer -nargs=1 -complete=customlist,foreplay#eval_complete Apropos :exe s:Apropos(<q-args>)
+augroup END
+
+" }}}1
+" Leiningen {{{1
+
+function! s:hunt(start, anchor) abort
+ let root = simplify(fnamemodify(a:start, ':p:s?[\/]$??'))
+ let previous = ""
+ while root !=# previous
+ if filereadable(root . '/' .a:anchor) && isdirectory(root . '/src')
+ 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 = readfile(portfile, 'b', 1)[0]
+ let s:leiningen_repl_ports[b:leiningen_root] = getftime(portfile)
+ try
+ call s:register_connection(nrepl#foreplay_connection#open(port), b:leiningen_root)
+ catch /^nREPL: Connection/
+ call delete(portfile)
+ endtry
+ endif
+endfunction
+
+function! s:leiningen_init() abort
+
+ if !exists('b:leiningen_root')
+ let root = s:hunt(expand('%:p'), 'project.clj')
+ 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 foreplay_leiningen
+ autocmd!
+ autocmd User ForeplacePreConnect call s:leiningen_connect()
+ autocmd FileType clojure call s:leiningen_init()
+augroup END
+
+" }}}1
+
+" vim:set et sw=2:
Please sign in to comment.
Something went wrong with that request. Please try again.