3 " Author: Meikel Brandmeyer <mb@kotka.de>
4 " URL: http://kotka.de/projects/clojure/vimclojure.html
6 " Maintainer: Sung Pae <self@sungpae.com>
7 " URL: https://github.com/guns/vim-clojure-static
9 " Last Change: 30 January 2013
11 " Only load this indent file when no other was loaded.
12 if exists("b:did_indent")
20 let b:undo_indent = 'setlocal autoindent< smartindent< lispwords< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys<'
22 setlocal noautoindent nosmartindent
23 setlocal softtabstop=2 shiftwidth=2 expandtab
24 setlocal indentkeys=!
\x06,o,O
26 if exists("*searchpairpos")
28 if !exists('g:clojure_maxlines')
29 let g:clojure_maxlines = 100
32 if !exists('g:clojure_fuzzy_indent')
33 let g:clojure_fuzzy_indent = 1
36 if !exists('g:clojure_fuzzy_indent_patterns')
37 let g:clojure_fuzzy_indent_patterns = ['^with', '^def', '^let']
40 if !exists('g:clojure_fuzzy_indent_blacklist')
41 let g:clojure_fuzzy_indent_blacklist = ['-fn$', '\v^with-%(meta|out-str|loading-context)$']
44 if !exists('g:clojure_special_indent_words')
45 let g:clojure_special_indent_words = 'deftype,defrecord,reify,proxy,extend-type,extend-protocol,letfn'
48 if !exists('g:clojure_align_multiline_strings')
49 let g:clojure_align_multiline_strings = 0
52 function! s:SynIdName()
53 return synIDattr(synID(line("."), col("."), 0), "name")
56 function! s:CurrentChar()
57 return getline('.')[col('.')-1]
60 function! s:CurrentWord()
61 return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2]
65 return s:CurrentChar() =~ '\v[\(\)\[\]\{\}]' &&
66 \ s:SynIdName() !~? '\vstring|comment'
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
72 function! s:MatchesOne(patterns, string)
73 let list = type(a:patterns) == type([])
75 \ : map(split(a:patterns, ','), '"^" . v:val . "$"')
77 if a:string =~ pat | return 1 | endif
81 function! s:SavePosition()
82 let [ _b, l, c, _o ] = getpos(".")
87 function! s:RestorePosition(value)
88 let [b, l, c] = a:value
92 call setpos(".", [0, l, c, 0])
95 function! s:MatchPairs(open, close, stopat)
96 " Stop only on vector and map [ resp. {. Ignore the ones in strings and
99 let stopat = max([line(".") - g:clojure_maxlines, 0])
101 let stopat = a:stopat
104 let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:IsParen()", stopat)
105 return [pos[0], virtcol(pos)]
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)
121 call cursor(0, col("$") - 1)
122 if s:SynIdName() !~? "string"
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"
132 if s:CurrentChar() != '\\'
135 call cursor(0, col("$") - 1)
138 let p = searchpos('\(^\|[^\\]\)\zs"', 'bW')
147 function! s:CheckForString()
148 let pos = s:SavePosition()
150 let val = s:ClojureCheckForStringWorker()
152 call s:RestorePosition(pos)
157 function! s:ClojureIsMethodSpecialCaseWorker(position)
158 " Find the next enclosing form.
159 call search('\S', 'Wb')
161 " Special case: we are at a '(('.
162 if s:CurrentChar() == '('
165 call cursor(a:position)
167 let nextParen = s:MatchPairs('(', ')', 0)
169 " Special case: we are now at toplevel.
170 if nextParen == [0, 0]
173 call cursor(nextParen)
175 call search('\S', 'W')
176 if g:clojure_special_indent_words =~ '\<' . s:CurrentWord() . '\>'
183 function! s:IsMethodSpecialCase(position)
184 let pos = s:SavePosition()
186 let val = s:ClojureIsMethodSpecialCaseWorker(a:position)
188 call s:RestorePosition(pos)
193 function! GetClojureIndent()
194 " Get rid of special case.
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()
203 return i + !!g:clojure_align_multiline_strings
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])
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
218 if curly[0] > bracket[0] || curly[1] > bracket[1]
219 if curly[0] > paren[0] || curly[1] > paren[1]
224 " If the curly was not chosen, we take the bracket indent - if
226 if bracket[0] > paren[0] || bracket[1] > paren[1]
230 " There are neither { nor [ nor (, ie. we are at the toplevel.
235 " Now we have to reimplement lispindent. This is surprisingly easy, as
236 " soon as one has access to syntax items.
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
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.
250 if s:IsMethodSpecialCase(paren)
251 return paren[1] + &shiftwidth - 1
254 " In case we are at the last character, we use the paren position.
255 if col("$") - 1 == paren[1]
259 " In case after the paren is a whitespace, we search for the next word.
261 if s:CurrentChar() == ' '
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]
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
278 " Test words without namespace qualifiers and leading reader macro
281 " e.g. clojure.core/defn and #'defn should both indent like defn.
282 let ww = substitute(w, "\\v%(.*/|[#'`~@^,]*)(.*)", '\1', '')
284 if &lispwords =~ '\V\<' . ww . '\>'
285 return paren[1] + &shiftwidth - 1
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
295 if paren[0] < line(".")
296 return paren[1] + &shiftwidth - 1
300 return virtcol(".") + 1
303 setlocal indentexpr=GetClojureIndent()
307 " In case we have searchpairpos not available we fall back to
308 " normal lisp indenting.
311 let b:undo_indent .= '| setlocal lisp<'
315 " Specially indented symbols from clojure.core and clojure.test.
317 " Clojure symbols are indented in the defn style when they:
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
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.
329 setlocal lispwords+=bound-fn
330 setlocal lispwords+=def
331 setlocal lispwords+=definline
332 setlocal lispwords+=definterface
333 setlocal lispwords+=defmacro
334 setlocal lispwords+=defmethod
335 setlocal lispwords+=defmulti
336 setlocal lispwords+=defn
337 setlocal lispwords+=defn-
338 setlocal lispwords+=defonce
339 setlocal lispwords+=defprotocol
340 setlocal lispwords+=defrecord
341 setlocal lispwords+=defstruct
342 setlocal lispwords+=deftest " clojure.test
343 setlocal lispwords+=deftest- " clojure.test
344 setlocal lispwords+=deftype
345 setlocal lispwords+=extend
346 setlocal lispwords+=extend-protocol
347 setlocal lispwords+=extend-type
348 setlocal lispwords+=fn
349 setlocal lispwords+=ns
350 setlocal lispwords+=proxy
351 setlocal lispwords+=reify
352 setlocal lispwords+=set-test " clojure.test
355 setlocal lispwords+=as->
356 setlocal lispwords+=binding
357 setlocal lispwords+=doall
358 setlocal lispwords+=dorun
359 setlocal lispwords+=doseq
360 setlocal lispwords+=dotimes
361 setlocal lispwords+=doto
362 setlocal lispwords+=for
363 setlocal lispwords+=if-let
364 setlocal lispwords+=let
365 setlocal lispwords+=letfn
366 setlocal lispwords+=locking
367 setlocal lispwords+=loop
368 setlocal lispwords+=testing " clojure.test
369 setlocal lispwords+=when-first
370 setlocal lispwords+=when-let
371 setlocal lispwords+=with-bindings
372 setlocal lispwords+=with-in-str
373 setlocal lispwords+=with-local-vars
374 setlocal lispwords+=with-open
375 setlocal lispwords+=with-precision
376 setlocal lispwords+=with-redefs
377 setlocal lispwords+=with-redefs-fn
378 setlocal lispwords+=with-test " clojure.test
380 " Conditional branching
381 setlocal lispwords+=case
382 setlocal lispwords+=cond->
383 setlocal lispwords+=cond->>
384 setlocal lispwords+=condp
385 setlocal lispwords+=if
386 setlocal lispwords+=if-not
387 setlocal lispwords+=when
388 setlocal lispwords+=when-not
389 setlocal lispwords+=while
392 setlocal lispwords+=catch
393 setlocal lispwords+=try " For aesthetics when enclosing single line
395 let &cpo = s:save_cpo