mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	Vim runtime files based on 7.4.384 / hg changeset 7090d7f160f7
Excluding:
  Amiga icons (*.info, icons/)
  doc/hangulin.txt
  tutor/
  spell/
  lang/ (only used for menu translations)
  macros/maze/, macros/hanoi/, macros/life/, macros/urm/
      These were used to test vi compatibility.
  termcap
      "Demonstration of a termcap file (for the Amiga and Archimedes)"
Helped-by: Rich Wareham <rjw57@cam.ac.uk>
Helped-by: John <john.schmidt.h@gmail.com>
Helped-by: Yann <yann@yann-salaun.com>
Helped-by: Christophe Badoit <c.badoit@lesiteimmo.com>
Helped-by: drasill <github@tof2k.com>
Helped-by: Tae Sandoval Murgan <taecilla@gmail.com>
Helped-by: Lowe Thiderman <lowe.thiderman@gmail.com>
		
	
		
			
				
	
	
		
			1391 lines
		
	
	
		
			45 KiB
		
	
	
	
		
			VimL
		
	
	
	
	
	
			
		
		
	
	
			1391 lines
		
	
	
		
			45 KiB
		
	
	
	
		
			VimL
		
	
	
	
	
	
" Vim indent file
 | 
						|
" Language:     Erlang (http://www.erlang.org)
 | 
						|
" Author:       Csaba Hoch <csaba.hoch@gmail.com>
 | 
						|
" Contributors: Edwin Fine <efine145_nospam01 at usa dot net>
 | 
						|
"               Pawel 'kTT' Salata <rockplayer.pl@gmail.com>
 | 
						|
"               Ricardo Catalinas Jiménez <jimenezrick@gmail.com>
 | 
						|
" Last Update:  2013-Jul-21
 | 
						|
" License:      Vim license
 | 
						|
" 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
 | 
						|
" Vim 7 or later is needed
 | 
						|
if exists("b:did_indent") || version < 700
 | 
						|
  finish
 | 
						|
else
 | 
						|
  let b:did_indent = 1
 | 
						|
endif
 | 
						|
 | 
						|
setlocal indentexpr=ErlangIndent()
 | 
						|
setlocal indentkeys+=0=end,0=of,0=catch,0=after,0=when,0=),0=],0=},0=>>
 | 
						|
 | 
						|
" Only define the functions once
 | 
						|
if exists("*ErlangIndent")
 | 
						|
  finish
 | 
						|
endif
 | 
						|
 | 
						|
let s:cpo_save = &cpo
 | 
						|
set cpo&vim
 | 
						|
 | 
						|
" Logging library {{{1
 | 
						|
" ===============
 | 
						|
 | 
						|
" Purpose:
 | 
						|
"   Logs the given string using the ErlangIndentLog function if it exists.
 | 
						|
" Parameters:
 | 
						|
"   s: string
 | 
						|
function! s:Log(s)
 | 
						|
  if exists("*ErlangIndentLog")
 | 
						|
    call ErlangIndentLog(a:s)
 | 
						|
  endif
 | 
						|
endfunction
 | 
						|
 | 
						|
" Line tokenizer library {{{1
 | 
						|
" ======================
 | 
						|
 | 
						|
" Indtokens are "indentation tokens".
 | 
						|
 | 
						|
