neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

erlang.vim (51955B)


      1 " Vim indent file
      2 " Language:     Erlang (http://www.erlang.org)
      3 " Author:       Csaba Hoch <csaba.hoch@gmail.com>
      4 " Contributors: Edwin Fine <efine145_nospam01 at usa dot net>
      5 "               Pawel 'kTT' Salata <rockplayer.pl@gmail.com>
      6 "               Ricardo Catalinas Jiménez <jimenezrick@gmail.com>
      7 " Last Update:  2022-Sep-06
      8 " License:      Vim license
      9 " URL:          https://github.com/vim-erlang/vim-erlang-runtime
     10 
     11 " Note About Usage:
     12 "   This indentation script works best with the Erlang syntax file created by
     13 "   Kreąimir Marľić (Kresimir Marzic) and maintained by Csaba Hoch.
     14 
     15 " Notes About Implementation:
     16 "
     17 " - LTI = Line to indent.
     18 " - The index of the first line is 1, but the index of the first column is 0.
     19 
     20 
     21 " Initialization {{{1
     22 " ==============
     23 
     24 " Only load this indent file when no other was loaded
     25 " Vim 7 or later is needed
     26 if exists("b:did_indent") || version < 700
     27  finish
     28 else
     29  let b:did_indent = 1
     30 endif
     31 
     32 setlocal indentexpr=ErlangIndent()
     33 setlocal indentkeys+=0=end,0=of,0=catch,0=after,0=else,0=when,0=),0=],0=},0=>>
     34 
     35 let b:undo_indent = "setl inde< indk<"
     36 
     37 " Only define the functions once
     38 if exists("*ErlangIndent")
     39  finish
     40 endif
     41 
     42 let s:cpo_save = &cpo
     43 set cpo&vim
     44 
     45 " Logging library {{{1
     46 " ===============
     47 
     48 " Purpose:
     49 "   Logs the given string using the ErlangIndentLog function if it exists.
     50 " Parameters:
     51 "   s: string
     52 function! s:Log(s)
     53  if exists("*ErlangIndentLog")
     54    call ErlangIndentLog(a:s)
     55  endif
     56 endfunction
     57 
     58 " Line tokenizer library {{{1
     59 " ======================
     60 
     61 " Indtokens are "indentation tokens". See their exact format in the
     62 " documentation of the s:GetTokensFromLine function.
     63 
     64 " Purpose:
     65 "   Calculate the new virtual column after the given segment of a line.
     66 " Parameters:
     67 "   line: string
     68 "   first_index: integer -- the index of the first character of the segment
     69 "   last_index: integer -- the index of the last character of the segment
     70 "   vcol: integer -- the virtual column of the first character of the token
     71 "   tabstop: integer -- the value of the 'tabstop' option to be used
     72 " Returns:
     73 "   vcol: integer
     74 " Example:
     75 "   " index:    0 12 34567
     76 "   " vcol:     0 45 89
     77 "   s:CalcVCol("\t'\tx', b", 1, 4, 4)  -> 10
     78 function! s:CalcVCol(line, first_index, last_index, vcol, tabstop)
     79 
     80  " We copy the relevant segment of the line, otherwise if the line were
     81  " e.g. `"\t", term` then the else branch below would consume the `", term`
     82  " part at once.
     83  let line = a:line[a:first_index : a:last_index]
     84 
     85  let i = 0
     86  let last_index = a:last_index - a:first_index
     87  let vcol = a:vcol
     88 
     89  while 0 <= i && i <= last_index
     90 
     91    if line[i] ==# "\t"
     92      " Example (when tabstop == 4):
     93      "
     94      " vcol + tab -> next_vcol
     95      " 0 + tab -> 4
     96      " 1 + tab -> 4
     97      " 2 + tab -> 4
     98      " 3 + tab -> 4
     99      " 4 + tab -> 8
    100      "
    101      " next_i - i == the number of tabs
    102      let next_i = matchend(line, '\t*', i + 1)
    103      let vcol = (vcol / a:tabstop + (next_i - i)) * a:tabstop
    104      call s:Log('new vcol after tab: '. vcol)
    105    else
    106      let next_i = matchend(line, '[^\t]*', i + 1)
    107      let vcol += next_i - i
    108      call s:Log('new vcol after other: '. vcol)
    109    endif
    110    let i = next_i
    111  endwhile
    112 
    113  return vcol
    114 endfunction
    115 
    116 " Purpose:
    117 "   Go through the whole line and return the tokens in the line.
    118 " Parameters:
    119 "   line: string -- the line to be examined
    120 "   string_continuation: bool
    121 "   atom_continuation: bool
    122 " Returns:
    123 "   indtokens = [indtoken]
    124 "   indtoken = [token, vcol, col]
    125 "   token = string (examples: 'begin', '<quoted_atom>', '}')
    126 "   vcol = integer (the virtual column of the first character of the token;
    127 "                   counting starts from 0)
    128 "   col = integer (counting starts from 0)
    129 function! s:GetTokensFromLine(line, string_continuation, atom_continuation,
    130                             \tabstop)
    131 
    132  let linelen = strlen(a:line) " The length of the line
    133  let i = 0 " The index of the current character in the line
    134  let vcol = 0 " The virtual column of the current character
    135  let indtokens = []
    136 
    137  if a:string_continuation
    138    let i = matchend(a:line, '^\%([^"\\]\|\\.\)*"', 0)
    139    if i ==# -1
    140      call s:Log('    Whole line is string continuation -> ignore')
    141      return []
    142    else
    143      let vcol = s:CalcVCol(a:line, 0, i - 1, 0, a:tabstop)
    144      call add(indtokens, ['<string_end>', vcol, i])
    145    endif
    146  elseif a:atom_continuation
    147    let i = matchend(a:line, "^\\%([^'\\\\]\\|\\\\.\\)*'", 0)
    148    if i ==# -1
    149      call s:Log('    Whole line is quoted atom continuation -> ignore')
    150      return []
    151    else
    152      let vcol = s:CalcVCol(a:line, 0, i - 1, 0, a:tabstop)
    153      call add(indtokens, ['<quoted_atom_end>', vcol, i])
    154    endif
    155  endif
    156 
    157  while 0 <= i && i < linelen
    158 
    159    let next_vcol = ''
    160 
    161    " Spaces
    162    if a:line[i] ==# ' '
    163      let next_i = matchend(a:line, ' *', i + 1)
    164 
    165    " Tabs
    166    elseif a:line[i] ==# "\t"
    167      let next_i = matchend(a:line, '\t*', i + 1)
    168 
    169      " See example in s:CalcVCol
    170      let next_vcol = (vcol / a:tabstop + (next_i - i)) * a:tabstop
    171 
    172    " Comment
    173    elseif a:line[i] ==# '%'
    174      let next_i = linelen
    175 
    176    " String token: "..."
    177    elseif a:line[i] ==# '"'
    178      let next_i = matchend(a:line, '\%([^"\\]\|\\.\)*"', i + 1)
    179      if next_i ==# -1
    180        call add(indtokens, ['<string_start>', vcol, i])
    181      else
    182        let next_vcol = s:CalcVCol(a:line, i, next_i - 1, vcol, a:tabstop)
    183        call add(indtokens, ['<string>', vcol, i])
    184      endif
    185 
    186    " Quoted atom token: '...'
    187    elseif a:line[i] ==# "'"
    188      let next_i = matchend(a:line, "\\%([^'\\\\]\\|\\\\.\\)*'", i + 1)
    189      if next_i ==# -1
    190        call add(indtokens, ['<quoted_atom_start>', vcol, i])
    191      else
    192        let next_vcol = s:CalcVCol(a:line, i, next_i - 1, vcol, a:tabstop)
    193        call add(indtokens, ['<quoted_atom>', vcol, i])
    194      endif
    195 
    196    " Keyword or atom or variable token or number
    197    elseif a:line[i] =~# '[a-zA-Z_@0-9]'
    198      let next_i = matchend(a:line,
    199                           \'[[:alnum:]_@:]*\%(\s*#\s*[[:alnum:]_@:]*\)\=',
    200                           \i + 1)
    201      call add(indtokens, [a:line[(i):(next_i - 1)], vcol, i])
    202 
    203    " Character token: $<char> (as in: $a)
    204    elseif a:line[i] ==# '$'
    205      call add(indtokens, ['$.', vcol, i])
    206      let next_i = i + 2
    207 
    208    " Dot token: .
    209    elseif a:line[i] ==# '.'
    210 
    211      let next_i = i + 1
    212 
    213      if i + 1 ==# linelen || a:line[i + 1] =~# '[[:blank:]%]'
    214        " End of clause token: . (as in: f() -> ok.)
    215        call add(indtokens, ['<end_of_clause>', vcol, i])
    216 
    217      else
    218        " Possibilities:
    219        " - Dot token in float: . (as in: 3.14)
    220        " - Dot token in record: . (as in: #myrec.myfield)
    221        call add(indtokens, ['.', vcol, i])
    222      endif
    223 
    224    " Equal sign
    225    elseif a:line[i] ==# '='
    226      " This is handled separately so that "=<<" will be parsed as
    227      " ['=', '<<'] instead of ['=<', '<']. Although Erlang parses it
    228      " currently in the latter way, that may be fixed some day.
    229      call add(indtokens, [a:line[i], vcol, i])
    230      let next_i = i + 1
    231 
    232    " Three-character tokens
    233    elseif i + 1 < linelen &&
    234         \ index(['=:=', '=/='], a:line[i : i + 1]) != -1
    235      call add(indtokens, [a:line[i : i + 1], vcol, i])
    236      let next_i = i + 2
    237 
    238    " Two-character tokens
    239    elseif i + 1 < linelen &&
    240         \ index(['->', '<<', '>>', '||', '==', '/=', '=<', '>=', '?=', '++',
    241         \        '--', '::'],
    242         \       a:line[i : i + 1]) != -1
    243      call add(indtokens, [a:line[i : i + 1], vcol, i])
    244      let next_i = i + 2
    245 
    246    " Other character: , ; < > ( ) [ ] { } # + - * / : ? = ! |
    247    else
    248      call add(indtokens, [a:line[i], vcol, i])
    249      let next_i = i + 1
    250 
    251    endif
    252 
    253    if next_vcol ==# ''
    254      let vcol += next_i - i
    255    else
    256      let vcol = next_vcol
    257    endif
    258 
    259    let i = next_i
    260 
    261  endwhile
    262 
    263  return indtokens
    264 
    265 endfunction
    266 
    267 " TODO: doc, handle "not found" case
    268 function! s:GetIndtokenAtCol(indtokens, col)
    269  let i = 0
    270  while i < len(a:indtokens)
    271    if a:indtokens[i][2] ==# a:col
    272      return [1, i]
    273    elseif a:indtokens[i][2] > a:col
    274      return [0, s:IndentError('No token at col ' . a:col . ', ' .
    275                              \'indtokens = ' . string(a:indtokens),
    276                              \'', '')]
    277    endif
    278    let i += 1
    279  endwhile
    280  return [0, s:IndentError('No token at col ' . a:col . ', ' .
    281                           \'indtokens = ' . string(a:indtokens),
    282                           \'', '')]
    283 endfunction
    284 
    285 " Stack library {{{1
    286 " =============
    287 
    288 " Purpose:
    289 "   Push a token onto the parser's stack.
    290 " Parameters:
    291 "   stack: [token]
    292 "   token: string
    293 function! s:Push(stack, token)
    294  call s:Log('    Stack Push: "' . a:token . '" into ' . string(a:stack))
    295  call insert(a:stack, a:token)
    296 endfunction
    297 
    298 " Purpose:
    299 "   Pop a token from the parser's stack.
    300 " Parameters:
    301 "   stack: [token]
    302 "   token: string
    303 " Returns:
    304 "   token: string -- the removed element
    305 function! s:Pop(stack)
    306  let head = remove(a:stack, 0)
    307  call s:Log('    Stack Pop: "' . head . '" from ' . string(a:stack))
    308  return head
    309 endfunction
    310 
    311 " Library for accessing and storing tokenized lines {{{1
    312 " =================================================
    313 
    314 " The Erlang token cache: an `lnum -> indtokens` dictionary that stores the
    315 " tokenized lines.
    316 let s:all_tokens = {}
    317 let s:file_name = ''
    318 let s:last_changedtick = -1
    319 
    320 " Purpose:
    321 "   Clear the Erlang token cache if we have a different file or the file has
    322 "   been changed since the last indentation.
    323 function! s:ClearTokenCacheIfNeeded()
    324  let file_name = expand('%:p')
    325  if file_name != s:file_name ||
    326   \ b:changedtick != s:last_changedtick
    327    let s:file_name = file_name
    328    let s:last_changedtick = b:changedtick
    329    let s:all_tokens = {}
    330  endif
    331 endfunction
    332 
    333 " Purpose:
    334 "   Return the tokens of line `lnum`, if that line is not empty. If it is
    335 "   empty, find the first non-empty line in the given `direction` and return
    336 "   the tokens of that line.
    337 " Parameters:
    338 "   lnum: integer
    339 "   direction: 'up' | 'down'
    340 " Returns:
    341 "   result: [] -- the result is an empty list if we hit the beginning or end
    342 "                  of the file
    343 "           | [lnum, indtokens]
    344 "   lnum: integer -- the index of the non-empty line that was found and
    345 "                    tokenized
    346 "   indtokens: [indtoken] -- the tokens of line `lnum`
    347 function! s:TokenizeLine(lnum, direction)
    348 
    349  call s:Log('Tokenizing starts from line ' . a:lnum)
    350  if a:direction ==# 'up'
    351    let lnum = prevnonblank(a:lnum)
    352  else " a:direction ==# 'down'
    353    let lnum = nextnonblank(a:lnum)
    354  endif
    355 
    356  " We hit the beginning or end of the file
    357  if lnum ==# 0
    358    let indtokens = []
    359    call s:Log('  We hit the beginning or end of the file.')
    360 
    361    " The line has already been parsed
    362  elseif has_key(s:all_tokens, lnum)
    363    let indtokens = s:all_tokens[lnum]
    364    call s:Log('Cached line ' . lnum . ': ' . getline(lnum))
    365    call s:Log("  Tokens in the line:\n    - " . join(indtokens, "\n    - "))
    366 
    367    " The line should be parsed now
    368  else
    369 
    370    " Parse the line
    371    let line = getline(lnum)
    372    let string_continuation = s:IsLineStringContinuation(lnum)
    373    let atom_continuation = s:IsLineAtomContinuation(lnum)
    374    let indtokens = s:GetTokensFromLine(line, string_continuation,
    375                                       \atom_continuation, &tabstop)
    376    let s:all_tokens[lnum] = indtokens
    377    call s:Log('Tokenizing line ' . lnum . ': ' . line)
    378    call s:Log("  Tokens in the line:\n    - " . join(indtokens, "\n    - "))
    379 
    380  endif
    381 
    382  return [lnum, indtokens]
    383 endfunction
    384 
    385 " Purpose:
    386 "   As a helper function for PrevIndToken and NextIndToken, the FindIndToken
    387 "   function finds the first line with at least one token in the given
    388 "   direction.
    389 " Parameters:
    390 "   lnum: integer
    391 "   direction: 'up' | 'down'
    392 " Returns:
    393 "   result: [[], 0, 0]
    394 "             -- the result is an empty list if we hit the beginning or end of
    395 "             the file
    396 "           | [indtoken, lnum, i]
    397 "             -- the content, lnum and token index of the next (or previous)
    398 "             indtoken
    399 function! s:FindIndToken(lnum, dir)
    400  let lnum = a:lnum
    401  while 1
    402    let lnum += (a:dir ==# 'up' ? -1 : 1)
    403    let [lnum, indtokens] = s:TokenizeLine(lnum, a:dir)
    404    if lnum ==# 0
    405      " We hit the beginning or end of the file
    406      return [[], 0, 0]
    407    elseif !empty(indtokens)
    408      " We found a non-empty line. If we were moving up, we return the last
    409      " token of this line. Otherwise we return the first token if this line.
    410      let i = (a:dir ==# 'up' ? len(indtokens) - 1 : 0)
    411      return [indtokens[i], lnum, i]
    412    endif
    413  endwhile
    414 endfunction
    415 
    416 " Purpose:
    417 "   Find the token that directly precedes the given token.
    418 " Parameters:
    419 "   lnum: integer -- the line of the given token
    420 "   i: the index of the given token within line `lnum`
    421 " Returns:
    422 "   result = [] -- the result is an empty list if the given token is the first
    423 "                  token of the file
    424 "          | indtoken
    425 function! s:PrevIndToken(lnum, i)
    426  call s:Log('    PrevIndToken called: lnum=' . a:lnum . ', i =' . a:i)
    427 
    428  " If the current line has a previous token, return that
    429  if a:i > 0
    430    return [s:all_tokens[a:lnum][a:i - 1], a:lnum, a:i - 1]
    431  else
    432    return s:FindIndToken(a:lnum, 'up')
    433  endif
    434 endfunction
    435 
    436 " Purpose:
    437 "   Find the token that directly succeeds the given token.
    438 " Parameters:
    439 "   lnum: integer -- the line of the given token
    440 "   i: the index of the given token within line `lnum`
    441 " Returns:
    442 "   result = [] -- the result is an empty list if the given token is the last
    443 "                  token of the file
    444 "          | indtoken
    445 function! s:NextIndToken(lnum, i)
    446  call s:Log('    NextIndToken called: lnum=' . a:lnum . ', i =' . a:i)
    447 
    448  " If the current line has a next token, return that
    449  if len(s:all_tokens[a:lnum]) > a:i + 1
    450    return [s:all_tokens[a:lnum][a:i + 1], a:lnum, a:i + 1]
    451  else
    452    return s:FindIndToken(a:lnum, 'down')
    453  endif
    454 endfunction
    455 
    456 " ErlangCalcIndent helper functions {{{1
    457 " =================================
    458 
    459 " Purpose:
    460 "   This function is called when the parser encounters a syntax error.
    461 "
    462 "   If we encounter a syntax error, we return
    463 "   g:erlang_unexpected_token_indent, which is -1 by default. This means that
    464 "   the indentation of the LTI will not be changed.
    465 " Parameter:
    466 "   msg: string
    467 "   token: string
    468 "   stack: [token]
    469 " Returns:
    470 "   indent: integer
    471 function! s:IndentError(msg, token, stack)
    472  call s:Log('Indent error: ' . a:msg . ' -> return')
    473  call s:Log('  Token = ' . a:token . ', ' .
    474            \'  stack = ' . string(a:stack))
    475  return g:erlang_unexpected_token_indent
    476 endfunction
    477 
    478 " Purpose:
    479 "   This function is called when the parser encounters an unexpected token,
    480 "   and the parser will return the number given back by UnexpectedToken.
    481 "
    482 "   If we encounter an unexpected token, we return
    483 "   g:erlang_unexpected_token_indent, which is -1 by default. This means that
    484 "   the indentation of the LTI will not be changed.
    485 " Parameter:
    486 "   token: string
    487 "   stack: [token]
    488 " Returns:
    489 "   indent: integer
    490 function! s:UnexpectedToken(token, stack)
    491  call s:Log('    Unexpected token ' . a:token . ', stack = ' .
    492            \string(a:stack) . ' -> return')
    493  return g:erlang_unexpected_token_indent
    494 endfunction
    495 
    496 if !exists('g:erlang_unexpected_token_indent')
    497  let g:erlang_unexpected_token_indent = -1
    498 endif
    499 
    500 " Purpose:
    501 "   Return whether the given line starts with a string continuation.
    502 " Parameter:
    503 "   lnum: integer
    504 " Returns:
    505 "   result: bool
    506 " Example:
    507 "   f() ->           % IsLineStringContinuation = false
    508 "       "This is a   % IsLineStringContinuation = false
    509 "       multiline    % IsLineStringContinuation = true
    510 "       string".     % IsLineStringContinuation = true
    511 function! s:IsLineStringContinuation(lnum)
    512  if has('syntax_items')
    513    return synIDattr(synID(a:lnum, 1, 0), 'name') =~# '^erlangString'
    514  else
    515    return 0
    516  endif
    517 endfunction
    518 
    519 " Purpose:
    520 "   Return whether the given line starts with an atom continuation.
    521 " Parameter:
    522 "   lnum: integer
    523 " Returns:
    524 "   result: bool
    525 " Example:
    526 "   'function with   % IsLineAtomContinuation = true, but should be false
    527 "   weird name'() -> % IsLineAtomContinuation = true
    528 "       ok.          % IsLineAtomContinuation = false
    529 function! s:IsLineAtomContinuation(lnum)
    530  if has('syntax_items')
    531    let syn_name = synIDattr(synID(a:lnum, 1, 0), 'name')
    532    return syn_name =~# '^erlangQuotedAtom' ||
    533         \ syn_name =~# '^erlangQuotedRecord'
    534  else
    535    return 0
    536  endif
    537 endfunction
    538 
    539 " Purpose:
    540 "   Return whether the 'catch' token (which should be the `i`th token in line
    541 "   `lnum`) is standalone or part of a try-catch block, based on the preceding
    542 "   token.
    543 " Parameters:
    544 "   lnum: integer
    545 "   i: integer
    546 " Return:
    547 "   is_standalone: bool
    548 function! s:IsCatchStandalone(lnum, i)
    549  call s:Log('    IsCatchStandalone called: lnum=' . a:lnum . ', i=' . a:i)
    550  let [prev_indtoken, _, _] = s:PrevIndToken(a:lnum, a:i)
    551 
    552  " If we hit the beginning of the file, it is not a catch in a try block
    553  if prev_indtoken == []
    554    return 1
    555  endif
    556 
    557  let prev_token = prev_indtoken[0]
    558 
    559  if prev_token =~# '^[A-Z_@0-9]'
    560    let is_standalone = 0
    561  elseif prev_token =~# '[a-z]'
    562    if index(['after', 'and', 'andalso', 'band', 'begin', 'bnot', 'bor', 'bsl',
    563            \ 'bsr', 'bxor', 'case', 'catch', 'div', 'maybe', 'not', 'or',
    564            \ 'orelse', 'rem', 'try', 'xor'], prev_token) != -1
    565      " If catch is after these keywords, it is standalone
    566      let is_standalone = 1
    567    else
    568      " If catch is after another keyword (e.g. 'end') or an atom, it is
    569      " part of try-catch.
    570      "
    571      " Keywords:
    572      " - may precede 'catch': end
    573      " - may not precede 'catch': else fun if of receive when
    574      " - unused: cond let query
    575      let is_standalone = 0
    576    endif
    577  elseif index([')', ']', '}', '<string>', '<string_end>', '<quoted_atom>',
    578              \ '<quoted_atom_end>', '$.'], prev_token) != -1
    579    let is_standalone = 0
    580  else
    581    " This 'else' branch includes the following tokens:
    582    "   -> == /= =< < >= > ?= =:= =/= + - * / ++ -- :: < > ; ( [ { ? = ! . |
    583    let is_standalone = 1
    584  endif
    585 
    586  call s:Log('   "catch" preceded by "' . prev_token  . '" -> catch ' .
    587            \(is_standalone ? 'is standalone' : 'belongs to try-catch'))
    588  return is_standalone
    589 
    590 endfunction
    591 
    592 " Purpose:
    593 "   This function is called when a begin-type element ('begin', 'case',
    594 "   '[', '<<', etc.) is found. It asks the caller to return if the stack
    595 "   if already empty.
    596 " Parameters:
    597 "   stack: [token]
    598 "   token: string
    599 "   curr_vcol: integer
    600 "   stored_vcol: integer
    601 "   sw: integer -- number of spaces to be used after the begin element as
    602 "                  indentation
    603 " Returns:
    604 "   result: [should_return, indent]
    605 "   should_return: bool -- if true, the caller should return `indent` to Vim
    606 "   indent -- integer
    607 function! s:BeginElementFoundIfEmpty(stack, token, curr_vcol, stored_vcol, sw)
    608  if empty(a:stack)
    609    if a:stored_vcol ==# -1
    610      call s:Log('    "' . a:token . '" directly precedes LTI -> return')
    611      return [1, a:curr_vcol + a:sw]
    612    else
    613      call s:Log('    "' . a:token .
    614                \'" token (whose expression includes LTI) found -> return')
    615      return [1, a:stored_vcol]
    616    endif
    617  else
    618    return [0, 0]
    619  endif
    620 endfunction
    621 
    622 " Purpose:
    623 "   This function is called when a begin-type element ('begin', 'case', '[',
    624 "   '<<', etc.) is found, and in some cases when 'after' and 'when' is found.
    625 "   It asks the caller to return if the stack is already empty.
    626 " Parameters:
    627 "   stack: [token]
    628 "   token: string
    629 "   curr_vcol: integer
    630 "   stored_vcol: integer
    631 "   end_token: end token that belongs to the begin element found (e.g. if the
    632 "              begin element is 'begin', the end token is 'end')
    633 "   sw: integer -- number of spaces to be used after the begin element as
    634 "                  indentation
    635 " Returns:
    636 "   result: [should_return, indent]
    637 "   should_return: bool -- if true, the caller should return `indent` to Vim
    638 "   indent -- integer
    639 function! s:BeginElementFound(stack, token, curr_vcol, stored_vcol, end_token, sw)
    640 
    641  " Return 'return' if the stack is empty
    642  let [ret, res] = s:BeginElementFoundIfEmpty(a:stack, a:token, a:curr_vcol,
    643                                             \a:stored_vcol, a:sw)
    644  if ret | return [ret, res] | endif
    645 
    646  if a:stack[0] ==# a:end_token
    647    call s:Log('    "' . a:token . '" pops "' . a:end_token . '"')
    648    call s:Pop(a:stack)
    649    if !empty(a:stack) && a:stack[0] ==# 'align_to_begin_element'
    650      call s:Pop(a:stack)
    651      if empty(a:stack)
    652        return [1, a:curr_vcol]
    653      else
    654        return [1, s:UnexpectedToken(a:token, a:stack)]
    655      endif
    656    else
    657      return [0, 0]
    658    endif
    659  else
    660    return [1, s:UnexpectedToken(a:token, a:stack)]
    661  endif
    662 endfunction
    663 
    664 " Purpose:
    665 "   This function is called when we hit the beginning of a file or an
    666 "   end-of-clause token -- i.e. when we found the beginning of the current
    667 "   clause.
    668 "
    669 "   If the stack contains an '->' or 'when', this means that we can return
    670 "   now, since we were looking for the beginning of the clause.
    671 " Parameters:
    672 "   stack: [token]
    673 "   token: string
    674 "   stored_vcol: integer
    675 "   lnum: the line number of the "end of clause" mark (or 0 if we hit the
    676 "         beginning of the file)
    677 "   i: the index of the "end of clause" token within its own line
    678 " Returns:
    679 "   result: [should_return, indent]
    680 "   should_return: bool -- if true, the caller should return `indent` to Vim
    681 "   indent -- integer
    682 function! s:BeginningOfClauseFound(stack, token, stored_vcol, lnum, i)
    683  if !empty(a:stack) && a:stack[0] ==# 'when'
    684    call s:Log('    BeginningOfClauseFound: "when" found in stack')
    685    call s:Pop(a:stack)
    686    if empty(a:stack)
    687      call s:Log('    Stack is ["when"], so LTI is in a guard -> return')
    688      return [1, a:stored_vcol + shiftwidth() + 2]
    689    else
    690      return [1, s:UnexpectedToken(a:token, a:stack)]
    691    endif
    692  elseif !empty(a:stack) && a:stack[0] ==# '->'
    693    call s:Log('    BeginningOfClauseFound: "->" found in stack')
    694    call s:Pop(a:stack)
    695    if empty(a:stack)
    696      call s:Log('    Stack is ["->"], so LTI is in function body -> return')
    697      return [1, a:stored_vcol + shiftwidth()]
    698    elseif a:stack[0] ==# ';'
    699      call s:Pop(a:stack)
    700 
    701      if !empty(a:stack)
    702        return [1, s:UnexpectedToken(a:token, a:stack)]
    703      endif
    704 
    705      if a:lnum ==# 0
    706        " Set lnum and i to be NextIndToken-friendly
    707        let lnum = 1
    708        let i = -1 
    709      else
    710        let lnum = a:lnum
    711        let i = a:i
    712      endif
    713 
    714      " Are we after a "-spec func() ...;" clause?
    715      let [next1_indtoken, next1_lnum, next1_i] = s:NextIndToken(lnum, i)
    716      if !empty(next1_indtoken) && next1_indtoken[0] =~# '-'
    717        let [next2_indtoken, next2_lnum, next2_i] =
    718           \s:NextIndToken(next1_lnum, next1_i)
    719        if !empty(next2_indtoken) && next2_indtoken[0] =~# 'spec'
    720          let [next3_indtoken, next3_lnum, next3_i] =
    721             \s:NextIndToken(next2_lnum, next2_i)
    722          if !empty(next3_indtoken)
    723            let [next4_indtoken, next4_lnum, next4_i] =
    724               \s:NextIndToken(next3_lnum, next3_i)
    725            if !empty(next4_indtoken)
    726              " Yes, we are.
    727              call s:Log('    Stack is ["->", ";"], so LTI is in a "-spec" ' .
    728                        \'attribute -> return')
    729              return [1, next4_indtoken[1]]
    730            endif
    731          endif
    732        endif
    733      endif
    734 
    735      call s:Log('    Stack is ["->", ";"], so LTI is in a function head ' .
    736                \'-> return')
    737      return [1, a:stored_vcol]
    738 
    739    else
    740      return [1, s:UnexpectedToken(a:token, a:stack)]
    741    endif
    742  else
    743    return [0, 0]
    744  endif
    745 endfunction
    746 
    747 let g:erlang_indent_searchpair_timeout = 2000
    748 
    749 " TODO
    750 function! s:SearchPair(lnum, curr_col, start, middle, end)
    751  call cursor(a:lnum, a:curr_col + 1)
    752  let [lnum_new, col1_new] = 
    753      \searchpairpos(a:start, a:middle, a:end, 'bW',
    754                    \'synIDattr(synID(line("."), col("."), 0), "name") ' .
    755                    \'=~? "string\\|quotedatom\\|todo\\|comment\\|' . 
    756                    \'erlangmodifier"',
    757                    \0, g:erlang_indent_searchpair_timeout)
    758  return [lnum_new, col1_new - 1]
    759 endfunction
    760 
    761 function! s:SearchEndPair(lnum, curr_col)
    762  return s:SearchPair(
    763         \ a:lnum, a:curr_col,
    764         \ '\C\<\%(case\|try\|begin\|receive\|if\|maybe\)\>\|' .
    765         \ '\<fun\>\%(\s\|\n\|%.*$\|[A-Z_@][a-zA-Z_@]*\)*(',
    766         \ '',
    767         \ '\<end\>')
    768 endfunction
    769 
    770 " ErlangCalcIndent {{{1
    771 " ================
    772 
    773 " Purpose:
    774 "   Calculate the indentation of the given line.
    775 " Parameters:
    776 "   lnum: integer -- index of the line for which the indentation should be
    777 "                    calculated
    778 "   stack: [token] -- initial stack
    779 " Return:
    780 "   indent: integer -- if -1, that means "don't change the indentation";
    781 "                      otherwise it means "indent the line with `indent`
    782 "                      number of spaces or equivalent tabs"
    783 function! s:ErlangCalcIndent(lnum, stack)
    784  let res = s:ErlangCalcIndent2(a:lnum, a:stack)
    785  call s:Log("ErlangCalcIndent returned: " . res)
    786  return res
    787 endfunction
    788 
    789 function! s:ErlangCalcIndent2(lnum, stack)
    790 
    791  let lnum = a:lnum
    792  let stored_vcol = -1 " Virtual column of the first character of the token that
    793                   " we currently think we might align to.
    794  let mode = 'normal'
    795  let stack = a:stack
    796  let semicolon_abscol = ''
    797 
    798  " Walk through the lines of the buffer backwards (starting from the
    799  " previous line) until we can decide how to indent the current line.
    800  while 1
    801 
    802    let [lnum, indtokens] = s:TokenizeLine(lnum, 'up')
    803 
    804    " Hit the start of the file
    805    if lnum ==# 0
    806      let [ret, res] = s:BeginningOfClauseFound(stack, 'beginning_of_file',
    807                                               \stored_vcol, 0, 0)
    808      if ret | return res | endif
    809 
    810      return 0
    811    endif
    812 
    813    let i = len(indtokens) - 1
    814    let last_token_of_line = 1
    815 
    816    while i >= 0
    817 
    818      let [token, curr_vcol, curr_col] = indtokens[i]
    819      call s:Log('  Analyzing the following token: ' . string(indtokens[i]))
    820 
    821      if len(stack) > 256 " TODO: magic number
    822        return s:IndentError('Stack too long', token, stack)
    823      endif
    824 
    825      if token ==# '<end_of_clause>'
    826        let [ret, res] = s:BeginningOfClauseFound(stack, token, stored_vcol,
    827                                                 \lnum, i)
    828        if ret | return res | endif
    829 
    830        if stored_vcol ==# -1
    831          call s:Log('    End of clause directly precedes LTI -> return')
    832          return 0
    833        else
    834          call s:Log('    End of clause (but not end of line) -> return')
    835          return stored_vcol
    836        endif
    837 
    838      elseif stack == ['prev_term_plus']
    839        if token =~# '[a-zA-Z_@#]' ||
    840         \ token ==# '<string>' || token ==# '<string_start>' ||
    841         \ token ==# '<quoted_atom>' || token ==# '<quoted_atom_start>'
    842          call s:Log('    previous token found: curr_vcol + plus = ' .
    843                    \curr_vcol . " + " . plus)
    844          return curr_vcol + plus
    845        endif
    846 
    847      elseif token ==# 'begin'
    848        let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
    849                                            \stored_vcol, 'end', shiftwidth())
    850        if ret | return res | endif
    851 
    852      " case EXPR of BRANCHES end
    853      " if BRANCHES end
    854      " try EXPR catch BRANCHES end
    855      " try EXPR after BODY end
    856      " try EXPR catch BRANCHES after BODY end
    857      " try EXPR of BRANCHES catch BRANCHES end
    858      " try EXPR of BRANCHES after BODY end
    859      " try EXPR of BRANCHES catch BRANCHES after BODY end
    860      " receive BRANCHES end
    861      " receive BRANCHES after BRANCHES end
    862      " maybe EXPR end
    863      " maybe EXPR else BRANCHES end
    864 
    865      " This branch is not Emacs-compatible
    866      elseif (index(['of', 'receive', 'after', 'if', 'else'], token) != -1 ||
    867           \  (token ==# 'catch' && !s:IsCatchStandalone(lnum, i))) &&
    868           \ !last_token_of_line &&
    869           \ (empty(stack) || stack ==# ['when'] || stack ==# ['->'] ||
    870           \  stack ==# ['->', ';'])
    871 
    872        " If we are after of/receive/etc, but these are not the last
    873        " tokens of the line, we want to indent like this:
    874        "
    875        "   % stack == []
    876        "   receive stored_vcol,
    877        "           LTI
    878        "
    879        "   % stack == ['->', ';']
    880        "   receive stored_vcol ->
    881        "               B;
    882        "           LTI
    883        "
    884        "   % stack == ['->']
    885        "   receive stored_vcol ->
    886        "               LTI
    887        "
    888        "   % stack == ['when']
    889        "   receive stored_vcol when
    890        "               LTI
    891 
    892        " stack = []  =>  LTI is a condition
    893        " stack = ['->']  =>  LTI is a branch
    894        " stack = ['->', ';']  =>  LTI is a condition
    895        " stack = ['when']  =>  LTI is a guard
    896        if empty(stack) || stack == ['->', ';']
    897          call s:Log('    LTI is in a condition after ' .
    898                    \'"of/receive/after/if/else/catch" -> return')
    899          return stored_vcol
    900        elseif stack == ['->']
    901          call s:Log('    LTI is in a branch after ' .
    902                    \'"of/receive/after/if/else/catch" -> return')
    903          return stored_vcol + shiftwidth()
    904        elseif stack == ['when']
    905          call s:Log('    LTI is in a guard after ' .
    906                    \'"of/receive/after/if/else/catch" -> return')
    907          return stored_vcol + shiftwidth()
    908        else
    909          return s:UnexpectedToken(token, stack)
    910        endif
    911 
    912      elseif index(['case', 'if', 'try', 'receive', 'maybe'], token) != -1
    913 
    914        " stack = []  =>  LTI is a condition
    915        " stack = ['->']  =>  LTI is a branch
    916        " stack = ['->', ';']  =>  LTI is a condition
    917        " stack = ['when']  =>  LTI is in a guard
    918        if empty(stack)
    919          " pass
    920        elseif (token ==# 'case' && stack[0] ==# 'of') ||
    921             \ (token ==# 'if') ||
    922             \ (token ==# 'maybe' && stack[0] ==# 'else') ||
    923             \ (token ==# 'try' && (stack[0] ==# 'of' ||
    924             \                     stack[0] ==# 'catch' ||
    925             \                     stack[0] ==# 'after')) ||
    926             \ (token ==# 'receive')
    927 
    928          " From the indentation point of view, the keyword
    929          " (of/catch/after/else/end) before the LTI is what counts, so
    930          " when we reached these tokens, and the stack already had
    931          " a catch/after/else/end, we didn't modify it.
    932          "
    933          " This way when we reach case/try/receive/maybe (i.e. now),
    934          " there is at most one of/catch/after/else/end token in the
    935          " stack.
    936          if token ==# 'case' || token ==# 'try' ||
    937           \ (token ==# 'receive' && stack[0] ==# 'after') ||
    938           \ (token ==# 'maybe' && stack[0] ==# 'else')
    939            call s:Pop(stack)
    940          endif
    941 
    942          if empty(stack)
    943            call s:Log('    LTI is in a condition; matching ' .
    944                      \'"case/if/try/receive/maybe" found')
    945            let stored_vcol = curr_vcol + shiftwidth()
    946          elseif stack[0] ==# 'align_to_begin_element'
    947            call s:Pop(stack)
    948            let stored_vcol = curr_vcol
    949          elseif len(stack) > 1 && stack[0] ==# '->' && stack[1] ==# ';'
    950            call s:Log('    LTI is in a condition; matching ' .
    951                      \'"case/if/try/receive/maybe" found')
    952            call s:Pop(stack)
    953            call s:Pop(stack)
    954            let stored_vcol = curr_vcol + shiftwidth()
    955          elseif stack[0] ==# '->'
    956            call s:Log('    LTI is in a branch; matching ' .
    957                      \'"case/if/try/receive/maybe" found')
    958            call s:Pop(stack)
    959            let stored_vcol = curr_vcol + 2 * shiftwidth()
    960          elseif stack[0] ==# 'when'
    961            call s:Log('    LTI is in a guard; matching ' .
    962                      \'"case/if/try/receive/maybe" found')
    963            call s:Pop(stack)
    964            let stored_vcol = curr_vcol + 2 * shiftwidth() + 2
    965          endif
    966 
    967        endif
    968 
    969        let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
    970                                            \stored_vcol, 'end', shiftwidth())
    971        if ret | return res | endif
    972 
    973      elseif token ==# 'fun'
    974        let [next_indtoken, next_lnum, next_i] = s:NextIndToken(lnum, i)
    975        call s:Log('    Next indtoken = ' . string(next_indtoken))
    976 
    977        if !empty(next_indtoken) && next_indtoken[0] =~# '^[A-Z_@]'
    978          " The "fun" is followed by a variable, so we might have a named fun:
    979          " "fun Fun() -> ok end". Thus we take the next token to decide
    980          " whether this is a function definition ("fun()") or just a function
    981          " reference ("fun Mod:Fun").
    982          let [next_indtoken, _, _] = s:NextIndToken(next_lnum, next_i)
    983          call s:Log('    Next indtoken = ' . string(next_indtoken))
    984        endif
    985 
    986        if !empty(next_indtoken) && next_indtoken[0] ==# '('
    987          " We have an anonymous function definition
    988          " (e.g. "fun () -> ok end")
    989 
    990          " stack = []  =>  LTI is a condition
    991          " stack = ['->']  =>  LTI is a branch
    992          " stack = ['->', ';']  =>  LTI is a condition
    993          " stack = ['when']  =>  LTI is in a guard
    994          if empty(stack)
    995            call s:Log('    LTI is in a condition; matching "fun" found')
    996            let stored_vcol = curr_vcol + shiftwidth()
    997          elseif len(stack) > 1 && stack[0] ==# '->' && stack[1] ==# ';'
    998            call s:Log('    LTI is in a condition; matching "fun" found')
    999            call s:Pop(stack)
   1000            call s:Pop(stack)
   1001          elseif stack[0] ==# '->'
   1002            call s:Log('    LTI is in a branch; matching "fun" found')
   1003            call s:Pop(stack)
   1004            let stored_vcol = curr_vcol + 2 * shiftwidth()
   1005          elseif stack[0] ==# 'when'
   1006            call s:Log('    LTI is in a guard; matching "fun" found')
   1007            call s:Pop(stack)
   1008            let stored_vcol = curr_vcol + 2 * shiftwidth() + 2
   1009          endif
   1010 
   1011          let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
   1012                                              \stored_vcol, 'end', shiftwidth())
   1013          if ret | return res | endif
   1014        else
   1015          " Pass: we have a function reference (e.g. "fun f/0")
   1016        endif
   1017 
   1018      elseif token ==# '['
   1019        " Emacs compatibility
   1020        let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
   1021                                            \stored_vcol, ']', 1)
   1022        if ret | return res | endif
   1023 
   1024      elseif token ==# '<<'
   1025        " Emacs compatibility
   1026        let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
   1027                                            \stored_vcol, '>>', 2)
   1028        if ret | return res | endif
   1029 
   1030      elseif token ==# '(' || token ==# '{'
   1031 
   1032        let end_token = (token ==# '(' ? ')' :
   1033                        \token ==# '{' ? '}' : 'error')
   1034 
   1035        if empty(stack)
   1036          " We found the opening paren whose block contains the LTI.
   1037          let mode = 'inside'
   1038        elseif stack[0] ==# end_token
   1039          call s:Log('    "' . token . '" pops "' . end_token . '"')
   1040          call s:Pop(stack)
   1041 
   1042          if !empty(stack) && stack[0] ==# 'align_to_begin_element'
   1043            " We found the opening paren whose closing paren
   1044            " starts LTI
   1045            let mode = 'align_to_begin_element'
   1046          else
   1047            " We found the opening pair for a closing paren that
   1048            " was already in the stack.
   1049            let mode = 'outside'
   1050          endif
   1051        else
   1052          return s:UnexpectedToken(token, stack)
   1053        endif
   1054 
   1055        if mode ==# 'inside' || mode ==# 'align_to_begin_element'
   1056 
   1057          if last_token_of_line && i != 0
   1058            " Examples: {{{
   1059            "
   1060            " mode == 'inside':
   1061            "
   1062            "     my_func(
   1063            "       LTI
   1064            "
   1065            "     [Variable, {
   1066            "        LTI
   1067            "
   1068            " mode == 'align_to_begin_element':
   1069            "
   1070            "     my_func(
   1071            "       Params
   1072            "      ) % LTI
   1073            "
   1074            "     [Variable, {
   1075            "        Terms
   1076            "       } % LTI
   1077            " }}}
   1078            let stack = ['prev_term_plus']
   1079            let plus = (mode ==# 'inside' ? 2 : 1)
   1080            call s:Log('    "' . token .
   1081                      \'" token found at end of line -> find previous token')
   1082          elseif mode ==# 'align_to_begin_element'
   1083            " Examples: {{{
   1084            "
   1085            " mode == 'align_to_begin_element' && !last_token_of_line
   1086            "
   1087            "     my_func(stored_vcol
   1088            "            ) % LTI
   1089            "
   1090            "     [Variable, {stored_vcol
   1091            "                } % LTI
   1092            "
   1093            " mode == 'align_to_begin_element' && i == 0
   1094            "
   1095            "     (
   1096            "       stored_vcol
   1097            "     ) % LTI
   1098            "
   1099            "     {
   1100            "       stored_vcol
   1101            "     } % LTI
   1102            " }}}
   1103            call s:Log('    "' . token . '" token (whose closing token ' .
   1104                      \'starts LTI) found -> return')
   1105            return curr_vcol
   1106          elseif stored_vcol ==# -1
   1107            " Examples: {{{
   1108            "
   1109            " mode == 'inside' && stored_vcol == -1 && !last_token_of_line
   1110            "
   1111            "     my_func(
   1112            "             LTI
   1113            "     [Variable, {
   1114            "                 LTI
   1115            "
   1116            " mode == 'inside' && stored_vcol == -1 && i == 0
   1117            "
   1118            "     (
   1119            "      LTI
   1120            "
   1121            "     {
   1122            "      LTI
   1123            " }}}
   1124            call s:Log('    "' . token .
   1125                      \'" token (which directly precedes LTI) found -> return')
   1126            return curr_vcol + 1
   1127          else
   1128            " Examples: {{{
   1129            "
   1130            " mode == 'inside' && stored_vcol != -1 && !last_token_of_line
   1131            "
   1132            "     my_func(stored_vcol,
   1133            "             LTI
   1134            "
   1135            "     [Variable, {stored_vcol,
   1136            "                 LTI
   1137            "
   1138            " mode == 'inside' && stored_vcol != -1 && i == 0
   1139            "
   1140            "     (stored_vcol,
   1141            "      LTI
   1142            "
   1143            "     {stored_vcol,
   1144            "      LTI
   1145            " }}}
   1146            call s:Log('    "' . token .
   1147                      \'" token (whose block contains LTI) found -> return')
   1148            return stored_vcol
   1149          endif
   1150        endif
   1151 
   1152      elseif index(['end', ')', ']', '}', '>>'], token) != -1
   1153 
   1154        " If we can be sure that there is synchronization in the Erlang
   1155        " syntax, we use searchpair to make the script quicker. Otherwise we
   1156        " just push the token onto the stack and keep parsing.
   1157    
   1158        " No synchronization -> no searchpair optimization
   1159        if !exists('b:erlang_syntax_synced')
   1160          call s:Push(stack, token)
   1161 
   1162        " We don't have searchpair optimization for '>>'
   1163        elseif token ==# '>>'
   1164          call s:Push(stack, token)
   1165 
   1166        elseif token ==# 'end'
   1167          let [lnum_new, col_new] = s:SearchEndPair(lnum, curr_col)
   1168 
   1169          if lnum_new ==# 0
   1170            return s:IndentError('Matching token for "end" not found',
   1171                                \token, stack)
   1172          else
   1173            if lnum_new != lnum
   1174              call s:Log('    Tokenize for "end" <<<<')
   1175              let [lnum, indtokens] = s:TokenizeLine(lnum_new, 'up')
   1176              call s:Log('    >>>> Tokenize for "end"')
   1177            endif
   1178 
   1179            let [success, i] = s:GetIndtokenAtCol(indtokens, col_new)
   1180            if !success | return i | endif
   1181            let [token, curr_vcol, curr_col] = indtokens[i]
   1182            call s:Log('    Match for "end" in line ' . lnum_new . ': ' .
   1183                      \string(indtokens[i]))
   1184          endif
   1185 
   1186        else " token is one of the following: ')', ']', '}'
   1187 
   1188          call s:Push(stack, token)
   1189 
   1190          " We have to escape '[', because this string will be interpreted as a
   1191          " regexp
   1192          let open_paren = (token ==# ')' ? '(' :
   1193                           \token ==# ']' ? '\[' :
   1194                           \               '{')
   1195 
   1196          let [lnum_new, col_new] = s:SearchPair(lnum, curr_col,
   1197                                                \open_paren, '', token)
   1198 
   1199          if lnum_new ==# 0
   1200            return s:IndentError('Matching token not found',
   1201                                \token, stack)
   1202          else
   1203            if lnum_new != lnum
   1204              call s:Log('    Tokenize the opening paren <<<<')
   1205              let [lnum, indtokens] = s:TokenizeLine(lnum_new, 'up')
   1206              call s:Log('    >>>>')
   1207            endif
   1208 
   1209            let [success, i] = s:GetIndtokenAtCol(indtokens, col_new)
   1210            if !success | return i | endif
   1211            let [token, curr_vcol, curr_col] = indtokens[i]
   1212            call s:Log('    Match in line ' . lnum_new . ': ' .
   1213                      \string(indtokens[i]))
   1214 
   1215            " Go back to the beginning of the loop and handle the opening paren
   1216            continue
   1217          endif
   1218        endif
   1219 
   1220      elseif token ==# ';'
   1221 
   1222        if empty(stack)
   1223          call s:Push(stack, ';')
   1224        elseif index([';', '->', 'when', 'end', 'after', 'catch', 'else'],
   1225                    \stack[0]) != -1
   1226          " Pass:
   1227          "
   1228          " - If the stack top is another ';', then one ';' is
   1229          "   enough.
   1230          " - If the stack top is an '->' or a 'when', then we
   1231          "   should keep that, because they signify the type of the
   1232          "   LTI (branch, condition or guard).
   1233          " - From the indentation point of view, the keyword
   1234          "   (of/catch/after/else/end) before the LTI is what counts, so
   1235          "   if the stack already has a catch/after/else/end, we don't
   1236          "   modify it. This way when we reach case/try/receive/maybe,
   1237          "   there will be at most one of/catch/after/else/end token in
   1238          "   the stack.
   1239        else
   1240          return s:UnexpectedToken(token, stack)
   1241        endif
   1242 
   1243      elseif token ==# '->'
   1244 
   1245        if empty(stack) && !last_token_of_line
   1246          call s:Log('    LTI is in expression after arrow -> return')
   1247          return stored_vcol
   1248        elseif empty(stack) || stack[0] ==# ';' || stack[0] ==# 'end'
   1249          " stack = [';']  -> LTI is either a branch or in a guard
   1250          " stack = ['->']  ->  LTI is a condition
   1251          " stack = ['->', ';']  -> LTI is a branch
   1252          call s:Push(stack, '->')
   1253        elseif index(['->', 'when', 'end', 'after', 'catch', 'else'],
   1254                    \stack[0]) != -1
   1255          " Pass:
   1256          "
   1257          " - If the stack top is another '->', then one '->' is
   1258          "   enough.
   1259          " - If the stack top is a 'when', then we should keep
   1260          "   that, because this signifies that LTI is a in a guard.
   1261          " - From the indentation point of view, the keyword
   1262          "   (of/catch/after/else/end) before the LTI is what counts, so
   1263          "   if the stack already has a catch/after/else/end, we don't
   1264          "   modify it. This way when we reach case/try/receive/maybe,
   1265          "   there will be at most one of/catch/after/else/end token in
   1266          "   the stack.
   1267        else
   1268          return s:UnexpectedToken(token, stack)
   1269        endif
   1270 
   1271      elseif token ==# 'when'
   1272 
   1273        " Pop all ';' from the top of the stack
   1274        while !empty(stack) && stack[0] ==# ';'
   1275          call s:Pop(stack)
   1276        endwhile
   1277 
   1278        if empty(stack)
   1279          if semicolon_abscol != ''
   1280            let stored_vcol = semicolon_abscol
   1281          endif
   1282          if !last_token_of_line
   1283            " Example:
   1284            "   when A,
   1285            "        LTI
   1286            let [ret, res] = s:BeginElementFoundIfEmpty(stack, token, curr_vcol,
   1287                                                       \stored_vcol, shiftwidth())
   1288            if ret | return res | endif
   1289          else
   1290            " Example:
   1291            "   when
   1292            "       LTI
   1293            call s:Push(stack, token)
   1294          endif
   1295        elseif index(['->', 'when', 'end', 'after', 'catch', 'else'],
   1296                    \stack[0]) != -1
   1297          " Pass:
   1298          " - If the stack top is another 'when', then one 'when' is
   1299          "   enough.
   1300          " - If the stack top is an '->' or a 'when', then we
   1301          "   should keep that, because they signify the type of the
   1302          "   LTI (branch, condition or guard).
   1303          " - From the indentation point of view, the keyword
   1304          "   (of/catch/after/else/end) before the LTI is what counts, so
   1305          "   if the stack already has a catch/after/else/end, we don't
   1306          "   modify it. This way when we reach case/try/receive/maybe,
   1307          "   there will be at most one of/catch/after/else/end token in
   1308          "   the stack.
   1309        else
   1310          return s:UnexpectedToken(token, stack)
   1311        endif
   1312 
   1313      elseif token ==# 'of' || token ==# 'after' || token ==# 'else' ||
   1314           \ (token ==# 'catch' && !s:IsCatchStandalone(lnum, i))
   1315 
   1316        if token ==# 'after' || token ==# 'else'
   1317          " If LTI is between an after/else and the corresponding 'end', then
   1318          " let's return because calculating the indentation based on
   1319          " after/else is enough.
   1320          "
   1321          " Example:
   1322          "   receive A after
   1323          "                 LTI
   1324          "   maybe A else
   1325          "               LTI
   1326          "
   1327          " Note about Emacs compatibility {{{
   1328          "
   1329          " It would be fine to indent the examples above the following way:
   1330          "
   1331          "   receive A after
   1332          "       LTI
   1333          "   maybe A else
   1334          "       LTI
   1335          "
   1336          " We intend it the way above because that is how Emacs does it.
   1337          " Also, this is a bit faster.
   1338          "
   1339          " We are still not 100% Emacs compatible because of placing the
   1340          " 'end' after the indented blocks.
   1341          "
   1342          " Emacs example:
   1343          "
   1344          "   receive A after
   1345          "                 LTI
   1346          "             end,
   1347          "   maybe A else
   1348          "               LTI
   1349          "               end % Yes, it's here (in OTP 25.0, might change
   1350          "                   % later)
   1351          "
   1352          " vim-erlang example:
   1353          "
   1354          "   receive A after
   1355          "                 LTI
   1356          "   end,
   1357          "   maybe A else
   1358          "               LTI
   1359          "   end
   1360          " }}}
   1361          let [ret, res] = s:BeginElementFoundIfEmpty(stack, token, curr_vcol,
   1362                                                     \stored_vcol, shiftwidth())
   1363          if ret | return res | endif
   1364        endif
   1365 
   1366        if empty(stack) || stack[0] ==# '->' || stack[0] ==# 'when'
   1367          call s:Push(stack, token)
   1368        elseif stack[0] ==# 'catch' || stack[0] ==# 'after' ||
   1369              \stack[0] ==# 'else' || stack[0] ==# 'end'
   1370          " Pass: From the indentation point of view, the keyword
   1371          " (of/catch/after/end) before the LTI is what counts, so
   1372          " if the stack already has a catch/after/end, we don't
   1373          " modify it. This way when we reach case/try/receive,
   1374          " there will be at most one of/catch/after/end token in
   1375          " the stack.
   1376        else
   1377          return s:UnexpectedToken(token, stack)
   1378        endif
   1379 
   1380      elseif token ==# '||' && empty(stack) && !last_token_of_line
   1381 
   1382        call s:Log('    LTI is in expression after "||" -> return')
   1383        return stored_vcol
   1384 
   1385      else
   1386        call s:Log('    Misc token, stack unchanged = ' . string(stack))
   1387 
   1388      endif
   1389 
   1390      if empty(stack) || stack[0] ==# '->' || stack[0] ==# 'when'
   1391        let stored_vcol = curr_vcol
   1392        let semicolon_abscol = ''
   1393        call s:Log('    Misc token when the stack is empty or has "->" ' .
   1394                  \'-> setting stored_vcol to ' . stored_vcol)
   1395      elseif stack[0] ==# ';'
   1396        let semicolon_abscol = curr_vcol
   1397        call s:Log('    Setting semicolon-stored_vcol to ' . stored_vcol)
   1398      endif
   1399 
   1400      let i -= 1
   1401      call s:Log('    Token processed. stored_vcol=' . stored_vcol)
   1402 
   1403      let last_token_of_line = 0
   1404 
   1405    endwhile " iteration on tokens in a line
   1406 
   1407    call s:Log('  Line analyzed. stored_vcol=' . stored_vcol)
   1408 
   1409    if empty(stack) && stored_vcol != -1 &&
   1410     \ (!empty(indtokens) && indtokens[0][0] != '<string_end>' &&
   1411     \                       indtokens[0][0] != '<quoted_atom_end>')
   1412      call s:Log('    Empty stack at the beginning of the line -> return')
   1413      return stored_vcol
   1414    endif
   1415 
   1416    let lnum -= 1
   1417 
   1418  endwhile " iteration on lines
   1419 
   1420 endfunction
   1421 
   1422 " ErlangIndent function {{{1
   1423 " =====================
   1424 
   1425 function! ErlangIndent()
   1426 
   1427  call s:ClearTokenCacheIfNeeded()
   1428 
   1429  let currline = getline(v:lnum)
   1430  call s:Log('Indenting line ' . v:lnum . ': ' . currline)
   1431 
   1432  if s:IsLineStringContinuation(v:lnum) || s:IsLineAtomContinuation(v:lnum)
   1433    call s:Log('String or atom continuation found -> ' .
   1434              \'leaving indentation unchanged')
   1435    return -1
   1436  endif
   1437 
   1438  " If the line starts with the comment, and so is the previous non-blank line
   1439  if currline =~# '^\s*%'
   1440    let lnum = prevnonblank(v:lnum - 1)
   1441    if lnum ==# 0
   1442      call s:Log('First non-empty line of the file -> return 0.')
   1443      return 0
   1444    else
   1445      let ml = matchlist(getline(lnum), '^\(\s*\)%')
   1446      " If the previous line also starts with a comment, then return the same
   1447      " indentation that line has. Otherwise exit from this special "if" and
   1448      " don't care that the current line is a comment.
   1449      if !empty(ml)
   1450        let new_col = s:CalcVCol(ml[1], 0, len(ml[1]) - 1, 0, &tabstop)
   1451        call s:Log('Comment line after another comment line -> ' .
   1452                  \'use same indent: ' . new_col)
   1453        return new_col
   1454      endif
   1455    endif
   1456  endif
   1457 
   1458  let ml = matchlist(currline,
   1459                    \'^\(\s*\)\(\%(end\|of\|catch\|after\|else\)\>\|[)\]}]\|>>\)')
   1460 
   1461  " If the line has a special beginning, but not a standalone catch
   1462  if !empty(ml) && !(ml[2] ==# 'catch' && s:IsCatchStandalone(v:lnum, 0))
   1463 
   1464    let curr_col = len(ml[1])
   1465 
   1466    " If we can be sure that there is synchronization in the Erlang
   1467    " syntax, we use searchpair to make the script quicker.
   1468    if ml[2] ==# 'end' && exists('b:erlang_syntax_synced')
   1469 
   1470      let [lnum, col] = s:SearchEndPair(v:lnum, curr_col)
   1471 
   1472      if lnum ==# 0
   1473        return s:IndentError('Matching token for "end" not found',
   1474                            \'end', [])
   1475      else
   1476        call s:Log('    Tokenize for "end" <<<<')
   1477        let [lnum, indtokens] = s:TokenizeLine(lnum, 'up')
   1478        call s:Log('    >>>> Tokenize for "end"')
   1479 
   1480        let [success, i] = s:GetIndtokenAtCol(indtokens, col)
   1481        if !success | return i | endif
   1482        let [token, curr_vcol, curr_col] = indtokens[i]
   1483        call s:Log('    Match for "end" in line ' . lnum . ': ' .
   1484                   \string(indtokens[i]))
   1485        return curr_vcol
   1486      endif
   1487 
   1488    else
   1489 
   1490      call s:Log("  Line type = 'end'")
   1491      let new_col = s:ErlangCalcIndent(v:lnum - 1,
   1492                                      \[ml[2], 'align_to_begin_element'])
   1493    endif
   1494  else
   1495    call s:Log("  Line type = 'normal'")
   1496 
   1497    let new_col = s:ErlangCalcIndent(v:lnum - 1, [])
   1498    if currline =~# '^\s*when\>'
   1499      let new_col += 2
   1500    endif
   1501  endif
   1502 
   1503  if new_col < -1
   1504    call s:Log('WARNING: returning new_col == ' . new_col)
   1505    return g:erlang_unexpected_token_indent
   1506  endif
   1507 
   1508  return new_col
   1509 
   1510 endfunction
   1511 
   1512 " ErlangShowTokensInLine functions {{{1
   1513 " ================================
   1514 
   1515 " These functions are useful during development.
   1516 
   1517 function! ErlangShowTokensInLine(line)
   1518  echo "Line: " . a:line
   1519  let indtokens = s:GetTokensFromLine(a:line, 0, 0, &tabstop)
   1520  echo "Tokens:"
   1521  for it in indtokens
   1522    echo it
   1523  endfor
   1524 endfunction
   1525 
   1526 function! ErlangShowTokensInCurrentLine()
   1527  return ErlangShowTokensInLine(getline('.'))
   1528 endfunction
   1529 
   1530 " Cleanup {{{1
   1531 " =======
   1532 
   1533 let &cpo = s:cpo_save
   1534 unlet s:cpo_save
   1535 
   1536 " vim: sw=2 et fdm=marker