4098
|
1 " 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.
|
|
12 if exists("b:did_indent")
|
|
13 finish
|
|
14 endif
|
|
15 let b:did_indent = 1
|
|
16
|
|
17 let s:save_cpo = &cpo
|
|
18 set cpo&vim
|
|
19
|
|
20 let b:undo_indent = 'setlocal autoindent< smartindent< lispwords< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys<'
|
|
21
|
|
22 setlocal noautoindent nosmartindent
|
|
23 setlocal softtabstop=2 shiftwidth=2 expandtab
|
|
24 setlocal indentkeys=!,o,O
|
|
25
|
|
26 if 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
|
|
305 else
|
|
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
|
|
313 endif
|
|
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
|
|
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
|
|
353
|
|
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
|
|
379
|
|
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
|
|
390
|
|
391 " Exception handling
|
|
392 setlocal lispwords+=catch
|
|
393 setlocal lispwords+=try " For aesthetics when enclosing single line
|
|
394
|
|
395 let &cpo = s:save_cpo
|
|
396 unlet! s:save_cpo
|
|
397
|
|
398 " vim:sts=4 sw=4 et:
|