Mercurial > vim
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 |