" 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 = []
 | 
						|
 | 
						|
  if a:string_continuation
 | 
						|
    let i = matchend(a:line, '^\%([^"\\]\|\\.\)*"', 0)
 | 
						|
    if i ==# -1
 | 
						|
      call s:Log('    Whole line is string continuation -> ignore')
 | 
						|
      return []
 | 
						|
    else
 | 
						|
      let vcol = s:CalcVCol(a:line, 0, i - 1, 0, a:tabstop)
 | 
						|
      call add(indtokens, ['<string_end>', vcol, i])
 | 
						|
    endif
 | 
						|
  elseif a:atom_continuation
 | 
						|
    let i = matchend(a:line, "^\\%([^'\\\\]\\|\\\\.\\)*'", 0)
 | 
						|
    if i ==# -1
 | 
						|
      call s:Log('    Whole line is quoted atom continuation -> ignore')
 | 
						|
      return []
 | 
						|
    else
 | 
						|
      let vcol = s:CalcVCol(a:line, 0, i - 1, 0, a:tabstop)
 | 
						|
      call add(indtokens, ['<quoted_atom_end>', vcol, i])
 | 
						|
    endif
 | 
						|
  endif
 | 
						|
 | 
						|
  while 0 <= i && i < linelen
 | 
						|
 | 
						|
    let next_vcol = ''
 | 
						|
 | 
						|
    " Spaces
 | 
						|
    if a:line[i] ==# ' '
 | 
						|
      let next_i = matchend(a:line, ' *', i + 1)
 | 
						|
 | 
						|
    " Tabs
 | 
						|
    elseif a:line[i] ==# "\t"
 | 
						|
      let next_i = matchend(a:line, '\t*', i + 1)
 | 
						|
 | 
						|
      " See example in s:CalcVCol
 | 
						|
      let next_vcol = (vcol / a:tabstop + (next_i - i)) * a:tabstop
 | 
						|
 | 
						|
    " Comment
 | 
						|
    elseif a:line[i] ==# '%'
 | 
						|
      let next_i = linelen
 | 
						|
 | 
						|
    " String token: "..."
 | 
						|
    elseif a:line[i] ==# '"'
 | 
						|
      let next_i = matchend(a:line, '\%([^"\\]\|\\.\)*"', i + 1)
 | 
						|
      if next_i ==# -1
 | 
						|
        call add(indtokens, ['<string_start>', vcol, i])
 | 
						|
      else
 | 
						|
        let next_vcol = s:CalcVCol(a:line, i, next_i - 1, vcol, a:tabstop)
 | 
						|
        call add(indtokens, ['<string>', vcol, i])
 | 
						|
      endif
 | 
						|
 | 
						|
    " Quoted atom token: '...'
 | 
						|
    elseif a:line[i] ==# "'"
 | 
						|
      let next_i = matchend(a:line, "\\%([^'\\\\]\\|\\\\.\\)*'", i + 1)
 | 
						|
      if next_i ==# -1
 | 
						|
        call add(indtokens, ['<quoted_atom_start>', vcol, i])
 | 
						|
      else
 | 
						|
        let next_vcol = s:CalcVCol(a:line, i, next_i - 1, vcol, a:tabstop)
 | 
						|
        call add(indtokens, ['<quoted_atom>', vcol, i])
 | 
						|
      endif
 | 
						|
 | 
						|
    " Keyword or atom or variable token or number
 | 
						|
    elseif a:line[i] =~# '[a-zA-Z_@0-9]'
 | 
						|
      let next_i = matchend(a:line,
 | 
						|
                           \'[[:alnum:]_@:]*\%(\s*#\s*[[:alnum:]_@:]*\)\=',
 | 
						|
                           \i + 1)
 | 
						|
      call add(indtokens, [a:line[(i):(next_i - 1)], vcol, i])
 | 
						|
 | 
						|
    " Character token: $<char> (as in: $a)
 | 
						|
    elseif a:line[i] ==# '$'
 | 
						|
      call add(indtokens, ['$.', vcol, i])
 | 
						|
      let next_i = i + 2
 | 
						|
 | 
						|
    " Dot token: .
 | 
						|
    elseif a:line[i] ==# '.'
 | 
						|
 | 
						|
      let next_i = i + 1
 | 
						|
 | 
						|
      if i + 1 ==# linelen || a:line[i + 1] =~# '[[:blank:]%]'
 | 
						|
        " End of clause token: . (as in: f() -> ok.)
 | 
						|
        call add(indtokens, ['<end_of_clause>', vcol, i])
 | 
						|
 | 
						|
      else
 | 
						|
        " Possibilities:
 | 
						|
        " - Dot token in float: . (as in: 3.14)
 | 
						|
        " - Dot token in record: . (as in: #myrec.myfield)
 | 
						|
        call add(indtokens, ['.', vcol, i])
 | 
						|
      endif
 | 
						|
 | 
						|
    " Equal sign
 | 
						|
    elseif a:line[i] ==# '='
 | 
						|
      " This is handled separately so that "=<<" will be parsed as
 | 
						|
      " ['=', '<<'] instead of ['=<', '<']. Although Erlang parses it
 | 
						|
      " currently in the latter way, that may be fixed some day.
 | 
						|
      call add(indtokens, [a:line[i], vcol, i])
 | 
						|
      let next_i = i + 1
 | 
						|
 | 
						|
    " Three-character tokens
 | 
						|
    elseif i + 1 < linelen &&
 | 
						|
         \ index(['=:=', '=/='], a:line[i : i + 1]) != -1
 | 
						|
      call add(indtokens, [a:line[i : i + 1], vcol, i])
 | 
						|
      let next_i = i + 2
 | 
						|
 | 
						|
    " Two-character tokens
 | 
						|
    elseif i + 1 < linelen &&
 | 
						|
         \ index(['->', '<<', '>>', '||', '==', '/=', '=<', '>=', '++', '--',
 | 
						|
         \        '::'],
 | 
						|
         \       a:line[i : i + 1]) != -1
 | 
						|
      call add(indtokens, [a:line[i : i + 1], vcol, i])
 | 
						|
      let next_i = i + 2
 | 
						|
 | 
						|
    " Other character: , ; < > ( ) [ ] { } # + - * / : ? = ! |
 | 
						|
    else
 | 
						|
      call add(indtokens, [a:line[i], vcol, i])
 | 
						|
      let next_i = i + 1
 | 
						|
 | 
						|
    endif
 | 
						|
 | 
						|
    if next_vcol ==# ''
 | 
						|
      let vcol += next_i - i
 | 
						|
    else
 | 
						|
      let vcol = next_vcol
 | 
						|
    endif
 | 
						|
 | 
						|
    let i = next_i
 | 
						|
 | 
						|
  endwhile
 | 
						|
 | 
						|
  return indtokens
 | 
						|
 | 
						|
