blob: df88cea94245d874668e6be5472091530deaf734 [file] [log] [blame]
Bram Moolenaarfa13eef2013-02-06 17:34:04 +01001" Vim indent file
Bram Moolenaarbaca7f72013-09-22 14:42:24 +02002" Language: Clojure
3" Author: Meikel Brandmeyer <mb@kotka.de>
4" URL: http://kotka.de/projects/clojure/vimclojure.html
Bram Moolenaarfa13eef2013-02-06 17:34:04 +01005"
Bram Moolenaarbaca7f72013-09-22 14:42:24 +02006" Maintainer: Sung Pae <self@sungpae.com>
7" URL: https://github.com/guns/vim-clojure-static
8" License: Same as Vim
Bram Moolenaar438f67a2014-01-07 06:09:28 +01009" Last Change: 16 December 2013
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010010
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020011" TODO: Indenting after multibyte characters is broken:
12" (let [Δ (if foo
13" bar ; Indent error
14" baz)])
15
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010016if exists("b:did_indent")
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020017 finish
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010018endif
19let b:did_indent = 1
20
21let s:save_cpo = &cpo
22set cpo&vim
23
24let b:undo_indent = 'setlocal autoindent< smartindent< lispwords< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys<'
25
26setlocal noautoindent nosmartindent
27setlocal softtabstop=2 shiftwidth=2 expandtab
28setlocal indentkeys=!,o,O
29
30if exists("*searchpairpos")
31
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020032 if !exists('g:clojure_maxlines')
33 let g:clojure_maxlines = 100
34 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010035
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020036 if !exists('g:clojure_fuzzy_indent')
37 let g:clojure_fuzzy_indent = 1
38 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010039
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020040 if !exists('g:clojure_fuzzy_indent_patterns')
41 let g:clojure_fuzzy_indent_patterns = ['^with', '^def', '^let']
42 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010043
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020044 if !exists('g:clojure_fuzzy_indent_blacklist')
45 let g:clojure_fuzzy_indent_blacklist = ['-fn$', '\v^with-%(meta|out-str|loading-context)$']
46 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010047
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020048 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
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010051
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020052 if !exists('g:clojure_align_multiline_strings')
53 let g:clojure_align_multiline_strings = 0
54 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010055
Bram Moolenaar438f67a2014-01-07 06:09:28 +010056 if !exists('g:clojure_align_subforms')
57 let g:clojure_align_subforms = 0
58 endif
59
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020060 function! s:SynIdName()
61 return synIDattr(synID(line("."), col("."), 0), "name")
62 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010063
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020064 function! s:CurrentChar()
65 return getline('.')[col('.')-1]
66 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010067
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020068 function! s:CurrentWord()
69 return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2]
70 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010071
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020072 function! s:IsParen()
73 return s:CurrentChar() =~ '\v[\(\)\[\]\{\}]' &&
74 \ s:SynIdName() !~? '\vstring|regex|comment|character'
75 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010076
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020077 " 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
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010088
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020089 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
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010097
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020098 let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:IsParen()", stopat)
99 return [pos[0], virtcol(pos)]
100 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100101
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200102 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)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100109
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200110 if nb == 0
111 return -1
112 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100113
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200114 call cursor(nb, 0)
115 call cursor(0, col("$") - 1)
116 if s:SynIdName() !~? "string"
117 return -1
118 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100119
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200120 " 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
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100131
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200132 let p = searchpos('\(^\|[^\\]\)\zs"', 'bW')
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100133
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200134 if p != [0, 0]
135 return p[1] - 1
136 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100137
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200138 return indent(".")
139 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100140
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200141 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
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100150
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200151 function! s:ClojureIsMethodSpecialCaseWorker(position)
152 " Find the next enclosing form.
153 call search('\S', 'Wb')
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100154
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200155 " Special case: we are at a '(('.
156 if s:CurrentChar() == '('
157 return 0
158 endif
159 call cursor(a:position)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100160
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200161 let nextParen = s:MatchPairs('(', ')', 0)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100162
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200163 " Special case: we are now at toplevel.
164 if nextParen == [0, 0]
165 return 0
166 endif
167 call cursor(nextParen)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100168
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200169 call search('\S', 'W')
170 if g:clojure_special_indent_words =~ '\<' . s:CurrentWord() . '\>'
171 return 1
172 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100173
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200174 return 0
175 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100176
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200177 function! s:IsMethodSpecialCase(position)
178 let pos = getpos('.')
179 try
180 let val = s:ClojureIsMethodSpecialCaseWorker(a:position)
181 finally
182 call setpos('.', pos)
183 endtry
184 return val
185 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100186
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200187 function! GetClojureIndent()
188 " Get rid of special case.
189 if line(".") == 1
190 return 0
191 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100192
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200193 " We have to apply some heuristics here to figure out, whether to use
194 " normal lisp indenting or not.
195 let i = s:CheckForString()
196 if i > -1
197 return i + !!g:clojure_align_multiline_strings
198 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100199
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200200 call cursor(0, 1)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100201
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200202 " Find the next enclosing [ or {. We can limit the second search
203 " to the line, where the [ was found. If no [ was there this is
204 " zero and we search for an enclosing {.
205 let paren = s:MatchPairs('(', ')', 0)
206 let bracket = s:MatchPairs('\[', '\]', paren[0])
207 let curly = s:MatchPairs('{', '}', bracket[0])
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100208
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200209 " In case the curly brace is on a line later then the [ or - in
210 " case they are on the same line - in a higher column, we take the
211 " curly indent.
212 if curly[0] > bracket[0] || curly[1] > bracket[1]
213 if curly[0] > paren[0] || curly[1] > paren[1]
214 return curly[1]
215 endif
216 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100217
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200218 " If the curly was not chosen, we take the bracket indent - if
219 " there was one.
220 if bracket[0] > paren[0] || bracket[1] > paren[1]
221 return bracket[1]
222 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100223
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200224 " There are neither { nor [ nor (, ie. we are at the toplevel.
225 if paren == [0, 0]
226 return 0
227 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100228
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200229 " Now we have to reimplement lispindent. This is surprisingly easy, as
230 " soon as one has access to syntax items.
231 "
232 " - Check whether we are in a special position after a word in
233 " g:clojure_special_indent_words. These are special cases.
234 " - Get the next keyword after the (.
235 " - If its first character is also a (, we have another sexp and align
236 " one column to the right of the unmatched (.
237 " - In case it is in lispwords, we indent the next line to the column of
238 " the ( + sw.
239 " - If not, we check whether it is last word in the line. In that case
240 " we again use ( + sw for indent.
241 " - In any other case we use the column of the end of the word + 2.
242 call cursor(paren)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100243
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200244 if s:IsMethodSpecialCase(paren)
245 return paren[1] + &shiftwidth - 1
246 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100247
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200248 " In case we are at the last character, we use the paren position.
249 if col("$") - 1 == paren[1]
250 return paren[1]
251 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100252
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200253 " In case after the paren is a whitespace, we search for the next word.
254 call cursor(0, col('.') + 1)
255 if s:CurrentChar() == ' '
256 call search('\v\S', 'W')
257 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100258
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200259 " If we moved to another line, there is no word after the (. We
260 " use the ( position for indent.
261 if line(".") > paren[0]
262 return paren[1]
263 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100264
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200265 " We still have to check, whether the keyword starts with a (, [ or {.
266 " In that case we use the ( position for indent.
267 let w = s:CurrentWord()
268 if stridx('([{', w[0]) > -1
269 return paren[1]
270 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100271
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200272 " Test words without namespace qualifiers and leading reader macro
273 " metacharacters.
274 "
275 " e.g. clojure.core/defn and #'defn should both indent like defn.
276 let ww = substitute(w, "\\v%(.*/|[#'`~@^,]*)(.*)", '\1', '')
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100277
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200278 if &lispwords =~ '\V\<' . ww . '\>'
279 return paren[1] + &shiftwidth - 1
280 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100281
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200282 if g:clojure_fuzzy_indent
283 \ && !s:MatchesOne(g:clojure_fuzzy_indent_blacklist, ww)
284 \ && s:MatchesOne(g:clojure_fuzzy_indent_patterns, ww)
285 return paren[1] + &shiftwidth - 1
286 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100287
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200288 call search('\v\_s', 'cW')
289 call search('\v\S', 'W')
290 if paren[0] < line(".")
Bram Moolenaar438f67a2014-01-07 06:09:28 +0100291 return paren[1] + (g:clojure_align_subforms ? 0 : &shiftwidth - 1)
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200292 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100293
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200294 call search('\v\S', 'bW')
295 return virtcol(".") + 1
296 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100297
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200298 setlocal indentexpr=GetClojureIndent()
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100299
300else
301
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200302 " In case we have searchpairpos not available we fall back to
303 " normal lisp indenting.
304 setlocal indentexpr=
305 setlocal lisp
306 let b:undo_indent .= '| setlocal lisp<'
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100307
308endif
309
310" Specially indented symbols from clojure.core and clojure.test.
311"
312" Clojure symbols are indented in the defn style when they:
313"
314" * Define vars and anonymous functions
315" * Create new lexical scopes or scopes with altered environments
316" * Create conditional branches from a predicate function or value
317"
318" The arglists for these functions are generally in the form of [x & body];
319" Functions that accept a flat list of forms do not treat the first argument
320" specially and hence are not indented specially.
321
322" Definitions
323setlocal lispwords=
324setlocal lispwords+=bound-fn
325setlocal lispwords+=def
326setlocal lispwords+=definline
327setlocal lispwords+=definterface
328setlocal lispwords+=defmacro
329setlocal lispwords+=defmethod
330setlocal lispwords+=defmulti
331setlocal lispwords+=defn
332setlocal lispwords+=defn-
333setlocal lispwords+=defonce
334setlocal lispwords+=defprotocol
335setlocal lispwords+=defrecord
336setlocal lispwords+=defstruct
337setlocal lispwords+=deftest " clojure.test
338setlocal lispwords+=deftest- " clojure.test
339setlocal lispwords+=deftype
340setlocal lispwords+=extend
341setlocal lispwords+=extend-protocol
342setlocal lispwords+=extend-type
343setlocal lispwords+=fn
344setlocal lispwords+=ns
345setlocal lispwords+=proxy
346setlocal lispwords+=reify
347setlocal lispwords+=set-test " clojure.test
348
349" Binding forms
350setlocal lispwords+=as->
351setlocal lispwords+=binding
352setlocal lispwords+=doall
353setlocal lispwords+=dorun
354setlocal lispwords+=doseq
355setlocal lispwords+=dotimes
356setlocal lispwords+=doto
357setlocal lispwords+=for
358setlocal lispwords+=if-let
359setlocal lispwords+=let
360setlocal lispwords+=letfn
361setlocal lispwords+=locking
362setlocal lispwords+=loop
363setlocal lispwords+=testing " clojure.test
364setlocal lispwords+=when-first
365setlocal lispwords+=when-let
366setlocal lispwords+=with-bindings
367setlocal lispwords+=with-in-str
368setlocal lispwords+=with-local-vars
369setlocal lispwords+=with-open
370setlocal lispwords+=with-precision
371setlocal lispwords+=with-redefs
372setlocal lispwords+=with-redefs-fn
373setlocal lispwords+=with-test " clojure.test
374
375" Conditional branching
376setlocal lispwords+=case
377setlocal lispwords+=cond->
378setlocal lispwords+=cond->>
379setlocal lispwords+=condp
380setlocal lispwords+=if
381setlocal lispwords+=if-not
382setlocal lispwords+=when
383setlocal lispwords+=when-not
384setlocal lispwords+=while
385
386" Exception handling
387setlocal lispwords+=catch
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100388
389let &cpo = s:save_cpo
390unlet! s:save_cpo
391
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200392" vim:sts=8:sw=8:ts=8:noet