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