diff options
Diffstat (limited to 'runtime/indent/falcon.vim')
-rw-r--r-- | runtime/indent/falcon.vim | 455 |
1 files changed, 374 insertions, 81 deletions
diff --git a/runtime/indent/falcon.vim b/runtime/indent/falcon.vim index 46a228e8b..84b16d55f 100644 --- a/runtime/indent/falcon.vim +++ b/runtime/indent/falcon.vim @@ -2,13 +2,10 @@ " Language: Falcon " Maintainer: Steven Oliver <oliver.steven@gmail.com> " Website: https://steveno@github.com/steveno/falconpl-vim.git -" Credits: Thanks to the ruby.vim authors, I borrow a lot! -" Previous Maintainer: Brent A. Fulgham <bfulgham@debian.org> -" ----------------------------------------------------------- +" Credits: This is, to a great extent, a copy n' paste of ruby.vim. -"====================================== -" SETUP -"====================================== +" 1. Setup {{{1 +" ============ " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -19,7 +16,7 @@ let b:did_indent = 1 setlocal nosmartindent " Setup indent function and when to use it -setlocal indentexpr=FalconGetIndent() +setlocal indentexpr=FalconGetIndent(v:lnum) setlocal indentkeys=0{,0},0),0],!^F,o,O,e setlocal indentkeys+==~case,=~catch,=~default,=~elif,=~else,=~end,=~\" @@ -31,9 +28,8 @@ endif let s:cpo_save = &cpo set cpo&vim -"====================================== -" VARIABLES -"====================================== +" 2. Variables {{{1 +" ============ " Regex of syntax group names that are strings AND comments let s:syng_strcom = '\<falcon\%(String\|StringEscape\|Comment\)\>' @@ -41,6 +37,31 @@ let s:syng_strcom = '\<falcon\%(String\|StringEscape\|Comment\)\>' " Regex of syntax group names that are strings let s:syng_string = '\<falcon\%(String\|StringEscape\)\>' +" Regex that defines blocks. +" +" Note that there's a slight problem with this regex and s:continuation_regex. +" Code like this will be matched by both: +" +" method_call do |(a, b)| +" +" The reason is that the pipe matches a hanging "|" operator. +" +let s:block_regex = + \ '\%(\<do:\@!\>\|%\@<!{\)\s*\%(|\s*(*\s*\%([*@&]\=\h\w*,\=\s*\)\%(,\s*(*\s*[*@&]\=\h\w*\s*)*\s*\)*|\)\=\s*\%(#.*\)\=$' + +let s:block_continuation_regex = '^\s*[^])}\t ].*'.s:block_regex + +" Regex that defines continuation lines. +" TODO: this needs to deal with if ...: and so on +let s:continuation_regex = + \ '\%(%\@<![({[\\.,:*/%+]\|\<and\|\<or\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)\s*\%(#.*\)\=$' + +" Regex that defines bracket continuations +let s:bracket_continuation_regex = '%\@<!\%([({[]\)\s*\%(#.*\)\=$' + +" Regex that defines continuation lines, not including (, {, or [. +let s:non_bracket_continuation_regex = '\%([\\.,:*/%+]\|\<and\|\<or\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)\s*\%(#.*\)\=$' + " Keywords to indent on let s:falcon_indent_keywords = '^\s*\(case\|catch\|class\|enum\|default\|elif\|else' . \ '\|for\|function\|if.*"[^"]*:.*"\|if \(\(:\)\@!.\)*$\|loop\|object\|select' . @@ -49,109 +70,381 @@ let s:falcon_indent_keywords = '^\s*\(case\|catch\|class\|enum\|default\|elif\|e " Keywords to deindent on let s:falcon_deindent_keywords = '^\s*\(case\|catch\|default\|elif\|else\|end\)' -"====================================== -" FUNCTIONS -"====================================== +" 3. Functions {{{1 +" ============ -" Check if the character at lnum:col is inside a string +" Check if the character at lnum:col is inside a string, comment, or is ascii. function s:IsInStringOrComment(lnum, col) return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_strcom endfunction -"====================================== -" INDENT ROUTINE -"====================================== +" Check if the character at lnum:col is inside a string. +function s:IsInString(lnum, col) + return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_string +endfunction -function FalconGetIndent() - " Get the line to be indented - let cline = getline(v:lnum) +" Check if the character at lnum:col is inside a string delimiter +function s:IsInStringDelimiter(lnum, col) + return synIDattr(synID(a:lnum, a:col, 1), 'name') == 'falconStringDelimiter' +endfunction - " Don't reindent comments on first column - if cline =~ '^\/\/' - return 0 - endif +" Find line above 'lnum' that isn't empty, in a comment, or in a string. +function s:PrevNonBlankNonString(lnum) + let in_block = 0 + let lnum = prevnonblank(a:lnum) + while lnum > 0 + " Go in and out of blocks comments as necessary. + " If the line isn't empty (with opt. comment) or in a string, end search. + let line = getline(lnum) + if line =~ '^=begin' + if in_block + let in_block = 0 + else + break + endif + elseif !in_block && line =~ '^=end' + let in_block = 1 + elseif !in_block && line !~ '^\s*#.*$' && !(s:IsInStringOrComment(lnum, 1) + \ && s:IsInStringOrComment(lnum, strlen(line))) + break + endif + let lnum = prevnonblank(lnum - 1) + endwhile + return lnum +endfunction + +" Find line above 'lnum' that started the continuation 'lnum' may be part of. +function s:GetMSL(lnum) + " Start on the line we're at and use its indent. + let msl = a:lnum + let msl_body = getline(msl) + let lnum = s:PrevNonBlankNonString(a:lnum - 1) + while lnum > 0 + " If we have a continuation line, or we're in a string, use line as MSL. + " Otherwise, terminate search as we have found our MSL already. + let line = getline(lnum) + + if s:Match(line, s:non_bracket_continuation_regex) && + \ s:Match(msl, s:non_bracket_continuation_regex) + " If the current line is a non-bracket continuation and so is the + " previous one, keep its indent and continue looking for an MSL. + " + " Example: + " method_call one, + " two, + " three + " + let msl = lnum + elseif s:Match(lnum, s:non_bracket_continuation_regex) && + \ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex)) + " If the current line is a bracket continuation or a block-starter, but + " the previous is a non-bracket one, respect the previous' indentation, + " and stop here. + " + " Example: + " method_call one, + " two { + " three + " + return lnum + elseif s:Match(lnum, s:bracket_continuation_regex) && + \ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex)) + " If both lines are bracket continuations (the current may also be a + " block-starter), use the current one's and stop here + " + " Example: + " method_call( + " other_method_call( + " foo + return msl + elseif s:Match(lnum, s:block_regex) && + \ !s:Match(msl, s:continuation_regex) && + \ !s:Match(msl, s:block_continuation_regex) + " If the previous line is a block-starter and the current one is + " mostly ordinary, use the current one as the MSL. + " + " Example: + " method_call do + " something + " something_else + return msl + else + let col = match(line, s:continuation_regex) + 1 + if (col > 0 && !s:IsInStringOrComment(lnum, col)) + \ || s:IsInString(lnum, strlen(line)) + let msl = lnum + else + break + endif + endif + + let msl_body = getline(msl) + let lnum = s:PrevNonBlankNonString(lnum - 1) + endwhile + return msl +endfunction + +" Check if line 'lnum' has more opening brackets than closing ones. +function s:ExtraBrackets(lnum) + let opening = {'parentheses': [], 'braces': [], 'brackets': []} + let closing = {'parentheses': [], 'braces': [], 'brackets': []} + + let line = getline(a:lnum) + let pos = match(line, '[][(){}]', 0) + + " Save any encountered opening brackets, and remove them once a matching + " closing one has been found. If a closing bracket shows up that doesn't + " close anything, save it for later. + while pos != -1 + if !s:IsInStringOrComment(a:lnum, pos + 1) + if line[pos] == '(' + call add(opening.parentheses, {'type': '(', 'pos': pos}) + elseif line[pos] == ')' + if empty(opening.parentheses) + call add(closing.parentheses, {'type': ')', 'pos': pos}) + else + let opening.parentheses = opening.parentheses[0:-2] + endif + elseif line[pos] == '{' + call add(opening.braces, {'type': '{', 'pos': pos}) + elseif line[pos] == '}' + if empty(opening.braces) + call add(closing.braces, {'type': '}', 'pos': pos}) + else + let opening.braces = opening.braces[0:-2] + endif + elseif line[pos] == '[' + call add(opening.brackets, {'type': '[', 'pos': pos}) + elseif line[pos] == ']' + if empty(opening.brackets) + call add(closing.brackets, {'type': ']', 'pos': pos}) + else + let opening.brackets = opening.brackets[0:-2] + endif + endif + endif + + let pos = match(line, '[][(){}]', pos + 1) + endwhile + + " Find the rightmost brackets, since they're the ones that are important in + " both opening and closing cases + let rightmost_opening = {'type': '(', 'pos': -1} + let rightmost_closing = {'type': ')', 'pos': -1} + + for opening in opening.parentheses + opening.braces + opening.brackets + if opening.pos > rightmost_opening.pos + let rightmost_opening = opening + endif + endfor + + for closing in closing.parentheses + closing.braces + closing.brackets + if closing.pos > rightmost_closing.pos + let rightmost_closing = closing + endif + endfor + + return [rightmost_opening, rightmost_closing] +endfunction + +function s:Match(lnum, regex) + let col = match(getline(a:lnum), '\C'.a:regex) + 1 + return col > 0 && !s:IsInStringOrComment(a:lnum, col) ? col : 0 +endfunction - " Find the previous non-blank line - let lnum = prevnonblank(v:lnum - 1) +function s:MatchLast(lnum, regex) + let line = getline(a:lnum) + let col = match(line, '.*\zs' . a:regex) + while col != -1 && s:IsInStringOrComment(a:lnum, col) + let line = strpart(line, 0, col) + let col = match(line, '.*' . a:regex) + endwhile + return col + 1 +endfunction + +" 4. FalconGetIndent Routine {{{1 +" ============ + +function FalconGetIndent(...) + " For the current line, use the first argument if given, else v:lnum + let clnum = a:0 ? a:1 : v:lnum " Use zero indent at the top of the file - if lnum == 0 + if clnum == 0 return 0 endif - let prevline=getline(lnum) - let ind = indent(lnum) - let chg = 0 + let line = getline(clnum) + let ind = -1 + + " If we got a closing bracket on an empty line, find its match and indent + " according to it. For parentheses we indent to its column - 1, for the + " others we indent to the containing line's MSL's level. Return -1 if fail. + let col = matchend(line, '^\s*[]})]') + if col > 0 && !s:IsInStringOrComment(clnum, col) + call cursor(clnum, col) + let bs = strpart('(){}[]', stridx(')}]', line[col - 1]) * 2, 2) + if searchpair(escape(bs[0], '\['), '', bs[1], 'bW', s:skip_expr) > 0 + if line[col-1]==')' && col('.') != col('$') - 1 + let ind = virtcol('.') - 1 + else + let ind = indent(s:GetMSL(line('.'))) + endif + endif + return ind + endif + + " If we have a deindenting keyword, find its match and indent to its level. + " TODO: this is messy + if s:Match(clnum, s:falcon_deindent_keywords) + call cursor(clnum, 1) + if searchpair(s:end_start_regex, s:end_middle_regex, s:end_end_regex, 'bW', + \ s:end_skip_expr) > 0 + let msl = s:GetMSL(line('.')) + let line = getline(line('.')) + + if strpart(line, 0, col('.') - 1) =~ '=\s*$' && + \ strpart(line, col('.') - 1, 2) !~ 'do' + let ind = virtcol('.') - 1 + elseif getline(msl) =~ '=\s*\(#.*\)\=$' + let ind = indent(line('.')) + else + let ind = indent(msl) + endif + endif + return ind + endif + + " If we are in a multi-line string or line-comment, don't do anything to it. + if s:IsInString(clnum, matchend(line, '^\s*') + 1) + return indent('.') + endif + + " Find a non-blank, non-multi-line string line above the current line. + let lnum = s:PrevNonBlankNonString(clnum - 1) - " If we are in a multi-line string or line-comment, don't do anything - if s:IsInStringOrComment(v:lnum, matchend(cline, '^\s*') + 1 ) - return indent('.') + " If the line is empty and inside a string, use the previous line. + if line =~ '^\s*$' && lnum != prevnonblank(clnum - 1) + return indent(prevnonblank(clnum)) endif - " If the start of the line equals a double quote, then indent to the - " previous lines first double quote - if cline =~? '^\s*"' - let chg = chg + &sw + " At the start of the file use zero indent. + if lnum == 0 + return 0 endif - " If previous line started with a double quote and this one - " doesn't, unindent - if prevline =~? '^\s*"' && cline =~? '^\s*' - let chg = chg - &sw + " Set up variables for the previous line. + let line = getline(lnum) + let ind = indent(lnum) + + " If the previous line ended with a block opening, add a level of indent. + if s:Match(lnum, s:block_regex) + return indent(s:GetMSL(lnum)) + &sw endif - " Indent if proper keyword - if prevline =~? s:falcon_indent_keywords - let chg = &sw - " If previous line opened a parenthesis, and did not close it, indent - elseif prevline =~ '^.*(\s*[^)]*\((.*)\)*[^)]*$' - " Make sure this isn't just a function split between two lines - if prevline =~ ',\s*$' - return indent(prevnonblank(v:lnum - 1)) + &sw - else - return match(prevline, '(.*\((.*)\|[^)]\)*.*$') + 1 - endif - elseif prevline =~ '^[^(]*)\s*$' - " This line closes a parenthesis. Finds opening. - let curr_line = prevnonblank(lnum - 1) - while curr_line >= 0 - let str = getline(curr_line) - if str !~ '^.*(\s*[^)]*\((.*)\)*[^)]*$' - let curr_line = prevnonblank(curr_line - 1) - else - break - endif - endwhile - if curr_line < 0 - return -1 - endif - let ind = indent(curr_line) + " If it contained hanging closing brackets, find the rightmost one, find its + " match and indent according to that. + if line =~ '[[({]' || line =~ '[])}]\s*\%(#.*\)\=$' + let [opening, closing] = s:ExtraBrackets(lnum) + + if opening.pos != -1 + if opening.type == '(' && searchpair('(', '', ')', 'bW', s:skip_expr) > 0 + if col('.') + 1 == col('$') + return ind + &sw + else + return virtcol('.') + endif + else + let nonspace = matchend(line, '\S', opening.pos + 1) - 1 + return nonspace > 0 ? nonspace : ind + &sw + endif + elseif closing.pos != -1 + call cursor(lnum, closing.pos + 1) + normal! % + + if s:Match(line('.'), s:falcon_indent_keywords) + return indent('.') + &sw + else + return indent('.') + endif + else + call cursor(clnum, vcol) + end endif - " If previous line ends in a semi-colon reset indent to previous - " lines setting - if prevline =~? ';\s*$' && prevnonblank(prevline) =~? ',\s*$' - let chg = chg - (2 * &sw) + " If the previous line ended with an "end", match that "end"s beginning's + " indent. + let col = s:Match(lnum, '\%(^\|[^.:@$]\)\<end\>\s*\%(#.*\)\=$') + if col > 0 + call cursor(lnum, col) + if searchpair(s:end_start_regex, '', s:end_end_regex, 'bW', + \ s:end_skip_expr) > 0 + let n = line('.') + let ind = indent('.') + let msl = s:GetMSL(n) + if msl != n + let ind = indent(msl) + end + return ind + endif + end + + let col = s:Match(lnum, s:falcon_indent_keywords) + if col > 0 + call cursor(lnum, col) + let ind = virtcol('.') - 1 + &sw + " TODO: make this better (we need to count them) (or, if a searchpair + " fails, we know that something is lacking an end and thus we indent a + " level + if s:Match(lnum, s:end_end_regex) + let ind = indent('.') + endif + return ind endif - " If previous line ended in a comma, indent again - if prevline =~? ',\s*$' - let chg = chg + &sw + " Set up variables to use and search for MSL to the previous line. + let p_lnum = lnum + let lnum = s:GetMSL(lnum) + + " If the previous line wasn't a MSL and is continuation return its indent. + " TODO: the || s:IsInString() thing worries me a bit. + if p_lnum != lnum + if s:Match(p_lnum, s:non_bracket_continuation_regex) || s:IsInString(p_lnum,strlen(line)) + return ind + endif endif - " If previous line ended in a =>, indent again - if prevline =~? '=>\s*$' - let chg = chg + &sw + " Set up more variables, now that we know we wasn't continuation bound. + let line = getline(lnum) + let msl_ind = indent(lnum) + + " If the MSL line had an indenting keyword in it, add a level of indent. + " TODO: this does not take into account contrived things such as + " module Foo; class Bar; end + if s:Match(lnum, s:falcon_indent_keywords) + let ind = msl_ind + &sw + if s:Match(lnum, s:end_end_regex) + let ind = ind - &sw + endif + return ind endif - " Deindent on proper keywords - if cline =~? s:falcon_deindent_keywords - let chg = chg - &sw + " If the previous line ended with [*+/.,-=], but wasn't a block ending or a + " closing bracket, indent one extra level. + if s:Match(lnum, s:non_bracket_continuation_regex) && !s:Match(lnum, '^\s*\([\])}]\|end\)') + if lnum == p_lnum + let ind = msl_ind + &sw + else + let ind = msl_ind + endif + return ind endif - return ind + chg + return ind endfunction +" }}}1 + let &cpo = s:cpo_save unlet s:cpo_save |