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