erlang.vim 49 KB

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