git.m455.casa

fennel.vim

clone url: git://git.m455.casa/fennel.vim


indent/fennel.vim

1 " Vim filetype plugin file
2 " Language: FENNEL
3 " Maintainer: Calvin Rose
4 "
5 " Modified from vim-clojure-static
6 " https://github.com/guns/vim-clojure-static/blob/master/indent/clojure.vim
7 " This should eventually be replaced by a more standard and performant
8 " indenter.
9
10 if exists("b:did_indent")
11 finish
12 endif
13 let b:did_indent = 1
14
15 let s:save_cpo = &cpo
16 set cpo&vim
17
18 setlocal noautoindent nosmartindent
19 setlocal softtabstop=2 shiftwidth=2 expandtab
20 setlocal indentkeys=!,o,O
21
22 if exists("*searchpairpos")
23
24 if !exists('g:fennel_maxlines')
25 let g:fennel_maxlines = 100
26 endif
27
28 if !exists('g:fennel_fuzzy_indent')
29 let g:fennel_fuzzy_indent = 1
30 endif
31
32 if !exists('g:fennel_fuzzy_indent_patterns')
33 let g:fennel_fuzzy_indent_patterns = ['^def', '^let', '^while', '^if', '^fn$', '^var$', '^case$', '^for$', '^each$', '^local$', '^global$', '^match$', '^macro', '^lambda$', '^with-open$', '^collect$', '^icollect$']
34 endif
35
36 if !exists('g:fennel_fuzzy_indent_badlist')
37 let g:fennel_fuzzy_indent_badlist = []
38 endif
39
40 if !exists('g:fennel_special_indent_words')
41 let g:fennel_special_indent_words = ''
42 endif
43
44 if !exists('g:fennel_align_multiline_strings')
45 let g:fennel_align_multiline_strings = 0
46 endif
47
48 if !exists('g:fennel_align_subforms')
49 let g:fennel_align_subforms = 0
50 endif
51
52 function! s:syn_id_name()
53 return synIDattr(synID(line("."), col("."), 0), "name")
54 endfunction
55
56 function! s:ignored_region()
57 return s:syn_id_name() =~? '\vstring|comment|character'
58 endfunction
59
60 function! s:current_char()
61 return getline('.')[col('.')-1]
62 endfunction
63
64 function! s:current_word()
65 return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2]
66 endfunction
67
68 function! s:is_paren()
69 return s:current_char() =~# '\v[\(\)\[\]\{\}]' && !s:ignored_region()
70 endfunction
71
72 " Returns 1 if string matches a pattern in 'patterns', which may be a
73 " list of patterns, or a comma-delimited string of implicitly anchored
74 " patterns.
75 function! s:match_one(patterns, string)
76 let list = type(a:patterns) == type([])
77 \ ? a:patterns
78 \ : map(split(a:patterns, ','), '"^" . v:val . "$"')
79 for pat in list
80 if a:string =~# pat | return 1 | endif
81 endfor
82 endfunction
83
84 function! s:match_pairs(open, close, stopat)
85 " Stop only on vector and map [ resp. {. Ignore the ones in strings and
86 " comments.
87 if a:stopat == 0 && g:fennel_maxlines > 0
88 let stopat = max([line(".") - g:fennel_maxlines, 0])
89 else
90 let stopat = a:stopat
91 endif
92
93 let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:is_paren()", stopat)
94 return [pos[0], col(pos)]
95 endfunction
96
97 function! s:fennel_check_for_string_worker()
98 " Check whether there is the last character of the previous line is
99 " highlighted as a string. If so, we check whether it's a ". In this
100 " case we have to check also the previous character. The " might be the
101 " closing one. In case the we are still in the string, we search for the
102 " opening ". If this is not found we take the indent of the line.
103 let nb = prevnonblank(v:lnum - 1)
104
105 if nb == 0
106 return -1
107 endif
108
109 call cursor(nb, 0)
110 call cursor(0, col("$") - 1)
111 if s:syn_id_name() !~? "string"
112 return -1
113 endif
114
115 " This will not work for a " in the first column...
116 if s:current_char() == '"' || s:current_char() == '`'
117 call cursor(0, col("$") - 2)
118 if s:syn_id_name() !~? "string"
119 return -1
120 endif
121 if s:current_char() != '\'
122 return -1
123 endif
124 call cursor(0, col("$") - 1)
125 endif
126
127 let p = searchpos('\(^\|[^\\]\)\zs"\`', 'bW')
128
129 if p != [0, 0]
130 return p[1] - 1
131 endif
132
133 return indent(".")
134 endfunction
135
136 function! s:check_for_string()
137 let pos = getpos('.')
138 try
139 let val = s:fennel_check_for_string_worker()
140 finally
141 call setpos('.', pos)
142 endtry
143 return val
144 endfunction
145
146 " Returns 1 for opening brackets, -1 for _anything else_.
147 function! s:bracket_type(char)
148 return stridx('([{', a:char) > -1 ? 1 : -1
149 endfunction
150
151 " Returns: [opening-bracket-lnum, indent]
152 function! s:fennel_indent_pos()
153 " Get rid of special case.
154 if line(".") == 1
155 return [0, 0]
156 endif
157
158 " We have to apply some heuristics here to figure out, whether to use
159 " normal lisp indenting or not.
160 let i = s:check_for_string()
161 if i > -1
162 return [0, i + !!g:fennel_align_multiline_strings]
163 endif
164
165 call cursor(0, 1)
166
167 " Find the next enclosing [ or {. We can limit the second search
168 " to the line, where the [ was found. If no [ was there this is
169 " zero and we search for an enclosing {.
170 let paren = s:match_pairs('(', ')', 0)
171 let bracket = s:match_pairs('\[', '\]', paren[0])
172 let curly = s:match_pairs('{', '}', bracket[0])
173
174 " In case the curly brace is on a line later then the [ or - in
175 " case they are on the same line - in a higher column, we take the
176 " curly indent.
177 if curly[0] > bracket[0] || curly[1] > bracket[1]
178 if curly[0] > paren[0] || curly[1] > paren[1]
179 return curly
180 endif
181 endif
182
183 " If the curly was not chosen, we take the bracket indent - if
184 " there was one.
185 if bracket[0] > paren[0] || bracket[1] > paren[1]
186 return bracket
187 endif
188
189 " There are neither { nor [ nor (, ie. we are at the toplevel.
190 if paren == [0, 0]
191 return paren
192 endif
193
194 " Now we have to reimplement lispindent. This is surprisingly easy, as
195 " soon as one has access to syntax items.
196 "
197 " - Check whether we are in a special position after a word in
198 " g:fennel_special_indent_words. These are special cases.
199 " - Get the next keyword after the (.
200 " - If its first character is also a (, we have another sexp and align
201 " one column to the right of the unmatched (.
202 " - In case it is in lispwords, we indent the next line to the column of
203 " the ( + sw.
204 " - If not, we check whether it is last word in the line. In that case
205 " we again use ( + sw for indent.
206 " - In any other case we use the column of the end of the word + 2.
207 call cursor(paren)
208
209 " In case we are at the last character, we use the paren position.
210 if col("$") - 1 == paren[1]
211 return paren
212 endif
213
214 " In case after the paren is a whitespace, we search for the next word.
215 call cursor(0, col('.') + 1)
216 if s:current_char() == ' '
217 call search('\v\S', 'W')
218 endif
219
220 " If we moved to another line, there is no word after the (. We
221 " use the ( position for indent.
222 if line(".") > paren[0]
223 return paren
224 endif
225
226 " We still have to check, whether the keyword starts with a (, [ or {.
227 " In that case we use the ( position for indent.
228 let w = s:current_word()
229 if s:bracket_type(w[0]) == 1
230 return paren
231 endif
232
233 let ww = w
234
235 if &lispwords =~# '\V\<' . ww . '\>'
236 return [paren[0], paren[1] + &shiftwidth - 1]
237 endif
238
239 if g:fennel_fuzzy_indent
240 \ && !s:match_one(g:fennel_fuzzy_indent_badlist, ww)
241 \ && s:match_one(g:fennel_fuzzy_indent_patterns, ww)
242 return [paren[0], paren[1] + &shiftwidth - 1]
243 endif
244
245 call search('\v\_s', 'cW')
246 call search('\v\S', 'W')
247 if paren[0] < line(".")
248 return [paren[0], paren[1] + (g:fennel_align_subforms ? 0 : &shiftwidth - 1)]
249 endif
250
251 call search('\v\S', 'bW')
252 return [line('.'), col('.') + 1]
253 endfunction
254
255 function! GetFennelIndent()
256 let lnum = line('.')
257 let orig_lnum = lnum
258 let orig_col = col('.')
259 let [opening_lnum, indent] = s:fennel_indent_pos()
260
261 " Account for multibyte characters
262 if opening_lnum > 0
263 let indent -= indent - virtcol([opening_lnum, indent])
264 endif
265
266 " Return if there are no previous lines to inherit from
267 if opening_lnum < 1 || opening_lnum >= lnum - 1
268 call cursor(orig_lnum, orig_col)
269 return indent
270 endif
271
272 let bracket_count = 0
273
274 " Take the indent of the first previous non-white line that is
275 " at the same sexp level. cf. src/misc1.c:get_lisp_indent()
276 while 1
277 let lnum = prevnonblank(lnum - 1)
278 let col = 1
279
280 if lnum <= opening_lnum
281 break
282 endif
283
284 call cursor(lnum, col)
285
286 " Handle bracket counting edge case
287 if s:is_paren()
288 let bracket_count += s:bracket_type(s:current_char())
289 endif
290
291 while 1
292 if search('\v[(\[{}\])]', '', lnum) < 1
293 break
294 elseif !s:ignored_region()
295 let bracket_count += s:bracket_type(s:current_char())
296 endif
297 endwhile
298
299 if bracket_count == 0
300 " Check if this is part of a multiline string
301 call cursor(lnum, 1)
302 if s:syn_id_name() !~? '\vString|Buffer'
303 call cursor(orig_lnum, orig_col)
304 return indent(lnum)
305 endif
306 endif
307 endwhile
308
309 call cursor(orig_lnum, orig_col)
310 return indent
311 endfunction
312
313 setlocal indentexpr=GetFennelIndent()
314
315 else
316
317 " In case we have searchpairpos not available we fall back to
318 " normal lisp indenting.
319 setlocal indentexpr=
320 setlocal lisp
321 let b:undo_indent .= '| setlocal lisp<'
322
323 endif
324
325 let &cpo = s:save_cpo
326 unlet! s:save_cpo