summaryrefslogtreecommitdiffstats
path: root/autoload/elixir/indent.vim
diff options
context:
space:
mode:
Diffstat (limited to 'autoload/elixir/indent.vim')
-rw-r--r--autoload/elixir/indent.vim327
1 files changed, 104 insertions, 223 deletions
diff --git a/autoload/elixir/indent.vim b/autoload/elixir/indent.vim
index aca5a2fd..20271ac5 100644
--- a/autoload/elixir/indent.vim
+++ b/autoload/elixir/indent.vim
@@ -23,22 +23,18 @@ function! elixir#indent#indent(lnum)
let handlers = [
\'top_of_file',
- \'starts_with_end',
- \'starts_with_mid_or_end_block_keyword',
- \'following_trailing_do',
- \'following_trailing_rocket',
\'following_trailing_binary_operator',
\'starts_with_pipe',
- \'starts_with_close_bracket',
\'starts_with_binary_operator',
- \'inside_nested_construct',
- \'starts_with_comment',
+ \'inside_block',
+ \'starts_with_end',
\'inside_generic_block',
\'follow_prev_nb'
\]
for handler in handlers
call s:debug('testing handler elixir#indent#handle_'.handler)
- let indent = function('elixir#indent#handle_'.handler)(lnum, text, prev_nb_lnum, prev_nb_text)
+ let context = {'lnum': lnum, 'text': text, 'prev_nb_lnum': prev_nb_lnum, 'prev_nb_text': prev_nb_text}
+ let indent = function('elixir#indent#handle_'.handler)(context)
if indent != -1
call s:debug('line '.lnum.': elixir#indent#handle_'.handler.' returned '.indent)
call cursor(curs_lnum, curs_col)
@@ -57,9 +53,17 @@ function! s:debug(str)
endif
endfunction
+function! s:starts_with(context, expr)
+ return s:_starts_with(a:context.text, a:expr, a:context.lnum)
+endfunction
+
+function! s:prev_starts_with(context, expr)
+ return s:_starts_with(a:context.prev_nb_text, a:expr, a:context.prev_nb_lnum)
+endfunction
+
" Returns 0 or 1 based on whether or not the text starts with the given
" expression and is not a string or comment
-function! s:starts_with(text, expr, lnum)
+function! s:_starts_with(text, expr, lnum)
let pos = match(a:text, '^\s*'.a:expr)
if pos == -1
return 0
@@ -74,9 +78,13 @@ function! s:starts_with(text, expr, lnum)
end
endfunction
+function! s:prev_ends_with(context, expr)
+ return s:_ends_with(a:context.prev_nb_text, a:expr, a:context.prev_nb_lnum)
+endfunction
+
" Returns 0 or 1 based on whether or not the text ends with the given
" expression and is not a string or comment
-function! s:ends_with(text, expr, lnum)
+function! s:_ends_with(text, expr, lnum)
let pos = match(a:text, a:expr.'\s*$')
if pos == -1
return 0
@@ -140,16 +148,16 @@ function! s:find_last_pos(lnum, text, match)
return -1
endfunction
-function! elixir#indent#handle_top_of_file(_lnum, _text, prev_nb_lnum, _prev_nb_text)
- if a:prev_nb_lnum == 0
+function! elixir#indent#handle_top_of_file(context)
+ if a:context.prev_nb_lnum == 0
return 0
else
return -1
end
endfunction
-function! elixir#indent#handle_follow_prev_nb(_lnum, _text, prev_nb_lnum, prev_nb_text)
- return s:get_base_indent(a:prev_nb_lnum, a:prev_nb_text)
+function! elixir#indent#handle_follow_prev_nb(context)
+ return s:get_base_indent(a:context.prev_nb_lnum, a:context.prev_nb_text)
endfunction
" Given the line at `lnum`, returns the indent of the line that acts as the 'base indent'
@@ -163,13 +171,13 @@ function! s:get_base_indent(lnum, text)
let data_structure_close = '\%(\]\|}\|)\)'
let pipe = '|>'
- if s:starts_with(a:text, binary_operator, a:lnum)
+ if s:_starts_with(a:text, binary_operator, a:lnum)
return s:get_base_indent(prev_nb_lnum, prev_nb_text)
- elseif s:starts_with(a:text, pipe, a:lnum)
+ elseif s:_starts_with(a:text, pipe, a:lnum)
return s:get_base_indent(prev_nb_lnum, prev_nb_text)
- elseif s:ends_with(prev_nb_text, binary_operator, prev_nb_lnum)
+ elseif s:_ends_with(prev_nb_text, binary_operator, prev_nb_lnum)
return s:get_base_indent(prev_nb_lnum, prev_nb_text)
- elseif s:ends_with(a:text, data_structure_close, a:lnum)
+ elseif s:_ends_with(a:text, data_structure_close, a:lnum)
let data_structure_open = '\%(\[\|{\|(\)'
let close_match_idx = match(a:text, data_structure_close . '\s*$')
call cursor(a:lnum, close_match_idx + 1)
@@ -181,54 +189,26 @@ function! s:get_base_indent(lnum, text)
endif
endfunction
-function! elixir#indent#handle_following_trailing_do(lnum, text, prev_nb_lnum, prev_nb_text)
- if s:ends_with(a:prev_nb_text, s:keyword('do'), a:prev_nb_lnum)
- if s:starts_with(a:text, s:keyword('end'), a:lnum)
- return indent(a:prev_nb_lnum)
- else
- return indent(a:prev_nb_lnum) + s:sw()
- end
- else
- return -1
- endif
-endfunction
-
-function! elixir#indent#handle_following_trailing_rocket(lnum, text, prev_nb_lnum, prev_nb_text)
- if s:ends_with(a:prev_nb_text, '->', a:prev_nb_lnum)
- return indent(a:prev_nb_lnum) + s:sw()
- else
- return -1
- endif
-endfunction
-
-function! elixir#indent#handle_following_trailing_binary_operator(lnum, text, prev_nb_lnum, prev_nb_text)
+function! elixir#indent#handle_following_trailing_binary_operator(context)
let binary_operator = '\%(=\|<>\|>>>\|<=\|||\|+\|\~\~\~\|-\|&&\|<<<\|/\|\^\^\^\|\*\)'
- if s:ends_with(a:prev_nb_text, binary_operator, a:prev_nb_lnum)
- return indent(a:prev_nb_lnum) + s:sw()
+ if s:prev_ends_with(a:context, binary_operator)
+ return indent(a:context.prev_nb_lnum) + s:sw()
else
return -1
endif
endfunction
-function! elixir#indent#handle_following_prev_end(_lnum, _text, prev_nb_lnum, prev_nb_text)
- if s:ends_with(a:prev_nb_text, s:keyword('end'), a:prev_nb_lnum)
- return indent(a:prev_nb_lnum)
- else
- return -1
- endif
-endfunction
-
-function! elixir#indent#handle_starts_with_pipe(lnum, text, prev_nb_lnum, prev_nb_text)
- if s:starts_with(a:text, '|>', a:lnum)
+function! elixir#indent#handle_starts_with_pipe(context)
+ if s:starts_with(a:context, '|>')
let match_operator = '\%(!\|=\|<\|>\)\@<!=\%(=\|>\|\~\)\@!'
- let pos = s:find_last_pos(a:prev_nb_lnum, a:prev_nb_text, match_operator)
+ let pos = s:find_last_pos(a:context.prev_nb_lnum, a:context.prev_nb_text, match_operator)
if pos == -1
- return indent(a:prev_nb_lnum)
+ return indent(a:context.prev_nb_lnum)
else
- let next_word_pos = match(strpart(a:prev_nb_text, pos+1, len(a:prev_nb_text)-1), '\S')
+ let next_word_pos = match(strpart(a:context.prev_nb_text, pos+1, len(a:context.prev_nb_text)-1), '\S')
if next_word_pos == -1
- return indent(a:prev_nb_lnum) + s:sw()
+ return indent(a:context.prev_nb_lnum) + s:sw()
else
return pos + 1 + next_word_pos
end
@@ -238,16 +218,8 @@ function! elixir#indent#handle_starts_with_pipe(lnum, text, prev_nb_lnum, prev_n
endif
endfunction
-function! elixir#indent#handle_starts_with_comment(_lnum, text, prev_nb_lnum, _prev_nb_text)
- if match(a:text, '^\s*#') != -1
- return indent(a:prev_nb_lnum)
- else
- return -1
- endif
-endfunction
-
-function! elixir#indent#handle_starts_with_end(lnum, text, _prev_nb_lnum, _prev_nb_text)
- if s:starts_with(a:text, s:keyword('end'), a:lnum)
+function! elixir#indent#handle_starts_with_end(context)
+ if s:starts_with(a:context, s:keyword('end'))
let pair_lnum = searchpair(s:keyword('do\|fn'), '', s:keyword('end').'\zs', 'bnW', "line('.') == " . line('.') . " || elixir#indent#searchpair_back_skip()")
return indent(pair_lnum)
else
@@ -255,36 +227,18 @@ function! elixir#indent#handle_starts_with_end(lnum, text, _prev_nb_lnum, _prev_
endif
endfunction
-function! elixir#indent#handle_starts_with_mid_or_end_block_keyword(lnum, text, _prev_nb_lnum, _prev_nb_text)
- if s:starts_with(a:text, s:keyword('catch\|rescue\|after\|else'), a:lnum)
- let pair_lnum = searchpair(s:keyword('with\|receive\|try\|if\|fn'), s:keyword('catch\|rescue\|after\|else').'\zs', s:keyword('end'), 'bnW', "line('.') == " . line('.') . " || elixir#indent#searchpair_back_skip()")
- return indent(pair_lnum)
- else
- return -1
- endif
-endfunction
-
-function! elixir#indent#handle_starts_with_close_bracket(lnum, text, _prev_nb_lnum, _prev_nb_text)
- if s:starts_with(a:text, '\%(\]\|}\|)\)', a:lnum)
- let pair_lnum = searchpair('\%(\[\|{\|(\)', '', '\%(\]\|}\|)\)', 'bnW', "line('.') == " . line('.') . " || elixir#indent#searchpair_back_skip()")
- return indent(pair_lnum)
- else
- return -1
- endif
-endfunction
-
-function! elixir#indent#handle_starts_with_binary_operator(lnum, text, prev_nb_lnum, prev_nb_text)
+function! elixir#indent#handle_starts_with_binary_operator(context)
let binary_operator = '\%(=\|<>\|>>>\|<=\|||\|+\|\~\~\~\|-\|&&\|<<<\|/\|\^\^\^\|\*\)'
- if s:starts_with(a:text, binary_operator, a:lnum)
+ if s:starts_with(a:context, binary_operator)
let match_operator = '\%(!\|=\|<\|>\)\@<!=\%(=\|>\|\~\)\@!'
- let pos = s:find_last_pos(a:prev_nb_lnum, a:prev_nb_text, match_operator)
+ let pos = s:find_last_pos(a:context.prev_nb_lnum, a:context.prev_nb_text, match_operator)
if pos == -1
- return indent(a:prev_nb_lnum)
+ return indent(a:context.prev_nb_lnum)
else
- let next_word_pos = match(strpart(a:prev_nb_text, pos+1, len(a:prev_nb_text)-1), '\S')
+ let next_word_pos = match(strpart(a:context.prev_nb_text, pos+1, len(a:context.prev_nb_text)-1), '\S')
if next_word_pos == -1
- return indent(a:prev_nb_lnum) + s:sw()
+ return indent(a:context.prev_nb_lnum) + s:sw()
else
return pos + 1 + next_word_pos
end
@@ -298,162 +252,89 @@ endfunction
" nested structure. For example, we might be in a function in a map in a
" function, etc... so we need to first figure out what the innermost structure
" is then forward execution to the proper handler
-function! elixir#indent#handle_inside_nested_construct(lnum, text, prev_nb_lnum, prev_nb_text)
+function! elixir#indent#handle_inside_block(context)
let start_pattern = '\C\%(\<with\>\|\<if\>\|\<case\>\|\<cond\>\|\<try\>\|\<receive\>\|\<fn\>\|{\|\[\|(\)'
let end_pattern = '\C\%(\<end\>\|\]\|}\|)\)'
- let pair_info = searchpairpos(start_pattern, '', end_pattern, 'bnW', "line('.') == " . line('.') . " || elixir#indent#searchpair_back_skip()", max([0, a:lnum - g:elixir_indent_max_lookbehind]))
- let pair_lnum = pair_info[0]
- let pair_col = pair_info[1]
- if pair_lnum != 0 || pair_col != 0
- let pair_text = getline(pair_lnum)
- let pair_char = pair_text[pair_col - 1]
- if pair_char == 'f'
- call s:debug("testing s:do_handle_inside_fn")
- return s:do_handle_inside_fn(pair_lnum, pair_col, a:lnum, a:text, a:prev_nb_lnum, a:prev_nb_text)
- elseif pair_char == '['
- call s:debug("testing s:do_handle_inside_square_brace")
- return s:do_handle_inside_square_brace(pair_lnum, pair_col, a:lnum, a:text, a:prev_nb_lnum, a:prev_nb_text)
- elseif pair_char == '{'
- call s:debug("testing s:do_handle_inside_curly_brace")
- return s:do_handle_inside_curly_brace(pair_lnum, pair_col, a:lnum, a:text, a:prev_nb_lnum, a:prev_nb_text)
- elseif pair_char == '('
- call s:debug("testing s:do_handle_inside_parens")
- return s:do_handle_inside_parens(pair_lnum, pair_col, a:lnum, a:text, a:prev_nb_lnum, a:prev_nb_text)
- elseif pair_char == 'w'
- call s:debug("testing s:do_handle_inside_with")
- return s:do_handle_inside_with(pair_lnum, pair_col, a:lnum, a:text, a:prev_nb_lnum, a:prev_nb_text)
+ " hack - handle do: better
+ let block_info = searchpairpos(start_pattern, '', end_pattern, 'bnW', "line('.') == " . line('.') . " || elixir#indent#searchpair_back_skip() || getline(line('.')) =~ 'do:'", max([0, a:context.lnum - g:elixir_indent_max_lookbehind]))
+ let block_start_lnum = block_info[0]
+ let block_start_col = block_info[1]
+ if block_start_lnum != 0 || block_start_col != 0
+ let block_text = getline(block_start_lnum)
+ let block_start_char = block_text[block_start_col - 1]
+
+ let never_match = '\(a\)\@=b'
+ let config = {
+ \'f': {'aligned_clauses': s:keyword('end'), 'pattern_match_clauses': never_match},
+ \'c': {'aligned_clauses': s:keyword('end'), 'pattern_match_clauses': never_match},
+ \'t': {'aligned_clauses': s:keyword('end\|catch\|rescue\|after'), 'pattern_match_clauses': s:keyword('catch\|rescue')},
+ \'r': {'aligned_clauses': s:keyword('end\|after'), 'pattern_match_clauses': s:keyword('after')},
+ \'i': {'aligned_clauses': s:keyword('end\|else'), 'pattern_match_clauses': never_match},
+ \'[': {'aligned_clauses': ']', 'pattern_match_clauses': never_match},
+ \'{': {'aligned_clauses': '}', 'pattern_match_clauses': never_match},
+ \'(': {'aligned_clauses': ')', 'pattern_match_clauses': never_match}
+ \}
+
+ if block_start_char == 'w'
+ call s:debug("testing s:handle_with")
+ return s:handle_with(block_start_lnum, block_start_col, a:context)
else
- call s:debug("testing s:do_handle_inside_keyword_block")
- return s:do_handle_inside_keyword_block(pair_lnum, pair_col, a:lnum, a:text, a:prev_nb_lnum, a:prev_nb_text)
+ let block_config = config[block_start_char]
+ if s:starts_with(a:context, block_config.aligned_clauses)
+ call s:debug("clause")
+ return indent(block_start_lnum)
+ else
+ let clause_lnum = searchpair(block_config.pattern_match_clauses, '', '*', 'bnW', "line('.') == " . line('.') . " || elixir#indent#searchpair_back_skip()", block_start_lnum)
+ let relative_lnum = max([clause_lnum, block_start_lnum])
+ call s:debug("pattern matching relative to lnum " . relative_lnum)
+ return s:do_handle_pattern_match_block(relative_lnum, a:context)
+ endif
end
else
return -1
end
endfunction
-function! s:do_handle_inside_with(pair_lnum, pair_col, lnum, text, prev_nb_lnum, prev_nb_text)
- if a:pair_lnum == a:lnum
- " This is the `with` line or an inline `with`/`do`
- call s:debug("current line is `with`")
- return -1
- else
- " Determine if in with/do, do/else|end, or else/end
- let start_pattern = '\C\%(\<with\>\|\<else\>\|\<do\>\)'
- let end_pattern = '\C\%(\<end\>\)'
- let pair_info = searchpairpos(start_pattern, '', end_pattern, 'bnW', "line('.') == " . line('.') . " || elixir#indent#searchpair_back_skip()")
- let pair_lnum = pair_info[0]
- let pair_col = pair_info[1]
-
- let pair_text = getline(pair_lnum)
- let pair_char = pair_text[pair_col - 1]
-
- if s:starts_with(a:text, '\Cdo:', a:lnum)
- call s:debug("current line is do:")
- return pair_col - 1 + s:sw()
- elseif s:starts_with(a:text, '\Celse:', a:lnum)
- call s:debug("current line is else:")
- return pair_col - 1
- elseif s:starts_with(a:text, '\C\(\<do\>\|\<else\>\)', a:lnum)
- call s:debug("current line is do/else")
- return pair_col - 1
- elseif s:starts_with(pair_text, '\C\(do\|else\):', pair_lnum)
- call s:debug("inside do:/else:")
- return pair_col - 1 + s:sw()
- elseif pair_char == 'w'
- call s:debug("inside with/do")
- return pair_col + 4
- elseif pair_char == 'd'
- call s:debug("inside do/else|end")
- return pair_col - 1 + s:sw()
- else
- call s:debug("inside else/end")
- return s:do_handle_inside_pattern_match_block(pair_lnum, a:text, a:prev_nb_lnum, a:prev_nb_text)
- end
- end
-endfunction
+function! s:handle_with(start_lnum, start_col, context)
+ let block_info = searchpairpos('\C\%(\<with\>\|\<do\>\|\<else\>\)', '', s:keyword('end'), 'bnW', "line('.') == " . line('.') . " || elixir#indent#searchpair_back_skip()")
+ let block_start_lnum = block_info[0]
+ let block_start_col = block_info[1]
-function! s:do_handle_inside_keyword_block(pair_lnum, _pair_col, _lnum, text, prev_nb_lnum, prev_nb_text)
- let keyword_pattern = '\C\%(\<case\>\|\<cond\>\|\<try\>\|\<receive\>\|\<after\>\|\<catch\>\|\<rescue\>\|\<else\>\)'
- if a:pair_lnum
- " last line is a "receive" or something
- if s:starts_with(a:prev_nb_text, keyword_pattern, a:prev_nb_lnum)
- call s:debug("prev nb line is keyword")
- return indent(a:prev_nb_lnum) + s:sw()
- else
- return s:do_handle_inside_pattern_match_block(a:pair_lnum, a:text, a:prev_nb_lnum, a:prev_nb_text)
- end
- else
- return -1
- endif
-endfunction
+ let block_start_text = getline(block_start_lnum)
+ let block_start_char = block_start_text[block_start_col - 1]
-" Implements indent for pattern-matching blocks (e.g. case, fn, with/else)
-function! s:do_handle_inside_pattern_match_block(block_start_lnum, text, prev_nb_lnum, prev_nb_text)
- if a:text =~ '->'
- call s:debug("current line contains ->")
- return indent(a:block_start_lnum) + s:sw()
- elseif a:prev_nb_text =~ '->'
- call s:debug("prev nb line contains ->")
- return indent(a:prev_nb_lnum) + s:sw()
+ if s:starts_with(a:context, s:keyword('do\|else\|end'))
+ return indent(a:start_lnum)
+ elseif block_start_char == 'w' || s:starts_with(a:context, '\C\(do\|else\):')
+ return indent(a:start_lnum) + 5
+ elseif s:_starts_with(block_start_text, '\C\(do\|else\):', a:start_lnum)
+ return indent(block_start_lnum) + s:sw()
else
- return indent(a:prev_nb_lnum)
+ return s:do_handle_pattern_match_block(a:start_lnum, a:context)
end
endfunction
-function! s:do_handle_inside_fn(pair_lnum, _pair_col, lnum, text, prev_nb_lnum, prev_nb_text)
- if a:pair_lnum && a:pair_lnum != a:lnum
- return s:do_handle_inside_pattern_match_block(a:pair_lnum, a:text, a:prev_nb_lnum, a:prev_nb_text)
+function! s:do_handle_pattern_match_block(relative_line, context)
+ let relative_indent = indent(a:relative_line)
+ " hack!
+ if a:context.text =~ '\(fn.*\)\@<!->'
+ call s:debug("current line contains ->; assuming match definition")
+ return relative_indent + s:sw()
+ elseif search('\(fn.*\)\@<!->', 'bnW', a:relative_line) != 0
+ call s:debug("a previous line contains ->; assuming match handler")
+ return relative_indent + 2 * s:sw()
else
- return -1
- endif
-endfunction
-
-function! s:do_handle_inside_square_brace(pair_lnum, pair_col, _lnum, _text, _prev_nb_lnum, _prev_nb_text)
- " If in list...
- if a:pair_lnum != 0 || a:pair_col != 0
- let pair_text = getline(a:pair_lnum)
- let substr = strpart(pair_text, a:pair_col, len(pair_text)-1)
- let indent_pos = match(substr, '\S')
- if indent_pos != -1
- return indent_pos + a:pair_col
- else
- return indent(a:pair_lnum) + s:sw()
- endif
- else
- return -1
+ call s:debug("couldn't find any previous ->; assuming body text")
+ return relative_indent + s:sw()
end
endfunction
-function! s:do_handle_inside_curly_brace(pair_lnum, _pair_col, _lnum, _text, _prev_nb_lnum, _prev_nb_text)
- return indent(a:pair_lnum) + s:sw()
-endfunction
-
-function! s:do_handle_inside_parens(pair_lnum, pair_col, _lnum, _text, prev_nb_lnum, prev_nb_text)
- if a:pair_lnum
- if s:ends_with(a:prev_nb_text, '(', a:prev_nb_lnum)
- return indent(a:prev_nb_lnum) + s:sw()
- elseif a:pair_lnum == a:prev_nb_lnum
- " Align indent (e.g. "def add(a,")
- let pos = s:find_last_pos(a:prev_nb_lnum, a:prev_nb_text, '[^(]\+,')
- if pos == -1
- return 0
- else
- return pos
- end
- else
- return indent(a:prev_nb_lnum)
- end
- else
- return -1
- endif
-endfunction
-
-function! elixir#indent#handle_inside_generic_block(lnum, _text, prev_nb_lnum, prev_nb_text)
- let pair_lnum = searchpair(s:keyword('do\|fn'), '', s:keyword('end'), 'bW', "line('.') == ".a:lnum." || s:is_string_or_comment(line('.'), col('.'))", max([0, a:lnum - g:elixir_indent_max_lookbehind]))
+function! elixir#indent#handle_inside_generic_block(context)
+ let pair_lnum = searchpair(s:keyword('do\|fn'), '', s:keyword('end'), 'bW', "line('.') == ".a:context.lnum." || s:is_string_or_comment(line('.'), col('.'))", max([0, a:context.lnum - g:elixir_indent_max_lookbehind]))
if pair_lnum
" TODO: @jbodah 2017-03-29: this should probably be the case in *all*
" blocks
- if s:ends_with(a:prev_nb_text, ',', a:prev_nb_lnum)
+ if s:prev_ends_with(a:context, ',')
return indent(pair_lnum) + 2 * s:sw()
else
return indent(pair_lnum) + s:sw()