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