From 6a80df105218ce9d94b167e0737a3625cd27af70 Mon Sep 17 00:00:00 2001 From: Anthony Grimes Date: Tue, 7 May 2013 11:58:15 -0700 Subject: [PATCH] Add vim-redl --- bundle/vim-redl/.gitignore | 1 + bundle/vim-redl/README.markdown | 78 ++++++++ bundle/vim-redl/doc/redl.txt | 68 +++++++ bundle/vim-redl/plugin/redl.vim | 109 ++++++++++++ bundle/vim-redl/plugin/redl/repl.vim | 255 +++++++++++++++++++++++++++ bundle/vim-redl/plugin/redl/util.vim | 107 +++++++++++ 6 files changed, 618 insertions(+) create mode 100644 bundle/vim-redl/.gitignore create mode 100644 bundle/vim-redl/README.markdown create mode 100644 bundle/vim-redl/doc/redl.txt create mode 100644 bundle/vim-redl/plugin/redl.vim create mode 100644 bundle/vim-redl/plugin/redl/repl.vim create mode 100644 bundle/vim-redl/plugin/redl/util.vim diff --git a/bundle/vim-redl/.gitignore b/bundle/vim-redl/.gitignore new file mode 100644 index 0000000..0a56e3f --- /dev/null +++ b/bundle/vim-redl/.gitignore @@ -0,0 +1 @@ +/doc/tags diff --git a/bundle/vim-redl/README.markdown b/bundle/vim-redl/README.markdown new file mode 100644 index 0000000..950c3da --- /dev/null +++ b/bundle/vim-redl/README.markdown @@ -0,0 +1,78 @@ +# Redl.vim -- Read Eval Debug Loop + +This plugin integrates Vim with a running Clojure JVM. It provides a repl that +supports breakpoints, documentation lookup, source code navigation, and +omnicompletion. + +## Installation + +First, you'll need to install this as a Vim plugin. Do that with + + cd ~/.vim/bundle + git clone git://github.com/dgrnbrg/vim-redl.git + +You'll need Pathogen so that vim-redl gets loaded. Otherwise, if you are +a user of Vundle or NeoBundle, you can just use `Bundle 'dgrnbrg/vim-redl'` +or `NeoBundle 'dgrnbrg/vim-redl'` respectively to install the Vim component. + +Then, you'll need to install the Clojure component. To get this, you just need to add 2 lines +to your `~/.lein/profiles.clj`. + +- `:injections [(require '[redl core complete])]` ensure that redl is loaded on jvm startup +- `:dependencies [[redl "0.1.0"]]` ensures that redl is available on the classpath + +A minimal profiles.clj (including REDL, Spyscope, and `lein pprint`) would look like: + +```clojure +{:user {:dependencies [[spyscope "0.1.2"] + [redl "0.1.0"]] + :injections [(require 'spyscope.core) + (require '[redl complete core])] + :plugins [[lein-pprint "1.1.1"]]}} +``` + +### A REPL + +To access the other repl, use `:Repl` or `:ReplHere`. The former opens a new +split window containing a repl in the namespace `user`. The latter opens the +repl in the namespace of the current file. + +If you don't reconfigure the plugs below, these are the default controls for +the repl: + +- `ctrl-e` in insert mode evaluates the current line, regardless of cursor position. +- `return` in insert mode at the end of the line evaluates the line, otherwise inserts a newline. +- `ctrl-up` goes up in the history +- `ctrl-down` goes down in the history + +To use the breakpoint feature, check out dgrnbrg/redl (short version: `redl.core/break` +and `redl.core/continue`. + +The plugs for the repl: + + clj_repl_enter. + clj_repl_eval. + clj_repl_hat. + clj_repl_Ins. + clj_repl_uphist. + clj_repl_downhist. + + +### Omnicomplete + +This feature requires redl to be loaded in the connected JVM, as this plugin +uses redl's advanced fuzzy completion engine. + +## Contributing + +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. + +This plugin was made by borrowing generous amounts of code from vimclojure Meikel Brandmeyer. + +## License + +Copyright © David Greenberg, Tim Pope, and Meikel Brandmeyer. +Distributed under the same terms as Vim itself. +See `:help license`. diff --git a/bundle/vim-redl/doc/redl.txt b/bundle/vim-redl/doc/redl.txt new file mode 100644 index 0000000..7ad224d --- /dev/null +++ b/bundle/vim-redl/doc/redl.txt @@ -0,0 +1,68 @@ +*redl.txt* Clojure Read Eval Debug Loop + +Author: David Greenberg +License: Same terms as Vim itself (see |license|) + +You need Clojure runtime (vim-clojure-static) plugin and vim-fireplace + vim-classpath plugins to use this plugin. + +CLOJURE CONFIGURATION *redl-clojure-configuration* + +Add following lines to your ~/.lein/profiles.clj + +:injections [(require '[redl core complete])] ensure that redl is loaded on jvm startup +:dependencies [[redl "0.1.0"]] ensures that redl is available on the classpath + +A minimal profiles.clj (including REDL, Spyscope, and lein pprint) would look like: + +{:user {:dependencies [[spyscope "0.1.2"] + [redl "0.1.0"]] + :injections [(require 'spyscope.core) + (require '[redl complete core])] + :plugins [[lein-pprint "1.1.1"]]}} + +REPL *redl-repl* + +Repl is only available in Clojure filetype. + + *redl-:Repl* +:Repl Show repl in user namespace. + + *redl-:ReplHere* +:ReplHere Show repl in current namespace. + + *redl-CTRL-e* +C-e Evaluate current line in instert mode, regardless of + cursor position. + + *redl-CR* +CR In insert mode at the end of the line evaluates the line, otherwise inserts a newline. + + *redl-CTRL-UP* +C-Up Goes up in the repl history. + + *redl-CTRL-DOWN* +C-Down Goes down in the repl history. + +OMNICOMPLETE *redl-omnicomplete* + +This feature requires redl to be loaded in the connected JVM, as this plugin +uses redl's advanced fuzzy completion engine. + +KEYS *redl-keys* + +To change default keys you can use following plugs: + +clj_repl_enter. CR +clj_repl_eval. C-e +clj_repl_hat. ^ +clj_repl_Ins. I +clj_repl_uphist. C-Up +clj_repl_downhist. C-Down + +ABOUT *redl-about* + +Grab the latest version or report a bug on GitHub: + +https://github.com/dgrnbrg/vim-redl + + vim:tw=78:et:ft=help:norl: diff --git a/bundle/vim-redl/plugin/redl.vim b/bundle/vim-redl/plugin/redl.vim new file mode 100644 index 0000000..eb87460 --- /dev/null +++ b/bundle/vim-redl/plugin/redl.vim @@ -0,0 +1,109 @@ +" redl.vim - Clojure REPL Enhancements +" Maintainer: David Greenberg + +if exists("g:loaded_redl") || v:version < 700 || &cp + finish +endif +let g:loaded_redl = 1 + +function! redl#eval_complete(A, L, P) abort + let prefix = matchstr(a:A, '\%(.* \|^\)\%(#\=[\[{('']\)*') + let keyword = a:A[strlen(prefix) : -1] + return sort(map(redl#omnicomplete(0, keyword), 'prefix . v:val.word')) +endfunction + +function! redl#omnicomplete(findstart, base) abort + if a:findstart + let line = getline('.')[0 : col('.')-2] + return col('.') - strlen(matchstr(line, '\k\+$')) - 1 + else + try + let ns = fireplace#ns() + let results = fireplace#evalparse('(map redl.complete/->vim-omnicomplete'. + \' (redl.complete/completions '.s:qsym(ns).' "'.a:base.'"))') + if type(results) == type([]) + return results + else + return [] + endif + catch /.*/ + return [] + endtry + endif +endfunction + +function! s:qsym(symbol) + if a:symbol =~# '^[[:alnum:]?*!+/=<>.:-]\+$' + return "'".a:symbol + else + return '(symbol "'.escape(a:symbol, '"').'")' + endif +endfunction + + +augroup redl_completion + autocmd! + autocmd FileType clojure setlocal omnifunc=redl#omnicomplete +augroup END + +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:fireplace#skip) + let line2 = searchpair('(','',')', 'rn', g:fireplace#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 + if a:bang + try + let result = fireplace#session_eval(expr) + if a:args !=# '' + call append(a:line1, result) + exe a:line1 + else + call append(a:line1-1, result) + exe a:line1-1 + endif + catch /^Clojure:/ + endtry + else + call fireplace#echo_session_eval(expr) + endif + return '' +endfunction + +function! s:setup_eval() abort + command! -buffer -bang -range=0 -nargs=? -complete=customlist,redl#eval_complete Eval :exe s:Eval(0, , , , ) + + command! -buffer Repl :call redl#repl#create("user") + command! -buffer ReplHere :call redl#repl#create(fireplace#ns()) +endfunction + +function! s:cmdwinenter() + setlocal filetype=clojure +endfunction + +function! s:cmdwinleave() + setlocal filetype< omnifunc< +endfunction + +augroup redl_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 + +" vim:set et sw=2: diff --git a/bundle/vim-redl/plugin/redl/repl.vim b/bundle/vim-redl/plugin/redl/repl.vim new file mode 100644 index 0000000..0d9c23c --- /dev/null +++ b/bundle/vim-redl/plugin/redl/repl.vim @@ -0,0 +1,255 @@ +" Part of Vim filetype plugin for Clojure +" Language: Clojure +" Maintainer: David Greenberg + +" Evaluate a string form on the given repl +" Returns a dictionary with the stdout, stderr, +" result, and repl status metadata +function! redl#repl#eval(form) + if !exists('b:repl_id') + throw "Can only do repl eval in a repl buffer!" + endif + let escaped_form = escape(a:form,'\"') + let full_form = '(redl.core/repl-eval '.b:repl_id . + \ ' (read-string "(do '.escaped_form.')"))' + return fireplace#evalparse(full_form) +endfunction + +" Returns the prompt for the namespace the repl is +" currently in. Also includes how many levels of debug +" are currently in use. +function! redl#repl#prompt() + let prompt = b:repl_namespace + if b:repl_depth > 0 + let prompt .= ':debug-' . b:repl_depth + endif + return prompt.'=>' +endfunction + +" Moves the cursor to the beginning of the line, +" respecting the prompt as the beginning +function! redl#repl#beginning_of_line() + let l = getline(".") + + let prompt = redl#repl#prompt() + if l =~ "^".prompt + let [buf, line, col, off] = getpos(".") + call setpos(".", [buf, line, len(prompt) + 2, off]) + else + normal! ^ + endif +endfunction + +" Appends the given lines to the end of the buffer +function! redl#repl#show_text(lines) + call append(line('$'), split(a:lines, "\n")) +endfunction + +" When invoked, writes out the prompt in a new line, +" and moves the cursor to the prompt in insert mode. +function! redl#repl#show_prompt() + call redl#repl#show_text(redl#repl#prompt().' ') + normal! G + startinsert! +endfunction + +inoremap clj_repl_enter. :call redl#repl#enter_hook() +inoremap clj_repl_eval. G$:call redl#repl#enter_hook() +nnoremap clj_repl_hat. :call redl#repl#beginning_of_line() +nnoremap clj_repl_Ins. :call redl#repl#beginning_of_line()i +inoremap clj_repl_uphist. :call redl#repl#up_history() +inoremap clj_repl_downhist. :call redl#repl#down_history() + +" This creates a new repl in a split +function! redl#repl#create(namespace) + new + setlocal buftype=nofile + setlocal noswapfile + set filetype=clojure + let ns = "'".a:namespace + let b:repl_id = fireplace#evalparse('(redl.core/make-repl '.ns.')') + let b:repl_namespace = a:namespace + let b:repl_depth = 0 + let b:repl_history_depth = 0 + let b:repl_history = [] + + if !hasmapto("clj_repl_enter.", "i") + imap clj_repl_enter. + endif + if !hasmapto("clj_repl_eval.", "i") + imap clj_repl_eval. + endif + if !hasmapto("clj_repl_hat.", "n") + nmap ^ clj_repl_hat. + endif + if !hasmapto("clj_repl_Ins.", "n") + nmap I clj_repl_Ins. + endif + if !hasmapto("clj_repl_uphist.", "i") + imap clj_repl_uphist. + endif + if !hasmapto("clj_repl_downhist.", "i") + imap clj_repl_downhist. + endif + + call redl#repl#show_prompt() +endfunction + +" Like pressing enter in insert mode. +" This was lifted from vimclojure, and appears to do +" other fixups to the line. +function! redl#repl#do_enter() + execute "normal! a\x" + normal! ==x + if getline(".") =~ '^\s*$' + startinsert! + else + startinsert + endif +endfunction + +function! redl#repl#get_command() + let ln = line("$") + + let prompt = redl#repl#prompt() + while getline(ln) !~ "^".prompt && ln > 0 + let ln = ln - 1 + endwhile + + " Special Case: User deleted Prompt by accident. Insert a new one. + if ln == 0 + call redl#repl#show_prompt() + return "" + endif + + let cmd = redl#util#Yank("l", ln.",".line("$")."yank l") + + let cmd = substitute(cmd, "^".prompt."\\s*", "", "") + let cmd = substitute(cmd, "\n$", "", "") + return cmd +endfunction + +" Returns 1 if the given string results in a readable form, +" 0 otherwise. +function! redl#repl#is_readable(form) + let test_form = '(try (read-string "' . + \ escape(a:form, '\"').'") 1 ' . + \ '(catch RuntimeException _ 0))' + let result = fireplace#evalparse(test_form) + return result +endfunction + +function! redl#repl#enter_hook() abort + let lastCol = {} + + function lastCol.f() dict + normal! g_ + return col(".") + endfunction + + let last_col = redl#util#WithSavedPosition(lastCol) + if line(".") < line("$") || col(".") < last_col + call redl#repl#do_enter() + return + endif + + let cmd = redl#repl#get_command() + + " Special Case: Showed prompt (or user just hit enter). + if cmd =~ '^\(\s\|\n\)*$' + execute "normal! a\" + startinsert! + return + endif + + "Currently unused feature (special commands) + "if self.isReplCommand(cmd) + "call self.doReplCommand(cmd) + "return + "endif + + if !redl#repl#is_readable(cmd) + call redl#repl#do_enter() + else + let result = redl#repl#eval(cmd) + if has_key(result, 'out') + call redl#repl#show_text(result.out) + endif + if has_key(result, 'err') && result.err !=# '' + call redl#repl#show_text("\nstderr:\n".result.err) + endif + + let b:repl_history_depth = 0 + let b:repl_history = [cmd] + b:repl_history + + if has_key(result, 'ns') + let b:repl_namespace = result.ns + endif + if has_key(result, 'repl-depth') + let b:repl_depth = result['repl-depth'] + endif + + call redl#repl#show_prompt() + endif +endfunction + +inoremap clj_repl_uphist. :call redl#repl#up_history() +inoremap clj_repl_downhist. :call redl#repl#down_history() + +function! redl#repl#delete_last() + normal! G + + while getline("$") !~ redl#repl#prompt() + normal! dd + endwhile + + normal! dd +endfunction + +function! redl#repl#up_history() + let histLen = len(b:repl_history) + let histDepth = b:repl_history_depth + + if histLen > 0 && histLen > histDepth + let cmd = b:repl_history[histDepth] + let b:repl_history_depth = histDepth + 1 + + call redl#repl#delete_last() + + let prompt = redl#repl#prompt() + call redl#repl#show_text(prompt.' '.cmd) + endif + + normal! G$ +endfunction + +function! redl#repl#down_history() + let histLen = len(b:repl_history) + let histDepth = b:repl_history_depth + let prompt = redl#repl#prompt() + + if histDepth > 0 && histLen > 0 + let b:repl_history_depth = histDepth - 1 + let cmd = b:repl_history[b:repl_history_depth] + + call redl#repl#delete_last() + + call redl#repl#show_text(prompt.' '.cmd) + elseif histDepth == 0 + call redl#repl#delete_last() + call redl#repl#show_text(prompt.' ') + endif + + normal! G$ +endfunction + + +"TODO: +"-repl history +"--for this, need to store which index we're currently looking at +"and the entire history +"--also need a deleteLast/replaceLastWith function +"--when eval occurs, reset history pointer to latest +" +"-keyboard commands: ctrl-up, ctrl-down +"-multi-expr evaluation diff --git a/bundle/vim-redl/plugin/redl/util.vim b/bundle/vim-redl/plugin/redl/util.vim new file mode 100644 index 0000000..578c796 --- /dev/null +++ b/bundle/vim-redl/plugin/redl/util.vim @@ -0,0 +1,107 @@ +" Part of Vim filetype plugin for Clojure +" Language: Clojure +" Maintainer: Meikel Brandmeyer + +let s:save_cpo = &cpo +set cpo&vim + +function! redl#util#SynIdName() + return synIDattr(synID(line("."), col("."), 0), "name") +endfunction + +function! redl#util#WithSaved(closure) + let v = a:closure.save() + try + let r = a:closure.f() + finally + call a:closure.restore(v) + endtry + return r +endfunction + +function! s:SavePosition() dict + let [ _b, l, c, _o ] = getpos(".") + let b = bufnr("%") + return [b, l, c] +endfunction + +function! s:RestorePosition(value) dict + let [b, l, c] = a:value + + if bufnr("%") != b + execute b "buffer!" + endif + call setpos(".", [0, l, c, 0]) +endfunction + +function! redl#util#WithSavedPosition(closure) + let a:closure.save = function("s:SavePosition") + let a:closure.restore = function("s:RestorePosition") + + return redl#util#WithSaved(a:closure) +endfunction + +function! s:SaveRegister(reg) + return [a:reg, getreg(a:reg, 1), getregtype(a:reg)] +endfunction + +function! s:SaveRegisters() dict + return map([self._register, "", "/", "-", + \ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], + \ "s:SaveRegister(v:val)") +endfunction + +function! s:RestoreRegisters(registers) dict + for register in a:registers + call call(function("setreg"), register) + endfor +endfunction + +function! redl#util#WithSavedRegister(reg, closure) + let a:closure._register = a:reg + let a:closure.save = function("s:SaveRegisters") + let a:closure.restore = function("s:RestoreRegisters") + + return redl#util#WithSaved(a:closure) +endfunction + +function! s:SaveOption() dict + return eval("&" . self._option) +endfunction + +function! s:RestoreOption(value) dict + execute "let &" . self._option . " = a:value" +endfunction + +function! redl#util#WithSavedOption(option, closure) + let a:closure._option = a:option + let a:closure.save = function("s:SaveOption") + let a:closure.restore = function("s:RestoreOption") + + return redl#util#WithSaved(a:closure) +endfunction + +function! s:DoYank() dict + silent execute self.yank + return getreg(self.reg) +endfunction + +function! redl#util#Yank(r, how) + let closure = { + \ 'reg': a:r, + \ 'yank': a:how, + \ 'f': function("s:DoYank") + \ } + + return redl#util#WithSavedRegister(a:r, closure) +endfunction + +function! redl#util#MoveBackward() + call search('\S', 'Wb') +endfunction + +function! redl#util#MoveForward() + call search('\S', 'W') +endfunction + +" Epilog