blob: 625cfde0c1fa94491c3e5443851767e7b1c2904d [file] [log] [blame]
Bram Moolenaar3577c6f2008-06-24 21:16:56 +00001" Vim indent file
Bram Moolenaarad3b3662013-05-17 18:14:19 +02002" Language: Erlang (http://www.erlang.org)
Bram Moolenaar6be7f872012-01-20 21:08:56 +01003" 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>
Bram Moolenaar1d59aa12020-09-19 18:50:13 +02007" Last Update: 2020-Jun-11
Bram Moolenaar6be7f872012-01-20 21:08:56 +01008" License: Vim license
Bram Moolenaar1d59aa12020-09-19 18:50:13 +02009" URL: https://github.com/vim-erlang/vim-erlang-runtime
Bram Moolenaarad3b3662013-05-17 18:14:19 +020010
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" ==============
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000023
Bram Moolenaar6be7f872012-01-20 21:08:56 +010024" Only load this indent file when no other was loaded
Bram Moolenaarad3b3662013-05-17 18:14:19 +020025" Vim 7 or later is needed
26if exists("b:did_indent") || version < 700
27 finish
Bram Moolenaar6be7f872012-01-20 21:08:56 +010028else
Bram Moolenaarad3b3662013-05-17 18:14:19 +020029 let b:did_indent = 1
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000030endif
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000031
32setlocal indentexpr=ErlangIndent()
Bram Moolenaarad3b3662013-05-17 18:14:19 +020033setlocal indentkeys+=0=end,0=of,0=catch,0=after,0=when,0=),0=],0=},0=>>
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000034
Bram Moolenaar6be7f872012-01-20 21:08:56 +010035" Only define the functions once
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000036if exists("*ErlangIndent")
Bram Moolenaarad3b3662013-05-17 18:14:19 +020037 finish
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000038endif
39
Bram Moolenaarad3b3662013-05-17 18:14:19 +020040let s:cpo_save = &cpo
41set cpo&vim
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000042
Bram Moolenaarad3b3662013-05-17 18:14:19 +020043" Logging library {{{1
44" ===============
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000045
Bram Moolenaarad3b3662013-05-17 18:14:19 +020046" Purpose:
47" Logs the given string using the ErlangIndentLog function if it exists.
48" Parameters:
49" s: string
50function! s:Log(s)
51 if exists("*ErlangIndentLog")
52 call ErlangIndentLog(a:s)
53 endif
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000054endfunction
55
Bram Moolenaarad3b3662013-05-17 18:14:19 +020056" Line tokenizer library {{{1
57" ======================
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000058
Bram Moolenaar1d59aa12020-09-19 18:50:13 +020059" Indtokens are "indentation tokens". See their exact format in the
60" documentaiton of the s:GetTokensFromLine function.
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000061
Bram Moolenaarad3b3662013-05-17 18:14:19 +020062" 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
76function! s:CalcVCol(line, first_index, last_index, vcol, tabstop)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000077
Bram Moolenaarad3b3662013-05-17 18:14:19 +020078 " 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]
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000082
Bram Moolenaarad3b3662013-05-17 18:14:19 +020083 let i = 0
84 let last_index = a:last_index - a:first_index
85 let vcol = a:vcol
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000086
Bram Moolenaarad3b3662013-05-17 18:14:19 +020087 while 0 <= i && i <= last_index
88
Bram Moolenaar9d98fe92013-08-03 18:35:36 +020089 if line[i] ==# "\t"
Bram Moolenaarad3b3662013-05-17 18:14:19 +020090 " 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)
Bram Moolenaar6be7f872012-01-20 21:08:56 +0100103 else
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200104 let next_i = matchend(line, '[^\t]*', i + 1)
105 let vcol += next_i - i
106 call s:Log('new vcol after other: '. vcol)
Bram Moolenaar6be7f872012-01-20 21:08:56 +0100107 endif
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200108 let i = next_i
109 endwhile
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000110
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200111 return vcol
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000112endfunction
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200113
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]
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200123" 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)
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200127function! 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 = []
134
135 if a:string_continuation
136 let i = matchend(a:line, '^\%([^"\\]\|\\.\)*"', 0)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200137 if i ==# -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200138 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)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200146 if i ==# -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200147 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
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200160 if a:line[i] ==# ' '
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200161 let next_i = matchend(a:line, ' *', i + 1)
162
163 " Tabs
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200164 elseif a:line[i] ==# "\t"
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200165 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
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200171 elseif a:line[i] ==# '%'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200172 let next_i = linelen
173
174 " String token: "..."
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200175 elseif a:line[i] ==# '"'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200176 let next_i = matchend(a:line, '\%([^"\\]\|\\.\)*"', i + 1)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200177 if next_i ==# -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200178 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: '...'
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200185 elseif a:line[i] ==# "'"
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200186 let next_i = matchend(a:line, "\\%([^'\\\\]\\|\\\\.\\)*'", i + 1)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200187 if next_i ==# -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200188 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)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200202 elseif a:line[i] ==# '$'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200203 call add(indtokens, ['$.', vcol, i])
204 let next_i = i + 2
205
206 " Dot token: .
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200207 elseif a:line[i] ==# '.'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200208
209 let next_i = i + 1
210
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200211 if i + 1 ==# linelen || a:line[i + 1] =~# '[[:blank:]%]'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200212 " 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
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200223 elseif a:line[i] ==# '='
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200224 " 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
249 endif
250
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200251 if next_vcol ==# ''
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200252 let vcol += next_i - i
253 else
254 let vcol = next_vcol
255 endif
256
257 let i = next_i
258
259 endwhile
260
261 return indtokens
262
263endfunction
264
265" TODO: doc, handle "not found" case
266function! s:GetIndtokenAtCol(indtokens, col)
267 let i = 0
268 while i < len(a:indtokens)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200269 if a:indtokens[i][2] ==# a:col
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200270 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 \'', '')]
281endfunction
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
291function! s:Push(stack, token)
292 call s:Log(' Stack Push: "' . a:token . '" into ' . string(a:stack))
293 call insert(a:stack, a:token)
294endfunction
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
303function! s:Pop(stack)
304 let head = remove(a:stack, 0)
305 call s:Log(' Stack Pop: "' . head . '" from ' . string(a:stack))
306 return head
307endfunction
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.
314let s:all_tokens = {}
315let s:file_name = ''
316let 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.
321function! 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
329endfunction
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`
345function! s:TokenizeLine(lnum, direction)
346
347 call s:Log('Tokenizing starts from line ' . a:lnum)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200348 if a:direction ==# 'up'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200349 let lnum = prevnonblank(a:lnum)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200350 else " a:direction ==# 'down'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200351 let lnum = nextnonblank(a:lnum)
352 endif
353
354 " We hit the beginning or end of the file
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200355 if lnum ==# 0
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200356 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]
381endfunction
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:
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200391" 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
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200397function! s:FindIndToken(lnum, dir)
398 let lnum = a:lnum
399 while 1
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200400 let lnum += (a:dir ==# 'up' ? -1 : 1)
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200401 let [lnum, indtokens] = s:TokenizeLine(lnum, a:dir)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200402 if lnum ==# 0
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200403 " We hit the beginning or end of the file
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200404 return [[], 0, 0]
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200405 elseif !empty(indtokens)
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200406 " 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]
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200410 endif
411 endwhile
412endfunction
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
423function! 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
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200428 return [s:all_tokens[a:lnum][a:i - 1], a:lnum, a:i - 1]
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200429 else
430 return s:FindIndToken(a:lnum, 'up')
431 endif
432endfunction
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
443function! 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
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200448 return [s:all_tokens[a:lnum][a:i + 1], a:lnum, a:i + 1]
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200449 else
450 return s:FindIndToken(a:lnum, 'down')
451 endif
452endfunction
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
469function! 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
474endfunction
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
488function! 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
492endfunction
493
494if !exists('g:erlang_unexpected_token_indent')
495 let g:erlang_unexpected_token_indent = -1
496endif
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
509function! 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
515endfunction
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
527function! s:IsLineAtomContinuation(lnum)
528 if has('syntax_items')
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200529 let syn_name = synIDattr(synID(a:lnum, 1, 0), 'name')
530 return syn_name =~# '^erlangQuotedAtom' ||
531 \ syn_name =~# '^erlangQuotedRecord'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200532 else
533 return 0
534 endif
535endfunction
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
546function! s:IsCatchStandalone(lnum, i)
547 call s:Log(' IsCatchStandalone called: lnum=' . a:lnum . ', i=' . a:i)
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200548 let [prev_indtoken, _, _] = s:PrevIndToken(a:lnum, a:i)
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200549
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
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200557 if prev_token =~# '^[A-Z_@0-9]'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200558 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
588endfunction
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
604function! s:BeginElementFoundIfEmpty(stack, token, curr_vcol, stored_vcol, sw)
605 if empty(a:stack)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200606 if a:stored_vcol ==# -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200607 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
617endfunction
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
636function! 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
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200643 if a:stack[0] ==# a:end_token
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200644 call s:Log(' "' . a:token . '" pops "' . a:end_token . '"')
645 call s:Pop(a:stack)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200646 if !empty(a:stack) && a:stack[0] ==# 'align_to_begin_element'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200647 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
659endfunction
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
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200672" 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
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200675" Returns:
676" result: [should_return, indent]
677" should_return: bool -- if true, the caller should return `indent` to Vim
678" indent -- integer
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200679function! s:BeginningOfClauseFound(stack, token, stored_vcol, lnum, i)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200680 if !empty(a:stack) && a:stack[0] ==# 'when'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200681 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')
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200685 return [1, a:stored_vcol + shiftwidth() + 2]
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200686 else
687 return [1, s:UnexpectedToken(a:token, a:stack)]
688 endif
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200689 elseif !empty(a:stack) && a:stack[0] ==# '->'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200690 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')
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200694 return [1, a:stored_vcol + shiftwidth()]
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200695 elseif a:stack[0] ==# ';'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200696 call s:Pop(a:stack)
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200697
698 if !empty(a:stack)
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200699 return [1, s:UnexpectedToken(a:token, a:stack)]
700 endif
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200701
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
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200736 else
737 return [1, s:UnexpectedToken(a:token, a:stack)]
738 endif
739 else
740 return [0, 0]
741 endif
742endfunction
743
744let g:erlang_indent_searchpair_timeout = 2000
745
746" TODO
747function! 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]
756endfunction
757
758function! s:SearchEndPair(lnum, curr_col)
759 return s:SearchPair(
760 \ a:lnum, a:curr_col,
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200761 \ '\C\<\%(case\|try\|begin\|receive\|if\)\>\|' .
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200762 \ '\<fun\>\%(\s\|\n\|%.*$\|[A-Z_@][a-zA-Z_@]*\)*(',
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200763 \ '',
764 \ '\<end\>')
765endfunction
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"
780function! s:ErlangCalcIndent(lnum, stack)
781 let res = s:ErlangCalcIndent2(a:lnum, a:stack)
782 call s:Log("ErlangCalcIndent returned: " . res)
783 return res
784endfunction
785
786function! 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
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200802 if lnum ==# 0
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200803 let [ret, res] = s:BeginningOfClauseFound(stack, 'beginning_of_file',
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200804 \stored_vcol, 0, 0)
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200805 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
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200822 if token ==# '<end_of_clause>'
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200823 let [ret, res] = s:BeginningOfClauseFound(stack, token, stored_vcol,
824 \lnum, i)
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200825 if ret | return res | endif
826
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200827 if stored_vcol ==# -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200828 call s:Log(' End of clause directly preceeds LTI -> return')
829 return 0
830 else
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']
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200836 if token =~# '[a-zA-Z_@#]' ||
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200837 \ token ==# '<string>' || token ==# '<string_start>' ||
838 \ token ==# '<quoted_atom>' || token ==# '<quoted_atom_start>'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200839 call s:Log(' previous token found: curr_vcol + plus = ' .
840 \curr_vcol . " + " . plus)
841 return curr_vcol + plus
842 endif
843
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200844 elseif token ==# 'begin'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200845 let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200846 \stored_vcol, 'end', shiftwidth())
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200847 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 ||
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200861 \ (token ==# 'catch' && !s:IsCatchStandalone(lnum, i))) &&
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200862 \ !last_token_of_line &&
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200863 \ (empty(stack) || stack ==# ['when'] || stack ==# ['->'] ||
864 \ stack ==# ['->', ';'])
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200865
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
885
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')
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200897 return stored_vcol + shiftwidth()
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200898 elseif stack == ['when']
899 call s:Log(' LTI is in a guard after ' .
900 \'"of/receive/after/if/catch" -> return')
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200901 return stored_vcol + shiftwidth()
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200902 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
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200914 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')
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200920
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.
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200929 if token ==# 'case' || token ==# 'try' ||
930 \ (token ==# 'receive' && stack[0] ==# 'after')
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200931 call s:Pop(stack)
932 endif
933
934 if empty(stack)
935 call s:Log(' LTI is in a condition; matching ' .
936 \'"case/if/try/receive" found')
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200937 let stored_vcol = curr_vcol + shiftwidth()
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200938 elseif stack[0] ==# 'align_to_begin_element'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200939 call s:Pop(stack)
940 let stored_vcol = curr_vcol
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200941 elseif len(stack) > 1 && stack[0] ==# '->' && stack[1] ==# ';'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200942 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)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200946 let stored_vcol = curr_vcol + shiftwidth()
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200947 elseif stack[0] ==# '->'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200948 call s:Log(' LTI is in a branch; matching ' .
949 \'"case/if/try/receive" found')
950 call s:Pop(stack)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200951 let stored_vcol = curr_vcol + 2 * shiftwidth()
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200952 elseif stack[0] ==# 'when'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200953 call s:Log(' LTI is in a guard; matching ' .
954 \'"case/if/try/receive" found')
955 call s:Pop(stack)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200956 let stored_vcol = curr_vcol + 2 * shiftwidth() + 2
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200957 endif
958
959 endif
960
961 let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200962 \stored_vcol, 'end', shiftwidth())
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200963 if ret | return res | endif
964
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200965 elseif token ==# 'fun'
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200966 let [next_indtoken, next_lnum, next_i] = s:NextIndToken(lnum, i)
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200967 call s:Log(' Next indtoken = ' . string(next_indtoken))
968
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200969 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
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200978 if !empty(next_indtoken) && next_indtoken[0] ==# '('
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200979 " 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')
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200988 let stored_vcol = curr_vcol + shiftwidth()
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200989 elseif len(stack) > 1 && stack[0] ==# '->' && stack[1] ==# ';'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200990 call s:Log(' LTI is in a condition; matching "fun" found')
991 call s:Pop(stack)
992 call s:Pop(stack)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200993 elseif stack[0] ==# '->'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200994 call s:Log(' LTI is in a branch; matching "fun" found')
995 call s:Pop(stack)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200996 let stored_vcol = curr_vcol + 2 * shiftwidth()
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200997 elseif stack[0] ==# 'when'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200998 call s:Log(' LTI is in a guard; matching "fun" found')
999 call s:Pop(stack)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001000 let stored_vcol = curr_vcol + 2 * shiftwidth() + 2
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001001 endif
1002
1003 let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001004 \stored_vcol, 'end', shiftwidth())
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001005 if ret | return res | endif
1006 else
1007 " Pass: we have a function reference (e.g. "fun f/0")
1008 endif
1009
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001010 elseif token ==# '['
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001011 " Emacs compatibility
1012 let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
1013 \stored_vcol, ']', 1)
1014 if ret | return res | endif
1015
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001016 elseif token ==# '<<'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001017 " Emacs compatibility
1018 let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
1019 \stored_vcol, '>>', 2)
1020 if ret | return res | endif
1021
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001022 elseif token ==# '(' || token ==# '{'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001023
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001024 let end_token = (token ==# '(' ? ')' :
1025 \token ==# '{' ? '}' : 'error')
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001026
1027 if empty(stack)
1028 " We found the opening paren whose block contains the LTI.
1029 let mode = 'inside'
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001030 elseif stack[0] ==# end_token
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001031 call s:Log(' "' . token . '" pops "' . end_token . '"')
1032 call s:Pop(stack)
1033
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001034 if !empty(stack) && stack[0] ==# 'align_to_begin_element'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001035 " 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)
1045 endif
1046
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001047 if mode ==# 'inside' || mode ==# 'align_to_begin_element'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001048
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']
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001071 let plus = (mode ==# 'inside' ? 2 : 1)
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001072 call s:Log(' "' . token .
1073 \'" token found at end of line -> find previous token')
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001074 elseif mode ==# 'align_to_begin_element'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001075 " 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
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001098 elseif stored_vcol ==# -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001099 " 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
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001144 elseif index(['end', ')', ']', '}', '>>'], token) != -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001145
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001146 " 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 '>>'
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001155 elseif token ==# '>>'
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001156 call s:Push(stack, token)
1157
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001158 elseif token ==# 'end'
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001159 let [lnum_new, col_new] = s:SearchEndPair(lnum, curr_col)
1160
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001161 if lnum_new ==# 0
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001162 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]))
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001176 endif
1177
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001178 else " token is one of the following: ')', ']', '}'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001179
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001180 call s:Push(stack, token)
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001181
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001182 " We have to escape '[', because this string will be interpreted as a
1183 " regexp
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001184 let open_paren = (token ==# ')' ? '(' :
1185 \token ==# ']' ? '\[' :
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001186 \ '{')
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001187
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001188 let [lnum_new, col_new] = s:SearchPair(lnum, curr_col,
1189 \open_paren, '', token)
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001190
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001191 if lnum_new ==# 0
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001192 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
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001200
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001201 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]))
1206
1207 " Go back to the beginning of the loop and handle the opening paren
1208 continue
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001209 endif
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001210 endif
1211
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001212 elseif token ==# ';'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001213
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
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001235 elseif token ==# '->'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001236
1237 if empty(stack) && !last_token_of_line
1238 call s:Log(' LTI is in expression after arrow -> return')
1239 return stored_vcol
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001240 elseif empty(stack) || stack[0] ==# ';' || stack[0] ==# 'end'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001241 " 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
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001262 elseif token ==# 'when'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001263
1264 " Pop all ';' from the top of the stack
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001265 while !empty(stack) && stack[0] ==# ';'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001266 call s:Pop(stack)
1267 endwhile
1268
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,
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001278 \stored_vcol, shiftwidth())
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001279 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
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001303 elseif token ==# 'of' || token ==# 'after' ||
1304 \ (token ==# 'catch' && !s:IsCatchStandalone(lnum, i))
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001305
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001306 if token ==# 'after'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001307 " 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,
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001310 \stored_vcol, shiftwidth())
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001311 if ret | return res | endif
1312 endif
1313
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001314 if empty(stack) || stack[0] ==# '->' || stack[0] ==# 'when'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001315 call s:Push(stack, token)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001316 elseif stack[0] ==# 'catch' || stack[0] ==# 'after' || stack[0] ==# 'end'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001317 " 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
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001327 elseif token ==# '||' && empty(stack) && !last_token_of_line
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001328
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
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001337 if empty(stack) || stack[0] ==# '->' || stack[0] ==# 'when'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001338 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)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001342 elseif stack[0] ==# ';'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001343 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
1361 endif
1362
1363 let lnum -= 1
1364
1365 endwhile " iteration on lines
1366
1367endfunction
1368
1369" ErlangIndent function {{{1
1370" =====================
1371
1372function! 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
Bram Moolenaar1d59aa12020-09-19 18:50:13 +02001385 " 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
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001405 let ml = matchlist(currline,
1406 \'^\(\s*\)\(\%(end\|of\|catch\|after\)\>\|[)\]}]\|>>\)')
1407
1408 " If the line has a special beginning, but not a standalone catch
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001409 if !empty(ml) && !(ml[2] ==# 'catch' && s:IsCatchStandalone(v:lnum, 0))
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001410
1411 let curr_col = len(ml[1])
1412
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001413 " If we can be sure that there is synchronization in the Erlang
1414 " syntax, we use searchpair to make the script quicker.
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001415 if ml[2] ==# 'end' && exists('b:erlang_syntax_synced')
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001416
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001417 let [lnum, col] = s:SearchEndPair(v:lnum, curr_col)
1418
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001419 if lnum ==# 0
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001420 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
1435 else
1436
1437 call s:Log(" Line type = 'end'")
1438 let new_col = s:ErlangCalcIndent(v:lnum - 1,
1439 \[ml[2], 'align_to_begin_element'])
1440 endif
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
1447 endif
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
1456
1457endfunction
1458
Bram Moolenaar1d59aa12020-09-19 18:50:13 +02001459" ErlangShowTokensInLine functions {{{1
1460" ================================
1461
1462" These functions are useful during development.
1463
1464function! 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
1471endfunction
1472
1473function! ErlangShowTokensInCurrentLine()
1474 return ErlangShowTokensInLine(getline('.'))
1475endfunction
1476
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001477" Cleanup {{{1
1478" =======
1479
1480let &cpo = s:cpo_save
1481unlet s:cpo_save
1482
1483" vim: sw=2 et fdm=marker