1" Vim indent file 2" Language: JSON 3" Maintainer: Eli Parra <[email protected]> https://github.com/elzr/vim-json 4" Last Change: 2020 Aug 30 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(v:lnum) 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 " For the current line, use the first argument if given, else v:lnum 93 let clnum = a:0 ? a:1 : v:lnum 94 95 " Set up variables for restoring position in file. Could use clnum here. 96 let vcol = col('.') 97 98 " 3.2. Work on the current line {{{2 99 " ----------------------------- 100 101 " Get the current line. 102 let line = getline(clnum) 103 let ind = -1 104 105 " If we got a closing bracket on an empty line, find its match and indent 106 " according to it. 107 let col = matchend(line, '^\s*[]}]') 108 109 if col > 0 && !s:IsInString(clnum, col) 110 call cursor(clnum, col) 111 let bs = strpart('{}[]', stridx('}]', line[col - 1]) * 2, 2) 112 113 let pairstart = escape(bs[0], '[') 114 let pairend = escape(bs[1], ']') 115 let pairline = searchpair(pairstart, '', pairend, 'bW') 116 117 if pairline > 0 118 let ind = indent(pairline) 119 else 120 let ind = virtcol('.') - 1 121 endif 122 123 return ind 124 endif 125 126 " If we are in a multi-line string, don't do anything to it. 127 if s:IsInString(clnum, matchend(line, '^\s*') + 1) 128 return indent('.') 129 endif 130 131 " 3.3. Work on the previous line. {{{2 132 " ------------------------------- 133 134 let lnum = prevnonblank(clnum - 1) 135 136 if lnum == 0 137 return 0 138 endif 139 140 " Set up variables for current line. 141 let line = getline(lnum) 142 let ind = indent(lnum) 143 144 " If the previous line ended with a block opening, add a level of indent. 145 " if s:Match(lnum, s:block_regex) 146 " return indent(lnum) + shiftwidth() 147 " endif 148 149 " If the previous line contained an opening bracket, and we are still in it, 150 " add indent depending on the bracket type. 151 if line =~ '[[({]' 152 let counts = s:LineHasOpeningBrackets(lnum) 153 if counts[0] == '1' || counts[1] == '1' || counts[2] == '1' 154 return ind + shiftwidth() 155 else 156 call cursor(clnum, vcol) 157 end 158 endif 159 160 " }}}2 161 162 return ind 163endfunction 164 165" }}}1 166 167let &cpo = s:cpo_save 168unlet s:cpo_save 169 170" vim:set sw=2 sts=2 ts=8 noet: 171