comparison runtime/indent/erlang.vim @ 4437:eb6ab7e78925

Update runtime files.
author Bram Moolenaar <bram@vim.org>
date Fri, 17 May 2013 18:14:19 +0200
parents af1e8a1714c2
children 2b11ac90d9e9
comparison
equal deleted inserted replaced
4436:a4fb812810e3 4437:eb6ab7e78925
1 " Vim indent file 1 " Vim indent file
2 " Language: Erlang 2 " Language: Erlang (http://www.erlang.org)
3 " Author: Csaba Hoch <csaba.hoch@gmail.com> 3 " Author: Csaba Hoch <csaba.hoch@gmail.com>
4 " Contributors: Edwin Fine <efine145_nospam01 at usa dot net> 4 " Contributors: Edwin Fine <efine145_nospam01 at usa dot net>
5 " Pawel 'kTT' Salata <rockplayer.pl@gmail.com> 5 " Pawel 'kTT' Salata <rockplayer.pl@gmail.com>
6 " Ricardo Catalinas Jiménez <jimenezrick@gmail.com> 6 " Ricardo Catalinas Jiménez <jimenezrick@gmail.com>
7 " Last Update: 2013-Mar-05
7 " License: Vim license 8 " License: Vim license
8 " Version: 2011/09/06 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 " ==============
9 23
10 " Only load this indent file when no other was loaded 24 " Only load this indent file when no other was loaded
11 if exists("b:did_indent") 25 " Vim 7 or later is needed
12 finish 26 if exists("b:did_indent") || version < 700
27 finish
13 else 28 else
14 let b:did_indent = 1 29 let b:did_indent = 1
15 endif 30 endif
16 31
17 setlocal indentexpr=ErlangIndent() 32 setlocal indentexpr=ErlangIndent()
18 setlocal indentkeys+==after,=end,=catch,=),=],=} 33 setlocal indentkeys+=0=end,0=of,0=catch,0=after,0=when,0=),0=],0=},0=>>
19 34
20 " Only define the functions once 35 " Only define the functions once
21 if exists("*ErlangIndent") 36 if exists("*ErlangIndent")
22 finish 37 finish
23 endif 38 endif
24 39
25 " The function goes through the whole line, analyses it and returns the 40 let s:cpo_save = &cpo
26 " indentation level. 41 set 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
50 function! s:Log(s)
51 if exists("*ErlangIndentLog")
52 call ErlangIndentLog(a:s)
53 endif
54 endfunction
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
75 function! 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
111 endfunction
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
125 function! 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
261 endfunction
262
263 " TODO: doc, handle "not found" case
264 function! 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 \'', '')]
279 endfunction
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
289 function! s:Push(stack, token)
290 call s:Log(' Stack Push: "' . a:token . '" into ' . string(a:stack))
291 call insert(a:stack, a:token)
292 endfunction
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
301 function! s:Pop(stack)
302 let head = remove(a:stack, 0)
303 call s:Log(' Stack Pop: "' . head . '" from ' . string(a:stack))
304 return head
305 endfunction
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.
312 let s:all_tokens = {}
313 let s:file_name = ''
314 let 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.
319 function! 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
327 endfunction
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`
343 function! 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]
379 endfunction
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
392 function! 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
404 endfunction
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
415 function! 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
424 endfunction
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
435 function! 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
444 endfunction
445
446 " ErlangCalcIndent helper functions {{{1
447 " =================================
448
449 " Purpose:
450 " This function is called when the parser encounters a syntax error.
27 " 451 "
28 " line: the line to be examined 452 " If we encounter a syntax error, we return
29 " return: the indentation level of the examined line 453 " g:erlang_unexpected_token_indent, which is -1 by default. This means that
30 function s:ErlangIndentAfterLine(line) 454 " the indentation of the LTI will not be changed.
31 let linelen = strlen(a:line) " the length of the line 455 " Parameter:
32 let i = 0 " the index of the current character in the line 456 " msg: string
33 let ind = 0 " how much should be the difference between the indentation of 457 " token: string
34 " the current line and the indentation of the next line? 458 " stack: [token]
35 " e.g. +1: the indentation of the next line should be equal to 459 " Returns:
36 " the indentation of the current line plus one shiftwidth 460 " indent: integer
37 let last_fun = 0 " the last token was a 'fun' 461 function! s:IndentError(msg, token, stack)
38 let last_receive = 0 " the last token was a 'receive'; needed for 'after' 462 call s:Log('Indent error: ' . a:msg . ' -> return')
39 let last_hash_sym = 0 " the last token was a '#' 463 call s:Log(' Token = ' . a:token . ', ' .
40 464 \' stack = ' . string(a:stack))
41 " Ignore comments 465 return g:erlang_unexpected_token_indent
42 if a:line =~# '^\s*%' 466 endfunction
43 return 0 467
44 endif 468 " Purpose:
45 469 " This function is called when the parser encounters an unexpected token,
46 " Partial function head where the guard is missing 470 " and the parser will return the number given back by UnexpectedToken.
47 if a:line =~# "\\(^\\l[[:alnum:]_]*\\)\\|\\(^'[^']\\+'\\)(" && a:line !~# '->' 471 "
48 return 2 472 " If we encounter an unexpected token, we return
49 endif 473 " g:erlang_unexpected_token_indent, which is -1 by default. This means that
50 474 " the indentation of the LTI will not be changed.
51 " The missing guard from the split function head 475 " Parameter:
52 if a:line =~# '^\s*when\s\+.*->' 476 " token: string
53 return -1 477 " stack: [token]
54 endif 478 " Returns:
55 479 " indent: integer
56 while 0<=i && i<linelen 480 function! s:UnexpectedToken(token, stack)
57 " m: the next value of the i 481 call s:Log(' Unexpected token ' . a:token . ', stack = ' .
58 if a:line[i] == '"' 482 \string(a:stack) . ' -> return')
59 let m = matchend(a:line,'"\%([^"\\]\|\\.\)*"',i) 483 return g:erlang_unexpected_token_indent
60 let last_receive = 0 484 endfunction
61 elseif a:line[i] == "'" 485
62 let m = matchend(a:line,"'[^']*'",i) 486 if !exists('g:erlang_unexpected_token_indent')
63 let last_receive = 0 487 let g:erlang_unexpected_token_indent = -1
64 elseif a:line[i] =~# "[a-z]" 488 endif
65 let m = matchend(a:line,".[[:alnum:]_]*",i) 489
66 if last_fun 490 " Purpose:
67 let ind = ind - 1 491 " Return whether the given line starts with a string continuation.
68 let last_fun = 0 492 " Parameter:
69 let last_receive = 0 493 " lnum: integer
70 elseif a:line[(i):(m-1)] =~# '^\%(case\|if\|try\)$' 494 " Returns:
71 let ind = ind + 1 495 " result: bool
72 elseif a:line[(i):(m-1)] =~# '^receive$' 496 " Example:
73 let ind = ind + 1 497 " f() -> % IsLineStringContinuation = false
74 let last_receive = 1 498 " "This is a % IsLineStringContinuation = false
75 elseif a:line[(i):(m-1)] =~# '^begin$' 499 " multiline % IsLineStringContinuation = true
76 let ind = ind + 2 500 " string". % IsLineStringContinuation = true
77 let last_receive = 0 501 function! s:IsLineStringContinuation(lnum)
78 elseif a:line[(i):(m-1)] =~# '^end$' 502 if has('syntax_items')
79 let ind = ind - 2 503 return synIDattr(synID(a:lnum, 1, 0), 'name') =~# '^erlangString'
80 let last_receive = 0 504 else
81 elseif a:line[(i):(m-1)] =~# '^after$' 505 return 0
82 if last_receive == 0 506 endif
83 let ind = ind - 1 507 endfunction
84 else 508
85 let ind = ind + 0 509 " Purpose:
86 endif 510 " Return whether the given line starts with an atom continuation.
87 let last_receive = 0 511 " Parameter:
88 elseif a:line[(i):(m-1)] =~# '^fun$' 512 " lnum: integer
89 let ind = ind + 1 513 " Returns:
90 let last_fun = 1 514 " result: bool
91 let last_receive = 0 515 " Example:
92 endif 516 " 'function with % IsLineAtomContinuation = true, but should be false
93 elseif a:line[i] =~# "[A-Z_]" 517 " weird name'() -> % IsLineAtomContinuation = true
94 let m = matchend(a:line,".[[:alnum:]_]*",i) 518 " ok. % IsLineAtomContinuation = false
95 let last_receive = 0 519 function! s:IsLineAtomContinuation(lnum)
96 elseif a:line[i] == '$' 520 if has('syntax_items')
97 let m = i+2 521 return synIDattr(synID(a:lnum, 1, 0), 'name') =~# '^erlangQuotedAtom'
98 let last_receive = 0 522 else
99 elseif a:line[i] == "." && (i+1>=linelen || a:line[i+1]!~ "[0-9]") 523 return 0
100 let m = i+1 524 endif
101 if last_hash_sym 525 endfunction
102 let last_hash_sym = 0 526
103 else 527 " Purpose:
104 let ind = ind - 1 528 " Return whether the 'catch' token (which should be the `i`th token in line
105 endif 529 " `lnum`) is standalone or part of a try-catch block, based on the preceding
106 let last_receive = 0 530 " token.
107 elseif a:line[i] == '-' && (i+1<linelen && a:line[i+1]=='>') 531 " Parameters:
108 let m = i+2 532 " lnum: integer
109 let ind = ind + 1 533 " i: integer
110 let last_receive = 0 534 " Return:
111 elseif a:line[i] == ';' && a:line[(i):(linelen)] !~# '.*->.*' 535 " is_standalone: bool
112 let m = i+1 536 function! s:IsCatchStandalone(lnum, i)
113 let ind = ind - 1 537 call s:Log(' IsCatchStandalone called: lnum=' . a:lnum . ', i=' . a:i)
114 let last_receive = 0 538 let prev_indtoken = s:PrevIndToken(a:lnum, a:i)
115 elseif a:line[i] == '#' 539
116 let m = i+1 540 " If we hit the beginning of the file, it is not a catch in a try block
117 let last_hash_sym = 1 541 if prev_indtoken == []
118 elseif a:line[i] =~# '[({[]' 542 return 1
119 let m = i+1 543 endif
120 let ind = ind + 1 544
121 let last_fun = 0 545 let prev_token = prev_indtoken[0]
122 let last_receive = 0 546
123 let last_hash_sym = 0 547 if prev_token =~# '[A-Z_@0-9]'
124 elseif a:line[i] =~# '[)}\]]' 548 let is_standalone = 0
125 let m = i+1 549 elseif prev_token =~# '[a-z]'
126 let ind = ind - 1 550 if index(['after', 'and', 'andalso', 'band', 'begin', 'bnot', 'bor', 'bsl',
127 let last_receive = 0 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
578 endfunction
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
594 function! 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
607 endfunction
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
626 function! 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
649 endfunction
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
666 function! 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
697 endfunction
698
699 let g:erlang_indent_searchpair_timeout = 2000
700
701 " TODO
702 function! 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]
711 endfunction
712
713 function! 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\>')
720 endfunction
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"
735 function! s:ErlangCalcIndent(lnum, stack)
736 let res = s:ErlangCalcIndent2(a:lnum, a:stack)
737 call s:Log("ErlangCalcIndent returned: " . res)
738 return res
739 endfunction
740
741 function! 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
128 else 784 else
129 let m = i+1 785 call s:Log(' End of clause (but not end of line) -> return')
786 return stored_vcol
130 endif 787 endif
131 788
132 let i = m 789 elseif stack == ['prev_term_plus']
133 endwhile 790 if token =~# '[a-zA-Z_@]' ||
134 791 \ token == '<string>' || token == '<string_start>' ||
135 return ind 792 \ token == '<quoted_atom>' || token == '<quoted_atom_start>'
136 endfunction 793 call s:Log(' previous token found: curr_vcol + plus = ' .
137 794 \curr_vcol . " + " . plus)
138 function s:FindPrevNonBlankNonComment(lnum) 795 return curr_vcol + plus
139 let lnum = prevnonblank(a:lnum)
140 let line = getline(lnum)
141 " Continue to search above if the current line begins with a '%'
142 while line =~# '^\s*%.*$'
143 let lnum = prevnonblank(lnum - 1)
144 if 0 == lnum
145 return 0
146 endif 796 endif
147 let line = getline(lnum) 797
148 endwhile 798 elseif token == 'begin'
149 return lnum 799 let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
150 endfunction 800 \stored_vcol, 'end', &sw)
151 801 if ret | return res | endif
152 " The function returns the indentation level of the line adjusted to a mutiple 802
153 " of 'shiftwidth' option. 803 " case EXPR of BRANCHES end
154 " 804 " try EXPR catch BRANCHES end
155 " lnum: line number 805 " try EXPR after BODY end
156 " return: the indentation level of the line 806 " try EXPR catch BRANCHES after BODY end
157 function s:GetLineIndent(lnum) 807 " try EXPR of BRANCHES catch BRANCHES end
158 return (indent(a:lnum) / &sw) * &sw 808 " try EXPR of BRANCHES after BODY end
159 endfunction 809 " try EXPR of BRANCHES catch BRANCHES after BODY end
160 810 " receive BRANCHES end
161 function ErlangIndent() 811 " receive BRANCHES after BRANCHES end
162 " Find a non-blank line above the current line 812
163 let lnum = prevnonblank(v:lnum - 1) 813 " This branch is not Emacs-compatible
164 814 elseif (index(['of', 'receive', 'after', 'if'], token) != -1 ||
165 " Hit the start of the file, use zero indent 815 \ (token == 'catch' && !s:IsCatchStandalone(lnum, i))) &&
166 if lnum == 0 816 \ !last_token_of_line &&
167 return 0 817 \ (empty(stack) || stack == ['when'] || stack == ['->'] ||
168 endif 818 \ stack == ['->', ';'])
169 819
170 let prevline = getline(lnum) 820 " If we are after of/receive, but these are not the last
171 let currline = getline(v:lnum) 821 " tokens of the line, we want to indent like this:
172 822 "
173 let ind_after = s:ErlangIndentAfterLine(prevline) 823 " % stack == []
174 if ind_after != 0 824 " receive stored_vcol,
175 let ind = s:GetLineIndent(lnum) + ind_after * &sw 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
1300 endfunction
1301
1302 " ErlangIndent function {{{1
1303 " =====================
1304
1305 function! 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
176 else 1345 else
177 let ind = indent(lnum) + ind_after * &sw 1346
178 endif 1347 call s:Log(" Line type = 'end'")
179 1348 let new_col = s:ErlangCalcIndent(v:lnum - 1,
180 " Special cases: 1349 \[ml[2], 'align_to_begin_element'])
181 if prevline =~# '^\s*\%(after\|end\)\>' 1350 endif
182 let ind = ind + 2*&sw 1351 else
183 endif 1352 call s:Log(" Line type = 'normal'")
184 if currline =~# '^\s*end\>' 1353
185 let ind = ind - 2*&sw 1354 let new_col = s:ErlangCalcIndent(v:lnum - 1, [])
186 endif 1355 if currline =~# '^\s*when\>'
187 if currline =~# '^\s*after\>' 1356 let new_col += 2
188 let plnum = s:FindPrevNonBlankNonComment(v:lnum-1) 1357 endif
189 if getline(plnum) =~# '^[^%]*\<receive\>\s*\%(%.*\)\=$' 1358 endif
190 " If the 'receive' is not in the same line as the 'after' 1359
191 let ind = ind - 1*&sw 1360 if new_col < -1
192 else 1361 call s:Log('WARNING: returning new_col == ' . new_col)
193 let ind = ind - 2*&sw 1362 return g:erlang_unexpected_token_indent
194 endif 1363 endif
195 endif 1364
196 if prevline =~# '^\s*[)}\]]' 1365 return new_col
197 let ind = ind + 1*&sw 1366
198 endif 1367 endfunction
199 if currline =~# '^\s*[)}\]]' 1368
200 let ind = ind - 1*&sw 1369 " Cleanup {{{1
201 endif 1370 " =======
202 if prevline =~# '^\s*\%(catch\)\s*\%(%\|$\)' 1371
203 let ind = ind + 1*&sw 1372 let &cpo = s:cpo_save
204 endif 1373 unlet s:cpo_save
205 if currline =~# '^\s*\%(catch\)\s*\%(%\|$\)' 1374
206 let ind = ind - 1*&sw 1375 " vim: sw=2 et fdm=marker
207 endif
208
209 if ind<0
210 let ind = 0
211 endif
212 return ind
213 endfunction