diff options
Diffstat (limited to 'indent/elixir.vim')
-rw-r--r-- | indent/elixir.vim | 255 |
1 files changed, 156 insertions, 99 deletions
diff --git a/indent/elixir.vim b/indent/elixir.vim index 7ccaea12..3e7b6de0 100644 --- a/indent/elixir.vim +++ b/indent/elixir.vim @@ -2,141 +2,198 @@ if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'elixir') == -1 if exists("b:did_indent") finish -endif +end let b:did_indent = 1 setlocal nosmartindent setlocal indentexpr=GetElixirIndent() -setlocal indentkeys+=0),0],0=end,0=else,0=match,0=elsif,0=catch,0=after,0=rescue +setlocal indentkeys+=0),0],0=end,0=else,0=match,0=elsif,0=catch,0=after,0=rescue,0=\|> if exists("*GetElixirIndent") finish -endif +end let s:cpo_save = &cpo set cpo&vim -let s:no_colon_before = ':\@<!' -let s:no_colon_after = ':\@!' -let s:symbols_end = '\]\|}\|)' -let s:symbols_start = '\[\|{\|(' -let s:arrow = '^.*->$' -let s:skip_syntax = '\%(Comment\|String\)$' -let s:block_skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '".s:skip_syntax."'" -let s:block_start = '\<\%(do\|fn\)\>' -let s:block_middle = 'else\|match\|elsif\|catch\|after\|rescue' -let s:block_end = 'end' -let s:starts_with_pipeline = '^\s*|>.*$' +let s:no_colon_before = ':\@<!' +let s:no_colon_after = ':\@!' +let s:symbols_end = '\]\|}\|)' +let s:symbols_start = '\[\|{\|(' +let s:arrow = '^.*->$' +let s:skip_syntax = '\%(Comment\|String\)$' +let s:block_skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '".s:skip_syntax."'" +let s:block_start = '\<\%(do\|fn\)\>' +let s:block_middle = 'else\|match\|elsif\|catch\|after\|rescue' +let s:block_end = 'end' +let s:starts_with_pipeline = '^\s*|>.*$' let s:ending_with_assignment = '=\s*$' -let s:indent_keywords = '\<'.s:no_colon_before.'\%('.s:block_start.'\|'.s:block_middle.'\)$'.'\|'.s:arrow +let s:indent_keywords = '\<'.s:no_colon_before.'\%('.s:block_start.'\|'.s:block_middle.'\)$'.'\|'.s:arrow let s:deindent_keywords = '^\s*\<\%('.s:block_end.'\|'.s:block_middle.'\)\>'.'\|'.s:arrow -let s:pair_start = '\<\%('.s:no_colon_before.s:block_start.'\)\>'.s:no_colon_after -let s:pair_middle = '\<\%('.s:block_middle.'\)\>'.s:no_colon_after.'\zs' -let s:pair_end = '\<\%('.s:no_colon_before.s:block_end.'\)\>\zs' - -let s:inside_block = 0 - -function! GetElixirIndent() - let lnum = prevnonblank(v:lnum - 1) - - " At the start of the file use zero indent. - if lnum == 0 - return 0 - endif - - let opened_symbol = 0 - let current_line = getline(v:lnum) - let last_line = getline(lnum) - let ind = indent(lnum) +let s:pair_start = '\<\%('.s:no_colon_before.s:block_start.'\)\>'.s:no_colon_after +let s:pair_middle = '^\s*\%('.s:block_middle.'\)\>'.s:no_colon_after.'\zs' +let s:pair_end = '\<\%('.s:no_colon_before.s:block_end.'\)\>\zs' +function! s:is_indentable_syntax() " TODO: Remove these 2 lines " I don't know why, but for the test on spec/indent/lists_spec.rb:24. " Vim is making some mess on parsing the syntax of 'end', it is being " recognized as 'elixirString' when should be recognized as 'elixirBlock'. + call synID(s:current_line_ref, 1, 1) " This forces vim to sync the syntax. - call synID(v:lnum, 1, 1) syntax sync fromstart - if synIDattr(synID(v:lnum, 1, 1), "name") !~ s:skip_syntax - - if last_line !~ s:arrow - let split_line = split(last_line, '\zs') - let opened_symbol += count(split_line, '(') - count(split_line, ')') - let opened_symbol += count(split_line, '[') - count(split_line, ']') - let opened_symbol += count(split_line, '{') - count(split_line, '}') - end + return synIDattr(synID(s:current_line_ref, 1, 1), "name") + \ !~ s:skip_syntax +endfunction - " if start symbol is followed by a character, indent based on the - " whitespace after the symbol, otherwise use the default shiftwidth - if last_line =~ '\('.s:symbols_start.'\).' - let opened_prefix = matchlist(last_line, '\('.s:symbols_start.'\)\s*')[0] - let ind += (opened_symbol * strlen(opened_prefix)) +function! s:indent_opened_symbol(ind) + if s:opened_symbol > 0 + if s:pending_parenthesis > 0 + \ && s:last_line !~ '^\s*def' + \ && s:last_line !~ s:arrow + let b:old_ind = a:ind + return matchend(s:last_line, '(') + " if start symbol is followed by a character, indent based on the + " whitespace after the symbol, otherwise use the default shiftwidth + " Avoid negative indentation index + elseif s:last_line =~ '\('.s:symbols_start.'\).' + let regex = '\('.s:symbols_start.'\)\s*' + let opened_prefix = matchlist(s:last_line, regex)[0] + return a:ind + (s:opened_symbol * strlen(opened_prefix)) else - let ind += (opened_symbol * &sw) - endif - - if last_line =~ '^\s*\('.s:symbols_end.'\)' || last_line =~ s:indent_keywords - let ind += &sw - endif + return a:ind + (s:opened_symbol * &sw) + end + elseif s:opened_symbol < 0 + let ind = get(b:, 'old_ind', a:ind + (s:opened_symbol * &sw)) + let ind = float2nr(ceil(floor(ind)/&sw)*&sw) + return ind <= 0 ? 0 : ind + else + return a:ind + end +endfunction - if current_line =~ '^\s*\('.s:symbols_end.'\)' - let ind -= &sw - endif +function! s:indent_last_line_end_symbol_or_indent_keyword(ind) + if s:last_line =~ '^\s*\('.s:symbols_end.'\)' + \ || s:last_line =~ s:indent_keywords + return a:ind + &sw + else + return a:ind + end +endfunction - if last_line =~ s:ending_with_assignment && opened_symbol == 0 - let b:old_ind = indent(lnum) - let ind += &sw - end +function! s:indent_symbols_ending(ind) + if s:current_line =~ '^\s*\('.s:symbols_end.'\)' + return a:ind - &sw + else + return a:ind + end +endfunction - " if line starts with pipeline - " and last line ends with a pipeline, - " align them - if last_line =~ '|>.*$' && - \ current_line =~ s:starts_with_pipeline - let ind = float2nr(match(last_line, '|>') / &sw) * &sw +function! s:indent_assignment(ind) + if s:last_line =~ s:ending_with_assignment + let b:old_ind = indent(s:last_line_ref) " FIXME: side effect + return a:ind + &sw + else + return a:ind + end +endfunction +function! s:indent_pipeline(ind) + if s:last_line =~ s:starts_with_pipeline + \ && s:current_line =~ s:starts_with_pipeline + indent(s:last_line_ref) + elseif s:current_line =~ s:starts_with_pipeline + \ && s:last_line =~ '^[^=]\+=.\+$' + let b:old_ind = indent(s:last_line_ref) " if line starts with pipeline " and last line is an attribution " indents pipeline in same level as attribution - elseif current_line =~ s:starts_with_pipeline && - \ last_line =~ '^[^=]\+=.\+$' - - if !exists('b:old_ind') || b:old_ind == 0 - let b:old_ind = indent(lnum) - end - let ind = float2nr(matchend(last_line, '=\s*[^ ]') / &sw) * &sw - endif - - " if last line starts with pipeline - " and current line doesn't start with pipeline - " returns the indentation before the pipeline - if last_line =~ s:starts_with_pipeline && - \ current_line !~ s:starts_with_pipeline - let ind = b:old_ind - endif - - if current_line =~ s:deindent_keywords - let bslnum = searchpair( - \ s:pair_start, - \ s:pair_middle, - \ s:pair_end, - \ 'nbW', - \ s:block_skip - \ ) - - let ind = indent(bslnum) - endif + return match(s:last_line, '=\s*\zs[^ ]') + else + return a:ind + end +endfunction + +function! s:indent_after_pipeline(ind) + if s:last_line =~ s:starts_with_pipeline + if empty(substitute(s:current_line, ' ', '', 'g')) + \ || s:current_line =~ s:starts_with_pipeline + return indent(s:last_line_ref) + elseif s:last_line !~ s:indent_keywords + return b:old_ind + else + return a:ind + end + else + return a:ind + end +endfunction + +function! s:deindent_keyword(ind) + if s:current_line =~ s:deindent_keywords + let bslnum = searchpair( + \ s:pair_start, + \ s:pair_middle, + \ s:pair_end, + \ 'nbW', + \ s:block_skip + \ ) + + return indent(bslnum) + else + return a:ind + end +endfunction +function! s:indent_arrow(ind) + if s:current_line =~ s:arrow " indent case statements '->' - if current_line =~ s:arrow - let ind += &sw - endif - endif + return a:ind + &sw + else + return a:ind + end +endfunction - return ind +function! GetElixirIndent() + let s:current_line_ref = v:lnum + let s:last_line_ref = prevnonblank(s:current_line_ref - 1) + let s:current_line = getline(s:current_line_ref) + let s:last_line = getline(s:last_line_ref) + let s:pending_parenthesis = 0 + let s:opened_symbol = 0 + + if s:last_line !~ s:arrow + let splitted_line = split(s:last_line, '\zs') + let s:pending_parenthesis = + \ + count(splitted_line, '(') - count(splitted_line, ')') + let s:opened_symbol = + \ + s:pending_parenthesis + \ + count(splitted_line, '[') - count(splitted_line, ']') + \ + count(splitted_line, '{') - count(splitted_line, '}') + end + + if s:last_line_ref == 0 + " At the start of the file use zero indent. + return 0 + elseif !s:is_indentable_syntax() + " Current syntax is not indentable, keep last line indentation + return indent(s:last_line_ref) + else + let ind = indent(s:last_line_ref) + let ind = s:indent_opened_symbol(ind) + let ind = s:indent_symbols_ending(ind) + let ind = s:indent_pipeline(ind) + let ind = s:indent_after_pipeline(ind) + let ind = s:indent_assignment(ind) + let ind = s:indent_last_line_end_symbol_or_indent_keyword(ind) + let ind = s:deindent_keyword(ind) + let ind = s:indent_arrow(ind) + return ind + end endfunction let &cpo = s:cpo_save |