Update vim-plug

This commit is contained in:
Kenneth Benzie 2017-05-05 21:23:33 +01:00
parent c953ae7dbf
commit be47e8ec7b

View File

@ -18,7 +18,7 @@
" " Any valid git URL is allowed " " Any valid git URL is allowed
" Plug 'https://github.com/junegunn/vim-github-dashboard.git' " Plug 'https://github.com/junegunn/vim-github-dashboard.git'
" "
" " Group dependencies, vim-snippets depends on ultisnips " " Multiple Plug commands can be written in a single line using | separators
" Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets' " Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets'
" "
" " On-demand loading " " On-demand loading
@ -40,7 +40,7 @@
" " Unmanaged plugin (manually installed and updated) " " Unmanaged plugin (manually installed and updated)
" Plug '~/my-prototype-plugin' " Plug '~/my-prototype-plugin'
" "
" " Add plugins to &runtimepath " " Initialize plugin system
" call plug#end() " call plug#end()
" "
" Then reload .vimrc and :PlugInstall to install plugins. " Then reload .vimrc and :PlugInstall to install plugins.
@ -61,7 +61,7 @@
" More information: https://github.com/junegunn/vim-plug " More information: https://github.com/junegunn/vim-plug
" "
" "
" Copyright (c) 2016 Junegunn Choi " Copyright (c) 2017 Junegunn Choi
" "
" MIT License " MIT License
" "
@ -97,7 +97,8 @@ let s:plug_tab = get(s:, 'plug_tab', -1)
let s:plug_buf = get(s:, 'plug_buf', -1) let s:plug_buf = get(s:, 'plug_buf', -1)
let s:mac_gui = has('gui_macvim') && has('gui_running') let s:mac_gui = has('gui_macvim') && has('gui_running')
let s:is_win = has('win32') || has('win64') let s:is_win = has('win32') || has('win64')
let s:nvim = has('nvim') && exists('*jobwait') && !s:is_win let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win)
let s:vim8 = has('patch-8.0.0039') && exists('*job_start')
let s:me = resolve(expand('<sfile>:p')) let s:me = resolve(expand('<sfile>:p'))
let s:base_spec = { 'branch': 'master', 'frozen': 0 } let s:base_spec = { 'branch': 'master', 'frozen': 0 }
let s:TYPE = { let s:TYPE = {
@ -131,7 +132,7 @@ function! plug#begin(...)
endfunction endfunction
function! s:define_commands() function! s:define_commands()
command! -nargs=+ -bar Plug call s:Plug(<args>) command! -nargs=+ -bar Plug call plug#(<args>)
if !executable('git') if !executable('git')
return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
endif endif
@ -171,14 +172,22 @@ function! s:assoc(dict, key, val)
let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
endfunction endfunction
function! s:ask(message) function! s:ask(message, ...)
call inputsave() call inputsave()
echohl WarningMsg echohl WarningMsg
let proceed = input(a:message.' (y/N) ') =~? '^y' let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
echohl None echohl None
call inputrestore() call inputrestore()
echo "\r" echo "\r"
return proceed return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
endfunction
function! s:ask_no_interrupt(...)
try
return call('s:ask', a:000)
catch
return 0
endtry
endfunction endfunction
function! plug#end() function! plug#end()
@ -194,8 +203,13 @@ function! plug#end()
endif endif
let lod = { 'ft': {}, 'map': {}, 'cmd': {} } let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
if exists('g:did_load_filetypes')
filetype off filetype off
endif
for name in g:plugs_order for name in g:plugs_order
if !has_key(g:plugs, name)
continue
endif
let plug = g:plugs[name] let plug = g:plugs[name]
if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for') if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for')
let s:loaded[name] = 1 let s:loaded[name] = 1
@ -211,6 +225,7 @@ function! plug#end()
endif endif
call add(s:triggers[name].map, cmd) call add(s:triggers[name].map, cmd)
elseif cmd =~# '^[A-Z]' elseif cmd =~# '^[A-Z]'
let cmd = substitute(cmd, '!*$', '', '')
if exists(':'.cmd) != 2 if exists(':'.cmd) != 2
call s:assoc(lod.cmd, cmd, name) call s:assoc(lod.cmd, cmd, name)
endif endif
@ -237,7 +252,7 @@ function! plug#end()
for [cmd, names] in items(lod.cmd) for [cmd, names] in items(lod.cmd)
execute printf( execute printf(
\ 'command! -nargs=* -range -bang %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)', \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
\ cmd, string(cmd), string(names)) \ cmd, string(cmd), string(names))
endfor endfor
@ -245,8 +260,8 @@ function! plug#end()
for [mode, map_prefix, key_prefix] in for [mode, map_prefix, key_prefix] in
\ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
execute printf( execute printf(
\ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, "%s")<CR>', \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
\ mode, map, map_prefix, string(map), string(names), key_prefix) \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
endfor endfor
endfor endfor
@ -264,7 +279,7 @@ function! plug#end()
syntax enable syntax enable
end end
else else
call s:reload() call s:reload_plugins()
endif endif
endfunction endfunction
@ -272,9 +287,13 @@ function! s:loaded_names()
return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
endfunction endfunction
function! s:reload() function! s:load_plugin(spec)
call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
endfunction
function! s:reload_plugins()
for name in s:loaded_names() for name in s:loaded_names()
call s:source(s:rtp(g:plugs[name]), 'plugin/**/*.vim', 'after/plugin/**/*.vim') call s:load_plugin(g:plugs[name])
endfor endfor
endfunction endfunction
@ -294,7 +313,7 @@ endfunction
function! s:git_version_requirement(...) function! s:git_version_requirement(...)
if !exists('s:git_version') if !exists('s:git_version')
let s:git_version = map(split(split(s:system('git --version'))[-1], '\.'), 'str2nr(v:val)') let s:git_version = map(split(split(s:system('git --version'))[2], '\.'), 'str2nr(v:val)')
endif endif
return s:version_requirement(s:git_version, a:000) return s:version_requirement(s:git_version, a:000)
endfunction endfunction
@ -383,7 +402,7 @@ function! s:reorg_rtp()
let s:middle = get(s:, 'middle', &rtp) let s:middle = get(s:, 'middle', &rtp)
let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), 'isdirectory(v:val)') let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
\ . ','.s:middle.',' \ . ','.s:middle.','
\ . join(map(afters, 'escape(v:val, ",")'), ',') \ . join(map(afters, 'escape(v:val, ",")'), ',')
@ -397,7 +416,23 @@ function! s:reorg_rtp()
endfunction endfunction
function! s:doautocmd(...) function! s:doautocmd(...)
if exists('#'.join(a:000, '#'))
execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000) execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
endif
endfunction
function! s:dobufread(names)
for name in a:names
let path = s:rtp(g:plugs[name]).'/**'
for dir in ['ftdetect', 'ftplugin']
if len(finddir(dir, path))
if exists('#BufRead')
doautocmd BufRead
endif
return
endif
endfor
endfor
endfunction endfunction
function! plug#load(...) function! plug#load(...)
@ -412,13 +447,15 @@ function! plug#load(...)
let s = len(unknowns) > 1 ? 's' : '' let s = len(unknowns) > 1 ? 's' : ''
return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
end end
for name in a:000 let unloaded = filter(copy(a:000), '!get(s:loaded, v:val, 0)')
if !empty(unloaded)
for name in unloaded
call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
endfor endfor
if exists('#BufRead') call s:dobufread(unloaded)
doautocmd BufRead
endif
return 1 return 1
end
return 0
endfunction endfunction
function! s:remove_triggers(name) function! s:remove_triggers(name)
@ -453,9 +490,7 @@ function! s:lod(names, types, ...)
endif endif
call s:source(rtp, a:2) call s:source(rtp, a:2)
endif endif
if exists('#User#'.name)
call s:doautocmd('User', name) call s:doautocmd('User', name)
endif
endfor endfor
endfunction endfunction
@ -463,21 +498,19 @@ function! s:lod_ft(pat, names)
let syn = 'syntax/'.a:pat.'.vim' let syn = 'syntax/'.a:pat.'.vim'
call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
execute 'autocmd! PlugLOD FileType' a:pat execute 'autocmd! PlugLOD FileType' a:pat
if exists('#filetypeplugin#FileType') call s:doautocmd('filetypeplugin', 'FileType')
doautocmd filetypeplugin FileType call s:doautocmd('filetypeindent', 'FileType')
endif
if exists('#filetypeindent#FileType')
doautocmd filetypeindent FileType
endif
endfunction endfunction
function! s:lod_cmd(cmd, bang, l1, l2, args, names) function! s:lod_cmd(cmd, bang, l1, l2, args, names)
call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
call s:dobufread(a:names)
execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
endfunction endfunction
function! s:lod_map(map, names, prefix) function! s:lod_map(map, names, with_prefix, prefix)
call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
call s:dobufread(a:names)
let extra = '' let extra = ''
while 1 while 1
let c = getchar(0) let c = getchar(0)
@ -486,10 +519,22 @@ function! s:lod_map(map, names, prefix)
endif endif
let extra .= nr2char(c) let extra .= nr2char(c)
endwhile endwhile
call feedkeys(a:prefix . substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
if a:with_prefix
let prefix = v:count ? v:count : ''
let prefix .= '"'.v:register.a:prefix
if mode(1) == 'no'
if v:operator == 'c'
let prefix = "\<esc>" . prefix
endif
let prefix .= v:operator
endif
call feedkeys(prefix, 'n')
endif
call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
endfunction endfunction
function! s:Plug(repo, ...) function! plug#(repo, ...)
if a:0 > 1 if a:0 > 1
return s:err('Invalid number of arguments (1..2)') return s:err('Invalid number of arguments (1..2)')
endif endif
@ -534,13 +579,12 @@ function! s:infer_properties(name, repo)
let uri = repo let uri = repo
else else
if repo !~ '/' if repo !~ '/'
let repo = 'vim-scripts/'. repo throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
endif endif
let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
let uri = printf(fmt, repo) let uri = printf(fmt, repo)
endif endif
let dir = s:dirpath( fnamemodify(join([g:plug_home, a:name], '/'), ':p') ) return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
return { 'dir': dir, 'uri': uri }
endif endif
endfunction endfunction
@ -581,13 +625,14 @@ function! s:syntax()
syn match plugTag /(tag: [^)]\+)/ syn match plugTag /(tag: [^)]\+)/
syn match plugInstall /\(^+ \)\@<=[^:]*/ syn match plugInstall /\(^+ \)\@<=[^:]*/
syn match plugUpdate /\(^* \)\@<=[^:]*/ syn match plugUpdate /\(^* \)\@<=[^:]*/
syn match plugCommit /^ \X*[0-9a-f]\{7} .*/ contains=plugRelDate,plugEdge,plugTag syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
syn match plugEdge /^ \X\+$/ syn match plugEdge /^ \X\+$/
syn match plugEdge /^ \X*/ contained nextgroup=plugSha syn match plugEdge /^ \X*/ contained nextgroup=plugSha
syn match plugSha /[0-9a-f]\{7}/ contained syn match plugSha /[0-9a-f]\{7,9}/ contained
syn match plugRelDate /([^)]*)$/ contained syn match plugRelDate /([^)]*)$/ contained
syn match plugNotLoaded /(not loaded)$/ syn match plugNotLoaded /(not loaded)$/
syn match plugError /^x.*/ syn match plugError /^x.*/
syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
syn match plugH2 /^.*:\n-\+$/ syn match plugH2 /^.*:\n-\+$/
syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
hi def link plug1 Title hi def link plug1 Title
@ -607,6 +652,7 @@ function! s:syntax()
hi def link plugUpdate Type hi def link plugUpdate Type
hi def link plugError Error hi def link plugError Error
hi def link plugDeleted Ignore
hi def link plugRelDate Comment hi def link plugRelDate Comment
hi def link plugEdge PreProc hi def link plugEdge PreProc
hi def link plugSha Identifier hi def link plugSha Identifier
@ -684,12 +730,22 @@ function! s:prepare(...)
throw 'Invalid current working directory. Cannot proceed.' throw 'Invalid current working directory. Cannot proceed.'
endif endif
for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
if exists(evar)
throw evar.' detected. Cannot proceed.'
endif
endfor
call s:job_abort() call s:job_abort()
if s:switch_in() if s:switch_in()
normal q if b:plug_preview == 1
pc
endif
enew
else
call s:new_window()
endif endif
call s:new_window()
nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>bd<cr> nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>bd<cr>
if a:0 == 0 if a:0 == 0
call s:finish_bindings() call s:finish_bindings()
@ -699,11 +755,10 @@ function! s:prepare(...)
let s:plug_buf = winbufnr(0) let s:plug_buf = winbufnr(0)
call s:assign_name() call s:assign_name()
silent! unmap <buffer> <cr> for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
silent! unmap <buffer> L execute 'silent! unmap <buffer>' k
silent! unmap <buffer> o endfor
silent! unmap <buffer> X setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline modifiable
setf vim-plug setf vim-plug
if exists('g:syntax_on') if exists('g:syntax_on')
call s:syntax() call s:syntax()
@ -722,15 +777,25 @@ function! s:assign_name()
silent! execute 'f' fnameescape(name) silent! execute 'f' fnameescape(name)
endfunction endfunction
function! s:chsh(swap)
let prev = [&shell, &shellredir]
if !s:is_win && a:swap
set shell=sh shellredir=>%s\ 2>&1
endif
return prev
endfunction
function! s:bang(cmd, ...) function! s:bang(cmd, ...)
try try
let [sh, shrd] = s:chsh(a:0)
" FIXME: Escaping is incomplete. We could use shellescape with eval, " FIXME: Escaping is incomplete. We could use shellescape with eval,
" but it won't work on Windows. " but it won't work on Windows.
let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
let g:_plug_bang = '!'.escape(cmd, '#!%') let g:_plug_bang = '!'.escape(cmd, '#!%')
execute "normal! :execute g:_plug_bang\<cr>\<cr>" execute "normal! :execute g:_plug_bang\<cr>\<cr>"
finally finally
unlet g:_plug_bang unlet g:_plug_bang
let [&shell, &shellredir] = [sh, shrd]
endtry endtry
return v:shell_error ? 'Exit status: ' . v:shell_error : '' return v:shell_error ? 'Exit status: ' . v:shell_error : ''
endfunction endfunction
@ -758,7 +823,24 @@ function! s:do(pull, force, todo)
let error = '' let error = ''
let type = type(spec.do) let type = type(spec.do)
if type == s:TYPE.string if type == s:TYPE.string
if spec.do[0] == ':'
if !get(s:loaded, name, 0)
let s:loaded[name] = 1
call s:reorg_rtp()
endif
call s:load_plugin(spec)
try
execute spec.do[1:]
catch
let error = v:exception
endtry
if !s:plug_window_exists()
cd -
throw 'Warning: vim-plug was terminated by the post-update hook of '.name
endif
else
let error = s:bang(spec.do) let error = s:bang(spec.do)
endif
elseif type == s:TYPE.funcref elseif type == s:TYPE.funcref
try try
let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
@ -769,6 +851,7 @@ function! s:do(pull, force, todo)
else else
let error = 'Invalid hook type' let error = 'Invalid hook type'
endif endif
call s:switch_in()
call setline(4, empty(error) ? (getline(4) . 'OK') call setline(4, empty(error) ? (getline(4) . 'OK')
\ : ('x' . getline(4)[1:] . error)) \ : ('x' . getline(4)[1:] . error))
if !empty(error) if !empty(error)
@ -789,7 +872,7 @@ function! s:checkout(spec)
let output = s:system('git rev-parse HEAD', a:spec.dir) let output = s:system('git rev-parse HEAD', a:spec.dir)
if !v:shell_error && !s:hash_match(sha, s:lines(output)[0]) if !v:shell_error && !s:hash_match(sha, s:lines(output)[0])
let output = s:system( let output = s:system(
\ 'git fetch --depth 999999 && git checkout '.s:esc(sha), a:spec.dir) \ 'git fetch --depth 999999 && git checkout '.s:esc(sha).' --', a:spec.dir)
endif endif
return output return output
endfunction endfunction
@ -836,17 +919,19 @@ function! s:names(...)
endfunction endfunction
function! s:check_ruby() function! s:check_ruby()
silent! ruby require 'thread'; VIM::command('let g:plug_ruby = 1') silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
if get(g:, 'plug_ruby', 0) if !exists('g:plug_ruby')
unlet g:plug_ruby
return 1
endif
redraw! redraw!
return s:warn('echom', 'Warning: Ruby interface is broken') return s:warn('echom', 'Warning: Ruby interface is broken')
endif
let ruby_version = split(g:plug_ruby, '\.')
unlet g:plug_ruby
return s:version_requirement(ruby_version, [1, 8, 7])
endfunction endfunction
function! s:update_impl(pull, force, args) abort function! s:update_impl(pull, force, args) abort
let args = copy(a:args) let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
let args = filter(copy(a:args), 'v:val != "--sync"')
let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
\ remove(args, -1) : get(g:, 'plug_threads', 16) \ remove(args, -1) : get(g:, 'plug_threads', 16)
@ -880,8 +965,9 @@ function! s:update_impl(pull, force, args) abort
call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
endif endif
let python = (has('python') || has('python3')) && (!s:nvim || has('vim_starting')) let use_job = s:nvim || s:vim8
let ruby = has('ruby') && !s:nvim && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && s:check_ruby() let python = (has('python') || has('python3')) && !use_job
let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby()
let s:update = { let s:update = {
\ 'start': reltime(), \ 'start': reltime(),
@ -891,7 +977,7 @@ function! s:update_impl(pull, force, args) abort
\ 'pull': a:pull, \ 'pull': a:pull,
\ 'force': a:force, \ 'force': a:force,
\ 'new': {}, \ 'new': {},
\ 'threads': (python || ruby || s:nvim) ? min([len(todo), threads]) : 1, \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
\ 'bar': '', \ 'bar': '',
\ 'fin': 0 \ 'fin': 0
\ } \ }
@ -904,8 +990,12 @@ function! s:update_impl(pull, force, args) abort
let s:clone_opt = get(g:, 'plug_shallow', 1) ? let s:clone_opt = get(g:, 'plug_shallow', 1) ?
\ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : '' \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : ''
if has('win32unix')
let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input'
endif
" Python version requirement (>= 2.7) " Python version requirement (>= 2.7)
if python && !has('python3') && !ruby && !s:nvim && s:update.threads > 1 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
redir => pyv redir => pyv
silent python import platform; print platform.python_version() silent python import platform; print platform.python_version()
redir END redir END
@ -946,6 +1036,12 @@ function! s:update_impl(pull, force, args) abort
endtry endtry
else else
call s:update_vim() call s:update_vim()
while use_job && sync
sleep 100m
if s:update.fin
break
endif
endwhile
endif endif
endfunction endfunction
@ -961,7 +1057,7 @@ function! s:update_finish()
if s:switch_in() if s:switch_in()
call append(3, '- Updating ...') | 4 call append(3, '- Updating ...') | 4
for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))')) for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))'))
let pos = s:logpos(name) let [pos, _] = s:logpos(name)
if !pos if !pos
continue continue
endif endif
@ -971,7 +1067,7 @@ function! s:update_finish()
elseif has_key(spec, 'tag') elseif has_key(spec, 'tag')
let tag = spec.tag let tag = spec.tag
if tag =~ '\*' if tag =~ '\*'
let tags = s:lines(s:system('git tag --list '.string(tag).' --sort -version:refname 2>&1', spec.dir)) let tags = s:lines(s:system('git tag --list '.s:shellesc(tag).' --sort -version:refname 2>&1', spec.dir))
if !v:shell_error && !empty(tags) if !v:shell_error && !empty(tags)
let tag = tags[0] let tag = tags[0]
call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
@ -979,11 +1075,11 @@ function! s:update_finish()
endif endif
endif endif
call s:log4(name, 'Checking out '.tag) call s:log4(name, 'Checking out '.tag)
let out = s:system('git checkout -q '.s:esc(tag).' 2>&1', spec.dir) let out = s:system('git checkout -q '.s:esc(tag).' -- 2>&1', spec.dir)
else else
let branch = s:esc(get(spec, 'branch', 'master')) let branch = s:esc(get(spec, 'branch', 'master'))
call s:log4(name, 'Merging origin/'.branch) call s:log4(name, 'Merging origin/'.branch)
let out = s:system('git checkout -q '.branch.' 2>&1' let out = s:system('git checkout -q '.branch.' -- 2>&1'
\. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir) \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir)
endif endif
if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
@ -991,19 +1087,25 @@ function! s:update_finish()
call s:log4(name, 'Updating submodules. This may take a while.') call s:log4(name, 'Updating submodules. This may take a while.')
let out .= s:bang('git submodule update --init --recursive 2>&1', spec.dir) let out .= s:bang('git submodule update --init --recursive 2>&1', spec.dir)
endif endif
let msg = printf('%s %s: %s', v:shell_error ? 'x': '-', name, s:lastline(out)) let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
if v:shell_error if v:shell_error
call add(s:update.errors, name) call add(s:update.errors, name)
call s:regress_bar() call s:regress_bar()
execute pos 'd _' silent execute pos 'd _'
call append(4, msg) | 4 call append(4, msg) | 4
elseif !empty(out) elseif !empty(out)
call setline(pos, msg) call setline(pos, msg[0])
endif endif
redraw redraw
endfor endfor
4 d _ silent 4 d _
try
call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")')) call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")'))
catch
call s:warn('echom', v:exception)
call s:warn('echo', '')
return
endtry
call s:finish(s:update.pull) call s:finish(s:update.pull)
call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
call s:switch_out('normal! gg') call s:switch_out('normal! gg')
@ -1011,12 +1113,16 @@ function! s:update_finish()
endfunction endfunction
function! s:job_abort() function! s:job_abort()
if !s:nvim || !exists('s:jobs') if (!s:nvim && !s:vim8) || !exists('s:jobs')
return return
endif endif
for [name, j] in items(s:jobs) for [name, j] in items(s:jobs)
if s:nvim
silent! call jobstop(j.jobid) silent! call jobstop(j.jobid)
elseif s:vim8
silent! call job_stop(j.jobid)
endif
if j.new if j.new
call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir)) call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir))
endif endif
@ -1024,59 +1130,88 @@ function! s:job_abort()
let s:jobs = {} let s:jobs = {}
endfunction endfunction
" When a:event == 'stdout', data = list of strings function! s:last_non_empty_line(lines)
" When a:event == 'exit', data = returncode let len = len(a:lines)
function! s:job_handler(job_id, data, event) abort for idx in range(len)
if !s:plug_window_exists() " plug window closed let line = a:lines[len-idx-1]
return s:job_abort() if !empty(line)
return line
endif endif
endfor
return ''
endfunction
if a:event == 'stdout' function! s:job_out_cb(self, data) abort
let complete = empty(a:data[-1]) let self = a:self
let lines = map(filter(a:data, 'len(v:val) > 0'), 'split(v:val, "[\r\n]")[-1]') let data = remove(self.lines, -1) . a:data
let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
call extend(self.lines, lines) call extend(self.lines, lines)
let self.result = join(self.lines, "\n")
if !complete
call remove(self.lines, -1)
endif
" To reduce the number of buffer updates " To reduce the number of buffer updates
let self.tick = get(self, 'tick', -1) + 1 let self.tick = get(self, 'tick', -1) + 1
if self.tick % len(s:jobs) == 0 if !self.running || self.tick % len(s:jobs) == 0
call s:log(self.new ? '+' : '*', self.name, self.result) let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
endif let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
elseif a:event == 'exit' call s:log(bullet, self.name, result)
let self.running = 0
if a:data != 0
let self.error = 1
endif
call s:reap(self.name)
call s:tick()
endif endif
endfunction endfunction
function! s:job_exit_cb(self, data) abort
let a:self.running = 0
let a:self.error = a:data != 0
call s:reap(a:self.name)
call s:tick()
endfunction
function! s:job_cb(fn, job, ch, data)
if !s:plug_window_exists() " plug window closed
return s:job_abort()
endif
call call(a:fn, [a:job, a:data])
endfunction
function! s:nvim_cb(job_id, data, event) dict abort
return a:event == 'stdout' ?
\ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
\ s:job_cb('s:job_exit_cb', self, 0, a:data)
endfunction
function! s:spawn(name, cmd, opts) function! s:spawn(name, cmd, opts)
let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [], 'result': '', let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
\ 'new': get(a:opts, 'new', 0), \ 'new': get(a:opts, 'new', 0) }
\ 'on_stdout': function('s:job_handler'),
\ 'on_exit' : function('s:job_handler'),
\ }
let s:jobs[a:name] = job let s:jobs[a:name] = job
let argv = add(s:is_win ? ['cmd', '/c'] : ['sh', '-c'],
\ has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd)
if s:nvim if s:nvim
let argv = [ 'sh', '-c', call extend(job, {
\ (has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd) ] \ 'on_stdout': function('s:nvim_cb'),
\ 'on_exit': function('s:nvim_cb'),
\ })
let jid = jobstart(argv, job) let jid = jobstart(argv, job)
if jid > 0 if jid > 0
let job.jobid = jid let job.jobid = jid
else else
let job.running = 0 let job.running = 0
let job.error = 1 let job.error = 1
let job.result = jid < 0 ? 'sh is not executable' : let job.lines = [jid < 0 ? argv[0].' is not executable' :
\ 'Invalid arguments (or job table is full)' \ 'Invalid arguments (or job table is full)']
endif
elseif s:vim8
let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
\ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
\ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
\ 'out_mode': 'raw'
\})
if job_status(jid) == 'run'
let job.jobid = jid
else
let job.running = 0
let job.error = 1
let job.lines = ['Failed to start job']
endif endif
else else
let params = has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd] let params = has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]
let job.result = call('s:system', params) let job.lines = s:lines(call('s:system', params))
let job.error = v:shell_error != 0 let job.error = v:shell_error != 0
let job.running = 0 let job.running = 0
endif endif
@ -1091,7 +1226,9 @@ function! s:reap(name)
endif endif
let s:update.bar .= job.error ? 'x' : '=' let s:update.bar .= job.error ? 'x' : '='
call s:log(job.error ? 'x' : '-', a:name, empty(job.result) ? 'OK' : job.result) let bullet = job.error ? 'x' : '-'
let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
call s:log(bullet, a:name, empty(result) ? 'OK' : result)
call s:bar() call s:bar()
call remove(s:jobs, a:name) call remove(s:jobs, a:name)
@ -1110,23 +1247,31 @@ endfunction
function! s:logpos(name) function! s:logpos(name)
for i in range(4, line('$')) for i in range(4, line('$'))
if getline(i) =~# '^[-+x*] '.a:name.':' if getline(i) =~# '^[-+x*] '.a:name.':'
return i for j in range(i + 1, line('$'))
if getline(j) !~ '^ '
return [i, j - 1]
endif endif
endfor endfor
return [i, i]
endif
endfor
return [0, 0]
endfunction endfunction
function! s:log(bullet, name, lines) function! s:log(bullet, name, lines)
if s:switch_in() if s:switch_in()
let pos = s:logpos(a:name) let [b, e] = s:logpos(a:name)
if pos > 0 if b > 0
execute pos 'd _' silent execute printf('%d,%d d _', b, e)
if pos > winheight('.') if b > winheight('.')
let pos = 4 let b = 4
endif endif
else else
let pos = 4 let b = 4
endif endif
call append(pos - 1, s:format_message(a:bullet, a:name, a:lines)) " FIXME For some reason, nomodifiable is set after :d in vim8
setlocal modifiable
call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
call s:switch_out() call s:switch_out()
endif endif
endfunction endfunction
@ -1140,12 +1285,12 @@ endfunction
function! s:tick() function! s:tick()
let pull = s:update.pull let pull = s:update.pull
let prog = s:progress_opt(s:nvim) let prog = s:progress_opt(s:nvim || s:vim8)
while 1 " Without TCO, Vim stack is bound to explode while 1 " Without TCO, Vim stack is bound to explode
if empty(s:update.todo) if empty(s:update.todo)
if empty(s:jobs) && !s:update.fin if empty(s:jobs) && !s:update.fin
let s:update.fin = 1
call s:update_finish() call s:update_finish()
let s:update.fin = 1
endif endif
return return
endif endif
@ -1165,10 +1310,10 @@ while 1 " Without TCO, Vim stack is bound to explode
let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : '' let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : ''
call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir }) call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir })
else else
let s:jobs[name] = { 'running': 0, 'result': 'Already installed', 'error': 0 } let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
endif endif
else else
let s:jobs[name] = { 'running': 0, 'result': error, 'error': 1 } let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
endif endif
else else
call s:spawn(name, call s:spawn(name,
@ -1454,10 +1599,12 @@ class Plugin(object):
return result[-1] return result[-1]
def update(self): def update(self):
match = re.compile(r'git::?@') actual_uri = self.repo_uri()
actual_uri = re.sub(match, '', self.repo_uri()) expect_uri = self.args['uri']
expect_uri = re.sub(match, '', self.args['uri']) regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
if actual_uri != expect_uri: ma = regex.match(actual_uri)
mb = regex.match(expect_uri)
if ma is None or mb is None or ma.groups() != mb.groups():
msg = ['', msg = ['',
'Invalid URI: {0}'.format(actual_uri), 'Invalid URI: {0}'.format(actual_uri),
'Expected {0}'.format(expect_uri), 'Expected {0}'.format(expect_uri),
@ -1616,6 +1763,11 @@ function! s:update_ruby()
end end
end end
def compare_git_uri a, b
regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
end
require 'thread' require 'thread'
require 'fileutils' require 'fileutils'
require 'timeout' require 'timeout'
@ -1628,6 +1780,7 @@ function! s:update_ruby()
tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
nthr = VIM::evaluate('s:update.threads').to_i nthr = VIM::evaluate('s:update.threads').to_i
maxy = VIM::evaluate('winheight(".")').to_i maxy = VIM::evaluate('winheight(".")').to_i
vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
cd = iswin ? 'cd /d' : 'cd' cd = iswin ? 'cd /d' : 'cd'
tot = VIM::evaluate('len(s:update.todo)') || 0 tot = VIM::evaluate('len(s:update.todo)') || 0
bar = '' bar = ''
@ -1717,12 +1870,17 @@ function! s:update_ruby()
main = Thread.current main = Thread.current
threads = [] threads = []
watcher = Thread.new { watcher = Thread.new {
if vim7
while VIM::evaluate('getchar(1)') while VIM::evaluate('getchar(1)')
sleep 0.1 sleep 0.1
end end
else
require 'io/console' # >= Ruby 1.9
nil until IO.console.getch == 3.chr
end
mtx.synchronize do mtx.synchronize do
running = false running = false
threads.each { |t| t.raise Interrupt } threads.each { |t| t.raise Interrupt } unless vim7
end end
threads.each { |t| t.join rescue nil } threads.each { |t| t.join rescue nil }
main.kill main.kill
@ -1757,7 +1915,7 @@ function! s:update_ruby()
else else
[false, [data.chomp, "PlugClean required."].join($/)] [false, [data.chomp, "PlugClean required."].join($/)]
end end
elsif current_uri.sub(/git::?@/, '') != uri.sub(/git::?@/, '') elsif !compare_git_uri(current_uri, uri)
[false, ["Invalid URI: #{current_uri}", [false, ["Invalid URI: #{current_uri}",
"Expected: #{uri}", "Expected: #{uri}",
"PlugClean required."].join($/)] "PlugClean required."].join($/)]
@ -1803,9 +1961,15 @@ function! s:progress_bar(line, bar, total)
endfunction endfunction
function! s:compare_git_uri(a, b) function! s:compare_git_uri(a, b)
let a = substitute(a:a, 'git:\{1,2}@', '', '') " See `git help clone'
let b = substitute(a:b, 'git:\{1,2}@', '', '') " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
return a ==# b " [git@] github.com[:port] : junegunn/vim-plug [.git]
" file:// / junegunn/vim-plug [/]
" / junegunn/vim-plug [/]
let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
let ma = matchlist(a:a, pat)
let mb = matchlist(a:b, pat)
return ma[1:2] ==# mb[1:2]
endfunction endfunction
function! s:format_message(bullet, name, message) function! s:format_message(bullet, name, message)
@ -1823,10 +1987,7 @@ endfunction
function! s:system(cmd, ...) function! s:system(cmd, ...)
try try
let [sh, shrd] = [&shell, &shellredir] let [sh, shrd] = s:chsh(1)
if !s:is_win
set shell=sh shellredir=>%s\ 2>&1
endif
let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd
return system(s:is_win ? '('.cmd.')' : cmd) return system(s:is_win ? '('.cmd.')' : cmd)
finally finally
@ -1865,7 +2026,7 @@ function! s:git_validate(spec, check_branch)
" Check tag " Check tag
if has_key(a:spec, 'tag') if has_key(a:spec, 'tag')
let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
if a:spec.tag !=# tag if a:spec.tag !=# tag && a:spec.tag !~ '\*'
let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
\ (empty(tag) ? 'N/A' : tag), a:spec.tag) \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
endif endif
@ -1875,10 +2036,21 @@ function! s:git_validate(spec, check_branch)
\ branch, a:spec.branch) \ branch, a:spec.branch)
endif endif
if empty(err) if empty(err)
let commits = len(s:lines(s:system(printf('git rev-list origin/%s..HEAD', a:spec.branch), a:spec.dir))) let [ahead, behind] = split(s:lastline(s:system(printf(
if !v:shell_error && commits \ 'git rev-list --count --left-right HEAD...origin/%s',
let err = join([printf('Diverged from origin/%s by %d commit(s).', a:spec.branch, commits), \ a:spec.branch), a:spec.dir)), '\t')
\ 'Reinstall after PlugClean.'], "\n") if !v:shell_error && ahead
if behind
" Only mention PlugClean if diverged, otherwise it's likely to be
" pushable (and probably not that messed up).
let err = printf(
\ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
\ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind)
else
let err = printf("Ahead of origin/%s by %d commit(s).\n"
\ .'Cannot update until local changes are pushed.',
\ a:spec.branch, ahead)
endif
endif endif
endif endif
endif endif
@ -1948,16 +2120,48 @@ function! s:clean(force)
if empty(todo) if empty(todo)
call append(line('$'), 'Already clean.') call append(line('$'), 'Already clean.')
else else
if a:force || s:ask('Proceed?') let s:clean_count = 0
for dir in todo call append(3, ['Directories to delete:', ''])
call s:rm_rf(dir) redraw!
endfor if a:force || s:ask_no_interrupt('Delete all directories?')
call append(3, ['Removed.', '']) call s:delete([6, line('$')], 1)
else else
call append(3, ['Cancelled.', '']) call setline(4, 'Cancelled.')
nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
nmap <silent> <buffer> dd d_
xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
echo 'Delete the lines (d{motion}) to delete the corresponding directories'
endif endif
endif endif
4 4
setlocal nomodifiable
endfunction
function! s:delete_op(type, ...)
call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
endfunction
function! s:delete(range, force)
let [l1, l2] = a:range
let force = a:force
while l1 <= l2
let line = getline(l1)
if line =~ '^- ' && isdirectory(line[2:])
execute l1
redraw!
let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
let force = force || answer > 1
if answer
call s:rm_rf(line[2:])
setlocal modifiable
call setline(l1, '~'.line[1:])
let s:clean_count += 1
call setline(4, printf('Removed %d directories.', s:clean_count))
setlocal nomodifiable
endif
endif
let l1 += 1
endwhile
endfunction endfunction
function! s:upgrade() function! s:upgrade()
@ -2089,7 +2293,7 @@ function! s:preview_commit()
let b:plug_preview = !s:is_preview_window_open() let b:plug_preview = !s:is_preview_window_open()
endif endif
let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7}') let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
if empty(sha) if empty(sha)
return return
endif endif
@ -2099,11 +2303,20 @@ function! s:preview_commit()
return return
endif endif
if exists('g:plug_pwindow') && !s:is_preview_window_open()
execute g:plug_pwindow
execute 'e' sha
else
execute 'pedit' sha execute 'pedit' sha
wincmd P wincmd P
setlocal filetype=git buftype=nofile nobuflisted modifiable endif
execute 'silent read !cd' s:shellesc(g:plugs[name].dir) '&& git show --no-color --pretty=medium' sha setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
normal! gg"_dd try
let [sh, shrd] = s:chsh(1)
execute 'silent %!cd' s:shellesc(g:plugs[name].dir) '&& git show --no-color --pretty=medium' sha
finally
let [&shell, &shellredir] = [sh, shrd]
endtry
setlocal nomodifiable setlocal nomodifiable
nnoremap <silent> <buffer> q :q<cr> nnoremap <silent> <buffer> q :q<cr>
wincmd p wincmd p
@ -2185,11 +2398,11 @@ function! s:revert()
return return
endif endif
call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch), g:plugs[name].dir) call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch).' --', g:plugs[name].dir)
setlocal modifiable setlocal modifiable
normal! "_dap normal! "_dap
setlocal nomodifiable setlocal nomodifiable
echo 'Reverted.' echo 'Reverted'
endfunction endfunction
function! s:snapshot(force, ...) abort function! s:snapshot(force, ...) abort