neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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