erlang.vim 45 KB

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