julia.vim 15 KB

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