3 " Maintainer: Steven Oliver <oliver.steven@gmail.com>
4 " Website: https://steveno@github.com/steveno/falconpl-vim.git
5 " Credits: This is, to a great extent, a copy n' paste of ruby.vim.
10 " Only load this indent file when no other was loaded.
11 if exists("b:did_indent")
16 setlocal nosmartindent
18 " Setup indent function and when to use it
19 setlocal indentexpr=FalconGetIndent(v:lnum)
20 setlocal indentkeys=0{,0},0),0],!^F,o,O,e
21 setlocal indentkeys+==~case,=~catch,=~default,=~elif,=~else,=~end,=~\"
23 " Define the appropriate indent function but only once
24 if exists("*FalconGetIndent")
34 " Regex of syntax group names that are strings AND comments
35 let s:syng_strcom = '\<falcon\%(String\|StringEscape\|Comment\)\>'
37 " Regex of syntax group names that are strings
38 let s:syng_string = '\<falcon\%(String\|StringEscape\)\>'
40 " Regex that defines blocks.
42 " Note that there's a slight problem with this regex and s:continuation_regex.
43 " Code like this will be matched by both:
45 " method_call do |(a, b)|
47 " The reason is that the pipe matches a hanging "|" operator.
50 \ '\%(\<do:\@!\>\|%\@<!{\)\s*\%(|\s*(*\s*\%([*@&]\=\h\w*,\=\s*\)\%(,\s*(*\s*[*@&]\=\h\w*\s*)*\s*\)*|\)\=\s*\%(#.*\)\=$'
52 let s:block_continuation_regex = '^\s*[^])}\t ].*'.s:block_regex
54 " Regex that defines continuation lines.
55 " TODO: this needs to deal with if ...: and so on
56 let s:continuation_regex =
57 \ '\%(%\@<![({[\\.,:*/%+]\|\<and\|\<or\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)\s*\%(#.*\)\=$'
59 " Regex that defines bracket continuations
60 let s:bracket_continuation_regex = '%\@<!\%([({[]\)\s*\%(#.*\)\=$'
62 " Regex that defines continuation lines, not including (, {, or [.
63 let s:non_bracket_continuation_regex = '\%([\\.,:*/%+]\|\<and\|\<or\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)\s*\%(#.*\)\=$'
65 " Keywords to indent on
66 let s:falcon_indent_keywords = '^\s*\(case\|catch\|class\|enum\|default\|elif\|else' .
67 \ '\|for\|function\|if.*"[^"]*:.*"\|if \(\(:\)\@!.\)*$\|loop\|object\|select' .
68 \ '\|switch\|try\|while\|\w*\s*=\s*\w*([$\)'
70 " Keywords to deindent on
71 let s:falcon_deindent_keywords = '^\s*\(case\|catch\|default\|elif\|else\|end\)'
76 " Check if the character at lnum:col is inside a string, comment, or is ascii.
77 function s:IsInStringOrComment(lnum, col)
78 return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_strcom
81 " Check if the character at lnum:col is inside a string.
82 function s:IsInString(lnum, col)
83 return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_string
86 " Check if the character at lnum:col is inside a string delimiter
87 function s:IsInStringDelimiter(lnum, col)
88 return synIDattr(synID(a:lnum, a:col, 1), 'name') == 'falconStringDelimiter'
91 " Find line above 'lnum' that isn't empty, in a comment, or in a string.
92 function s:PrevNonBlankNonString(lnum)
94 let lnum = prevnonblank(a:lnum)
96 " Go in and out of blocks comments as necessary.
97 " If the line isn't empty (with opt. comment) or in a string, end search.
98 let line = getline(lnum)
105 elseif !in_block && line =~ '^=end'
107 elseif !in_block && line !~ '^\s*#.*$' && !(s:IsInStringOrComment(lnum, 1)
108 \ && s:IsInStringOrComment(lnum, strlen(line)))
111 let lnum = prevnonblank(lnum - 1)
116 " Find line above 'lnum' that started the continuation 'lnum' may be part of.
117 function s:GetMSL(lnum)
118 " Start on the line we're at and use its indent.
120 let msl_body = getline(msl)
121 let lnum = s:PrevNonBlankNonString(a:lnum - 1)
123 " If we have a continuation line, or we're in a string, use line as MSL.
124 " Otherwise, terminate search as we have found our MSL already.
125 let line = getline(lnum)
127 if s:Match(line, s:non_bracket_continuation_regex) &&
128 \ s:Match(msl, s:non_bracket_continuation_regex)
129 " If the current line is a non-bracket continuation and so is the
130 " previous one, keep its indent and continue looking for an MSL.
138 elseif s:Match(lnum, s:non_bracket_continuation_regex) &&
139 \ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex))
140 " If the current line is a bracket continuation or a block-starter, but
141 " the previous is a non-bracket one, respect the previous' indentation,
150 elseif s:Match(lnum, s:bracket_continuation_regex) &&
151 \ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex))
152 " If both lines are bracket continuations (the current may also be a
153 " block-starter), use the current one's and stop here
160 elseif s:Match(lnum, s:block_regex) &&
161 \ !s:Match(msl, s:continuation_regex) &&
162 \ !s:Match(msl, s:block_continuation_regex)
163 " If the previous line is a block-starter and the current one is
164 " mostly ordinary, use the current one as the MSL.
172 let col = match(line, s:continuation_regex) + 1
173 if (col > 0 && !s:IsInStringOrComment(lnum, col))
174 \ || s:IsInString(lnum, strlen(line))
181 let msl_body = getline(msl)
182 let lnum = s:PrevNonBlankNonString(lnum - 1)
187 " Check if line 'lnum' has more opening brackets than closing ones.
188 function s:ExtraBrackets(lnum)
189 let opening = {'parentheses': [], 'braces': [], 'brackets': []}
190 let closing = {'parentheses': [], 'braces': [], 'brackets': []}
192 let line = getline(a:lnum)
193 let pos = match(line, '[][(){}]', 0)
195 " Save any encountered opening brackets, and remove them once a matching
196 " closing one has been found. If a closing bracket shows up that doesn't
197 " close anything, save it for later.
199 if !s:IsInStringOrComment(a:lnum, pos + 1)
201 call add(opening.parentheses, {'type': '(', 'pos': pos})
202 elseif line[pos] == ')'
203 if empty(opening.parentheses)
204 call add(closing.parentheses, {'type': ')', 'pos': pos})
206 let opening.parentheses = opening.parentheses[0:-2]
208 elseif line[pos] == '{'
209 call add(opening.braces, {'type': '{', 'pos': pos})
210 elseif line[pos] == '}'
211 if empty(opening.braces)
212 call add(closing.braces, {'type': '}', 'pos': pos})
214 let opening.braces = opening.braces[0:-2]
216 elseif line[pos] == '['
217 call add(opening.brackets, {'type': '[', 'pos': pos})
218 elseif line[pos] == ']'
219 if empty(opening.brackets)
220 call add(closing.brackets, {'type': ']', 'pos': pos})
222 let opening.brackets = opening.brackets[0:-2]
227 let pos = match(line, '[][(){}]', pos + 1)
230 " Find the rightmost brackets, since they're the ones that are important in
231 " both opening and closing cases
232 let rightmost_opening = {'type': '(', 'pos': -1}
233 let rightmost_closing = {'type': ')', 'pos': -1}
235 for opening in opening.parentheses + opening.braces + opening.brackets
236 if opening.pos > rightmost_opening.pos
237 let rightmost_opening = opening
241 for closing in closing.parentheses + closing.braces + closing.brackets
242 if closing.pos > rightmost_closing.pos
243 let rightmost_closing = closing
247 return [rightmost_opening, rightmost_closing]
250 function s:Match(lnum, regex)
251 let col = match(getline(a:lnum), '\C'.a:regex) + 1
252 return col > 0 && !s:IsInStringOrComment(a:lnum, col) ? col : 0
255 function s:MatchLast(lnum, regex)
256 let line = getline(a:lnum)
257 let col = match(line, '.*\zs' . a:regex)
258 while col != -1 && s:IsInStringOrComment(a:lnum, col)
259 let line = strpart(line, 0, col)
260 let col = match(line, '.*' . a:regex)
265 " 4. FalconGetIndent Routine {{{1
268 function FalconGetIndent(...)
269 " For the current line, use the first argument if given, else v:lnum
270 let clnum = a:0 ? a:1 : v:lnum
272 " Use zero indent at the top of the file
277 let line = getline(clnum)
280 " If we got a closing bracket on an empty line, find its match and indent
281 " according to it. For parentheses we indent to its column - 1, for the
282 " others we indent to the containing line's MSL's level. Return -1 if fail.
283 let col = matchend(line, '^\s*[]})]')
284 if col > 0 && !s:IsInStringOrComment(clnum, col)
285 call cursor(clnum, col)
286 let bs = strpart('(){}[]', stridx(')}]', line[col - 1]) * 2, 2)
287 if searchpair(escape(bs[0], '\['), '', bs[1], 'bW', s:skip_expr) > 0
288 if line[col-1]==')' && col('.') != col('$') - 1
289 let ind = virtcol('.') - 1
291 let ind = indent(s:GetMSL(line('.')))
297 " If we have a deindenting keyword, find its match and indent to its level.
298 " TODO: this is messy
299 if s:Match(clnum, s:falcon_deindent_keywords)
300 call cursor(clnum, 1)
301 if searchpair(s:end_start_regex, s:end_middle_regex, s:end_end_regex, 'bW',
302 \ s:end_skip_expr) > 0
303 let msl = s:GetMSL(line('.'))
304 let line = getline(line('.'))
306 if strpart(line, 0, col('.') - 1) =~ '=\s*$' &&
307 \ strpart(line, col('.') - 1, 2) !~ 'do'
308 let ind = virtcol('.') - 1
309 elseif getline(msl) =~ '=\s*\(#.*\)\=$'
310 let ind = indent(line('.'))
312 let ind = indent(msl)
318 " If we are in a multi-line string or line-comment, don't do anything to it.
319 if s:IsInString(clnum, matchend(line, '^\s*') + 1)
323 " Find a non-blank, non-multi-line string line above the current line.
324 let lnum = s:PrevNonBlankNonString(clnum - 1)
326 " If the line is empty and inside a string, use the previous line.
327 if line =~ '^\s*$' && lnum != prevnonblank(clnum - 1)
328 return indent(prevnonblank(clnum))
331 " At the start of the file use zero indent.
336 " Set up variables for the previous line.
337 let line = getline(lnum)
338 let ind = indent(lnum)
340 " If the previous line ended with a block opening, add a level of indent.
341 if s:Match(lnum, s:block_regex)
342 return indent(s:GetMSL(lnum)) + &sw
345 " If it contained hanging closing brackets, find the rightmost one, find its
346 " match and indent according to that.
347 if line =~ '[[({]' || line =~ '[])}]\s*\%(#.*\)\=$'
348 let [opening, closing] = s:ExtraBrackets(lnum)
351 if opening.type == '(' && searchpair('(', '', ')', 'bW', s:skip_expr) > 0
352 if col('.') + 1 == col('$')
358 let nonspace = matchend(line, '\S', opening.pos + 1) - 1
359 return nonspace > 0 ? nonspace : ind + &sw
361 elseif closing.pos != -1
362 call cursor(lnum, closing.pos + 1)
365 if s:Match(line('.'), s:falcon_indent_keywords)
366 return indent('.') + &sw
371 call cursor(clnum, vcol)
375 " If the previous line ended with an "end", match that "end"s beginning's
377 let col = s:Match(lnum, '\%(^\|[^.:@$]\)\<end\>\s*\%(#.*\)\=$')
379 call cursor(lnum, col)
380 if searchpair(s:end_start_regex, '', s:end_end_regex, 'bW',
381 \ s:end_skip_expr) > 0
383 let ind = indent('.')
384 let msl = s:GetMSL(n)
386 let ind = indent(msl)
392 let col = s:Match(lnum, s:falcon_indent_keywords)
394 call cursor(lnum, col)
395 let ind = virtcol('.') - 1 + &sw
396 " TODO: make this better (we need to count them) (or, if a searchpair
397 " fails, we know that something is lacking an end and thus we indent a
399 if s:Match(lnum, s:end_end_regex)
400 let ind = indent('.')
405 " Set up variables to use and search for MSL to the previous line.
407 let lnum = s:GetMSL(lnum)
409 " If the previous line wasn't a MSL and is continuation return its indent.
410 " TODO: the || s:IsInString() thing worries me a bit.
412 if s:Match(p_lnum, s:non_bracket_continuation_regex) || s:IsInString(p_lnum,strlen(line))
417 " Set up more variables, now that we know we wasn't continuation bound.
418 let line = getline(lnum)
419 let msl_ind = indent(lnum)
421 " If the MSL line had an indenting keyword in it, add a level of indent.
422 " TODO: this does not take into account contrived things such as
423 " module Foo; class Bar; end
424 if s:Match(lnum, s:falcon_indent_keywords)
425 let ind = msl_ind + &sw
426 if s:Match(lnum, s:end_end_regex)
432 " If the previous line ended with [*+/.,-=], but wasn't a block ending or a
433 " closing bracket, indent one extra level.
434 if s:Match(lnum, s:non_bracket_continuation_regex) && !s:Match(lnum, '^\s*\([\])}]\|end\)')
436 let ind = msl_ind + &sw
448 let &cpo = s:cpo_save
451 " vim: set sw=4 sts=4 et tw=80 :