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