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: 27 March 2014 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< 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:StripNamespaceAndMacroChars(word) 152 return substitute(a:word, "\\v%(.*/|[#'`~@^,]*)(.*)", '\1', '') 153 endfunction 154 155 function! s:ClojureIsMethodSpecialCaseWorker(position) 156 " Find the next enclosing form. 157 call search('\S', 'Wb') 158 159 " Special case: we are at a '(('. 160 if s:CurrentChar() == '(' 161 return 0 162 endif 163 call cursor(a:position) 164 165 let nextParen = s:MatchPairs('(', ')', 0) 166 167 " Special case: we are now at toplevel. 168 if nextParen == [0, 0] 169 return 0 170 endif 171 call cursor(nextParen) 172 173 call search('\S', 'W') 174 let w = s:StripNamespaceAndMacroChars(s:CurrentWord()) 175 if g:clojure_special_indent_words =~# '\V\<' . w . '\>' 176 return 1 177 endif 178 179 return 0 180 endfunction 181 182 function! s:IsMethodSpecialCase(position) 183 let pos = getpos('.') 184 try 185 let val = s:ClojureIsMethodSpecialCaseWorker(a:position) 186 finally 187 call setpos('.', pos) 188 endtry 189 return val 190 endfunction 191 192 function! GetClojureIndent() 193 " Get rid of special case. 194 if line(".") == 1 195 return 0 196 endif 197 198 " We have to apply some heuristics here to figure out, whether to use 199 " normal lisp indenting or not. 200 let i = s:CheckForString() 201 if i > -1 202 return i + !!g:clojure_align_multiline_strings 203 endif 204 205 call cursor(0, 1) 206 207 " Find the next enclosing [ or {. We can limit the second search 208 " to the line, where the [ was found. If no [ was there this is 209 " zero and we search for an enclosing {. 210 let paren = s:MatchPairs('(', ')', 0) 211 let bracket = s:MatchPairs('\[', '\]', paren[0]) 212 let curly = s:MatchPairs('{', '}', bracket[0]) 213 214 " In case the curly brace is on a line later then the [ or - in 215 " case they are on the same line - in a higher column, we take the 216 " curly indent. 217 if curly[0] > bracket[0] || curly[1] > bracket[1] 218 if curly[0] > paren[0] || curly[1] > paren[1] 219 return curly[1] 220 endif 221 endif 222 223 " If the curly was not chosen, we take the bracket indent - if 224 " there was one. 225 if bracket[0] > paren[0] || bracket[1] > paren[1] 226 return bracket[1] 227 endif 228 229 " There are neither { nor [ nor (, ie. we are at the toplevel. 230 if paren == [0, 0] 231 return 0 232 endif 233 234 " Now we have to reimplement lispindent. This is surprisingly easy, as 235 " soon as one has access to syntax items. 236 " 237 " - Check whether we are in a special position after a word in 238 " g:clojure_special_indent_words. These are special cases. 239 " - Get the next keyword after the (. 240 " - If its first character is also a (, we have another sexp and align 241 " one column to the right of the unmatched (. 242 " - In case it is in lispwords, we indent the next line to the column of 243 " the ( + sw. 244 " - If not, we check whether it is last word in the line. In that case 245 " we again use ( + sw for indent. 246 " - In any other case we use the column of the end of the word + 2. 247 call cursor(paren) 248 249 if s:IsMethodSpecialCase(paren) 250 return paren[1] + &shiftwidth - 1 251 endif 252 253 " In case we are at the last character, we use the paren position. 254 if col("$") - 1 == paren[1] 255 return paren[1] 256 endif 257 258 " In case after the paren is a whitespace, we search for the next word. 259 call cursor(0, col('.') + 1) 260 if s:CurrentChar() == ' ' 261 call search('\v\S', 'W') 262 endif 263 264 " If we moved to another line, there is no word after the (. We 265 " use the ( position for indent. 266 if line(".") > paren[0] 267 return paren[1] 268 endif 269 270 " We still have to check, whether the keyword starts with a (, [ or {. 271 " In that case we use the ( position for indent. 272 let w = s:CurrentWord() 273 if stridx('([{', w[0]) > -1 274 return paren[1] 275 endif 276 277 " Test words without namespace qualifiers and leading reader macro 278 " metacharacters. 279 " 280 " e.g. clojure.core/defn and #'defn should both indent like defn. 281 let ww = s:StripNamespaceAndMacroChars(w) 282 283 if &lispwords =~# '\V\<' . ww . '\>' 284 return paren[1] + &shiftwidth - 1 285 endif 286 287 if g:clojure_fuzzy_indent 288 \ && !s:MatchesOne(g:clojure_fuzzy_indent_blacklist, ww) 289 \ && s:MatchesOne(g:clojure_fuzzy_indent_patterns, ww) 290 return paren[1] + &shiftwidth - 1 291 endif 292 293 call search('\v\_s', 'cW') 294 call search('\v\S', 'W') 295 if paren[0] < line(".") 296 return paren[1] + (g:clojure_align_subforms ? 0 : &shiftwidth - 1) 297 endif 298 299 call search('\v\S', 'bW') 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 315let &cpo = s:save_cpo 316unlet! s:save_cpo 317 318" vim:sts=8:sw=8:ts=8:noet 319