Initial commit
This commit is contained in:
commit
ff9e3b5cd7
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/doc/tags
|
140
README.markdown
Normal file
140
README.markdown
Normal file
@ -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
Normal file
126
autoload/classpath.vim
Normal 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')), "\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
Normal file
212
autoload/nrepl/foreplay_connection.vim
Normal file
@ -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
Normal file
152
doc/foreplay.txt
Normal file
@ -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
Normal file
894
plugin/foreplay.vim
Normal file
@ -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:
|
Loading…
Reference in New Issue
Block a user