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: