" scriptease.vim - An amalgamation of crap for editing runtime files
" Maintainer: Tim Pope
" Version: 1.0
if exists('g:loaded_scriptease') || &cp
finish
endif
let g:loaded_scriptease = 1
" Utility {{{1
function! s:function(name) abort
return function(substitute(a:name,'^s:',matchstr(expand(''), '.*\zs\d\+_'),''))
endfunction
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
function! s:shellesc(arg) abort
if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
return a:arg
elseif &shell =~# 'cmd' && a:arg !~# '"'
return '"'.a:arg.'"'
else
return shellescape(a:arg)
endif
endfunction
function! s:fnameescape(file) abort
if exists('*fnameescape')
return fnameescape(a:file)
else
return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
endif
endfunction
function! s:shellslash(path) abort
if exists('+shellslash') && !&shellslash
return s:gsub(a:path,'\\','/')
else
return a:path
endif
endfunction
" }}}1
" Completion {{{1
function! s:Complete(A,L,P)
let sep = !exists("+shellslash") || &shellslash ? '/' : '\'
let cheats = {
\ 'a': 'autoload',
\ 'd': 'doc',
\ 'f': 'ftplugin',
\ 'i': 'indent',
\ 'p': 'plugin',
\ 's': 'syntax'}
if a:A =~# '^\w[\\/]' && has_key(cheats,a:A[0])
let request = cheats[a:A[0]].a:A[1:-1]
else
let request = a:A
endif
let pattern = substitute(request,'/\|\'.sep,'*'.sep,'g').'*'
let found = {}
for glob in split(&runtimepath, ',')
for path in map(split(glob(glob), "\n"), 'fnamemodify(v:val, ":p")')
let matches = split(glob(path.sep.pattern),"\n")
call map(matches,'isdirectory(v:val) ? v:val.sep : v:val')
call map(matches,'fnamemodify(v:val, ":p")[strlen(path)+1:-1]')
for match in matches
let found[match] = 1
endfor
endfor
endfor
return sort(keys(found))
endfunction
" }}}1
" :PP, :PPmsg {{{1
let s:escapes = {
\ "\b": '\b',
\ "\e": '\e',
\ "\f": '\f',
\ "\n": '\n',
\ "\r": '\r',
\ "\t": '\t',
\ "\"": '\"',
\ "\\": '\\'}
function! scriptease#dump(object, ...) abort
let opt = extend({'width': 0, 'level': 0, 'indent': 1, 'tail': 0, 'seen': []}, a:0 ? copy(a:1) : {})
let opt.seen = copy(opt.seen)
let childopt = copy(opt)
let childopt.tail += 1
let childopt.level += 1
for i in range(len(opt.seen))
if a:object is opt.seen[i]
return type(a:object) == type([]) ? '[...]' : '{...}'
endif
endfor
if type(a:object) ==# type('')
if a:object =~# "[\001-\037']"
let dump = '"'.s:gsub(a:object, "[\001-\037\"\\\\]", '\=get(s:escapes, submatch(0), printf("\\%03o", char2nr(submatch(0))))').'"'
else
let dump = string(a:object)
endif
elseif type(a:object) ==# type([])
let childopt.seen += [a:object]
let dump = '['.join(map(copy(a:object), 'scriptease#dump(v:val, {"seen": childopt.seen, "level": childopt.level})'), ', ').']'
if opt.width && opt.level + len(s:gsub(dump, '.', '.')) > opt.width
let space = repeat(' ', opt.level)
let dump = "[".join(map(copy(a:object), 'scriptease#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), 'scriptease#dump(v:val) . ": " . scriptease#dump(a:object[v:val], {"seen": childopt.seen, "level": childopt.level})'), ', ').'}'
if opt.width && opt.level + len(s:gsub(dump, '.', '.')) > opt.width
let space = repeat(' ', opt.level)
let lines = []
let last = get(keys, -1, '')
for k in keys
let prefix = scriptease#dump(k) . ':'
let suffix = scriptease#dump(a:object[k]) . ','
if len(space . prefix . ' ' . suffix) >= opt.width - (k ==# last ? opt.tail : '')
call extend(lines, [prefix, scriptease#dump(a:object[k], childopt) . ','])
else
call extend(lines, [prefix . ' ' . suffix])
endif
endfor
let dump = s:sub("{".join(lines, "\n " . space), ',$', '}')
endif
elseif type(a:object) ==# type(function('tr'))
let dump = s:sub(string(a:object), '^function\(''(\d+)''\)$', 'function(''{\1}'')')
else
let dump = string(a:object)
endif
return dump
endfunction
function! s:backslashdump(value, indent)
let out = scriptease#dump(a:value, {'level': 0, 'width': &textwidth - &shiftwidth * 3 - a:indent})
return s:gsub(out, '\n', "\n".repeat(' ', a:indent + &shiftwidth * 3).'\\')
endfunction
function! s:dump(bang, lnum, value) abort
if v:errmsg !=# ''
return
elseif a:lnum == 999998
echo scriptease#dump(a:value, {'width': a:bang ? 0 : &columns-1})
else
exe a:lnum
let indent = indent(prevnonblank('.'))
if a:bang
let out = scriptease#dump(a:value)
else
let out = s:backslashdump(a:value, indent)
endif
put =repeat(' ', indent).'PP '.out
'[
endif
endfunction
function! s:dumpmsg(bang, count, value) abort
if v:errmsg !=# ''
return
elseif &verbose >= a:count
for line in split(scriptease#dump(a:value, {'width': a:bang ? 0 : &columns-1}), "\n")
echomsg line
endfor
endif
endfunction
command! -bang -range=999998 -nargs=? -complete=expression PP
\ if empty() |
\ let s:more = &more |
\ try |
\ set nomore |
\ while 1 |
\ let s:input = input('PP> ', '', 'expression') |
\ if empty(s:input) |
\ break |
\ endif |
\ echon "\n" |
\ let v:errmsg = '' |
\ try |
\ call s:dump(0, 999998, eval(s:input)) |
\ catch |
\ echohl ErrorMsg |
\ echo v:exception |
\ echo v:throwpoint |
\ echohl NONE |
\ endtry |
\ endwhile |
\ finally |
\ let &more = s:more |
\ endtry |
\ else |
\ let v:errmsg = '' |
\ call s:dump(0, , eval()) |
\ endif
command! -bang -range=0 -nargs=? -complete=expression PPmsg
\ if !empty() |
\ let v:errmsg = '' |
\ call s:dumpmsg(0, , empty() ? expand('') : eval()) |
\ elseif &verbose >= && !empty(expand('')) |
\ echomsg expand('').', line '.expand('') |
\ endif
" }}}1
" g! {{{1
function! s:opfunc(type) abort
let sel_save = &selection
let cb_save = &clipboard
let reg_save = @@
try
set selection=inclusive clipboard-=unnamed clipboard-=unnamedplus
if a:type =~ '^\d\+$'
silent exe 'normal! ^v'.a:type.'$hy'
elseif a:type =~# '^.$'
silent exe "normal! `<" . a:type . "`>y"
elseif a:type ==# 'line'
silent exe "normal! '[V']y"
elseif a:type ==# 'block'
silent exe "normal! `[\`]y"
else
silent exe "normal! `[v`]y"
endif
redraw
return @@
finally
let @@ = reg_save
let &selection = sel_save
let &clipboard = cb_save
endtry
endfunction
function! s:filterop(type) abort
let reg_save = @@
try
let expr = s:opfunc(a:type)
let @@ = matchstr(expr, '^\_s\+').scriptease#dump(eval(s:gsub(expr,'\n%(\s*\\)=',''))).matchstr(expr, '\_s\+$')
if @@ !~# '^\n*$'
normal! gvp
endif
catch /^.*/
echohl ErrorMSG
echo v:errmsg
echohl NONE
finally
let @@ = reg_save
endtry
endfunction
nnoremap ScripteaseFilter :set opfunc=filteropg@
xnoremap ScripteaseFilter :call filterop(visualmode())
nmap g! ScripteaseFilter
nmap g!! ScripteaseFilter_
xmap g! ScripteaseFilter
" }}}1
" :Verbose {{{1
command! -range=999998 -nargs=1 -complete=command Verbose
\ :exe s:Verbose( == 999998 ? '' : , )
function! s:Verbose(level, excmd)
let temp = tempname()
let verbosefile = &verbosefile
call writefile([':'.a:level.'Verbose '.a:excmd], temp, 'b')
return
\ 'try|' .
\ 'let &verbosefile = '.string(temp).'|' .
\ 'silent '.a:level.'verbose exe '.string(a:excmd).'|' .
\ 'finally|' .
\ 'let &verbosefile = '.string(verbosefile).'|' .
\ 'endtry|' .
\ 'pedit '.temp.'|wincmd P|nnoremap q :bd'
endfunction
" }}}1
" :Scriptnames {{{1
function! scriptease#capture(excmd) abort
try
redir => out
exe 'silent! '.a:excmd
finally
redir END
endtry
return out
endfunction
function! s:names() abort
let names = scriptease#capture('scriptnames')
let list = []
for line in split(names, "\n")
if line =~# ':'
call add(list, {'text': matchstr(line, '\d\+'), 'filename': expand(matchstr(line, ': \zs.*'))})
endif
endfor
return list
endfunction
function! scriptease#scriptname(file) abort
if a:file =~# '^\d\+$'
return get(s:names(), a:file-1, {'filename': a:file}).filename
else
return a:file
endif
endfunction
function! scriptease#scriptid(filename) abort
let filename = fnamemodify(expand(a:filename), ':p')
for script in s:names()
if script.filename ==# filename
return +script.text
endif
endfor
return ''
endfunction
command! -bar Scriptnames call setqflist(s:names())|copen
" }}}1
" :Runtime {{{1
function! s:unlet_for(files) abort
let guards = []
for file in a:files
if filereadable(file)
let lines = readfile(file, '', 500)
if len(lines)
for i in range(len(lines)-1)
let unlet = matchstr(lines[i], '^if .*\'
call extend(guards, [unlet])
break
endif
endfor
endif
endfor
endif
endif
endfor
if empty(guards)
return ''
else
return 'unlet! '.join(map(guards, '"g:".v:val'), ' ')
endif
endfunction
function! s:lencompare(a, b)
return len(a:a) - len(a:b)
endfunction
function! s:findinrtp(path)
let path = fnamemodify(a:path, ':p')
let candidates = []
for glob in split(&runtimepath, ',')
let candidates += filter(split(glob(glob), "\n"), 'path[0 : len(v:val)-1] ==# v:val && path[len(v:val)] =~# "[\\/]"')
endfor
if empty(candidates)
return ['', '']
endif
let preferred = sort(candidates, s:function('s:lencompare'))[-1]
return [preferred, path[strlen(preferred)+1 : -1]]
endfunction
function! s:runtime(bang, ...) abort
let unlets = []
let do = []
let predo = ''
if a:0
let files = a:000
elseif &filetype ==# 'vim' || expand('%:e') ==# 'vim'
let files = [s:findinrtp(expand('%:p'))[1]]
if empty(files[0])
let files = ['%']
endif
if &modified && (&autowrite || &autowriteall)
let predo = 'silent wall|'
endif
else
for ft in split(&filetype, '\.')
for pattern in ['ftplugin/%s.vim', 'ftplugin/%s_*.vim', 'ftplugin/%s/*.vim', 'indent/%s.vim', 'syntax/%s.vim', 'syntax/%s/*.vim']
call extend(unlets, split(globpath(&rtp, printf(pattern, ft)), "\n"))
endfor
endfor
let run = s:unlet_for(unlets)
if run !=# ''
let run .= '|'
endif
let run .= 'filetype detect'
echo ':'.run
return run
endif
for request in files
if request =~# '^\.\=[\\/]\|^\w:[\\/]\|^[%#~]\|^\d\+$'
let request = scriptease#scriptname(request)
let unlets += split(glob(request), "\n")
let do += map(copy(unlets), '"source ".escape(v:val, " \t|!")')
else
if get(do, 0, [''])[0] !~# '^runtime!'
let do += ['runtime!']
endif
let unlets += split(globpath(&rtp, request, 1), "\n")
let do[-1] .= ' '.escape(request, " \t|!")
endif
endfor
call extend(do, ['filetype detect'])
let run = s:unlet_for(unlets)
if run !=# ''
let run .= '|'
endif
let run .= join(do, '|')
echo ':'.run
return predo.run
endfunction
command! -bang -bar -nargs=* -complete=customlist,s:Complete Runtime
\ :exe s:runtime('', )
" }}}1
" :Disarm {{{1
function! scriptease#disarm(file)
let augroups = filter(readfile(a:file), 'v:val =~# "^\\s*aug\\%[roup]\\s"')
call filter(augroups, 'v:val !~# "^\\s*aug\\%[roup]\\s\\+END"')
for augroup in augroups
exe augroup
autocmd!
augroup END
exe s:sub(augroup, 'aug\%[roup]', '&!')
endfor
call s:disable_maps_and_commands(a:file, 0)
let tabnr = tabpagenr()
let winnr = winnr()
let altwinnr = winnr('#')
tabdo windo call s:disable_maps_and_commands(a:file, 1)
exe 'tabnext '.tabnr
exe altwinnr.'wincmd w'
exe winnr.'wincmd w'
return s:unlet_for([a:file])
endfunction
function! s:disable_maps_and_commands(file, buf)
let last_set = "\tLast set from " . fnamemodify(a:file, ':~')
for line in split(scriptease#capture('verbose command'), "\n")
if line ==# last_set
if last[2] ==# (a:buf ? 'b' : ' ')
exe 'delcommand '.matchstr(last[4:-1], '^\w\+')
endif
else
let last = line
endif
endfor
for line in split(scriptease#capture('verbose map').scriptease#capture('verbose map!'), "\n")
if line ==# last_set
let map = matchstr(last, '^.\s\+\zs\S\+')
let rest = matchstr(last, '^.\s\+\S\+\s\+\zs[&* ][ @].*')
if rest[1] ==# (a:buf ? '@' : ' ')
let cmd = last =~# '^!' ? 'unmap! ' : last[0].'unmap '
exe cmd.(a:buf ? '' : '').map
endif
else
let last = line
endif
endfor
endfunction
function! s:disarm(...) abort
let files = []
let unlets = []
for request in a:000
if request =~# '^\.\=[\\/]\|^\w:[\\/]\|^[%#~]\|^\d\+$'
let request = expand(scriptease#scriptname(request))
if isdirectory(request)
let request .= "/**/*.vim"
endif
let files += split(glob(request), "\n")
else
let files += split(globpath(&rtp, request, 1), "\n")
endif
endfor
for file in files
let unlets += [scriptease#disarm(expand(file))]
endfor
echo join(files, ' ')
return join(filter(unlets, 'v:val !=# ""'), '|')
endfunction
command! -bang -bar -nargs=* -complete=customlist,s:Complete Disarm
\ :exe s:disarm()
" }}}1
" :Breakadd, :Breakdel {{{1
augroup scriptease_breakadd
autocmd!
autocmd FileType vim command!
\ -buffer -bar -nargs=? -complete=custom,s:Complete_breakadd Breakadd
\ :exe s:break('add',)
autocmd FileType vim command!
\ -buffer -bar -nargs=? -complete=custom,s:Complete_breakdel Breakdel
\ :exe s:break('del',)
augroup END
function! s:breaksnr(arg) abort
let id = scriptease#scriptid('%')
if id
return s:gsub(a:arg, '^func.*\zs%()', ''.id.'_')
else
return a:arg
endif
endfunction
function! s:break(type, arg) abort
if a:arg ==# 'here' || a:arg ==# ''
let lnum = searchpair('^\s*fu\%[nction]\>.*(', '', '^\s*endf\%[unction]\>', 'Wbn')
if lnum && lnum < line('.')
let function = matchstr(getline(lnum), '^\s*\w\+!\=\s*\zs[^( ]*')
if function =~# '^s:\|^'
let id = scriptease#scriptid('%')
if id
let function = s:sub(function, '^s:|^\', ''.id.'_')
else
return 'echoerr "Could not determine script id"'
endif
endif
if function =~# '\.'
return 'echoerr "Dictionary functions not supported"'
endif
return 'break'.a:type.' func '.(line('.')==lnum ? '' : line('.')-lnum).' '.function
else
return 'break'.a:type.' here'
endif
endif
return 'break'.a:type.' '.s:breaksnr(a:arg)
endfunction
function! s:Complete_breakadd(A, L, P)
let functions = join(sort(map(split(scriptease#capture('function'), "\n"), 'matchstr(v:val, " \\zs[^(]*")')), "\n")
if a:L =~# '^\w\+\s\+\w*$'
return "here\nfile\nfunc"
elseif a:L =~# '^\w\+\s\+func\s*\d*\s\+s:'
let id = scriptease#scriptid('%')
return s:gsub(functions,'\'.id.'_', 's:')
elseif a:L =~# '^\w\+\s\+func '
return functions
elseif a:L =~# '^\w\+\s\+file '
return glob(a:A."*")
else
return ''
endif
endfunction
function! s:Complete_breakdel(A, L, P)
let args = matchstr(a:L, '\s\zs\S.*')
let list = split(scriptease#capture('breaklist'), "\n")
call map(list, 's:sub(v:val, ''^\s*\d+\s*(\w+) (.*) line (\d+)$'', ''\1 \3 \2'')')
if a:L =~# '^\w\+\s\+\w*$'
return "*\nhere\nfile\nfunc"
elseif a:L =~# '^\w\+\s\+func\s'
return join(map(filter(list, 'v:val =~# "^func"'), 'v:val[5 : -1]'), "\n")
elseif a:L =~# '^\w\+\s\+file\s'
return join(map(filter(list, 'v:val =~# "^file"'), 'v:val[5 : -1]'), "\n")
else
return ''
endif
endfunction
" }}}1
" :Vopen, :Vedit, ... {{{1
function! s:previewwindow()
for i in range(1, winnr('$'))
if getwinvar(i, '&previewwindow') == 1
return i
endif
endfor
return -1
endfunction
function! s:runtime_globpath(file)
return split(globpath(escape(&runtimepath, ' '), a:file), "\n")
endfunction
function! s:find(count,cmd,file,lcd)
let found = s:runtime_globpath(a:file)
let file = get(found, a:count - 1, '')
if file ==# ''
return "echoerr 'E345: Can''t find file \"".a:file."\" in runtimepath'"
elseif a:cmd ==# 'read'
return a:cmd.' '.s:fnameescape(file)
elseif a:lcd
let path = file[0:-strlen(a:file)-2]
return a:cmd.' '.s:fnameescape(file) . '|lcd '.s:fnameescape(path)
else
let window = 0
let precmd = ''
let postcmd = ''
if a:cmd =~# '^pedit'
try
exe 'silent ' . a:cmd
catch /^Vim\%((\a\+)\)\=:E32/
endtry
let window = s:previewwindow()
let precmd = printf('%d wincmd w|', window)
let postcmd = '|wincmd w'
elseif a:cmd !~# '^edit'
exe a:cmd
endif
call setloclist(window, map(found,
\ '{"filename": v:val, "text": v:val[0 : -len(a:file)-2]}'))
return precmd . 'll'.matchstr(a:cmd, '!$').' '.a:count . postcmd
endif
endfunction
command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Complete Ve
\ :execute s:find(,'edit',,0)
command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Complete Vedit
\ :execute s:find(,'edit',,0)
command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Complete Vopen
\ :execute s:find(,'edit',,1)
command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Complete Vsplit
\ :execute s:find(,'split',,0)
command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Complete Vvsplit
\ :execute s:find(,'vsplit',,0)
command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Complete Vtabedit
\ :execute s:find(,'tabedit',,0)
command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Complete Vpedit
\ :execute s:find(,'pedit',,0)
command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Complete Vread
\ :execute s:find(,'read',,0)
" }}}1
" :Time {{{1
command! -count=1 -nargs=? -complete=command Time :exe s:time(, )
function! s:time(cmd, count)
let time = reltime()
try
if a:count > 1
let i = 0
while i < a:count
execute a:cmd
let i += 1
endwhile
else
execute a:cmd
endif
finally
redraw
echomsg matchstr(reltimestr(reltime(time)), '.*\..\{,3\}') . ' seconds to run :'.a:cmd
endtry
return ''
endfunction
" }}}1
" zS {{{1
function! scriptease#synnames(...) abort
if a:0
let [line, col] = [a:1, a:2]
else
let [line, col] = [line('.'), col('.')]
endif
return reverse(map(synstack(line, col), 'synIDattr(v:val,"name")'))
endfunction
function! s:zS(count)
if a:count
let name = get(scriptease#synnames(), a:count-1, '')
if name !=# ''
return 'syntax list '.name
endif
else
echo join(scriptease#synnames(), ' ')
endif
return ''
endfunction
nnoremap ScripteaseSynnames :exe zS(v:count)
nmap zS ScripteaseSynnames
" }}}1
" K {{{1
augroup scriptease_help
autocmd!
autocmd FileType vim nnoremap K :exe 'help '.helptopic()
augroup END
function! s:helptopic()
let col = col('.') - 1
while col && getline('.')[col] =~# '\k'
let col -= 1
endwhile
let pre = col == 0 ? '' : getline('.')[0 : col]
let col = col('.') - 1
while col && getline('.')[col] =~# '\k'
let col += 1
endwhile
let post = getline('.')[col : -1]
let syn = get(scriptease#synnames(), 0, '')
let cword = expand('')
if syn ==# 'vimFuncName'
return cword.'()'
elseif syn ==# 'vimOption'
return "'".cword."'"
elseif syn ==# 'vimUserAttrbKey'
return ':command-'.cword
elseif pre =~# '^\s*:\=$'
return ':'.cword
elseif pre =~# '\