erlang.vim 51 KB

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