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