endfunction
 | 
						|
 | 
						|
" TODO: doc, handle "not found" case
 | 
						|
function! s:GetIndtokenAtCol(indtokens, col)
 | 
						|
  let i = 0
 | 
						|
  while i < len(a:indtokens)
 | 
						|
    if a:indtokens[i][2] ==# a:col
 | 
						|
      return [1, i]
 | 
						|
    elseif a:indtokens[i][2] > a:col
 | 
						|
      return [0, s:IndentError('No token at col ' . a:col . ', ' .
 | 
						|
                              \'indtokens = ' . string(a:indtokens),
 | 
						|
                              \'', '')]
 | 
						|
    endif
 | 
						|
    let i += 1
 | 
						|
  endwhile
 | 
						|
  return [0, s:IndentError('No token at col ' . a:col . ', ' .
 | 
						|
                           \'indtokens = ' . string(a:indtokens),
 | 
						|
                           \'', '')]
 | 
						|
endfunction
 | 
						|
 | 
						|
" Stack library {{{1
 | 
						|
" =============
 | 
						|
 | 
						|
" Purpose:
 | 
						|
"   Push a token onto the parser's stack.
 | 
						|
" Parameters:
 | 
						|
"   stack: [token]
 | 
						|
"   token: string
 | 
						|
function! s:Push(stack, token)
 | 
						|
  call s:Log('    Stack Push: "' . a:token . '" into ' . string(a:stack))
 | 
						|
  call insert(a:stack, a:token)
 | 
						|
endfunction
 | 
						|
 | 
						|
" Purpose:
 | 
						|
"   Pop a token from the parser's stack.
 | 
						|
" Parameters:
 | 
						|
"   stack: [token]
 | 
						|
"   token: string
 | 
						|
" Returns:
 | 
						|
"   token: string -- the removed element
 | 
						|
function! s:Pop(stack)
 | 
						|
  let head = remove(a:stack, 0)
 | 
						|
  call s:Log('    Stack Pop: "' . head . '" from ' . string(a:stack))
 | 
						|
  return head
 | 
						|
endfunction
 | 
						|
 | 
						|
" Library for accessing and storing tokenized lines {{{1
 | 
						|
" =================================================
 | 
						|
 | 
						|
" The Erlang token cache: an `lnum -> indtokens` dictionary that stores the
 | 
						|
" tokenized lines.
 | 
						|
let s:all_tokens = {}
 | 
						|
let s:file_name = ''
 | 
						|
let s:last_changedtick = -1
 | 
						|
 | 
						|
" Purpose:
 | 
						|
"   Clear the Erlang token cache if we have a different file or the file has
 | 
						|
"   been changed since the last indentation.
 | 
						|
function! s:ClearTokenCacheIfNeeded()
 | 
						|
  let file_name = expand('%:p')
 | 
						|
  if file_name != s:file_name ||
 | 
						|
   \ b:changedtick != s:last_changedtick
 | 
						|
    let s:file_name = file_name
 | 
						|
    let s:last_changedtick = b:changedtick
 | 
						|
    let s:all_tokens = {}
 | 
						|
  endif
 | 
						|
endfunction
 | 
						|
 | 
						|
" Purpose:
 | 
						|
"   Return the tokens of line `lnum`, if that line is not empty. If it is
 | 
						|
"   empty, find the first non-empty line in the given `direction` and return
 | 
						|
"   the tokens of that line.
 | 
						|
" Parameters:
 | 
						|
"   lnum: integer
 | 
						|
"   direction: 'up' | 'down'
 | 
						|
" Returns:
 | 
						|
"   result: [] -- the result is an empty list if we hit the beginning or end
 | 
						|
"                  of the file
 | 
						|
"           | [lnum, indtokens]
 | 
						|
"   lnum: integer -- the index of the non-empty line that was found and
 | 
						|
"                    tokenized
 | 
						|
"   indtokens: [indtoken] -- the tokens of line `lnum`
 | 
						|
function! s:TokenizeLine(lnum, direction)
 | 
						|
 | 
						|
  call s:Log('Tokenizing starts from line ' . a:lnum)
 | 
						|
  if a:direction ==# 'up'
 | 
						|
    let lnum = prevnonblank(a:lnum)
 | 
						|
  else " a:direction ==# 'down'
 | 
						|
    let lnum = nextnonblank(a:lnum)
 | 
						|
  endif
 | 
						|
 | 
						|
  " We hit the beginning or end of the file
 | 
						|
  if lnum ==# 0
 | 
						|
    let indtokens = []
 | 
						|
    call s:Log('  We hit the beginning or end of the file.')
 | 
						|
 | 
						|
    " The line has already been parsed
 | 
						|
  elseif has_key(s:all_tokens, lnum)
 | 
						|
    let indtokens = s:all_tokens[lnum]
 | 
						|
    call s:Log('Cached line ' . lnum . ': ' . getline(lnum))
 | 
						|
    call s:Log("  Tokens in the line:\n    - " . join(indtokens, "\n    - "))
 | 
						|
 | 
						|
    " The line should be parsed now
 | 
						|
  else
 | 
						|
 | 
						|
    " Parse the line
 | 
						|
    let line = getline(lnum)
 | 
						|
    let string_continuation = s:IsLineStringContinuation(lnum)
 | 
						|
    let atom_continuation = s:IsLineAtomContinuation(lnum)
 | 
						|
    let indtokens = s:GetTokensFromLine(line, string_continuation,
 | 
						|
                                       \atom_continuation, &tabstop)
 | 
						|
    let s:all_tokens[lnum] = indtokens
 | 
						|
    call s:Log('Tokenizing line ' . lnum . ': ' . line)
 | 
						|
    call s:Log("  Tokens in the line:\n    - " . join(indtokens, "\n    - "))
 | 
						|
 | 
						|
  endif
 | 
						|
 | 
						|
  return [lnum, indtokens]
 | 
						|
endfunction
 | 
						|
 | 
						|
" Purpose:
 | 
						|
"   As a helper function for PrevIndToken and NextIndToken, the FindIndToken
 | 
						|
"   function finds the first line with at least one token in the given
 | 
						|
