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