Install vim74
[msysgit.git] / share / vim / vim74 / indent / falcon.vim
blob84b16d55f0d5228981af90ef05465a4def8d6373
1 " Vim indent file
2 " Language: Falcon
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.
7 " 1. Setup {{{1
8 " ============
10 " Only load this indent file when no other was loaded.
11 if exists("b:did_indent")
12     finish
13 endif
14 let b:did_indent = 1
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")
25     finish
26 endif
28 let s:cpo_save = &cpo
29 set cpo&vim
31 " 2. Variables {{{1
32 " ============
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.
49 let s:block_regex =
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\)'
73 " 3. Functions {{{1
74 " ============
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
79 endfunction
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
84 endfunction
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'
89 endfunction
91 " Find line above 'lnum' that isn't empty, in a comment, or in a string.
92 function s:PrevNonBlankNonString(lnum)
93     let in_block = 0
94     let lnum = prevnonblank(a:lnum)
95     while lnum > 0
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)
99         if line =~ '^=begin'
100             if in_block
101                 let in_block = 0
102             else
103                 break
104             endif
105         elseif !in_block && line =~ '^=end'
106             let in_block = 1
107         elseif !in_block && line !~ '^\s*#.*$' && !(s:IsInStringOrComment(lnum, 1)
108                     \ && s:IsInStringOrComment(lnum, strlen(line)))
109             break
110         endif
111         let lnum = prevnonblank(lnum - 1)
112     endwhile
113     return lnum
114 endfunction
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.
119     let msl = a:lnum
120     let msl_body = getline(msl)
121     let lnum = s:PrevNonBlankNonString(a:lnum - 1)
122     while lnum > 0
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)
126         
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.
131             "    
132             " Example:
133             "   method_call one,
134             "       two,
135             "           three
136             "           
137             let msl = lnum
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,
142             " and stop here.
143             " 
144             " Example:
145             "   method_call one,
146             "       two {
147             "           three
148             "
149             return lnum
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
154             "
155             " Example:
156             "   method_call(
157             "       other_method_call(
158             "             foo
159             return msl
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.
165             " 
166             " Example:
167             "   method_call do
168             "       something
169             "           something_else
170             return msl
171         else
172             let col = match(line, s:continuation_regex) + 1
173             if (col > 0 && !s:IsInStringOrComment(lnum, col))
174                         \ || s:IsInString(lnum, strlen(line))
175                 let msl = lnum
176             else
177                 break
178             endif
179         endif
180         
181         let msl_body = getline(msl)
182         let lnum = s:PrevNonBlankNonString(lnum - 1)
183     endwhile
184     return msl
185 endfunction
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.
198     while pos != -1
199         if !s:IsInStringOrComment(a:lnum, pos + 1)
200             if line[pos] == '('
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})
205                 else
206                     let opening.parentheses = opening.parentheses[0:-2]
207                 endif
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})
213                 else
214                     let opening.braces = opening.braces[0:-2]
215                 endif
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})
221                 else
222                     let opening.brackets = opening.brackets[0:-2]
223                 endif
224             endif
225         endif
226         
227         let pos = match(line, '[][(){}]', pos + 1)
228     endwhile
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
238         endif
239     endfor
241     for closing in closing.parentheses + closing.braces + closing.brackets
242         if closing.pos > rightmost_closing.pos
243             let rightmost_closing = closing
244         endif
245     endfor
247     return [rightmost_opening, rightmost_closing]
248 endfunction
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
253 endfunction
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)
261     endwhile
262     return col + 1
263 endfunction
265 " 4. FalconGetIndent Routine {{{1
266 " ============
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
273     if clnum == 0
274         return 0
275     endif
277     let line = getline(clnum)
278     let ind = -1
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
290             else
291                 let ind = indent(s:GetMSL(line('.')))
292             endif
293         endif
294         return ind
295     endif
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('.'))
311             else
312                 let ind = indent(msl)
313             endif
314         endif
315         return ind
316     endif
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)
320         return indent('.')
321     endif
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))
329     endif
331     " At the start of the file use zero indent.
332     if lnum == 0
333         return 0
334     endif
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
343     endif
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)
350         if opening.pos != -1
351             if opening.type == '(' && searchpair('(', '', ')', 'bW', s:skip_expr) > 0
352                 if col('.') + 1 == col('$')
353                     return ind + &sw
354                 else
355                     return virtcol('.')
356                 endif
357             else
358                 let nonspace = matchend(line, '\S', opening.pos + 1) - 1
359                 return nonspace > 0 ? nonspace : ind + &sw
360             endif
361         elseif closing.pos != -1
362             call cursor(lnum, closing.pos + 1)
363             normal! %
365             if s:Match(line('.'), s:falcon_indent_keywords)
366                 return indent('.') + &sw
367             else
368                 return indent('.')
369             endif
370         else
371             call cursor(clnum, vcol)
372         end
373     endif
375     " If the previous line ended with an "end", match that "end"s beginning's
376     " indent.
377     let col = s:Match(lnum, '\%(^\|[^.:@$]\)\<end\>\s*\%(#.*\)\=$')
378     if col > 0
379         call cursor(lnum, col)
380         if searchpair(s:end_start_regex, '', s:end_end_regex, 'bW',
381                     \ s:end_skip_expr) > 0
382             let n = line('.')
383             let ind = indent('.')
384             let msl = s:GetMSL(n)
385             if msl != n
386                 let ind = indent(msl)
387             end
388             return ind
389         endif
390     end
392     let col = s:Match(lnum, s:falcon_indent_keywords)
393     if col > 0
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
398         " level
399         if s:Match(lnum, s:end_end_regex)
400             let ind = indent('.')
401         endif
402         return ind
403     endif
405     " Set up variables to use and search for MSL to the previous line.
406     let p_lnum = lnum
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.
411     if p_lnum != lnum
412         if s:Match(p_lnum, s:non_bracket_continuation_regex) || s:IsInString(p_lnum,strlen(line))
413             return ind
414         endif
415     endif
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)
427             let ind = ind - &sw
428         endif
429         return ind
430     endif
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\)')
435         if lnum == p_lnum
436             let ind = msl_ind + &sw
437         else
438             let ind = msl_ind
439         endif
440         return ind
441     endif
443   return ind
444 endfunction
446 " }}}1
448 let &cpo = s:cpo_save
449 unlet s:cpo_save
451 " vim: set sw=4 sts=4 et tw=80 :