mirror of
https://github.com/neovim/neovim.git
synced 2024-12-19 18:55:14 -07:00
3222f0ad00
runtime: Fix more typos (vim/vim#13354)
* Fix more typos
* Fix typos in ignored runtime/ directory
dbf749bd5a
Co-authored-by: Viktor Szépe <viktor@szepe.net>
1537 lines
51 KiB
VimL
1537 lines
51 KiB
VimL
" Vim indent file
|
|
" Language: Erlang (http://www.erlang.org)
|
|
" Author: Csaba Hoch <csaba.hoch@gmail.com>
|
|
" Contributors: Edwin Fine <efine145_nospam01 at usa dot net>
|
|
" Pawel 'kTT' Salata <rockplayer.pl@gmail.com>
|
|
" Ricardo Catalinas Jiménez <jimenezrick@gmail.com>
|
|
" Last Update: 2022-Sep-06
|
|
" License: Vim license
|
|
" URL: https://github.com/vim-erlang/vim-erlang-runtime
|
|
|
|
" Note About Usage:
|
|
" This indentation script works best with the Erlang syntax file created by
|
|
" Kreąimir Marľić (Kresimir Marzic) and maintained by Csaba Hoch.
|
|
|
|
" Notes About Implementation:
|
|
"
|
|
" - LTI = Line to indent.
|
|
" - The index of the first line is 1, but the index of the first column is 0.
|
|
|
|
|
|
" Initialization {{{1
|
|
" ==============
|
|
|
|
" Only load this indent file when no other was loaded
|
|
" Vim 7 or later is needed
|
|
if exists("b:did_indent") || version < 700
|
|
finish
|
|
else
|
|
let b:did_indent = 1
|
|
endif
|
|
|
|
setlocal indentexpr=ErlangIndent()
|
|
setlocal indentkeys+=0=end,0=of,0=catch,0=after,0=else,0=when,0=),0=],0=},0=>>
|
|
|
|
let b:undo_indent = "setl inde< indk<"
|
|
|
|
" Only define the functions once
|
|
if exists("*ErlangIndent")
|
|
finish
|
|
endif
|
|
|
|
let s:cpo_save = &cpo
|
|
set cpo&vim
|
|
|
|
" Logging library {{{1
|
|
" ===============
|
|
|
|
" Purpose:
|
|
" Logs the given string using the ErlangIndentLog function if it exists.
|
|
" Parameters:
|
|
" s: string
|
|
function! s:Log(s)
|
|
if exists("*ErlangIndentLog")
|
|
call ErlangIndentLog(a:s)
|
|
endif
|
|
endfunction
|
|
|
|
" Line tokenizer library {{{1
|
|
" ======================
|
|
|
|
" Indtokens are "indentation tokens". See their exact format in the
|
|
" documentation of the s:GetTokensFromLine function.
|
|
|
|
" Purpose:
|
|
" Calculate the new virtual column after the given segment of a line.
|
|
" Parameters:
|
|
" line: string
|
|
" first_index: integer -- the index of the first character of the segment
|
|
" last_index: integer -- the index of the last character of the segment
|
|
" vcol: integer -- the virtual column of the first character of the token
|
|
" tabstop: integer -- the value of the 'tabstop' option to be used
|
|
" Returns:
|
|
" vcol: integer
|
|
" Example:
|
|
" " index: 0 12 34567
|
|
" " vcol: 0 45 89
|
|
" s:CalcVCol("\t'\tx', b", 1, 4, 4) -> 10
|
|
function! s:CalcVCol(line, first_index, last_index, vcol, tabstop)
|
|
|
|
" We copy the relevant segment of the line, otherwise if the line were
|
|
" e.g. `"\t", term` then the else branch below would consume the `", term`
|
|
" part at once.
|
|
let line = a:line[a:first_index : a:last_index]
|
|
|
|
let i = 0
|
|
let last_index = a:last_index - a:first_index
|
|
let vcol = a:vcol
|
|
|
|
while 0 <= i && i <= last_index
|
|
|
|
if line[i] ==# "\t"
|
|
" Example (when tabstop == 4):
|
|
"
|
|
" vcol + tab -> next_vcol
|
|
" 0 + tab -> 4
|
|
" 1 + tab -> 4
|
|
" 2 + tab -> 4
|
|
" 3 + tab -> 4
|
|
" 4 + tab -> 8
|
|
"
|
|
" next_i - i == the number of tabs
|
|
let next_i = matchend(line, '\t*', i + 1)
|
|
let vcol = (vcol / a:tabstop + (next_i - i)) * a:tabstop
|
|
call s:Log('new vcol after tab: '. vcol)
|
|
else
|
|
let next_i = matchend(line, '[^\t]*', i + 1)
|
|
let vcol += next_i - i
|
|
call s:Log('new vcol after other: '. vcol)
|
|
endif
|
|
let i = next_i
|
|
endwhile
|
|
|
|
return vcol
|
|
endfunction
|
|
|
|
" Purpose:
|
|
" Go through the whole line and return the tokens in the line.
|
|
" Parameters:
|
|
" line: string -- the line to be examined
|
|
" string_continuation: bool
|
|
" atom_continuation: bool
|
|
" Returns:
|
|
" indtokens = [indtoken]
|
|
" indtoken = [token, vcol, col]
|
|
" token = string (examples: 'begin', '<quoted_atom>', '}')
|
|
" vcol = integer (the virtual column of the first character of the token;
|
|
" counting starts from 0)
|
|
" col = integer (counting starts from 0)
|
|
function! s:GetTokensFromLine(line, string_continuation, atom_continuation,
|
|
\tabstop)
|
|
|
|
let linelen = strlen(a:line) " The length of the line
|
|
let i = 0 " The index of the current character in the line
|
|
let vcol = 0 " The virtual column of the current character
|
|
let indtokens = []
|
|
|
|
if a:string_continuation
|
|
let i = matchend(a:line, '^\%([^"\\]\|\\.\)*"', 0)
|
|
if i ==# -1
|
|
call s:Log(' Whole line is string continuation -> ignore')
|
|
return []
|
|
else
|
|
let vcol = s:CalcVCol(a:line, 0, i - 1, 0, a:tabstop)
|
|
call add(indtokens, ['<string_end>', vcol, i])
|
|
endif
|
|
elseif a:atom_continuation
|
|
let i = matchend(a:line, "^\\%([^'\\\\]\\|\\\\.\\)*'", 0)
|
|
if i ==# -1
|
|
call s:Log(' Whole line is quoted atom continuation -> ignore')
|
|
return []
|
|
else
|
|
let vcol = s:CalcVCol(a:line, 0, i - 1, 0, a:tabstop)
|
|
call add(indtokens, ['<quoted_atom_end>', vcol, i])
|
|
endif
|
|
endif
|
|
|
|
while 0 <= i && i < linelen
|
|
|
|
let next_vcol = ''
|
|
|
|
" Spaces
|
|
if a:line[i] ==# ' '
|
|
let next_i = matchend(a:line, ' *', i + 1)
|
|
|
|
" Tabs
|
|
elseif a:line[i] ==# "\t"
|
|
let next_i = matchend(a:line, '\t*', i + 1)
|
|
|
|
" See example in s:CalcVCol
|
|
let next_vcol = (vcol / a:tabstop + (next_i - i)) * a:tabstop
|
|
|
|
" Comment
|
|
elseif a:line[i] ==# '%'
|
|
let next_i = linelen
|
|
|
|
" String token: "..."
|
|
elseif a:line[i] ==# '"'
|
|
let next_i = matchend(a:line, '\%([^"\\]\|\\.\)*"', i + 1)
|
|
if next_i ==# -1
|
|
call add(indtokens, ['<string_start>', vcol, i])
|
|
else
|
|
let next_vcol = s:CalcVCol(a:line, i, next_i - 1, vcol, a:tabstop)
|
|
call add(indtokens, ['<string>', vcol, i])
|
|
endif
|
|
|
|
" Quoted atom token: '...'
|
|
elseif a:line[i] ==# "'"
|
|
let next_i = matchend(a:line, "\\%([^'\\\\]\\|\\\\.\\)*'", i + 1)
|
|
if next_i ==# -1
|
|
call add(indtokens, ['<quoted_atom_start>', vcol, i])
|
|
else
|
|
let next_vcol = s:CalcVCol(a:line, i, next_i - 1, vcol, a:tabstop)
|
|
call add(indtokens, ['<quoted_atom>', vcol, i])
|
|
endif
|
|
|
|
" Keyword or atom or variable token or number
|
|
elseif a:line[i] =~# '[a-zA-Z_@0-9]'
|
|
let next_i = matchend(a:line,
|
|
\'[[:alnum:]_@:]*\%(\s*#\s*[[:alnum:]_@:]*\)\=',
|
|
\i + 1)
|
|
call add(indtokens, [a:line[(i):(next_i - 1)], vcol, i])
|
|
|
|
" Character token: $<char> (as in: $a)
|
|
elseif a:line[i] ==# '$'
|
|
call add(indtokens, ['$.', vcol, i])
|
|
let next_i = i + 2
|
|
|
|
" Dot token: .
|
|
elseif a:line[i] ==# '.'
|
|
|
|
let next_i = i + 1
|
|
|
|
if i + 1 ==# linelen || a:line[i + 1] =~# '[[:blank:]%]'
|
|
" End of clause token: . (as in: f() -> ok.)
|
|
call add(indtokens, ['<end_of_clause>', vcol, i])
|
|
|
|
else
|
|
" Possibilities:
|
|
" - Dot token in float: . (as in: 3.14)
|
|
" - Dot token in record: . (as in: #myrec.myfield)
|
|
call add(indtokens, ['.', vcol, i])
|
|
endif
|
|
|
|
" Equal sign
|
|
elseif a:line[i] ==# '='
|
|
" This is handled separately so that "=<<" will be parsed as
|
|
" ['=', '<<'] instead of ['=<', '<']. Although Erlang parses it
|
|
" currently in the latter way, that may be fixed some day.
|
|
call add(indtokens, [a:line[i], vcol, i])
|
|
let next_i = i + 1
|
|
|
|
" Three-character tokens
|
|
elseif i + 1 < linelen &&
|
|
\ index(['=:=', '=/='], a:line[i : i + 1]) != -1
|
|
call add(indtokens, [a:line[i : i + 1], vcol, i])
|
|
let next_i = i + 2
|
|
|
|
" Two-character tokens
|
|
elseif i + 1 < linelen &&
|
|
\ index(['->', '<<', '>>', '||', '==', '/=', '=<', '>=', '?=', '++',
|
|
\ '--', '::'],
|
|
\ a:line[i : i + 1]) != -1
|
|
call add(indtokens, [a:line[i : i + 1], vcol, i])
|
|
let next_i = i + 2
|
|
|
|
" Other character: , ; < > ( ) [ ] { } # + - * / : ? = ! |
|
|
else
|
|
call add(indtokens, [a:line[i], vcol, i])
|
|
let next_i = i + 1
|
|
|
|
endif
|
|
|
|
if next_vcol ==# ''
|
|
let vcol += next_i - i
|
|
else
|
|
let vcol = next_vcol
|
|
endif
|
|
|
|
let i = next_i
|
|
|
|
endwhile
|
|
|
|
return indtokens
|
|
|
|
endfunction
|
|
|
|
" TODO: doc, handle "not found" case
|
|
function! s:GetIndtokenAtCol(indtokens, col)
|
|
let i = 0
|
|
while i < len(a:indtokens)
|
|
if a:indtokens[i][2] ==# a:col
|
|
return [1, i]
|
|
elseif a:indtokens[i][2] > a:col
|
|
return [0, s:IndentError('No token at col ' . a:col . ', ' .
|
|
\'indtokens = ' . string(a:indtokens),
|
|
\'', '')]
|
|
endif
|
|
let i += 1
|
|
endwhile
|
|
return [0, s:IndentError('No token at col ' . a:col . ', ' .
|
|
\'indtokens = ' . string(a:indtokens),
|
|
\'', '')]
|
|
endfunction
|
|
|
|
" Stack library {{{1
|
|
" =============
|
|
|
|
" Purpose:
|
|
" Push a token onto the parser's stack.
|
|
" Parameters:
|
|
" stack: [token]
|
|
" token: string
|
|
function! s:Push(stack, token)
|
|
call s:Log(' Stack Push: "' . a:token . '" into ' . string(a:stack))
|
|
call insert(a:stack, a:token)
|
|
endfunction
|
|
|
|
" Purpose:
|
|
" Pop a token from the parser's stack.
|
|
" Parameters:
|
|
" stack: [token]
|
|
" token: string
|
|
" Returns:
|
|
" token: string -- the removed element
|
|
function! s:Pop(stack)
|
|
let head = remove(a:stack, 0)
|
|
call s:Log(' Stack Pop: "' . head . '" from ' . string(a:stack))
|
|
return head
|
|
endfunction
|
|
|
|
" Library for accessing and storing tokenized lines {{{1
|
|
" =================================================
|
|
|
|
" The Erlang token cache: an `lnum -> indtokens` dictionary that stores the
|
|
" tokenized lines.
|
|
let s:all_tokens = {}
|
|
let s:file_name = ''
|
|
let s:last_changedtick = -1
|
|
|
|
" Purpose:
|
|
" Clear the Erlang token cache if we have a different file or the file has
|
|
" been changed since the last indentation.
|
|
function! s:ClearTokenCacheIfNeeded()
|
|
let file_name = expand('%:p')
|
|
if file_name != s:file_name ||
|
|
\ b:changedtick != s:last_changedtick
|
|
let s:file_name = file_name
|
|
let s:last_changedtick = b:changedtick
|
|
let s:all_tokens = {}
|
|
endif
|
|
endfunction
|
|
|
|
" Purpose:
|
|
" Return the tokens of line `lnum`, if that line is not empty. If it is
|
|
" empty, find the first non-empty line in the given `direction` and return
|
|
" the tokens of that line.
|
|
" Parameters:
|
|
" lnum: integer
|
|
" direction: 'up' | 'down'
|
|
" Returns:
|
|
" result: [] -- the result is an empty list if we hit the beginning or end
|
|
" of the file
|
|
" | [lnum, indtokens]
|
|
" lnum: integer -- the index of the non-empty line that was found and
|
|
" tokenized
|
|
" indtokens: [indtoken] -- the tokens of line `lnum`
|
|
function! s:TokenizeLine(lnum, direction)
|
|
|
|
call s:Log('Tokenizing starts from line ' . a:lnum)
|
|
if a:direction ==# 'up'
|
|
let lnum = prevnonblank(a:lnum)
|
|
else " a:direction ==# 'down'
|
|
let lnum = nextnonblank(a:lnum)
|
|
endif
|
|
|
|
" We hit the beginning or end of the file
|
|
if lnum ==# 0
|
|
let indtokens = []
|
|
call s:Log(' We hit the beginning or end of the file.')
|
|
|
|
" The line has already been parsed
|
|
elseif has_key(s:all_tokens, lnum)
|
|
let indtokens = s:all_tokens[lnum]
|
|
call s:Log('Cached line ' . lnum . ': ' . getline(lnum))
|
|
call s:Log(" Tokens in the line:\n - " . join(indtokens, "\n - "))
|
|
|
|
" The line should be parsed now
|
|
else
|
|
|
|
" Parse the line
|
|
let line = getline(lnum)
|
|
let string_continuation = s:IsLineStringContinuation(lnum)
|
|
let atom_continuation = s:IsLineAtomContinuation(lnum)
|
|
let indtokens = s:GetTokensFromLine(line, string_continuation,
|
|
\atom_continuation, &tabstop)
|
|
let s:all_tokens[lnum] = indtokens
|
|
call s:Log('Tokenizing line ' . lnum . ': ' . line)
|
|
call s:Log(" Tokens in the line:\n - " . join(indtokens, "\n - "))
|
|
|
|
endif
|
|
|
|
return [lnum, indtokens]
|
|
endfunction
|
|
|
|
" Purpose:
|
|
" As a helper function for PrevIndToken and NextIndToken, the FindIndToken
|
|
" function finds the first line with at least one token in the given
|
|
" direction.
|
|
" Parameters:
|
|
" lnum: integer
|
|
" direction: 'up' | 'down'
|
|
" Returns:
|
|
" result: [[], 0, 0]
|
|
" -- the result is an empty list if we hit the beginning or end of
|
|
" the file
|
|
" | [indtoken, lnum, i]
|
|
" -- the content, lnum and token index of the next (or previous)
|
|
" indtoken
|
|
function! s:FindIndToken(lnum, dir)
|
|
let lnum = a:lnum
|
|
while 1
|
|
let lnum += (a:dir ==# 'up' ? -1 : 1)
|
|
let [lnum, indtokens] = s:TokenizeLine(lnum, a:dir)
|
|
if lnum ==# 0
|
|
" We hit the beginning or end of the file
|
|
return [[], 0, 0]
|
|
elseif !empty(indtokens)
|
|
" We found a non-empty line. If we were moving up, we return the last
|
|
" token of this line. Otherwise we return the first token if this line.
|
|
let i = (a:dir ==# 'up' ? len(indtokens) - 1 : 0)
|
|
return [indtokens[i], lnum, i]
|
|
endif
|
|
endwhile
|
|
endfunction
|
|
|
|
" Purpose:
|
|
" Find the token that directly precedes the given token.
|
|
" Parameters:
|
|
" lnum: integer -- the line of the given token
|
|
" i: the index of the given token within line `lnum`
|
|
" Returns:
|
|
" result = [] -- the result is an empty list if the given token is the first
|
|
" token of the file
|
|
" | indtoken
|
|
function! s:PrevIndToken(lnum, i)
|
|
call s:Log(' PrevIndToken called: lnum=' . a:lnum . ', i =' . a:i)
|
|
|
|
" If the current line has a previous token, return that
|
|
if a:i > 0
|
|
return [s:all_tokens[a:lnum][a:i - 1], a:lnum, a:i - 1]
|
|
else
|
|
return s:FindIndToken(a:lnum, 'up')
|
|
endif
|
|
endfunction
|
|
|
|
" Purpose:
|
|
" Find the token that directly succeeds the given token.
|
|
" Parameters:
|
|
" lnum: integer -- the line of the given token
|
|
" i: the index of the given token within line `lnum`
|
|
" Returns:
|
|
" result = [] -- the result is an empty list if the given token is the last
|
|
" token of the file
|
|
" | indtoken
|
|
function! s:NextIndToken(lnum, i)
|
|
call s:Log(' NextIndToken called: lnum=' . a:lnum . ', i =' . a:i)
|
|
|
|
" If the current line has a next token, return that
|
|
if len(s:all_tokens[a:lnum]) > a:i + 1
|
|
return [s:all_tokens[a:lnum][a:i + 1], a:lnum, a:i + 1]
|
|
else
|
|
return s:FindIndToken(a:lnum, 'down')
|
|
endif
|
|
endfunction
|
|
|
|
" ErlangCalcIndent helper functions {{{1
|
|
" =================================
|
|
|
|
" Purpose:
|
|
" This function is called when the parser encounters a syntax error.
|
|
"
|
|
" If we encounter a syntax error, we return
|
|
" g:erlang_unexpected_token_indent, which is -1 by default. This means that
|
|
" the indentation of the LTI will not be changed.
|
|
" Parameter:
|
|
" msg: string
|
|
" token: string
|
|
" stack: [token]
|
|
" Returns:
|
|
" indent: integer
|
|
function! s:IndentError(msg, token, stack)
|
|
call s:Log('Indent error: ' . a:msg . ' -> return')
|
|
call s:Log(' Token = ' . a:token . ', ' .
|
|
\' stack = ' . string(a:stack))
|
|
return g:erlang_unexpected_token_indent
|
|
endfunction
|
|
|
|
" Purpose:
|
|
" This function is called when the parser encounters an unexpected token,
|
|
" and the parser will return the number given back by UnexpectedToken.
|
|
"
|
|
" If we encounter an unexpected token, we return
|
|
" g:erlang_unexpected_token_indent, which is -1 by default. This means that
|
|
" the indentation of the LTI will not be changed.
|
|
" Parameter:
|
|
" token: string
|
|
" stack: [token]
|
|
" Returns:
|
|
" indent: integer
|
|
function! s:UnexpectedToken(token, stack)
|
|
call s:Log(' Unexpected token ' . a:token . ', stack = ' .
|
|
\string(a:stack) . ' -> return')
|
|
return g:erlang_unexpected_token_indent
|
|
endfunction
|
|
|
|
if !exists('g:erlang_unexpected_token_indent')
|
|
let g:erlang_unexpected_token_indent = -1
|
|
endif
|
|
|
|
" Purpose:
|
|
" Return whether the given line starts with a string continuation.
|
|
" Parameter:
|
|
" lnum: integer
|
|
" Returns:
|
|
" result: bool
|
|
" Example:
|
|
" f() -> % IsLineStringContinuation = false
|
|
" "This is a % IsLineStringContinuation = false
|
|
" multiline % IsLineStringContinuation = true
|
|
" string". % IsLineStringContinuation = true
|
|
function! s:IsLineStringContinuation(lnum)
|
|
if has('syntax_items')
|
|
return synIDattr(synID(a:lnum, 1, 0), 'name') =~# '^erlangString'
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction
|
|
|
|
" Purpose:
|
|
" Return whether the given line starts with an atom continuation.
|
|
" Parameter:
|
|
" lnum: integer
|
|
" Returns:
|
|
" result: bool
|
|
" Example:
|
|
" 'function with % IsLineAtomContinuation = true, but should be false
|
|
" weird name'() -> % IsLineAtomContinuation = true
|
|
" ok. % IsLineAtomContinuation = false
|
|
function! s:IsLineAtomContinuation(lnum)
|
|
if has('syntax_items')
|
|
let syn_name = synIDattr(synID(a:lnum, 1, 0), 'name')
|
|
return syn_name =~# '^erlangQuotedAtom' ||
|
|
\ syn_name =~# '^erlangQuotedRecord'
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction
|
|
|
|
" Purpose:
|
|
" Return whether the 'catch' token (which should be the `i`th token in line
|
|
" `lnum`) is standalone or part of a try-catch block, based on the preceding
|
|
" token.
|
|
" Parameters:
|
|
" lnum: integer
|
|
" i: integer
|
|
" Return:
|
|
" is_standalone: bool
|
|
function! s:IsCatchStandalone(lnum, i)
|
|
call s:Log(' IsCatchStandalone called: lnum=' . a:lnum . ', i=' . a:i)
|
|
let [prev_indtoken, _, _] = s:PrevIndToken(a:lnum, a:i)
|
|
|
|
" If we hit the beginning of the file, it is not a catch in a try block
|
|
if prev_indtoken == []
|
|
return 1
|
|
endif
|
|
|
|
let prev_token = prev_indtoken[0]
|
|
|
|
if prev_token =~# '^[A-Z_@0-9]'
|
|
let is_standalone = 0
|
|
elseif prev_token =~# '[a-z]'
|
|
if index(['after', 'and', 'andalso', 'band', 'begin', 'bnot', 'bor', 'bsl',
|
|
\ 'bsr', 'bxor', 'case', 'catch', 'div', 'maybe', 'not', 'or',
|
|
\ 'orelse', 'rem', 'try', 'xor'], prev_token) != -1
|
|
" If catch is after these keywords, it is standalone
|
|
let is_standalone = 1
|
|
else
|
|
" If catch is after another keyword (e.g. 'end') or an atom, it is
|
|
" part of try-catch.
|
|
"
|
|
" Keywords:
|
|
" - may precede 'catch': end
|
|
" - may not precede 'catch': else fun if of receive when
|
|
" - unused: cond let query
|
|
let is_standalone = 0
|
|
endif
|
|
elseif index([')', ']', '}', '<string>', '<string_end>', '<quoted_atom>',
|
|
\ '<quoted_atom_end>', '$.'], prev_token) != -1
|
|
let is_standalone = 0
|
|
else
|
|
" This 'else' branch includes the following tokens:
|
|
" -> == /= =< < >= > ?= =:= =/= + - * / ++ -- :: < > ; ( [ { ? = ! . |
|
|
let is_standalone = 1
|
|
endif
|
|
|
|
call s:Log(' "catch" preceded by "' . prev_token . '" -> catch ' .
|
|
\(is_standalone ? 'is standalone' : 'belongs to try-catch'))
|
|
return is_standalone
|
|
|
|
endfunction
|
|
|
|
" Purpose:
|
|
" This function is called when a begin-type element ('begin', 'case',
|
|
" '[', '<<', etc.) is found. It asks the caller to return if the stack
|
|
" if already empty.
|
|
" Parameters:
|
|
" stack: [token]
|
|
" token: string
|
|
" curr_vcol: integer
|
|
" stored_vcol: integer
|
|
" sw: integer -- number of spaces to be used after the begin element as
|
|
" indentation
|
|
" Returns:
|
|
" result: [should_return, indent]
|
|
" should_return: bool -- if true, the caller should return `indent` to Vim
|
|
" indent -- integer
|
|
function! s:BeginElementFoundIfEmpty(stack, token, curr_vcol, stored_vcol, sw)
|
|
if empty(a:stack)
|
|
if a:stored_vcol ==# -1
|
|
call s:Log(' "' . a:token . '" directly precedes LTI -> return')
|
|
return [1, a:curr_vcol + a:sw]
|
|
else
|
|
call s:Log(' "' . a:token .
|
|
\'" token (whose expression includes LTI) found -> return')
|
|
return [1, a:stored_vcol]
|
|
endif
|
|
else
|
|
return [0, 0]
|
|
endif
|
|
endfunction
|
|
|
|
" Purpose:
|
|
" This function is called when a begin-type element ('begin', 'case', '[',
|
|
" '<<', etc.) is found, and in some cases when 'after' and 'when' is found.
|
|
" It asks the caller to return if the stack is already empty.
|
|
" Parameters:
|
|
" stack: [token]
|
|
" token: string
|
|
" curr_vcol: integer
|
|
" stored_vcol: integer
|
|
" end_token: end token that belongs to the begin element found (e.g. if the
|
|
" begin element is 'begin', the end token is 'end')
|
|
" sw: integer -- number of spaces to be used after the begin element as
|
|
" indentation
|
|
" Returns:
|
|
" result: [should_return, indent]
|
|
" should_return: bool -- if true, the caller should return `indent` to Vim
|
|
" indent -- integer
|
|
function! s:BeginElementFound(stack, token, curr_vcol, stored_vcol, end_token, sw)
|
|
|
|
" Return 'return' if the stack is empty
|
|
let [ret, res] = s:BeginElementFoundIfEmpty(a:stack, a:token, a:curr_vcol,
|
|
\a:stored_vcol, a:sw)
|
|
if ret | return [ret, res] | endif
|
|
|
|
if a:stack[0] ==# a:end_token
|
|
call s:Log(' "' . a:token . '" pops "' . a:end_token . '"')
|
|
call s:Pop(a:stack)
|
|
if !empty(a:stack) && a:stack[0] ==# 'align_to_begin_element'
|
|
call s:Pop(a:stack)
|
|
if empty(a:stack)
|
|
return [1, a:curr_vcol]
|
|
else
|
|
return [1, s:UnexpectedToken(a:token, a:stack)]
|
|
endif
|
|
else
|
|
return [0, 0]
|
|
endif
|
|
else
|
|
return [1, s:UnexpectedToken(a:token, a:stack)]
|
|
endif
|
|
endfunction
|
|
|
|
" Purpose:
|
|
" This function is called when we hit the beginning of a file or an
|
|
" end-of-clause token -- i.e. when we found the beginning of the current
|
|
" clause.
|
|
"
|
|
" If the stack contains an '->' or 'when', this means that we can return
|
|
" now, since we were looking for the beginning of the clause.
|
|
" Parameters:
|
|
" stack: [token]
|
|
" token: string
|
|
" stored_vcol: integer
|
|
" lnum: the line number of the "end of clause" mark (or 0 if we hit the
|
|
" beginning of the file)
|
|
" i: the index of the "end of clause" token within its own line
|
|
" Returns:
|
|
" result: [should_return, indent]
|
|
" should_return: bool -- if true, the caller should return `indent` to Vim
|
|
" indent -- integer
|
|
function! s:BeginningOfClauseFound(stack, token, stored_vcol, lnum, i)
|
|
if !empty(a:stack) && a:stack[0] ==# 'when'
|
|
call s:Log(' BeginningOfClauseFound: "when" found in stack')
|
|
call s:Pop(a:stack)
|
|
if empty(a:stack)
|
|
call s:Log(' Stack is ["when"], so LTI is in a guard -> return')
|
|
return [1, a:stored_vcol + shiftwidth() + 2]
|
|
else
|
|
return [1, s:UnexpectedToken(a:token, a:stack)]
|
|
endif
|
|
elseif !empty(a:stack) && a:stack[0] ==# '->'
|
|
call s:Log(' BeginningOfClauseFound: "->" found in stack')
|
|
call s:Pop(a:stack)
|
|
if empty(a:stack)
|
|
call s:Log(' Stack is ["->"], so LTI is in function body -> return')
|
|
return [1, a:stored_vcol + shiftwidth()]
|
|
elseif a:stack[0] ==# ';'
|
|
call s:Pop(a:stack)
|
|
|
|
if !empty(a:stack)
|
|
return [1, s:UnexpectedToken(a:token, a:stack)]
|
|
endif
|
|
|
|
if a:lnum ==# 0
|
|
" Set lnum and i to be NextIndToken-friendly
|
|
let lnum = 1
|
|
let i = -1
|
|
else
|
|
let lnum = a:lnum
|
|
let i = a:i
|
|
endif
|
|
|
|
" Are we after a "-spec func() ...;" clause?
|
|
let [next1_indtoken, next1_lnum, next1_i] = s:NextIndToken(lnum, i)
|
|
if !empty(next1_indtoken) && next1_indtoken[0] =~# '-'
|
|
let [next2_indtoken, next2_lnum, next2_i] =
|
|
\s:NextIndToken(next1_lnum, next1_i)
|
|
if !empty(next2_indtoken) && next2_indtoken[0] =~# 'spec'
|
|
let [next3_indtoken, next3_lnum, next3_i] =
|
|
\s:NextIndToken(next2_lnum, next2_i)
|
|
if !empty(next3_indtoken)
|
|
let [next4_indtoken, next4_lnum, next4_i] =
|
|
\s:NextIndToken(next3_lnum, next3_i)
|
|
if !empty(next4_indtoken)
|
|
" Yes, we are.
|
|
call s:Log(' Stack is ["->", ";"], so LTI is in a "-spec" ' .
|
|
\'attribute -> return')
|
|
return [1, next4_indtoken[1]]
|
|
endif
|
|
endif
|
|
endif
|
|
endif
|
|
|
|
call s:Log(' Stack is ["->", ";"], so LTI is in a function head ' .
|
|
\'-> return')
|
|
return [1, a:stored_vcol]
|
|
|
|
else
|
|
return [1, s:UnexpectedToken(a:token, a:stack)]
|
|
endif
|
|
else
|
|
return [0, 0]
|
|
endif
|
|
endfunction
|
|
|
|
let g:erlang_indent_searchpair_timeout = 2000
|
|
|
|
" TODO
|
|
function! s:SearchPair(lnum, curr_col, start, middle, end)
|
|
call cursor(a:lnum, a:curr_col + 1)
|
|
let [lnum_new, col1_new] =
|
|
\searchpairpos(a:start, a:middle, a:end, 'bW',
|
|
\'synIDattr(synID(line("."), col("."), 0), "name") ' .
|
|
\'=~? "string\\|quotedatom\\|todo\\|comment\\|' .
|
|
\'erlangmodifier"',
|
|
\0, g:erlang_indent_searchpair_timeout)
|
|
return [lnum_new, col1_new - 1]
|
|
endfunction
|
|
|
|
function! s:SearchEndPair(lnum, curr_col)
|
|
return s:SearchPair(
|
|
\ a:lnum, a:curr_col,
|
|
\ '\C\<\%(case\|try\|begin\|receive\|if\|maybe\)\>\|' .
|
|
\ '\<fun\>\%(\s\|\n\|%.*$\|[A-Z_@][a-zA-Z_@]*\)*(',
|
|
\ '',
|
|
\ '\<end\>')
|
|
endfunction
|
|
|
|
" ErlangCalcIndent {{{1
|
|
" ================
|
|
|
|
" Purpose:
|
|
" Calculate the indentation of the given line.
|
|
" Parameters:
|
|
" lnum: integer -- index of the line for which the indentation should be
|
|
" calculated
|
|
" stack: [token] -- initial stack
|
|
" Return:
|
|
" indent: integer -- if -1, that means "don't change the indentation";
|
|
" otherwise it means "indent the line with `indent`
|
|
" number of spaces or equivalent tabs"
|
|
function! s:ErlangCalcIndent(lnum, stack)
|
|
let res = s:ErlangCalcIndent2(a:lnum, a:stack)
|
|
call s:Log("ErlangCalcIndent returned: " . res)
|
|
return res
|
|
endfunction
|
|
|
|
function! s:ErlangCalcIndent2(lnum, stack)
|
|
|
|
let lnum = a:lnum
|
|
let stored_vcol = -1 " Virtual column of the first character of the token that
|
|
" we currently think we might align to.
|
|
let mode = 'normal'
|
|
let stack = a:stack
|
|
let semicolon_abscol = ''
|
|
|
|
" Walk through the lines of the buffer backwards (starting from the
|
|
" previous line) until we can decide how to indent the current line.
|
|
while 1
|
|
|
|
let [lnum, indtokens] = s:TokenizeLine(lnum, 'up')
|
|
|
|
" Hit the start of the file
|
|
if lnum ==# 0
|
|
let [ret, res] = s:BeginningOfClauseFound(stack, 'beginning_of_file',
|
|
\stored_vcol, 0, 0)
|
|
if ret | return res | endif
|
|
|
|
return 0
|
|
endif
|
|
|
|
let i = len(indtokens) - 1
|
|
let last_token_of_line = 1
|
|
|
|
while i >= 0
|
|
|
|
let [token, curr_vcol, curr_col] = indtokens[i]
|
|
call s:Log(' Analyzing the following token: ' . string(indtokens[i]))
|
|
|
|
if len(stack) > 256 " TODO: magic number
|
|
return s:IndentError('Stack too long', token, stack)
|
|
endif
|
|
|
|
if token ==# '<end_of_clause>'
|
|
let [ret, res] = s:BeginningOfClauseFound(stack, token, stored_vcol,
|
|
\lnum, i)
|
|
if ret | return res | endif
|
|
|
|
if stored_vcol ==# -1
|
|
call s:Log(' End of clause directly precedes LTI -> return')
|
|
return 0
|
|
else
|
|
call s:Log(' End of clause (but not end of line) -> return')
|
|
return stored_vcol
|
|
endif
|
|
|
|
elseif stack == ['prev_term_plus']
|
|
if token =~# '[a-zA-Z_@#]' ||
|
|
\ token ==# '<string>' || token ==# '<string_start>' ||
|
|
\ token ==# '<quoted_atom>' || token ==# '<quoted_atom_start>'
|
|
call s:Log(' previous token found: curr_vcol + plus = ' .
|
|
\curr_vcol . " + " . plus)
|
|
return curr_vcol + plus
|
|
endif
|
|
|
|
elseif token ==# 'begin'
|
|
let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
|
|
\stored_vcol, 'end', shiftwidth())
|
|
if ret | return res | endif
|
|
|
|
" case EXPR of BRANCHES end
|
|
" if BRANCHES end
|
|
" try EXPR catch BRANCHES end
|
|
" try EXPR after BODY end
|
|
" try EXPR catch BRANCHES after BODY end
|
|
" try EXPR of BRANCHES catch BRANCHES end
|
|
" try EXPR of BRANCHES after BODY end
|
|
" try EXPR of BRANCHES catch BRANCHES after BODY end
|
|
" receive BRANCHES end
|
|
" receive BRANCHES after BRANCHES end
|
|
" maybe EXPR end
|
|
" maybe EXPR else BRANCHES end
|
|
|
|
" This branch is not Emacs-compatible
|
|
elseif (index(['of', 'receive', 'after', 'if', 'else'], token) != -1 ||
|
|
\ (token ==# 'catch' && !s:IsCatchStandalone(lnum, i))) &&
|
|
\ !last_token_of_line &&
|
|
\ (empty(stack) || stack ==# ['when'] || stack ==# ['->'] ||
|
|
\ stack ==# ['->', ';'])
|
|
|
|
" If we are after of/receive/etc, but these are not the last
|
|
" tokens of the line, we want to indent like this:
|
|
"
|
|
" % stack == []
|
|
" receive stored_vcol,
|
|
" LTI
|
|
"
|
|
" % stack == ['->', ';']
|
|
" receive stored_vcol ->
|
|
" B;
|
|
" LTI
|
|
"
|
|
" % stack == ['->']
|
|
" receive stored_vcol ->
|
|
" LTI
|
|
"
|
|
" % stack == ['when']
|
|
" receive stored_vcol when
|
|
" LTI
|
|
|
|
" stack = [] => LTI is a condition
|
|
" stack = ['->'] => LTI is a branch
|
|
" stack = ['->', ';'] => LTI is a condition
|
|
" stack = ['when'] => LTI is a guard
|
|
if empty(stack) || stack == ['->', ';']
|
|
call s:Log(' LTI is in a condition after ' .
|
|
\'"of/receive/after/if/else/catch" -> return')
|
|
return stored_vcol
|
|
elseif stack == ['->']
|
|
call s:Log(' LTI is in a branch after ' .
|
|
\'"of/receive/after/if/else/catch" -> return')
|
|
return stored_vcol + shiftwidth()
|
|
elseif stack == ['when']
|
|
call s:Log(' LTI is in a guard after ' .
|
|
\'"of/receive/after/if/else/catch" -> return')
|
|
return stored_vcol + shiftwidth()
|
|
else
|
|
return s:UnexpectedToken(token, stack)
|
|
endif
|
|
|
|
elseif index(['case', 'if', 'try', 'receive', 'maybe'], token) != -1
|
|
|
|
" stack = [] => LTI is a condition
|
|
" stack = ['->'] => LTI is a branch
|
|
" stack = ['->', ';'] => LTI is a condition
|
|
" stack = ['when'] => LTI is in a guard
|
|
if empty(stack)
|
|
" pass
|
|
elseif (token ==# 'case' && stack[0] ==# 'of') ||
|
|
\ (token ==# 'if') ||
|
|
\ (token ==# 'maybe' && stack[0] ==# 'else') ||
|
|
\ (token ==# 'try' && (stack[0] ==# 'of' ||
|
|
\ stack[0] ==# 'catch' ||
|
|
\ stack[0] ==# 'after')) ||
|
|
\ (token ==# 'receive')
|
|
|
|
" From the indentation point of view, the keyword
|
|
" (of/catch/after/else/end) before the LTI is what counts, so
|
|
" when we reached these tokens, and the stack already had
|
|
" a catch/after/else/end, we didn't modify it.
|
|
"
|
|
" This way when we reach case/try/receive/maybe (i.e. now),
|
|
" there is at most one of/catch/after/else/end token in the
|
|
" stack.
|
|
if token ==# 'case' || token ==# 'try' ||
|
|
\ (token ==# 'receive' && stack[0] ==# 'after') ||
|
|
\ (token ==# 'maybe' && stack[0] ==# 'else')
|
|
call s:Pop(stack)
|
|
endif
|
|
|
|
if empty(stack)
|
|
call s:Log(' LTI is in a condition; matching ' .
|
|
\'"case/if/try/receive/maybe" found')
|
|
let stored_vcol = curr_vcol + shiftwidth()
|
|
elseif stack[0] ==# 'align_to_begin_element'
|
|
call s:Pop(stack)
|
|
let stored_vcol = curr_vcol
|
|
elseif len(stack) > 1 && stack[0] ==# '->' && stack[1] ==# ';'
|
|
call s:Log(' LTI is in a condition; matching ' .
|
|
\'"case/if/try/receive/maybe" found')
|
|
call s:Pop(stack)
|
|
call s:Pop(stack)
|
|
let stored_vcol = curr_vcol + shiftwidth()
|
|
elseif stack[0] ==# '->'
|
|
call s:Log(' LTI is in a branch; matching ' .
|
|
\'"case/if/try/receive/maybe" found')
|
|
call s:Pop(stack)
|
|
let stored_vcol = curr_vcol + 2 * shiftwidth()
|
|
elseif stack[0] ==# 'when'
|
|
call s:Log(' LTI is in a guard; matching ' .
|
|
\'"case/if/try/receive/maybe" found')
|
|
call s:Pop(stack)
|
|
let stored_vcol = curr_vcol + 2 * shiftwidth() + 2
|
|
endif
|
|
|
|
endif
|
|
|
|
let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
|
|
\stored_vcol, 'end', shiftwidth())
|
|
if ret | return res | endif
|
|
|
|
elseif token ==# 'fun'
|
|
let [next_indtoken, next_lnum, next_i] = s:NextIndToken(lnum, i)
|
|
call s:Log(' Next indtoken = ' . string(next_indtoken))
|
|
|
|
if !empty(next_indtoken) && next_indtoken[0] =~# '^[A-Z_@]'
|
|
" The "fun" is followed by a variable, so we might have a named fun:
|
|
" "fun Fun() -> ok end". Thus we take the next token to decide
|
|
" whether this is a function definition ("fun()") or just a function
|
|
" reference ("fun Mod:Fun").
|
|
let [next_indtoken, _, _] = s:NextIndToken(next_lnum, next_i)
|
|
call s:Log(' Next indtoken = ' . string(next_indtoken))
|
|
endif
|
|
|
|
if !empty(next_indtoken) && next_indtoken[0] ==# '('
|
|
" We have an anonymous function definition
|
|
" (e.g. "fun () -> ok end")
|
|
|
|
" stack = [] => LTI is a condition
|
|
" stack = ['->'] => LTI is a branch
|
|
" stack = ['->', ';'] => LTI is a condition
|
|
" stack = ['when'] => LTI is in a guard
|
|
if empty(stack)
|
|
call s:Log(' LTI is in a condition; matching "fun" found')
|
|
let stored_vcol = curr_vcol + shiftwidth()
|
|
elseif len(stack) > 1 && stack[0] ==# '->' && stack[1] ==# ';'
|
|
call s:Log(' LTI is in a condition; matching "fun" found')
|
|
call s:Pop(stack)
|
|
call s:Pop(stack)
|
|
elseif stack[0] ==# '->'
|
|
call s:Log(' LTI is in a branch; matching "fun" found')
|
|
call s:Pop(stack)
|
|
let stored_vcol = curr_vcol + 2 * shiftwidth()
|
|
elseif stack[0] ==# 'when'
|
|
call s:Log(' LTI is in a guard; matching "fun" found')
|
|
call s:Pop(stack)
|
|
let stored_vcol = curr_vcol + 2 * shiftwidth() + 2
|
|
endif
|
|
|
|
let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
|
|
\stored_vcol, 'end', shiftwidth())
|
|
if ret | return res | endif
|
|
else
|
|
" Pass: we have a function reference (e.g. "fun f/0")
|
|
endif
|
|
|
|
elseif token ==# '['
|
|
" Emacs compatibility
|
|
let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
|
|
\stored_vcol, ']', 1)
|
|
if ret | return res | endif
|
|
|
|
elseif token ==# '<<'
|
|
" Emacs compatibility
|
|
let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
|
|
\stored_vcol, '>>', 2)
|
|
if ret | return res | endif
|
|
|
|
elseif token ==# '(' || token ==# '{'
|
|
|
|
let end_token = (token ==# '(' ? ')' :
|
|
\token ==# '{' ? '}' : 'error')
|
|
|
|
if empty(stack)
|
|
" We found the opening paren whose block contains the LTI.
|
|
let mode = 'inside'
|
|
elseif stack[0] ==# end_token
|
|
call s:Log(' "' . token . '" pops "' . end_token . '"')
|
|
call s:Pop(stack)
|
|
|
|
if !empty(stack) && stack[0] ==# 'align_to_begin_element'
|
|
" We found the opening paren whose closing paren
|
|
" starts LTI
|
|
let mode = 'align_to_begin_element'
|
|
else
|
|
" We found the opening pair for a closing paren that
|
|
" was already in the stack.
|
|
let mode = 'outside'
|
|
endif
|
|
else
|
|
return s:UnexpectedToken(token, stack)
|
|
endif
|
|
|
|
if mode ==# 'inside' || mode ==# 'align_to_begin_element'
|
|
|
|
if last_token_of_line && i != 0
|
|
" Examples: {{{
|
|
"
|
|
" mode == 'inside':
|
|
"
|
|
" my_func(
|
|
" LTI
|
|
"
|
|
" [Variable, {
|
|
" LTI
|
|
"
|
|
" mode == 'align_to_begin_element':
|
|
"
|
|
" my_func(
|
|
" Params
|
|
" ) % LTI
|
|
"
|
|
" [Variable, {
|
|
" Terms
|
|
" } % LTI
|
|
" }}}
|
|
let stack = ['prev_term_plus']
|
|
let plus = (mode ==# 'inside' ? 2 : 1)
|
|
call s:Log(' "' . token .
|
|
\'" token found at end of line -> find previous token')
|
|
elseif mode ==# 'align_to_begin_element'
|
|
" Examples: {{{
|
|
"
|
|
" mode == 'align_to_begin_element' && !last_token_of_line
|
|
"
|
|
" my_func(stored_vcol
|
|
" ) % LTI
|
|
"
|
|
" [Variable, {stored_vcol
|
|
" } % LTI
|
|
"
|
|
" mode == 'align_to_begin_element' && i == 0
|
|
"
|
|
" (
|
|
" stored_vcol
|
|
" ) % LTI
|
|
"
|
|
" {
|
|
" stored_vcol
|
|
" } % LTI
|
|
" }}}
|
|
call s:Log(' "' . token . '" token (whose closing token ' .
|
|
\'starts LTI) found -> return')
|
|
return curr_vcol
|
|
elseif stored_vcol ==# -1
|
|
" Examples: {{{
|
|
"
|
|
" mode == 'inside' && stored_vcol == -1 && !last_token_of_line
|
|
"
|
|
" my_func(
|
|
" LTI
|
|
" [Variable, {
|
|
" LTI
|
|
"
|
|
" mode == 'inside' && stored_vcol == -1 && i == 0
|
|
"
|
|
" (
|
|
" LTI
|
|
"
|
|
" {
|
|
" LTI
|
|
" }}}
|
|
call s:Log(' "' . token .
|
|
\'" token (which directly precedes LTI) found -> return')
|
|
return curr_vcol + 1
|
|
else
|
|
" Examples: {{{
|
|
"
|
|
" mode == 'inside' && stored_vcol != -1 && !last_token_of_line
|
|
"
|
|
" my_func(stored_vcol,
|
|
" LTI
|
|
"
|
|
" [Variable, {stored_vcol,
|
|
" LTI
|
|
"
|
|
" mode == 'inside' && stored_vcol != -1 && i == 0
|
|
"
|
|
" (stored_vcol,
|
|
" LTI
|
|
"
|
|
" {stored_vcol,
|
|
" LTI
|
|
" }}}
|
|
call s:Log(' "' . token .
|
|
\'" token (whose block contains LTI) found -> return')
|
|
return stored_vcol
|
|
endif
|
|
endif
|
|
|
|
elseif index(['end', ')', ']', '}', '>>'], token) != -1
|
|
|
|
" If we can be sure that there is synchronization in the Erlang
|
|
" syntax, we use searchpair to make the script quicker. Otherwise we
|
|
" just push the token onto the stack and keep parsing.
|
|
|
|
" No synchronization -> no searchpair optimization
|
|
if !exists('b:erlang_syntax_synced')
|
|
call s:Push(stack, token)
|
|
|
|
" We don't have searchpair optimization for '>>'
|
|
elseif token ==# '>>'
|
|
call s:Push(stack, token)
|
|
|
|
elseif token ==# 'end'
|
|
let [lnum_new, col_new] = s:SearchEndPair(lnum, curr_col)
|
|
|
|
if lnum_new ==# 0
|
|
return s:IndentError('Matching token for "end" not found',
|
|
\token, stack)
|
|
else
|
|
if lnum_new != lnum
|
|
call s:Log(' Tokenize for "end" <<<<')
|
|
let [lnum, indtokens] = s:TokenizeLine(lnum_new, 'up')
|
|
call s:Log(' >>>> Tokenize for "end"')
|
|
endif
|
|
|
|
let [success, i] = s:GetIndtokenAtCol(indtokens, col_new)
|
|
if !success | return i | endif
|
|
let [token, curr_vcol, curr_col] = indtokens[i]
|
|
call s:Log(' Match for "end" in line ' . lnum_new . ': ' .
|
|
\string(indtokens[i]))
|
|
endif
|
|
|
|
else " token is one of the following: ')', ']', '}'
|
|
|
|
call s:Push(stack, token)
|
|
|
|
" We have to escape '[', because this string will be interpreted as a
|
|
" regexp
|
|
let open_paren = (token ==# ')' ? '(' :
|
|
\token ==# ']' ? '\[' :
|
|
\ '{')
|
|
|
|
let [lnum_new, col_new] = s:SearchPair(lnum, curr_col,
|
|
\open_paren, '', token)
|
|
|
|
if lnum_new ==# 0
|
|
return s:IndentError('Matching token not found',
|
|
\token, stack)
|
|
else
|
|
if lnum_new != lnum
|
|
call s:Log(' Tokenize the opening paren <<<<')
|
|
let [lnum, indtokens] = s:TokenizeLine(lnum_new, 'up')
|
|
call s:Log(' >>>>')
|
|
endif
|
|
|
|
let [success, i] = s:GetIndtokenAtCol(indtokens, col_new)
|
|
if !success | return i | endif
|
|
let [token, curr_vcol, curr_col] = indtokens[i]
|
|
call s:Log(' Match in line ' . lnum_new . ': ' .
|
|
\string(indtokens[i]))
|
|
|
|
" Go back to the beginning of the loop and handle the opening paren
|
|
continue
|
|
endif
|
|
endif
|
|
|
|
elseif token ==# ';'
|
|
|
|
if empty(stack)
|
|
call s:Push(stack, ';')
|
|
elseif index([';', '->', 'when', 'end', 'after', 'catch', 'else'],
|
|
\stack[0]) != -1
|
|
" Pass:
|
|
"
|
|
" - If the stack top is another ';', then one ';' is
|
|
" enough.
|
|
" - If the stack top is an '->' or a 'when', then we
|
|
" should keep that, because they signify the type of the
|
|
" LTI (branch, condition or guard).
|
|
" - From the indentation point of view, the keyword
|
|
" (of/catch/after/else/end) before the LTI is what counts, so
|
|
" if the stack already has a catch/after/else/end, we don't
|
|
" modify it. This way when we reach case/try/receive/maybe,
|
|
" there will be at most one of/catch/after/else/end token in
|
|
" the stack.
|
|
else
|
|
return s:UnexpectedToken(token, stack)
|
|
endif
|
|
|
|
elseif token ==# '->'
|
|
|
|
if empty(stack) && !last_token_of_line
|
|
call s:Log(' LTI is in expression after arrow -> return')
|
|
return stored_vcol
|
|
elseif empty(stack) || stack[0] ==# ';' || stack[0] ==# 'end'
|
|
" stack = [';'] -> LTI is either a branch or in a guard
|
|
" stack = ['->'] -> LTI is a condition
|
|
" stack = ['->', ';'] -> LTI is a branch
|
|
call s:Push(stack, '->')
|
|
elseif index(['->', 'when', 'end', 'after', 'catch', 'else'],
|
|
\stack[0]) != -1
|
|
" Pass:
|
|
"
|
|
" - If the stack top is another '->', then one '->' is
|
|
" enough.
|
|
" - If the stack top is a 'when', then we should keep
|
|
" that, because this signifies that LTI is a in a guard.
|
|
" - From the indentation point of view, the keyword
|
|
" (of/catch/after/else/end) before the LTI is what counts, so
|
|
" if the stack already has a catch/after/else/end, we don't
|
|
" modify it. This way when we reach case/try/receive/maybe,
|
|
" there will be at most one of/catch/after/else/end token in
|
|
" the stack.
|
|
else
|
|
return s:UnexpectedToken(token, stack)
|
|
endif
|
|
|
|
elseif token ==# 'when'
|
|
|
|
" Pop all ';' from the top of the stack
|
|
while !empty(stack) && stack[0] ==# ';'
|
|
call s:Pop(stack)
|
|
endwhile
|
|
|
|
if empty(stack)
|
|
if semicolon_abscol != ''
|
|
let stored_vcol = semicolon_abscol
|
|
endif
|
|
if !last_token_of_line
|
|
" Example:
|
|
" when A,
|
|
" LTI
|
|
let [ret, res] = s:BeginElementFoundIfEmpty(stack, token, curr_vcol,
|
|
\stored_vcol, shiftwidth())
|
|
if ret | return res | endif
|
|
else
|
|
" Example:
|
|
" when
|
|
" LTI
|
|
call s:Push(stack, token)
|
|
endif
|
|
elseif index(['->', 'when', 'end', 'after', 'catch', 'else'],
|
|
\stack[0]) != -1
|
|
" Pass:
|
|
" - If the stack top is another 'when', then one 'when' is
|
|
" enough.
|
|
" - If the stack top is an '->' or a 'when', then we
|
|
" should keep that, because they signify the type of the
|
|
" LTI (branch, condition or guard).
|
|
" - From the indentation point of view, the keyword
|
|
" (of/catch/after/else/end) before the LTI is what counts, so
|
|
" if the stack already has a catch/after/else/end, we don't
|
|
" modify it. This way when we reach case/try/receive/maybe,
|
|
" there will be at most one of/catch/after/else/end token in
|
|
" the stack.
|
|
else
|
|
return s:UnexpectedToken(token, stack)
|
|
endif
|
|
|
|
elseif token ==# 'of' || token ==# 'after' || token ==# 'else' ||
|
|
\ (token ==# 'catch' && !s:IsCatchStandalone(lnum, i))
|
|
|
|
if token ==# 'after' || token ==# 'else'
|
|
" If LTI is between an after/else and the corresponding 'end', then
|
|
" let's return because calculating the indentation based on
|
|
" after/else is enough.
|
|
"
|
|
" Example:
|
|
" receive A after
|
|
" LTI
|
|
" maybe A else
|
|
" LTI
|
|
"
|
|
" Note about Emacs compatibility {{{
|
|
"
|
|
" It would be fine to indent the examples above the following way:
|
|
"
|
|
" receive A after
|
|
" LTI
|
|
" maybe A else
|
|
" LTI
|
|
"
|
|
" We intend it the way above because that is how Emacs does it.
|
|
" Also, this is a bit faster.
|
|
"
|
|
" We are still not 100% Emacs compatible because of placing the
|
|
" 'end' after the indented blocks.
|
|
"
|
|
" Emacs example:
|
|
"
|
|
" receive A after
|
|
" LTI
|
|
" end,
|
|
" maybe A else
|
|
" LTI
|
|
" end % Yes, it's here (in OTP 25.0, might change
|
|
" % later)
|
|
"
|
|
" vim-erlang example:
|
|
"
|
|
" receive A after
|
|
" LTI
|
|
" end,
|
|
" maybe A else
|
|
" LTI
|
|
" end
|
|
" }}}
|
|
let [ret, res] = s:BeginElementFoundIfEmpty(stack, token, curr_vcol,
|
|
\stored_vcol, shiftwidth())
|
|
if ret | return res | endif
|
|
endif
|
|
|
|
if empty(stack) || stack[0] ==# '->' || stack[0] ==# 'when'
|
|
call s:Push(stack, token)
|
|
elseif stack[0] ==# 'catch' || stack[0] ==# 'after' ||
|
|
\stack[0] ==# 'else' || stack[0] ==# 'end'
|
|
" Pass: From the indentation point of view, the keyword
|
|
" (of/catch/after/end) before the LTI is what counts, so
|
|
" if the stack already has a catch/after/end, we don't
|
|
" modify it. This way when we reach case/try/receive,
|
|
" there will be at most one of/catch/after/end token in
|
|
" the stack.
|
|
else
|
|
return s:UnexpectedToken(token, stack)
|
|
endif
|
|
|
|
elseif token ==# '||' && empty(stack) && !last_token_of_line
|
|
|
|
call s:Log(' LTI is in expression after "||" -> return')
|
|
return stored_vcol
|
|
|
|
else
|
|
call s:Log(' Misc token, stack unchanged = ' . string(stack))
|
|
|
|
endif
|
|
|
|
if empty(stack) || stack[0] ==# '->' || stack[0] ==# 'when'
|
|
let stored_vcol = curr_vcol
|
|
let semicolon_abscol = ''
|
|
call s:Log(' Misc token when the stack is empty or has "->" ' .
|
|
\'-> setting stored_vcol to ' . stored_vcol)
|
|
elseif stack[0] ==# ';'
|
|
let semicolon_abscol = curr_vcol
|
|
call s:Log(' Setting semicolon-stored_vcol to ' . stored_vcol)
|
|
endif
|
|
|
|
let i -= 1
|
|
call s:Log(' Token processed. stored_vcol=' . stored_vcol)
|
|
|
|
let last_token_of_line = 0
|
|
|
|
endwhile " iteration on tokens in a line
|
|
|
|
call s:Log(' Line analyzed. stored_vcol=' . stored_vcol)
|
|
|
|
if empty(stack) && stored_vcol != -1 &&
|
|
\ (!empty(indtokens) && indtokens[0][0] != '<string_end>' &&
|
|
\ indtokens[0][0] != '<quoted_atom_end>')
|
|
call s:Log(' Empty stack at the beginning of the line -> return')
|
|
return stored_vcol
|
|
endif
|
|
|
|
let lnum -= 1
|
|
|
|
endwhile " iteration on lines
|
|
|
|
endfunction
|
|
|
|
" ErlangIndent function {{{1
|
|
" =====================
|
|
|
|
function! ErlangIndent()
|
|
|
|
call s:ClearTokenCacheIfNeeded()
|
|
|
|
let currline = getline(v:lnum)
|
|
call s:Log('Indenting line ' . v:lnum . ': ' . currline)
|
|
|
|
if s:IsLineStringContinuation(v:lnum) || s:IsLineAtomContinuation(v:lnum)
|
|
call s:Log('String or atom continuation found -> ' .
|
|
\'leaving indentation unchanged')
|
|
return -1
|
|
endif
|
|
|
|
" If the line starts with the comment, and so is the previous non-blank line
|
|
if currline =~# '^\s*%'
|
|
let lnum = prevnonblank(v:lnum - 1)
|
|
if lnum ==# 0
|
|
call s:Log('First non-empty line of the file -> return 0.')
|
|
return 0
|
|
else
|
|
let ml = matchlist(getline(lnum), '^\(\s*\)%')
|
|
" If the previous line also starts with a comment, then return the same
|
|
" indentation that line has. Otherwise exit from this special "if" and
|
|
" don't care that the current line is a comment.
|
|
if !empty(ml)
|
|
let new_col = s:CalcVCol(ml[1], 0, len(ml[1]) - 1, 0, &tabstop)
|
|
call s:Log('Comment line after another comment line -> ' .
|
|
\'use same indent: ' . new_col)
|
|
return new_col
|
|
endif
|
|
endif
|
|
endif
|
|
|
|
let ml = matchlist(currline,
|
|
\'^\(\s*\)\(\%(end\|of\|catch\|after\|else\)\>\|[)\]}]\|>>\)')
|
|
|
|
" If the line has a special beginning, but not a standalone catch
|
|
if !empty(ml) && !(ml[2] ==# 'catch' && s:IsCatchStandalone(v:lnum, 0))
|
|
|
|
let curr_col = len(ml[1])
|
|
|
|
" If we can be sure that there is synchronization in the Erlang
|
|
" syntax, we use searchpair to make the script quicker.
|
|
if ml[2] ==# 'end' && exists('b:erlang_syntax_synced')
|
|
|
|
let [lnum, col] = s:SearchEndPair(v:lnum, curr_col)
|
|
|
|
if lnum ==# 0
|
|
return s:IndentError('Matching token for "end" not found',
|
|
\'end', [])
|
|
else
|
|
call s:Log(' Tokenize for "end" <<<<')
|
|
let [lnum, indtokens] = s:TokenizeLine(lnum, 'up')
|
|
call s:Log(' >>>> Tokenize for "end"')
|
|
|
|
let [success, i] = s:GetIndtokenAtCol(indtokens, col)
|
|
if !success | return i | endif
|
|
let [token, curr_vcol, curr_col] = indtokens[i]
|
|
call s:Log(' Match for "end" in line ' . lnum . ': ' .
|
|
\string(indtokens[i]))
|
|
return curr_vcol
|
|
endif
|
|
|
|
else
|
|
|
|
call s:Log(" Line type = 'end'")
|
|
let new_col = s:ErlangCalcIndent(v:lnum - 1,
|
|
\[ml[2], 'align_to_begin_element'])
|
|
endif
|
|
else
|
|
call s:Log(" Line type = 'normal'")
|
|
|
|
let new_col = s:ErlangCalcIndent(v:lnum - 1, [])
|
|
if currline =~# '^\s*when\>'
|
|
let new_col += 2
|
|
endif
|
|
endif
|
|
|
|
if new_col < -1
|
|
call s:Log('WARNING: returning new_col == ' . new_col)
|
|
return g:erlang_unexpected_token_indent
|
|
endif
|
|
|
|
return new_col
|
|
|
|
endfunction
|
|
|
|
" ErlangShowTokensInLine functions {{{1
|
|
" ================================
|
|
|
|
" These functions are useful during development.
|
|
|
|
function! ErlangShowTokensInLine(line)
|
|
echo "Line: " . a:line
|
|
let indtokens = s:GetTokensFromLine(a:line, 0, 0, &tabstop)
|
|
echo "Tokens:"
|
|
for it in indtokens
|
|
echo it
|
|
endfor
|
|
endfunction
|
|
|
|
function! ErlangShowTokensInCurrentLine()
|
|
return ErlangShowTokensInLine(getline('.'))
|
|
endfunction
|
|
|
|
" Cleanup {{{1
|
|
" =======
|
|
|
|
let &cpo = s:cpo_save
|
|
unlet s:cpo_save
|
|
|
|
" vim: sw=2 et fdm=marker
|