blob: 7d0c2aa91f13f8895c04d1824f08f1663f2a61fc [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
9" Last Change: 08 September 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 Moolenaarbaca7f72013-09-22 14:42:24 +020056 function! s:SynIdName()
57 return synIDattr(synID(line("."), col("."), 0), "name")
58 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010059
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020060 function! s:CurrentChar()
61 return getline('.')[col('.')-1]
62 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010063
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020064 function! s:CurrentWord()
65 return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2]
66 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010067
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020068 function! s:IsParen()
69 return s:CurrentChar() =~ '\v[\(\)\[\]\{\}]' &&
70 \ s:SynIdName() !~? '\vstring|regex|comment|character'
71 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010072
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020073 " Returns 1 if string matches a pattern in 'patterns', which may be a
74 " list of patterns, or a comma-delimited string of implicitly anchored
75 " patterns.
76 function! s:MatchesOne(patterns, string)
77 let list = type(a:patterns) == type([])
78 \ ? a:patterns
79 \ : map(split(a:patterns, ','), '"^" . v:val . "$"')
80 for pat in list
81 if a:string =~ pat | return 1 | endif
82 endfor
83 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010084
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020085 function! s:MatchPairs(open, close, stopat)
86 " Stop only on vector and map [ resp. {. Ignore the ones in strings and
87 " comments.
88 if a:stopat == 0
89 let stopat = max([line(".") - g:clojure_maxlines, 0])
90 else
91 let stopat = a:stopat
92 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010093
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020094 let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:IsParen()", stopat)
95 return [pos[0], virtcol(pos)]
96 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010097
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020098 function! s:ClojureCheckForStringWorker()
99 " Check whether there is the last character of the previous line is
100 " highlighted as a string. If so, we check whether it's a ". In this
101 " case we have to check also the previous character. The " might be the
102 " closing one. In case the we are still in the string, we search for the
103 " opening ". If this is not found we take the indent of the line.
104 let nb = prevnonblank(v:lnum - 1)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100105
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200106 if nb == 0
107 return -1
108 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100109
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200110 call cursor(nb, 0)
111 call cursor(0, col("$") - 1)
112 if s:SynIdName() !~? "string"
113 return -1
114 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100115
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200116 " This will not work for a " in the first column...
117 if s:CurrentChar() == '"'
118 call cursor(0, col("$") - 2)
119 if s:SynIdName() !~? "string"
120 return -1
121 endif
122 if s:CurrentChar() != '\\'
123 return -1
124 endif
125 call cursor(0, col("$") - 1)
126 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100127
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200128 let p = searchpos('\(^\|[^\\]\)\zs"', 'bW')
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100129
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200130 if p != [0, 0]
131 return p[1] - 1
132 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100133
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200134 return indent(".")
135 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100136
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200137 function! s:CheckForString()
138 let pos = getpos('.')
139 try
140 let val = s:ClojureCheckForStringWorker()
141 finally
142 call setpos('.', pos)
143 endtry
144 return val
145 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100146
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200147 function! s:ClojureIsMethodSpecialCaseWorker(position)
148 " Find the next enclosing form.
149 call search('\S', 'Wb')
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100150
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200151 " Special case: we are at a '(('.
152 if s:CurrentChar() == '('
153 return 0
154 endif
155 call cursor(a:position)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100156
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200157 let nextParen = s:MatchPairs('(', ')', 0)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100158
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200159 " Special case: we are now at toplevel.
160 if nextParen == [0, 0]
161 return 0
162 endif
163 call cursor(nextParen)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100164
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200165 call search('\S', 'W')
166 if g:clojure_special_indent_words =~ '\<' . s:CurrentWord() . '\>'
167 return 1
168 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100169
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200170 return 0
171 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100172
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200173 function! s:IsMethodSpecialCase(position)
174 let pos = getpos('.')
175 try
176 let val = s:ClojureIsMethodSpecialCaseWorker(a:position)
177 finally
178 call setpos('.', pos)
179 endtry
180 return val
181 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100182
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200183 function! GetClojureIndent()
184 " Get rid of special case.
185 if line(".") == 1
186 return 0
187 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100188
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200189 " We have to apply some heuristics here to figure out, whether to use
190 " normal lisp indenting or not.
191 let i = s:CheckForString()
192 if i > -1
193 return i + !!g:clojure_align_multiline_strings
194 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100195
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200196 call cursor(0, 1)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100197
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200198 " Find the next enclosing [ or {. We can limit the second search
199 " to the line, where the [ was found. If no [ was there this is
200 " zero and we search for an enclosing {.
201 let paren = s:MatchPairs('(', ')', 0)
202 let bracket = s:MatchPairs('\[', '\]', paren[0])
203 let curly = s:MatchPairs('{', '}', bracket[0])
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100204
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200205 " In case the curly brace is on a line later then the [ or - in
206 " case they are on the same line - in a higher column, we take the
207 " curly indent.
208 if curly[0] > bracket[0] || curly[1] > bracket[1]
209 if curly[0] > paren[0] || curly[1] > paren[1]
210 return curly[1]
211 endif
212 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100213
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200214 " If the curly was not chosen, we take the bracket indent - if
215 " there was one.
216 if bracket[0] > paren[0] || bracket[1] > paren[1]
217 return bracket[1]
218 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100219
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200220 " There are neither { nor [ nor (, ie. we are at the toplevel.
221 if paren == [0, 0]
222 return 0
223 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100224
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200225 " Now we have to reimplement lispindent. This is surprisingly easy, as
226 " soon as one has access to syntax items.
227 "
228 " - Check whether we are in a special position after a word in
229 " g:clojure_special_indent_words. These are special cases.
230 " - Get the next keyword after the (.
231 " - If its first character is also a (, we have another sexp and align
232 " one column to the right of the unmatched (.
233 " - In case it is in lispwords, we indent the next line to the column of
234 " the ( + sw.
235 " - If not, we check whether it is last word in the line. In that case
236 " we again use ( + sw for indent.
237 " - In any other case we use the column of the end of the word + 2.
238 call cursor(paren)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100239
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200240 if s:IsMethodSpecialCase(paren)
241 return paren[1] + &shiftwidth - 1
242 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100243
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200244 " In case we are at the last character, we use the paren position.
245 if col("$") - 1 == paren[1]
246 return paren[1]
247 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100248
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200249 " In case after the paren is a whitespace, we search for the next word.
250 call cursor(0, col('.') + 1)
251 if s:CurrentChar() == ' '
252 call search('\v\S', 'W')
253 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100254
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200255 " If we moved to another line, there is no word after the (. We
256 " use the ( position for indent.
257 if line(".") > paren[0]
258 return paren[1]
259 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100260
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200261 " We still have to check, whether the keyword starts with a (, [ or {.
262 " In that case we use the ( position for indent.
263 let w = s:CurrentWord()
264 if stridx('([{', w[0]) > -1
265 return paren[1]
266 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100267
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200268 " Test words without namespace qualifiers and leading reader macro
269 " metacharacters.
270 "
271 " e.g. clojure.core/defn and #'defn should both indent like defn.
272 let ww = substitute(w, "\\v%(.*/|[#'`~@^,]*)(.*)", '\1', '')
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100273
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200274 if &lispwords =~ '\V\<' . ww . '\>'
275 return paren[1] + &shiftwidth - 1
276 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100277
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200278 if g:clojure_fuzzy_indent
279 \ && !s:MatchesOne(g:clojure_fuzzy_indent_blacklist, ww)
280 \ && s:MatchesOne(g:clojure_fuzzy_indent_patterns, ww)
281 return paren[1] + &shiftwidth - 1
282 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100283
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200284 call search('\v\_s', 'cW')
285 call search('\v\S', 'W')
286 if paren[0] < line(".")
287 return paren[1] + &shiftwidth - 1
288 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100289
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200290 call search('\v\S', 'bW')
291 return virtcol(".") + 1
292 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100293
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200294 setlocal indentexpr=GetClojureIndent()
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100295
296else
297
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200298 " In case we have searchpairpos not available we fall back to
299 " normal lisp indenting.
300 setlocal indentexpr=
301 setlocal lisp
302 let b:undo_indent .= '| setlocal lisp<'
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100303
304endif
305
306" Specially indented symbols from clojure.core and clojure.test.
307"
308" Clojure symbols are indented in the defn style when they:
309"
310" * Define vars and anonymous functions
311" * Create new lexical scopes or scopes with altered environments
312" * Create conditional branches from a predicate function or value
313"
314" The arglists for these functions are generally in the form of [x & body];
315" Functions that accept a flat list of forms do not treat the first argument
316" specially and hence are not indented specially.
317
318" Definitions
319setlocal lispwords=
320setlocal lispwords+=bound-fn
321setlocal lispwords+=def
322setlocal lispwords+=definline
323setlocal lispwords+=definterface
324setlocal lispwords+=defmacro
325setlocal lispwords+=defmethod
326setlocal lispwords+=defmulti
327setlocal lispwords+=defn
328setlocal lispwords+=defn-
329setlocal lispwords+=defonce
330setlocal lispwords+=defprotocol
331setlocal lispwords+=defrecord
332setlocal lispwords+=defstruct
333setlocal lispwords+=deftest " clojure.test
334setlocal lispwords+=deftest- " clojure.test
335setlocal lispwords+=deftype
336setlocal lispwords+=extend
337setlocal lispwords+=extend-protocol
338setlocal lispwords+=extend-type
339setlocal lispwords+=fn
340setlocal lispwords+=ns
341setlocal lispwords+=proxy
342setlocal lispwords+=reify
343setlocal lispwords+=set-test " clojure.test
344
345" Binding forms
346setlocal lispwords+=as->
347setlocal lispwords+=binding
348setlocal lispwords+=doall
349setlocal lispwords+=dorun
350setlocal lispwords+=doseq
351setlocal lispwords+=dotimes
352setlocal lispwords+=doto
353setlocal lispwords+=for
354setlocal lispwords+=if-let
355setlocal lispwords+=let
356setlocal lispwords+=letfn
357setlocal lispwords+=locking
358setlocal lispwords+=loop
359setlocal lispwords+=testing " clojure.test
360setlocal lispwords+=when-first
361setlocal lispwords+=when-let
362setlocal lispwords+=with-bindings
363setlocal lispwords+=with-in-str
364setlocal lispwords+=with-local-vars
365setlocal lispwords+=with-open
366setlocal lispwords+=with-precision
367setlocal lispwords+=with-redefs
368setlocal lispwords+=with-redefs-fn
369setlocal lispwords+=with-test " clojure.test
370
371" Conditional branching
372setlocal lispwords+=case
373setlocal lispwords+=cond->
374setlocal lispwords+=cond->>
375setlocal lispwords+=condp
376setlocal lispwords+=if
377setlocal lispwords+=if-not
378setlocal lispwords+=when
379setlocal lispwords+=when-not
380setlocal lispwords+=while
381
382" Exception handling
383setlocal lispwords+=catch
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100384
385let &cpo = s:save_cpo
386unlet! s:save_cpo
387
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200388" vim:sts=8:sw=8:ts=8:noet