1" Vim indent file 2" Language: Clojure 3" Author: Meikel Brandmeyer <[email protected]> 4" URL: http://kotka.de/projects/clojure/vimclojure.html 5" 6" Maintainer: Sung Pae <[email protected]> 7" URL: https://github.com/guns/vim-clojure-static 8" License: Same as Vim 9" Last Change: 16 December 2013 10 11" TODO: Indenting after multibyte characters is broken: 12" (let [Δ (if foo 13" bar ; Indent error 14" baz)]) 15 16if exists("b:did_indent") 17 finish 18endif 19let b:did_indent = 1 20 21let s:save_cpo = &cpo 22set cpo&vim 23 24let b:undo_indent = 'setlocal autoindent< smartindent< lispwords< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys<' 25 26setlocal noautoindent nosmartindent 27setlocal softtabstop=2 shiftwidth=2 expandtab 28setlocal indentkeys=!,o,O 29 30if exists("*searchpairpos") 31 32 if !exists('g:clojure_maxlines') 33 let g:clojure_maxlines = 100 34 endif 35 36 if !exists('g:clojure_fuzzy_indent') 37 let g:clojure_fuzzy_indent = 1 38 endif 39 40 if !exists('g:clojure_fuzzy_indent_patterns') 41 let g:clojure_fuzzy_indent_patterns = ['^with', '^def', '^let'] 42 endif 43 44 if !exists('g:clojure_fuzzy_indent_blacklist') 45 let g:clojure_fuzzy_indent_blacklist = ['-fn$', '\v^with-%(meta|out-str|loading-context)$'] 46 endif 47 48 if !exists('g:clojure_special_indent_words') 49 let g:clojure_special_indent_words = 'deftype,defrecord,reify,proxy,extend-type,extend-protocol,letfn' 50 endif 51 52 if !exists('g:clojure_align_multiline_strings') 53 let g:clojure_align_multiline_strings = 0 54 endif 55 56 if !exists('g:clojure_align_subforms') 57 let g:clojure_align_subforms = 0 58 endif 59 60 function! s:SynIdName() 61 return synIDattr(synID(line("."), col("."), 0), "name") 62 endfunction 63 64 function! s:CurrentChar() 65 return getline('.')[col('.')-1] 66 endfunction 67 68 function! s:CurrentWord() 69 return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2] 70 endfunction 71 72 function! s:IsParen() 73 return s:CurrentChar() =~ '\v[\(\)\[\]\{\}]' && 74 \ s:SynIdName() !~? '\vstring|regex|comment|character' 75 endfunction 76 77 " Returns 1 if string matches a pattern in 'patterns', which may be a 78 " list of patterns, or a comma-delimited string of implicitly anchored 79 " patterns. 80 function! s:MatchesOne(patterns, string) 81 let list = type(a:patterns) == type([]) 82 \ ? a:patterns 83 \ : map(split(a:patterns, ','), '"^" . v:val . "$"') 84 for pat in list 85 if a:string =~ pat | return 1 | endif 86 endfor 87 endfunction 88 89 function! s:MatchPairs(open, close, stopat) 90 " Stop only on vector and map [ resp. {. Ignore the ones in strings and 91 " comments. 92 if a:stopat == 0 93 let stopat = max([line(".") - g:clojure_maxlines, 0]) 94 else 95 let stopat = a:stopat 96 endif 97 98 let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:IsParen()", stopat) 99 return [pos[0], virtcol(pos)] 100 endfunction 101 102 function! s:ClojureCheckForStringWorker() 103 " Check whether there is the last character of the previous line is 104 " highlighted as a string. If so, we check whether it's a ". In this 105 " case we have to check also the previous character. The " might be the 106 " closing one. In case the we are still in the string, we search for the 107 " opening ". If this is not found we take the indent of the line. 108 let nb = prevnonblank(v:lnum - 1) 109 110 if nb == 0 111 return -1 112 endif 113 114 call cursor(nb, 0) 115 call cursor(0, col("$") - 1) 116 if s:SynIdName() !~? "string" 117 return -1 118 endif 119 120 " This will not work for a " in the first column... 121 if s:CurrentChar() == '"' 122 call cursor(0, col("$") - 2) 123 if s:SynIdName() !~? "string" 124 return -1 125 endif 126 if s:CurrentChar() != '\\' 127 return -1 128 endif 129 call cursor(0, col("$") - 1) 130 endif 131 132 let p = searchpos('\(^\|[^\\]\)\zs"', 'bW') 133 134 if p != [0, 0] 135 return p[1] - 1 136 endif 137 138 return indent(".") 139 endfunction 140 141 function! s:CheckForString() 142 let pos = getpos('.') 143 try 144 let val = s:ClojureCheckForStringWorker() 145 finally 146 call setpos('.', pos) 147 endtry 148 return val 149 endfunction 150 151 function! s:ClojureIsMethodSpecialCaseWorker(position) 152 " Find the next enclosing form. 153 call search('\S', 'Wb') 154 155 " Special case: we are at a '(('. 156 if s:CurrentChar() == '(' 157 return 0 158 endif 159 call cursor(a:position) 160 161 let nextParen = s:MatchPairs('(', ')', 0) 162 163 " Special case: we are now at toplevel. 164 if nextParen == [0, 0] 165 return 0 166 endif 167 call cursor(nextParen) 168 169 call search('\S', 'W') 170 if g:clojure_special_indent_words =~ '\<' . s:CurrentWord() . '\>' 171 return 1 172 endif 173 174 return 0 175 endfunction 176 177 function! s:IsMethodSpecialCase(position) 178 let pos = getpos('.') 179 try 180 let val = s:ClojureIsMethodSpecialCaseWorker(a:position) 181 finally 182 call setpos('.', pos) 183 endtry 184 return val 185 endfunction 186 187 function! GetClojureIndent() 188 " Get rid of special case. 189 if line(".") == 1 190 return 0 191 endif 192 193 " We have to apply some heuristics here to figure out, whether to use 194 " normal lisp indenting or not. 195 let i = s:CheckForString() 196 if i > -1 197 return i + !!g:clojure_align_multiline_strings 198 endif 199 200 call cursor(0, 1) 201 202 " Find the next enclosing [ or {. We can limit the second search 203 " to the line, where the [ was found. If no [ was there this is 204 " zero and we search for an enclosing {. 205 let paren = s:MatchPairs('(', ')', 0) 206 let bracket = s:MatchPairs('\[', '\]', paren[0]) 207 let curly = s:MatchPairs('{', '}', bracket[0]) 208 209 " In case the curly brace is on a line later then the [ or - in 210 " case they are on the same line - in a higher column, we take the 211 " curly indent. 212 if curly[0] > bracket[0] || curly[1] > bracket[1] 213 if curly[0] > paren[0] || curly[1] > paren[1] 214 return curly[1] 215 endif 216 endif 217 218 " If the curly was not chosen, we take the bracket indent - if 219 " there was one. 220 if bracket[0] > paren[0] || bracket[1] > paren[1] 221 return bracket[1] 222 endif 223 224 " There are neither { nor [ nor (, ie. we are at the toplevel. 225 if paren == [0, 0] 226 return 0 227 endif 228 229 " Now we have to reimplement lispindent. This is surprisingly easy, as 230 " soon as one has access to syntax items. 231 " 232 " - Check whether we are in a special position after a word in 233 " g:clojure_special_indent_words. These are special cases. 234 " - Get the next keyword after the (. 235 " - If its first character is also a (, we have another sexp and align 236 " one column to the right of the unmatched (. 237 " - In case it is in lispwords, we indent the next line to the column of 238 " the ( + sw. 239 " - If not, we check whether it is last word in the line. In that case 240 " we again use ( + sw for indent. 241 " - In any other case we use the column of the end of the word + 2. 242 call cursor(paren) 243 244 if s:IsMethodSpecialCase(paren) 245 return paren[1] + &shiftwidth - 1 246 endif 247 248 " In case we are at the last character, we use the paren position. 249 if col("$") - 1 == paren[1] 250 return paren[1] 251 endif 252 253 " In case after the paren is a whitespace, we search for the next word. 254 call cursor(0, col('.') + 1) 255 if s:CurrentChar() == ' ' 256 call search('\v\S', 'W') 257 endif 258 259 " If we moved to another line, there is no word after the (. We 260 " use the ( position for indent. 261 if line(".") > paren[0] 262 return paren[1] 263 endif 264 265 " We still have to check, whether the keyword starts with a (, [ or {. 266 " In that case we use the ( position for indent. 267 let w = s:CurrentWord() 268 if stridx('([{', w[0]) > -1 269 return paren[1] 270 endif 271 272 " Test words without namespace qualifiers and leading reader macro 273 " metacharacters. 274 " 275 " e.g. clojure.core/defn and #'defn should both indent like defn. 276 let ww = substitute(w, "\\v%(.*/|[#'`~@^,]*)(.*)", '\1', '') 277 278 if &lispwords =~ '\V\<' . ww . '\>' 279 return paren[1] + &shiftwidth - 1 280 endif 281 282 if g:clojure_fuzzy_indent 283 \ && !s:MatchesOne(g:clojure_fuzzy_indent_blacklist, ww) 284 \ && s:MatchesOne(g:clojure_fuzzy_indent_patterns, ww) 285 return paren[1] + &shiftwidth - 1 286 endif 287 288 call search('\v\_s', 'cW') 289 call search('\v\S', 'W') 290 if paren[0] < line(".") 291 return paren[1] + (g:clojure_align_subforms ? 0 : &shiftwidth - 1) 292 endif 293 294 call search('\v\S', 'bW') 295 return virtcol(".") + 1 296 endfunction 297 298 setlocal indentexpr=GetClojureIndent() 299 300else 301 302 " In case we have searchpairpos not available we fall back to 303 " normal lisp indenting. 304 setlocal indentexpr= 305 setlocal lisp 306 let b:undo_indent .= '| setlocal lisp<' 307 308endif 309 310" Specially indented symbols from clojure.core and clojure.test. 311" 312" Clojure symbols are indented in the defn style when they: 313" 314" * Define vars and anonymous functions 315" * Create new lexical scopes or scopes with altered environments 316" * Create conditional branches from a predicate function or value 317" 318" The arglists for these functions are generally in the form of [x & body]; 319" Functions that accept a flat list of forms do not treat the first argument 320" specially and hence are not indented specially. 321 322" Definitions 323setlocal lispwords= 324setlocal lispwords+=bound-fn 325setlocal lispwords+=def 326setlocal lispwords+=definline 327setlocal lispwords+=definterface 328setlocal lispwords+=defmacro 329setlocal lispwords+=defmethod 330setlocal lispwords+=defmulti 331setlocal lispwords+=defn 332setlocal lispwords+=defn- 333setlocal lispwords+=defonce 334setlocal lispwords+=defprotocol 335setlocal lispwords+=defrecord 336setlocal lispwords+=defstruct 337setlocal lispwords+=deftest " clojure.test 338setlocal lispwords+=deftest- " clojure.test 339setlocal lispwords+=deftype 340setlocal lispwords+=extend 341setlocal lispwords+=extend-protocol 342setlocal lispwords+=extend-type 343setlocal lispwords+=fn 344setlocal lispwords+=ns 345setlocal lispwords+=proxy 346setlocal lispwords+=reify 347setlocal lispwords+=set-test " clojure.test 348 349" Binding forms 350setlocal lispwords+=as-> 351setlocal lispwords+=binding 352setlocal lispwords+=doall 353setlocal lispwords+=dorun 354setlocal lispwords+=doseq 355setlocal lispwords+=dotimes 356setlocal lispwords+=doto 357setlocal lispwords+=for 358setlocal lispwords+=if-let 359setlocal lispwords+=let 360setlocal lispwords+=letfn 361setlocal lispwords+=locking 362setlocal lispwords+=loop 363setlocal lispwords+=testing " clojure.test 364setlocal lispwords+=when-first 365setlocal lispwords+=when-let 366setlocal lispwords+=with-bindings 367setlocal lispwords+=with-in-str 368setlocal lispwords+=with-local-vars 369setlocal lispwords+=with-open 370setlocal lispwords+=with-precision 371setlocal lispwords+=with-redefs 372setlocal lispwords+=with-redefs-fn 373setlocal lispwords+=with-test " clojure.test 374 375" Conditional branching 376setlocal lispwords+=case 377setlocal lispwords+=cond-> 378setlocal lispwords+=cond->> 379setlocal lispwords+=condp 380setlocal lispwords+=if 381setlocal lispwords+=if-not 382setlocal lispwords+=when 383setlocal lispwords+=when-not 384setlocal lispwords+=while 385 386" Exception handling 387setlocal lispwords+=catch 388 389let &cpo = s:save_cpo 390unlet! s:save_cpo 391 392" vim:sts=8:sw=8:ts=8:noet 393