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 "
|
|
1327 " Note about Emacs compabitility {{{
|
|
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
|