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