"   direction.
 | 
						|
" Parameters:
 | 
						|
"   lnum: integer
 | 
						|
"   direction: 'up' | 'down'
 | 
						|
" Returns:
 | 
						|
"   result: [] -- 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,
 | 
						|
         \ '\C\<\%(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
 | 
						|
          call s:Log('    End of clause (but not end of line) -> return')
 | 
						|
          return stored_vcol
 | 
						|
        endif
 | 
						|
 | 
						|
      elseif stack == ['prev_term_plus']
 | 
						|
        if token =~# '[a-zA-Z_@]' ||
 | 
						|
         \ token ==# '<string>' || token ==# '<string_start>' ||
 | 
						|
         \ token ==# '<quoted_atom>' || token ==# '<quoted_atom_start>'
 | 
						|
          call s:Log('    previous token found: curr_vcol + plus = ' .
 | 
						|
                    \curr_vcol . " + " . plus)
 | 
						|
          return curr_vcol + plus
 | 
						|
        endif
 | 
						|
 | 
						|
      elseif token ==# 'begin'
 | 
						|
        let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
 | 
						|
                                            \stored_vcol, 'end', &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
 | 
						|
 | 
						|
        " 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
 | 
						|
 | 
						|
          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
 | 
						|
 | 
						|
        if mode ==# 'inside' || mode ==# 'align_to_begin_element'
 | 
						|
 | 
						|
          if last_token_of_line && i != 0
 | 
						|
            " Examples: {{{
 | 
						|
            "
 | 
						|
            " mode == 'inside':
 | 
						|
            "
 | 
						|
            "     my_func(
 | 
						|
            "       LTI
 | 
						|
            "
 | 
						|
            "     [Variable, {
 | 
						|
            "        LTI
 | 
						|
            "
 | 
						|
            " mode == 'align_to_begin_element':
 | 
						|
            "
 | 
						|
            "     my_func(
 | 
						|
            "       Params
 | 
						|
            "      ) % LTI
 | 
						|
            "
 | 
						|
            "     [Variable, {
 | 
						|
            "        Terms
 | 
						|
            "       } % LTI
 | 
						|
            " }}}
 | 
						|
            let stack = ['prev_term_plus']
 | 
						|
            let plus = (mode ==# 'inside' ? 2 : 1)
 | 
						|
            call s:Log('    "' . token .
 | 
						|
                      \'" token found at end of line -> find previous token')
 | 
						|
          elseif mode ==# 'align_to_begin_element'
 | 
						|
            " Examples: {{{
 | 
						|
            "
 | 
						|
            " mode == 'align_to_begin_element' && !last_token_of_line
 | 
						|
            "
 | 
						|
            "     my_func(stored_vcol
 | 
						|
            "            ) % LTI
 | 
						|
            "
 | 
						|
            "     [Variable, {stored_vcol
 | 
						|
            "                } % LTI
 | 
						|
            "
 | 
						|
            " mode == 'align_to_begin_element' && i == 0
 | 
						|
            "
 | 
						|
            "     (
 | 
						|
            "       stored_vcol
 | 
						|
            "     ) % LTI
 | 
						|
            "
 | 
						|
            "     {
 | 
						|
            "       stored_vcol
 | 
						|
            "     } % LTI
 | 
						|
            " }}}
 | 
						|
            call s:Log('    "' . token . '" token (whose closing token ' .
 | 
						|
                      \'starts LTI) found -> return')
 | 
						|
            return curr_vcol
 | 
						|
          elseif stored_vcol ==# -1
 | 
						|
            " Examples: {{{
 | 
						|
            "
 | 
						|
            " mode == 'inside' && stored_vcol == -1 && !last_token_of_line
 | 
						|
            "
 | 
						|
            "     my_func(
 | 
						|
            "             LTI
 | 
						|
            "     [Variable, {
 | 
						|
            "                 LTI
 | 
						|
            "
 | 
						|
            " mode == 'inside' && stored_vcol == -1 && i == 0
 | 
						|
            "
 | 
						|
            "     (
 | 
						|
            "      LTI
 | 
						|
            "
 | 
						|
            "     {
 | 
						|
            "      LTI
 | 
						|
            " }}}
 | 
						|
            call s:Log('    "' . token .
 | 
						|
                      \'" token (which directly precedes LTI) found -> return')
 | 
						|
            return curr_vcol + 1
 | 
						|
          else
 | 
						|
            " Examples: {{{
 | 
						|
            "
 | 
						|
            " mode == 'inside' && stored_vcol != -1 && !last_token_of_line
 | 
						|
            "
 | 
						|
            "     my_func(stored_vcol,
 | 
						|
            "             LTI
 | 
						|
            "
 | 
						|
            "     [Variable, {stored_vcol,
 | 
						|
            "                 LTI
 | 
						|
            "
 | 
						|
            " mode == 'inside' && stored_vcol != -1 && i == 0
 | 
						|
            "
 | 
						|
            "     (stored_vcol,
 | 
						|
            "      LTI
 | 
						|
            "
 | 
						|
            "     {stored_vcol,
 | 
						|
            "      LTI
 | 
						|
            " }}}
 | 
						|
            call s:Log('    "' . token .
 | 
						|
                      \'" token (whose block contains LTI) found -> return')
 | 
						|
            return stored_vcol
 | 
						|
          endif
 | 
						|
        endif
 | 
						|
 | 
						|
      elseif index(['end', ')', ']', '}', '>>'], token) != -1
 | 
						|
 | 
						|
        " If we can be sure that there is synchronization in the Erlang
 | 
						|
        " syntax, we use searchpair to make the script quicker. Otherwise we
 | 
						|
        " just push the token onto the stack and keep parsing.
 | 
						|
    
 | 
						|
        " No synchronization -> no searchpair optimization
 | 
						|
        if !exists('b:erlang_syntax_synced')
 | 
						|
          call s:Push(stack, token)
 | 
						|
 | 
						|
        " We don't have searchpair optimization for '>>'
 | 
						|
        elseif token ==# '>>'
 | 
						|
          call s:Push(stack, token)
 | 
						|
 | 
						|
        elseif token ==# 'end'
 | 
						|
          let [lnum_new, col_new] = s:SearchEndPair(lnum, curr_col)
 | 
						|
 | 
						|
          if lnum_new ==# 0
 | 
						|
            return s:IndentError('Matching token for "end" not found',
 | 
						|
                                \token, stack)
 | 
						|
          else
 | 
						|
            if lnum_new != lnum
 | 
						|
              call s:Log('    Tokenize for "end" <<<<')
 | 
						|
              let [lnum, indtokens] = s:TokenizeLine(lnum_new, 'up')
 | 
						|
              call s:Log('    >>>> Tokenize for "end"')
 | 
						|
            endif
 | 
						|
 | 
						|
            let [success, i] = s:GetIndtokenAtCol(indtokens, col_new)
 | 
						|
            if !success | return i | endif
 | 
						|
            let [token, curr_vcol, curr_col] = indtokens[i]
 | 
						|
            call s:Log('    Match for "end" in line ' . lnum_new . ': ' .
 | 
						|
                      \string(indtokens[i]))
 | 
						|
          endif
 | 
						|
 | 
						|
        else " token is one of the following: ')', ']', '}'
 | 
						|
 | 
						|
          call s:Push(stack, token)
 | 
						|
 | 
						|
          " We have to escape '[', because this string will be interpreted as a
 | 
						|
          " regexp
 | 
						|
          let open_paren = (token ==# ')' ? '(' :
 | 
						|
                           \token ==# ']' ? '\[' :
 | 
						|
                           \               '{')
 | 
						|
 | 
						|
          let [lnum_new, col_new] = s:SearchPair(lnum, curr_col,
 | 
						|
                                                \open_paren, '', token)
 | 
						|
 | 
						|
          if lnum_new ==# 0
 | 
						|
            return s:IndentError('Matching token not found',
 | 
						|
                                \token, stack)
 | 
						|
          else
 | 
						|
            if lnum_new != lnum
 | 
						|
              call s:Log('    Tokenize the opening paren <<<<')
 | 
						|
              let [lnum, indtokens] = s:TokenizeLine(lnum_new, 'up')
 | 
						|
              call s:Log('    >>>>')
 | 
						|
            endif
 | 
						|
 | 
						|
            let [success, i] = s:GetIndtokenAtCol(indtokens, col_new)
 | 
						|
            if !success | return i | endif
 | 
						|
            let [token, curr_vcol, curr_col] = indtokens[i]
 | 
						|
            call s:Log('    Match in line ' . lnum_new . ': ' .
 | 
						|
                      \string(indtokens[i]))
 | 
						|
 | 
						|
            " Go back to the beginning of the loop and handle the opening paren
 | 
						|
            continue
 | 
						|
          endif
 | 
						|
        endif
 | 
						|
 | 
						|
      elseif token ==# ';'
 | 
						|
 | 
						|
        if empty(stack)
 | 
						|
          call s:Push(stack, ';')
 | 
						|
        elseif index([';', '->', 'when', 'end', 'after', 'catch'],
 | 
						|
                    \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
 | 
						|
 | 
						|
        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
 | 
						|
 | 
						|
        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 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\)\>\|[)\]}]\|>>\)')
 | 
						|
 | 
						|
  " If the line has a special beginning, but not a standalone catch
 | 
						|
  if !empty(ml) && !(ml[2] ==# 'catch' && s:IsCatchStandalone(v:lnum, 0))
 | 
						|
 | 
						|
    let curr_col = len(ml[1])
 | 
						|
 | 
						|
    " If we can be sure that there is synchronization in the Erlang
 | 
						|
    " syntax, we use searchpair to make the script quicker.
 | 
						|
    if ml[2] ==# 'end' && exists('b:erlang_syntax_synced')
 | 
						|
 | 
						|
      let [lnum, col] = s:SearchEndPair(v:lnum, curr_col)
 | 
						|
 | 
						|
      if lnum ==# 0
 | 
						|
        return s:IndentError('Matching token for "end" not found',
 | 
						|
                            \'end', [])
 | 
						|
      else
 | 
						|
        call s:Log('    Tokenize for "end" <<<<')
 | 
						|
        let [lnum, indtokens] = s:TokenizeLine(lnum, 'up')
 | 
						|
        call s:Log('    >>>> Tokenize for "end"')
 | 
						|
 | 
						|
        let [success, i] = s:GetIndtokenAtCol(indtokens, col)
 | 
						|
        if !success | return i | endif
 | 
						|
        let [token, curr_vcol, curr_col] = indtokens[i]
 | 
						|
        call s:Log('    Match for "end" in line ' . lnum . ': ' .
 | 
						|
                   \string(indtokens[i]))
 | 
						|
        return curr_vcol
 | 
						|
      endif
 | 
						|
 | 
						|
    else
 | 
						|
 | 
						|
      call s:Log("  Line type = 'end'")
 | 
						|
      let new_col = s:ErlangCalcIndent(v:lnum - 1,
 | 
						|
                                      \[ml[2], 'align_to_begin_element'])
 | 
						|
    endif
 | 
						|
  else
 | 
						|
    call s:Log("  Line type = 'normal'")
 | 
						|
 | 
						|
    let new_col = s:ErlangCalcIndent(v:lnum - 1, [])
 | 
						|
    if currline =~# '^\s*when\>'
 | 
						|
      let new_col += 2
 | 
						|
    endif
 | 
						|
  endif
 | 
						|
 | 
						|
  if new_col < -1
 | 
						|
    call s:Log('WARNING: returning new_col == ' . new_col)
 | 
						|
    return g:erlang_unexpected_token_indent
 | 
						|
  endif
 | 
						|
 | 
						|
  return new_col
 | 
						|
 | 
						|
endfunction
 | 
						|
 | 
						|
" Cleanup {{{1
 | 
						|
" =======
 | 
						|
 | 
						|
let &cpo = s:cpo_save
 | 
						|
unlet s:cpo_save
 | 
						|
 | 
						|
" vim: sw=2 et fdm=marker
 |