Compare commits

..

1 Commits
master ... cqP

Author SHA1 Message Date
Tim Pope 37ace16eac Provide "REPL" on cqP 2012-12-29 13:36:02 -05:00
13 changed files with 1547 additions and 2586 deletions

1
.gitignore vendored
View File

@ -1,2 +1 @@
/doc/tags /doc/tags
*.pyc

View File

@ -1,10 +0,0 @@
## Contributing
Open [GitHub issues][] for bug reports and feature requests.
I'm a stickler for [commit messages][], so if you send me a pull
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-fireplace/issues
[commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html

View File

@ -1,27 +1,24 @@
# fireplace.vim # foreplay.vim
There's a REPL in fireplace, but you probably wouldn't have noticed if I hadn't There's a REPL in foreplay, but you probably wouldn't have noticed if I hadn't
told you. Such is the way with fireplace.vim. By the way, this plugin is for told you. Such is the way with foreplay.vim. By the way, this plugin is for
Clojure. Clojure.
## Installation ## Installation
First, set up [cider-nrepl][]. (If you skip this step, fireplace.vim will Foreplay.vim doesn't provide indenting or syntax highlighting, so you'll want
make do with eval, which mostly works.) Next, fireplace.vim doesn't provide [a set of Clojure runtime files](https://github.com/guns/vim-clojure-static).
indenting or syntax highlighting, so you'll want [a set of Clojure runtime
files](https://github.com/guns/vim-clojure-static) if you're on a version of
Vim earlier than 7.4. You might also want [salve.vim][] for assorted
static project support.
If you don't have a preferred installation method, I recommend If you don't have a preferred installation method, I recommend
installing [pathogen.vim](https://github.com/tpope/vim-pathogen), and installing [pathogen.vim](https://github.com/tpope/vim-pathogen), and
then simply copy and paste: then simply copy and paste:
cd ~/.vim/bundle cd ~/.vim/bundle
git clone git://github.com/tpope/vim-fireplace.git git clone git://github.com/tpope/vim-foreplay.git
git clone git://github.com/guns/vim-clojure-static.git
Once help tags have been generated, you can view the manual with Once help tags have been generated, you can view the manual with
`:help fireplace`. `:help foreplay`.
## Features ## Features
@ -29,25 +26,17 @@ This list isn't exhaustive; see the `:help` for details.
### Transparent setup ### Transparent setup
Fireplace.vim talks to nREPL. With Leiningen, it connects automatically based Foreplay.vim talks to nREPL. With Leiningen 2, it connects automatically
on `.nrepl-port`, otherwise it's just a `:Connect` away. You can connect to based on `target/repl-port`, otherwise it's just a `:Connect` away. You can
multiple instances of nREPL for different projects, and it will use the right connect to multiple instances of nREPL for different projects, and it will
one automatically. ClojureScript support is just as seamless with use the right one automatically.
[Piggieback][].
The only external dependency is that you have either a Vim with Python support The only external dependency is that you have Ruby installed.
compiled in, or `python` in your path.
Oh, and if you don't have an nREPL connection, installing [salve.vim][] Oh, and if you don't have an nREPL connection, it falls back to using
lets it fall back to using `java clojure.main` for some of the basics, using a `java clojure.main`, using a class path based on your Leiningen or Maven
class path based on your Leiningen config. It's a bit slow, but a two-second config. It's a bit slow, but a two second delay its vastly preferable to
delay is vastly preferable to being forced out of my flow for a single being forced out of my flow for a single command, in my book.
command, in my book.
[cider-nrepl]: https://github.com/clojure-emacs/cider-nrepl
[Piggieback]: https://github.com/cemerick/piggieback
[classpath.vim]: https://github.com/tpope/vim-classpath
[salve.vim]: https://github.com/tpope/vim-salve
### Not quite a REPL ### Not quite a REPL
@ -56,7 +45,7 @@ absolutely flawlessly, never breaking just because you did something innocuous
like backspace through part of the prompt? No? Such a shame, you really like backspace through part of the prompt? No? Such a shame, you really
would have liked it. would have liked it.
I've taken a different approach in fireplace.vim. `cq` (Think "Clojure 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 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 window* — the same thing you get when you hit `q:` — but set up for Clojure
code. code.
@ -68,24 +57,15 @@ cursor. `cqc` gives you a blank line in insert mode.
Standard stuff here. `:Eval` evaluates a range (`:%Eval` gets the whole Standard stuff here. `:Eval` evaluates a range (`:%Eval` gets the whole
file), `:Require` requires a namespace with `:reload` (`:Require!` does file), `:Require` requires a namespace with `:reload` (`:Require!` does
`:reload-all`), either the current buffer or a given argument. `:RunTests` `:reload-all`), either the current buffer or a given argument. There's a `cp`
kicks off `(clojure.test/run-tests)` and loads the results into the quickfix operator that evaluates a given motion (`cpp` for the expression under the
list. cursor).
There's a `cp` operator that evaluates a given motion (`cpp` for the
innermost form under the cursor). `cm` and `c1m` are similar, but they only
run `clojure.walk/macroexpand-all` and `macroexpand-1` instead of evaluating
the form entirely.
Any failed evaluation loads the stack trace into the location list, which
can be easily accessed with `:lopen`.
### Navigating and Comprehending ### Navigating and Comprehending
I was brand new to Clojure when I started this plugin, so stuff that helped me I'm new to Clojure, so stuff that helps me understand code is a top priority.
understand code was a top priority.
* `:Source`, `:Doc`, and `:FindDoc`, which map to the underlying * `:Source`, `:Doc`, `:FindDoc`, and `:Apropros`, which map to the underlying
`clojure.repl` macro (with tab complete, of course). `clojure.repl` macro (with tab complete, of course).
* `K` is mapped to look up the symbol under the cursor with `doc`. * `K` is mapped to look up the symbol under the cursor with `doc`.
@ -104,17 +84,47 @@ Where possible, I favor enhancing built-ins over inventing a bunch of
Because why not? It works in the quasi-REPL too. Because why not? It works in the quasi-REPL too.
## FAQ ### FAQ
> Why does it take so long for Vim to startup? > Why does it take so long for Vim to startup?
That's either [classpath.vim][] or [salve.vim][]. 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 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 ## Self-Promotion
Like fireplace.vim? Follow the repository on Like foreplay.vim? Follow the repository on
[GitHub](https://github.com/tpope/vim-fireplace) and vote for it on [GitHub](https://github.com/tpope/vim-foreplay). And if
[vim.org](http://www.vim.org/scripts/script.php?script_id=4978). And if
you're feeling especially charitable, follow [tpope](http://tpo.pe/) on you're feeling especially charitable, follow [tpope](http://tpo.pe/) on
[Twitter](http://twitter.com/tpope) and [Twitter](http://twitter.com/tpope) and
[GitHub](https://github.com/tpope). [GitHub](https://github.com/tpope).

126
autoload/classpath.vim Normal file
View File

@ -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'), 1), "\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 !isdirectory(fnamemodify(root, ':h'))
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:

View File

@ -1,263 +0,0 @@
" Location: autoload/nrepl/fireplace.vim
if exists("g:autoloaded_fireplace_nrepl")
finish
endif
let g:autoloaded_fireplace_nrepl = 1
function! s:function(name) abort
return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
endfunction
if !exists('s:id')
let s:vim_id = localtime()
let s:id = 0
endif
function! fireplace#nrepl#next_id() abort
let s:id += 1
return 'fireplace-'.hostname().'-'.s:vim_id.'-'.s:id
endfunction
if !exists('g:fireplace_nrepl_sessions')
let g:fireplace_nrepl_sessions = {}
endif
augroup fireplace_nrepl_connection
autocmd!
autocmd VimLeave * for s:session in values(g:fireplace_nrepl_sessions)
\ | call s:session.close()
\ | endfor
augroup END
function! fireplace#nrepl#for(transport) abort
let client = copy(s:nrepl)
let client.transport = a:transport
let client.session = client.process({'op': 'clone', 'session': 0})['new-session']
let client.describe = client.process({'op': 'describe', 'verbose?': 1})
if get(client.describe.versions.nrepl, 'major', -1) == 0 &&
\ client.describe.versions.nrepl.minor < 2
throw 'nREPL: 0.2.0 or higher required'
endif
" Handle boot, which sets a fake.class.path entry
let response = client.process({'op': 'eval', 'code':
\ '[(System/getProperty "path.separator") (System/getProperty "fake.class.path")]', 'session': ''})
let cpath = response.value[-1][5:-2]
if cpath !=# 'nil'
let cpath = eval(cpath)
if !empty(cpath)
let client._path = split(cpath, response.value[-1][2])
endif
endif
if !has_key(client, '_path') && client.has_op('classpath')
let response = client.message({'op': 'classpath'})[0]
if type(get(response, 'classpath')) == type([])
let client._path = response.classpath
endif
endif
if !has_key(client, '_path')
let response = client.process({'op': 'eval', 'code':
\ '[(System/getProperty "path.separator") (System/getProperty "java.class.path")]', 'session': ''})
let client._path = split(eval(response.value[-1][5:-2]), response.value[-1][2])
endif
let g:fireplace_nrepl_sessions[client.session] = client
return client
endfunction
function! s:nrepl_close() dict abort
if has_key(self, 'session')
try
unlet! g:fireplace_nrepl_sessions[self.session]
call self.message({'op': 'close'}, 'ignore')
catch
finally
unlet self.session
endtry
endif
call self.transport.close()
return self
endfunction
function! s:nrepl_clone() dict abort
let client = copy(self)
if has_key(self, 'session')
let client.session = client.process({'op': 'clone'})['new-session']
let g:fireplace_nrepl_sessions[client.session] = client
endif
return client
endfunction
function! s:nrepl_path() dict abort
return self._path
endfunction
function! fireplace#nrepl#combine(responses)
let combined = {'status': [], 'session': []}
for response in a:responses
for key in keys(response)
if key ==# 'id' || key ==# 'ns'
let combined[key] = response[key]
elseif key ==# 'value'
let combined.value = extend(get(combined, 'value', []), [response.value])
elseif key ==# 'status'
for entry in response[key]
if index(combined[key], entry) < 0
call extend(combined[key], [entry])
endif
endfor
elseif key ==# 'session'
if index(combined[key], response[key]) < 0
call extend(combined[key], [response[key]])
endif
elseif type(response[key]) == type('')
let combined[key] = get(combined, key, '') . response[key]
else
let combined[key] = response[key]
endif
endfor
endfor
return combined
endfunction
function! s:nrepl_process(msg) dict abort
let combined = fireplace#nrepl#combine(self.message(a:msg))
if index(combined.status, 'error') < 0
return combined
endif
throw 'nREPL: ' . tr(combined.status[0], '-', ' ')
endfunction
function! s:nrepl_eval(expr, ...) dict abort
let msg = {"op": "eval"}
let msg.code = a:expr
let options = a:0 ? a:1 : {}
if has_key(options, 'ns')
let msg.ns = options.ns
elseif has_key(self, 'ns')
let msg.ns = self.ns
endif
if has_key(options, 'session')
let msg.session = options.session
endif
if has_key(options, 'id')
let msg.id = options.id
else
let msg.id = fireplace#nrepl#next_id()
endif
if has_key(options, 'file_path')
let msg.op = 'load-file'
let msg['file-path'] = options.file_path
let msg['file-name'] = fnamemodify(options.file_path, ':t')
if has_key(msg, 'ns')
let msg.file = "(in-ns '".msg.ns.") ".msg.code
call remove(msg, 'ns')
else
let msg.file = msg.code
endif
call remove(msg, 'code')
endif
try
let response = self.process(msg)
finally
if !exists('response')
let session = get(msg, 'session', self.session)
if !empty(session)
call self.message({'op': 'interrupt', 'session': session, 'interrupt-id': msg.id}, 'ignore')
endif
throw 'Clojure: Interrupt'
endif
endtry
if has_key(response, 'ns') && empty(get(options, 'ns'))
let self.ns = response.ns
endif
if has_key(response, 'ex') && !empty(get(msg, 'session', 1))
let response.stacktrace = s:extract_last_stacktrace(self, get(msg, 'session', self.session))
endif
if has_key(response, 'value')
let response.value = response.value[-1]
endif
return response
endfunction
function! s:extract_last_stacktrace(nrepl, session) abort
if a:nrepl.has_op('stacktrace')
let stacktrace = a:nrepl.message({'op': 'stacktrace', 'session': a:session})
if len(stacktrace) > 0 && has_key(stacktrace[0], 'stacktrace')
let stacktrace = stacktrace[0].stacktrace
endif
call filter(stacktrace, 'has_key(v:val, "file")')
if !empty(stacktrace)
return map(stacktrace, 'v:val.class.".".v:val.method."(".v:val.file.":".v:val.line.")"')
endif
endif
let format_st = '(symbol (str "\n\b" (apply str (interleave (repeat "\n") (map str (.getStackTrace *e)))) "\n\b\n"))'
let response = a:nrepl.process({'op': 'eval', 'code': '['.format_st.' *3 *2 *1]', 'ns': 'user', 'session': a:session})
try
let stacktrace = split(get(split(response.value[0], "\n\b\n"), 1, ""), "\n")
catch
throw string(response)
endtry
call a:nrepl.message({'op': 'eval', 'code': '(*1 1)', 'ns': 'user', 'session': a:session})
call a:nrepl.message({'op': 'eval', 'code': '(*2 2)', 'ns': 'user', 'session': a:session})
call a:nrepl.message({'op': 'eval', 'code': '(*3 3)', 'ns': 'user', 'session': a:session})
return stacktrace
endfunction
let s:keepalive = tempname()
call writefile([getpid()], s:keepalive)
function! s:nrepl_prepare(msg) dict abort
let msg = copy(a:msg)
if !has_key(msg, 'id')
let msg.id = fireplace#nrepl#next_id()
endif
if empty(get(msg, 'ns', 1))
unlet msg.ns
endif
if empty(get(msg, 'session', 1))
unlet msg.session
elseif !has_key(msg, 'session')
let msg.session = self.session
endif
return msg
endfunction
function! fireplace#nrepl#callback(body, type, callback) abort
try
let response = {'body': a:body, 'type': a:type}
if has_key(g:fireplace_nrepl_sessions, get(a:body, 'session'))
let response.session = g:fireplace_nrepl_sessions[a:body.session]
endif
call call(a:callback[0], [response] + a:callback[1:-1])
catch
endtry
endfunction
function! s:nrepl_call(msg, ...) dict abort
let terms = a:0 ? a:1 : ['done']
let sels = a:0 > 1 ? a:2 : {}
return call(self.transport.call, [a:msg, terms, sels] + a:000[2:-1], self.transport)
endfunction
function! s:nrepl_message(msg, ...) dict abort
let msg = self.prepare(a:msg)
let sel = {'id': msg.id}
return call(self.call, [msg, ['done'], sel] + a:000, self)
endfunction
function! s:nrepl_has_op(op) dict abort
return has_key(self.describe.ops, a:op)
endfunction
let s:nrepl = {
\ 'close': s:function('s:nrepl_close'),
\ 'clone': s:function('s:nrepl_clone'),
\ 'prepare': s:function('s:nrepl_prepare'),
\ 'call': s:function('s:nrepl_call'),
\ 'message': s:function('s:nrepl_message'),
\ 'eval': s:function('s:nrepl_eval'),
\ 'has_op': s:function('s:nrepl_has_op'),
\ 'path': s:function('s:nrepl_path'),
\ 'process': s:function('s:nrepl_process')}

View File

@ -1,156 +0,0 @@
" Location: autoload/nrepl/fireplace_connection.vim
if exists("g:autoloaded_nrepl_fireplace_connection") || &cp
finish
endif
let g:autoloaded_nrepl_fireplace_connection = 1
let s:python_dir = fnamemodify(expand("<sfile>"), ':p:h:h:h') . '/python'
function! s:function(name) abort
return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
endfunction
" Bencode {{{1
function! fireplace#nrepl_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(copy(a:value),'fireplace#nrepl_connection#bencode(v:val)'),'').'e'
elseif type(a:value) == type({})
return 'd'.join(map(
\ sort(keys(a:value)),
\ 'fireplace#nrepl_connection#bencode(v:val) . ' .
\ 'fireplace#nrepl_connection#bencode(a:value[v:val])'
\ ),'').'e'
else
throw "Can't bencode ".string(a:value)
endif
endfunction
" }}}1
function! s:shellesc(arg) abort
if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
return a:arg
elseif &shell =~# 'cmd'
throw 'Python interface not working. See :help python-dynamic'
else
let escaped = shellescape(a:arg)
if &shell =~# 'sh' && &shell !~# 'csh'
return substitute(escaped, '\\\n', '\n', 'g')
else
return escaped
endif
endif
endfunction
if !exists('s:id')
let s:vim_id = localtime()
let s:id = 0
endif
function! s:id() abort
let s:id += 1
return 'fireplace-'.hostname().'-'.s:vim_id.'-'.s:id
endfunction
function! fireplace#nrepl_connection#prompt() abort
return fireplace#input_host_port()
endfunction
function! fireplace#nrepl_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 transport = deepcopy(s:nrepl_transport)
let transport.host = host
let transport.port = port
return fireplace#nrepl#for(transport)
endfunction
function! s:nrepl_transport_close() dict abort
return self
endfunction
let s:keepalive = tempname()
call writefile([getpid()], s:keepalive)
function! s:nrepl_transport_command(cmd, args) dict abort
return 'python'
\ . ' ' . s:shellesc(s:python_dir.'/nrepl_fireplace.py')
\ . ' ' . s:shellesc(self.host)
\ . ' ' . s:shellesc(self.port)
\ . ' ' . s:shellesc(s:keepalive)
\ . ' ' . s:shellesc(a:cmd)
\ . ' ' . join(map(copy(a:args), 's:shellesc(fireplace#nrepl_connection#bencode(v:val))'), ' ')
endfunction
function! s:nrepl_transport_dispatch(cmd, ...) dict abort
let in = self.command(a:cmd, a:000)
let out = system(in)
if !v:shell_error
return eval(out)
endif
throw 'nREPL: '.out
endfunction
function! s:nrepl_transport_call(msg, terms, sels, ...) dict abort
let payload = fireplace#nrepl_connection#bencode(a:msg)
let response = self.dispatch('call', payload, a:terms, a:sels)
if !a:0
return response
elseif a:1 !=# 'ignore'
return map(response, 'fireplace#nrepl#callback(v:val, "synchronous", a:000)')
endif
endfunction
let s:nrepl_transport = {
\ 'close': s:function('s:nrepl_transport_close'),
\ 'command': s:function('s:nrepl_transport_command'),
\ 'dispatch': s:function('s:nrepl_transport_dispatch'),
\ 'call': s:function('s:nrepl_transport_call')}
if !has('python') || $FIREPLACE_NO_IF_PYTHON
finish
endif
if !exists('s:python')
exe 'python sys.path.insert(0, "'.escape(s:python_dir, '\"').'")'
let s:python = 1
python import nrepl_fireplace
else
python reload(nrepl_fireplace)
endif
python << EOF
import vim
def fireplace_let(var, value):
return vim.command('let ' + var + ' = ' + nrepl_fireplace.vim_encode(value))
def fireplace_check():
vim.eval('getchar(1)')
def fireplace_repl_dispatch(command, *args):
try:
fireplace_let('out', nrepl_fireplace.dispatch(vim.eval('self.host'), vim.eval('self.port'), fireplace_check, None, command, *args))
except Exception, e:
fireplace_let('err', str(e))
EOF
function! s:nrepl_transport_dispatch(command, ...) dict abort
python fireplace_repl_dispatch(vim.eval('a:command'), *vim.eval('a:000'))
if !exists('err')
return out
endif
throw 'nREPL Connection Error: '.err
endfunction

View File

@ -0,0 +1,232 @@
" 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 session = client.process({'op': 'clone'})['new-session']
let response = client.process({'op': 'eval', 'session': session, 'code':
\ '(do (println "success") (symbol (str (System/getProperty "path.separator") (System/getProperty "java.class.path"))))'})
let client._path = response.value[-1]
if has_key(response, 'out')
let client.session = session
endif
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 combined = {'status': [], 'session': []}
for response in self.call(a:payload)
for key in keys(response)
if key ==# 'id' || key ==# 'ns'
let combined[key] = response[key]
elseif key ==# 'value'
let combined.value = extend(get(combined, 'value', []), [response.value])
elseif key ==# 'status'
for entry in response[key]
if index(combined[key], entry) < 0
call extend(combined[key], [entry])
endif
endfor
elseif key ==# 'session'
if index(combined[key], response[key]) < 0
call extend(combined[key], [response[key]])
endif
elseif type(response[key]) == type('')
let combined[key] = get(combined, key, '') . response[key]
else
let combined[key] = response[key]
endif
endfor
endfor
if index(combined.status, 'error') >= 0
throw 'nREPL: ' . tr(combined.status[0], '-', ' ')
endif
return combined
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
if has_key(self, 'session')
let payload.session = self.session
endif
let response = self.process(payload)
if has_key(response, 'ns') && !a:0
let self.ns = response.ns
endif
if has_key(response, 'value')
let response.value = response.value[-1]
endif
return response
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'
class << ::VIM
def string_encode(str)
'"' + str.gsub(/[\000-\037"\\]/) { |x| "\\%03o" % (x.respond_to?(:ord) ? x.ord : x[0]) } + '"'
end
def let(var, value)
command("let #{var} = #{string_encode(value)}")
end
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:

View File

@ -1,194 +0,0 @@
*fireplace.txt* Clojure REPL support
Author: Tim Pope <http://tpo.pe/>
License: Same terms as Vim itself (see |license|)
This plugin is only available if 'compatible' is not set.
*fireplace*
While not strictly necessary, this plugin works best with the middleware
provided by <https://github.com/clojure-emacs/cider-nrepl>.
CONNECTING TO A REPL *fireplace-connect*
Connecting to lein repl happens automatically. If you have a different setup,
you can connect by hand.
*fireplace-:Connect*
:Connect {proto}://{host}:{port} {path}
Connect to a REPL server. The path is the root of the
project that the REPL applies to.
: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 and 'path' contains at least one jar file, java (or $JAVA_CMD) is
invoked directly, which can be quite slow depending on your setup.
The only adapter shipped with fireplace.vim is for nREPL. You need either
|if_pyth| or the python command in your PATH.
*fireplace-piggieback* *fireplace-clojurescript*
ClojureScript can be evaled with Piggieback if the appropriate nREPL
middleware is loaded: https://github.com/cemerick/piggieback. Be aware that
your ClojureScript files must be available on the classpath for this to work
properly, and that not all operations are supported.
*fireplace-:Piggieback*
:Piggieback [{env}] Create a new nREPL session and invoke
cemerick.piggieback/cljs-repl with the given or
default (Rhino) environment. This will also happen
automatically on first eval in a ClojureScript buffer
if not invoked explicitly. If {env} is a number, the
piggieback repl-env will will use a cljs.repl.browser
(rather than a Rhino) env with the port set to the
number provided.
:Piggieback! Terminate the most recently created piggieback
session.
DOCUMENTATION *fireplace-documentation*
*fireplace-:Doc*
:Doc {symbol} Show the docs for the given symbol.
*fireplace-:Javadoc*
:Javadoc {class} Open the java docs for the given class in a browser.
*fireplace-K*
K Look up docs for symbol under cursor.
*fireplace-:FindDoc*
:FindDoc {arg} Wrapper around (clojure.repl/find-doc ...).
*fireplace-:Source*
:Source {symbol} Show the source for the given symbol.
*fireplace-[d*
[d Show source for symbol under cursor.
]d
NAVIGATING *fireplace-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.
*fireplace-[_CTRL-D*
[<C-D> Jump to the source of the symbol under the cursor.
]<C-D>
*fireplace-CTRL-W_CTRL-D*
<C-W><C-D> Jump to the source of the symbol under the cursor in
<C-W>d a split.
*fireplace-gf*
gf Go to the file for the namespace under the cursor.
*fireplace-:Djump*
:Djump {symbol} Jump to the definition for the given symbol.
*fireplace-:Dsplit*
:Dsplit {symbol} Jump to the definition for the given symbol in a
split.
EVALUATING CODE *fireplace-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. If an exception occurs, the
stack trace is loaded into the |location-list|. Use |:lopen| to view it.
*fireplace-:Require*
:Require [ns] Require :reload the given/current namespace.
*fireplace-:Require!*
:Require! [ns] Require :reload-all the given/current namespace.
*fireplace-:Eval*
:Eval Eval/print the outermost form for the current line.
:{range}Eval Eval/print the given range.
:Eval {expr} Eval/print the given expression.
*fireplace-: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.
*fireplace-:RunTests*
:RunTests [ns] [...] Call clojure.test/run-tests on the given namespaces
and load the results into the quickfix list.
:[range]RunTests Call clojure.test/test-var on the var defined at or
above the specicied line and load the results into the
quickfix list. Typically invoked as :.RunTests to run
the test under the cursor.
:0RunTests [pattern] Call clojure.test/run-all-tests with the given pattern
and load the results into the quickfix list.
*fireplace-cp*
cp{motion} Eval/print the code indicated by {motion}.
cpp Eval/print the innermost form at the cursor.
*fireplace-cpr*
cpr :Require|RunTests
*fireplace-c!*
c!{motion} Eval/replace the code indicated by {motion}.
c!! Eval/replace the innermost form at the cursor.
*fireplace-cm*
cm{motion} Fully macroexpand the code indicated by {motion}.
*fireplace-cmm*
cmm Fully macroexpand the innermost form at the cursor.
*fireplace-c1m*
c1m{motion} Macroexpand the code indicated by {motion} once.
*fireplace-c1mm*
c1mm Macroexpand the innermost form at the cursor once.
*fireplace-cqp*
cqp Bring up a prompt for code to eval/print.
*fireplace-cqq*
cqq Bring up a |command-line-window| with innermost form
at the cursor prepopulated.
*fireplace-cqc*
cqc Bring up a |command-line-window| for code to
eval/print. Equivalent to cqp<C-F>i.
*fireplace-cq*
cq{motion} Bring up a |command-line-window| with text indicated
by {motion} prepopulated.
*fireplace-:Last*
:Last Open the result of the last evaluation in the preview
window. Use :2Last to get the next-to-last result,
and so on. Once the window is open, cycle to older
and newer entries with |:lprevious| and |:lnext|.
And insert mode:
*fireplace-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 *fireplace-about*
Grab the latest version or report a bug on GitHub:
http://github.com/tpope/vim-fireplace
vim:tw=78:et:ft=help:norl:

161
doc/foreplay.txt Normal file
View File

@ -0,0 +1,161 @@
*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-:Javadoc*
:Javadoc {class} Open the java docs for the given class in a browser.
*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-cpr*
cpr Eval a require :reload form.
*foreplay-cpR*
cpR Eval a require :reload-all form.
*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:

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +0,0 @@
" Location: plugin/fireplace/zip.vim
if exists("g:loaded_zip") || &cp || v:version >= 704
finish
endif
runtime! autoload/zip.vim
" Patched to allow loading from the quickfix list. The version that ships
" with Vim 7.4 already has this change.
fun! zip#Read(fname,mode)
" call Dfunc("zip#Read(fname<".a:fname.">,mode=".a:mode.")")
let repkeep= &report
set report=10
if has("unix")
let zipfile = substitute(a:fname,'zipfile:\(.\{-}\)::[^\\].*$','\1','')
let fname = substitute(a:fname,'zipfile:.\{-}::\([^\\].*\)$','\1','')
else
let zipfile = substitute(a:fname,'^.\{-}zipfile:\(.\{-}\)::[^\\].*$','\1','')
let fname = substitute(a:fname,'^.\{-}zipfile:.\{-}::\([^\\].*\)$','\1','')
let fname = substitute(fname, '[', '[[]', 'g')
endif
" call Decho("zipfile<".zipfile.">")
" call Decho("fname <".fname.">")
" Changes for fireplace.
let temp = tempname()
let fn = expand('%:p')
exe "sil! ! ".g:zip_unzipcmd." -p -- ".shellescape(zipfile,1)." ".shellescape(fnameescape(fname),1). ' > '.temp
silent exe 'keepalt file '.temp
silent keepjumps edit!
silent exe 'keepalt file '.fnameescape(fn)
call delete(temp)
filetype detect
" Resume regularly scheduled programming.
set nomod
endfun

970
plugin/foreplay.vim Normal file
View File

@ -0,0 +1,970 @@
" 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
\ if expand('%:p') !~# '^zipfile:' |
\ let &l:path = classpath#detect() |
\ endif
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
let s:jar_contents = {}
function! foreplay#jar_contents(path) abort
if !has_key(s:jar_contents, a:path) && executable('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! 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 dir in classpath#split(classpath#from_vim(&path))
if dir =~# '\.jar$'
let files = filter(foreplay#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: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\)\=$' && !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)', 'user')
let self.requires[a:lib] = !has_key(result, 'ex')
endif
return ''
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 = fnamemodify(exists('b:java_root') ? b:java_root : fnamemodify(expand('%'), ':p:s?.*\zs[\/]src[\/].*??'), ':~')
let root = input('Scope connection to: ', path, 'dir')
if root !=# ''
let s:repl_paths[fnamemodify(root, ':p:s?[\/]$??')] = client
endif
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_stk = 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([], 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 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))' .
\ ' (spit "'.s:oneoff_stk.'" (apply str (interpose "\n" (.getStackTrace e))))))' .
\ ' nil)')
let wtf = system(command)
let result = {}
let result.value = join(readfile(s:oneoff_pr, 'b'), "\n")
let result.out = join(readfile(s:oneoff_out, 'b'), "\n")
let result.err = join(readfile(s:oneoff_err, 'b'), "\n")
let result.ex = join(readfile(s:oneoff_ex, 'b'), "\n")
let result.stacktrace = readfile(s:oneoff_stk)
call filter(result, '!empty(v:val)')
if v:shell_error && get(result, 'ex', '') ==# ''
throw 'Error running Clojure: '.wtf
else
return result
endif
endfunction
function! s:oneoff.require(symbol)
return ''
endfunction
" }}}1
" Client {{{1
function! s:client() abort
silent doautocmd User 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 && foreplay#ns() !~# '^\%(user\)$'
call c.require(foreplay#ns())
endif
let result = c.eval(a:expr, a:0 ? a:1 : foreplay#ns())
if get(result, 'err', '') !=# ''
echohl ErrorMSG
echo substitute(result.err, '\n$', '', '')
echohl NONE
endif
if get(result, 'out', '') !=# ''
echo substitute(result.out, '\n$', '', '')
endif
if get(result, 'ex', '') !=# ''
let err = 'Clojure: '.result.ex
elseif has_key(result, 'value')
return result.value
else
let err = 'foreplay.vim: Something went wrong: '.string(result)
endif
throw err
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
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\+').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
let s:todo = s:opfunc(a:type)
call feedkeys("\<Plug>ForeplayPrintLast")
endfunction
function! s:print_last() abort
try
echo foreplay#eval(s:todo)
catch /^Clojure:/
endtry
return ''
endfunction
function! s:editop(type) abort
call feedkeys(&cedit . "\<Home>", 'n')
let input = s:input(substitute(substitute(s:opfunc(a:type), "\s*;[^\n]*", '', 'g'), '\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('(','',')', 'rn', 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:inputevalloop() abort
while 1
let input = s:input('')
if input ==# ''
return ''
else
try
echo "\n".foreplay#eval(input)
catch /^Clojure:/
catch
return 'echoerr '.string(v:exception)
endtry
endif
endwhile
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>ForeplayPrintLast :exe <SID>print_last()<CR>
nnoremap <silent> <Plug>ForeplayPrint :<C-U>set opfunc=<SID>printop<CR>g@
xnoremap <silent> <Plug>ForeplayPrint :<C-U>call <SID>printop(visualmode())<CR>
nnoremap <silent> <Plug>ForeplayFilter :<C-U>set opfunc=<SID>filterop<CR>g@
xnoremap <silent> <Plug>ForeplayFilter :<C-U>call <SID>filterop(visualmode())<CR>
nnoremap <silent> <Plug>ForeplayEdit :<C-U>set opfunc=<SID>editop<CR>g@
xnoremap <silent> <Plug>ForeplayEdit :<C-U>call <SID>editop(visualmode())<CR>
nnoremap <Plug>ForeplayPrompt :exe <SID>inputeval()<CR>
nnoremap <Plug>ForeplayLoop :exe <SID>inputevalloop()<CR>
noremap! <Plug>ForeplayRecall <C-R>=<SID>recall()<CR>
function! s:setup_eval() 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>ForeplayPrint
nmap <buffer> cpp <Plug>ForeplayPrintab
nmap <buffer> c! <Plug>ForeplayFilter
nmap <buffer> c!! <Plug>ForeplayFilterab
nmap <buffer> cq <Plug>ForeplayEdit
nmap <buffer> cqq <Plug>ForeplayEditab
nmap <buffer> cqp <Plug>ForeplayPrompt
nmap <buffer> cqP <Plug>ForeplayLoop
exe 'nmap <buffer> cqc <Plug>ForeplayPrompt' . &cedit . 'i'
map! <buffer> <C-R>( <Plug>ForeplayRecall
endfunction
function! s:cmdwinenter()
setlocal filetype=clojure
endfunction
function! s:cmdwinleave()
setlocal filetype< omnifunc<
endfunction
augroup foreplay_eval
autocmd!
autocmd FileType clojure call s:setup_eval()
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
function! s:setup_require()
command! -buffer -bar -bang -complete=customlist,foreplay#ns_complete -nargs=? Require :exe s:Require(<bang>0, <q-args>)
nnoremap <silent><buffer> cpr :Require<CR>
nnoremap <silent><buffer> cpR :Require!<CR>
endfunction
augroup foreplay_require
autocmd!
autocmd FileType clojure call s:setup_require()
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()).value, "\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
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()).value, "\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()).value, "\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
let lnum = 1
while lnum < line('$') && getline(lnum) =~# '^\s*\%(;.*\)\=$'
let lnum += 1
endwhile
let ns = matchstr(getline(lnum), '\C^(\s*\%(in-ns\s*''\|ns\s\+\)\zs\k\+\ze')
if ns !=# ''
return ns
endif
let path = s:buffer_path()
return s:tons(path ==# '' ? 'user' : path)
endfunction
function! s:Lookup(ns, macro, arg) abort
" doc is in clojure.core in older Clojure versions
try
call foreplay#eval("(require '".a:ns.") (eval (list (if (ns-resolve 'clojure.core '".a:macro.") 'clojure.core/".a:macro." '".a:ns.'/'.a:macro.") '".a:arg.'))')
catch /^Clojure:/
catch /.*/
echohl ErrorMSG
echo v:exception
echohl None
endtry
return ''
endfunction
function! s:inputlist(label, entries)
let choices = [a:label]
for i in range(len(a:entries))
let choices += [printf('%2d. %s', i+1, a:entries[i])]
endfor
let choice = inputlist(choices)
if choice
return a:entries[choice-1]
else
return ''
endif
endfunction
function! s:Apropos(pattern) abort
if a:pattern =~# '^#\="'
let pattern = a:pattern
elseif a:pattern =~# '^^'
let pattern = '#"' . a:pattern . '"'
else
let pattern = '"' . a:pattern . '"'
endif
let matches = foreplay#evalparse('(apropos '.pattern.')')
if empty(matches)
return ''
endif
let choice = s:inputlist('Look up docs for:', matches)
if choice !=# ''
return 'echo "\n"|Doc '.choice
else
return ''
endif
endfunction
function! s:K()
let word = expand('<cword>')
let java_candidate = matchstr(word, '^\%(\w\+\.\)*\u\l\w*\ze\%(\.\|\/\w\+\)\=$')
if java_candidate !=# ''
return 'Javadoc '.java_candidate
else
return 'Doc '.word
endif
endfunction
augroup foreplay_doc
autocmd!
autocmd FileType clojure nnoremap <buffer> K :<C-R>=<SID>K()<CR><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 Apropos :exe s:Apropos(<q-args>)
autocmd FileType clojure command! -buffer -nargs=1 FindDoc :exe s:Lookup('clojure.repl', 'find-doc', printf('#"%s"', <q-args>))
autocmd FileType clojure command! -buffer -bar -nargs=1 Javadoc :exe s:Lookup('clojure.java.javadoc', 'javadoc', <q-args>)
autocmd FileType clojure command! -buffer -bar -nargs=1 -complete=customlist,foreplay#eval_complete Doc :exe s:Lookup('clojure.repl', 'doc', <q-args>)
autocmd FileType clojure command! -buffer -bar -nargs=1 -complete=customlist,foreplay#eval_complete Source :exe s:Lookup('clojure.repl', 'source', <q-args>)
augroup END
" }}}1
" Leiningen {{{1
function! s:hunt(start, anchor) 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) && 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 ForeplayPreConnect call s:leiningen_connect()
autocmd FileType clojure call s:leiningen_init()
augroup END
" }}}1
" vim:set et sw=2:

View File

@ -1,132 +0,0 @@
import os
import re
import select
import socket
import sys
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
def noop():
pass
def vim_encode(data):
if isinstance(data, list):
return "[" + ",".join([vim_encode(x) for x in data]) + "]"
elif isinstance(data, dict):
return "{" + ",".join([vim_encode(x)+":"+vim_encode(y) for x,y in data.items()]) + "}"
elif isinstance(data, str):
str_list = []
for c in data:
if (0 <= ord(c) and ord(c) <= 31) or c == '"' or c == "\\":
str_list.append("\\%03o" % ord(c))
else:
str_list.append(c)
return '"' + ''.join(str_list) + '"'
elif isinstance(data, int):
return str(data)
else:
raise TypeError("can't encode a " + type(data).__name__)
def bdecode(f, char=None):
if char == None:
char = f.read(1)
if char == 'l':
l = []
while True:
char = f.read(1)
if char == 'e':
return l
l.append(bdecode(f, char))
elif char == 'd':
d = {}
while True:
char = f.read(1)
if char == 'e':
return d
key = bdecode(f, char)
d[key] = bdecode(f)
elif char == 'i':
i = ''
while True:
char = f.read(1)
if char == 'e':
return int(i)
i += char
elif char.isdigit():
i = int(char)
while True:
char = f.read(1)
if char == ':':
return f.read(i)
i = 10 * i + int(char)
elif char == '':
raise EOFError("unexpected end of bencode data")
else:
raise TypeError("unexpected type "+char+"in bencode data")
class Connection:
def __init__(self, host, port, custom_poll=noop, keepalive_file=None):
self.custom_poll = custom_poll
self.keepalive_file = keepalive_file
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(8)
s.connect((host, int(port)))
s.setblocking(1)
self.socket = s
def poll(self):
self.custom_poll()
if self.keepalive_file and not os.path.exists(self.keepalive_file):
exit(0)
def close(self):
return self.socket.close()
def send(self, payload):
if sys.version_info[0] >= 3:
self.socket.sendall(bytes(payload, 'UTF-8'))
else:
self.socket.sendall(payload)
return ''
def receive(self, char=None):
f = self.socket.makefile()
while len(select.select([f], [], [], 0.1)[0]) == 0:
self.poll()
try:
return bdecode(f)
finally:
f.close()
def call(self, payload, terminators, selectors):
self.send(payload)
responses = []
while True:
response = self.receive()
for key in selectors:
if response[key] != selectors[key]:
continue
responses.append(response)
if 'status' in response and set(terminators) & set(response['status']):
return responses
def dispatch(host, port, poll, keepalive, command, *args):
conn = Connection(host, port, poll, keepalive)
try:
return getattr(conn, command)(*args)
finally:
conn.close()
def main(host, port, keepalive, command, *args):
try:
sys.stdout.write(vim_encode(dispatch(host, port, noop, keepalive, command, *[bdecode(StringIO(arg)) for arg in args])))
except Exception:
print((sys.exc_info()[1]))
exit(1)
if __name__ == "__main__":
main(*sys.argv[1:])