summaryrefslogtreecommitdiffstats
path: root/indent/elixir.vim
diff options
context:
space:
mode:
Diffstat (limited to 'indent/elixir.vim')
-rw-r--r--indent/elixir.vim255
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