blob: 416c40e9358f74bb2daf5f6a605b4eb3a1dfbf34 [file] [log] [blame]
Bram Moolenaar3577c6f2008-06-24 21:16:56 +00001" Vim indent file
Bram Moolenaarad3b3662013-05-17 18:14:19 +02002" Language: Erlang (http://www.erlang.org)
Bram Moolenaar6be7f872012-01-20 21:08:56 +01003" Author: Csaba Hoch <csaba.hoch@gmail.com>
4" Contributors: Edwin Fine <efine145_nospam01 at usa dot net>
5" Pawel 'kTT' Salata <rockplayer.pl@gmail.com>
6" Ricardo Catalinas Jiménez <jimenezrick@gmail.com>
Bram Moolenaarad3b3662013-05-17 18:14:19 +02007" Last Update: 2013-Mar-05
Bram Moolenaar6be7f872012-01-20 21:08:56 +01008" License: Vim license
Bram Moolenaarad3b3662013-05-17 18:14:19 +02009" 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" ==============
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000023
Bram Moolenaar6be7f872012-01-20 21:08:56 +010024" Only load this indent file when no other was loaded
Bram Moolenaarad3b3662013-05-17 18:14:19 +020025" Vim 7 or later is needed
26if exists("b:did_indent") || version < 700
27 finish
Bram Moolenaar6be7f872012-01-20 21:08:56 +010028else
Bram Moolenaarad3b3662013-05-17 18:14:19 +020029 let b:did_indent = 1
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000030endif
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000031
32setlocal indentexpr=ErlangIndent()
Bram Moolenaarad3b3662013-05-17 18:14:19 +020033setlocal indentkeys+=0=end,0=of,0=catch,0=after,0=when,0=),0=],0=},0=>>
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000034
Bram Moolenaar6be7f872012-01-20 21:08:56 +010035" Only define the functions once
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000036if exists("*ErlangIndent")
Bram Moolenaarad3b3662013-05-17 18:14:19 +020037 finish
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000038endif
39
Bram Moolenaarad3b3662013-05-17 18:14:19 +020040let s:cpo_save = &cpo
41set cpo&vim
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000042
Bram Moolenaarad3b3662013-05-17 18:14:19 +020043" Logging library {{{1
44" ===============
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000045
Bram Moolenaarad3b3662013-05-17 18:14:19 +020046" 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
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000054endfunction
55
Bram Moolenaarad3b3662013-05-17 18:14:19 +020056" Line tokenizer library {{{1
57" ======================
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000058
Bram Moolenaarad3b3662013-05-17 18:14:19 +020059" Indtokens are "indentation tokens".
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000060
Bram Moolenaarad3b3662013-05-17 18:14:19 +020061" 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)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000076
Bram Moolenaarad3b3662013-05-17 18:14:19 +020077 " 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]
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000081
Bram Moolenaarad3b3662013-05-17 18:14:19 +020082 let i = 0
83 let last_index = a:last_index - a:first_index
84 let vcol = a:vcol
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000085
Bram Moolenaarad3b3662013-05-17 18:14:19 +020086 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)
Bram Moolenaar6be7f872012-01-20 21:08:56 +0100102 else
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200103 let next_i = matchend(line, '[^\t]*', i + 1)
104 let vcol += next_i - i
105 call s:Log('new vcol after other: '. vcol)
Bram Moolenaar6be7f872012-01-20 21:08:56 +0100106 endif
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200107 let i = next_i
108 endwhile
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000109
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200110 return vcol
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000111endfunction
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200112
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 \ '\<\%(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 token == 'end'
1090 let [lnum_new, col_new] = s:SearchEndPair(lnum, curr_col)
1091
1092 if lnum_new == 0
1093 return s:IndentError('Matching token for "end" not found',
1094 \token, stack)
1095 else
1096 if lnum_new != lnum
1097 call s:Log(' Tokenize for "end" <<<<')
1098 let [lnum, indtokens] = s:TokenizeLine(lnum_new, 'up')
1099 call s:Log(' >>>> Tokenize for "end"')
1100 endif
1101
1102 let [success, i] = s:GetIndtokenAtCol(indtokens, col_new)
1103 if !success | return i | endif
1104 let [token, curr_vcol, curr_col] = indtokens[i]
1105 call s:Log(' Match for "end" in line ' . lnum_new . ': ' .
1106 \string(indtokens[i]))
1107 endif
1108
1109 elseif index([')', ']', '}'], token) != -1
1110
1111 call s:Push(stack, token)
1112
1113 " We have to escape '[', because this string will be interpreted as a
1114 " regexp
1115 let open_paren = (token == ')' ? '(' :
1116 \token == ']' ? '\[' :
1117 \ '{')
1118
1119 let [lnum_new, col_new] = s:SearchPair(lnum, curr_col,
1120 \open_paren, '', token)
1121
1122 if lnum_new == 0
1123 return s:IndentError('Matching token not found',
1124 \token, stack)
1125 else
1126 if lnum_new != lnum
1127 call s:Log(' Tokenize the opening paren <<<<')
1128 let [lnum, indtokens] = s:TokenizeLine(lnum_new, 'up')
1129 call s:Log(' >>>>')
1130 endif
1131
1132 let [success, i] = s:GetIndtokenAtCol(indtokens, col_new)
1133 if !success | return i | endif
1134 let [token, curr_vcol, curr_col] = indtokens[i]
1135 call s:Log(' Match in line ' . lnum_new . ': ' .
1136 \string(indtokens[i]))
1137
1138 " Go back to the beginning of the loop and handle the opening paren
1139 continue
1140 endif
1141
1142 elseif token == '>>'
1143 call s:Push(stack, token)
1144
1145 elseif token == ';'
1146
1147 if empty(stack)
1148 call s:Push(stack, ';')
1149 elseif index([';', '->', 'when', 'end', 'after', 'catch'],
1150 \stack[0]) != -1
1151 " Pass:
1152 "
1153 " - If the stack top is another ';', then one ';' is
1154 " enough.
1155 " - If the stack top is an '->' or a 'when', then we
1156 " should keep that, because they signify the type of the
1157 " LTI (branch, condition or guard).
1158 " - From the indentation point of view, the keyword
1159 " (of/catch/after/end) before the LTI is what counts, so
1160 " if the stack already has a catch/after/end, we don't
1161 " modify it. This way when we reach case/try/receive,
1162 " there will be at most one of/catch/after/end token in
1163 " the stack.
1164 else
1165 return s:UnexpectedToken(token, stack)
1166 endif
1167
1168 elseif token == '->'
1169
1170 if empty(stack) && !last_token_of_line
1171 call s:Log(' LTI is in expression after arrow -> return')
1172 return stored_vcol
1173 elseif empty(stack) || stack[0] == ';' || stack[0] == 'end'
1174 " stack = [';'] -> LTI is either a branch or in a guard
1175 " stack = ['->'] -> LTI is a condition
1176 " stack = ['->', ';'] -> LTI is a branch
1177 call s:Push(stack, '->')
1178 elseif index(['->', 'when', 'end', 'after', 'catch'], stack[0]) != -1
1179 " Pass:
1180 "
1181 " - If the stack top is another '->', then one '->' is
1182 " enough.
1183 " - If the stack top is a 'when', then we should keep
1184 " that, because this signifies that LTI is a in a guard.
1185 " - From the indentation point of view, the keyword
1186 " (of/catch/after/end) before the LTI is what counts, so
1187 " if the stack already has a catch/after/end, we don't
1188 " modify it. This way when we reach case/try/receive,
1189 " there will be at most one of/catch/after/end token in
1190 " the stack.
1191 else
1192 return s:UnexpectedToken(token, stack)
1193 endif
1194
1195 elseif token == 'when'
1196
1197 " Pop all ';' from the top of the stack
1198 while !empty(stack) && stack[0] == ';'
1199 call s:Pop(stack)
1200 endwhile
1201
1202 if empty(stack)
1203 if semicolon_abscol != ''
1204 let stored_vcol = semicolon_abscol
1205 endif
1206 if !last_token_of_line
1207 " Example:
1208 " when A,
1209 " LTI
1210 let [ret, res] = s:BeginElementFoundIfEmpty(stack, token, curr_vcol,
1211 \stored_vcol, &sw)
1212 if ret | return res | endif
1213 else
1214 " Example:
1215 " when
1216 " LTI
1217 call s:Push(stack, token)
1218 endif
1219 elseif index(['->', 'when', 'end', 'after', 'catch'], stack[0]) != -1
1220 " Pass:
1221 " - If the stack top is another 'when', then one 'when' is
1222 " enough.
1223 " - If the stack top is an '->' or a 'when', then we
1224 " should keep that, because they signify the type of the
1225 " LTI (branch, condition or guard).
1226 " - From the indentation point of view, the keyword
1227 " (of/catch/after/end) before the LTI is what counts, so
1228 " if the stack already has a catch/after/end, we don't
1229 " modify it. This way when we reach case/try/receive,
1230 " there will be at most one of/catch/after/end token in
1231 " the stack.
1232 else
1233 return s:UnexpectedToken(token, stack)
1234 endif
1235
1236 elseif token == 'of' || token == 'after' ||
1237 \ (token == 'catch' && !s:IsCatchStandalone(lnum, i))
1238
1239 if token == 'after'
1240 " If LTI is between an 'after' and the corresponding
1241 " 'end', then let's return
1242 let [ret, res] = s:BeginElementFoundIfEmpty(stack, token, curr_vcol,
1243 \stored_vcol, &sw)
1244 if ret | return res | endif
1245 endif
1246
1247 if empty(stack) || stack[0] == '->' || stack[0] == 'when'
1248 call s:Push(stack, token)
1249 elseif stack[0] == 'catch' || stack[0] == 'after' || stack[0] == 'end'
1250 " Pass: From the indentation point of view, the keyword
1251 " (of/catch/after/end) before the LTI is what counts, so
1252 " if the stack already has a catch/after/end, we don't
1253 " modify it. This way when we reach case/try/receive,
1254 " there will be at most one of/catch/after/end token in
1255 " the stack.
1256 else
1257 return s:UnexpectedToken(token, stack)
1258 endif
1259
1260 elseif token == '||' && empty(stack) && !last_token_of_line
1261
1262 call s:Log(' LTI is in expression after "||" -> return')
1263 return stored_vcol
1264
1265 else
1266 call s:Log(' Misc token, stack unchanged = ' . string(stack))
1267
1268 endif
1269
1270 if empty(stack) || stack[0] == '->' || stack[0] == 'when'
1271 let stored_vcol = curr_vcol
1272 let semicolon_abscol = ''
1273 call s:Log(' Misc token when the stack is empty or has "->" ' .
1274 \'-> setting stored_vcol to ' . stored_vcol)
1275 elseif stack[0] == ';'
1276 let semicolon_abscol = curr_vcol
1277 call s:Log(' Setting semicolon-stored_vcol to ' . stored_vcol)
1278 endif
1279
1280 let i -= 1
1281 call s:Log(' Token processed. stored_vcol=' . stored_vcol)
1282
1283 let last_token_of_line = 0
1284
1285 endwhile " iteration on tokens in a line
1286
1287 call s:Log(' Line analyzed. stored_vcol=' . stored_vcol)
1288
1289 if empty(stack) && stored_vcol != -1 &&
1290 \ (!empty(indtokens) && indtokens[0][0] != '<string_end>' &&
1291 \ indtokens[0][0] != '<quoted_atom_end>')
1292 call s:Log(' Empty stack at the beginning of the line -> return')
1293 return stored_vcol
1294 endif
1295
1296 let lnum -= 1
1297
1298 endwhile " iteration on lines
1299
1300endfunction
1301
1302" ErlangIndent function {{{1
1303" =====================
1304
1305function! ErlangIndent()
1306
1307 call s:ClearTokenCacheIfNeeded()
1308
1309 let currline = getline(v:lnum)
1310 call s:Log('Indenting line ' . v:lnum . ': ' . currline)
1311
1312 if s:IsLineStringContinuation(v:lnum) || s:IsLineAtomContinuation(v:lnum)
1313 call s:Log('String or atom continuation found -> ' .
1314 \'leaving indentation unchanged')
1315 return -1
1316 endif
1317
1318 let ml = matchlist(currline,
1319 \'^\(\s*\)\(\%(end\|of\|catch\|after\)\>\|[)\]}]\|>>\)')
1320
1321 " If the line has a special beginning, but not a standalone catch
1322 if !empty(ml) && !(ml[2] == 'catch' && s:IsCatchStandalone(v:lnum, 0))
1323
1324 let curr_col = len(ml[1])
1325
1326 if ml[2] == 'end'
1327 let [lnum, col] = s:SearchEndPair(v:lnum, curr_col)
1328
1329 if lnum == 0
1330 return s:IndentError('Matching token for "end" not found',
1331 \'end', [])
1332 else
1333 call s:Log(' Tokenize for "end" <<<<')
1334 let [lnum, indtokens] = s:TokenizeLine(lnum, 'up')
1335 call s:Log(' >>>> Tokenize for "end"')
1336
1337 let [success, i] = s:GetIndtokenAtCol(indtokens, col)
1338 if !success | return i | endif
1339 let [token, curr_vcol, curr_col] = indtokens[i]
1340 call s:Log(' Match for "end" in line ' . lnum . ': ' .
1341 \string(indtokens[i]))
1342 return curr_vcol
1343 endif
1344
1345 else
1346
1347 call s:Log(" Line type = 'end'")
1348 let new_col = s:ErlangCalcIndent(v:lnum - 1,
1349 \[ml[2], 'align_to_begin_element'])
1350 endif
1351 else
1352 call s:Log(" Line type = 'normal'")
1353
1354 let new_col = s:ErlangCalcIndent(v:lnum - 1, [])
1355 if currline =~# '^\s*when\>'
1356 let new_col += 2
1357 endif
1358 endif
1359
1360 if new_col < -1
1361 call s:Log('WARNING: returning new_col == ' . new_col)
1362 return g:erlang_unexpected_token_indent
1363 endif
1364
1365 return new_col
1366
1367endfunction
1368
1369" Cleanup {{{1
1370" =======
1371
1372let &cpo = s:cpo_save
1373unlet s:cpo_save
1374
1375" vim: sw=2 et fdm=marker