Install vim74
[msysgit.git] / share / vim / vim74 / indent / clojure.vim
blob9a00fb3253fc2deaf700d2a0f5a3c93c06b41562
1 " Vim indent file
2 " Language:     Clojure
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
8 " License:      Same as Vim
9 " Last Change:  30 January 2013
11 " Only load this indent file when no other was loaded.
12 if exists("b:did_indent")
13     finish
14 endif
15 let b:did_indent = 1
17 let s:save_cpo = &cpo
18 set cpo&vim
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
30     endif
32     if !exists('g:clojure_fuzzy_indent')
33         let g:clojure_fuzzy_indent = 1
34     endif
36     if !exists('g:clojure_fuzzy_indent_patterns')
37         let g:clojure_fuzzy_indent_patterns = ['^with', '^def', '^let']
38     endif
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
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
48     if !exists('g:clojure_align_multiline_strings')
49         let g:clojure_align_multiline_strings = 0
50     endif
52     function! s:SynIdName()
53         return synIDattr(synID(line("."), col("."), 0), "name")
54     endfunction
56     function! s:CurrentChar()
57         return getline('.')[col('.')-1]
58     endfunction
60     function! s:CurrentWord()
61         return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2]
62     endfunction
64     function! s:IsParen()
65         return s:CurrentChar() =~ '\v[\(\)\[\]\{\}]' &&
66              \ s:SynIdName() !~? '\vstring|comment'
67     endfunction
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
81     function! s:SavePosition()
82         let [ _b, l, c, _o ] = getpos(".")
83         let b = bufnr("%")
84         return [b, l, c]
85     endfunction
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
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
104         let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:IsParen()", stopat)
105         return [pos[0], virtcol(pos)]
106     endfunction
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)
116         if nb == 0
117             return -1
118         endif
120         call cursor(nb, 0)
121         call cursor(0, col("$") - 1)
122         if s:SynIdName() !~? "string"
123             return -1
124         endif
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
138         let p = searchpos('\(^\|[^\\]\)\zs"', 'bW')
140         if p != [0, 0]
141             return p[1] - 1
142         endif
144         return indent(".")
145     endfunction
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
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() == '('
163             return 0
164         endif
165         call cursor(a:position)
167         let nextParen = s:MatchPairs('(', ')', 0)
169         " Special case: we are now at toplevel.
170         if nextParen == [0, 0]
171             return 0
172         endif
173         call cursor(nextParen)
175         call search('\S', 'W')
176         if g:clojure_special_indent_words =~ '\<' . s:CurrentWord() . '\>'
177             return 1
178         endif
180         return 0
181     endfunction
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
193     function! GetClojureIndent()
194         " Get rid of special case.
195         if line(".") == 1
196             return 0
197         endif
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
206         call cursor(0, 1)
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
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
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
230         " There are neither { nor [ nor (, ie. we are at the toplevel.
231         if paren == [0, 0]
232             return 0
233         endif
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)
250         if s:IsMethodSpecialCase(paren)
251             return paren[1] + &shiftwidth - 1
252         endif
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
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
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
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
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', '')
284         if &lispwords =~ '\V\<' . ww . '\>'
285             return paren[1] + &shiftwidth - 1
286         endif
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
294         normal! W
295         if paren[0] < line(".")
296             return paren[1] + &shiftwidth - 1
297         endif
299         normal! ge
300         return virtcol(".") + 1
301     endfunction
303     setlocal indentexpr=GetClojureIndent()
305 else
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<'
313 endif
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.
327 " Definitions
328 setlocal lispwords=
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
354 " Binding forms
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
391 " Exception handling
392 setlocal lispwords+=catch
393 setlocal lispwords+=try " For aesthetics when enclosing single line
395 let &cpo = s:save_cpo
396 unlet! s:save_cpo
398 " vim:sts=4 sw=4 et: