diff runtime/indent/erlang.vim @ 4437:eb6ab7e78925

Update runtime files.
author Bram Moolenaar <bram@vim.org>
date Fri, 17 May 2013 18:14:19 +0200
parents af1e8a1714c2
children 2b11ac90d9e9
line wrap: on
line diff
--- a/runtime/indent/erlang.vim
+++ b/runtime/indent/erlang.vim
@@ -1,213 +1,1375 @@
 " Vim indent file
-" Language:     Erlang
+" 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:  2013-Mar-05
 " License:      Vim license
-" Version:      2011/09/06
+" URL:          https://github.com/hcs42/vim-erlang
+
+" 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
-if exists("b:did_indent")
-    finish
+" Vim 7 or later is needed
+if exists("b:did_indent") || version < 700
+  finish
 else
-    let b:did_indent = 1
+  let b:did_indent = 1
 endif
 
 setlocal indentexpr=ErlangIndent()
-setlocal indentkeys+==after,=end,=catch,=),=],=}
+setlocal indentkeys+=0=end,0=of,0=catch,0=after,0=when,0=),0=],0=},0=>>
 
 " Only define the functions once
 if exists("*ErlangIndent")
-    finish
+  finish
 endif
 
-" The function goes through the whole line, analyses it and returns the
-" indentation level.
-"
-" line: the line to be examined
-" return: the indentation level of the examined line
-function s:ErlangIndentAfterLine(line)
-    let linelen = strlen(a:line) " the length of the line
-    let i       = 0 " the index of the current character in the line
-    let ind = 0 " how much should be the difference between the indentation of
-                " the current line and the indentation of the next line?
-                " e.g. +1: the indentation of the next line should be equal to
-                " the indentation of the current line plus one shiftwidth
-    let last_fun      = 0 " the last token was a 'fun'
-    let last_receive  = 0 " the last token was a 'receive'; needed for 'after'
-    let last_hash_sym = 0 " the last token was a '#'
+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".
+
+" 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 relevent 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', '<variable>', '}')
+"   vcol = integer (the virtual column of the first character of the token)
+"   col = integer
+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 = []
 
-    " Ignore comments
-    if a:line =~# '^\s*%'
-        return 0
+  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
 
-    " Partial function head where the guard is missing
-    if a:line =~# "\\(^\\l[[:alnum:]_]*\\)\\|\\(^'[^']\\+'\\)(" && a:line !~# '->'
-        return 2
-    endif
-
-    " The missing guard from the split function head
-    if a:line =~# '^\s*when\s\+.*->'
-        return -1
+    if next_vcol == ''
+      let vcol += next_i - i
+    else
+      let vcol = next_vcol
     endif
 
-    while 0<=i && i<linelen
-        " m: the next value of the i
-        if a:line[i] == '"'
-            let m = matchend(a:line,'"\%([^"\\]\|\\.\)*"',i)
-            let last_receive = 0
-        elseif a:line[i] == "'"
-            let m = matchend(a:line,"'[^']*'",i)
-            let last_receive = 0
-        elseif a:line[i] =~# "[a-z]"
-            let m = matchend(a:line,".[[:alnum:]_]*",i)
-            if last_fun
-                let ind = ind - 1
-                let last_fun = 0
-                let last_receive = 0
-            elseif a:line[(i):(m-1)] =~# '^\%(case\|if\|try\)$'
-                let ind = ind + 1
-            elseif a:line[(i):(m-1)] =~# '^receive$'
-                let ind = ind + 1
-                let last_receive = 1
-            elseif a:line[(i):(m-1)] =~# '^begin$'
-                let ind = ind + 2
-                let last_receive = 0
-            elseif a:line[(i):(m-1)] =~# '^end$'
-                let ind = ind - 2
-                let last_receive = 0
-            elseif a:line[(i):(m-1)] =~# '^after$'
-                if last_receive == 0
-                    let ind = ind - 1
-                else
-                    let ind = ind + 0
-                endif
-                let last_receive = 0
-            elseif a:line[(i):(m-1)] =~# '^fun$'
-                let ind = ind + 1
-                let last_fun = 1
-                let last_receive = 0
-            endif
-        elseif a:line[i] =~# "[A-Z_]"
-            let m = matchend(a:line,".[[:alnum:]_]*",i)
-            let last_receive = 0
-        elseif a:line[i] == '$'
-            let m = i+2
-            let last_receive = 0
-        elseif a:line[i] == "." && (i+1>=linelen || a:line[i+1]!~ "[0-9]")
-            let m = i+1
-            if last_hash_sym
-                let last_hash_sym = 0
-            else
-                let ind = ind - 1
-            endif
-            let last_receive = 0
-        elseif a:line[i] == '-' && (i+1<linelen && a:line[i+1]=='>')
-            let m = i+2
-            let ind = ind + 1
-            let last_receive = 0
-        elseif a:line[i] == ';' && a:line[(i):(linelen)] !~# '.*->.*'
-            let m = i+1
-            let ind = ind - 1
-            let last_receive = 0
-        elseif a:line[i] == '#'
-            let m = i+1
-            let last_hash_sym = 1
-        elseif a:line[i] =~# '[({[]'
-            let m = i+1
-            let ind = ind + 1
-            let last_fun = 0
-            let last_receive = 0
-            let last_hash_sym = 0
-        elseif a:line[i] =~# '[)}\]]'
-            let m = i+1
-            let ind = ind - 1
-            let last_receive = 0
+    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: [] -- the result is an empty list if we hit the beginning or end
+"                  of the file
+"           | 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 []
+    elseif !empty(indtokens)
+      return indtokens[a:dir == 'up' ? -1 : 0]
+    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]
+  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]
+  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')
+    return synIDattr(synID(a:lnum, 1, 0), 'name') =~# '^erlangQuotedAtom'
+  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', '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': 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
+" 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 preceeds 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
+" 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)
+  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 + &sw + 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 + &sw]
+    elseif a:stack[0] == ';'
+      call s:Pop(a:stack)
+      if empty(a:stack)
+        call s:Log('    Stack is ["->", ";"], so LTI is in a function head ' .
+                  \'-> return')
+        return [0, a:stored_vcol]
+      else
+        return [1, s:UnexpectedToken(a:token, a:stack)]
+      endif
+    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,
+         \ '\<\%(case\|try\|begin\|receive\|if\)\>\|' .
+         \ '\<fun\>\%(\s\|\n\|%.*$\)*(',
+         \ '',
+         \ '\<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)
+      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)
+        if ret | return res | endif
+
+        if stored_vcol == -1
+          call s:Log('    End of clause directly preceeds LTI -> return')
+          return 0
         else
-            let m = i+1
+          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
 
-        let i = m
-    endwhile
+      elseif token == 'begin'
+        let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
+                                            \stored_vcol, 'end', &sw)
+        if ret | return res | endif
+
+      " case EXPR of 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
+
+      " This branch is not Emacs-compatible
+      elseif (index(['of', 'receive', 'after', 'if'], token) != -1 ||
+           \  (token == 'catch' && !s:IsCatchStandalone(lnum, i))) &&
+           \ !last_token_of_line &&
+           \ (empty(stack) || stack == ['when'] || stack == ['->'] ||
+           \  stack == ['->', ';'])
+
+        " If we are after of/receive, 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
 
-    return ind
-endfunction
+        " 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/catch" -> return')
+          return stored_vcol
+        elseif stack == ['->']
+          call s:Log('    LTI is in a branch after ' .
+                    \'"of/receive/after/if/catch" -> return')
+          return stored_vcol + &sw
+        elseif stack == ['when']
+          call s:Log('    LTI is in a guard after ' .
+                    \'"of/receive/after/if/catch" -> return')
+          return stored_vcol + &sw
+        else
+          return s:UnexpectedToken(token, stack)
+        endif
+
+      elseif index(['case', 'if', 'try', 'receive'], 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 == 'try' && (stack[0] == 'of' ||
+             \                     stack[0] == 'catch' ||
+             \                     stack[0] == 'after')) ||
+             \ (token == 'receive')
+
+          " From the indentation point of view, the keyword
+          " (of/catch/after/end) before the LTI is what counts, so
+          " when we reached these tokens, and the stack already had
+          " a catch/after/end, we didn't modify it.
+          "
+          " This way when we reach case/try/receive (i.e. now),
+          " there is at most one of/catch/after/end token in the
+          " stack.
+          if token == 'case' || token == 'try' ||
+           \ (token == 'receive' && stack[0] == 'after')
+            call s:Pop(stack)
+          endif
 
-function s:FindPrevNonBlankNonComment(lnum)
-    let lnum = prevnonblank(a:lnum)
-    let line = getline(lnum)
-    " Continue to search above if the current line begins with a '%'
-    while line =~# '^\s*%.*$'
-        let lnum = prevnonblank(lnum - 1)
-        if 0 == lnum
-            return 0
+          if empty(stack)
+            call s:Log('    LTI is in a condition; matching ' .
+                      \'"case/if/try/receive" found')
+            let stored_vcol = curr_vcol + &sw
+          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" found')
+            call s:Pop(stack)
+            call s:Pop(stack)
+            let stored_vcol = curr_vcol + &sw
+          elseif stack[0] == '->'
+            call s:Log('    LTI is in a branch; matching ' .
+                      \'"case/if/try/receive" found')
+            call s:Pop(stack)
+            let stored_vcol = curr_vcol + 2 * &sw
+          elseif stack[0] == 'when'
+            call s:Log('    LTI is in a guard; matching ' .
+                      \'"case/if/try/receive" found')
+            call s:Pop(stack)
+            let stored_vcol = curr_vcol + 2 * &sw + 2
+          endif
+
+        endif
+
+        let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
+                                            \stored_vcol, 'end', &sw)
+        if ret | return res | endif
+
+      elseif token == 'fun'
+        let next_indtoken = s:NextIndToken(lnum, i)
+        call s:Log('    Next indtoken = ' . string(next_indtoken))
+
+        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 + &sw
+          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 * &sw
+          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 * &sw + 2
+          endif
+
+          let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
+                                              \stored_vcol, 'end', &sw)
+          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
-        let line = getline(lnum)
-    endwhile
-    return lnum
-endfunction
+
+        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 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
+
+      elseif index([')', ']', '}'], token) != -1
+
+        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
 
