1 " vim-plug: Vim plugin manager
2 " ============================
4 " Download plug.vim and put it in ~/.vim/autoload
6 " curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
7 " https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
11 " call plug#begin('~/.vim/plugged')
13 " " Make sure you use single quotes
15 " " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align
16 " Plug 'junegunn/vim-easy-align'
18 " " Any valid git URL is allowed
19 " Plug 'https://github.com/junegunn/vim-github-dashboard.git'
21 " " Multiple Plug commands can be written in a single line using | separators
22 " Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets'
25 " Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' }
26 " Plug 'tpope/vim-fireplace', { 'for': 'clojure' }
28 " " Using a non-master branch
29 " Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' }
31 " " Using a tagged release; wildcard allowed (requires git 1.9.2 or above)
32 " Plug 'fatih/vim-go', { 'tag': '*' }
35 " Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' }
37 " " Plugin outside ~/.vim/plugged with post-update hook
38 " Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
40 " " Unmanaged plugin (manually installed and updated)
41 " Plug '~/my-prototype-plugin'
43 " " Initialize plugin system
46 " Then reload .vimrc and :PlugInstall to install plugins.
50 "| Option | Description |
51 "| ----------------------- | ------------------------------------------------ |
52 "| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use |
53 "| `rtp` | Subdirectory that contains Vim plugin |
54 "| `dir` | Custom directory for the plugin |
55 "| `as` | Use different name for the plugin |
56 "| `do` | Post-update hook (string or funcref) |
57 "| `on` | On-demand loading: Commands or `<Plug>`-mappings |
58 "| `for` | On-demand loading: File types |
59 "| `frozen` | Do not update unless explicitly specified |
61 " More information: https://github.com/junegunn/vim-plug
64 " Copyright (c) 2017 Junegunn Choi
68 " Permission is hereby granted, free of charge, to any person obtaining
69 " a copy of this software and associated documentation files (the
70 " "Software"), to deal in the Software without restriction, including
71 " without limitation the rights to use, copy, modify, merge, publish,
72 " distribute, sublicense, and/or sell copies of the Software, and to
73 " permit persons to whom the Software is furnished to do so, subject to
74 " the following conditions:
76 " The above copyright notice and this permission notice shall be
77 " included in all copies or substantial portions of the Software.
79 " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
80 " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
81 " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
82 " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
83 " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
84 " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
85 " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
87 if exists('g:loaded_plug')
95 let s:plug_src = 'https://github.com/junegunn/vim-plug.git'
96 let s:plug_tab = get(s:, 'plug_tab', -1)
97 let s:plug_buf = get(s:, 'plug_buf', -1)
98 let s:mac_gui = has('gui_macvim') && has('gui_running')
99 let s:is_win = has('win32')
100 let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win)
101 let s:vim8 = has('patch-8.0.0039') && exists('*job_start')
102 if s:is_win && &shellslash
104 let s:me = resolve(expand('<sfile>:p'))
107 let s:me = resolve(expand('<sfile>:p'))
109 let s:base_spec = { 'branch': 'master', 'frozen': 0 }
111 \ 'string': type(''),
114 \ 'funcref': type(function('call'))
116 let s:loaded = get(s:, 'loaded', {})
117 let s:triggers = get(s:, 'triggers', {})
120 function! s:plug_call(fn, ...)
121 let shellslash = &shellslash
124 return call(a:fn, a:000)
126 let &shellslash = shellslash
130 function! s:plug_call(fn, ...)
131 return call(a:fn, a:000)
135 function! s:plug_getcwd()
136 return s:plug_call('getcwd')
139 function! s:plug_fnamemodify(fname, mods)
140 return s:plug_call('fnamemodify', a:fname, a:mods)
143 function! s:plug_expand(fmt)
144 return s:plug_call('expand', a:fmt, 1)
147 function! s:plug_tempname()
148 return s:plug_call('tempname')
151 function! plug#begin(...)
153 let s:plug_home_org = a:1
154 let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p'))
155 elseif exists('g:plug_home')
156 let home = s:path(g:plug_home)
158 let home = s:path(split(&rtp, ',')[0]) . '/plugged'
160 return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
162 if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp
163 return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.')
166 let g:plug_home = home
168 let g:plugs_order = []
171 call s:define_commands()
175 function! s:define_commands()
176 command! -nargs=+ -bar Plug call plug#(<args>)
177 if !executable('git')
178 return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
182 \ && (&shell =~# 'cmd\.exe' || &shell =~# 'powershell\.exe')
183 return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.')
186 \ && (has('win32') || has('win32unix'))
187 \ && !has('multi_byte')
188 return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.')
190 command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>])
191 command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(<bang>0, [<f-args>])
192 command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0)
193 command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
194 command! -nargs=0 -bar PlugStatus call s:status()
195 command! -nargs=0 -bar PlugDiff call s:diff()
196 command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>)
200 return type(a:v) == s:TYPE.list ? a:v : [a:v]
204 return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
207 function! s:glob(from, pattern)
208 return s:lines(globpath(a:from, a:pattern))
211 function! s:source(from, ...)
214 for vim in s:glob(a:from, pattern)
215 execute 'source' s:esc(vim)
222 function! s:assoc(dict, key, val)
223 let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
226 function! s:ask(message, ...)
229 let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
233 return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
236 function! s:ask_no_interrupt(...)
238 return call('s:ask', a:000)
244 function! s:lazy(plug, opt)
245 return has_key(a:plug, a:opt) &&
246 \ (empty(s:to_a(a:plug[a:opt])) ||
247 \ !isdirectory(a:plug.dir) ||
248 \ len(s:glob(s:rtp(a:plug), 'plugin')) ||
249 \ len(s:glob(s:rtp(a:plug), 'after/plugin')))
253 if !exists('g:plugs')
254 return s:err('plug#end() called without calling plug#begin() first')
257 if exists('#PlugLOD')
263 let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
265 if exists('g:did_load_filetypes')
268 for name in g:plugs_order
269 if !has_key(g:plugs, name)
272 let plug = g:plugs[name]
273 if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for')
274 let s:loaded[name] = 1
278 if has_key(plug, 'on')
279 let s:triggers[name] = { 'map': [], 'cmd': [] }
280 for cmd in s:to_a(plug.on)
281 if cmd =~? '^<Plug>.\+'
282 if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
283 call s:assoc(lod.map, cmd, name)
285 call add(s:triggers[name].map, cmd)
286 elseif cmd =~# '^[A-Z]'
287 let cmd = substitute(cmd, '!*$', '', '')
288 if exists(':'.cmd) != 2
289 call s:assoc(lod.cmd, cmd, name)
291 call add(s:triggers[name].cmd, cmd)
293 call s:err('Invalid `on` option: '.cmd.
294 \ '. Should start with an uppercase letter or `<Plug>`.')
299 if has_key(plug, 'for')
300 let types = s:to_a(plug.for)
302 augroup filetypedetect
303 call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
307 call s:assoc(lod.ft, type, name)
312 for [cmd, names] in items(lod.cmd)
314 \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
315 \ cmd, string(cmd), string(names))
318 for [map, names] in items(lod.map)
319 for [mode, map_prefix, key_prefix] in
320 \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
322 \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
323 \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
327 for [ft, names] in items(lod.ft)
329 execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
330 \ ft, string(ft), string(names))
335 filetype plugin indent on
336 if has('vim_starting')
337 if has('syntax') && !exists('g:syntax_on')
341 call s:reload_plugins()
345 function! s:loaded_names()
346 return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
349 function! s:load_plugin(spec)
350 call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
353 function! s:reload_plugins()
354 for name in s:loaded_names()
355 call s:load_plugin(g:plugs[name])
359 function! s:trim(str)
360 return substitute(a:str, '[\/]\+$', '', '')
363 function! s:version_requirement(val, min)
364 for idx in range(0, len(a:min) - 1)
365 let v = get(a:val, idx, 0)
366 if v < a:min[idx] | return 0
367 elseif v > a:min[idx] | return 1
373 function! s:git_version_requirement(...)
374 if !exists('s:git_version')
375 let s:git_version = map(split(split(s:system('git --version'))[2], '\.'), 'str2nr(v:val)')
377 return s:version_requirement(s:git_version, a:000)
380 function! s:progress_opt(base)
381 return a:base && !s:is_win &&
382 \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
385 function! s:rtp(spec)
386 return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
390 function! s:path(path)
391 return s:trim(substitute(a:path, '/', '\', 'g'))
394 function! s:dirpath(path)
395 return s:path(a:path) . '\'
398 function! s:is_local_plug(repo)
399 return a:repo =~? '^[a-z]:\|^[%~]'
403 function! s:wrap_cmds(cmds)
406 \ 'setlocal enabledelayedexpansion']
407 \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
410 if !exists('s:codepage')
411 let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
413 return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage))
415 return map(cmds, 'v:val."\r"')
418 function! s:batchfile(cmd)
419 let batchfile = s:plug_tempname().'.bat'
420 call writefile(s:wrap_cmds(a:cmd), batchfile)
421 let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0})
422 if &shell =~# 'powershell\.exe'
425 return [batchfile, cmd]
428 function! s:path(path)
429 return s:trim(a:path)
432 function! s:dirpath(path)
433 return substitute(a:path, '[/\\]*$', '/', '')
436 function! s:is_local_plug(repo)
437 return a:repo[0] =~ '[/$~]'
443 echom '[vim-plug] '.a:msg
447 function! s:warn(cmd, msg)
449 execute a:cmd 'a:msg'
453 function! s:esc(path)
454 return escape(a:path, ' ')
457 function! s:escrtp(path)
458 return escape(a:path, ' ,')
461 function! s:remove_rtp()
462 for name in s:loaded_names()
463 let rtp = s:rtp(g:plugs[name])
464 execute 'set rtp-='.s:escrtp(rtp)
465 let after = globpath(rtp, 'after')
466 if isdirectory(after)
467 execute 'set rtp-='.s:escrtp(after)
472 function! s:reorg_rtp()
473 if !empty(s:first_rtp)
474 execute 'set rtp-='.s:first_rtp
475 execute 'set rtp-='.s:last_rtp
478 " &rtp is modified from outside
479 if exists('s:prtp') && s:prtp !=# &rtp
484 let s:middle = get(s:, 'middle', &rtp)
485 let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
486 let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
487 let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
489 \ . join(map(afters, 'escape(v:val, ",")'), ',')
490 let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
493 if !empty(s:first_rtp)
494 execute 'set rtp^='.s:first_rtp
495 execute 'set rtp+='.s:last_rtp
499 function! s:doautocmd(...)
500 if exists('#'.join(a:000, '#'))
501 execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
505 function! s:dobufread(names)
507 let path = s:rtp(g:plugs[name])
508 for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin']
509 if len(finddir(dir, path))
510 if exists('#BufRead')
519 function! plug#load(...)
521 return s:err('Argument missing: plugin name(s) required')
523 if !exists('g:plugs')
524 return s:err('plug#begin was not called')
526 let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
527 let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
529 let s = len(unknowns) > 1 ? 's' : ''
530 return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
532 let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
535 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
537 call s:dobufread(unloaded)
543 function! s:remove_triggers(name)
544 if !has_key(s:triggers, a:name)
547 for cmd in s:triggers[a:name].cmd
548 execute 'silent! delc' cmd
550 for map in s:triggers[a:name].map
551 execute 'silent! unmap' map
552 execute 'silent! iunmap' map
554 call remove(s:triggers, a:name)
557 function! s:lod(names, types, ...)
559 call s:remove_triggers(name)
560 let s:loaded[name] = 1
565 let rtp = s:rtp(g:plugs[name])
567 call s:source(rtp, dir.'/**/*.vim')
570 if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
571 execute 'runtime' a:1
573 call s:source(rtp, a:2)
575 call s:doautocmd('User', name)
579 function! s:lod_ft(pat, names)
580 let syn = 'syntax/'.a:pat.'.vim'
581 call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
582 execute 'autocmd! PlugLOD FileType' a:pat
583 call s:doautocmd('filetypeplugin', 'FileType')
584 call s:doautocmd('filetypeindent', 'FileType')
587 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
588 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
589 call s:dobufread(a:names)
590 execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
593 function! s:lod_map(map, names, with_prefix, prefix)
594 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
595 call s:dobufread(a:names)
602 let extra .= nr2char(c)
606 let prefix = v:count ? v:count : ''
607 let prefix .= '"'.v:register.a:prefix
610 let prefix = "\<esc>" . prefix
612 let prefix .= v:operator
614 call feedkeys(prefix, 'n')
616 call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
619 function! plug#(repo, ...)
621 return s:err('Invalid number of arguments (1..2)')
625 let repo = s:trim(a:repo)
626 let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
627 let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??'))
628 let spec = extend(s:infer_properties(name, repo), opts)
629 if !has_key(g:plugs, name)
630 call add(g:plugs_order, name)
632 let g:plugs[name] = spec
633 let s:loaded[name] = get(s:loaded, name, 0)
635 return s:err(v:exception)
639 function! s:parse_options(arg)
640 let opts = copy(s:base_spec)
641 let type = type(a:arg)
642 if type == s:TYPE.string
644 elseif type == s:TYPE.dict
645 call extend(opts, a:arg)
646 if has_key(opts, 'dir')
647 let opts.dir = s:dirpath(s:plug_expand(opts.dir))
650 throw 'Invalid argument type (expected: string or dictionary)'
655 function! s:infer_properties(name, repo)
657 if s:is_local_plug(repo)
658 return { 'dir': s:dirpath(s:plug_expand(repo)) }
664 throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
666 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
667 let uri = printf(fmt, repo)
669 return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
673 function! s:install(force, names)
674 call s:update_impl(0, a:force, a:names)
677 function! s:update(force, names)
678 call s:update_impl(1, a:force, a:names)
681 function! plug#helptags()
682 if !exists('g:plugs')
683 return s:err('plug#begin was not called')
685 for spec in values(g:plugs)
686 let docd = join([s:rtp(spec), 'doc'], '/')
688 silent! execute 'helptags' s:esc(docd)
696 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
697 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
698 syn match plugNumber /[0-9]\+[0-9.]*/ contained
699 syn match plugBracket /[[\]]/ contained
700 syn match plugX /x/ contained
701 syn match plugDash /^-/
702 syn match plugPlus /^+/
703 syn match plugStar /^*/
704 syn match plugMessage /\(^- \)\@<=.*/
705 syn match plugName /\(^- \)\@<=[^ ]*:/
706 syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
707 syn match plugTag /(tag: [^)]\+)/
708 syn match plugInstall /\(^+ \)\@<=[^:]*/
709 syn match plugUpdate /\(^* \)\@<=[^:]*/
710 syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
711 syn match plugEdge /^ \X\+$/
712 syn match plugEdge /^ \X*/ contained nextgroup=plugSha
713 syn match plugSha /[0-9a-f]\{7,9}/ contained
714 syn match plugRelDate /([^)]*)$/ contained
715 syn match plugNotLoaded /(not loaded)$/
716 syn match plugError /^x.*/
717 syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
718 syn match plugH2 /^.*:\n-\+$/
719 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
720 hi def link plug1 Title
721 hi def link plug2 Repeat
722 hi def link plugH2 Type
723 hi def link plugX Exception
724 hi def link plugBracket Structure
725 hi def link plugNumber Number
727 hi def link plugDash Special
728 hi def link plugPlus Constant
729 hi def link plugStar Boolean
731 hi def link plugMessage Function
732 hi def link plugName Label
733 hi def link plugInstall Function
734 hi def link plugUpdate Type
736 hi def link plugError Error
737 hi def link plugDeleted Ignore
738 hi def link plugRelDate Comment
739 hi def link plugEdge PreProc
740 hi def link plugSha Identifier
741 hi def link plugTag Constant
743 hi def link plugNotLoaded Comment
746 function! s:lpad(str, len)
747 return a:str . repeat(' ', a:len - len(a:str))
750 function! s:lines(msg)
751 return split(a:msg, "[\r\n]")
754 function! s:lastline(msg)
755 return get(s:lines(a:msg), -1, '')
758 function! s:new_window()
759 execute get(g:, 'plug_window', 'vertical topleft new')
762 function! s:plug_window_exists()
763 let buflist = tabpagebuflist(s:plug_tab)
764 return !empty(buflist) && index(buflist, s:plug_buf) >= 0
767 function! s:switch_in()
768 if !s:plug_window_exists()
772 if winbufnr(0) != s:plug_buf
773 let s:pos = [tabpagenr(), winnr(), winsaveview()]
774 execute 'normal!' s:plug_tab.'gt'
775 let winnr = bufwinnr(s:plug_buf)
776 execute winnr.'wincmd w'
777 call add(s:pos, winsaveview())
779 let s:pos = [winsaveview()]
786 function! s:switch_out(...)
787 call winrestview(s:pos[-1])
788 setlocal nomodifiable
794 execute 'normal!' s:pos[0].'gt'
795 execute s:pos[1] 'wincmd w'
796 call winrestview(s:pos[2])
800 function! s:finish_bindings()
801 nnoremap <silent> <buffer> R :call <SID>retry()<cr>
802 nnoremap <silent> <buffer> D :PlugDiff<cr>
803 nnoremap <silent> <buffer> S :PlugStatus<cr>
804 nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
805 xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
806 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
807 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
810 function! s:prepare(...)
811 if empty(s:plug_getcwd())
812 throw 'Invalid current working directory. Cannot proceed.'
815 for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
817 throw evar.' detected. Cannot proceed.'
823 if b:plug_preview == 1
831 nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>bd<cr>
833 call s:finish_bindings()
835 let b:plug_preview = -1
836 let s:plug_tab = tabpagenr()
837 let s:plug_buf = winbufnr(0)
840 for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
841 execute 'silent! unmap <buffer>' k
843 setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
844 if exists('+colorcolumn')
845 setlocal colorcolumn=
848 if exists('g:syntax_on')
853 function! s:assign_name()
855 let prefix = '[Plugins]'
858 while bufexists(name)
859 let name = printf('%s (%s)', prefix, idx)
862 silent! execute 'f' fnameescape(name)
865 function! s:chsh(swap)
866 let prev = [&shell, &shellcmdflag, &shellredir]
867 if !s:is_win && a:swap
868 set shell=sh shellredir=>%s\ 2>&1
873 function! s:bang(cmd, ...)
876 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
877 " FIXME: Escaping is incomplete. We could use shellescape with eval,
878 " but it won't work on Windows.
879 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
881 let [batchfile, cmd] = s:batchfile(cmd)
883 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
884 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
887 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
888 if s:is_win && filereadable(batchfile)
889 call delete(batchfile)
892 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
895 function! s:regress_bar()
896 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
897 call s:progress_bar(2, bar, len(bar))
900 function! s:is_updated(dir)
901 return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir))
904 function! s:do(pull, force, todo)
905 for [name, spec] in items(a:todo)
906 if !isdirectory(spec.dir)
909 let installed = has_key(s:update.new, name)
910 let updated = installed ? 0 :
911 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
912 if a:force || installed || updated
913 execute 'cd' s:esc(spec.dir)
914 call append(3, '- Post-update hook for '. name .' ... ')
916 let type = type(spec.do)
917 if type == s:TYPE.string
919 if !get(s:loaded, name, 0)
920 let s:loaded[name] = 1
923 call s:load_plugin(spec)
927 let error = v:exception
929 if !s:plug_window_exists()
931 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
934 let error = s:bang(spec.do)
936 elseif type == s:TYPE.funcref
938 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
939 call spec.do({ 'name': name, 'status': status, 'force': a:force })
941 let error = v:exception
944 let error = 'Invalid hook type'
947 call setline(4, empty(error) ? (getline(4) . 'OK')
948 \ : ('x' . getline(4)[1:] . error))
950 call add(s:update.errors, name)
958 function! s:hash_match(a, b)
959 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
962 function! s:checkout(spec)
963 let sha = a:spec.commit
964 let output = s:system('git rev-parse HEAD', a:spec.dir)
965 if !v:shell_error && !s:hash_match(sha, s:lines(output)[0])
966 let output = s:system(
967 \ 'git fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
972 function! s:finish(pull)
973 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
975 let s = new_frozen > 1 ? 's' : ''
976 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
978 call append(3, '- Finishing ... ') | 4
982 call setline(4, getline(4) . 'Done!')
985 if !empty(s:update.errors)
986 call add(msgs, "Press 'R' to retry.")
988 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
989 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
990 call add(msgs, "Press 'D' to see the updated changes.")
993 call s:finish_bindings()
997 if empty(s:update.errors)
1001 call s:update_impl(s:update.pull, s:update.force,
1002 \ extend(copy(s:update.errors), [s:update.threads]))
1005 function! s:is_managed(name)
1006 return has_key(g:plugs[a:name], 'uri')
1009 function! s:names(...)
1010 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
1013 function! s:check_ruby()
1014 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
1015 if !exists('g:plug_ruby')
1017 return s:warn('echom', 'Warning: Ruby interface is broken')
1019 let ruby_version = split(g:plug_ruby, '\.')
1021 return s:version_requirement(ruby_version, [1, 8, 7])
1024 function! s:update_impl(pull, force, args) abort
1025 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
1026 let args = filter(copy(a:args), 'v:val != "--sync"')
1027 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
1028 \ remove(args, -1) : get(g:, 'plug_threads', 16)
1030 let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
1031 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
1032 \ filter(managed, 'index(args, v:key) >= 0')
1035 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
1038 if !s:is_win && s:git_version_requirement(2, 3)
1039 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
1040 let $GIT_TERMINAL_PROMPT = 0
1041 for plug in values(todo)
1042 let plug.uri = substitute(plug.uri,
1043 \ '^https://git::@github\.com', 'https://github.com', '')
1047 if !isdirectory(g:plug_home)
1049 call mkdir(g:plug_home, 'p')
1051 return s:err(printf('Invalid plug directory: %s. '.
1052 \ 'Try to call plug#begin with a valid directory', g:plug_home))
1056 if has('nvim') && !exists('*jobwait') && threads > 1
1057 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
1060 let use_job = s:nvim || s:vim8
1061 let python = (has('python') || has('python3')) && !use_job
1062 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()
1065 \ 'start': reltime(),
1067 \ 'todo': copy(todo),
1072 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1078 call append(0, ['', ''])
1082 let s:clone_opt = get(g:, 'plug_shallow', 1) ?
1083 \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : ''
1085 if has('win32unix') || has('wsl')
1086 let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input'
1089 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1091 " Python version requirement (>= 2.7)
1092 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1094 silent python import platform; print platform.python_version()
1096 let python = s:version_requirement(
1097 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1100 if (python || ruby) && s:update.threads > 1
1107 call s:update_ruby()
1109 call s:update_python()
1112 let lines = getline(4, '$')
1116 let name = s:extract_name(line, '.', '')
1117 if empty(name) || !has_key(printed, name)
1118 call append('$', line)
1120 let printed[name] = 1
1121 if line[0] == 'x' && index(s:update.errors, name) < 0
1122 call add(s:update.errors, name)
1129 call s:update_finish()
1133 while use_job && sync
1142 function! s:log4(name, msg)
1143 call setline(4, printf('- %s (%s)', a:msg, a:name))
1147 function! s:update_finish()
1148 if exists('s:git_terminal_prompt')
1149 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1152 call append(3, '- Updating ...') | 4
1153 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))'))
1154 let [pos, _] = s:logpos(name)
1158 if has_key(spec, 'commit')
1159 call s:log4(name, 'Checking out '.spec.commit)
1160 let out = s:checkout(spec)
1161 elseif has_key(spec, 'tag')
1164 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1165 if !v:shell_error && !empty(tags)
1167 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1171 call s:log4(name, 'Checking out '.tag)
1172 let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1174 let branch = get(spec, 'branch', 'master')
1175 call s:log4(name, 'Merging origin/'.s:esc(branch))
1176 let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1'
1177 \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir)
1179 if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1180 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1181 call s:log4(name, 'Updating submodules. This may take a while.')
1182 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1184 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1186 call add(s:update.errors, name)
1187 call s:regress_bar()
1188 silent execute pos 'd _'
1189 call append(4, msg) | 4
1191 call setline(pos, msg[0])
1197 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")'))
1199 call s:warn('echom', v:exception)
1200 call s:warn('echo', '')
1203 call s:finish(s:update.pull)
1204 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1205 call s:switch_out('normal! gg')
1209 function! s:job_abort()
1210 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1214 for [name, j] in items(s:jobs)
1216 silent! call jobstop(j.jobid)
1218 silent! call job_stop(j.jobid)
1221 call s:rm_rf(g:plugs[name].dir)
1227 function! s:last_non_empty_line(lines)
1228 let len = len(a:lines)
1229 for idx in range(len)
1230 let line = a:lines[len-idx-1]
1238 function! s:job_out_cb(self, data) abort
1240 let data = remove(self.lines, -1) . a:data
1241 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1242 call extend(self.lines, lines)
1243 " To reduce the number of buffer updates
1244 let self.tick = get(self, 'tick', -1) + 1
1245 if !self.running || self.tick % len(s:jobs) == 0
1246 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1247 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1248 call s:log(bullet, self.name, result)
1252 function! s:job_exit_cb(self, data) abort
1253 let a:self.running = 0
1254 let a:self.error = a:data != 0
1255 call s:reap(a:self.name)
1259 function! s:job_cb(fn, job, ch, data)
1260 if !s:plug_window_exists() " plug window closed
1261 return s:job_abort()
1263 call call(a:fn, [a:job, a:data])
1266 function! s:nvim_cb(job_id, data, event) dict abort
1267 return a:event == 'stdout' ?
1268 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1269 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1272 function! s:spawn(name, cmd, opts)
1273 let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1274 \ 'new': get(a:opts, 'new', 0) }
1275 let s:jobs[a:name] = job
1276 let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir, 0) : a:cmd
1277 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1281 \ 'on_stdout': function('s:nvim_cb'),
1282 \ 'on_exit': function('s:nvim_cb'),
1284 let jid = s:plug_call('jobstart', argv, job)
1290 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1291 \ 'Invalid arguments (or job table is full)']
1294 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1295 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1296 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1299 if job_status(jid) == 'run'
1304 let job.lines = ['Failed to start job']
1307 let job.lines = s:lines(call('s:system', [cmd]))
1308 let job.error = v:shell_error != 0
1313 function! s:reap(name)
1314 let job = s:jobs[a:name]
1316 call add(s:update.errors, a:name)
1317 elseif get(job, 'new', 0)
1318 let s:update.new[a:name] = 1
1320 let s:update.bar .= job.error ? 'x' : '='
1322 let bullet = job.error ? 'x' : '-'
1323 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1324 call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1327 call remove(s:jobs, a:name)
1332 let total = len(s:update.all)
1333 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1334 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1335 call s:progress_bar(2, s:update.bar, total)
1340 function! s:logpos(name)
1342 for i in range(4, max > 4 ? max : 4)
1343 if getline(i) =~# '^[-+x*] '.a:name.':'
1344 for j in range(i + 1, max > 5 ? max : 5)
1345 if getline(j) !~ '^ '
1355 function! s:log(bullet, name, lines)
1357 let [b, e] = s:logpos(a:name)
1359 silent execute printf('%d,%d d _', b, e)
1360 if b > winheight('.')
1366 " FIXME For some reason, nomodifiable is set after :d in vim8
1368 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1373 function! s:update_vim()
1381 let pull = s:update.pull
1382 let prog = s:progress_opt(s:nvim || s:vim8)
1383 while 1 " Without TCO, Vim stack is bound to explode
1384 if empty(s:update.todo)
1385 if empty(s:jobs) && !s:update.fin
1386 call s:update_finish()
1387 let s:update.fin = 1
1392 let name = keys(s:update.todo)[0]
1393 let spec = remove(s:update.todo, name)
1394 let new = empty(globpath(spec.dir, '.git', 1))
1396 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1399 let has_tag = has_key(spec, 'tag')
1401 let [error, _] = s:git_validate(spec, 0)
1404 let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : ''
1405 call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir })
1407 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1410 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1414 \ printf('git clone %s %s %s %s 2>&1',
1415 \ has_tag ? '' : s:clone_opt,
1417 \ plug#shellescape(spec.uri, {'script': 0}),
1418 \ plug#shellescape(s:trim(spec.dir), {'script': 0})), { 'new': 1 })
1421 if !s:jobs[name].running
1424 if len(s:jobs) >= s:update.threads
1430 function! s:update_python()
1431 let py_exe = has('python') ? 'python' : 'python3'
1432 execute py_exe "<< EOF"
1439 import Queue as queue
1446 import threading as thr
1451 G_NVIM = vim.eval("has('nvim')") == '1'
1452 G_PULL = vim.eval('s:update.pull') == '1'
1453 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1454 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1455 G_CLONE_OPT = vim.eval('s:clone_opt')
1456 G_PROGRESS = vim.eval('s:progress_opt(1)')
1457 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1458 G_STOP = thr.Event()
1459 G_IS_WIN = vim.eval('s:is_win') == '1'
1461 class PlugError(Exception):
1462 def __init__(self, msg):
1464 class CmdTimedOut(PlugError):
1466 class CmdFailed(PlugError):
1468 class InvalidURI(PlugError):
1470 class Action(object):
1471 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1473 class Buffer(object):
1474 def __init__(self, lock, num_plugs, is_pull):
1476 self.event = 'Updating' if is_pull else 'Installing'
1478 self.maxy = int(vim.eval('winheight(".")'))
1479 self.num_plugs = num_plugs
1481 def __where(self, name):
1482 """ Find first line with name in current buffer. Return line num. """
1483 found, lnum = False, 0
1484 matcher = re.compile('^[-+x*] {0}:'.format(name))
1485 for line in vim.current.buffer:
1486 if matcher.search(line) is not None:
1496 curbuf = vim.current.buffer
1497 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1499 num_spaces = self.num_plugs - len(self.bar)
1500 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1503 vim.command('normal! 2G')
1504 vim.command('redraw')
1506 def write(self, action, name, lines):
1507 first, rest = lines[0], lines[1:]
1508 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1509 msg.extend([' ' + line for line in rest])
1512 if action == Action.ERROR:
1514 vim.command("call add(s:update.errors, '{0}')".format(name))
1515 elif action == Action.DONE:
1518 curbuf = vim.current.buffer
1519 lnum = self.__where(name)
1520 if lnum != -1: # Found matching line num
1522 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1526 curbuf.append(msg, lnum)
1532 class Command(object):
1533 CD = 'cd /d' if G_IS_WIN else 'cd'
1535 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1538 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1539 self.timeout = timeout
1540 self.callback = cb if cb else (lambda msg: None)
1541 self.clean = clean if clean else (lambda: None)
1546 """ Returns true only if command still running. """
1547 return self.proc and self.proc.poll() is None
1549 def execute(self, ntries=3):
1550 """ Execute the command with ntries if CmdTimedOut.
1551 Returns the output of the command if no Exception.
1553 attempt, finished, limit = 0, False, self.timeout
1558 result = self.try_command()
1562 if attempt != ntries:
1564 self.timeout += limit
1568 def notify_retry(self):
1569 """ Retry required for command, notify user. """
1570 for count in range(3, 0, -1):
1572 raise KeyboardInterrupt
1573 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1574 count, 's' if count != 1 else '')
1575 self.callback([msg])
1577 self.callback(['Retrying ...'])
1579 def try_command(self):
1580 """ Execute a cmd & poll for callback. Returns list of output.
1581 Raises CmdFailed -> return code for Popen isn't 0
1582 Raises CmdTimedOut -> command exceeded timeout without new output
1587 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1588 preexec_fn = not G_IS_WIN and os.setsid or None
1589 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1590 stderr=subprocess.STDOUT,
1591 stdin=subprocess.PIPE, shell=True,
1592 preexec_fn=preexec_fn)
1593 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1596 thread_not_started = True
1597 while thread_not_started:
1600 thread_not_started = False
1601 except RuntimeError:
1606 raise KeyboardInterrupt
1608 if first_line or random.random() < G_LOG_PROB:
1610 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1612 self.callback([line])
1614 time_diff = time.time() - os.path.getmtime(tfile.name)
1615 if time_diff > self.timeout:
1616 raise CmdTimedOut(['Timeout!'])
1621 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1623 if self.proc.returncode != 0:
1624 raise CmdFailed([''] + result)
1631 def terminate(self):
1632 """ Terminate process and cleanup. """
1635 os.kill(self.proc.pid, signal.SIGINT)
1637 os.killpg(self.proc.pid, signal.SIGTERM)
1640 class Plugin(object):
1641 def __init__(self, name, args, buf_q, lock):
1646 self.tag = args.get('tag', 0)
1650 if os.path.exists(self.args['dir']):
1655 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1656 except PlugError as exc:
1657 self.write(Action.ERROR, self.name, exc.msg)
1658 except KeyboardInterrupt:
1660 self.write(Action.ERROR, self.name, ['Interrupted!'])
1662 # Any exception except those above print stack trace
1663 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1664 self.write(Action.ERROR, self.name, msg.split('\n'))
1668 target = self.args['dir']
1669 if target[-1] == '\\':
1670 target = target[0:-1]
1675 shutil.rmtree(target)
1680 self.write(Action.INSTALL, self.name, ['Installing ...'])
1681 callback = functools.partial(self.write, Action.INSTALL, self.name)
1682 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1683 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1685 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1686 result = com.execute(G_RETRIES)
1687 self.write(Action.DONE, self.name, result[-1:])
1690 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1691 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1692 result = command.execute(G_RETRIES)
1696 actual_uri = self.repo_uri()
1697 expect_uri = self.args['uri']
1698 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1699 ma = regex.match(actual_uri)
1700 mb = regex.match(expect_uri)
1701 if ma is None or mb is None or ma.groups() != mb.groups():
1703 'Invalid URI: {0}'.format(actual_uri),
1704 'Expected {0}'.format(expect_uri),
1705 'PlugClean required.']
1706 raise InvalidURI(msg)
1709 self.write(Action.UPDATE, self.name, ['Updating ...'])
1710 callback = functools.partial(self.write, Action.UPDATE, self.name)
1711 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1712 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1713 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1714 result = com.execute(G_RETRIES)
1715 self.write(Action.DONE, self.name, result[-1:])
1717 self.write(Action.DONE, self.name, ['Already installed'])
1719 def write(self, action, name, msg):
1720 self.buf_q.put((action, name, msg))
1722 class PlugThread(thr.Thread):
1723 def __init__(self, tname, args):
1724 super(PlugThread, self).__init__()
1729 thr.current_thread().name = self.tname
1730 buf_q, work_q, lock = self.args
1733 while not G_STOP.is_set():
1734 name, args = work_q.get_nowait()
1735 plug = Plugin(name, args, buf_q, lock)
1741 class RefreshThread(thr.Thread):
1742 def __init__(self, lock):
1743 super(RefreshThread, self).__init__()
1750 thread_vim_command('noautocmd normal! a')
1754 self.running = False
1757 def thread_vim_command(cmd):
1758 vim.session.threadsafe_call(lambda: vim.command(cmd))
1760 def thread_vim_command(cmd):
1764 return '"' + name.replace('"', '\"') + '"'
1766 def nonblock_read(fname):
1767 """ Read a file with nonblock flag. Return the last line. """
1768 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1769 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1772 line = buf.rstrip('\r\n')
1773 left = max(line.rfind('\r'), line.rfind('\n'))
1781 thr.current_thread().name = 'main'
1782 nthreads = int(vim.eval('s:update.threads'))
1783 plugs = vim.eval('s:update.todo')
1784 mac_gui = vim.eval('s:mac_gui') == '1'
1787 buf = Buffer(lock, len(plugs), G_PULL)
1788 buf_q, work_q = queue.Queue(), queue.Queue()
1789 for work in plugs.items():
1792 start_cnt = thr.active_count()
1793 for num in range(nthreads):
1794 tname = 'PlugT-{0:02}'.format(num)
1795 thread = PlugThread(tname, (buf_q, work_q, lock))
1798 rthread = RefreshThread(lock)
1801 while not buf_q.empty() or thr.active_count() != start_cnt:
1803 action, name, msg = buf_q.get(True, 0.25)
1804 buf.write(action, name, ['OK'] if not msg else msg)
1808 except KeyboardInterrupt:
1819 function! s:update_ruby()
1822 SEP = ["\r", "\n", nil]
1826 char = readchar rescue return
1827 if SEP.include? char.chr
1836 end unless defined?(PlugStream)
1839 %["#{arg.gsub('"', '\"')}"]
1844 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
1845 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
1847 unless `which pgrep 2> /dev/null`.empty?
1849 until children.empty?
1850 children = children.map { |pid|
1851 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
1856 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
1860 def compare_git_uri a, b
1861 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
1862 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
1869 iswin = VIM::evaluate('s:is_win').to_i == 1
1870 pull = VIM::evaluate('s:update.pull').to_i == 1
1871 base = VIM::evaluate('g:plug_home')
1872 all = VIM::evaluate('s:update.todo')
1873 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
1874 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
1875 nthr = VIM::evaluate('s:update.threads').to_i
1876 maxy = VIM::evaluate('winheight(".")').to_i
1877 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
1878 cd = iswin ? 'cd /d' : 'cd'
1879 tot = VIM::evaluate('len(s:update.todo)') || 0
1881 skip = 'Already installed'
1883 take1 = proc { mtx.synchronize { running && all.shift } }
1886 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
1887 $curbuf[2] = '[' + bar.ljust(tot) + ']'
1888 VIM::command('normal! 2G')
1889 VIM::command('redraw')
1891 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
1892 log = proc { |name, result, type|
1894 ing = ![true, false].include?(type)
1895 bar += type ? '=' : 'x' unless ing
1897 when :install then '+' when :update then '*'
1898 when true, nil then '-' else
1899 VIM::command("call add(s:update.errors, '#{name}')")
1903 if type || type.nil?
1904 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
1905 elsif result =~ /^Interrupted|^Timeout/
1906 ["#{b} #{name}: #{result}"]
1908 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
1910 if lnum = where.call(name)
1912 lnum = 4 if ing && lnum > maxy
1914 result.each_with_index do |line, offset|
1915 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
1920 bt = proc { |cmd, name, type, cleanup|
1928 Timeout::timeout(timeout) do
1929 tmp = VIM::evaluate('tempname()')
1930 system("(#{cmd}) > #{tmp}")
1931 data = File.read(tmp).chomp
1932 File.unlink tmp rescue nil
1935 fd = IO.popen(cmd).extend(PlugStream)
1937 log_prob = 1.0 / nthr
1938 while line = Timeout::timeout(timeout) { fd.get_line }
1940 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
1945 [$? == 0, data.chomp]
1946 rescue Timeout::Error, Interrupt => e
1947 if fd && !fd.closed?
1951 cleanup.call if cleanup
1952 if e.is_a?(Timeout::Error) && tried < tries
1953 3.downto(1) do |countdown|
1954 s = countdown > 1 ? 's' : ''
1955 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
1958 log.call name, 'Retrying ...', type
1961 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
1964 main = Thread.current
1966 watcher = Thread.new {
1968 while VIM::evaluate('getchar(1)')
1972 require 'io/console' # >= Ruby 1.9
1973 nil until IO.console.getch == 3.chr
1977 threads.each { |t| t.raise Interrupt } unless vim7
1979 threads.each { |t| t.join rescue nil }
1982 refresh = Thread.new {
1985 break unless running
1986 VIM::command('noautocmd normal! a')
1990 } if VIM::evaluate('s:mac_gui') == 1
1992 clone_opt = VIM::evaluate('s:clone_opt')
1993 progress = VIM::evaluate('s:progress_opt(1)')
1996 threads << Thread.new {
1997 while pair = take1.call
1999 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2000 exists = File.directory? dir
2003 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2004 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2005 current_uri = data.lines.to_a.last
2007 if data =~ /^Interrupted|^Timeout/
2010 [false, [data.chomp, "PlugClean required."].join($/)]
2012 elsif !compare_git_uri(current_uri, uri)
2013 [false, ["Invalid URI: #{current_uri}",
2015 "PlugClean required."].join($/)]
2018 log.call name, 'Updating ...', :update
2019 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2020 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2026 d = esc dir.sub(%r{[\\/]+$}, '')
2027 log.call name, 'Installing ...', :install
2028 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2032 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2033 log.call name, result, ok
2038 threads.each { |t| t.join rescue nil }
2040 refresh.kill if refresh
2045 function! s:shellesc_cmd(arg, script)
2046 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2047 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2050 function! s:shellesc_ps1(arg)
2051 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2054 function! s:shellesc_sh(arg)
2055 return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2058 function! plug#shellescape(arg, ...)
2059 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2060 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2061 let script = get(opts, 'script', 1)
2062 if shell =~# 'cmd\.exe'
2063 return s:shellesc_cmd(a:arg, script)
2064 elseif shell =~# 'powershell\.exe' || shell =~# 'pwsh$'
2065 return s:shellesc_ps1(a:arg)
2067 return s:shellesc_sh(a:arg)
2070 function! s:glob_dir(path)
2071 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2074 function! s:progress_bar(line, bar, total)
2075 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2078 function! s:compare_git_uri(a, b)
2079 " See `git help clone'
2080 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2081 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2082 " file:// / junegunn/vim-plug [/]
2083 " / junegunn/vim-plug [/]
2084 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2085 let ma = matchlist(a:a, pat)
2086 let mb = matchlist(a:b, pat)
2087 return ma[1:2] ==# mb[1:2]
2090 function! s:format_message(bullet, name, message)
2092 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2094 let lines = map(s:lines(a:message), '" ".v:val')
2095 return extend([printf('x %s:', a:name)], lines)
2099 function! s:with_cd(cmd, dir, ...)
2100 let script = a:0 > 0 ? a:1 : 1
2101 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
2104 function! s:system(cmd, ...)
2107 let [sh, shellcmdflag, shrd] = s:chsh(1)
2108 let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd
2110 let [batchfile, cmd] = s:batchfile(cmd)
2114 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2115 if s:is_win && filereadable(batchfile)
2116 call delete(batchfile)
2121 function! s:system_chomp(...)
2122 let ret = call('s:system', a:000)
2123 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2126 function! s:git_validate(spec, check_branch)
2128 if isdirectory(a:spec.dir)
2129 let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url', a:spec.dir))
2130 let remote = result[-1]
2132 let err = join([remote, 'PlugClean required.'], "\n")
2133 elseif !s:compare_git_uri(remote, a:spec.uri)
2134 let err = join(['Invalid URI: '.remote,
2135 \ 'Expected: '.a:spec.uri,
2136 \ 'PlugClean required.'], "\n")
2137 elseif a:check_branch && has_key(a:spec, 'commit')
2138 let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir))
2139 let sha = result[-1]
2141 let err = join(add(result, 'PlugClean required.'), "\n")
2142 elseif !s:hash_match(sha, a:spec.commit)
2143 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2144 \ a:spec.commit[:6], sha[:6]),
2145 \ 'PlugUpdate required.'], "\n")
2147 elseif a:check_branch
2148 let branch = result[0]
2150 if has_key(a:spec, 'tag')
2151 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2152 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2153 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2154 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2157 elseif a:spec.branch !=# branch
2158 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2159 \ branch, a:spec.branch)
2162 let [ahead, behind] = split(s:lastline(s:system(printf(
2163 \ 'git rev-list --count --left-right HEAD...origin/%s',
2164 \ a:spec.branch), a:spec.dir)), '\t')
2165 if !v:shell_error && ahead
2167 " Only mention PlugClean if diverged, otherwise it's likely to be
2168 " pushable (and probably not that messed up).
2170 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2171 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind)
2173 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2174 \ .'Cannot update until local changes are pushed.',
2175 \ a:spec.branch, ahead)
2181 let err = 'Not found'
2183 return [err, err =~# 'PlugClean']
2186 function! s:rm_rf(dir)
2187 if isdirectory(a:dir)
2188 call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . plug#shellescape(a:dir))
2192 function! s:clean(force)
2194 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2197 " List of valid directories
2200 let [cnt, total] = [0, len(g:plugs)]
2201 for [name, spec] in items(g:plugs)
2202 if !s:is_managed(name)
2203 call add(dirs, spec.dir)
2205 let [err, clean] = s:git_validate(spec, 1)
2207 let errs[spec.dir] = s:lines(err)[0]
2209 call add(dirs, spec.dir)
2213 call s:progress_bar(2, repeat('=', cnt), total)
2220 let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2221 let allowed[dir] = 1
2222 for child in s:glob_dir(dir)
2223 let allowed[child] = 1
2228 let found = sort(s:glob_dir(g:plug_home))
2230 let f = remove(found, 0)
2231 if !has_key(allowed, f) && isdirectory(f)
2233 call append(line('$'), '- ' . f)
2235 call append(line('$'), ' ' . errs[f])
2237 let found = filter(found, 'stridx(v:val, f) != 0')
2244 call append(line('$'), 'Already clean.')
2246 let s:clean_count = 0
2247 call append(3, ['Directories to delete:', ''])
2249 if a:force || s:ask_no_interrupt('Delete all directories?')
2250 call s:delete([6, line('$')], 1)
2252 call setline(4, 'Cancelled.')
2253 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2254 nmap <silent> <buffer> dd d_
2255 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2256 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2260 setlocal nomodifiable
2263 function! s:delete_op(type, ...)
2264 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2267 function! s:delete(range, force)
2268 let [l1, l2] = a:range
2271 let line = getline(l1)
2272 if line =~ '^- ' && isdirectory(line[2:])
2275 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2276 let force = force || answer > 1
2278 call s:rm_rf(line[2:])
2280 call setline(l1, '~'.line[1:])
2281 let s:clean_count += 1
2282 call setline(4, printf('Removed %d directories.', s:clean_count))
2283 setlocal nomodifiable
2290 function! s:upgrade()
2291 echo 'Downloading the latest version of vim-plug'
2293 let tmp = s:plug_tempname()
2294 let new = tmp . '/plug.vim'
2297 let out = s:system(printf('git clone --depth 1 %s %s', plug#shellescape(s:plug_src), plug#shellescape(tmp)))
2299 return s:err('Error upgrading vim-plug: '. out)
2302 if readfile(s:me) ==# readfile(new)
2303 echo 'vim-plug is already up-to-date'
2306 call rename(s:me, s:me . '.old')
2307 call rename(new, s:me)
2309 echo 'vim-plug has been upgraded'
2313 silent! call s:rm_rf(tmp)
2317 function! s:upgrade_specs()
2318 for spec in values(g:plugs)
2319 let spec.frozen = get(spec, 'frozen', 0)
2323 function! s:status()
2325 call append(0, 'Checking plugins')
2330 let [cnt, total] = [0, len(g:plugs)]
2331 for [name, spec] in items(g:plugs)
2332 let is_dir = isdirectory(spec.dir)
2333 if has_key(spec, 'uri')
2335 let [err, _] = s:git_validate(spec, 1)
2336 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2338 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2342 let [valid, msg] = [1, 'OK']
2344 let [valid, msg] = [0, 'Not found.']
2349 " `s:loaded` entry can be missing if PlugUpgraded
2350 if is_dir && get(s:loaded, name, -1) == 0
2352 let msg .= ' (not loaded)'
2354 call s:progress_bar(2, repeat('=', cnt), total)
2355 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2359 call setline(1, 'Finished. '.ecnt.' error(s).')
2361 setlocal nomodifiable
2363 echo "Press 'L' on each line to load plugin, or 'U' to update"
2364 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2365 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2369 function! s:extract_name(str, prefix, suffix)
2370 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2373 function! s:status_load(lnum)
2374 let line = getline(a:lnum)
2375 let name = s:extract_name(line, '-', '(not loaded)')
2377 call plug#load(name)
2379 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2380 setlocal nomodifiable
2384 function! s:status_update() range
2385 let lines = getline(a:firstline, a:lastline)
2386 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2389 execute 'PlugUpdate' join(names)
2393 function! s:is_preview_window_open()
2401 function! s:find_name(lnum)
2402 for lnum in reverse(range(1, a:lnum))
2403 let line = getline(lnum)
2407 let name = s:extract_name(line, '-', '')
2415 function! s:preview_commit()
2416 if b:plug_preview < 0
2417 let b:plug_preview = !s:is_preview_window_open()
2420 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2425 let name = s:find_name(line('.'))
2426 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2430 if exists('g:plug_pwindow') && !s:is_preview_window_open()
2431 execute g:plug_pwindow
2437 setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
2440 let [sh, shellcmdflag, shrd] = s:chsh(1)
2441 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
2443 let [batchfile, cmd] = s:batchfile(cmd)
2445 execute 'silent %!' cmd
2447 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2448 if s:is_win && filereadable(batchfile)
2449 call delete(batchfile)
2452 setlocal nomodifiable
2453 nnoremap <silent> <buffer> q :q<cr>
2457 function! s:section(flags)
2458 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2461 function! s:format_git_log(line)
2463 let tokens = split(a:line, nr2char(1))
2465 return indent.substitute(a:line, '\s*$', '', '')
2467 let [graph, sha, refs, subject, date] = tokens
2468 let tag = matchstr(refs, 'tag: [^,)]\+')
2469 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2470 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2473 function! s:append_ul(lnum, text)
2474 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2479 call append(0, ['Collecting changes ...', ''])
2482 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2483 call s:progress_bar(2, bar, len(total))
2484 for origin in [1, 0]
2485 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2489 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2491 let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..'
2492 let cmd = 'git log --graph --color=never '
2493 \ . (s:git_version_requirement(2, 10, 0) ? '--no-show-signature ' : '')
2494 \ . join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 'plug#shellescape(v:val)'))
2495 if has_key(v, 'rtp')
2496 let cmd .= ' -- '.plug#shellescape(v.rtp)
2498 let diff = s:system_chomp(cmd, v.dir)
2500 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2501 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2502 let cnts[origin] += 1
2505 call s:progress_bar(2, bar, len(total))
2510 call append(5, ['', 'N/A'])
2513 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2514 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2516 if cnts[0] || cnts[1]
2517 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2518 if empty(maparg("\<cr>", 'n'))
2519 nmap <buffer> <cr> <plug>(plug-preview)
2521 if empty(maparg('o', 'n'))
2522 nmap <buffer> o <plug>(plug-preview)
2526 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2527 echo "Press 'X' on each block to revert the update"
2530 setlocal nomodifiable
2533 function! s:revert()
2534 if search('^Pending updates', 'bnW')
2538 let name = s:find_name(line('.'))
2539 if empty(name) || !has_key(g:plugs, name) ||
2540 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2544 call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2547 setlocal nomodifiable
2551 function! s:snapshot(force, ...) abort
2554 call append(0, ['" Generated by vim-plug',
2555 \ '" '.strftime("%c"),
2556 \ '" :source this file in vim to restore the snapshot',
2557 \ '" or execute: vim -S snapshot.vim',
2558 \ '', '', 'PlugUpdate!'])
2560 let anchor = line('$') - 3
2561 let names = sort(keys(filter(copy(g:plugs),
2562 \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
2563 for name in reverse(names)
2564 let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir)
2566 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2572 let fn = s:plug_expand(a:1)
2573 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2576 call writefile(getline(1, '$'), fn)
2577 echo 'Saved as '.a:1
2578 silent execute 'e' s:esc(fn)
2583 function! s:split_rtp()
2584 return split(&rtp, '\\\@<!,')
2587 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2588 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2590 if exists('g:plugs')
2591 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2592 call s:upgrade_specs()
2593 call s:define_commands()
2596 let &cpo = s:cpo_save