2 " Author: Tim Pope <http://tpo.pe/>
4 if exists("g:autoloaded_jdaddy")
7 let g:autoloaded_jdaddy = 1
9 if !exists('g:jdaddy#null')
10 let g:jdaddy#null = ['null']
11 let g:jdaddy#false = ['false']
12 let g:jdaddy#true = ['true']
15 function! s:sub(str,pat,rep) abort
16 return substitute(a:str,'\v\C'.a:pat,a:rep,'')
19 function! s:gsub(str,pat,rep) abort
20 return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
25 function! jdaddy#inner_pos(...) abort
26 let cnt = a:0 ? a:1 : 1
27 let line = getline('.')
28 let char = line[col('.')-1]
29 if char ==# '"' || len(s:gsub(s:gsub(line[0 : col('.')-1], '\\.', ''), '[^"]', '')) % 2
33 for pos in range(len(line))
36 elseif line[pos] ==# '\'
38 elseif line[pos] ==# '"'
42 let before = filter(copy(quotes), 'v:val <= col(".")-1')
43 let after = filter(copy(quotes), 'v:val > col(".")-1')
44 if before[-1] == col('.')-1 && len(before) % 2 == 0
45 return [line('.'), before[-2]+1, line('.'), before[-1]+1]
47 return [line('.'), before[-1]+1, line('.'), after[0]+1]
50 elseif char =~# '[[:alnum:]._+-]'
53 let [start, end] = [col('.')-1, col('.')-1]
54 while line[start-1] =~# '[[:alnum:]._+-]'
57 while line[end+1] =~# '[[:alnum:]._+-]'
60 return [line('.'), start+1, line('.'), end+1]
65 let [lclose, cclose] = [line('.'), col('.')]
67 let [lclose, cclose] = searchpairpos('[[{(]', '', '[]})]', 'W')
69 let [lopen, copen] = searchpairpos('[[{(]', '', '[]})]', 'Wb')
73 return [lopen, copen, lclose, cclose]
76 function! jdaddy#outer_pos(...) abort
77 if getline('.')[col('.')-1] =~# '[]}]'
78 let [lclose, cclose] = [line('.'), col('.')]
80 let [lclose, cclose] = searchpairpos('[[{]', '', '[]}]', 'r')
82 let [lopen, copen] = searchpairpos('[[{]', '', '[]}]', 'rb')
84 return [lopen, copen, lclose, cclose]
89 function! s:movement_string(line, col) abort
90 return a:line . "G0" . (a:col > 1 ? (a:col - 1) . "l" : "")
93 function! jdaddy#inner_movement(count) abort
94 let [lopen, copen, lclose, cclose] = jdaddy#inner_pos(a:count)
98 call setpos("'[", [0, lopen, copen, 0])
99 call setpos("']", [0, lclose, cclose, 0])
103 function! jdaddy#outer_movement(count) abort
104 let [lopen, copen, lclose, cclose] = jdaddy#outer_pos(a:count)
108 call setpos("'[", [0, lopen, copen, 0])
109 call setpos("']", [0, lclose, cclose, 0])
110 return s:movement_string(lopen, copen) . 'o' . s:movement_string(lclose, cclose)
115 function! jdaddy#parse(string) abort
116 let [null, false, true] = [g:jdaddy#null, g:jdaddy#false, g:jdaddy#true]
117 let one_line = substitute(a:string, "[\r\n]\\s*", ' ', 'g')
118 let quoted_keys = substitute(one_line,
119 \ '\C"\(\\.\|[^"\\]\)*"\|\w\+\ze:\|[[:,]\s*\zs\h\w*\ze\s*[]},]',
120 \ '\=submatch(0) =~# "^\\%(\"\\|true$\\|false$\\|null$\\)" ? submatch(0) : "\"\002" . submatch(0) . "\""',
122 let stripped = substitute(quoted_keys,'\C"\(\\.\|[^"\\]\)*"','','g')
123 if stripped !~# "[^,:{}\\[\\]0-9.\\-+Eaeflnr-u \n\r\t]"
125 return eval(quoted_keys)
129 throw "jdaddy: invalid JSON: ".one_line
141 function! jdaddy#dump(object, ...) abort
142 let opt = extend({'width': 0, 'level': 0, 'indent': 1, 'before': 0, 'seen': []}, a:0 ? copy(a:1) : {})
143 let opt.seen = copy(opt.seen)
144 let childopt = copy(opt)
145 let childopt.before = 0
146 let childopt.level += 1
147 let indent = repeat(' ', opt.indent)
148 for i in range(len(opt.seen))
149 if a:object is opt.seen[i]
150 return type(a:object) == type([]) ? '[...]' : '{...}'
153 if a:object is g:jdaddy#null
155 elseif a:object is g:jdaddy#false
157 elseif a:object is g:jdaddy#true
159 elseif type(a:object) ==# type('')
160 if a:object =~# '^\%x02.'
161 let dump = a:object[1:-1]
163 let dump = '"'.s:gsub(a:object, "[\001-\037\"\\\\]", '\=get(s:escapes, submatch(0), printf("\\u%04x", char2nr(submatch(0))))').'"'
165 elseif type(a:object) ==# type([])
166 let childopt.seen += [a:object]
167 let dump = '['.join(map(copy(a:object), 'jdaddy#dump(v:val, {"seen": childopt.seen, "level": childopt.level})'), ', ').']'
168 if opt.width && opt.before + opt.level * opt.indent + len(s:gsub(dump, '.', '.')) > opt.width
169 let space = repeat(indent, opt.level)
170 let dump = '[' . join(map(copy(a:object), '"\n".indent.space.jdaddy#dump(v:val, childopt)'), ",") . "\n" . space . ']'
172 elseif type(a:object) ==# type({})
173 let childopt.seen += [a:object]
174 let keys = sort(keys(a:object))
175 let dump = '{'.join(map(copy(keys), 'jdaddy#dump(v:val) . ": " . jdaddy#dump(a:object[v:val], {"seen": childopt.seen, "indent": childopt.indent, "level": childopt.level})'), ', ').'}'
176 if opt.width && opt.before + opt.level * opt.indent + len(s:gsub(dump, '.', '.')) > opt.width
177 let space = repeat(indent, opt.level)
179 let last = get(keys, -1, '')
181 let prefix = jdaddy#dump(k) . ':'
182 let suffix = jdaddy#dump(a:object[k]) . ','
183 if len(space . prefix . ' ' . suffix) >= opt.width - (k ==# last ? -1 : 0)
184 call extend(lines, [prefix . ' ' . jdaddy#dump(a:object[k], extend(copy(childopt), {'before': len(prefix)+1})) . ','])
186 call extend(lines, [prefix . ' ' . suffix])
189 let dump = s:sub("{\n" . indent . space . join(lines, "\n" . indent . space), ',$', "\n" . space . '}')
192 let dump = string(a:object)
197 function! jdaddy#reformat(func, count, ...) abort
198 let [lopen, copen, lclose, cclose] = call(a:func, [a:count])
203 let body = getline(lopen)[copen-1 : cclose-1]
205 let body = getline(lopen)[copen-1 : -1] . "\n" . join(map(getline(lopen+1, lclose-1), 'v:val."\n"'), '') . getline(lclose)[0 : cclose-1]
207 if &filetype ==# 'vim'
208 let body = substitute(body, "\n\\s*\\\\", "\n", "g")
212 let json = jdaddy#combine(jdaddy#parse(body), jdaddy#parse(getreg(a:1)))
214 let json = jdaddy#parse(body)
217 return 'echoerr '.string(v:exception)
219 let level = indent(lopen)/&sw
220 if &filetype ==# 'vim' && getline(lopen) =~# '^\s*\\'
221 let level = len(matchstr(getline(lopen), '\\\zs\s*'))/&sw
224 \ (copen == 1 ? '' : getline(lopen)[0 : copen-2]) .
225 \ jdaddy#dump(json, {'width': (&tw ? &tw : 79), 'indent': &sw, 'level': level, 'before': copen-1-level}) .
226 \ getline(lclose)[cclose : -1]
227 if &filetype ==# 'vim' && getline(lclose) =~# '^\s*\\'
228 let pre = matchstr(getline(lclose), '^\s*')
229 let dump = substitute(dump, "\n", "\n" . pre . '\\', 'g')
231 call append(lclose, split(dump, "\n"))
232 silent exe lopen.','.lclose.'delete _'
233 call setpos('.', [0, lopen, copen, 0])
234 silent! call repeat#set(":call jdaddy#reformat(".string(a:func).",".string(a:count).(a:0 ? ",".string(a:1) : "").")\<CR>")
238 function! jdaddy#combine(one, two) abort
239 if a:one is g:jdaddy#null || a:one is g:jdaddy#false
241 elseif a:two is g:jdaddy#null || a:two is g:jdaddy#false
243 elseif a:one is g:jdaddy#true
245 elseif type(a:one) != type(a:two)
246 throw "jdaddy: Can't combine disparate types"
247 elseif type(a:one) == type({})
248 return extend(copy(a:one), a:two)
249 elseif type(a:one) == type('')