blob: c11bbeb4b7eff5cc96a16690a554d0ae703d3730 [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 Moolenaar203d04d2013-06-06 21:36:40 +02007" Last Update: 2013-Jun-01
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
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001089 elseif index(['end', ')', ']', '}', '>>'], token) != -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001090
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001091 " 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]))
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001121 endif
1122
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001123 else " token is one of the following: ')', ']', '}'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001124
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001125 call s:Push(stack, token)
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001126
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001127 " We have to escape '[', because this string will be interpreted as a
1128 " regexp
1129 let open_paren = (token == ')' ? '(' :
1130 \token == ']' ? '\[' :
1131 \ '{')
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001132
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001133 let [lnum_new, col_new] = s:SearchPair(lnum, curr_col,
1134 \open_paren, '', token)
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001135
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001136 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
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001145
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001146 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
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001154 endif
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001155 endif
1156
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001157 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
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001338 " 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
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001342 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