summaryrefslogtreecommitdiffstats
path: root/autoload/elixir/indent.vim
diff options
context:
space:
mode:
authorAdam Stankiewicz <sheerun@sher.pl>2017-05-17 11:07:28 +0200
committerAdam Stankiewicz <sheerun@sher.pl>2017-05-17 11:07:28 +0200
commitaf870100716f20ee4daef9cc527a9ecf41b54114 (patch)
tree0859464c3145682cbfc29ad08de4527dd661abf7 /autoload/elixir/indent.vim
parentef369d45a505403587ea0bae30ce6768ba51398c (diff)
downloadvim-polyglot-af870100716f20ee4daef9cc527a9ecf41b54114.tar.gz
vim-polyglot-af870100716f20ee4daef9cc527a9ecf41b54114.zip
Update
Diffstat (limited to 'autoload/elixir/indent.vim')
-rw-r--r--autoload/elixir/indent.vim442
1 files changed, 285 insertions, 157 deletions
diff --git a/autoload/elixir/indent.vim b/autoload/elixir/indent.vim
index b6df3b5d..fd2f4a3f 100644
--- a/autoload/elixir/indent.vim
+++ b/autoload/elixir/indent.vim
@@ -1,219 +1,347 @@
if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'elixir') == -1
-let s:NO_COLON_BEFORE = ':\@<!'
-let s:NO_COLON_AFTER = ':\@!'
-let s:ENDING_SYMBOLS = '\]\|}\|)'
-let s:ARROW = '->'
-let s:END_WITH_ARROW = s:ARROW.'$'
-let s:SKIP_SYNTAX = '\%(Comment\|String\)$'
-let s:BLOCK_SKIP = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '".s:SKIP_SYNTAX."'"
-let s:DEF = '^\s*def'
-let s:FN = '\<fn\>'
-let s:MULTILINE_FN = s:FN.'\%(.*end\)\@!'
-let s:BLOCK_START = '\%(\<do\>\|'.s:FN.'\)\>'
-let s:MULTILINE_BLOCK = '\%(\<do\>'.s:NO_COLON_AFTER.'\|'.s:MULTILINE_FN.'\)'
-let s:BLOCK_MIDDLE = '\<\%(else\|match\|elsif\|catch\|after\|rescue\)\>'
-let s:BLOCK_END = 'end'
-let s:STARTS_WITH_PIPELINE = '^\s*|>.*$'
-let s:QUERY_FROM = '^\s*\<from\>.*\<in\>.*,'
-let s:ENDING_WITH_ASSIGNMENT = '=\s*$'
-let s:INDENT_KEYWORDS = s:NO_COLON_BEFORE.'\%('.s:MULTILINE_BLOCK.'\|'.s:BLOCK_MIDDLE.'\)'
-let s:DEINDENT_KEYWORDS = '^\s*\<\%('.s:BLOCK_END.'\|'.s:BLOCK_MIDDLE.'\)\>'
-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'
-let s:LINE_COMMENT = '^\s*#'
-let s:MATCH_OPERATOR = '[^!><=]=[^~=>]'
-
-function! s:pending_parenthesis(line)
- if a:line.last_non_blank.text !~ s:ARROW
- return elixir#util#count_indentable_symbol_diff(a:line.last_non_blank, '(', '\%(end\s*\)\@<!)')
- end
+function! elixir#indent#debug(str)
+ if exists("g:elixir_indent_debug") && g:elixir_indent_debug
+ echom a:str
+ endif
endfunction
-function! s:pending_square_brackets(line)
- if a:line.last_non_blank.text !~ s:ARROW
- return elixir#util#count_indentable_symbol_diff(a:line.last_non_blank, '[', ']')
+" Returns 0 or 1 based on whether or not the text starts with the given
+" expression and is not a string or comment
+function! elixir#indent#starts_with(text, expr, lnum)
+ let pos = match(a:text, '^\s*'.a:expr)
+ if pos == -1
+ return 0
+ else
+ " NOTE: @jbodah 2017-02-24: pos is the index of the match which is
+ " zero-indexed. Add one to make it the column number
+ if elixir#indent#is_string_or_comment(a:lnum, pos + 1)
+ return 0
+ else
+ return 1
+ end
end
endfunction
-function! s:pending_brackets(line)
- if a:line.last_non_blank.text !~ s:ARROW
- return elixir#util#count_indentable_symbol_diff(a:line.last_non_blank, '{', '}')
+" Returns 0 or 1 based on whether or not the text ends with the given
+" expression and is not a string or comment
+function! elixir#indent#ends_with(text, expr, lnum)
+ let pos = match(a:text, a:expr.'\s*$')
+ if pos == -1
+ return 0
+ else
+ if elixir#indent#is_string_or_comment(a:lnum, pos)
+ return 0
+ else
+ return 1
+ end
end
endfunction
-function! elixir#indent#deindent_case_arrow(ind, line)
- if get(b:old_ind, 'arrow', 0) > 0
- \ && (a:line.current.text =~ s:ARROW
- \ || a:line.current.text =~ s:BLOCK_END)
- let ind = b:old_ind.arrow
- let b:old_ind.arrow = 0
- return ind
- else
- return a:ind
- end
+" Returns 0 or 1 based on whether or not the text matches the given expression
+function! elixir#indent#contains(text, expr)
+ return a:text =~ a:expr
endfunction
-function! elixir#indent#deindent_ending_symbols(ind, line)
- if a:line.current.text =~ '^\s*\('.s:ENDING_SYMBOLS.'\)'
- return a:ind - &sw
- else
- return a:ind
- end
+" Returns 0 or 1 based on whether or not the given line number and column
+" number pair is a string or comment
+function! elixir#indent#is_string_or_comment(line, col)
+ return synIDattr(synID(a:line, a:col, 1), "name") =~ '\%(String\|Comment\)'
endfunction
-function! elixir#indent#deindent_keywords(ind, line)
- if a:line.current.text =~ s:DEINDENT_KEYWORDS
- let bslnum = searchpair(
- \ s:PAIR_START,
- \ s:PAIR_MIDDLE,
- \ s:PAIR_END,
- \ 'nbW',
- \ s:BLOCK_SKIP
- \ )
+" Skip expression for searchpair. Returns 0 or 1 based on whether the value
+" under the cursor is a string or comment
+function! elixir#indent#searchpair_back_skip()
+ " NOTE: @jbodah 2017-02-27: for some reason this function gets called with
+ " and index that doesn't exist in the line sometimes. Detect and account for
+ " that situation
+ let curr_col = col('.')
+ if getline('.')[curr_col-1] == ''
+ let curr_col = curr_col-1
+ endif
+ return elixir#indent#is_string_or_comment(line('.'), curr_col)
+endfunction
- return indent(bslnum)
+" DRY up searchpair calls
+function! elixir#indent#searchpair_back(start, mid, end)
+ let line = line('.')
+ return searchpair(a:start, a:mid, a:end, 'bnW', "line('.') == " . line . " || elixir#indent#searchpair_back_skip()")
+endfunction
+
+" DRY up searchpairpos calls
+function! elixir#indent#searchpairpos_back(start, mid, end)
+ let line = line('.')
+ return searchpairpos(a:start, a:mid, a:end, 'bnW', "line('.') == " . line . " || elixir#indent#searchpair_back_skip()")
+endfunction
+
+" DRY up regex for keywords that 1) makes sure we only look at complete words
+" and 2) ignores atoms
+function! elixir#indent#keyword(expr)
+ return ':\@<!\<\C\%('.a:expr.'\)\>:\@!'
+endfunction
+
+function! elixir#indent#starts_with_comment(text)
+ return match(a:text, '^\s*#') != -1
+endfunction
+
+" Start at the end of text and search backwards looking for a match. Also peek
+" ahead if we get a match to make sure we get a complete match. This means
+" that the result should be the position of the start of the right-most match
+function! elixir#indent#find_last_pos(lnum, text, match)
+ let last = len(a:text) - 1
+ let c = last
+
+ while c >= 0
+ let substr = strpart(a:text, c, last)
+ let peek = strpart(a:text, c - 1, last)
+ let ss_match = match(substr, a:match)
+ if ss_match != -1
+ let peek_match = match(peek, a:match)
+ if peek_match == ss_match + 1
+ let syng = synIDattr(synID(a:lnum, c + ss_match, 1), 'name')
+ if syng !~ '\%(String\|Comment\)'
+ return c + ss_match
+ end
+ end
+ end
+ let c -= 1
+ endwhile
+
+ return -1
+endfunction
+
+function! elixir#indent#handle_top_of_file(_lnum, _text, prev_nb_lnum, _prev_nb_text)
+ if a:prev_nb_lnum == 0
+ return 0
else
- return a:ind
+ return -1
end
endfunction
-function! elixir#indent#deindent_opened_symbols(ind, line)
- let s:opened_symbol =
- \ s:pending_parenthesis(a:line)
- \ + s:pending_square_brackets(a:line)
- \ + s:pending_brackets(a:line)
+" TODO: @jbodah 2017-03-31: remove
+function! elixir#indent#handle_following_trailing_do(lnum, text, prev_nb_lnum, prev_nb_text)
+ if elixir#indent#ends_with(a:prev_nb_text, elixir#indent#keyword('do'), a:prev_nb_lnum)
+ if elixir#indent#starts_with(a:text, elixir#indent#keyword('end'), a:lnum)
+ return indent(a:prev_nb_lnum)
+ else
+ return indent(a:prev_nb_lnum) + &sw
+ end
+ else
+ return -1
+ endif
+endfunction
- if s:opened_symbol < 0
- let ind = get(b:old_ind, 'symbol', a:ind + (s:opened_symbol * &sw))
- let ind = float2nr(ceil(floor(ind)/&sw)*&sw)
- return ind <= 0 ? 0 : ind
+function! elixir#indent#handle_following_trailing_binary_operator(lnum, text, prev_nb_lnum, prev_nb_text)
+ let binary_operator = '\%(=\|<>\|>>>\|<=\|||\|+\|\~\~\~\|-\|&&\|<<<\|/\|\^\^\^\|\*\)'
+
+ if elixir#indent#ends_with(a:prev_nb_text, binary_operator, a:prev_nb_lnum)
+ return indent(a:prev_nb_lnum) + &sw
else
- return a:ind
- end
+ return -1
+ endif
endfunction
-function! elixir#indent#indent_after_pipeline(ind, line)
- if exists("b:old_ind.pipeline")
- \ && elixir#util#is_blank(a:line.last.text)
- \ && a:line.current.text !~ s:STARTS_WITH_PIPELINE
- " Reset indentation in pipelines if there is a blank line between
- " pipes
- let ind = b:old_ind.pipeline
- unlet b:old_ind.pipeline
- return ind
- elseif a:line.last_non_blank.text =~ s:STARTS_WITH_PIPELINE
- if empty(substitute(a:line.current.text, ' ', '', 'g'))
- \ || a:line.current.text =~ s:STARTS_WITH_PIPELINE
- return indent(a:line.last_non_blank.num)
- elseif a:line.last_non_blank.text !~ s:INDENT_KEYWORDS
- let ind = b:old_ind.pipeline
- unlet b:old_ind.pipeline
- return ind
+function! elixir#indent#handle_starts_with_pipe(lnum, text, prev_nb_lnum, prev_nb_text)
+ if elixir#indent#starts_with(a:text, '|>', a:lnum)
+ let match_operator = '\%(!\|=\|<\|>\)\@<!=\%(=\|>\|\~\)\@!'
+ let pos = elixir#indent#find_last_pos(a:prev_nb_lnum, a:prev_nb_text, match_operator)
+ if pos == -1
+ return indent(a:prev_nb_lnum)
+ else
+ let next_word_pos = match(strpart(a:prev_nb_text, pos+1, len(a:prev_nb_text)-1), '\S')
+ if next_word_pos == -1
+ return indent(a:prev_nb_lnum) + &sw
+ else
+ return pos + 1 + next_word_pos
+ end
end
- end
+ else
+ return -1
+ endif
+endfunction
- return a:ind
+function! elixir#indent#handle_starts_with_comment(_lnum, text, prev_nb_lnum, _prev_nb_text)
+ if elixir#indent#starts_with_comment(a:text)
+ return indent(a:prev_nb_lnum)
+ else
+ return -1
+ endif
endfunction
-function! elixir#indent#indent_assignment(ind, line)
- if a:line.last_non_blank.text =~ s:ENDING_WITH_ASSIGNMENT
- let b:old_ind.pipeline = indent(a:line.last_non_blank.num) " FIXME: side effect
- return a:ind + &sw
+function! elixir#indent#handle_starts_with_end(lnum, text, _prev_nb_lnum, _prev_nb_text)
+ if elixir#indent#starts_with(a:text, elixir#indent#keyword('end'), a:lnum)
+ let pair_lnum = elixir#indent#searchpair_back(elixir#indent#keyword('do\|fn'), '', elixir#indent#keyword('end').'\zs')
+ return indent(pair_lnum)
else
- return a:ind
- end
+ return -1
+ endif
endfunction
-function! elixir#indent#indent_brackets(ind, line)
- if s:pending_brackets(a:line) > 0
- return a:ind + &sw
+function! elixir#indent#handle_starts_with_mid_or_end_block_keyword(lnum, text, _prev_nb_lnum, _prev_nb_text)
+ if elixir#indent#starts_with(a:text, elixir#indent#keyword('catch\|rescue\|after\|else'), a:lnum)
+ let pair_lnum = elixir#indent#searchpair_back(elixir#indent#keyword('with\|receive\|try\|if\|fn'), elixir#indent#keyword('catch\|rescue\|after\|else').'\zs', elixir#indent#keyword('end'))
+ return indent(pair_lnum)
else
- return a:ind
- end
+ return -1
+ endif
endfunction
-function! elixir#indent#indent_case_arrow(ind, line)
- if a:line.last_non_blank.text =~ s:END_WITH_ARROW && a:line.last_non_blank.text !~ '\<fn\>'
- let b:old_ind.arrow = a:ind
- return a:ind + &sw
+function! elixir#indent#handle_starts_with_close_bracket(lnum, text, _prev_nb_lnum, _prev_nb_text)
+ if elixir#indent#starts_with(a:text, '\%(\]\|}\|)\)', a:lnum)
+ let pair_lnum = elixir#indent#searchpair_back('\%(\[\|{\|(\)', '', '\%(\]\|}\|)\)')
+ return indent(pair_lnum)
else
- return a:ind
- end
+ return -1
+ endif
endfunction
-function! elixir#indent#indent_ending_symbols(ind, line)
- if a:line.last_non_blank.text =~ '^\s*\('.s:ENDING_SYMBOLS.'\)\s*$'
- return a:ind + &sw
+function! elixir#indent#handle_starts_with_binary_operator(lnum, text, prev_nb_lnum, prev_nb_text)
+ let binary_operator = '\%(=\|<>\|>>>\|<=\|||\|+\|\~\~\~\|-\|&&\|<<<\|/\|\^\^\^\|\*\)'
+
+ if elixir#indent#starts_with(a:text, binary_operator, a:lnum)
+ let match_operator = '\%(!\|=\|<\|>\)\@<!=\%(=\|>\|\~\)\@!'
+ let pos = elixir#indent#find_last_pos(a:prev_nb_lnum, a:prev_nb_text, match_operator)
+ if pos == -1
+ return indent(a:prev_nb_lnum)
+ else
+ let next_word_pos = match(strpart(a:prev_nb_text, pos+1, len(a:prev_nb_text)-1), '\S')
+ if next_word_pos == -1
+ return indent(a:prev_nb_lnum) + &sw
+ else
+ return pos + 1 + next_word_pos
+ end
+ end
else
- return a:ind
- end
+ return -1
+ endif
endfunction
-function! elixir#indent#indent_keywords(ind, line)
- if a:line.last_non_blank.text =~ s:INDENT_KEYWORDS && a:line.last_non_blank.text !~ s:LINE_COMMENT
- return a:ind + &sw
+" To handle nested structures properly we need to find the innermost
+" 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)
+ let start_pattern = '\C\%(\<if\>\|\<case\>\|\<cond\>\|\<try\>\|\<receive\>\|\<fn\>\|{\|\[\|(\)'
+ let end_pattern = '\C\%(\<end\>\|\]\|}\|)\)'
+ let pair_info = elixir#indent#searchpairpos_back(start_pattern, '', end_pattern)
+ 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 elixir#indent#debug("testing elixir#indent#do_handle_inside_fn")
+ return elixir#indent#do_handle_inside_fn(pair_lnum, pair_col, a:lnum, a:text, a:prev_nb_lnum, a:prev_nb_text)
+ elseif pair_char == '['
+ call elixir#indent#debug("testing elixir#indent#do_handle_inside_square_brace")
+ return elixir#indent#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 elixir#indent#debug("testing elixir#indent#do_handle_inside_curly_brace")
+ return elixir#indent#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 elixir#indent#debug("testing elixir#indent#do_handle_inside_parens")
+ return elixir#indent#do_handle_inside_parens(pair_lnum, pair_col, a:lnum, a:text, a:prev_nb_lnum, a:prev_nb_text)
+ else
+ call elixir#indent#debug("testing elixir#indent#do_handle_inside_keyword_block")
+ return elixir#indent#do_handle_inside_keyword_block(pair_lnum, pair_col, a:lnum, a:text, a:prev_nb_lnum, a:prev_nb_text)
+ end
else
- return a:ind
+ return -1
end
endfunction
-function! elixir#indent#indent_parenthesis(ind, line)
- if s:pending_parenthesis(a:line) > 0
- \ && a:line.last_non_blank.text !~ s:DEF
- \ && a:line.last_non_blank.text !~ s:END_WITH_ARROW
- let b:old_ind.symbol = a:ind
- return matchend(a:line.last_non_blank.text, '(')
+function! elixir#indent#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 elixir#indent#starts_with(a:prev_nb_text, keyword_pattern, a:prev_nb_lnum)
+ call elixir#indent#debug("prev nb line is keyword")
+ return indent(a:prev_nb_lnum) + &sw
+ elseif elixir#indent#contains(a:text, '->')
+ call elixir#indent#debug("contains ->")
+ " TODO: @jbodah 2017-03-31: test contains ignores str + comments
+ return indent(a:pair_lnum) + &sw
+ elseif elixir#indent#contains(a:prev_nb_text, '->')
+ call elixir#indent#debug("prev nb line contains ->")
+ return indent(a:prev_nb_lnum) + &sw
+ else
+ call elixir#indent#debug("doesnt start with comment or contain ->")
+ return indent(a:prev_nb_lnum)
+ end
else
- return a:ind
- end
+ return -1
+ endif
endfunction
-function! elixir#indent#indent_pipeline_assignment(ind, line)
- if a:line.current.text =~ s:STARTS_WITH_PIPELINE
- \ && a:line.last_non_blank.text =~ s:MATCH_OPERATOR
- let b:old_ind.pipeline = indent(a:line.last_non_blank.num)
- " if line starts with pipeline
- " and last_non_blank line is an attribution
- " indents pipeline in same level as attribution
- let assign_pos = match(a:line.last_non_blank.text, '=\s*\zs[^ ]')
- return (elixir#util#is_indentable_at(a:line.last_non_blank.num, assign_pos) ? assign_pos : a:ind)
+function! elixir#indent#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
+ if elixir#indent#contains(a:text, '->')
+ return indent(a:pair_lnum) + &sw
+ else
+ if elixir#indent#ends_with(a:prev_nb_text, '->', a:prev_nb_lnum)
+ return indent(a:prev_nb_lnum) + &sw
+ else
+ return indent(a:prev_nb_lnum)
+ end
+ end
else
- return a:ind
- end
+ return -1
+ endif
endfunction
-function! elixir#indent#indent_pipeline_continuation(ind, line)
- if a:line.last_non_blank.text =~ s:STARTS_WITH_PIPELINE
- \ && a:line.current.text =~ s:STARTS_WITH_PIPELINE
- return indent(a:line.last_non_blank.num)
+function! elixir#indent#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) + &sw
+ endif
else
- return a:ind
+ return -1
end
endfunction
-function! elixir#indent#indent_square_brackets(ind, line)
- if s:pending_square_brackets(a:line) > 0
- if a:line.last_non_blank.text =~ '[\s*$'
- return a:ind + &sw
+function! elixir#indent#do_handle_inside_curly_brace(pair_lnum, _pair_col, _lnum, _text, _prev_nb_lnum, _prev_nb_text)
+ return indent(a:pair_lnum) + &sw
+endfunction
+
+function! elixir#indent#do_handle_inside_parens(pair_lnum, pair_col, _lnum, _text, prev_nb_lnum, prev_nb_text)
+ if a:pair_lnum
+ if elixir#indent#ends_with(a:prev_nb_text, '(', a:prev_nb_lnum)
+ return indent(a:prev_nb_lnum) + &sw
+ elseif a:pair_lnum == a:prev_nb_lnum
+ " Align indent (e.g. "def add(a,")
+ let pos = elixir#indent#find_last_pos(a:prev_nb_lnum, a:prev_nb_text, '[^(]\+,')
+ if pos == -1
+ return 0
+ else
+ return pos
+ end
else
- " 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
- return matchend(a:line.last_non_blank.text, '[\s*')
+ return indent(a:prev_nb_lnum)
end
else
- return a:ind
- end
+ return -1
+ endif
endfunction
-function! elixir#indent#indent_ecto_queries(ind, line)
- if a:line.last_non_blank.text =~ s:QUERY_FROM
- return a:ind + &sw
+function! elixir#indent#handle_inside_generic_block(lnum, _text, prev_nb_lnum, prev_nb_text)
+ let pair_lnum = searchpair(elixir#indent#keyword('do\|fn'), '', elixir#indent#keyword('end'), 'bW', "line('.') == ".a:lnum." || elixir#indent#is_string_or_comment(line('.'), col('.'))")
+ if pair_lnum
+ " TODO: @jbodah 2017-03-29: this should probably be the case in *all*
+ " blocks
+ if elixir#indent#ends_with(a:prev_nb_text, ',', a:prev_nb_lnum)
+ return indent(pair_lnum) + 2 * &sw
+ else
+ return indent(pair_lnum) + &sw
+ endif
else
- return a:ind
- end
+ return -1
+ endif
endfunction
endif