Fix missing slashes in short dir.
[profile.git] / .vim / autoload / jdaddy.vim
1 " autoload/jdaddy.vim
2 " Author: Tim Pope <http://tpo.pe/>
3
4 if exists("g:autoloaded_jdaddy")
5   finish
6 endif
7 let g:autoloaded_jdaddy = 1
8
9 if !exists('g:jdaddy#null')
10   let g:jdaddy#null = ['null']
11   let g:jdaddy#false = ['false']
12   let g:jdaddy#true = ['true']
13 endif
14
15 function! s:sub(str,pat,rep) abort
16   return substitute(a:str,'\v\C'.a:pat,a:rep,'')
17 endfunction
18
19 function! s:gsub(str,pat,rep) abort
20   return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
21 endfunction
22
23 " Text Objects {{{1
24
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
30     let cnt -= 1
31     if !cnt
32       let quotes = []
33       for pos in range(len(line))
34         if exists('skip')
35           unlet skip
36         elseif line[pos] ==# '\'
37           let skip = 1
38         elseif line[pos] ==# '"'
39           let quotes += [pos]
40         endif
41       endfor
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]
46       else
47         return [line('.'), before[-1]+1, line('.'), after[0]+1]
48       endif
49     endif
50   elseif char =~# '[[:alnum:]._+-]'
51     let cnt -= 1
52     if !cnt
53       let [start, end] = [col('.')-1, col('.')-1]
54       while line[start-1] =~# '[[:alnum:]._+-]'
55         let start -= 1
56       endwhile
57       while line[end+1] =~# '[[:alnum:]._+-]'
58         let end += 1
59       endwhile
60       return [line('.'), start+1, line('.'), end+1]
61     endif
62   endif
63   if char =~# '[]})]'
64     let cnt -= 1
65     let [lclose, cclose] = [line('.'), col('.')]
66   else
67     let [lclose, cclose] = searchpairpos('[[{(]', '', '[]})]', 'W')
68   endif
69   let [lopen, copen] = searchpairpos('[[{(]', '', '[]})]', 'Wb')
70   if !lopen || !lclose
71     return [0, 0, 0, 0]
72   endif
73   return [lopen, copen, lclose, cclose]
74 endfunction
75
76 function! jdaddy#outer_pos(...) abort
77   if getline('.')[col('.')-1] =~# '[]}]'
78     let [lclose, cclose] = [line('.'), col('.')]
79   else
80     let [lclose, cclose] = searchpairpos('[[{]', '', '[]}]', 'r')
81   endif
82   let [lopen, copen] = searchpairpos('[[{]', '', '[]}]', 'rb')
83   if lopen && lclose
84     return [lopen, copen, lclose, cclose]
85   endif
86   return [0, 0, 0, 0]
87 endfunction
88
89 function! s:movement_string(line, col) abort
90   return a:line . "G0" . (a:col > 1 ? (a:col - 1) . "l" : "")
91 endfunction
92
93 function! jdaddy#inner_movement(count) abort
94   let [lopen, copen, lclose, cclose] = jdaddy#inner_pos(a:count)
95   if !lopen
96     return "\<Esc>"
97   endif
98   call setpos("'[", [0, lopen, copen, 0])
99   call setpos("']", [0, lclose, cclose, 0])
100   return "`[o`]"
101 endfunction
102
103 function! jdaddy#outer_movement(count) abort
104   let [lopen, copen, lclose, cclose] = jdaddy#outer_pos(a:count)
105   if !lopen
106     return "\<Esc>"
107   endif
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)
111 endfunction
112
113 " }}}1
114
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) . "\""',
121         \ 'g')
122   let stripped = substitute(quoted_keys,'\C"\(\\.\|[^"\\]\)*"','','g')
123   if stripped !~# "[^,:{}\\[\\]0-9.\\-+Eaeflnr-u \n\r\t]"
124     try
125       return eval(quoted_keys)
126     catch
127     endtry
128   endif
129   throw "jdaddy: invalid JSON: ".one_line
130 endfunction
131
132 let s:escapes = {
133       \ "\b": '\b',
134       \ "\f": '\f',
135       \ "\n": '\n',
136       \ "\r": '\r',
137       \ "\t": '\t',
138       \ "\"": '\"',
139       \ "\\": '\\'}
140
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([]) ? '[...]' : '{...}'
151     endif
152   endfor
153   if a:object is g:jdaddy#null
154     return 'null'
155   elseif a:object is g:jdaddy#false
156     return 'false'
157   elseif a:object is g:jdaddy#true
158     return 'true'
159   elseif type(a:object) ==# type('')
160     if a:object =~# '^\%x02.'
161       let dump = a:object[1:-1]
162     else
163       let dump = '"'.s:gsub(a:object, "[\001-\037\"\\\\]", '\=get(s:escapes, submatch(0), printf("\\u%04x", char2nr(submatch(0))))').'"'
164     endif
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 . ']'
171     endif
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)
178       let lines = []
179       let last = get(keys, -1, '')
180       for k in keys
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})) . ','])
185         else
186           call extend(lines, [prefix . ' ' . suffix])
187         endif
188       endfor
189       let dump = s:sub("{\n" . indent . space . join(lines, "\n" . indent . space), ',$', "\n" . space . '}')
190     endif
191   else
192     let dump = string(a:object)
193   endif
194   return dump
195 endfunction
196
197 function! jdaddy#reformat(func, count, ...) abort
198   let [lopen, copen, lclose, cclose] = call(a:func, [a:count])
199   if !lopen
200     return ''
201   endif
202   if lopen == lclose
203     let body = getline(lopen)[copen-1 : cclose-1]
204   else
205     let body = getline(lopen)[copen-1 : -1] . "\n" . join(map(getline(lopen+1, lclose-1), 'v:val."\n"'), '') . getline(lclose)[0 : cclose-1]
206   endif
207   if &filetype ==# 'vim'
208     let body = substitute(body, "\n\\s*\\\\", "\n", "g")
209   endif
210   try
211     if a:0
212       let json = jdaddy#combine(jdaddy#parse(body), jdaddy#parse(getreg(a:1)))
213     else
214       let json = jdaddy#parse(body)
215     endif
216   catch /^jdaddy:/
217     return 'echoerr '.string(v:exception)
218   endtry
219   let level = indent(lopen)/&sw
220   if &filetype ==# 'vim' && getline(lopen) =~# '^\s*\\'
221     let level = len(matchstr(getline(lopen), '\\\zs\s*'))/&sw
222   endif
223   let dump =
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')
230   endif
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>")
235   return ''
236 endfunction
237
238 function! jdaddy#combine(one, two) abort
239   if a:one is g:jdaddy#null || a:one is g:jdaddy#false
240     return a:two
241   elseif a:two is g:jdaddy#null || a:two is g:jdaddy#false
242     return a:one
243   elseif a:one is g:jdaddy#true
244     return a:one
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('')
250     return a:one . a:two
251   else
252     return a:one + a:two
253   endif
254 endfunction
255
256 " vim:set et sw=2: