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