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 |