neovim

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

jsonc.vim (4857B)


      1 " Vim indent file
      2 " Language:         JSONC (JSON with Comments)
      3 " Original Author:  Izhak Jakov <izhak724@gmail.com>
      4 " Acknowledgement:  Based off of vim-json maintained by Eli Parra <eli@elzr.com>
      5 "                   https://github.com/elzr/vim-json
      6 " Last Change:      2021-07-01
      7 "                   2023 Aug 28 by Vim Project (undo_indent)
      8 
      9 " 0. Initialization {{{1
     10 " =================
     11 
     12 " Only load this indent file when no other was loaded.
     13 if exists("b:did_indent")
     14  finish
     15 endif
     16 let b:did_indent = 1
     17 
     18 setlocal nosmartindent
     19 
     20 " Now, set up our indentation expression and keys that trigger it.
     21 setlocal indentexpr=GetJSONCIndent()
     22 setlocal indentkeys=0{,0},0),0[,0],!^F,o,O,e
     23 
     24 let b:undo_indent = "setlocal indentexpr< indentkeys< smartindent<"
     25 
     26 " Only define the function once.
     27 if exists("*GetJSONCIndent")
     28  finish
     29 endif
     30 
     31 let s:cpo_save = &cpo
     32 set cpo&vim
     33 
     34 " 1. Variables {{{1
     35 " ============
     36 
     37 let s:line_term = '\s*\%(\%(\/\/\).*\)\=$'
     38 " Regex that defines blocks.
     39 let s:block_regex = '\%({\)\s*\%(|\%([*@]\=\h\w*,\=\s*\)\%(,\s*[*@]\=\h\w*\)*|\)\=' . s:line_term
     40 
     41 " 2. Auxiliary Functions {{{1
     42 " ======================
     43 
     44 " Check if the character at lnum:col is inside a string.
     45 function s:IsInString(lnum, col)
     46  return synIDattr(synID(a:lnum, a:col, 1), 'name') == 'jsonString'
     47 endfunction
     48 
     49 " Find line above 'lnum' that isn't empty, or in a string.
     50 function s:PrevNonBlankNonString(lnum)
     51  let lnum = prevnonblank(a:lnum)
     52  while lnum > 0
     53    " If the line isn't empty or in a string, end search.
     54    let line = getline(lnum)
     55    if !(s:IsInString(lnum, 1) && s:IsInString(lnum, strlen(line)))
     56      break
     57    endif
     58    let lnum = prevnonblank(lnum - 1)
     59  endwhile
     60  return lnum
     61 endfunction
     62 
     63 " Check if line 'lnum' has more opening brackets than closing ones.
     64 function s:LineHasOpeningBrackets(lnum)
     65  let open_0 = 0
     66  let open_2 = 0
     67  let open_4 = 0
     68  let line = getline(a:lnum)
     69  let pos = match(line, '[][(){}]', 0)
     70  while pos != -1
     71    let idx = stridx('(){}[]', line[pos])
     72    if idx % 2 == 0
     73      let open_{idx} = open_{idx} + 1
     74    else
     75      let open_{idx - 1} = open_{idx - 1} - 1
     76    endif
     77    let pos = match(line, '[][(){}]', pos + 1)
     78  endwhile
     79  return (open_0 > 0) . (open_2 > 0) . (open_4 > 0)
     80 endfunction
     81 
     82 function s:Match(lnum, regex)
     83  let col = match(getline(a:lnum), a:regex) + 1
     84  return col > 0 && !s:IsInString(a:lnum, col) ? col : 0
     85 endfunction
     86 
     87 " 3. GetJSONCIndent Function {{{1
     88 " =========================
     89 
     90 function GetJSONCIndent()
     91  if !exists("s:inside_comment")
     92    let s:inside_comment = 0
     93  endif
     94 
     95  " 3.1. Setup {{{2
     96  " ----------
     97 
     98  " Set up variables for restoring position in file.  Could use v:lnum here.
     99  let vcol = col('.')
    100 
    101  " 3.2. Work on the current line {{{2
    102  " -----------------------------
    103 
    104 
    105  " Get the current line.
    106  let line = getline(v:lnum)
    107  let ind = -1
    108  if s:inside_comment == 0
    109    " TODO iterate through all the matches in a line
    110    let col = matchend(line, '\/\*')
    111    if col > 0 && !s:IsInString(v:lnum, col)
    112      let s:inside_comment = 1
    113    endif
    114  endif
    115  " If we're in the middle of a comment
    116  if s:inside_comment == 1
    117    let col = matchend(line, '\*\/')
    118    if col > 0 && !s:IsInString(v:lnum, col)
    119      let s:inside_comment = 0
    120    endif
    121    return ind
    122  endif
    123  if line =~ '^\s*//'
    124    return ind
    125  endif
    126 
    127  " If we got a closing bracket on an empty line, find its match and indent
    128  " according to it.
    129  let col = matchend(line, '^\s*[]}]')
    130 
    131  if col > 0 && !s:IsInString(v:lnum, col)
    132    call cursor(v:lnum, col)
    133    let bs = strpart('{}[]', stridx('}]', line[col - 1]) * 2, 2)
    134 
    135    let pairstart = escape(bs[0], '[')
    136    let pairend = escape(bs[1], ']')
    137    let pairline = searchpair(pairstart, '', pairend, 'bW')
    138 
    139    if pairline > 0 
    140      let ind = indent(pairline)
    141    else
    142      let ind = virtcol('.') - 1
    143    endif
    144 
    145    return ind
    146  endif
    147 
    148  " If we are in a multi-line string, don't do anything to it.
    149  if s:IsInString(v:lnum, matchend(line, '^\s*') + 1)
    150    return indent('.')
    151  endif
    152 
    153  " 3.3. Work on the previous line. {{{2
    154  " -------------------------------
    155 
    156  let lnum = prevnonblank(v:lnum - 1)
    157 
    158  if lnum == 0
    159    return 0
    160  endif
    161 
    162  " Set up variables for current line.
    163  let line = getline(lnum)
    164  let ind = indent(lnum)
    165 
    166  " If the previous line ended with a block opening, add a level of indent.
    167  " if s:Match(lnum, s:block_regex)
    168  " return indent(lnum) + shiftwidth()
    169  " endif
    170 
    171  " If the previous line contained an opening bracket, and we are still in it,
    172  " add indent depending on the bracket type.
    173  if line =~ '[[({]'
    174    let counts = s:LineHasOpeningBrackets(lnum)
    175    if counts[0] == '1' || counts[1] == '1' || counts[2] == '1'
    176      return ind + shiftwidth()
    177    else
    178      call cursor(v:lnum, vcol)
    179    end
    180  endif
    181 
    182  " }}}2
    183 
    184  return ind
    185 endfunction
    186 
    187 " }}}1
    188 
    189 let &cpo = s:cpo_save
    190 unlet s:cpo_save
    191 
    192 " vim:set sw=2 sts=2 ts=8 noet: