├── README.md ├── doc └── cider.txt └── plugin └── cider.vim /README.md: -------------------------------------------------------------------------------- 1 | # cider.vim 2 | 3 | Some additional IDE-like functionality for Clojure development on Vim using 4 | [cider-nrepl][cider-nrepl] and 5 | [refactor-nrepl][refactor-nrepl]. 6 | 7 | ## Using 8 | 9 | Requires [fireplace.vim][vim-fireplace], cider-nrepl 10 | and refactor-nrepl middlewares: 11 | 12 | **Leiningen**, `~/.lein/profiles.clj` 13 | ```clj 14 | {:user {:plugins [[cider/cider-nrepl "0.9.1"] 15 | [refactor-nrepl "1.1.0"]]}} 16 | ``` 17 | 18 | **Boot**, `~/.boot/profile.boot` 19 | ```clj 20 | (swap! boot.repl/*default-dependencies* 21 | concat '[[cider/cider-nrepl "0.9.1"] 22 | [refactor-nrepl "1.1.0"]) 23 | 24 | (swap! boot.repl/*default-middleware* conj 25 | 'cider.nrepl/cider-middleware 26 | 'refactor-nrepl.middleware/wrap-refactor) 27 | ``` 28 | 29 | Cider-nrepl takes care of all other dependencies so you don't need to depend 30 | e.g. on cljfmt. 31 | 32 | ## Features 33 | 34 | - Code formatting (uses [cljfmt][cljfmt]) 35 | - `=ff` (current form), `=f{motion}`, `=F` (current file) 36 | - Var undef / alias unmap 37 | - `cdd` 38 | - Clean ns (eliminate `:use`, sort, remove unused stuff and duplication) 39 | - `` 40 | - Resolve missing 41 | - `cRR` (TODO: Find better binding) 42 | - ``: `findSymbol` (TODO: Find better binding) 43 | 44 | ## Configuration 45 | 46 | If you do not like the default bindings, you can disable them and create your 47 | own. Check the implementation file for `` bindings. 48 | 49 | ```vim 50 | let g:cider_no_maps=1 " Disable built-in mappings 51 | 52 | " Set refactor-nrepl options 53 | let g:refactor_nrepl_options = { 54 | \ 'prefix-rewriting': 'false', " tell clean-ns to not use prefix forms 55 | \ 'prune-ns-form': 'false', " ... and don't remove unused symbols 56 | \ } 57 | 58 | " Setup visualmode bindings yourself, to some keys which don't interact 59 | " with e.g. change command 60 | autocmd FileType clojure xmap f CiderFormat 61 | ``` 62 | 63 | ## TODO 64 | 65 | - Cider-nrepl 66 | - Test utilities 67 | - Code reloading 68 | - Inspecting, tracing, debugging? 69 | - Refactor-nrepl 70 | - Rename symbol 71 | 72 | # License 73 | 74 | Copyright (C) 2015-2017 Juho Teperi 75 | 76 | Distributed under the MIT License. 77 | 78 | [vim-fireplace]: https://github.com/tpope/vim-fireplace 79 | [cider-nrepl]: https://github.com/clojure-emacs/cider-nrepl 80 | [refactor-nrepl]: https://github.com/clojure-emacs/refactor-nrepl 81 | [cljfmt]: https://github.com/weavejester/cljfmt 82 | -------------------------------------------------------------------------------- /doc/cider.txt: -------------------------------------------------------------------------------- 1 | *cider.txt* IDE-like functionality for Clojure 2 | 3 | Author: Juho Teperi 4 | License: MIT 5 | 6 | *cider* 7 | This plugins requires and 8 | . 9 | 10 | FEATURES *cider-features* 11 | 12 | Code formatting |cider-cf| 13 | 14 | DOCUMENTATION *cider-documentation* 15 | 16 | *cider-no-maps* 17 | g:cider_no_maps Set this option to true to disable built-in maps. 18 | 19 | *refactor-nrepl-options* 20 | g:refactor_nrepl_options 21 | Use this option to set settings for refactor-nrepl. 22 | Check https://github.com/clojure-emacs/refactor-nrepl#configure 23 | for documentation. 24 | 25 | *cider-=f* 26 | =f{motion} Format the code indicated by {motion}. 27 | Use CiderFormat to map this yourself. 28 | Visual mode mapping is not by default enabled but you 29 | can do that yourself: 30 | > 31 | autocmd FileType clojure xmap f CiderFormat 32 | < 33 | Just remeber to use mapping which doesn't conflict 34 | with e.g. change command. 35 | 36 | *cider-=ff* 37 | =ff Format the innertmost form at the cursor. 38 | Like |fireplace-c!| 39 | Use CiderFormatCount to map this yourself. 40 | 41 | *cider-=F* 42 | =F Format the current file. 43 | Use gg=fG to map this yourself (notice to use your own 44 | |cider-=f| mapping.) 45 | 46 | *cider-cdd* 47 | cdd Undefine a variable or unalias namespace a alias 48 | defined by a symbol under the cursor. 49 | 50 | *cider-F4* 51 | F4 Clean the namespace form of current file. 52 | 53 | ABOUT *cider-about* 54 | 55 | Grab the latest version or report a bug on GitHub: 56 | 57 | http://github.com/Deraen/vim-cider 58 | 59 | vim:tw=78:et:ft=help:norl: 60 | -------------------------------------------------------------------------------- /plugin/cider.vim: -------------------------------------------------------------------------------- 1 | " cider.vim 2 | " Maintainer: Juho Teperi 3 | 4 | if exists("g:loaded_cider") || v:version < 700 || &cp 5 | finish 6 | endif 7 | let g:loaded_cider = 1 8 | 9 | " FIXME: From fireplace 10 | function! s:opfunc(type) abort 11 | let sel_save = &selection 12 | let cb_save = &clipboard 13 | let reg_save = @@ 14 | try 15 | set selection=inclusive clipboard-=unnamed clipboard-=unnamedplus 16 | if type(a:type) == type(0) 17 | let open = '[[{(]' 18 | let close = '[]})]' 19 | if getline('.')[col('.')-1] =~# close 20 | let [line1, col1] = searchpairpos(open, '', close, 'bn', g:fireplace#skip) 21 | let [line2, col2] = [line('.'), col('.')] 22 | else 23 | let [line1, col1] = searchpairpos(open, '', close, 'bcn', g:fireplace#skip) 24 | let [line2, col2] = searchpairpos(open, '', close, 'n', g:fireplace#skip) 25 | endif 26 | while col1 > 1 && getline(line1)[col1-2] =~# '[#''`~@]' 27 | let col1 -= 1 28 | endwhile 29 | call setpos("'[", [0, line1, col1, 0]) 30 | call setpos("']", [0, line2, col2, 0]) 31 | silent exe "normal! `[v`]y" 32 | elseif a:type =~# '^.$' 33 | silent exe "normal! `<" . a:type . "`>y" 34 | elseif a:type ==# 'line' 35 | silent exe "normal! '[V']y" 36 | elseif a:type ==# 'block' 37 | silent exe "normal! `[\`]y" 38 | elseif a:type ==# 'outer' 39 | call searchpair('(','',')', 'Wbcr', g:fireplace#skip) 40 | silent exe "normal! vaby" 41 | else 42 | silent exe "normal! `[v`]y" 43 | endif 44 | redraw 45 | return repeat("\n", line("'<")-1) . repeat(" ", col("'<")-1) . @@ 46 | finally 47 | let @@ = reg_save 48 | let &selection = sel_save 49 | let &clipboard = cb_save 50 | endtry 51 | endfunction 52 | 53 | function! s:save_regs(fn, ...) 54 | let reg_save = @@ 55 | let sel_save = &selection 56 | let cb_save = &clipboard 57 | try 58 | set selection=inclusive clipboard-=unnamed clipboard-=unnamedplus 59 | call call(a:fn, a:000) 60 | catch /^Clojure:/ 61 | return '' 62 | finally 63 | let @@ = reg_save 64 | let &selection = sel_save 65 | let &clipboard = cb_save 66 | endtry 67 | endfunction 68 | 69 | " 70 | " Format operation 71 | " 72 | 73 | function! s:formatop_impl(type) abort 74 | let expr = s:opfunc(a:type) 75 | let res = fireplace#message({'op': 'format-code', 'code': expr})[0] 76 | " Code is aligned to start in same position as in the original file 77 | let formatted = substitute(get(res, 'formatted-code'), '^[\n ]\+', '', '') 78 | let @@ = formatted 79 | if @@ !~# '^\n*$' 80 | normal! gvp 81 | endif 82 | endfunction 83 | 84 | function! s:formatop(type) abort 85 | call s:save_regs(function('s:formatop_impl'), a:type) 86 | endfunction 87 | 88 | nnoremap CiderFormat :set opfunc=formatopg@ 89 | xnoremap CiderFormat :call formatop(visualmode()) 90 | nnoremap CiderCountFormat :call formatop(v:count) 91 | 92 | " 93 | " Undef 94 | " 95 | 96 | function! s:undef() abort 97 | let ns = fireplace#ns() 98 | let s = expand('') 99 | let res = fireplace#message({'op': 'undef', 'ns': ns, 'symbol': s})[0] 100 | let error = get(res, 'err') 101 | if !empty(error) 102 | throw error 103 | else 104 | echo 'Undefined ' . s 105 | endif 106 | endfunction 107 | 108 | nnoremap CiderUndef :call undef() 109 | 110 | " 111 | " CleanNs 112 | " 113 | 114 | function! s:paste(text) abort 115 | " Does charwise paste to current '[ and '] marks 116 | let @@ = a:text 117 | let reg_type = getregtype('@@') 118 | call setreg('@@', getreg('@@'), 'v') 119 | silent exe "normal! `[v`]p" 120 | call setreg('@@', getreg('@@'), reg_type)" 121 | endfunction 122 | 123 | function! s:clean_ns() abort 124 | " FIXME: Moves cursor 125 | 126 | let p = expand('%:p') 127 | normal! ggw 128 | 129 | let [line1, col1] = searchpairpos('(', '', ')', 'bc') 130 | let [line2, col2] = searchpairpos('(', '', ')', 'n') 131 | 132 | while col1 > 1 && getline(line1)[col1-2] =~# '[#''`~@]' 133 | let col1 -= 1 134 | endwhile 135 | call setpos("'[", [0, line1, col1, 0]) 136 | call setpos("']", [0, line2, col2, 0]) 137 | 138 | if expand('') ==? 'ns' 139 | let opts = { 'op': 'clean-ns', 'path': p } 140 | call extend(opts, get(g:, 'refactor_nrepl_options', {})) 141 | 142 | let res = fireplace#message(opts)[0] 143 | let error = get(res, 'error') 144 | if !empty(error) 145 | throw error 146 | elseif type(res.ns) == type("") 147 | call s:paste(substitute(res.ns, '\n$', '', '')) 148 | silent exe "normal! `[v`]==" 149 | echo "Ns form cleaned" 150 | else 151 | echo "Ns form already clean" 152 | endif 153 | endif 154 | endfunction 155 | 156 | nnoremap RefactorCleanNs :call clean_ns() 157 | 158 | function! s:split_symbol(sym) abort 159 | if a:sym =~ '\/' 160 | return split(a:sym, '\/') 161 | elseif a:sym =~ '\.' 162 | let parts = split(a:sym, '\.') 163 | return [join(parts[0:-1], '.'), parts[-1]] 164 | else 165 | return [0, a:sym] 166 | endif 167 | endfunction 168 | 169 | " echom scriptease#dump(s:split_symbol('java.util.Date')) 170 | " echom scriptease#dump(s:split_symbol('Date')) 171 | " echom scriptease#dump(s:split_symbol('clojure.string/split')) 172 | " echom scriptease#dump(s:split_symbol('str/split')) 173 | 174 | function! s:resolve_missing() abort 175 | " TODO: Check indices 176 | 177 | call s:init_refactor_nrepl() 178 | 179 | let [alias, sym] = s:split_symbol(expand('')) 180 | let res = fireplace#message({'op': 'resolve-missing', 'symbol': sym}) 181 | let choices = fireplace#evalparse('(quote ' . res[0].candidates . ')') 182 | 183 | " TODO: Investigate using something prettier? Check how CtrlP is 184 | " implemented. 185 | call inputsave() 186 | let x = inputlist(["Select: "] + map(copy(choices), '(v:key+ 1) . ". " . v:val[0]')) 187 | call inputrestore() 188 | let @@ = "[" . choices[x - 1][0] . " :as " . alias . "]" 189 | 190 | " Insert as last entry in :require list 191 | normal gg 192 | if search('(:require', 'W') !=# 0 193 | execute "normal! %i\\p" 194 | endif 195 | endfunction 196 | 197 | nnoremap RefactorResolveMissing :call resolve_missing() 198 | 199 | function! s:kwpairs_to_dict(x) abort 200 | let m = {} 201 | for i in range(0, len(a:x) - 2, 2) 202 | let m[a:x[i]] = a:x[i + 1] 203 | endfor 204 | return m 205 | endfunction 206 | 207 | " echom scriptease#dump(s:kwpairs_to_dict(['a', 5])) 208 | " echom scriptease#dump(s:kwpairs_to_dict(['a', 5, 'b', 3])) 209 | 210 | function! s:find_symbol() abort 211 | let sym = expand('') 212 | let meta = fireplace#info(sym) 213 | " echom scriptease#dump(meta) 214 | let [bufnum, lnum, col, off] = getpos('.') 215 | let filepath = expand('%:p') 216 | let res = fireplace#message({'op': 'find-symbol', 'dir': '.', 'file': filepath, 'ns': meta.ns, 'name': meta.name, 'line': lnum, 'column': col, 'serialization-format': 'bencode'}) 217 | 218 | call filter(res, 'has_key(v:val, "occurrence")') 219 | call map(res, 's:kwpairs_to_dict(v:val.occurrence)') 220 | call map(res, 'v:val["file"] . ":" . v:val["line-beg"] . ":" . v:val["col-beg"] . ":" . v:val["match"]') 221 | 222 | " TODO: Investigate using something more like CtrlP 223 | 224 | cgetexpr res 225 | copen 226 | endfunction 227 | 228 | nnoremap RefactorFindSymbol :call find_symbol() 229 | 230 | function! s:set_up() abort 231 | if get(g:, 'cider_no_maps') | return | endif 232 | 233 | nmap =f CiderFormat 234 | nmap =ff CiderCountFormat 235 | nmap =F gg=fG 236 | 237 | nmap cdd CiderUndef 238 | nmap RefactorCleanNs 239 | " FIXME: Find better binding 240 | nmap cRR RefactorResolveMissing 241 | nmap RefactorFindSymbol 242 | endfunction 243 | 244 | augroup cider_eval 245 | autocmd! 246 | autocmd FileType clojure call s:set_up() 247 | augroup END 248 | --------------------------------------------------------------------------------