xref: /vim-8.2.3635/runtime/indent/jsonc.vim (revision 90df4b9d)
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