xref: /vim-8.2.3635/runtime/indent/clojure.vim (revision 36e294c0)
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:	27 March 2014
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< 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:StripNamespaceAndMacroChars(word)
152		return substitute(a:word, "\\v%(.*/|[#'`~@^,]*)(.*)", '\1', '')
153	endfunction
154
155	function! s:ClojureIsMethodSpecialCaseWorker(position)
156		" Find the next enclosing form.
157		call search('\S', 'Wb')
158
159		" Special case: we are at a '(('.
160		if s:CurrentChar() == '('
161			return 0
162		endif
163		call cursor(a:position)
164
165		let nextParen = s:MatchPairs('(', ')', 0)
166
167		" Special case: we are now at toplevel.
168		if nextParen == [0, 0]
169			return 0
170		endif
171		call cursor(nextParen)
172
173		call search('\S', 'W')
174		let w = s:StripNamespaceAndMacroChars(s:CurrentWord())
175		if g:clojure_special_indent_words =~# '\V\<' . w . '\>'
176			return 1
177		endif
178
179		return 0
180	endfunction
181
182	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
191
192	function! GetClojureIndent()
193		" Get rid of special case.
194		if line(".") == 1
195			return 0
196		endif
197
198		" 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
204
205		call cursor(0, 1)
206
207		" 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])
213
214		" 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
222
223		" 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
228
229		" There are neither { nor [ nor (, ie. we are at the toplevel.
230		if paren == [0, 0]
231			return 0
232		endif
233
234		" 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)
248
249		if s:IsMethodSpecialCase(paren)
250			return paren[1] + &shiftwidth - 1
251		endif
252
253		" 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
257
258		" 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
263
264		" 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
269
270		" 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
276
277		" 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.
281		let ww = s:StripNamespaceAndMacroChars(w)
282
283		if &lispwords =~# '\V\<' . ww . '\>'
284			return paren[1] + &shiftwidth - 1
285		endif
286
287		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
292
293		call search('\v\_s', 'cW')
294		call search('\v\S', 'W')
295		if paren[0] < line(".")
296			return paren[1] + (g:clojure_align_subforms ? 0 : &shiftwidth - 1)
297		endif
298
299		call search('\v\S', 'bW')
300		return virtcol(".") + 1
301	endfunction
302
303	setlocal indentexpr=GetClojureIndent()
304
305else
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
313endif
314
315let &cpo = s:save_cpo
316unlet! s:save_cpo
317
318" vim:sts=8:sw=8:ts=8:noet
319