" autoload/jdaddy.vim " Author: Tim Pope if exists("g:autoloaded_jdaddy") finish endif let g:autoloaded_jdaddy = 1 if !exists('g:jdaddy#null') let g:jdaddy#null = ['null'] let g:jdaddy#false = ['false'] let g:jdaddy#true = ['true'] endif function! s:sub(str,pat,rep) abort return substitute(a:str,'\v\C'.a:pat,a:rep,'') endfunction function! s:gsub(str,pat,rep) abort return substitute(a:str,'\v\C'.a:pat,a:rep,'g') endfunction " Text Objects {{{1 function! jdaddy#inner_pos(...) abort let cnt = a:0 ? a:1 : 1 let line = getline('.') let char = line[col('.')-1] if char ==# '"' || len(s:gsub(s:gsub(line[0 : col('.')-1], '\\.', ''), '[^"]', '')) % 2 let cnt -= 1 if !cnt let quotes = [] for pos in range(len(line)) if exists('skip') unlet skip elseif line[pos] ==# '\' let skip = 1 elseif line[pos] ==# '"' let quotes += [pos] endif endfor let before = filter(copy(quotes), 'v:val <= col(".")-1') let after = filter(copy(quotes), 'v:val > col(".")-1') if before[-1] == col('.')-1 && len(before) % 2 == 0 return [line('.'), before[-2]+1, line('.'), before[-1]+1] else return [line('.'), before[-1]+1, line('.'), after[0]+1] endif endif elseif char =~# '[[:alnum:]._+-]' let cnt -= 1 if !cnt let [start, end] = [col('.')-1, col('.')-1] while line[start-1] =~# '[[:alnum:]._+-]' let start -= 1 endwhile while line[end+1] =~# '[[:alnum:]._+-]' let end += 1 endwhile return [line('.'), start+1, line('.'), end+1] endif endif if char =~# '[]})]' let cnt -= 1 let [lclose, cclose] = [line('.'), col('.')] else let [lclose, cclose] = searchpairpos('[[{(]', '', '[]})]', 'W') endif let [lopen, copen] = searchpairpos('[[{(]', '', '[]})]', 'Wb') if !lopen || !lclose return [0, 0, 0, 0] endif return [lopen, copen, lclose, cclose] endfunction function! jdaddy#outer_pos(...) abort if getline('.')[col('.')-1] =~# '[]}]' let [lclose, cclose] = [line('.'), col('.')] else let [lclose, cclose] = searchpairpos('[[{]', '', '[]}]', 'r') endif let [lopen, copen] = searchpairpos('[[{]', '', '[]}]', 'rb') if lopen && lclose return [lopen, copen, lclose, cclose] endif return [0, 0, 0, 0] endfunction function! s:movement_string(line, col) abort return a:line . "G0" . (a:col > 1 ? (a:col - 1) . "l" : "") endfunction function! jdaddy#inner_movement(count) abort let [lopen, copen, lclose, cclose] = jdaddy#inner_pos(a:count) if !lopen return "\" endif call setpos("'[", [0, lopen, copen, 0]) call setpos("']", [0, lclose, cclose, 0]) return "`[o`]" endfunction function! jdaddy#outer_movement(count) abort let [lopen, copen, lclose, cclose] = jdaddy#outer_pos(a:count) if !lopen return "\" endif call setpos("'[", [0, lopen, copen, 0]) call setpos("']", [0, lclose, cclose, 0]) return s:movement_string(lopen, copen) . 'o' . s:movement_string(lclose, cclose) endfunction " }}}1 function! jdaddy#parse(string) abort let [null, false, true] = [g:jdaddy#null, g:jdaddy#false, g:jdaddy#true] let one_line = substitute(a:string, "[\r\n]\\s*", ' ', 'g') let quoted_keys = substitute(one_line, \ '\C"\(\\.\|[^"\\]\)*"\|\w\+\ze:\|[[:,]\s*\zs\h\w*\ze\s*[]},]', \ '\=submatch(0) =~# "^\\%(\"\\|true$\\|false$\\|null$\\)" ? submatch(0) : "\"\002" . submatch(0) . "\""', \ 'g') let stripped = substitute(quoted_keys,'\C"\(\\.\|[^"\\]\)*"','','g') if stripped !~# "[^,:{}\\[\\]0-9.\\-+Eaeflnr-u \n\r\t]" try return eval(quoted_keys) catch endtry endif throw "jdaddy: invalid JSON: ".one_line endfunction let s:escapes = { \ "\b": '\b', \ "\f": '\f', \ "\n": '\n', \ "\r": '\r', \ "\t": '\t', \ "\"": '\"', \ "\\": '\\'} function! jdaddy#dump(object, ...) abort let opt = extend({'width': 0, 'level': 0, 'indent': 1, 'before': 0, 'seen': []}, a:0 ? copy(a:1) : {}) let opt.seen = copy(opt.seen) let childopt = copy(opt) let childopt.before = 0 let childopt.level += 1 let indent = repeat(' ', opt.indent) for i in range(len(opt.seen)) if a:object is opt.seen[i] return type(a:object) == type([]) ? '[...]' : '{...}' endif endfor if a:object is g:jdaddy#null return 'null' elseif a:object is g:jdaddy#false return 'false' elseif a:object is g:jdaddy#true return 'true' elseif type(a:object) ==# type('') if a:object =~# '^\%x02.' let dump = a:object[1:-1] else let dump = '"'.s:gsub(a:object, "[\001-\037\"\\\\]", '\=get(s:escapes, submatch(0), printf("\\u%04x", char2nr(submatch(0))))').'"' endif elseif type(a:object) ==# type([]) let childopt.seen += [a:object] let dump = '['.join(map(copy(a:object), 'jdaddy#dump(v:val, {"seen": childopt.seen, "level": childopt.level})'), ', ').']' if opt.width && opt.before + opt.level * opt.indent + len(s:gsub(dump, '.', '.')) > opt.width let space = repeat(indent, opt.level) let dump = '[' . join(map(copy(a:object), '"\n".indent.space.jdaddy#dump(v:val, childopt)'), ",") . "\n" . space . ']' endif elseif type(a:object) ==# type({}) let childopt.seen += [a:object] let keys = sort(keys(a:object)) 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})'), ', ').'}' if opt.width && opt.before + opt.level * opt.indent + len(s:gsub(dump, '.', '.')) > opt.width let space = repeat(indent, opt.level) let lines = [] let last = get(keys, -1, '') for k in keys let prefix = jdaddy#dump(k) . ':' let suffix = jdaddy#dump(a:object[k]) . ',' if len(space . prefix . ' ' . suffix) >= opt.width - (k ==# last ? -1 : 0) call extend(lines, [prefix . ' ' . jdaddy#dump(a:object[k], extend(copy(childopt), {'before': len(prefix)+1})) . ',']) else call extend(lines, [prefix . ' ' . suffix]) endif endfor let dump = s:sub("{\n" . indent . space . join(lines, "\n" . indent . space), ',$', "\n" . space . '}') endif else let dump = string(a:object) endif return dump endfunction function! jdaddy#reformat(func, count, ...) abort let [lopen, copen, lclose, cclose] = call(a:func, [a:count]) if !lopen return '' endif if lopen == lclose let body = getline(lopen)[copen-1 : cclose-1] else let body = getline(lopen)[copen-1 : -1] . "\n" . join(map(getline(lopen+1, lclose-1), 'v:val."\n"'), '') . getline(lclose)[0 : cclose-1] endif if &filetype ==# 'vim' let body = substitute(body, "\n\\s*\\\\", "\n", "g") endif try if a:0 let json = jdaddy#combine(jdaddy#parse(body), jdaddy#parse(getreg(a:1))) else let json = jdaddy#parse(body) endif catch /^jdaddy:/ return 'echoerr '.string(v:exception) endtry let level = indent(lopen)/&sw if &filetype ==# 'vim' && getline(lopen) =~# '^\s*\\' let level = len(matchstr(getline(lopen), '\\\zs\s*'))/&sw endif let dump = \ (copen == 1 ? '' : getline(lopen)[0 : copen-2]) . \ jdaddy#dump(json, {'width': (&tw ? &tw : 79), 'indent': &sw, 'level': level, 'before': copen-1-level}) . \ getline(lclose)[cclose : -1] if &filetype ==# 'vim' && getline(lclose) =~# '^\s*\\' let pre = matchstr(getline(lclose), '^\s*') let dump = substitute(dump, "\n", "\n" . pre . '\\', 'g') endif call append(lclose, split(dump, "\n")) silent exe lopen.','.lclose.'delete _' call setpos('.', [0, lopen, copen, 0]) silent! call repeat#set(":call jdaddy#reformat(".string(a:func).",".string(a:count).(a:0 ? ",".string(a:1) : "").")\") return '' endfunction function! jdaddy#combine(one, two) abort if a:one is g:jdaddy#null || a:one is g:jdaddy#false return a:two elseif a:two is g:jdaddy#null || a:two is g:jdaddy#false return a:one elseif a:one is g:jdaddy#true return a:one elseif type(a:one) != type(a:two) throw "jdaddy: Can't combine disparate types" elseif type(a:one) == type({}) return extend(copy(a:one), a:two) elseif type(a:one) == type('') return a:one . a:two else return a:one + a:two endif endfunction " vim:set et sw=2: