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