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