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