blob: 7aa38587a69e0478e81d862963cb9ce981d4e90c [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 Moolenaarf269eab2022-10-03 18:04:35 +01007" Last Update: 2022-Sep-06
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 Moolenaarf269eab2022-10-03 18:04:35 +010033setlocal indentkeys+=0=end,0=of,0=catch,0=after,0=else,0=when,0=),0=],0=},0=>>
34
35let b:undo_indent = "setl inde< indk<"
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000036
Bram Moolenaar6be7f872012-01-20 21:08:56 +010037" Only define the functions once
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000038if exists("*ErlangIndent")
Bram Moolenaarad3b3662013-05-17 18:14:19 +020039 finish
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000040endif
41
Bram Moolenaarad3b3662013-05-17 18:14:19 +020042let s:cpo_save = &cpo
43set cpo&vim
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000044
Bram Moolenaarad3b3662013-05-17 18:14:19 +020045" Logging library {{{1
46" ===============
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000047
Bram Moolenaarad3b3662013-05-17 18:14:19 +020048" Purpose:
49" Logs the given string using the ErlangIndentLog function if it exists.
50" Parameters:
51" s: string
52function! s:Log(s)
53 if exists("*ErlangIndentLog")
54 call ErlangIndentLog(a:s)
55 endif
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000056endfunction
57
Bram Moolenaarad3b3662013-05-17 18:14:19 +020058" Line tokenizer library {{{1
59" ======================
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000060
Bram Moolenaar1d59aa12020-09-19 18:50:13 +020061" Indtokens are "indentation tokens". See their exact format in the
Bram Moolenaar6c391a72021-09-09 21:55:11 +020062" documentation of the s:GetTokensFromLine function.
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000063
Bram Moolenaarad3b3662013-05-17 18:14:19 +020064" Purpose:
65" Calculate the new virtual column after the given segment of a line.
66" Parameters:
67" line: string
68" first_index: integer -- the index of the first character of the segment
69" last_index: integer -- the index of the last character of the segment
70" vcol: integer -- the virtual column of the first character of the token
71" tabstop: integer -- the value of the 'tabstop' option to be used
72" Returns:
73" vcol: integer
74" Example:
75" " index: 0 12 34567
76" " vcol: 0 45 89
77" s:CalcVCol("\t'\tx', b", 1, 4, 4) -> 10
78function! s:CalcVCol(line, first_index, last_index, vcol, tabstop)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000079
Bram Moolenaar6c391a72021-09-09 21:55:11 +020080 " We copy the relevant segment of the line, otherwise if the line were
Bram Moolenaarad3b3662013-05-17 18:14:19 +020081 " e.g. `"\t", term` then the else branch below would consume the `", term`
82 " part at once.
83 let line = a:line[a:first_index : a:last_index]
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000084
Bram Moolenaarad3b3662013-05-17 18:14:19 +020085 let i = 0
86 let last_index = a:last_index - a:first_index
87 let vcol = a:vcol
Bram Moolenaar3577c6f2008-06-24 21:16:56 +000088
Bram Moolenaarad3b3662013-05-17 18:14:19 +020089 while 0 <= i && i <= last_index
90
Bram Moolenaar9d98fe92013-08-03 18:35:36 +020091 if line[i] ==# "\t"
Bram Moolenaarad3b3662013-05-17 18:14:19 +020092 " Example (when tabstop == 4):
93 "
94 " vcol + tab -> next_vcol
95 " 0 + tab -> 4
96 " 1 + tab -> 4
97 " 2 + tab -> 4
98 " 3 + tab -> 4
99 " 4 + tab -> 8
100 "
101 " next_i - i == the number of tabs
102 let next_i = matchend(line, '\t*', i + 1)
103 let vcol = (vcol / a:tabstop + (next_i - i)) * a:tabstop
104 call s:Log('new vcol after tab: '. vcol)
Bram Moolenaar6be7f872012-01-20 21:08:56 +0100105 else
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200106 let next_i = matchend(line, '[^\t]*', i + 1)
107 let vcol += next_i - i
108 call s:Log('new vcol after other: '. vcol)
Bram Moolenaar6be7f872012-01-20 21:08:56 +0100109 endif
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200110 let i = next_i
111 endwhile
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000112
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200113 return vcol
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000114endfunction
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200115
116" Purpose:
117" Go through the whole line and return the tokens in the line.
118" Parameters:
119" line: string -- the line to be examined
120" string_continuation: bool
121" atom_continuation: bool
122" Returns:
123" indtokens = [indtoken]
124" indtoken = [token, vcol, col]
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200125" token = string (examples: 'begin', '<quoted_atom>', '}')
126" vcol = integer (the virtual column of the first character of the token;
127" counting starts from 0)
128" col = integer (counting starts from 0)
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200129function! s:GetTokensFromLine(line, string_continuation, atom_continuation,
130 \tabstop)
131
132 let linelen = strlen(a:line) " The length of the line
133 let i = 0 " The index of the current character in the line
134 let vcol = 0 " The virtual column of the current character
135 let indtokens = []
136
137 if a:string_continuation
138 let i = matchend(a:line, '^\%([^"\\]\|\\.\)*"', 0)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200139 if i ==# -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200140 call s:Log(' Whole line is string continuation -> ignore')
141 return []
142 else
143 let vcol = s:CalcVCol(a:line, 0, i - 1, 0, a:tabstop)
144 call add(indtokens, ['<string_end>', vcol, i])
145 endif
146 elseif a:atom_continuation
147 let i = matchend(a:line, "^\\%([^'\\\\]\\|\\\\.\\)*'", 0)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200148 if i ==# -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200149 call s:Log(' Whole line is quoted atom continuation -> ignore')
150 return []
151 else
152 let vcol = s:CalcVCol(a:line, 0, i - 1, 0, a:tabstop)
153 call add(indtokens, ['<quoted_atom_end>', vcol, i])
154 endif
155 endif
156
157 while 0 <= i && i < linelen
158
159 let next_vcol = ''
160
161 " Spaces
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200162 if a:line[i] ==# ' '
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200163 let next_i = matchend(a:line, ' *', i + 1)
164
165 " Tabs
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200166 elseif a:line[i] ==# "\t"
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200167 let next_i = matchend(a:line, '\t*', i + 1)
168
169 " See example in s:CalcVCol
170 let next_vcol = (vcol / a:tabstop + (next_i - i)) * a:tabstop
171
172 " Comment
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200173 elseif a:line[i] ==# '%'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200174 let next_i = linelen
175
176 " String token: "..."
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200177 elseif a:line[i] ==# '"'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200178 let next_i = matchend(a:line, '\%([^"\\]\|\\.\)*"', i + 1)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200179 if next_i ==# -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200180 call add(indtokens, ['<string_start>', vcol, i])
181 else
182 let next_vcol = s:CalcVCol(a:line, i, next_i - 1, vcol, a:tabstop)
183 call add(indtokens, ['<string>', vcol, i])
184 endif
185
186 " Quoted atom token: '...'
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200187 elseif a:line[i] ==# "'"
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200188 let next_i = matchend(a:line, "\\%([^'\\\\]\\|\\\\.\\)*'", i + 1)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200189 if next_i ==# -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200190 call add(indtokens, ['<quoted_atom_start>', vcol, i])
191 else
192 let next_vcol = s:CalcVCol(a:line, i, next_i - 1, vcol, a:tabstop)
193 call add(indtokens, ['<quoted_atom>', vcol, i])
194 endif
195
196 " Keyword or atom or variable token or number
197 elseif a:line[i] =~# '[a-zA-Z_@0-9]'
198 let next_i = matchend(a:line,
199 \'[[:alnum:]_@:]*\%(\s*#\s*[[:alnum:]_@:]*\)\=',
200 \i + 1)
201 call add(indtokens, [a:line[(i):(next_i - 1)], vcol, i])
202
203 " Character token: $<char> (as in: $a)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200204 elseif a:line[i] ==# '$'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200205 call add(indtokens, ['$.', vcol, i])
206 let next_i = i + 2
207
208 " Dot token: .
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200209 elseif a:line[i] ==# '.'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200210
211 let next_i = i + 1
212
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200213 if i + 1 ==# linelen || a:line[i + 1] =~# '[[:blank:]%]'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200214 " End of clause token: . (as in: f() -> ok.)
215 call add(indtokens, ['<end_of_clause>', vcol, i])
216
217 else
218 " Possibilities:
219 " - Dot token in float: . (as in: 3.14)
220 " - Dot token in record: . (as in: #myrec.myfield)
221 call add(indtokens, ['.', vcol, i])
222 endif
223
224 " Equal sign
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200225 elseif a:line[i] ==# '='
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200226 " This is handled separately so that "=<<" will be parsed as
227 " ['=', '<<'] instead of ['=<', '<']. Although Erlang parses it
228 " currently in the latter way, that may be fixed some day.
229 call add(indtokens, [a:line[i], vcol, i])
230 let next_i = i + 1
231
232 " Three-character tokens
233 elseif i + 1 < linelen &&
234 \ index(['=:=', '=/='], a:line[i : i + 1]) != -1
235 call add(indtokens, [a:line[i : i + 1], vcol, i])
236 let next_i = i + 2
237
238 " Two-character tokens
239 elseif i + 1 < linelen &&
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100240 \ index(['->', '<<', '>>', '||', '==', '/=', '=<', '>=', '?=', '++',
241 \ '--', '::'],
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200242 \ a:line[i : i + 1]) != -1
243 call add(indtokens, [a:line[i : i + 1], vcol, i])
244 let next_i = i + 2
245
246 " Other character: , ; < > ( ) [ ] { } # + - * / : ? = ! |
247 else
248 call add(indtokens, [a:line[i], vcol, i])
249 let next_i = i + 1
250
251 endif
252
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200253 if next_vcol ==# ''
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200254 let vcol += next_i - i
255 else
256 let vcol = next_vcol
257 endif
258
259 let i = next_i
260
261 endwhile
262
263 return indtokens
264
265endfunction
266
267" TODO: doc, handle "not found" case
268function! s:GetIndtokenAtCol(indtokens, col)
269 let i = 0
270 while i < len(a:indtokens)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200271 if a:indtokens[i][2] ==# a:col
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200272 return [1, i]
273 elseif a:indtokens[i][2] > a:col
274 return [0, s:IndentError('No token at col ' . a:col . ', ' .
275 \'indtokens = ' . string(a:indtokens),
276 \'', '')]
277 endif
278 let i += 1
279 endwhile
280 return [0, s:IndentError('No token at col ' . a:col . ', ' .
281 \'indtokens = ' . string(a:indtokens),
282 \'', '')]
283endfunction
284
285" Stack library {{{1
286" =============
287
288" Purpose:
289" Push a token onto the parser's stack.
290" Parameters:
291" stack: [token]
292" token: string
293function! s:Push(stack, token)
294 call s:Log(' Stack Push: "' . a:token . '" into ' . string(a:stack))
295 call insert(a:stack, a:token)
296endfunction
297
298" Purpose:
299" Pop a token from the parser's stack.
300" Parameters:
301" stack: [token]
302" token: string
303" Returns:
304" token: string -- the removed element
305function! s:Pop(stack)
306 let head = remove(a:stack, 0)
307 call s:Log(' Stack Pop: "' . head . '" from ' . string(a:stack))
308 return head
309endfunction
310
311" Library for accessing and storing tokenized lines {{{1
312" =================================================
313
314" The Erlang token cache: an `lnum -> indtokens` dictionary that stores the
315" tokenized lines.
316let s:all_tokens = {}
317let s:file_name = ''
318let s:last_changedtick = -1
319
320" Purpose:
321" Clear the Erlang token cache if we have a different file or the file has
322" been changed since the last indentation.
323function! s:ClearTokenCacheIfNeeded()
324 let file_name = expand('%:p')
325 if file_name != s:file_name ||
326 \ b:changedtick != s:last_changedtick
327 let s:file_name = file_name
328 let s:last_changedtick = b:changedtick
329 let s:all_tokens = {}
330 endif
331endfunction
332
333" Purpose:
334" Return the tokens of line `lnum`, if that line is not empty. If it is
335" empty, find the first non-empty line in the given `direction` and return
336" the tokens of that line.
337" Parameters:
338" lnum: integer
339" direction: 'up' | 'down'
340" Returns:
341" result: [] -- the result is an empty list if we hit the beginning or end
342" of the file
343" | [lnum, indtokens]
344" lnum: integer -- the index of the non-empty line that was found and
345" tokenized
346" indtokens: [indtoken] -- the tokens of line `lnum`
347function! s:TokenizeLine(lnum, direction)
348
349 call s:Log('Tokenizing starts from line ' . a:lnum)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200350 if a:direction ==# 'up'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200351 let lnum = prevnonblank(a:lnum)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200352 else " a:direction ==# 'down'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200353 let lnum = nextnonblank(a:lnum)
354 endif
355
356 " We hit the beginning or end of the file
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200357 if lnum ==# 0
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200358 let indtokens = []
359 call s:Log(' We hit the beginning or end of the file.')
360
361 " The line has already been parsed
362 elseif has_key(s:all_tokens, lnum)
363 let indtokens = s:all_tokens[lnum]
364 call s:Log('Cached line ' . lnum . ': ' . getline(lnum))
365 call s:Log(" Tokens in the line:\n - " . join(indtokens, "\n - "))
366
367 " The line should be parsed now
368 else
369
370 " Parse the line
371 let line = getline(lnum)
372 let string_continuation = s:IsLineStringContinuation(lnum)
373 let atom_continuation = s:IsLineAtomContinuation(lnum)
374 let indtokens = s:GetTokensFromLine(line, string_continuation,
375 \atom_continuation, &tabstop)
376 let s:all_tokens[lnum] = indtokens
377 call s:Log('Tokenizing line ' . lnum . ': ' . line)
378 call s:Log(" Tokens in the line:\n - " . join(indtokens, "\n - "))
379
380 endif
381
382 return [lnum, indtokens]
383endfunction
384
385" Purpose:
386" As a helper function for PrevIndToken and NextIndToken, the FindIndToken
387" function finds the first line with at least one token in the given
388" direction.
389" Parameters:
390" lnum: integer
391" direction: 'up' | 'down'
392" Returns:
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200393" result: [[], 0, 0]
394" -- the result is an empty list if we hit the beginning or end of
395" the file
396" | [indtoken, lnum, i]
397" -- the content, lnum and token index of the next (or previous)
398" indtoken
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200399function! s:FindIndToken(lnum, dir)
400 let lnum = a:lnum
401 while 1
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200402 let lnum += (a:dir ==# 'up' ? -1 : 1)
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200403 let [lnum, indtokens] = s:TokenizeLine(lnum, a:dir)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200404 if lnum ==# 0
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200405 " We hit the beginning or end of the file
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200406 return [[], 0, 0]
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200407 elseif !empty(indtokens)
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200408 " We found a non-empty line. If we were moving up, we return the last
409 " token of this line. Otherwise we return the first token if this line.
410 let i = (a:dir ==# 'up' ? len(indtokens) - 1 : 0)
411 return [indtokens[i], lnum, i]
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200412 endif
413 endwhile
414endfunction
415
416" Purpose:
417" Find the token that directly precedes the given token.
418" Parameters:
419" lnum: integer -- the line of the given token
420" i: the index of the given token within line `lnum`
421" Returns:
422" result = [] -- the result is an empty list if the given token is the first
423" token of the file
424" | indtoken
425function! s:PrevIndToken(lnum, i)
426 call s:Log(' PrevIndToken called: lnum=' . a:lnum . ', i =' . a:i)
427
428 " If the current line has a previous token, return that
429 if a:i > 0
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200430 return [s:all_tokens[a:lnum][a:i - 1], a:lnum, a:i - 1]
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200431 else
432 return s:FindIndToken(a:lnum, 'up')
433 endif
434endfunction
435
436" Purpose:
437" Find the token that directly succeeds the given token.
438" Parameters:
439" lnum: integer -- the line of the given token
440" i: the index of the given token within line `lnum`
441" Returns:
442" result = [] -- the result is an empty list if the given token is the last
443" token of the file
444" | indtoken
445function! s:NextIndToken(lnum, i)
446 call s:Log(' NextIndToken called: lnum=' . a:lnum . ', i =' . a:i)
447
448 " If the current line has a next token, return that
449 if len(s:all_tokens[a:lnum]) > a:i + 1
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200450 return [s:all_tokens[a:lnum][a:i + 1], a:lnum, a:i + 1]
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200451 else
452 return s:FindIndToken(a:lnum, 'down')
453 endif
454endfunction
455
456" ErlangCalcIndent helper functions {{{1
457" =================================
458
459" Purpose:
460" This function is called when the parser encounters a syntax error.
461"
462" If we encounter a syntax error, we return
463" g:erlang_unexpected_token_indent, which is -1 by default. This means that
464" the indentation of the LTI will not be changed.
465" Parameter:
466" msg: string
467" token: string
468" stack: [token]
469" Returns:
470" indent: integer
471function! s:IndentError(msg, token, stack)
472 call s:Log('Indent error: ' . a:msg . ' -> return')
473 call s:Log(' Token = ' . a:token . ', ' .
474 \' stack = ' . string(a:stack))
475 return g:erlang_unexpected_token_indent
476endfunction
477
478" Purpose:
479" This function is called when the parser encounters an unexpected token,
480" and the parser will return the number given back by UnexpectedToken.
481"
482" If we encounter an unexpected token, we return
483" g:erlang_unexpected_token_indent, which is -1 by default. This means that
484" the indentation of the LTI will not be changed.
485" Parameter:
486" token: string
487" stack: [token]
488" Returns:
489" indent: integer
490function! s:UnexpectedToken(token, stack)
491 call s:Log(' Unexpected token ' . a:token . ', stack = ' .
492 \string(a:stack) . ' -> return')
493 return g:erlang_unexpected_token_indent
494endfunction
495
496if !exists('g:erlang_unexpected_token_indent')
497 let g:erlang_unexpected_token_indent = -1
498endif
499
500" Purpose:
501" Return whether the given line starts with a string continuation.
502" Parameter:
503" lnum: integer
504" Returns:
505" result: bool
506" Example:
507" f() -> % IsLineStringContinuation = false
508" "This is a % IsLineStringContinuation = false
509" multiline % IsLineStringContinuation = true
510" string". % IsLineStringContinuation = true
511function! s:IsLineStringContinuation(lnum)
512 if has('syntax_items')
513 return synIDattr(synID(a:lnum, 1, 0), 'name') =~# '^erlangString'
514 else
515 return 0
516 endif
517endfunction
518
519" Purpose:
520" Return whether the given line starts with an atom continuation.
521" Parameter:
522" lnum: integer
523" Returns:
524" result: bool
525" Example:
526" 'function with % IsLineAtomContinuation = true, but should be false
527" weird name'() -> % IsLineAtomContinuation = true
528" ok. % IsLineAtomContinuation = false
529function! s:IsLineAtomContinuation(lnum)
530 if has('syntax_items')
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200531 let syn_name = synIDattr(synID(a:lnum, 1, 0), 'name')
532 return syn_name =~# '^erlangQuotedAtom' ||
533 \ syn_name =~# '^erlangQuotedRecord'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200534 else
535 return 0
536 endif
537endfunction
538
539" Purpose:
540" Return whether the 'catch' token (which should be the `i`th token in line
541" `lnum`) is standalone or part of a try-catch block, based on the preceding
542" token.
543" Parameters:
544" lnum: integer
545" i: integer
546" Return:
547" is_standalone: bool
548function! s:IsCatchStandalone(lnum, i)
549 call s:Log(' IsCatchStandalone called: lnum=' . a:lnum . ', i=' . a:i)
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200550 let [prev_indtoken, _, _] = s:PrevIndToken(a:lnum, a:i)
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200551
552 " If we hit the beginning of the file, it is not a catch in a try block
553 if prev_indtoken == []
554 return 1
555 endif
556
557 let prev_token = prev_indtoken[0]
558
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200559 if prev_token =~# '^[A-Z_@0-9]'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200560 let is_standalone = 0
561 elseif prev_token =~# '[a-z]'
562 if index(['after', 'and', 'andalso', 'band', 'begin', 'bnot', 'bor', 'bsl',
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100563 \ 'bsr', 'bxor', 'case', 'catch', 'div', 'maybe', 'not', 'or',
564 \ 'orelse', 'rem', 'try', 'xor'], prev_token) != -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200565 " If catch is after these keywords, it is standalone
566 let is_standalone = 1
567 else
568 " If catch is after another keyword (e.g. 'end') or an atom, it is
569 " part of try-catch.
570 "
571 " Keywords:
572 " - may precede 'catch': end
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100573 " - may not precede 'catch': else fun if of receive when
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200574 " - unused: cond let query
575 let is_standalone = 0
576 endif
577 elseif index([')', ']', '}', '<string>', '<string_end>', '<quoted_atom>',
578 \ '<quoted_atom_end>', '$.'], prev_token) != -1
579 let is_standalone = 0
580 else
581 " This 'else' branch includes the following tokens:
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100582 " -> == /= =< < >= > ?= =:= =/= + - * / ++ -- :: < > ; ( [ { ? = ! . |
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200583 let is_standalone = 1
584 endif
585
586 call s:Log(' "catch" preceded by "' . prev_token . '" -> catch ' .
587 \(is_standalone ? 'is standalone' : 'belongs to try-catch'))
588 return is_standalone
589
590endfunction
591
592" Purpose:
593" This function is called when a begin-type element ('begin', 'case',
594" '[', '<<', etc.) is found. It asks the caller to return if the stack
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100595" if already empty.
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200596" Parameters:
597" stack: [token]
598" token: string
599" curr_vcol: integer
600" stored_vcol: integer
601" sw: integer -- number of spaces to be used after the begin element as
602" indentation
603" Returns:
604" result: [should_return, indent]
605" should_return: bool -- if true, the caller should return `indent` to Vim
606" indent -- integer
607function! s:BeginElementFoundIfEmpty(stack, token, curr_vcol, stored_vcol, sw)
608 if empty(a:stack)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200609 if a:stored_vcol ==# -1
Bram Moolenaar6c391a72021-09-09 21:55:11 +0200610 call s:Log(' "' . a:token . '" directly precedes LTI -> return')
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200611 return [1, a:curr_vcol + a:sw]
612 else
613 call s:Log(' "' . a:token .
614 \'" token (whose expression includes LTI) found -> return')
615 return [1, a:stored_vcol]
616 endif
617 else
618 return [0, 0]
619 endif
620endfunction
621
622" Purpose:
623" This function is called when a begin-type element ('begin', 'case', '[',
624" '<<', etc.) is found, and in some cases when 'after' and 'when' is found.
625" It asks the caller to return if the stack is already empty.
626" Parameters:
627" stack: [token]
628" token: string
629" curr_vcol: integer
630" stored_vcol: integer
631" end_token: end token that belongs to the begin element found (e.g. if the
632" begin element is 'begin', the end token is 'end')
633" sw: integer -- number of spaces to be used after the begin element as
634" indentation
635" Returns:
636" result: [should_return, indent]
637" should_return: bool -- if true, the caller should return `indent` to Vim
638" indent -- integer
639function! s:BeginElementFound(stack, token, curr_vcol, stored_vcol, end_token, sw)
640
641 " Return 'return' if the stack is empty
642 let [ret, res] = s:BeginElementFoundIfEmpty(a:stack, a:token, a:curr_vcol,
643 \a:stored_vcol, a:sw)
644 if ret | return [ret, res] | endif
645
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200646 if a:stack[0] ==# a:end_token
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200647 call s:Log(' "' . a:token . '" pops "' . a:end_token . '"')
648 call s:Pop(a:stack)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200649 if !empty(a:stack) && a:stack[0] ==# 'align_to_begin_element'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200650 call s:Pop(a:stack)
651 if empty(a:stack)
652 return [1, a:curr_vcol]
653 else
654 return [1, s:UnexpectedToken(a:token, a:stack)]
655 endif
656 else
657 return [0, 0]
658 endif
659 else
660 return [1, s:UnexpectedToken(a:token, a:stack)]
661 endif
662endfunction
663
664" Purpose:
665" This function is called when we hit the beginning of a file or an
666" end-of-clause token -- i.e. when we found the beginning of the current
667" clause.
668"
669" If the stack contains an '->' or 'when', this means that we can return
670" now, since we were looking for the beginning of the clause.
671" Parameters:
672" stack: [token]
673" token: string
674" stored_vcol: integer
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200675" lnum: the line number of the "end of clause" mark (or 0 if we hit the
676" beginning of the file)
677" i: the index of the "end of clause" token within its own line
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200678" Returns:
679" result: [should_return, indent]
680" should_return: bool -- if true, the caller should return `indent` to Vim
681" indent -- integer
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200682function! s:BeginningOfClauseFound(stack, token, stored_vcol, lnum, i)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200683 if !empty(a:stack) && a:stack[0] ==# 'when'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200684 call s:Log(' BeginningOfClauseFound: "when" found in stack')
685 call s:Pop(a:stack)
686 if empty(a:stack)
687 call s:Log(' Stack is ["when"], so LTI is in a guard -> return')
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200688 return [1, a:stored_vcol + shiftwidth() + 2]
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200689 else
690 return [1, s:UnexpectedToken(a:token, a:stack)]
691 endif
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200692 elseif !empty(a:stack) && a:stack[0] ==# '->'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200693 call s:Log(' BeginningOfClauseFound: "->" found in stack')
694 call s:Pop(a:stack)
695 if empty(a:stack)
696 call s:Log(' Stack is ["->"], so LTI is in function body -> return')
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200697 return [1, a:stored_vcol + shiftwidth()]
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200698 elseif a:stack[0] ==# ';'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200699 call s:Pop(a:stack)
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200700
701 if !empty(a:stack)
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200702 return [1, s:UnexpectedToken(a:token, a:stack)]
703 endif
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200704
705 if a:lnum ==# 0
706 " Set lnum and i to be NextIndToken-friendly
707 let lnum = 1
708 let i = -1
709 else
710 let lnum = a:lnum
711 let i = a:i
712 endif
713
714 " Are we after a "-spec func() ...;" clause?
715 let [next1_indtoken, next1_lnum, next1_i] = s:NextIndToken(lnum, i)
716 if !empty(next1_indtoken) && next1_indtoken[0] =~# '-'
717 let [next2_indtoken, next2_lnum, next2_i] =
718 \s:NextIndToken(next1_lnum, next1_i)
719 if !empty(next2_indtoken) && next2_indtoken[0] =~# 'spec'
720 let [next3_indtoken, next3_lnum, next3_i] =
721 \s:NextIndToken(next2_lnum, next2_i)
722 if !empty(next3_indtoken)
723 let [next4_indtoken, next4_lnum, next4_i] =
724 \s:NextIndToken(next3_lnum, next3_i)
725 if !empty(next4_indtoken)
726 " Yes, we are.
727 call s:Log(' Stack is ["->", ";"], so LTI is in a "-spec" ' .
728 \'attribute -> return')
729 return [1, next4_indtoken[1]]
730 endif
731 endif
732 endif
733 endif
734
735 call s:Log(' Stack is ["->", ";"], so LTI is in a function head ' .
736 \'-> return')
737 return [1, a:stored_vcol]
738
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200739 else
740 return [1, s:UnexpectedToken(a:token, a:stack)]
741 endif
742 else
743 return [0, 0]
744 endif
745endfunction
746
747let g:erlang_indent_searchpair_timeout = 2000
748
749" TODO
750function! s:SearchPair(lnum, curr_col, start, middle, end)
751 call cursor(a:lnum, a:curr_col + 1)
752 let [lnum_new, col1_new] =
753 \searchpairpos(a:start, a:middle, a:end, 'bW',
754 \'synIDattr(synID(line("."), col("."), 0), "name") ' .
755 \'=~? "string\\|quotedatom\\|todo\\|comment\\|' .
756 \'erlangmodifier"',
757 \0, g:erlang_indent_searchpair_timeout)
758 return [lnum_new, col1_new - 1]
759endfunction
760
761function! s:SearchEndPair(lnum, curr_col)
762 return s:SearchPair(
763 \ a:lnum, a:curr_col,
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100764 \ '\C\<\%(case\|try\|begin\|receive\|if\|maybe\)\>\|' .
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200765 \ '\<fun\>\%(\s\|\n\|%.*$\|[A-Z_@][a-zA-Z_@]*\)*(',
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200766 \ '',
767 \ '\<end\>')
768endfunction
769
770" ErlangCalcIndent {{{1
771" ================
772
773" Purpose:
774" Calculate the indentation of the given line.
775" Parameters:
776" lnum: integer -- index of the line for which the indentation should be
777" calculated
778" stack: [token] -- initial stack
779" Return:
780" indent: integer -- if -1, that means "don't change the indentation";
781" otherwise it means "indent the line with `indent`
782" number of spaces or equivalent tabs"
783function! s:ErlangCalcIndent(lnum, stack)
784 let res = s:ErlangCalcIndent2(a:lnum, a:stack)
785 call s:Log("ErlangCalcIndent returned: " . res)
786 return res
787endfunction
788
789function! s:ErlangCalcIndent2(lnum, stack)
790
791 let lnum = a:lnum
792 let stored_vcol = -1 " Virtual column of the first character of the token that
793 " we currently think we might align to.
794 let mode = 'normal'
795 let stack = a:stack
796 let semicolon_abscol = ''
797
798 " Walk through the lines of the buffer backwards (starting from the
799 " previous line) until we can decide how to indent the current line.
800 while 1
801
802 let [lnum, indtokens] = s:TokenizeLine(lnum, 'up')
803
804 " Hit the start of the file
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200805 if lnum ==# 0
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200806 let [ret, res] = s:BeginningOfClauseFound(stack, 'beginning_of_file',
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200807 \stored_vcol, 0, 0)
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200808 if ret | return res | endif
809
810 return 0
811 endif
812
813 let i = len(indtokens) - 1
814 let last_token_of_line = 1
815
816 while i >= 0
817
818 let [token, curr_vcol, curr_col] = indtokens[i]
819 call s:Log(' Analyzing the following token: ' . string(indtokens[i]))
820
821 if len(stack) > 256 " TODO: magic number
822 return s:IndentError('Stack too long', token, stack)
823 endif
824
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200825 if token ==# '<end_of_clause>'
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200826 let [ret, res] = s:BeginningOfClauseFound(stack, token, stored_vcol,
827 \lnum, i)
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200828 if ret | return res | endif
829
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200830 if stored_vcol ==# -1
Bram Moolenaar6c391a72021-09-09 21:55:11 +0200831 call s:Log(' End of clause directly precedes LTI -> return')
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200832 return 0
833 else
834 call s:Log(' End of clause (but not end of line) -> return')
835 return stored_vcol
836 endif
837
838 elseif stack == ['prev_term_plus']
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200839 if token =~# '[a-zA-Z_@#]' ||
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200840 \ token ==# '<string>' || token ==# '<string_start>' ||
841 \ token ==# '<quoted_atom>' || token ==# '<quoted_atom_start>'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200842 call s:Log(' previous token found: curr_vcol + plus = ' .
843 \curr_vcol . " + " . plus)
844 return curr_vcol + plus
845 endif
846
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200847 elseif token ==# 'begin'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200848 let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200849 \stored_vcol, 'end', shiftwidth())
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200850 if ret | return res | endif
851
852 " case EXPR of BRANCHES end
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100853 " if BRANCHES end
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200854 " try EXPR catch BRANCHES end
855 " try EXPR after BODY end
856 " try EXPR catch BRANCHES after BODY end
857 " try EXPR of BRANCHES catch BRANCHES end
858 " try EXPR of BRANCHES after BODY end
859 " try EXPR of BRANCHES catch BRANCHES after BODY end
860 " receive BRANCHES end
861 " receive BRANCHES after BRANCHES end
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100862 " maybe EXPR end
863 " maybe EXPR else BRANCHES end
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200864
865 " This branch is not Emacs-compatible
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100866 elseif (index(['of', 'receive', 'after', 'if', 'else'], token) != -1 ||
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200867 \ (token ==# 'catch' && !s:IsCatchStandalone(lnum, i))) &&
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200868 \ !last_token_of_line &&
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200869 \ (empty(stack) || stack ==# ['when'] || stack ==# ['->'] ||
870 \ stack ==# ['->', ';'])
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200871
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100872 " If we are after of/receive/etc, but these are not the last
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200873 " tokens of the line, we want to indent like this:
874 "
875 " % stack == []
876 " receive stored_vcol,
877 " LTI
878 "
879 " % stack == ['->', ';']
880 " receive stored_vcol ->
881 " B;
882 " LTI
883 "
884 " % stack == ['->']
885 " receive stored_vcol ->
886 " LTI
887 "
888 " % stack == ['when']
889 " receive stored_vcol when
890 " LTI
891
892 " stack = [] => LTI is a condition
893 " stack = ['->'] => LTI is a branch
894 " stack = ['->', ';'] => LTI is a condition
895 " stack = ['when'] => LTI is a guard
896 if empty(stack) || stack == ['->', ';']
897 call s:Log(' LTI is in a condition after ' .
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100898 \'"of/receive/after/if/else/catch" -> return')
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200899 return stored_vcol
900 elseif stack == ['->']
901 call s:Log(' LTI is in a branch after ' .
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100902 \'"of/receive/after/if/else/catch" -> return')
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200903 return stored_vcol + shiftwidth()
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200904 elseif stack == ['when']
905 call s:Log(' LTI is in a guard after ' .
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100906 \'"of/receive/after/if/else/catch" -> return')
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200907 return stored_vcol + shiftwidth()
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200908 else
909 return s:UnexpectedToken(token, stack)
910 endif
911
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100912 elseif index(['case', 'if', 'try', 'receive', 'maybe'], token) != -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200913
914 " stack = [] => LTI is a condition
915 " stack = ['->'] => LTI is a branch
916 " stack = ['->', ';'] => LTI is a condition
917 " stack = ['when'] => LTI is in a guard
918 if empty(stack)
919 " pass
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200920 elseif (token ==# 'case' && stack[0] ==# 'of') ||
921 \ (token ==# 'if') ||
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100922 \ (token ==# 'maybe' && stack[0] ==# 'else') ||
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200923 \ (token ==# 'try' && (stack[0] ==# 'of' ||
924 \ stack[0] ==# 'catch' ||
925 \ stack[0] ==# 'after')) ||
926 \ (token ==# 'receive')
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200927
928 " From the indentation point of view, the keyword
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100929 " (of/catch/after/else/end) before the LTI is what counts, so
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200930 " when we reached these tokens, and the stack already had
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100931 " a catch/after/else/end, we didn't modify it.
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200932 "
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100933 " This way when we reach case/try/receive/maybe (i.e. now),
934 " there is at most one of/catch/after/else/end token in the
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200935 " stack.
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200936 if token ==# 'case' || token ==# 'try' ||
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100937 \ (token ==# 'receive' && stack[0] ==# 'after') ||
938 \ (token ==# 'maybe' && stack[0] ==# 'else')
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200939 call s:Pop(stack)
940 endif
941
942 if empty(stack)
943 call s:Log(' LTI is in a condition; matching ' .
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100944 \'"case/if/try/receive/maybe" found')
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200945 let stored_vcol = curr_vcol + shiftwidth()
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200946 elseif stack[0] ==# 'align_to_begin_element'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200947 call s:Pop(stack)
948 let stored_vcol = curr_vcol
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200949 elseif len(stack) > 1 && stack[0] ==# '->' && stack[1] ==# ';'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200950 call s:Log(' LTI is in a condition; matching ' .
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100951 \'"case/if/try/receive/maybe" found')
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200952 call s:Pop(stack)
953 call s:Pop(stack)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200954 let stored_vcol = curr_vcol + shiftwidth()
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200955 elseif stack[0] ==# '->'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200956 call s:Log(' LTI is in a branch; matching ' .
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100957 \'"case/if/try/receive/maybe" found')
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200958 call s:Pop(stack)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200959 let stored_vcol = curr_vcol + 2 * shiftwidth()
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200960 elseif stack[0] ==# 'when'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200961 call s:Log(' LTI is in a guard; matching ' .
Bram Moolenaarf269eab2022-10-03 18:04:35 +0100962 \'"case/if/try/receive/maybe" found')
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200963 call s:Pop(stack)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200964 let stored_vcol = curr_vcol + 2 * shiftwidth() + 2
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200965 endif
966
967 endif
968
969 let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200970 \stored_vcol, 'end', shiftwidth())
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200971 if ret | return res | endif
972
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200973 elseif token ==# 'fun'
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200974 let [next_indtoken, next_lnum, next_i] = s:NextIndToken(lnum, i)
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200975 call s:Log(' Next indtoken = ' . string(next_indtoken))
976
Bram Moolenaar1d59aa12020-09-19 18:50:13 +0200977 if !empty(next_indtoken) && next_indtoken[0] =~# '^[A-Z_@]'
978 " The "fun" is followed by a variable, so we might have a named fun:
979 " "fun Fun() -> ok end". Thus we take the next token to decide
980 " whether this is a function definition ("fun()") or just a function
981 " reference ("fun Mod:Fun").
982 let [next_indtoken, _, _] = s:NextIndToken(next_lnum, next_i)
983 call s:Log(' Next indtoken = ' . string(next_indtoken))
984 endif
985
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200986 if !empty(next_indtoken) && next_indtoken[0] ==# '('
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200987 " We have an anonymous function definition
988 " (e.g. "fun () -> ok end")
989
990 " stack = [] => LTI is a condition
991 " stack = ['->'] => LTI is a branch
992 " stack = ['->', ';'] => LTI is a condition
993 " stack = ['when'] => LTI is in a guard
994 if empty(stack)
995 call s:Log(' LTI is in a condition; matching "fun" found')
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200996 let stored_vcol = curr_vcol + shiftwidth()
Bram Moolenaar9d98fe92013-08-03 18:35:36 +0200997 elseif len(stack) > 1 && stack[0] ==# '->' && stack[1] ==# ';'
Bram Moolenaarad3b3662013-05-17 18:14:19 +0200998 call s:Log(' LTI is in a condition; matching "fun" found')
999 call s:Pop(stack)
1000 call s:Pop(stack)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001001 elseif stack[0] ==# '->'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001002 call s:Log(' LTI is in a branch; matching "fun" found')
1003 call s:Pop(stack)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001004 let stored_vcol = curr_vcol + 2 * shiftwidth()
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001005 elseif stack[0] ==# 'when'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001006 call s:Log(' LTI is in a guard; matching "fun" found')
1007 call s:Pop(stack)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001008 let stored_vcol = curr_vcol + 2 * shiftwidth() + 2
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001009 endif
1010
1011 let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001012 \stored_vcol, 'end', shiftwidth())
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001013 if ret | return res | endif
1014 else
1015 " Pass: we have a function reference (e.g. "fun f/0")
1016 endif
1017
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001018 elseif token ==# '['
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001019 " Emacs compatibility
1020 let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
1021 \stored_vcol, ']', 1)
1022 if ret | return res | endif
1023
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001024 elseif token ==# '<<'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001025 " Emacs compatibility
1026 let [ret, res] = s:BeginElementFound(stack, token, curr_vcol,
1027 \stored_vcol, '>>', 2)
1028 if ret | return res | endif
1029
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001030 elseif token ==# '(' || token ==# '{'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001031
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001032 let end_token = (token ==# '(' ? ')' :
1033 \token ==# '{' ? '}' : 'error')
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001034
1035 if empty(stack)
1036 " We found the opening paren whose block contains the LTI.
1037 let mode = 'inside'
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001038 elseif stack[0] ==# end_token
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001039 call s:Log(' "' . token . '" pops "' . end_token . '"')
1040 call s:Pop(stack)
1041
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001042 if !empty(stack) && stack[0] ==# 'align_to_begin_element'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001043 " We found the opening paren whose closing paren
1044 " starts LTI
1045 let mode = 'align_to_begin_element'
1046 else
1047 " We found the opening pair for a closing paren that
1048 " was already in the stack.
1049 let mode = 'outside'
1050 endif
1051 else
1052 return s:UnexpectedToken(token, stack)
1053 endif
1054
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001055 if mode ==# 'inside' || mode ==# 'align_to_begin_element'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001056
1057 if last_token_of_line && i != 0
1058 " Examples: {{{
1059 "
1060 " mode == 'inside':
1061 "
1062 " my_func(
1063 " LTI
1064 "
1065 " [Variable, {
1066 " LTI
1067 "
1068 " mode == 'align_to_begin_element':
1069 "
1070 " my_func(
1071 " Params
1072 " ) % LTI
1073 "
1074 " [Variable, {
1075 " Terms
1076 " } % LTI
1077 " }}}
1078 let stack = ['prev_term_plus']
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001079 let plus = (mode ==# 'inside' ? 2 : 1)
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001080 call s:Log(' "' . token .
1081 \'" token found at end of line -> find previous token')
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001082 elseif mode ==# 'align_to_begin_element'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001083 " Examples: {{{
1084 "
1085 " mode == 'align_to_begin_element' && !last_token_of_line
1086 "
1087 " my_func(stored_vcol
1088 " ) % LTI
1089 "
1090 " [Variable, {stored_vcol
1091 " } % LTI
1092 "
1093 " mode == 'align_to_begin_element' && i == 0
1094 "
1095 " (
1096 " stored_vcol
1097 " ) % LTI
1098 "
1099 " {
1100 " stored_vcol
1101 " } % LTI
1102 " }}}
1103 call s:Log(' "' . token . '" token (whose closing token ' .
1104 \'starts LTI) found -> return')
1105 return curr_vcol
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001106 elseif stored_vcol ==# -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001107 " Examples: {{{
1108 "
1109 " mode == 'inside' && stored_vcol == -1 && !last_token_of_line
1110 "
1111 " my_func(
1112 " LTI
1113 " [Variable, {
1114 " LTI
1115 "
1116 " mode == 'inside' && stored_vcol == -1 && i == 0
1117 "
1118 " (
1119 " LTI
1120 "
1121 " {
1122 " LTI
1123 " }}}
1124 call s:Log(' "' . token .
1125 \'" token (which directly precedes LTI) found -> return')
1126 return curr_vcol + 1
1127 else
1128 " Examples: {{{
1129 "
1130 " mode == 'inside' && stored_vcol != -1 && !last_token_of_line
1131 "
1132 " my_func(stored_vcol,
1133 " LTI
1134 "
1135 " [Variable, {stored_vcol,
1136 " LTI
1137 "
1138 " mode == 'inside' && stored_vcol != -1 && i == 0
1139 "
1140 " (stored_vcol,
1141 " LTI
1142 "
1143 " {stored_vcol,
1144 " LTI
1145 " }}}
1146 call s:Log(' "' . token .
1147 \'" token (whose block contains LTI) found -> return')
1148 return stored_vcol
1149 endif
1150 endif
1151
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001152 elseif index(['end', ')', ']', '}', '>>'], token) != -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001153
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001154 " If we can be sure that there is synchronization in the Erlang
1155 " syntax, we use searchpair to make the script quicker. Otherwise we
1156 " just push the token onto the stack and keep parsing.
1157
1158 " No synchronization -> no searchpair optimization
1159 if !exists('b:erlang_syntax_synced')
1160 call s:Push(stack, token)
1161
1162 " We don't have searchpair optimization for '>>'
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001163 elseif token ==# '>>'
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001164 call s:Push(stack, token)
1165
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001166 elseif token ==# 'end'
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001167 let [lnum_new, col_new] = s:SearchEndPair(lnum, curr_col)
1168
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001169 if lnum_new ==# 0
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001170 return s:IndentError('Matching token for "end" not found',
1171 \token, stack)
1172 else
1173 if lnum_new != lnum
1174 call s:Log(' Tokenize for "end" <<<<')
1175 let [lnum, indtokens] = s:TokenizeLine(lnum_new, 'up')
1176 call s:Log(' >>>> Tokenize for "end"')
1177 endif
1178
1179 let [success, i] = s:GetIndtokenAtCol(indtokens, col_new)
1180 if !success | return i | endif
1181 let [token, curr_vcol, curr_col] = indtokens[i]
1182 call s:Log(' Match for "end" in line ' . lnum_new . ': ' .
1183 \string(indtokens[i]))
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001184 endif
1185
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001186 else " token is one of the following: ')', ']', '}'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001187
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001188 call s:Push(stack, token)
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001189
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001190 " We have to escape '[', because this string will be interpreted as a
1191 " regexp
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001192 let open_paren = (token ==# ')' ? '(' :
1193 \token ==# ']' ? '\[' :
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001194 \ '{')
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001195
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001196 let [lnum_new, col_new] = s:SearchPair(lnum, curr_col,
1197 \open_paren, '', token)
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001198
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001199 if lnum_new ==# 0
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001200 return s:IndentError('Matching token not found',
1201 \token, stack)
1202 else
1203 if lnum_new != lnum
1204 call s:Log(' Tokenize the opening paren <<<<')
1205 let [lnum, indtokens] = s:TokenizeLine(lnum_new, 'up')
1206 call s:Log(' >>>>')
1207 endif
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001208
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001209 let [success, i] = s:GetIndtokenAtCol(indtokens, col_new)
1210 if !success | return i | endif
1211 let [token, curr_vcol, curr_col] = indtokens[i]
1212 call s:Log(' Match in line ' . lnum_new . ': ' .
1213 \string(indtokens[i]))
1214
1215 " Go back to the beginning of the loop and handle the opening paren
1216 continue
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001217 endif
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001218 endif
1219
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001220 elseif token ==# ';'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001221
1222 if empty(stack)
1223 call s:Push(stack, ';')
Bram Moolenaarf269eab2022-10-03 18:04:35 +01001224 elseif index([';', '->', 'when', 'end', 'after', 'catch', 'else'],
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001225 \stack[0]) != -1
1226 " Pass:
1227 "
1228 " - If the stack top is another ';', then one ';' is
1229 " enough.
1230 " - If the stack top is an '->' or a 'when', then we
1231 " should keep that, because they signify the type of the
1232 " LTI (branch, condition or guard).
1233 " - From the indentation point of view, the keyword
Bram Moolenaarf269eab2022-10-03 18:04:35 +01001234 " (of/catch/after/else/end) before the LTI is what counts, so
1235 " if the stack already has a catch/after/else/end, we don't
1236 " modify it. This way when we reach case/try/receive/maybe,
1237 " there will be at most one of/catch/after/else/end token in
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001238 " the stack.
1239 else
1240 return s:UnexpectedToken(token, stack)
1241 endif
1242
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001243 elseif token ==# '->'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001244
1245 if empty(stack) && !last_token_of_line
1246 call s:Log(' LTI is in expression after arrow -> return')
1247 return stored_vcol
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001248 elseif empty(stack) || stack[0] ==# ';' || stack[0] ==# 'end'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001249 " stack = [';'] -> LTI is either a branch or in a guard
1250 " stack = ['->'] -> LTI is a condition
1251 " stack = ['->', ';'] -> LTI is a branch
1252 call s:Push(stack, '->')
Bram Moolenaarf269eab2022-10-03 18:04:35 +01001253 elseif index(['->', 'when', 'end', 'after', 'catch', 'else'],
1254 \stack[0]) != -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001255 " Pass:
1256 "
1257 " - If the stack top is another '->', then one '->' is
1258 " enough.
1259 " - If the stack top is a 'when', then we should keep
1260 " that, because this signifies that LTI is a in a guard.
1261 " - From the indentation point of view, the keyword
Bram Moolenaarf269eab2022-10-03 18:04:35 +01001262 " (of/catch/after/else/end) before the LTI is what counts, so
1263 " if the stack already has a catch/after/else/end, we don't
1264 " modify it. This way when we reach case/try/receive/maybe,
1265 " there will be at most one of/catch/after/else/end token in
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001266 " the stack.
1267 else
1268 return s:UnexpectedToken(token, stack)
1269 endif
1270
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001271 elseif token ==# 'when'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001272
1273 " Pop all ';' from the top of the stack
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001274 while !empty(stack) && stack[0] ==# ';'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001275 call s:Pop(stack)
1276 endwhile
1277
1278 if empty(stack)
1279 if semicolon_abscol != ''
1280 let stored_vcol = semicolon_abscol
1281 endif
1282 if !last_token_of_line
1283 " Example:
1284 " when A,
1285 " LTI
1286 let [ret, res] = s:BeginElementFoundIfEmpty(stack, token, curr_vcol,
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001287 \stored_vcol, shiftwidth())
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001288 if ret | return res | endif
1289 else
1290 " Example:
1291 " when
1292 " LTI
1293 call s:Push(stack, token)
1294 endif
Bram Moolenaarf269eab2022-10-03 18:04:35 +01001295 elseif index(['->', 'when', 'end', 'after', 'catch', 'else'],
1296 \stack[0]) != -1
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001297 " Pass:
1298 " - If the stack top is another 'when', then one 'when' is
1299 " enough.
1300 " - If the stack top is an '->' or a 'when', then we
1301 " should keep that, because they signify the type of the
1302 " LTI (branch, condition or guard).
1303 " - From the indentation point of view, the keyword
Bram Moolenaarf269eab2022-10-03 18:04:35 +01001304 " (of/catch/after/else/end) before the LTI is what counts, so
1305 " if the stack already has a catch/after/else/end, we don't
1306 " modify it. This way when we reach case/try/receive/maybe,
1307 " there will be at most one of/catch/after/else/end token in
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001308 " the stack.
1309 else
1310 return s:UnexpectedToken(token, stack)
1311 endif
1312
Bram Moolenaarf269eab2022-10-03 18:04:35 +01001313 elseif token ==# 'of' || token ==# 'after' || token ==# 'else' ||
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001314 \ (token ==# 'catch' && !s:IsCatchStandalone(lnum, i))
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001315
Bram Moolenaarf269eab2022-10-03 18:04:35 +01001316 if token ==# 'after' || token ==# 'else'
1317 " If LTI is between an after/else and the corresponding 'end', then
1318 " let's return because calculating the indentation based on
1319 " after/else is enough.
1320 "
1321 " Example:
1322 " receive A after
1323 " LTI
1324 " maybe A else
1325 " LTI
1326 "
1327 " Note about Emacs compabitility {{{
1328 "
1329 " It would be fine to indent the examples above the following way:
1330 "
1331 " receive A after
1332 " LTI
1333 " maybe A else
1334 " LTI
1335 "
1336 " We intend it the way above because that is how Emacs does it.
1337 " Also, this is a bit faster.
1338 "
1339 " We are still not 100% Emacs compatible because of placing the
1340 " 'end' after the indented blocks.
1341 "
1342 " Emacs example:
1343 "
1344 " receive A after
1345 " LTI
1346 " end,
1347 " maybe A else
1348 " LTI
1349 " end % Yes, it's here (in OTP 25.0, might change
1350 " % later)
1351 "
1352 " vim-erlang example:
1353 "
1354 " receive A after
1355 " LTI
1356 " end,
1357 " maybe A else
1358 " LTI
1359 " end
1360 " }}}
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001361 let [ret, res] = s:BeginElementFoundIfEmpty(stack, token, curr_vcol,
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001362 \stored_vcol, shiftwidth())
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001363 if ret | return res | endif
1364 endif
1365
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001366 if empty(stack) || stack[0] ==# '->' || stack[0] ==# 'when'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001367 call s:Push(stack, token)
Bram Moolenaarf269eab2022-10-03 18:04:35 +01001368 elseif stack[0] ==# 'catch' || stack[0] ==# 'after' ||
1369 \stack[0] ==# 'else' || stack[0] ==# 'end'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001370 " Pass: From the indentation point of view, the keyword
1371 " (of/catch/after/end) before the LTI is what counts, so
1372 " if the stack already has a catch/after/end, we don't
1373 " modify it. This way when we reach case/try/receive,
1374 " there will be at most one of/catch/after/end token in
1375 " the stack.
1376 else
1377 return s:UnexpectedToken(token, stack)
1378 endif
1379
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001380 elseif token ==# '||' && empty(stack) && !last_token_of_line
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001381
1382 call s:Log(' LTI is in expression after "||" -> return')
1383 return stored_vcol
1384
1385 else
1386 call s:Log(' Misc token, stack unchanged = ' . string(stack))
1387
1388 endif
1389
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001390 if empty(stack) || stack[0] ==# '->' || stack[0] ==# 'when'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001391 let stored_vcol = curr_vcol
1392 let semicolon_abscol = ''
1393 call s:Log(' Misc token when the stack is empty or has "->" ' .
1394 \'-> setting stored_vcol to ' . stored_vcol)
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001395 elseif stack[0] ==# ';'
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001396 let semicolon_abscol = curr_vcol
1397 call s:Log(' Setting semicolon-stored_vcol to ' . stored_vcol)
1398 endif
1399
1400 let i -= 1
1401 call s:Log(' Token processed. stored_vcol=' . stored_vcol)
1402
1403 let last_token_of_line = 0
1404
1405 endwhile " iteration on tokens in a line
1406
1407 call s:Log(' Line analyzed. stored_vcol=' . stored_vcol)
1408
1409 if empty(stack) && stored_vcol != -1 &&
1410 \ (!empty(indtokens) && indtokens[0][0] != '<string_end>' &&
1411 \ indtokens[0][0] != '<quoted_atom_end>')
1412 call s:Log(' Empty stack at the beginning of the line -> return')
1413 return stored_vcol
1414 endif
1415
1416 let lnum -= 1
1417
1418 endwhile " iteration on lines
1419
1420endfunction
1421
1422" ErlangIndent function {{{1
1423" =====================
1424
1425function! ErlangIndent()
1426
1427 call s:ClearTokenCacheIfNeeded()
1428
1429 let currline = getline(v:lnum)
1430 call s:Log('Indenting line ' . v:lnum . ': ' . currline)
1431
1432 if s:IsLineStringContinuation(v:lnum) || s:IsLineAtomContinuation(v:lnum)
1433 call s:Log('String or atom continuation found -> ' .
1434 \'leaving indentation unchanged')
1435 return -1
1436 endif
1437
Bram Moolenaar1d59aa12020-09-19 18:50:13 +02001438 " If the line starts with the comment, and so is the previous non-blank line
1439 if currline =~# '^\s*%'
1440 let lnum = prevnonblank(v:lnum - 1)
1441 if lnum ==# 0
1442 call s:Log('First non-empty line of the file -> return 0.')
1443 return 0
1444 else
1445 let ml = matchlist(getline(lnum), '^\(\s*\)%')
1446 " If the previous line also starts with a comment, then return the same
1447 " indentation that line has. Otherwise exit from this special "if" and
1448 " don't care that the current line is a comment.
1449 if !empty(ml)
1450 let new_col = s:CalcVCol(ml[1], 0, len(ml[1]) - 1, 0, &tabstop)
1451 call s:Log('Comment line after another comment line -> ' .
1452 \'use same indent: ' . new_col)
1453 return new_col
1454 endif
1455 endif
1456 endif
1457
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001458 let ml = matchlist(currline,
Bram Moolenaarf269eab2022-10-03 18:04:35 +01001459 \'^\(\s*\)\(\%(end\|of\|catch\|after\|else\)\>\|[)\]}]\|>>\)')
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001460
1461 " If the line has a special beginning, but not a standalone catch
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001462 if !empty(ml) && !(ml[2] ==# 'catch' && s:IsCatchStandalone(v:lnum, 0))
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001463
1464 let curr_col = len(ml[1])
1465
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001466 " If we can be sure that there is synchronization in the Erlang
1467 " syntax, we use searchpair to make the script quicker.
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001468 if ml[2] ==# 'end' && exists('b:erlang_syntax_synced')
Bram Moolenaar203d04d2013-06-06 21:36:40 +02001469
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001470 let [lnum, col] = s:SearchEndPair(v:lnum, curr_col)
1471
Bram Moolenaar9d98fe92013-08-03 18:35:36 +02001472 if lnum ==# 0
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001473 return s:IndentError('Matching token for "end" not found',
1474 \'end', [])
1475 else
1476 call s:Log(' Tokenize for "end" <<<<')
1477 let [lnum, indtokens] = s:TokenizeLine(lnum, 'up')
1478 call s:Log(' >>>> Tokenize for "end"')
1479
1480 let [success, i] = s:GetIndtokenAtCol(indtokens, col)
1481 if !success | return i | endif
1482 let [token, curr_vcol, curr_col] = indtokens[i]
1483 call s:Log(' Match for "end" in line ' . lnum . ': ' .
1484 \string(indtokens[i]))
1485 return curr_vcol
1486 endif
1487
1488 else
1489
1490 call s:Log(" Line type = 'end'")
1491 let new_col = s:ErlangCalcIndent(v:lnum - 1,
1492 \[ml[2], 'align_to_begin_element'])
1493 endif
1494 else
1495 call s:Log(" Line type = 'normal'")
1496
1497 let new_col = s:ErlangCalcIndent(v:lnum - 1, [])
1498 if currline =~# '^\s*when\>'
1499 let new_col += 2
1500 endif
1501 endif
1502
1503 if new_col < -1
1504 call s:Log('WARNING: returning new_col == ' . new_col)
1505 return g:erlang_unexpected_token_indent
1506 endif
1507
1508 return new_col
1509
1510endfunction
1511
Bram Moolenaar1d59aa12020-09-19 18:50:13 +02001512" ErlangShowTokensInLine functions {{{1
1513" ================================
1514
1515" These functions are useful during development.
1516
1517function! ErlangShowTokensInLine(line)
1518 echo "Line: " . a:line
1519 let indtokens = s:GetTokensFromLine(a:line, 0, 0, &tabstop)
1520 echo "Tokens:"
1521 for it in indtokens
1522 echo it
1523 endfor
1524endfunction
1525
1526function! ErlangShowTokensInCurrentLine()
1527 return ErlangShowTokensInLine(getline('.'))
1528endfunction
1529
Bram Moolenaarad3b3662013-05-17 18:14:19 +02001530" Cleanup {{{1
1531" =======
1532
1533let &cpo = s:cpo_save
1534unlet s:cpo_save
1535
1536" vim: sw=2 et fdm=marker