blob: 058634a6d2ff9b760609a9da22683bff8e3c8585 [file] [log] [blame]
Bram Moolenaar90df4b92021-07-07 20:26:08 +02001" 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
dkearns0382f052023-08-29 05:32:59 +10007" 2023 Aug 28 by Vim Project (undo_indent)
Bram Moolenaar90df4b92021-07-07 20:26:08 +02008
9" 0. Initialization {{{1
10" =================
11
12" Only load this indent file when no other was loaded.
13if exists("b:did_indent")
14 finish
15endif
16let b:did_indent = 1
17
18setlocal nosmartindent
19
20" Now, set up our indentation expression and keys that trigger it.
21setlocal indentexpr=GetJSONCIndent()
22setlocal indentkeys=0{,0},0),0[,0],!^F,o,O,e
23
dkearns0382f052023-08-29 05:32:59 +100024let b:undo_indent = "setlocal indentexpr< indentkeys< smartindent<"
25
Bram Moolenaar90df4b92021-07-07 20:26:08 +020026" Only define the function once.
27if exists("*GetJSONCIndent")
28 finish
29endif
30
31let s:cpo_save = &cpo
32set cpo&vim
33
34" 1. Variables {{{1
35" ============
36
37let s:line_term = '\s*\%(\%(\/\/\).*\)\=$'
38" Regex that defines blocks.
39let 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.
45function s:IsInString(lnum, col)
46 return synIDattr(synID(a:lnum, a:col, 1), 'name') == 'jsonString'
47endfunction
48
49" Find line above 'lnum' that isn't empty, or in a string.
50function 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
61endfunction
62
63" Check if line 'lnum' has more opening brackets than closing ones.
64function 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)
80endfunction
81
82function 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
85endfunction
86
87" 3. GetJSONCIndent Function {{{1
88" =========================
89
90function 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
185endfunction
186
187" }}}1
188
189let &cpo = s:cpo_save
190unlet s:cpo_save
191
192" vim:set sw=2 sts=2 ts=8 noet: