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