blob: a58ccad8706bb6f563a3c892f7a2880a6d41c1a8 [file] [log] [blame]
Bram Moolenaar4a748032010-09-30 21:47:56 +02001" Vim indent file
2" Language: Falcon
3" Maintainer: Steven Oliver <oliver.steven@gmail.com>
4" Website: https://steveno@github.com/steveno/falconpl-vim.git
Bram Moolenaarec7944a2013-06-12 21:29:15 +02005" Credits: This is, to a great extent, a copy n' paste of ruby.vim.
Bram Moolenaarcbaff5e2022-04-08 17:45:08 +01006" 2022 April: b:undo_indent added by Doug Kearns
Bram Moolenaar4a748032010-09-30 21:47:56 +02007
Bram Moolenaarec7944a2013-06-12 21:29:15 +02008" 1. Setup {{{1
9" ============
Bram Moolenaar4a748032010-09-30 21:47:56 +020010
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
17setlocal nosmartindent
18
19" Setup indent function and when to use it
Bram Moolenaarec7944a2013-06-12 21:29:15 +020020setlocal indentexpr=FalconGetIndent(v:lnum)
Bram Moolenaar4a748032010-09-30 21:47:56 +020021setlocal indentkeys=0{,0},0),0],!^F,o,O,e
22setlocal indentkeys+==~case,=~catch,=~default,=~elif,=~else,=~end,=~\"
23
Bram Moolenaarcbaff5e2022-04-08 17:45:08 +010024let b:undo_indent = "setl inde< indk< si<"
25
Bram Moolenaar4a748032010-09-30 21:47:56 +020026" Define the appropriate indent function but only once
27if exists("*FalconGetIndent")
28 finish
29endif
30
31let s:cpo_save = &cpo
32set cpo&vim
33
Bram Moolenaarec7944a2013-06-12 21:29:15 +020034" 2. Variables {{{1
35" ============
Bram Moolenaar4a748032010-09-30 21:47:56 +020036
37" Regex of syntax group names that are strings AND comments
38let s:syng_strcom = '\<falcon\%(String\|StringEscape\|Comment\)\>'
39
40" Regex of syntax group names that are strings
41let s:syng_string = '\<falcon\%(String\|StringEscape\)\>'
42
Bram Moolenaarec7944a2013-06-12 21:29:15 +020043" Regex that defines blocks.
44"
45" Note that there's a slight problem with this regex and s:continuation_regex.
46" Code like this will be matched by both:
47"
48" method_call do |(a, b)|
49"
50" The reason is that the pipe matches a hanging "|" operator.
51"
52let s:block_regex =
53 \ '\%(\<do:\@!\>\|%\@<!{\)\s*\%(|\s*(*\s*\%([*@&]\=\h\w*,\=\s*\)\%(,\s*(*\s*[*@&]\=\h\w*\s*)*\s*\)*|\)\=\s*\%(#.*\)\=$'
54
55let s:block_continuation_regex = '^\s*[^])}\t ].*'.s:block_regex
56
57" Regex that defines continuation lines.
58" TODO: this needs to deal with if ...: and so on
59let s:continuation_regex =
60 \ '\%(%\@<![({[\\.,:*/%+]\|\<and\|\<or\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)\s*\%(#.*\)\=$'
61
62" Regex that defines bracket continuations
63let s:bracket_continuation_regex = '%\@<!\%([({[]\)\s*\%(#.*\)\=$'
64
65" Regex that defines continuation lines, not including (, {, or [.
66let s:non_bracket_continuation_regex = '\%([\\.,:*/%+]\|\<and\|\<or\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)\s*\%(#.*\)\=$'
67
Bram Moolenaar4a748032010-09-30 21:47:56 +020068" Keywords to indent on
69let s:falcon_indent_keywords = '^\s*\(case\|catch\|class\|enum\|default\|elif\|else' .
70 \ '\|for\|function\|if.*"[^"]*:.*"\|if \(\(:\)\@!.\)*$\|loop\|object\|select' .
71 \ '\|switch\|try\|while\|\w*\s*=\s*\w*([$\)'
72
73" Keywords to deindent on
74let s:falcon_deindent_keywords = '^\s*\(case\|catch\|default\|elif\|else\|end\)'
75
Bram Moolenaarec7944a2013-06-12 21:29:15 +020076" 3. Functions {{{1
77" ============
Bram Moolenaar4a748032010-09-30 21:47:56 +020078
Bram Moolenaarec7944a2013-06-12 21:29:15 +020079" Check if the character at lnum:col is inside a string, comment, or is ascii.
Bram Moolenaar4a748032010-09-30 21:47:56 +020080function s:IsInStringOrComment(lnum, col)
81 return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_strcom
82endfunction
83
Bram Moolenaarec7944a2013-06-12 21:29:15 +020084" Check if the character at lnum:col is inside a string.
85function s:IsInString(lnum, col)
86 return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_string
87endfunction
Bram Moolenaar4a748032010-09-30 21:47:56 +020088
Bram Moolenaarec7944a2013-06-12 21:29:15 +020089" Check if the character at lnum:col is inside a string delimiter
90function s:IsInStringDelimiter(lnum, col)
91 return synIDattr(synID(a:lnum, a:col, 1), 'name') == 'falconStringDelimiter'
92endfunction
Bram Moolenaar4a748032010-09-30 21:47:56 +020093
Bram Moolenaarec7944a2013-06-12 21:29:15 +020094" Find line above 'lnum' that isn't empty, in a comment, or in a string.
95function s:PrevNonBlankNonString(lnum)
96 let in_block = 0
97 let lnum = prevnonblank(a:lnum)
98 while lnum > 0
99 " Go in and out of blocks comments as necessary.
100 " If the line isn't empty (with opt. comment) or in a string, end search.
101 let line = getline(lnum)
102 if line =~ '^=begin'
103 if in_block
104 let in_block = 0
105 else
106 break
107 endif
108 elseif !in_block && line =~ '^=end'
109 let in_block = 1
110 elseif !in_block && line !~ '^\s*#.*$' && !(s:IsInStringOrComment(lnum, 1)
111 \ && s:IsInStringOrComment(lnum, strlen(line)))
112 break
113 endif
114 let lnum = prevnonblank(lnum - 1)
115 endwhile
116 return lnum
117endfunction
Bram Moolenaar4a748032010-09-30 21:47:56 +0200118
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200119" Find line above 'lnum' that started the continuation 'lnum' may be part of.
120function s:GetMSL(lnum)
121 " Start on the line we're at and use its indent.
122 let msl = a:lnum
123 let msl_body = getline(msl)
124 let lnum = s:PrevNonBlankNonString(a:lnum - 1)
125 while lnum > 0
126 " If we have a continuation line, or we're in a string, use line as MSL.
127 " Otherwise, terminate search as we have found our MSL already.
128 let line = getline(lnum)
129
130 if s:Match(line, s:non_bracket_continuation_regex) &&
131 \ s:Match(msl, s:non_bracket_continuation_regex)
132 " If the current line is a non-bracket continuation and so is the
133 " previous one, keep its indent and continue looking for an MSL.
134 "
135 " Example:
136 " method_call one,
137 " two,
138 " three
139 "
140 let msl = lnum
141 elseif s:Match(lnum, s:non_bracket_continuation_regex) &&
142 \ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex))
143 " If the current line is a bracket continuation or a block-starter, but
144 " the previous is a non-bracket one, respect the previous' indentation,
145 " and stop here.
146 "
147 " Example:
148 " method_call one,
149 " two {
150 " three
151 "
152 return lnum
153 elseif s:Match(lnum, s:bracket_continuation_regex) &&
154 \ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex))
155 " If both lines are bracket continuations (the current may also be a
156 " block-starter), use the current one's and stop here
157 "
158 " Example:
159 " method_call(
160 " other_method_call(
161 " foo
162 return msl
163 elseif s:Match(lnum, s:block_regex) &&
164 \ !s:Match(msl, s:continuation_regex) &&
165 \ !s:Match(msl, s:block_continuation_regex)
166 " If the previous line is a block-starter and the current one is
167 " mostly ordinary, use the current one as the MSL.
168 "
169 " Example:
170 " method_call do
171 " something
172 " something_else
173 return msl
174 else
175 let col = match(line, s:continuation_regex) + 1
176 if (col > 0 && !s:IsInStringOrComment(lnum, col))
177 \ || s:IsInString(lnum, strlen(line))
178 let msl = lnum
179 else
180 break
181 endif
182 endif
183
184 let msl_body = getline(msl)
185 let lnum = s:PrevNonBlankNonString(lnum - 1)
186 endwhile
187 return msl
188endfunction
189
190" Check if line 'lnum' has more opening brackets than closing ones.
191function s:ExtraBrackets(lnum)
192 let opening = {'parentheses': [], 'braces': [], 'brackets': []}
193 let closing = {'parentheses': [], 'braces': [], 'brackets': []}
194
195 let line = getline(a:lnum)
196 let pos = match(line, '[][(){}]', 0)
197
198 " Save any encountered opening brackets, and remove them once a matching
199 " closing one has been found. If a closing bracket shows up that doesn't
200 " close anything, save it for later.
201 while pos != -1
202 if !s:IsInStringOrComment(a:lnum, pos + 1)
203 if line[pos] == '('
204 call add(opening.parentheses, {'type': '(', 'pos': pos})
205 elseif line[pos] == ')'
206 if empty(opening.parentheses)
207 call add(closing.parentheses, {'type': ')', 'pos': pos})
208 else
209 let opening.parentheses = opening.parentheses[0:-2]
210 endif
211 elseif line[pos] == '{'
212 call add(opening.braces, {'type': '{', 'pos': pos})
213 elseif line[pos] == '}'
214 if empty(opening.braces)
215 call add(closing.braces, {'type': '}', 'pos': pos})
216 else
217 let opening.braces = opening.braces[0:-2]
218 endif
219 elseif line[pos] == '['
220 call add(opening.brackets, {'type': '[', 'pos': pos})
221 elseif line[pos] == ']'
222 if empty(opening.brackets)
223 call add(closing.brackets, {'type': ']', 'pos': pos})
224 else
225 let opening.brackets = opening.brackets[0:-2]
226 endif
227 endif
228 endif
229
230 let pos = match(line, '[][(){}]', pos + 1)
231 endwhile
232
233 " Find the rightmost brackets, since they're the ones that are important in
234 " both opening and closing cases
235 let rightmost_opening = {'type': '(', 'pos': -1}
236 let rightmost_closing = {'type': ')', 'pos': -1}
237
238 for opening in opening.parentheses + opening.braces + opening.brackets
239 if opening.pos > rightmost_opening.pos
240 let rightmost_opening = opening
241 endif
242 endfor
243
244 for closing in closing.parentheses + closing.braces + closing.brackets
245 if closing.pos > rightmost_closing.pos
246 let rightmost_closing = closing
247 endif
248 endfor
249
250 return [rightmost_opening, rightmost_closing]
251endfunction
252
253function s:Match(lnum, regex)
254 let col = match(getline(a:lnum), '\C'.a:regex) + 1
255 return col > 0 && !s:IsInStringOrComment(a:lnum, col) ? col : 0
256endfunction
257
258function s:MatchLast(lnum, regex)
259 let line = getline(a:lnum)
260 let col = match(line, '.*\zs' . a:regex)
261 while col != -1 && s:IsInStringOrComment(a:lnum, col)
262 let line = strpart(line, 0, col)
263 let col = match(line, '.*' . a:regex)
264 endwhile
265 return col + 1
266endfunction
267
268" 4. FalconGetIndent Routine {{{1
269" ============
270
271function FalconGetIndent(...)
272 " For the current line, use the first argument if given, else v:lnum
273 let clnum = a:0 ? a:1 : v:lnum
Bram Moolenaar4a748032010-09-30 21:47:56 +0200274
275 " Use zero indent at the top of the file
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200276 if clnum == 0
Bram Moolenaar4a748032010-09-30 21:47:56 +0200277 return 0
278 endif
279
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200280 let line = getline(clnum)
281 let ind = -1
282
283 " If we got a closing bracket on an empty line, find its match and indent
284 " according to it. For parentheses we indent to its column - 1, for the
285 " others we indent to the containing line's MSL's level. Return -1 if fail.
286 let col = matchend(line, '^\s*[]})]')
287 if col > 0 && !s:IsInStringOrComment(clnum, col)
288 call cursor(clnum, col)
289 let bs = strpart('(){}[]', stridx(')}]', line[col - 1]) * 2, 2)
290 if searchpair(escape(bs[0], '\['), '', bs[1], 'bW', s:skip_expr) > 0
291 if line[col-1]==')' && col('.') != col('$') - 1
292 let ind = virtcol('.') - 1
293 else
294 let ind = indent(s:GetMSL(line('.')))
295 endif
296 endif
297 return ind
298 endif
299
300 " If we have a deindenting keyword, find its match and indent to its level.
301 " TODO: this is messy
302 if s:Match(clnum, s:falcon_deindent_keywords)
303 call cursor(clnum, 1)
304 if searchpair(s:end_start_regex, s:end_middle_regex, s:end_end_regex, 'bW',
305 \ s:end_skip_expr) > 0
306 let msl = s:GetMSL(line('.'))
307 let line = getline(line('.'))
308
309 if strpart(line, 0, col('.') - 1) =~ '=\s*$' &&
310 \ strpart(line, col('.') - 1, 2) !~ 'do'
311 let ind = virtcol('.') - 1
312 elseif getline(msl) =~ '=\s*\(#.*\)\=$'
313 let ind = indent(line('.'))
314 else
315 let ind = indent(msl)
316 endif
317 endif
318 return ind
319 endif
320
321 " If we are in a multi-line string or line-comment, don't do anything to it.
322 if s:IsInString(clnum, matchend(line, '^\s*') + 1)
323 return indent('.')
324 endif
325
326 " Find a non-blank, non-multi-line string line above the current line.
327 let lnum = s:PrevNonBlankNonString(clnum - 1)
328
329 " If the line is empty and inside a string, use the previous line.
330 if line =~ '^\s*$' && lnum != prevnonblank(clnum - 1)
331 return indent(prevnonblank(clnum))
332 endif
333
334 " At the start of the file use zero indent.
335 if lnum == 0
336 return 0
337 endif
338
339 " Set up variables for the previous line.
340 let line = getline(lnum)
Bram Moolenaar4a748032010-09-30 21:47:56 +0200341 let ind = indent(lnum)
Bram Moolenaar4a748032010-09-30 21:47:56 +0200342
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200343 " If the previous line ended with a block opening, add a level of indent.
344 if s:Match(lnum, s:block_regex)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200345 return indent(s:GetMSL(lnum)) + shiftwidth()
Bram Moolenaar4a748032010-09-30 21:47:56 +0200346 endif
347
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200348 " If it contained hanging closing brackets, find the rightmost one, find its
349 " match and indent according to that.
350 if line =~ '[[({]' || line =~ '[])}]\s*\%(#.*\)\=$'
351 let [opening, closing] = s:ExtraBrackets(lnum)
352
353 if opening.pos != -1
354 if opening.type == '(' && searchpair('(', '', ')', 'bW', s:skip_expr) > 0
355 if col('.') + 1 == col('$')
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200356 return ind + shiftwidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200357 else
358 return virtcol('.')
359 endif
360 else
361 let nonspace = matchend(line, '\S', opening.pos + 1) - 1
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200362 return nonspace > 0 ? nonspace : ind + shiftwidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200363 endif
364 elseif closing.pos != -1
365 call cursor(lnum, closing.pos + 1)
366 normal! %
367
368 if s:Match(line('.'), s:falcon_indent_keywords)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200369 return indent('.') + shiftwidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200370 else
371 return indent('.')
372 endif
373 else
Bram Moolenaar9d87a372018-12-18 21:41:50 +0100374 call cursor(clnum, 0) " FIXME: column was vcol
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200375 end
Bram Moolenaar4a748032010-09-30 21:47:56 +0200376 endif
377
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200378 " If the previous line ended with an "end", match that "end"s beginning's
379 " indent.
380 let col = s:Match(lnum, '\%(^\|[^.:@$]\)\<end\>\s*\%(#.*\)\=$')
381 if col > 0
382 call cursor(lnum, col)
383 if searchpair(s:end_start_regex, '', s:end_end_regex, 'bW',
384 \ s:end_skip_expr) > 0
385 let n = line('.')
386 let ind = indent('.')
387 let msl = s:GetMSL(n)
388 if msl != n
389 let ind = indent(msl)
390 end
391 return ind
392 endif
393 end
394
395 let col = s:Match(lnum, s:falcon_indent_keywords)
396 if col > 0
397 call cursor(lnum, col)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200398 let ind = virtcol('.') - 1 + shiftwidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200399 " TODO: make this better (we need to count them) (or, if a searchpair
400 " fails, we know that something is lacking an end and thus we indent a
401 " level
402 if s:Match(lnum, s:end_end_regex)
403 let ind = indent('.')
404 endif
405 return ind
Bram Moolenaar4a748032010-09-30 21:47:56 +0200406 endif
407
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200408 " Set up variables to use and search for MSL to the previous line.
409 let p_lnum = lnum
410 let lnum = s:GetMSL(lnum)
411
412 " If the previous line wasn't a MSL and is continuation return its indent.
413 " TODO: the || s:IsInString() thing worries me a bit.
414 if p_lnum != lnum
415 if s:Match(p_lnum, s:non_bracket_continuation_regex) || s:IsInString(p_lnum,strlen(line))
416 return ind
417 endif
Bram Moolenaar4a748032010-09-30 21:47:56 +0200418 endif
419
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200420 " Set up more variables, now that we know we wasn't continuation bound.
421 let line = getline(lnum)
422 let msl_ind = indent(lnum)
423
424 " If the MSL line had an indenting keyword in it, add a level of indent.
425 " TODO: this does not take into account contrived things such as
426 " module Foo; class Bar; end
427 if s:Match(lnum, s:falcon_indent_keywords)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200428 let ind = msl_ind + shiftwidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200429 if s:Match(lnum, s:end_end_regex)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200430 let ind = ind - shiftwidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200431 endif
432 return ind
Bram Moolenaar4a748032010-09-30 21:47:56 +0200433 endif
434
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200435 " If the previous line ended with [*+/.,-=], but wasn't a block ending or a
436 " closing bracket, indent one extra level.
437 if s:Match(lnum, s:non_bracket_continuation_regex) && !s:Match(lnum, '^\s*\([\])}]\|end\)')
438 if lnum == p_lnum
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200439 let ind = msl_ind + shiftwidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200440 else
441 let ind = msl_ind
442 endif
443 return ind
Bram Moolenaar4a748032010-09-30 21:47:56 +0200444 endif
445
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200446 return ind
Bram Moolenaar4a748032010-09-30 21:47:56 +0200447endfunction
448
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200449" }}}1
450
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100451let &cpo = s:cpo_save
452unlet s:cpo_save
453
Bram Moolenaar4a748032010-09-30 21:47:56 +0200454" vim: set sw=4 sts=4 et tw=80 :