blob: bf8e501dd5c6a666128d3ae161fa7d1a9c7bd887 [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
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: