julia.vim 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. " Vim indent file
  2. " Language: Julia
  3. " Maintainer: Carlo Baldassi <carlobaldassi@gmail.com>
  4. " Homepage: https://github.com/JuliaEditorSupport/julia-vim
  5. " Last Change: 2022 Jun 14
  6. " Notes: originally based on Bram Molenaar's indent file for vim
  7. " Only load this indent file when no other was loaded.
  8. if exists("b:did_indent")
  9. finish
  10. endif
  11. let b:did_indent = 1
  12. setlocal autoindent
  13. setlocal indentexpr=GetJuliaIndent()
  14. setlocal indentkeys+==end,=else,=catch,=finally,),],}
  15. setlocal indentkeys-=0#
  16. setlocal indentkeys-=:
  17. setlocal indentkeys-=0{
  18. setlocal indentkeys-=0}
  19. setlocal nosmartindent
  20. " Only define the function once.
  21. if exists("*GetJuliaIndent")
  22. finish
  23. endif
  24. let s:skipPatternsBasic = '\<julia\%(Comment\%([LM]\|Delim\)\)\>'
  25. let s:skipPatterns = '\<julia\%(Comprehension\%(For\|If\)\|RangeKeyword\|Comment\%([LM]\|Delim\)\|\%([bs]\|Shell\|Printf\|Doc\)\?String\|StringPrefixed\|DocStringM\(Raw\)\?\|RegEx\|SymbolS\?\|Macro\|Dotted\)\>'
  26. function JuliaMatch(lnum, str, regex, st, ...)
  27. let s = a:st
  28. let e = a:0 > 0 ? a:1 : -1
  29. let basic_skip = a:0 > 1 ? a:2 : 'all'
  30. let skip = basic_skip ==# 'basic' ? s:skipPatternsBasic : s:skipPatterns
  31. while 1
  32. let f = match(a:str, '\C' . a:regex, s)
  33. if e >= 0 && f >= e
  34. return -1
  35. endif
  36. if f >= 0
  37. let attr = synIDattr(synID(a:lnum,f+1,1),"name")
  38. let attrT = synIDattr(synID(a:lnum,f+1,0),"name")
  39. if attr =~# skip || attrT =~# skip
  40. let s = f+1
  41. continue
  42. endif
  43. endif
  44. break
  45. endwhile
  46. return f
  47. endfunction
  48. function GetJuliaNestingStruct(lnum, ...)
  49. " Auxiliary function to inspect the block structure of a line
  50. let line = getline(a:lnum)
  51. let s = a:0 > 0 ? a:1 : 0
  52. let e = a:0 > 1 ? a:2 : -1
  53. let blocks_stack = []
  54. let num_closed_blocks = 0
  55. while 1
  56. let fb = JuliaMatch(a:lnum, line, '\<\%(if\|else\%(if\)\?\|while\|for\|try\|catch\|finally\|\%(staged\)\?function\|macro\|begin\|mutable\s\+struct\|\%(mutable\s\+\)\@<!struct\|\%(abstract\|primitive\)\s\+type\|let\|\%(bare\)\?module\|quote\|do\)\>', s, e)
  57. let fe = JuliaMatch(a:lnum, line, '\<end\>', s, e)
  58. if fb < 0 && fe < 0
  59. " No blocks found
  60. break
  61. end
  62. if fb >= 0 && (fb < fe || fe < 0)
  63. " The first occurrence is an opening block keyword
  64. " Note: some keywords (elseif,else,catch,finally) are both
  65. " closing blocks and opening new ones
  66. let i = JuliaMatch(a:lnum, line, '\<if\>', s)
  67. if i >= 0 && i == fb
  68. let s = i+1
  69. call add(blocks_stack, 'if')
  70. continue
  71. endif
  72. let i = JuliaMatch(a:lnum, line, '\<elseif\>', s)
  73. if i >= 0 && i == fb
  74. let s = i+1
  75. if len(blocks_stack) > 0 && blocks_stack[-1] == 'if'
  76. let blocks_stack[-1] = 'elseif'
  77. elseif (len(blocks_stack) > 0 && blocks_stack[-1] != 'elseif') || len(blocks_stack) == 0
  78. call add(blocks_stack, 'elseif')
  79. let num_closed_blocks += 1
  80. endif
  81. continue
  82. endif
  83. let i = JuliaMatch(a:lnum, line, '\<else\>', s)
  84. if i >= 0 && i == fb
  85. let s = i+1
  86. if len(blocks_stack) > 0 && blocks_stack[-1] =~# '\<\%(else\)\=if\>'
  87. let blocks_stack[-1] = 'else'
  88. else
  89. call add(blocks_stack, 'else')
  90. let num_closed_blocks += 1
  91. endif
  92. continue
  93. endif
  94. let i = JuliaMatch(a:lnum, line, '\<try\>', s)
  95. if i >= 0 && i == fb
  96. let s = i+1
  97. call add(blocks_stack, 'try')
  98. continue
  99. endif
  100. let i = JuliaMatch(a:lnum, line, '\<catch\>', s)
  101. if i >= 0 && i == fb
  102. let s = i+1
  103. if len(blocks_stack) > 0 && blocks_stack[-1] == 'try'
  104. let blocks_stack[-1] = 'catch'
  105. else
  106. call add(blocks_stack, 'catch')
  107. let num_closed_blocks += 1
  108. endif
  109. continue
  110. endif
  111. let i = JuliaMatch(a:lnum, line, '\<finally\>', s)
  112. if i >= 0 && i == fb
  113. let s = i+1
  114. if len(blocks_stack) > 0 && (blocks_stack[-1] == 'try' || blocks_stack[-1] == 'catch')
  115. let blocks_stack[-1] = 'finally'
  116. else
  117. call add(blocks_stack, 'finally')
  118. let num_closed_blocks += 1
  119. endif
  120. continue
  121. endif
  122. let i = JuliaMatch(a:lnum, line, '\<\%(bare\)\?module\>', s)
  123. if i >= 0 && i == fb
  124. let s = i+1
  125. if i == 0
  126. call add(blocks_stack, 'col1module')
  127. else
  128. call add(blocks_stack, 'other')
  129. endif
  130. continue
  131. endif
  132. let i = JuliaMatch(a:lnum, line, '\<\%(while\|for\|function\|macro\|begin\|\%(mutable\s\+\)\?struct\|\%(abstract\|primitive\)\s\+type\|let\|quote\|do\)\>', s)
  133. if i >= 0 && i == fb
  134. if match(line, '\C\<\%(mutable\|abstract\|primitive\)', i) != -1
  135. let s = i+11
  136. else
  137. let s = i+1
  138. endif
  139. call add(blocks_stack, 'other')
  140. continue
  141. endif
  142. " Note: it should be impossible to get here
  143. break
  144. else
  145. " The first occurrence is an 'end'
  146. let s = fe+1
  147. if len(blocks_stack) == 0
  148. let num_closed_blocks += 1
  149. else
  150. call remove(blocks_stack, -1)
  151. endif
  152. continue
  153. endif
  154. " Note: it should be impossible to get here
  155. break
  156. endwhile
  157. let num_open_blocks = len(blocks_stack) - count(blocks_stack, 'col1module')
  158. return [num_open_blocks, num_closed_blocks]
  159. endfunction
  160. function GetJuliaNestingBrackets(lnum, c)
  161. " Auxiliary function to inspect the brackets structure of a line
  162. let line = getline(a:lnum)[0 : (a:c - 1)]
  163. let s = 0
  164. let brackets_stack = []
  165. let last_closed_bracket = -1
  166. while 1
  167. let fb = JuliaMatch(a:lnum, line, '[([{]', s)
  168. let fe = JuliaMatch(a:lnum, line, '[])}]', s)
  169. if fb < 0 && fe < 0
  170. " No brackets found
  171. break
  172. end
  173. if fb >= 0 && (fb < fe || fe < 0)
  174. " The first occurrence is an opening bracket
  175. let i = JuliaMatch(a:lnum, line, '(', s)
  176. if i >= 0 && i == fb
  177. let s = i+1
  178. call add(brackets_stack, ['par',i])
  179. continue
  180. endif
  181. let i = JuliaMatch(a:lnum, line, '\[', s)
  182. if i >= 0 && i == fb
  183. let s = i+1
  184. call add(brackets_stack, ['sqbra',i])
  185. continue
  186. endif
  187. let i = JuliaMatch(a:lnum, line, '{', s)
  188. if i >= 0 && i == fb
  189. let s = i+1
  190. call add(brackets_stack, ['curbra',i])
  191. continue
  192. endif
  193. " Note: it should be impossible to get here
  194. break
  195. else
  196. " The first occurrence is a closing bracket
  197. let i = JuliaMatch(a:lnum, line, ')', s)
  198. if i >= 0 && i == fe
  199. let s = i+1
  200. if len(brackets_stack) > 0 && brackets_stack[-1][0] == 'par'
  201. call remove(brackets_stack, -1)
  202. else
  203. let last_closed_bracket = i + 1
  204. endif
  205. continue
  206. endif
  207. let i = JuliaMatch(a:lnum, line, ']', s)
  208. if i >= 0 && i == fe
  209. let s = i+1
  210. if len(brackets_stack) > 0 && brackets_stack[-1][0] == 'sqbra'
  211. call remove(brackets_stack, -1)
  212. else
  213. let last_closed_bracket = i + 1
  214. endif
  215. continue
  216. endif
  217. let i = JuliaMatch(a:lnum, line, '}', s)
  218. if i >= 0 && i == fe
  219. let s = i+1
  220. if len(brackets_stack) > 0 && brackets_stack[-1][0] == 'curbra'
  221. call remove(brackets_stack, -1)
  222. else
  223. let last_closed_bracket = i + 1
  224. endif
  225. continue
  226. endif
  227. " Note: it should be impossible to get here
  228. break
  229. endif
  230. " Note: it should be impossible to get here
  231. break
  232. endwhile
  233. let first_open_bracket = -1
  234. let last_open_bracket = -1
  235. let infuncargs = 0
  236. if len(brackets_stack) > 0
  237. let first_open_bracket = brackets_stack[0][1]
  238. let last_open_bracket = brackets_stack[-1][1]
  239. if brackets_stack[-1][0] == 'par' && IsFunctionArgPar(a:lnum, last_open_bracket+1)
  240. let infuncargs = 1
  241. endif
  242. endif
  243. return [first_open_bracket, last_open_bracket, last_closed_bracket, infuncargs]
  244. endfunction
  245. let s:bracketBlocks = '\<julia\%(\%(\%(Printf\)\?Par\|SqBra\%(Idx\)\?\|CurBra\)Block\|ParBlockInRange\|StringVars\%(Par\|SqBra\|CurBra\)\|Dollar\%(Par\|SqBra\)\|QuotedParBlockS\?\)\>'
  246. function IsInBrackets(lnum, c)
  247. let stack = map(synstack(a:lnum, a:c), 'synIDattr(v:val, "name")')
  248. call filter(stack, 'v:val =~# s:bracketBlocks')
  249. return len(stack) > 0
  250. endfunction
  251. function IsInDocString(lnum)
  252. let stack = map(synstack(a:lnum, 1), 'synIDattr(v:val, "name")')
  253. call filter(stack, 'v:val =~# "\\<juliaDocString\\(Delim\\|M\\\(Raw\\)\\?\\)\\?\\>"')
  254. return len(stack) > 0
  255. endfunction
  256. function IsInContinuationImportLine(lnum)
  257. let stack = map(synstack(a:lnum, 1), 'synIDattr(v:val, "name")')
  258. call filter(stack, 'v:val =~# "\\<juliaImportLine\\>"')
  259. if len(stack) == 0
  260. return 0
  261. endif
  262. return JuliaMatch(a:lnum, getline(a:lnum), '\<\%(import\|using\|export\)\>', indent(a:lnum)) == -1
  263. endfunction
  264. function IsFunctionArgPar(lnum, c)
  265. if a:c == 0
  266. return 0
  267. endif
  268. let stack = map(synstack(a:lnum, a:c-1), 'synIDattr(v:val, "name")')
  269. return len(stack) >= 2 && stack[-2] ==# 'juliaFunctionDef'
  270. endfunction
  271. function JumpToMatch(lnum, last_closed_bracket)
  272. " we use the % command to skip back (tries to ues matchit if possible,
  273. " otherwise resorts to vim's default, which is buggy but better than
  274. " nothing)
  275. call cursor(a:lnum, a:last_closed_bracket)
  276. let percmap = maparg("%", "n")
  277. if exists("g:loaded_matchit") && percmap =~# 'Match\%(it\|_wrapper\)'
  278. normal %
  279. else
  280. normal! %
  281. end
  282. endfunction
  283. " Auxiliary function to find a line which does not start in the middle of a
  284. " multiline bracketed expression, to be used as reference for block
  285. " indentation.
  286. function LastBlockIndent(lnum)
  287. let lnum = a:lnum
  288. let ind = 0
  289. while lnum > 0
  290. let ind = indent(lnum)
  291. if ind == 0
  292. return [lnum, 0]
  293. endif
  294. if !IsInBrackets(lnum, 1)
  295. break
  296. endif
  297. let lnum = prevnonblank(lnum - 1)
  298. endwhile
  299. return [max([lnum,1]), ind]
  300. endfunction
  301. function GetJuliaIndent()
  302. " Do not alter doctrings indentation
  303. if IsInDocString(v:lnum)
  304. return -1
  305. endif
  306. " Find a non-blank line above the current line.
  307. let lnum = prevnonblank(v:lnum - 1)
  308. " At the start of the file use zero indent.
  309. if lnum == 0
  310. return 0
  311. endif
  312. let ind = -1
  313. let st = -1
  314. let lim = -1
  315. " Multiline bracketed expressions take precedence
  316. let align_brackets = get(g:, "julia_indent_align_brackets", 1)
  317. let align_funcargs = get(g:, "julia_indent_align_funcargs", 0)
  318. let c = len(getline(lnum)) + 1
  319. while IsInBrackets(lnum, c)
  320. let [first_open_bracket, last_open_bracket, last_closed_bracket, infuncargs] = GetJuliaNestingBrackets(lnum, c)
  321. " First scenario: the previous line has a hanging open bracket:
  322. " set the indentation to match the opening bracket (plus an extra space)
  323. " unless we're in a function arguments list or alignment is disabled, in
  324. " which case we just add an extra indent
  325. if last_open_bracket != -1
  326. if (!infuncargs && align_brackets) || (infuncargs && align_funcargs)
  327. let st = last_open_bracket
  328. let ind = virtcol([lnum, st + 1])
  329. else
  330. let ind = indent(lnum) + shiftwidth()
  331. endif
  332. " Second scenario: some multiline bracketed expression was closed in the
  333. " previous line. But since we know we are still in a bracketed expression,
  334. " we need to find the line where the bracket was opened
  335. elseif last_closed_bracket != -1
  336. call JumpToMatch(lnum, last_closed_bracket)
  337. if line(".") == lnum
  338. " something wrong here, give up
  339. let ind = indent(lnum)
  340. else
  341. let lnum = line(".")
  342. let c = col(".") - 1
  343. if c == 0
  344. " uhm, give up
  345. let ind = 0
  346. else
  347. " we skipped a bracket set, keep searching for an opening bracket
  348. let lim = c
  349. continue
  350. endif
  351. endif
  352. " Third scenario: nothing special: keep the indentation
  353. else
  354. let ind = indent(lnum)
  355. endif
  356. " Does the current line start with a closing bracket? Then depending on
  357. " the situation we align it with the opening one, or we let the rest of
  358. " the code figure it out (the case in which we're closing a function
  359. " argument list is special-cased)
  360. if JuliaMatch(v:lnum, getline(v:lnum), '[])}]', indent(v:lnum)) == indent(v:lnum) && ind > 0
  361. if !align_brackets && !align_funcargs
  362. call JumpToMatch(v:lnum, indent(v:lnum))
  363. return indent(line("."))
  364. elseif (align_brackets && getline(v:lnum)[indent(v:lnum)] != ')') || align_funcargs
  365. return ind - 1
  366. else " must be a ')' and align_brackets==1 and align_funcargs==0
  367. call JumpToMatch(v:lnum, indent(v:lnum))
  368. if IsFunctionArgPar(line("."), col("."))
  369. let ind = -1
  370. else
  371. return ind - 1
  372. endif
  373. endif
  374. endif
  375. break
  376. endwhile
  377. if ind == -1
  378. " We are not in a multiline bracketed expression. Thus we look for a
  379. " previous line to use as a reference
  380. let [lnum,ind] = LastBlockIndent(lnum)
  381. let c = len(getline(lnum)) + 1
  382. if IsInBrackets(lnum, c)
  383. let [first_open_bracket, last_open_bracket, last_closed_bracket, infuncargs] = GetJuliaNestingBrackets(lnum, c)
  384. let lim = first_open_bracket
  385. endif
  386. end
  387. " Analyse the reference line
  388. let [num_open_blocks, num_closed_blocks] = GetJuliaNestingStruct(lnum, st, lim)
  389. " Increase indentation for each newly opened block in the reference line
  390. let ind += shiftwidth() * num_open_blocks
  391. " Analyse the current line
  392. let [num_open_blocks, num_closed_blocks] = GetJuliaNestingStruct(v:lnum)
  393. " Decrease indentation for each closed block in the current line
  394. let ind -= shiftwidth() * num_closed_blocks
  395. " Additional special case: multiline import/using/export statements
  396. let prevline = getline(lnum)
  397. " Are we in a multiline import/using/export statement, right below the
  398. " opening line?
  399. if IsInContinuationImportLine(v:lnum) && !IsInContinuationImportLine(lnum)
  400. if get(g:, 'julia_indent_align_import', 1)
  401. " if the opening line has a colon followed by non-comments, use it as
  402. " reference point
  403. let cind = JuliaMatch(lnum, prevline, ':', indent(lnum), lim)
  404. if cind >= 0
  405. let nonwhiteind = JuliaMatch(lnum, prevline, '\S', cind+1, -1, 'basic')
  406. if nonwhiteind >= 0
  407. " return match(prevline, '\S', cind+1) " a bit overkill...
  408. return cind + 2
  409. endif
  410. else
  411. " if the opening line is not a naked import/using/export statement, use
  412. " it as reference
  413. let iind = JuliaMatch(lnum, prevline, '\<import\|using\|export\>', indent(lnum), lim)
  414. if iind >= 0
  415. " assuming whitespace after using... so no `using(XYZ)` please!
  416. let nonwhiteind = JuliaMatch(lnum, prevline, '\S', iind+6, -1, 'basic')
  417. if nonwhiteind >= 0
  418. return match(prevline, '\S', iind+6)
  419. endif
  420. endif
  421. endif
  422. endif
  423. let ind += shiftwidth()
  424. " Or did we just close a multiline import/using/export statement?
  425. elseif !IsInContinuationImportLine(v:lnum) && IsInContinuationImportLine(lnum)
  426. " find the starting line of the statement
  427. let ilnum = 0
  428. for iln in range(lnum-1, 1, -1)
  429. if !IsInContinuationImportLine(iln)
  430. let ilnum = iln
  431. break
  432. endif
  433. endfor
  434. if ilnum == 0
  435. " something went horribly wrong, give up
  436. let ind = indent(lnum)
  437. endif
  438. let ind = indent(ilnum)
  439. endif
  440. return ind
  441. endfunction