xref: /vim-8.2.3635/runtime/indent/clojure.vim (revision 438f67a0)
1" Vim indent file
2" Language:	Clojure
3" Author:	Meikel Brandmeyer <[email protected]>
4" URL:		http://kotka.de/projects/clojure/vimclojure.html
5"
6" Maintainer:	Sung Pae <[email protected]>
7" URL:		https://github.com/guns/vim-clojure-static
8" License:	Same as Vim
9" Last Change:	16 December 2013
10
11" TODO: Indenting after multibyte characters is broken:
12"       (let [Δ (if foo
13"                bar    ; Indent error
14"                baz)])
15
16if exists("b:did_indent")
17	finish
18endif
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
32	if !exists('g:clojure_maxlines')
33		let g:clojure_maxlines = 100
34	endif
35
36	if !exists('g:clojure_fuzzy_indent')
37		let g:clojure_fuzzy_indent = 1
38	endif
39
40	if !exists('g:clojure_fuzzy_indent_patterns')
41		let g:clojure_fuzzy_indent_patterns = ['^with', '^def', '^let']
42	endif
43
44	if !exists('g:clojure_fuzzy_indent_blacklist')
45		let g:clojure_fuzzy_indent_blacklist = ['-fn$', '\v^with-%(meta|out-str|loading-context)$']
46	endif
47
48	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
51
52	if !exists('g:clojure_align_multiline_strings')
53		let g:clojure_align_multiline_strings = 0
54	endif
55
56	if !exists('g:clojure_align_subforms')
57		let g:clojure_align_subforms = 0
58	endif
59
60	function! s:SynIdName()
61		return synIDattr(synID(line("."), col("."), 0), "name")
62	endfunction
63
64	function! s:CurrentChar()
65		return getline('.')[col('.')-1]
66	endfunction
67
68	function! s:CurrentWord()
69		return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2]
70	endfunction
71
72	function! s:IsParen()
73		return s:CurrentChar() =~ '\v[\(\)\[\]\{\}]' &&
74		     \ s:SynIdName() !~? '\vstring|regex|comment|character'
75	endfunction
76
77	" 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
88
89	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
97
98		let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:IsParen()", stopat)
99		return [pos[0], virtcol(pos)]
100	endfunction
101
102	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)
109
110		if nb == 0
111			return -1
112		endif
113
114		call cursor(nb, 0)
115		call cursor(0, col("$") - 1)
116		if s:SynIdName() !~? "string"
117			return -1
118		endif
119
120		" 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
131
132		let p = searchpos('\(^\|[^\\]\)\zs"', 'bW')
133
134		if p != [0, 0]
135			return p[1] - 1
136		endif
137
138		return indent(".")
139	endfunction
140
141	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
150
151	function! s:ClojureIsMethodSpecialCaseWorker(position)
152		" Find the next enclosing form.
153		call search('\S', 'Wb')
154
155		" Special case: we are at a '(('.
156		if s:CurrentChar() == '('
157			return 0
158		endif
159		call cursor(a:position)
160
161		let nextParen = s:MatchPairs('(', ')', 0)
162
163		" Special case: we are now at toplevel.
164		if nextParen == [0, 0]
165			return 0
166		endif
167		call cursor(nextParen)
168
169		call search('\S', 'W')
170		if g:clojure_special_indent_words =~ '\<' . s:CurrentWord() . '\>'
171			return 1
172		endif
173
174		return 0
175	endfunction
176
177	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
186
187	function! GetClojureIndent()
188		" Get rid of special case.
189		if line(".") == 1
190			return 0
191		endif
192
193		" 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
199
200		call cursor(0, 1)
201
202		" 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])
208
209		" 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
217
218		" 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
223
224		" There are neither { nor [ nor (, ie. we are at the toplevel.
225		if paren == [0, 0]
226			return 0
227		endif
228
229		" 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)
243
244		if s:IsMethodSpecialCase(paren)
245			return paren[1] + &shiftwidth - 1
246		endif
247
248		" 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
252
253		" 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
258
259		" 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
264
265		" 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
271
272		" 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', '')
277
278		if &lispwords =~ '\V\<' . ww . '\>'
279			return paren[1] + &shiftwidth - 1
280		endif
281
282		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
287
288		call search('\v\_s', 'cW')
289		call search('\v\S', 'W')
290		if paren[0] < line(".")
291			return paren[1] + (g:clojure_align_subforms ? 0 : &shiftwidth - 1)
292		endif
293
294		call search('\v\S', 'bW')
295		return virtcol(".") + 1
296	endfunction
297
298	setlocal indentexpr=GetClojureIndent()
299
300else
301
302	" 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<'
307
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
388
389let &cpo = s:save_cpo
390unlet! s:save_cpo
391
392" vim:sts=8:sw=8:ts=8:noet
393