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