-" The function returns the indentation level of the line adjusted to a mutiple
-" of 'shiftwidth' option.
-"
-" lnum: line number
-" return: the indentation level of the line
-function s:GetLineIndent(lnum)
-    return (indent(a:lnum) / &sw) * &sw
-endfunction
+      elseif token == '>>'
+        call s:Push(stack, token)
+
+      elseif token == ';'
+
+        if empty(stack)
+          call s:Push(stack, ';')
+        elseif index([';', '->', 'when', 'end', 'after', 'catch'],
+                    \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/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 == '->'
+
+        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'], 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/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 == 'when'
+
+        " Pop all ';' from the top of the stack
+        while !empty(stack) && stack[0] == ';'
+          call s:Pop(stack)
+        endwhile
 
-function ErlangIndent()
-    " Find a non-blank line above the current line
-    let lnum = prevnonblank(v:lnum - 1)
+        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, &sw)
+            if ret | return res | endif
+          else
+            " Example:
+            "   when
+            "       LTI
+            call s:Push(stack, token)
+          endif
+        elseif index(['->', 'when', 'end', 'after', 'catch'], 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/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 == 'of' || token == 'after' ||
+           \ (token == 'catch' && !s:IsCatchStandalone(lnum, i))
+
+        if token == 'after'
+          " If LTI is between an 'after' and the corresponding
+          " 'end', then let's return
+          let [ret, res] = s:BeginElementFoundIfEmpty(stack, token, curr_vcol,
+                                                     \stored_vcol, &sw)
+          if ret | return res | endif
+        endif
 
-    " Hit the start of the file, use zero indent
-    if lnum == 0
-        return 0
+        if empty(stack) || stack[0] == '->' || stack[0] == 'when'
+          call s:Push(stack, token)
+        elseif stack[0] == 'catch' || stack[0] == 'after' || 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 prevline = getline(lnum)
-    let currline = getline(v:lnum)
+    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
+
+  let ml = matchlist(currline,
+                    \'^\(\s*\)\(\%(end\|of\|catch\|after\)\>\|[)\]}]\|>>\)')
 
-    let ind_after = s:ErlangIndentAfterLine(prevline)
-    if ind_after != 0
-        let ind = s:GetLineIndent(lnum) + ind_after * &sw
+  " 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 ml[2] == 'end'
+      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
-        let ind = indent(lnum) + ind_after * &sw
-    endif
 
-    " Special cases:
-    if prevline =~# '^\s*\%(after\|end\)\>'
-        let ind = ind + 2*&sw
-    endif
-    if currline =~# '^\s*end\>'
-        let ind = ind - 2*&sw
+      call s:Log("  Line type = 'end'")
+      let new_col = s:ErlangCalcIndent(v:lnum - 1,
+                                      \[ml[2], 'align_to_begin_element'])
     endif
-    if currline =~# '^\s*after\>'
-        let plnum = s:FindPrevNonBlankNonComment(v:lnum-1)
-        if getline(plnum) =~# '^[^%]*\<receive\>\s*\%(%.*\)\=$'
-            " If the 'receive' is not in the same line as the 'after'
-            let ind = ind - 1*&sw
-        else
-            let ind = ind - 2*&sw
-        endif
-    endif
-    if prevline =~# '^\s*[)}\]]'
-        let ind = ind + 1*&sw
+  else
+    call s:Log("  Line type = 'normal'")
+
+    let new_col = s:ErlangCalcIndent(v:lnum - 1, [])
+    if currline =~# '^\s*when\>'
+      let new_col += 2
     endif
-    if currline =~# '^\s*[)}\]]'
-        let ind = ind - 1*&sw
-    endif
-    if prevline =~# '^\s*\%(catch\)\s*\%(%\|$\)'
-        let ind = ind + 1*&sw
-    endif
-    if currline =~# '^\s*\%(catch\)\s*\%(%\|$\)'
-        let ind = ind - 1*&sw
-    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
 
-    if ind<0
-        let ind = 0
-    endif
-    return ind
 endfunction
+
+" Cleanup {{{1
+" =======
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+" vim: sw=2 et fdm=marker