blob: 476ac1de1c218d61fb2107d3337d442e15e5b6ce [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 Moolenaar76f3b1a2014-03-27 22:30:07 +01009" Last Change: 27 March 2014
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
Bram Moolenaara6878372014-03-22 21:02:50 +010024let b:undo_indent = 'setlocal autoindent< smartindent< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys<'
Bram Moolenaarfa13eef2013-02-06 17:34:04 +010025
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()
Bram Moolenaar76f3b1a2014-03-27 22:30:07 +010073 return s:CurrentChar() =~# '\v[\(\)\[\]\{\}]' &&
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020074 \ 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
Bram Moolenaar76f3b1a2014-03-27 22:30:07 +010085 if a:string =~# pat | return 1 | endif
Bram Moolenaarbaca7f72013-09-22 14:42:24 +020086 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 Moolenaar76f3b1a2014-03-27 22:30:07 +0100151 function! s:StripNamespaceAndMacroChars(word)
152 return substitute(a:word, "\\v%(.*/|[#'`~@^,]*)(.*)", '\1', '')
153 endfunction
154
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200155 function! s:ClojureIsMethodSpecialCaseWorker(position)
156 " Find the next enclosing form.
157 call search('\S', 'Wb')
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100158
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200159 " Special case: we are at a '(('.
160 if s:CurrentChar() == '('
161 return 0
162 endif
163 call cursor(a:position)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100164
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200165 let nextParen = s:MatchPairs('(', ')', 0)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100166
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200167 " Special case: we are now at toplevel.
168 if nextParen == [0, 0]
169 return 0
170 endif
171 call cursor(nextParen)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100172
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200173 call search('\S', 'W')
Bram Moolenaar76f3b1a2014-03-27 22:30:07 +0100174 let w = s:StripNamespaceAndMacroChars(s:CurrentWord())
175 if g:clojure_special_indent_words =~# '\V\<' . w . '\>'
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200176 return 1
177 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100178
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200179 return 0
180 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100181
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200182 function! s:IsMethodSpecialCase(position)
183 let pos = getpos('.')
184 try
185 let val = s:ClojureIsMethodSpecialCaseWorker(a:position)
186 finally
187 call setpos('.', pos)
188 endtry
189 return val
190 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100191
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200192 function! GetClojureIndent()
193 " Get rid of special case.
194 if line(".") == 1
195 return 0
196 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100197
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200198 " We have to apply some heuristics here to figure out, whether to use
199 " normal lisp indenting or not.
200 let i = s:CheckForString()
201 if i > -1
202 return i + !!g:clojure_align_multiline_strings
203 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100204
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200205 call cursor(0, 1)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100206
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200207 " Find the next enclosing [ or {. We can limit the second search
208 " to the line, where the [ was found. If no [ was there this is
209 " zero and we search for an enclosing {.
210 let paren = s:MatchPairs('(', ')', 0)
211 let bracket = s:MatchPairs('\[', '\]', paren[0])
212 let curly = s:MatchPairs('{', '}', bracket[0])
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100213
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200214 " In case the curly brace is on a line later then the [ or - in
215 " case they are on the same line - in a higher column, we take the
216 " curly indent.
217 if curly[0] > bracket[0] || curly[1] > bracket[1]
218 if curly[0] > paren[0] || curly[1] > paren[1]
219 return curly[1]
220 endif
221 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100222
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200223 " If the curly was not chosen, we take the bracket indent - if
224 " there was one.
225 if bracket[0] > paren[0] || bracket[1] > paren[1]
226 return bracket[1]
227 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100228
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200229 " There are neither { nor [ nor (, ie. we are at the toplevel.
230 if paren == [0, 0]
231 return 0
232 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100233
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200234 " Now we have to reimplement lispindent. This is surprisingly easy, as
235 " soon as one has access to syntax items.
236 "
237 " - Check whether we are in a special position after a word in
238 " g:clojure_special_indent_words. These are special cases.
239 " - Get the next keyword after the (.
240 " - If its first character is also a (, we have another sexp and align
241 " one column to the right of the unmatched (.
242 " - In case it is in lispwords, we indent the next line to the column of
243 " the ( + sw.
244 " - If not, we check whether it is last word in the line. In that case
245 " we again use ( + sw for indent.
246 " - In any other case we use the column of the end of the word + 2.
247 call cursor(paren)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100248
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200249 if s:IsMethodSpecialCase(paren)
250 return paren[1] + &shiftwidth - 1
251 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100252
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200253 " In case we are at the last character, we use the paren position.
254 if col("$") - 1 == paren[1]
255 return paren[1]
256 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100257
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200258 " In case after the paren is a whitespace, we search for the next word.
259 call cursor(0, col('.') + 1)
260 if s:CurrentChar() == ' '
261 call search('\v\S', 'W')
262 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100263
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200264 " If we moved to another line, there is no word after the (. We
265 " use the ( position for indent.
266 if line(".") > paren[0]
267 return paren[1]
268 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100269
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200270 " We still have to check, whether the keyword starts with a (, [ or {.
271 " In that case we use the ( position for indent.
272 let w = s:CurrentWord()
273 if stridx('([{', w[0]) > -1
274 return paren[1]
275 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100276
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200277 " Test words without namespace qualifiers and leading reader macro
278 " metacharacters.
279 "
280 " e.g. clojure.core/defn and #'defn should both indent like defn.
Bram Moolenaar76f3b1a2014-03-27 22:30:07 +0100281 let ww = s:StripNamespaceAndMacroChars(w)
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100282
Bram Moolenaar76f3b1a2014-03-27 22:30:07 +0100283 if &lispwords =~# '\V\<' . ww . '\>'
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200284 return paren[1] + &shiftwidth - 1
285 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100286
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200287 if g:clojure_fuzzy_indent
288 \ && !s:MatchesOne(g:clojure_fuzzy_indent_blacklist, ww)
289 \ && s:MatchesOne(g:clojure_fuzzy_indent_patterns, ww)
290 return paren[1] + &shiftwidth - 1
291 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100292
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200293 call search('\v\_s', 'cW')
294 call search('\v\S', 'W')
295 if paren[0] < line(".")
Bram Moolenaar438f67a2014-01-07 06:09:28 +0100296 return paren[1] + (g:clojure_align_subforms ? 0 : &shiftwidth - 1)
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200297 endif
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100298
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200299 call search('\v\S', 'bW')
300 return virtcol(".") + 1
301 endfunction
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100302
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200303 setlocal indentexpr=GetClojureIndent()
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100304
305else
306
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200307 " 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<'
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100312
313endif
314
Bram Moolenaarfa13eef2013-02-06 17:34:04 +0100315let &cpo = s:save_cpo
316unlet! s:save_cpo
317
Bram Moolenaarbaca7f72013-09-22 14:42:24 +0200318" vim:sts=8:sw=8:ts=8:noet