xref: /vim-8.2.3635/runtime/indent/clojure.vim (revision dfb18411)
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:	08 September 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	function! s:SynIdName()
57		return synIDattr(synID(line("."), col("."), 0), "name")
58	endfunction
59
60	function! s:CurrentChar()
61		return getline('.')[col('.')-1]
62	endfunction
63
64	function! s:CurrentWord()
65		return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2]
66	endfunction
67
68	function! s:IsParen()
69		return s:CurrentChar() =~ '\v[\(\)\[\]\{\}]' &&
70		     \ s:SynIdName() !~? '\vstring|regex|comment|character'
71	endfunction
72
73	" 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
84
85	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
93
94		let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:IsParen()", stopat)
95		return [pos[0], virtcol(pos)]
96	endfunction
97
98	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)
105
106		if nb == 0
107			return -1
108		endif
109
110		call cursor(nb, 0)
111		call cursor(0, col("$") - 1)
112		if s:SynIdName() !~? "string"
113			return -1
114		endif
115
116		" 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
127
128		let p = searchpos('\(^\|[^\\]\)\zs"', 'bW')
129
130		if p != [0, 0]
131			return p[1] - 1
132		endif
133
134		return indent(".")
135	endfunction
136
137	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
146
147	function! s:ClojureIsMethodSpecialCaseWorker(position)
148		" Find the next enclosing form.
149		call search('\S', 'Wb')
150
151		" Special case: we are at a '(('.
152		if s:CurrentChar() == '('
153			return 0
154		endif
155		call cursor(a:position)
156
157		let nextParen = s:MatchPairs('(', ')', 0)
158
159		" Special case: we are now at toplevel.
160		if nextParen == [0, 0]
161			return 0
162		endif
163		call cursor(nextParen)
164
165		call search('\S', 'W')
166		if g:clojure_special_indent_words =~ '\<' . s:CurrentWord() . '\>'
167			return 1
168		endif
169
170		return 0
171	endfunction
172
173	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
182
183	function! GetClojureIndent()
184		" Get rid of special case.
185		if line(".") == 1
186			return 0
187		endif
188
189		" 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
195
196		call cursor(0, 1)
197
198		" 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])
204
205		" 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
213
214		" 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
219
220		" There are neither { nor [ nor (, ie. we are at the toplevel.
221		if paren == [0, 0]
222			return 0
223		endif
224
225		" 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)
239
240		if s:IsMethodSpecialCase(paren)
241			return paren[1] + &shiftwidth - 1
242		endif
243
244		" 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
248
249		" 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
254
255		" 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
260
261		" 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
267
268		" 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', '')
273
274		if &lispwords =~ '\V\<' . ww . '\>'
275			return paren[1] + &shiftwidth - 1
276		endif
277
278		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
283
284		call search('\v\_s', 'cW')
285		call search('\v\S', 'W')
286		if paren[0] < line(".")
287			return paren[1] + &shiftwidth - 1
288		endif
289
290		call search('\v\S', 'bW')
291		return virtcol(".") + 1
292	endfunction
293
294	setlocal indentexpr=GetClojureIndent()
295
296else
297
298	" 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<'
303
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
384
385let &cpo = s:save_cpo
386unlet! s:save_cpo
387
388" vim:sts=8:sw=8:ts=8:noet
389