xref: /vim-8.2.3635/runtime/indent/clojure.vim (revision fa13eef3)
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:  30 January 2013
10
11" Only load this indent file when no other was loaded.
12if exists("b:did_indent")
13    finish
14endif
15let b:did_indent = 1
16
17let s:save_cpo = &cpo
18set cpo&vim
19
20let b:undo_indent = 'setlocal autoindent< smartindent< lispwords< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys<'
21
22setlocal noautoindent nosmartindent
23setlocal softtabstop=2 shiftwidth=2 expandtab
24setlocal indentkeys=!,o,O
25
26if exists("*searchpairpos")
27
28    if !exists('g:clojure_maxlines')
29        let g:clojure_maxlines = 100
30    endif
31
32    if !exists('g:clojure_fuzzy_indent')
33        let g:clojure_fuzzy_indent = 1
34    endif
35
36    if !exists('g:clojure_fuzzy_indent_patterns')
37        let g:clojure_fuzzy_indent_patterns = ['^with', '^def', '^let']
38    endif
39
40    if !exists('g:clojure_fuzzy_indent_blacklist')
41        let g:clojure_fuzzy_indent_blacklist = ['-fn$', '\v^with-%(meta|out-str|loading-context)$']
42    endif
43
44    if !exists('g:clojure_special_indent_words')
45        let g:clojure_special_indent_words = 'deftype,defrecord,reify,proxy,extend-type,extend-protocol,letfn'
46    endif
47
48    if !exists('g:clojure_align_multiline_strings')
49        let g:clojure_align_multiline_strings = 0
50    endif
51
52    function! s:SynIdName()
53        return synIDattr(synID(line("."), col("."), 0), "name")
54    endfunction
55
56    function! s:CurrentChar()
57        return getline('.')[col('.')-1]
58    endfunction
59
60    function! s:CurrentWord()
61        return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2]
62    endfunction
63
64    function! s:IsParen()
65        return s:CurrentChar() =~ '\v[\(\)\[\]\{\}]' &&
66             \ s:SynIdName() !~? '\vstring|comment'
67    endfunction
68
69    " Returns 1 if string matches a pattern in 'patterns', which may be a
70    " list of patterns, or a comma-delimited string of implicitly anchored
71    " patterns.
72    function! s:MatchesOne(patterns, string)
73        let list = type(a:patterns) == type([])
74                   \ ? a:patterns
75                   \ : map(split(a:patterns, ','), '"^" . v:val . "$"')
76        for pat in list
77            if a:string =~ pat | return 1 | endif
78        endfor
79    endfunction
80
81    function! s:SavePosition()
82        let [ _b, l, c, _o ] = getpos(".")
83        let b = bufnr("%")
84        return [b, l, c]
85    endfunction
86
87    function! s:RestorePosition(value)
88        let [b, l, c] = a:value
89        if bufnr("%") != b
90            execute b "buffer!"
91        endif
92        call setpos(".", [0, l, c, 0])
93    endfunction
94
95    function! s:MatchPairs(open, close, stopat)
96        " Stop only on vector and map [ resp. {. Ignore the ones in strings and
97        " comments.
98        if a:stopat == 0
99            let stopat = max([line(".") - g:clojure_maxlines, 0])
100        else
101            let stopat = a:stopat
102        endif
103
104        let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:IsParen()", stopat)
105        return [pos[0], virtcol(pos)]
106    endfunction
107
108    function! s:ClojureCheckForStringWorker()
109        " Check whether there is the last character of the previous line is
110        " highlighted as a string. If so, we check whether it's a ". In this
111        " case we have to check also the previous character. The " might be the
112        " closing one. In case the we are still in the string, we search for the
113        " opening ". If this is not found we take the indent of the line.
114        let nb = prevnonblank(v:lnum - 1)
115
116        if nb == 0
117            return -1
118        endif
119
120        call cursor(nb, 0)
121        call cursor(0, col("$") - 1)
122        if s:SynIdName() !~? "string"
123            return -1
124        endif
125
126        " This will not work for a " in the first column...
127        if s:CurrentChar() == '"'
128            call cursor(0, col("$") - 2)
129            if s:SynIdName() !~? "string"
130                return -1
131            endif
132            if s:CurrentChar() != '\\'
133                return -1
134            endif
135            call cursor(0, col("$") - 1)
136        endif
137
138        let p = searchpos('\(^\|[^\\]\)\zs"', 'bW')
139
140        if p != [0, 0]
141            return p[1] - 1
142        endif
143
144        return indent(".")
145    endfunction
146
147    function! s:CheckForString()
148        let pos = s:SavePosition()
149        try
150            let val = s:ClojureCheckForStringWorker()
151        finally
152            call s:RestorePosition(pos)
153        endtry
154        return val
155    endfunction
156
157    function! s:ClojureIsMethodSpecialCaseWorker(position)
158        " Find the next enclosing form.
159        call search('\S', 'Wb')
160
161        " Special case: we are at a '(('.
162        if s:CurrentChar() == '('
163            return 0
164        endif
165        call cursor(a:position)
166
167        let nextParen = s:MatchPairs('(', ')', 0)
168
169        " Special case: we are now at toplevel.
170        if nextParen == [0, 0]
171            return 0
172        endif
173        call cursor(nextParen)
174
175        call search('\S', 'W')
176        if g:clojure_special_indent_words =~ '\<' . s:CurrentWord() . '\>'
177            return 1
178        endif
179
180        return 0
181    endfunction
182
183    function! s:IsMethodSpecialCase(position)
184        let pos = s:SavePosition()
185        try
186            let val = s:ClojureIsMethodSpecialCaseWorker(a:position)
187        finally
188            call s:RestorePosition(pos)
189        endtry
190        return val
191    endfunction
192
193    function! GetClojureIndent()
194        " Get rid of special case.
195        if line(".") == 1
196            return 0
197        endif
198
199        " We have to apply some heuristics here to figure out, whether to use
200        " normal lisp indenting or not.
201        let i = s:CheckForString()
202        if i > -1
203            return i + !!g:clojure_align_multiline_strings
204        endif
205
206        call cursor(0, 1)
207
208        " Find the next enclosing [ or {. We can limit the second search
209        " to the line, where the [ was found. If no [ was there this is
210        " zero and we search for an enclosing {.
211        let paren = s:MatchPairs('(', ')', 0)
212        let bracket = s:MatchPairs('\[', '\]', paren[0])
213        let curly = s:MatchPairs('{', '}', bracket[0])
214
215        " In case the curly brace is on a line later then the [ or - in
216        " case they are on the same line - in a higher column, we take the
217        " curly indent.
218        if curly[0] > bracket[0] || curly[1] > bracket[1]
219            if curly[0] > paren[0] || curly[1] > paren[1]
220                return curly[1]
221            endif
222        endif
223
224        " If the curly was not chosen, we take the bracket indent - if
225        " there was one.
226        if bracket[0] > paren[0] || bracket[1] > paren[1]
227            return bracket[1]
228        endif
229
230        " There are neither { nor [ nor (, ie. we are at the toplevel.
231        if paren == [0, 0]
232            return 0
233        endif
234
235        " Now we have to reimplement lispindent. This is surprisingly easy, as
236        " soon as one has access to syntax items.
237        "
238        " - Check whether we are in a special position after a word in
239        "   g:clojure_special_indent_words. These are special cases.
240        " - Get the next keyword after the (.
241        " - If its first character is also a (, we have another sexp and align
242        "   one column to the right of the unmatched (.
243        " - In case it is in lispwords, we indent the next line to the column of
244        "   the ( + sw.
245        " - If not, we check whether it is last word in the line. In that case
246        "   we again use ( + sw for indent.
247        " - In any other case we use the column of the end of the word + 2.
248        call cursor(paren)
249
250        if s:IsMethodSpecialCase(paren)
251            return paren[1] + &shiftwidth - 1
252        endif
253
254        " In case we are at the last character, we use the paren position.
255        if col("$") - 1 == paren[1]
256            return paren[1]
257        endif
258
259        " In case after the paren is a whitespace, we search for the next word.
260        normal! l
261        if s:CurrentChar() == ' '
262            normal! w
263        endif
264
265        " If we moved to another line, there is no word after the (. We
266        " use the ( position for indent.
267        if line(".") > paren[0]
268            return paren[1]
269        endif
270
271        " We still have to check, whether the keyword starts with a (, [ or {.
272        " In that case we use the ( position for indent.
273        let w = s:CurrentWord()
274        if stridx('([{', w[0]) > -1
275            return paren[1]
276        endif
277
278        " Test words without namespace qualifiers and leading reader macro
279        " metacharacters.
280        "
281        " e.g. clojure.core/defn and #'defn should both indent like defn.
282        let ww = substitute(w, "\\v%(.*/|[#'`~@^,]*)(.*)", '\1', '')
283
284        if &lispwords =~ '\V\<' . ww . '\>'
285            return paren[1] + &shiftwidth - 1
286        endif
287
288        if g:clojure_fuzzy_indent
289            \ && !s:MatchesOne(g:clojure_fuzzy_indent_blacklist, ww)
290            \ && s:MatchesOne(g:clojure_fuzzy_indent_patterns, ww)
291            return paren[1] + &shiftwidth - 1
292        endif
293
294        normal! W
295        if paren[0] < line(".")
296            return paren[1] + &shiftwidth - 1
297        endif
298
299        normal! ge
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
315" Specially indented symbols from clojure.core and clojure.test.
316"
317" Clojure symbols are indented in the defn style when they:
318"
319"   * Define vars and anonymous functions
320"   * Create new lexical scopes or scopes with altered environments
321"   * Create conditional branches from a predicate function or value
322"
323" The arglists for these functions are generally in the form of [x & body];
324" Functions that accept a flat list of forms do not treat the first argument
325" specially and hence are not indented specially.
326
327" Definitions
328setlocal lispwords=
329setlocal lispwords+=bound-fn
330setlocal lispwords+=def
331setlocal lispwords+=definline
332setlocal lispwords+=definterface
333setlocal lispwords+=defmacro
334setlocal lispwords+=defmethod
335setlocal lispwords+=defmulti
336setlocal lispwords+=defn
337setlocal lispwords+=defn-
338setlocal lispwords+=defonce
339setlocal lispwords+=defprotocol
340setlocal lispwords+=defrecord
341setlocal lispwords+=defstruct
342setlocal lispwords+=deftest " clojure.test
343setlocal lispwords+=deftest- " clojure.test
344setlocal lispwords+=deftype
345setlocal lispwords+=extend
346setlocal lispwords+=extend-protocol
347setlocal lispwords+=extend-type
348setlocal lispwords+=fn
349setlocal lispwords+=ns
350setlocal lispwords+=proxy
351setlocal lispwords+=reify
352setlocal lispwords+=set-test " clojure.test
353
354" Binding forms
355setlocal lispwords+=as->
356setlocal lispwords+=binding
357setlocal lispwords+=doall
358setlocal lispwords+=dorun
359setlocal lispwords+=doseq
360setlocal lispwords+=dotimes
361setlocal lispwords+=doto
362setlocal lispwords+=for
363setlocal lispwords+=if-let
364setlocal lispwords+=let
365setlocal lispwords+=letfn
366setlocal lispwords+=locking
367setlocal lispwords+=loop
368setlocal lispwords+=testing " clojure.test
369setlocal lispwords+=when-first
370setlocal lispwords+=when-let
371setlocal lispwords+=with-bindings
372setlocal lispwords+=with-in-str
373setlocal lispwords+=with-local-vars
374setlocal lispwords+=with-open
375setlocal lispwords+=with-precision
376setlocal lispwords+=with-redefs
377setlocal lispwords+=with-redefs-fn
378setlocal lispwords+=with-test " clojure.test
379
380" Conditional branching
381setlocal lispwords+=case
382setlocal lispwords+=cond->
383setlocal lispwords+=cond->>
384setlocal lispwords+=condp
385setlocal lispwords+=if
386setlocal lispwords+=if-not
387setlocal lispwords+=when
388setlocal lispwords+=when-not
389setlocal lispwords+=while
390
391" Exception handling
392setlocal lispwords+=catch
393setlocal lispwords+=try " For aesthetics when enclosing single line
394
395let &cpo = s:save_cpo
396unlet! s:save_cpo
397
398" vim:sts=4 sw=4 et:
399