blob: 9a00fb3253fc2deaf700d2a0f5a3c93c06b41562 [file] [log] [blame]
Bram Moolenaarfa13eef2013-02-06 17:34:04 +01001" Vim indent file
2" Language: Clojure
3" Author: Meikel Brandmeyer <mb@kotka.de>
4" URL: http://kotka.de/projects/clojure/vimclojure.html
5"
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
10
11" Only load this indent file when no other was loaded.
12if exists("b:did_indent")
13 finish
14endif
15let b:did_indent = 1
16
17let s:save_cpo = &cpo
18set cpo&vim
19
20let b:undo_indent = 'setlocal autoindent< smartindent< lispwords< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys<'
21
22setlocal noautoindent nosmartindent
23setlocal softtabstop=2 shiftwidth=2 expandtab
24setlocal indentkeys=!,o,O
25
26if exists("*searchpairpos")
27
28 if !exists('g:clojure_maxlines')
29 let g:clojure_maxlines = 100
30 endif
31
32 if !exists('g:clojure_fuzzy_indent')
33 let g:clojure_fuzzy_indent = 1
34 endif
35
36 if !exists('g:clojure_fuzzy_indent_patterns')
37 let g:clojure_fuzzy_indent_patterns = ['^with', '^def', '^let']
38 endif
39
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
43
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
47
48 if !exists('g:clojure_align_multiline_strings')
49 let g:clojure_align_multiline_strings = 0
50 endif
51
52 function! s:SynIdName()
53 return synIDattr(synID(line("."), col("."), 0), "name")
54 endfunction
55
56 function! s:CurrentChar()
57 return getline('.')[col('.')-1]
58 endfunction
59
60 function! s:CurrentWord()
61 return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2]
62 endfunction
63
64 function! s:IsParen()
65 return s:CurrentChar() =~ '\v[\(\)\[\]\{\}]' &&
66 \ s:SynIdName() !~? '\vstring|comment'
67 endfunction
68
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
80
81 function! s:SavePosition()
82 let [ _b, l, c, _o ] = getpos(".")
83 let b = bufnr("%")
84 return [b, l, c]
85 endfunction
86
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
94
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
103
104 let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:IsParen()", stopat)
105 return [pos[0], virtcol(pos)]
106 endfunction
107
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)
115
116 if nb == 0
117 return -1
118 endif
119
120 call cursor(nb, 0)
121 call cursor(0, col("$") - 1)
122 if s:SynIdName() !~? "string"
123 return -1
124 endif
125
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
137
138 let p = searchpos('\(^\|[^\\]\)\zs"', 'bW')
139
140 if p != [0, 0]
141 return p[1] - 1
142 endif
143
144 return indent(".")
145 endfunction
146
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
156
157 function! s:ClojureIsMethodSpecialCaseWorker(position)
158 " Find the next enclosing form.
159 call search('\S', 'Wb')
160
161 " Special case: we are at a '(('.
162 if s:CurrentChar() == '('
163 return 0
164 endif
165 call cursor(a:position)
166
167 let nextParen = s:MatchPairs('(', ')', 0)
168
169 " Special case: we are now at toplevel.
170 if nextParen == [0, 0]
171 return 0
172 endif
173 call cursor(nextParen)
174
175 call search('\S', 'W')
176 if g:clojure_special_indent_words =~ '\<' . s:CurrentWord() . '\>'
177 return 1
178 endif
179
180 return 0
181 endfunction
182
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
192
193 function! GetClojureIndent()
194 " Get rid of special case.
195 if line(".") == 1
196 return 0
197 endif
198
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
205
206 call cursor(0, 1)
207
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])
214
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
223
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
229
230 " There are neither { nor [ nor (, ie. we are at the toplevel.
231 if paren == [0, 0]
232 return 0
233 endif
234
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)
249
250 if s:IsMethodSpecialCase(paren)
251 return paren[1] + &shiftwidth - 1
252 endif
253
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
258
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
264
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
270
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
277
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', '')
283
284 if &lispwords =~ '\V\<' . ww . '\>'
285 return paren[1] + &shiftwidth - 1
286 endif
287
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
293
294 normal! W
295 if paren[0] < line(".")
296 return paren[1] + &shiftwidth - 1
297 endif
298
299 normal! ge
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
315" Specially indented symbols from clojure.core and clojure.test.
316"
317" Clojure symbols are indented in the defn style when they:
318"
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
322"
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.
326
327" Definitions
328setlocal lispwords=
329setlocal lispwords+=bound-fn
330setlocal lispwords+=def
331setlocal lispwords+=definline
332setlocal lispwords+=definterface
333setlocal lispwords+=defmacro
334setlocal lispwords+=defmethod
335setlocal lispwords+=defmulti
336setlocal lispwords+=defn
337setlocal lispwords+=defn-
338setlocal lispwords+=defonce
339setlocal lispwords+=defprotocol
340setlocal lispwords+=defrecord
341setlocal lispwords+=defstruct
342setlocal lispwords+=deftest " clojure.test
343setlocal lispwords+=deftest- " clojure.test
344setlocal lispwords+=deftype
345setlocal lispwords+=extend
346setlocal lispwords+=extend-protocol
347setlocal lispwords+=extend-type
348setlocal lispwords+=fn
349setlocal lispwords+=ns
350setlocal lispwords+=proxy
351setlocal lispwords+=reify
352setlocal lispwords+=set-test " clojure.test
353
354" Binding forms
355setlocal lispwords+=as->
356setlocal lispwords+=binding
357setlocal lispwords+=doall
358setlocal lispwords+=dorun
359setlocal lispwords+=doseq
360setlocal lispwords+=dotimes
361setlocal lispwords+=doto
362setlocal lispwords+=for
363setlocal lispwords+=if-let
364setlocal lispwords+=let
365setlocal lispwords+=letfn
366setlocal lispwords+=locking
367setlocal lispwords+=loop
368setlocal lispwords+=testing " clojure.test
369setlocal lispwords+=when-first
370setlocal lispwords+=when-let
371setlocal lispwords+=with-bindings
372setlocal lispwords+=with-in-str
373setlocal lispwords+=with-local-vars
374setlocal lispwords+=with-open
375setlocal lispwords+=with-precision
376setlocal lispwords+=with-redefs
377setlocal lispwords+=with-redefs-fn
378setlocal lispwords+=with-test " clojure.test
379
380" Conditional branching
381setlocal lispwords+=case
382setlocal lispwords+=cond->
383setlocal lispwords+=cond->>
384setlocal lispwords+=condp
385setlocal lispwords+=if
386setlocal lispwords+=if-not
387setlocal lispwords+=when
388setlocal lispwords+=when-not
389setlocal lispwords+=while
390
391" Exception handling
392setlocal lispwords+=catch
393setlocal lispwords+=try " For aesthetics when enclosing single line
394
395let &cpo = s:save_cpo
396unlet! s:save_cpo
397
398" vim:sts=4 sw=4 et: