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 let s:me = resolve(expand('<sfile>:p'))
103 let s:base_spec = { 'branch': 'master', 'frozen': 0 }
105 \ 'string': type(''),
108 \ 'funcref': type(function('call'))
110 let s:loaded = get(s:, 'loaded', {})
111 let s:triggers = get(s:, 'triggers', {})
113 function! plug#begin(...)
115 let s:plug_home_org = a:1
116 let home = s:path(fnamemodify(expand(a:1), ':p'))
117 elseif exists('g:plug_home')
118 let home = s:path(g:plug_home)
120 let home = s:path(split(&rtp, ',')[0]) . '/plugged'
122 return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
124 if fnamemodify(home, ':t') ==# 'plugin' && fnamemodify(home, ':h') ==# s:first_rtp
125 return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.')
128 let g:plug_home = home
130 let g:plugs_order = []
133 call s:define_commands()
137 function! s:define_commands()
138 command! -nargs=+ -bar Plug call plug#(<args>)
139 if !executable('git')
140 return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
142 command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>])
143 command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(<bang>0, [<f-args>])
144 command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0)
145 command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
146 command! -nargs=0 -bar PlugStatus call s:status()
147 command! -nargs=0 -bar PlugDiff call s:diff()
148 command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>)
152 return type(a:v) == s:TYPE.list ? a:v : [a:v]
156 return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
159 function! s:glob(from, pattern)
160 return s:lines(globpath(a:from, a:pattern))
163 function! s:source(from, ...)
166 for vim in s:glob(a:from, pattern)
167 execute 'source' s:esc(vim)
174 function! s:assoc(dict, key, val)
175 let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
178 function! s:ask(message, ...)
181 let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
185 return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
188 function! s:ask_no_interrupt(...)
190 return call('s:ask', a:000)
197 if !exists('g:plugs')
198 return s:err('Call plug#begin() first')
201 if exists('#PlugLOD')
207 let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
209 if exists('g:did_load_filetypes')
212 for name in g:plugs_order
213 if !has_key(g:plugs, name)
216 let plug = g:plugs[name]
217 if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for')
218 let s:loaded[name] = 1
222 if has_key(plug, 'on')
223 let s:triggers[name] = { 'map': [], 'cmd': [] }
224 for cmd in s:to_a(plug.on)
225 if cmd =~? '^<Plug>.\+'
226 if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
227 call s:assoc(lod.map, cmd, name)
229 call add(s:triggers[name].map, cmd)
230 elseif cmd =~# '^[A-Z]'
231 let cmd = substitute(cmd, '!*$', '', '')
232 if exists(':'.cmd) != 2
233 call s:assoc(lod.cmd, cmd, name)
235 call add(s:triggers[name].cmd, cmd)
237 call s:err('Invalid `on` option: '.cmd.
238 \ '. Should start with an uppercase letter or `<Plug>`.')
243 if has_key(plug, 'for')
244 let types = s:to_a(plug.for)
246 augroup filetypedetect
247 call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
251 call s:assoc(lod.ft, type, name)
256 for [cmd, names] in items(lod.cmd)
258 \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
259 \ cmd, string(cmd), string(names))
262 for [map, names] in items(lod.map)
263 for [mode, map_prefix, key_prefix] in
264 \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
266 \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
267 \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
271 for [ft, names] in items(lod.ft)
273 execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
274 \ ft, string(ft), string(names))
279 filetype plugin indent on
280 if has('vim_starting')
281 if has('syntax') && !exists('g:syntax_on')
285 call s:reload_plugins()
289 function! s:loaded_names()
290 return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
293 function! s:load_plugin(spec)
294 call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
297 function! s:reload_plugins()
298 for name in s:loaded_names()
299 call s:load_plugin(g:plugs[name])
303 function! s:trim(str)
304 return substitute(a:str, '[\/]\+$', '', '')
307 function! s:version_requirement(val, min)
308 for idx in range(0, len(a:min) - 1)
309 let v = get(a:val, idx, 0)
310 if v < a:min[idx] | return 0
311 elseif v > a:min[idx] | return 1
317 function! s:git_version_requirement(...)
318 if !exists('s:git_version')
319 let s:git_version = map(split(split(s:system('git --version'))[2], '\.'), 'str2nr(v:val)')
321 return s:version_requirement(s:git_version, a:000)
324 function! s:progress_opt(base)
325 return a:base && !s:is_win &&
326 \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
330 function! s:rtp(spec)
331 return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
334 function! s:path(path)
335 return s:trim(substitute(a:path, '/', '\', 'g'))
338 function! s:dirpath(path)
339 return s:path(a:path) . '\'
342 function! s:is_local_plug(repo)
343 return a:repo =~? '^[a-z]:\|^[%~]'
346 function! s:rtp(spec)
347 return s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
350 function! s:path(path)
351 return s:trim(a:path)
354 function! s:dirpath(path)
355 return substitute(a:path, '[/\\]*$', '/', '')
358 function! s:is_local_plug(repo)
359 return a:repo[0] =~ '[/$~]'
365 echom '[vim-plug] '.a:msg
369 function! s:warn(cmd, msg)
371 execute a:cmd 'a:msg'
375 function! s:esc(path)
376 return escape(a:path, ' ')
379 function! s:escrtp(path)
380 return escape(a:path, ' ,')
383 function! s:remove_rtp()
384 for name in s:loaded_names()
385 let rtp = s:rtp(g:plugs[name])
386 execute 'set rtp-='.s:escrtp(rtp)
387 let after = globpath(rtp, 'after')
388 if isdirectory(after)
389 execute 'set rtp-='.s:escrtp(after)
394 function! s:reorg_rtp()
395 if !empty(s:first_rtp)
396 execute 'set rtp-='.s:first_rtp
397 execute 'set rtp-='.s:last_rtp
400 " &rtp is modified from outside
401 if exists('s:prtp') && s:prtp !=# &rtp
406 let s:middle = get(s:, 'middle', &rtp)
407 let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
408 let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
409 let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
411 \ . join(map(afters, 'escape(v:val, ",")'), ',')
412 let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
415 if !empty(s:first_rtp)
416 execute 'set rtp^='.s:first_rtp
417 execute 'set rtp+='.s:last_rtp
421 function! s:doautocmd(...)
422 if exists('#'.join(a:000, '#'))
423 execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
427 function! s:dobufread(names)
429 let path = s:rtp(g:plugs[name]).'/**'
430 for dir in ['ftdetect', 'ftplugin']
431 if len(finddir(dir, path))
432 if exists('#BufRead')
441 function! plug#load(...)
443 return s:err('Argument missing: plugin name(s) required')
445 if !exists('g:plugs')
446 return s:err('plug#begin was not called')
448 let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
449 let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
451 let s = len(unknowns) > 1 ? 's' : ''
452 return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
454 let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
457 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
459 call s:dobufread(unloaded)
465 function! s:remove_triggers(name)
466 if !has_key(s:triggers, a:name)
469 for cmd in s:triggers[a:name].cmd
470 execute 'silent! delc' cmd
472 for map in s:triggers[a:name].map
473 execute 'silent! unmap' map
474 execute 'silent! iunmap' map
476 call remove(s:triggers, a:name)
479 function! s:lod(names, types, ...)
481 call s:remove_triggers(name)
482 let s:loaded[name] = 1
487 let rtp = s:rtp(g:plugs[name])
489 call s:source(rtp, dir.'/**/*.vim')
492 if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
493 execute 'runtime' a:1
495 call s:source(rtp, a:2)
497 call s:doautocmd('User', name)
501 function! s:lod_ft(pat, names)
502 let syn = 'syntax/'.a:pat.'.vim'
503 call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
504 execute 'autocmd! PlugLOD FileType' a:pat
505 call s:doautocmd('filetypeplugin', 'FileType')
506 call s:doautocmd('filetypeindent', 'FileType')
509 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
510 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
511 call s:dobufread(a:names)
512 execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
515 function! s:lod_map(map, names, with_prefix, prefix)
516 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
517 call s:dobufread(a:names)
524 let extra .= nr2char(c)
528 let prefix = v:count ? v:count : ''
529 let prefix .= '"'.v:register.a:prefix
532 let prefix = "\<esc>" . prefix
534 let prefix .= v:operator
536 call feedkeys(prefix, 'n')
538 call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
541 function! plug#(repo, ...)
543 return s:err('Invalid number of arguments (1..2)')
547 let repo = s:trim(a:repo)
548 let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
549 let name = get(opts, 'as', fnamemodify(repo, ':t:s?\.git$??'))
550 let spec = extend(s:infer_properties(name, repo), opts)
551 if !has_key(g:plugs, name)
552 call add(g:plugs_order, name)
554 let g:plugs[name] = spec
555 let s:loaded[name] = get(s:loaded, name, 0)
557 return s:err(v:exception)
561 function! s:parse_options(arg)
562 let opts = copy(s:base_spec)
563 let type = type(a:arg)
564 if type == s:TYPE.string
566 elseif type == s:TYPE.dict
567 call extend(opts, a:arg)
568 if has_key(opts, 'dir')
569 let opts.dir = s:dirpath(expand(opts.dir))
572 throw 'Invalid argument type (expected: string or dictionary)'
577 function! s:infer_properties(name, repo)
579 if s:is_local_plug(repo)
580 return { 'dir': s:dirpath(expand(repo)) }
586 throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
588 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
589 let uri = printf(fmt, repo)
591 return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
595 function! s:install(force, names)
596 call s:update_impl(0, a:force, a:names)
599 function! s:update(force, names)
600 call s:update_impl(1, a:force, a:names)
603 function! plug#helptags()
604 if !exists('g:plugs')
605 return s:err('plug#begin was not called')
607 for spec in values(g:plugs)
608 let docd = join([s:rtp(spec), 'doc'], '/')
610 silent! execute 'helptags' s:esc(docd)
618 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
619 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
620 syn match plugNumber /[0-9]\+[0-9.]*/ contained
621 syn match plugBracket /[[\]]/ contained
622 syn match plugX /x/ contained
623 syn match plugDash /^-/
624 syn match plugPlus /^+/
625 syn match plugStar /^*/
626 syn match plugMessage /\(^- \)\@<=.*/
627 syn match plugName /\(^- \)\@<=[^ ]*:/
628 syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
629 syn match plugTag /(tag: [^)]\+)/
630 syn match plugInstall /\(^+ \)\@<=[^:]*/
631 syn match plugUpdate /\(^* \)\@<=[^:]*/
632 syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
633 syn match plugEdge /^ \X\+$/
634 syn match plugEdge /^ \X*/ contained nextgroup=plugSha
635 syn match plugSha /[0-9a-f]\{7,9}/ contained
636 syn match plugRelDate /([^)]*)$/ contained
637 syn match plugNotLoaded /(not loaded)$/
638 syn match plugError /^x.*/
639 syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
640 syn match plugH2 /^.*:\n-\+$/
641 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
642 hi def link plug1 Title
643 hi def link plug2 Repeat
644 hi def link plugH2 Type
645 hi def link plugX Exception
646 hi def link plugBracket Structure
647 hi def link plugNumber Number
649 hi def link plugDash Special
650 hi def link plugPlus Constant
651 hi def link plugStar Boolean
653 hi def link plugMessage Function
654 hi def link plugName Label
655 hi def link plugInstall Function
656 hi def link plugUpdate Type
658 hi def link plugError Error
659 hi def link plugDeleted Ignore
660 hi def link plugRelDate Comment
661 hi def link plugEdge PreProc
662 hi def link plugSha Identifier
663 hi def link plugTag Constant
665 hi def link plugNotLoaded Comment
668 function! s:lpad(str, len)
669 return a:str . repeat(' ', a:len - len(a:str))
672 function! s:lines(msg)
673 return split(a:msg, "[\r\n]")
676 function! s:lastline(msg)
677 return get(s:lines(a:msg), -1, '')
680 function! s:new_window()
681 execute get(g:, 'plug_window', 'vertical topleft new')
684 function! s:plug_window_exists()
685 let buflist = tabpagebuflist(s:plug_tab)
686 return !empty(buflist) && index(buflist, s:plug_buf) >= 0
689 function! s:switch_in()
690 if !s:plug_window_exists()
694 if winbufnr(0) != s:plug_buf
695 let s:pos = [tabpagenr(), winnr(), winsaveview()]
696 execute 'normal!' s:plug_tab.'gt'
697 let winnr = bufwinnr(s:plug_buf)
698 execute winnr.'wincmd w'
699 call add(s:pos, winsaveview())
701 let s:pos = [winsaveview()]
708 function! s:switch_out(...)
709 call winrestview(s:pos[-1])
710 setlocal nomodifiable
716 execute 'normal!' s:pos[0].'gt'
717 execute s:pos[1] 'wincmd w'
718 call winrestview(s:pos[2])
722 function! s:finish_bindings()
723 nnoremap <silent> <buffer> R :call <SID>retry()<cr>
724 nnoremap <silent> <buffer> D :PlugDiff<cr>
725 nnoremap <silent> <buffer> S :PlugStatus<cr>
726 nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
727 xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
728 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
729 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
732 function! s:prepare(...)
734 throw 'Invalid current working directory. Cannot proceed.'
737 for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
739 throw evar.' detected. Cannot proceed.'
745 if b:plug_preview == 1
753 nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>bd<cr>
755 call s:finish_bindings()
757 let b:plug_preview = -1
758 let s:plug_tab = tabpagenr()
759 let s:plug_buf = winbufnr(0)
762 for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
763 execute 'silent! unmap <buffer>' k
765 setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
766 if exists('+colorcolumn')
767 setlocal colorcolumn=
770 if exists('g:syntax_on')
775 function! s:assign_name()
777 let prefix = '[Plugins]'
780 while bufexists(name)
781 let name = printf('%s (%s)', prefix, idx)
784 silent! execute 'f' fnameescape(name)
787 function! s:chsh(swap)
788 let prev = [&shell, &shellcmdflag, &shellredir]
790 set shell=cmd.exe shellcmdflag=/c shellredir=>%s\ 2>&1
792 set shell=sh shellredir=>%s\ 2>&1
797 function! s:bang(cmd, ...)
799 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
800 " FIXME: Escaping is incomplete. We could use shellescape with eval,
801 " but it won't work on Windows.
802 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
804 let batchfile = tempname().'.bat'
805 call writefile(["@echo off\r", cmd . "\r"], batchfile)
808 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
809 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
812 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
814 call delete(batchfile)
817 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
820 function! s:regress_bar()
821 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
822 call s:progress_bar(2, bar, len(bar))
825 function! s:is_updated(dir)
826 return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir))
829 function! s:do(pull, force, todo)
830 for [name, spec] in items(a:todo)
831 if !isdirectory(spec.dir)
834 let installed = has_key(s:update.new, name)
835 let updated = installed ? 0 :
836 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
837 if a:force || installed || updated
838 execute 'cd' s:esc(spec.dir)
839 call append(3, '- Post-update hook for '. name .' ... ')
841 let type = type(spec.do)
842 if type == s:TYPE.string
844 if !get(s:loaded, name, 0)
845 let s:loaded[name] = 1
848 call s:load_plugin(spec)
852 let error = v:exception
854 if !s:plug_window_exists()
856 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
859 let error = s:bang(spec.do)
861 elseif type == s:TYPE.funcref
863 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
864 call spec.do({ 'name': name, 'status': status, 'force': a:force })
866 let error = v:exception
869 let error = 'Invalid hook type'
872 call setline(4, empty(error) ? (getline(4) . 'OK')
873 \ : ('x' . getline(4)[1:] . error))
875 call add(s:update.errors, name)
883 function! s:hash_match(a, b)
884 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
887 function! s:checkout(spec)
888 let sha = a:spec.commit
889 let output = s:system('git rev-parse HEAD', a:spec.dir)
890 if !v:shell_error && !s:hash_match(sha, s:lines(output)[0])
891 let output = s:system(
892 \ 'git fetch --depth 999999 && git checkout '.s:esc(sha).' --', a:spec.dir)
897 function! s:finish(pull)
898 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
900 let s = new_frozen > 1 ? 's' : ''
901 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
903 call append(3, '- Finishing ... ') | 4
907 call setline(4, getline(4) . 'Done!')
910 if !empty(s:update.errors)
911 call add(msgs, "Press 'R' to retry.")
913 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
914 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
915 call add(msgs, "Press 'D' to see the updated changes.")
918 call s:finish_bindings()
922 if empty(s:update.errors)
926 call s:update_impl(s:update.pull, s:update.force,
927 \ extend(copy(s:update.errors), [s:update.threads]))
930 function! s:is_managed(name)
931 return has_key(g:plugs[a:name], 'uri')
934 function! s:names(...)
935 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
938 function! s:check_ruby()
939 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
940 if !exists('g:plug_ruby')
942 return s:warn('echom', 'Warning: Ruby interface is broken')
944 let ruby_version = split(g:plug_ruby, '\.')
946 return s:version_requirement(ruby_version, [1, 8, 7])
949 function! s:update_impl(pull, force, args) abort
950 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
951 let args = filter(copy(a:args), 'v:val != "--sync"')
952 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
953 \ remove(args, -1) : get(g:, 'plug_threads', 16)
955 let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
956 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
957 \ filter(managed, 'index(args, v:key) >= 0')
960 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
963 if !s:is_win && s:git_version_requirement(2, 3)
964 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
965 let $GIT_TERMINAL_PROMPT = 0
966 for plug in values(todo)
967 let plug.uri = substitute(plug.uri,
968 \ '^https://git::@github\.com', 'https://github.com', '')
972 if !isdirectory(g:plug_home)
974 call mkdir(g:plug_home, 'p')
976 return s:err(printf('Invalid plug directory: %s. '.
977 \ 'Try to call plug#begin with a valid directory', g:plug_home))
981 if has('nvim') && !exists('*jobwait') && threads > 1
982 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
985 let use_job = s:nvim || s:vim8
986 let python = (has('python') || has('python3')) && !use_job
987 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()
990 \ 'start': reltime(),
992 \ 'todo': copy(todo),
997 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1003 call append(0, ['', ''])
1007 let s:clone_opt = get(g:, 'plug_shallow', 1) ?
1008 \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : ''
1011 let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input'
1014 " Python version requirement (>= 2.7)
1015 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1017 silent python import platform; print platform.python_version()
1019 let python = s:version_requirement(
1020 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1023 if (python || ruby) && s:update.threads > 1
1030 call s:update_ruby()
1032 call s:update_python()
1035 let lines = getline(4, '$')
1039 let name = s:extract_name(line, '.', '')
1040 if empty(name) || !has_key(printed, name)
1041 call append('$', line)
1043 let printed[name] = 1
1044 if line[0] == 'x' && index(s:update.errors, name) < 0
1045 call add(s:update.errors, name)
1052 call s:update_finish()
1056 while use_job && sync
1065 function! s:log4(name, msg)
1066 call setline(4, printf('- %s (%s)', a:msg, a:name))
1070 function! s:update_finish()
1071 if exists('s:git_terminal_prompt')
1072 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1075 call append(3, '- Updating ...') | 4
1076 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))'))
1077 let [pos, _] = s:logpos(name)
1081 if has_key(spec, 'commit')
1082 call s:log4(name, 'Checking out '.spec.commit)
1083 let out = s:checkout(spec)
1084 elseif has_key(spec, 'tag')
1087 let tags = s:lines(s:system('git tag --list '.s:shellesc(tag).' --sort -version:refname 2>&1', spec.dir))
1088 if !v:shell_error && !empty(tags)
1090 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1094 call s:log4(name, 'Checking out '.tag)
1095 let out = s:system('git checkout -q '.s:esc(tag).' -- 2>&1', spec.dir)
1097 let branch = s:esc(get(spec, 'branch', 'master'))
1098 call s:log4(name, 'Merging origin/'.branch)
1099 let out = s:system('git checkout -q '.branch.' -- 2>&1'
1100 \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir)
1102 if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1103 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1104 call s:log4(name, 'Updating submodules. This may take a while.')
1105 let out .= s:bang('git submodule update --init --recursive 2>&1', spec.dir)
1107 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1109 call add(s:update.errors, name)
1110 call s:regress_bar()
1111 silent execute pos 'd _'
1112 call append(4, msg) | 4
1114 call setline(pos, msg[0])
1120 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")'))
1122 call s:warn('echom', v:exception)
1123 call s:warn('echo', '')
1126 call s:finish(s:update.pull)
1127 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1128 call s:switch_out('normal! gg')
1132 function! s:job_abort()
1133 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1137 for [name, j] in items(s:jobs)
1139 silent! call jobstop(j.jobid)
1141 silent! call job_stop(j.jobid)
1144 call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir))
1150 function! s:last_non_empty_line(lines)
1151 let len = len(a:lines)
1152 for idx in range(len)
1153 let line = a:lines[len-idx-1]
1161 function! s:job_out_cb(self, data) abort
1163 let data = remove(self.lines, -1) . a:data
1164 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1165 call extend(self.lines, lines)
1166 " To reduce the number of buffer updates
1167 let self.tick = get(self, 'tick', -1) + 1
1168 if !self.running || self.tick % len(s:jobs) == 0
1169 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1170 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1171 call s:log(bullet, self.name, result)
1175 function! s:job_exit_cb(self, data) abort
1176 let a:self.running = 0
1177 let a:self.error = a:data != 0
1178 call s:reap(a:self.name)
1182 function! s:job_cb(fn, job, ch, data)
1183 if !s:plug_window_exists() " plug window closed
1184 return s:job_abort()
1186 call call(a:fn, [a:job, a:data])
1189 function! s:nvim_cb(job_id, data, event) dict abort
1190 return a:event == 'stdout' ?
1191 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1192 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1195 function! s:spawn(name, cmd, opts)
1196 let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1197 \ 'batchfile': (s:is_win && (s:nvim || s:vim8)) ? tempname().'.bat' : '',
1198 \ 'new': get(a:opts, 'new', 0) }
1199 let s:jobs[a:name] = job
1200 let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd
1201 if !empty(job.batchfile)
1202 call writefile(["@echo off\r", cmd . "\r"], job.batchfile)
1203 let cmd = job.batchfile
1205 let argv = add(s:is_win ? ['cmd', '/c'] : ['sh', '-c'], cmd)
1209 \ 'on_stdout': function('s:nvim_cb'),
1210 \ 'on_exit': function('s:nvim_cb'),
1212 let jid = jobstart(argv, job)
1218 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1219 \ 'Invalid arguments (or job table is full)']
1222 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1223 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1224 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1227 if job_status(jid) == 'run'
1232 let job.lines = ['Failed to start job']
1235 let job.lines = s:lines(call('s:system', [cmd]))
1236 let job.error = v:shell_error != 0
1241 function! s:reap(name)
1242 let job = s:jobs[a:name]
1244 call add(s:update.errors, a:name)
1245 elseif get(job, 'new', 0)
1246 let s:update.new[a:name] = 1
1248 let s:update.bar .= job.error ? 'x' : '='
1250 let bullet = job.error ? 'x' : '-'
1251 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1252 call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1255 if has_key(job, 'batchfile') && !empty(job.batchfile)
1256 call delete(job.batchfile)
1258 call remove(s:jobs, a:name)
1263 let total = len(s:update.all)
1264 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1265 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1266 call s:progress_bar(2, s:update.bar, total)
1271 function! s:logpos(name)
1272 for i in range(4, line('$'))
1273 if getline(i) =~# '^[-+x*] '.a:name.':'
1274 for j in range(i + 1, line('$'))
1275 if getline(j) !~ '^ '
1285 function! s:log(bullet, name, lines)
1287 let [b, e] = s:logpos(a:name)
1289 silent execute printf('%d,%d d _', b, e)
1290 if b > winheight('.')
1296 " FIXME For some reason, nomodifiable is set after :d in vim8
1298 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1303 function! s:update_vim()
1311 let pull = s:update.pull
1312 let prog = s:progress_opt(s:nvim || s:vim8)
1313 while 1 " Without TCO, Vim stack is bound to explode
1314 if empty(s:update.todo)
1315 if empty(s:jobs) && !s:update.fin
1316 call s:update_finish()
1317 let s:update.fin = 1
1322 let name = keys(s:update.todo)[0]
1323 let spec = remove(s:update.todo, name)
1324 let new = !isdirectory(spec.dir)
1326 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1329 let has_tag = has_key(spec, 'tag')
1331 let [error, _] = s:git_validate(spec, 0)
1334 let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : ''
1335 call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir })
1337 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1340 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1344 \ printf('git clone %s %s %s %s 2>&1',
1345 \ has_tag ? '' : s:clone_opt,
1347 \ s:shellesc(spec.uri),
1348 \ s:shellesc(s:trim(spec.dir))), { 'new': 1 })
1351 if !s:jobs[name].running
1354 if len(s:jobs) >= s:update.threads
1360 function! s:update_python()
1361 let py_exe = has('python') ? 'python' : 'python3'
1362 execute py_exe "<< EOF"
1369 import Queue as queue
1376 import threading as thr
1381 G_NVIM = vim.eval("has('nvim')") == '1'
1382 G_PULL = vim.eval('s:update.pull') == '1'
1383 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1384 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1385 G_CLONE_OPT = vim.eval('s:clone_opt')
1386 G_PROGRESS = vim.eval('s:progress_opt(1)')
1387 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1388 G_STOP = thr.Event()
1389 G_IS_WIN = vim.eval('s:is_win') == '1'
1391 class PlugError(Exception):
1392 def __init__(self, msg):
1394 class CmdTimedOut(PlugError):
1396 class CmdFailed(PlugError):
1398 class InvalidURI(PlugError):
1400 class Action(object):
1401 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1403 class Buffer(object):
1404 def __init__(self, lock, num_plugs, is_pull):
1406 self.event = 'Updating' if is_pull else 'Installing'
1408 self.maxy = int(vim.eval('winheight(".")'))
1409 self.num_plugs = num_plugs
1411 def __where(self, name):
1412 """ Find first line with name in current buffer. Return line num. """
1413 found, lnum = False, 0
1414 matcher = re.compile('^[-+x*] {0}:'.format(name))
1415 for line in vim.current.buffer:
1416 if matcher.search(line) is not None:
1426 curbuf = vim.current.buffer
1427 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1429 num_spaces = self.num_plugs - len(self.bar)
1430 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1433 vim.command('normal! 2G')
1434 vim.command('redraw')
1436 def write(self, action, name, lines):
1437 first, rest = lines[0], lines[1:]
1438 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1439 msg.extend([' ' + line for line in rest])
1442 if action == Action.ERROR:
1444 vim.command("call add(s:update.errors, '{0}')".format(name))
1445 elif action == Action.DONE:
1448 curbuf = vim.current.buffer
1449 lnum = self.__where(name)
1450 if lnum != -1: # Found matching line num
1452 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1456 curbuf.append(msg, lnum)
1462 class Command(object):
1463 CD = 'cd /d' if G_IS_WIN else 'cd'
1465 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1468 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1469 self.timeout = timeout
1470 self.callback = cb if cb else (lambda msg: None)
1471 self.clean = clean if clean else (lambda: None)
1476 """ Returns true only if command still running. """
1477 return self.proc and self.proc.poll() is None
1479 def execute(self, ntries=3):
1480 """ Execute the command with ntries if CmdTimedOut.
1481 Returns the output of the command if no Exception.
1483 attempt, finished, limit = 0, False, self.timeout
1488 result = self.try_command()
1492 if attempt != ntries:
1494 self.timeout += limit
1498 def notify_retry(self):
1499 """ Retry required for command, notify user. """
1500 for count in range(3, 0, -1):
1502 raise KeyboardInterrupt
1503 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1504 count, 's' if count != 1 else '')
1505 self.callback([msg])
1507 self.callback(['Retrying ...'])
1509 def try_command(self):
1510 """ Execute a cmd & poll for callback. Returns list of output.
1511 Raises CmdFailed -> return code for Popen isn't 0
1512 Raises CmdTimedOut -> command exceeded timeout without new output
1517 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1518 preexec_fn = not G_IS_WIN and os.setsid or None
1519 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1520 stderr=subprocess.STDOUT,
1521 stdin=subprocess.PIPE, shell=True,
1522 preexec_fn=preexec_fn)
1523 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1526 thread_not_started = True
1527 while thread_not_started:
1530 thread_not_started = False
1531 except RuntimeError:
1536 raise KeyboardInterrupt
1538 if first_line or random.random() < G_LOG_PROB:
1540 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1542 self.callback([line])
1544 time_diff = time.time() - os.path.getmtime(tfile.name)
1545 if time_diff > self.timeout:
1546 raise CmdTimedOut(['Timeout!'])
1551 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1553 if self.proc.returncode != 0:
1554 raise CmdFailed([''] + result)
1561 def terminate(self):
1562 """ Terminate process and cleanup. """
1565 os.kill(self.proc.pid, signal.SIGINT)
1567 os.killpg(self.proc.pid, signal.SIGTERM)
1570 class Plugin(object):
1571 def __init__(self, name, args, buf_q, lock):
1576 self.tag = args.get('tag', 0)
1580 if os.path.exists(self.args['dir']):
1585 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1586 except PlugError as exc:
1587 self.write(Action.ERROR, self.name, exc.msg)
1588 except KeyboardInterrupt:
1590 self.write(Action.ERROR, self.name, ['Interrupted!'])
1592 # Any exception except those above print stack trace
1593 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1594 self.write(Action.ERROR, self.name, msg.split('\n'))
1598 target = self.args['dir']
1599 if target[-1] == '\\':
1600 target = target[0:-1]
1605 shutil.rmtree(target)
1610 self.write(Action.INSTALL, self.name, ['Installing ...'])
1611 callback = functools.partial(self.write, Action.INSTALL, self.name)
1612 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1613 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1615 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1616 result = com.execute(G_RETRIES)
1617 self.write(Action.DONE, self.name, result[-1:])
1620 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1621 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1622 result = command.execute(G_RETRIES)
1626 actual_uri = self.repo_uri()
1627 expect_uri = self.args['uri']
1628 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1629 ma = regex.match(actual_uri)
1630 mb = regex.match(expect_uri)
1631 if ma is None or mb is None or ma.groups() != mb.groups():
1633 'Invalid URI: {0}'.format(actual_uri),
1634 'Expected {0}'.format(expect_uri),
1635 'PlugClean required.']
1636 raise InvalidURI(msg)
1639 self.write(Action.UPDATE, self.name, ['Updating ...'])
1640 callback = functools.partial(self.write, Action.UPDATE, self.name)
1641 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1642 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1643 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1644 result = com.execute(G_RETRIES)
1645 self.write(Action.DONE, self.name, result[-1:])
1647 self.write(Action.DONE, self.name, ['Already installed'])
1649 def write(self, action, name, msg):
1650 self.buf_q.put((action, name, msg))
1652 class PlugThread(thr.Thread):
1653 def __init__(self, tname, args):
1654 super(PlugThread, self).__init__()
1659 thr.current_thread().name = self.tname
1660 buf_q, work_q, lock = self.args
1663 while not G_STOP.is_set():
1664 name, args = work_q.get_nowait()
1665 plug = Plugin(name, args, buf_q, lock)
1671 class RefreshThread(thr.Thread):
1672 def __init__(self, lock):
1673 super(RefreshThread, self).__init__()
1680 thread_vim_command('noautocmd normal! a')
1684 self.running = False
1687 def thread_vim_command(cmd):
1688 vim.session.threadsafe_call(lambda: vim.command(cmd))
1690 def thread_vim_command(cmd):
1694 return '"' + name.replace('"', '\"') + '"'
1696 def nonblock_read(fname):
1697 """ Read a file with nonblock flag. Return the last line. """
1698 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1699 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1702 line = buf.rstrip('\r\n')
1703 left = max(line.rfind('\r'), line.rfind('\n'))
1711 thr.current_thread().name = 'main'
1712 nthreads = int(vim.eval('s:update.threads'))
1713 plugs = vim.eval('s:update.todo')
1714 mac_gui = vim.eval('s:mac_gui') == '1'
1717 buf = Buffer(lock, len(plugs), G_PULL)
1718 buf_q, work_q = queue.Queue(), queue.Queue()
1719 for work in plugs.items():
1722 start_cnt = thr.active_count()
1723 for num in range(nthreads):
1724 tname = 'PlugT-{0:02}'.format(num)
1725 thread = PlugThread(tname, (buf_q, work_q, lock))
1728 rthread = RefreshThread(lock)
1731 while not buf_q.empty() or thr.active_count() != start_cnt:
1733 action, name, msg = buf_q.get(True, 0.25)
1734 buf.write(action, name, ['OK'] if not msg else msg)
1738 except KeyboardInterrupt:
1749 function! s:update_ruby()
1752 SEP = ["\r", "\n", nil]
1756 char = readchar rescue return
1757 if SEP.include? char.chr
1766 end unless defined?(PlugStream)
1769 %["#{arg.gsub('"', '\"')}"]
1774 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
1775 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
1777 unless `which pgrep 2> /dev/null`.empty?
1779 until children.empty?
1780 children = children.map { |pid|
1781 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
1786 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
1790 def compare_git_uri a, b
1791 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
1792 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
1799 iswin = VIM::evaluate('s:is_win').to_i == 1
1800 pull = VIM::evaluate('s:update.pull').to_i == 1
1801 base = VIM::evaluate('g:plug_home')
1802 all = VIM::evaluate('s:update.todo')
1803 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
1804 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
1805 nthr = VIM::evaluate('s:update.threads').to_i
1806 maxy = VIM::evaluate('winheight(".")').to_i
1807 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
1808 cd = iswin ? 'cd /d' : 'cd'
1809 tot = VIM::evaluate('len(s:update.todo)') || 0
1811 skip = 'Already installed'
1813 take1 = proc { mtx.synchronize { running && all.shift } }
1816 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
1817 $curbuf[2] = '[' + bar.ljust(tot) + ']'
1818 VIM::command('normal! 2G')
1819 VIM::command('redraw')
1821 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
1822 log = proc { |name, result, type|
1824 ing = ![true, false].include?(type)
1825 bar += type ? '=' : 'x' unless ing
1827 when :install then '+' when :update then '*'
1828 when true, nil then '-' else
1829 VIM::command("call add(s:update.errors, '#{name}')")
1833 if type || type.nil?
1834 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
1835 elsif result =~ /^Interrupted|^Timeout/
1836 ["#{b} #{name}: #{result}"]
1838 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
1840 if lnum = where.call(name)
1842 lnum = 4 if ing && lnum > maxy
1844 result.each_with_index do |line, offset|
1845 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
1850 bt = proc { |cmd, name, type, cleanup|
1858 Timeout::timeout(timeout) do
1859 tmp = VIM::evaluate('tempname()')
1860 system("(#{cmd}) > #{tmp}")
1861 data = File.read(tmp).chomp
1862 File.unlink tmp rescue nil
1865 fd = IO.popen(cmd).extend(PlugStream)
1867 log_prob = 1.0 / nthr
1868 while line = Timeout::timeout(timeout) { fd.get_line }
1870 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
1875 [$? == 0, data.chomp]
1876 rescue Timeout::Error, Interrupt => e
1877 if fd && !fd.closed?
1881 cleanup.call if cleanup
1882 if e.is_a?(Timeout::Error) && tried < tries
1883 3.downto(1) do |countdown|
1884 s = countdown > 1 ? 's' : ''
1885 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
1888 log.call name, 'Retrying ...', type
1891 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
1894 main = Thread.current
1896 watcher = Thread.new {
1898 while VIM::evaluate('getchar(1)')
1902 require 'io/console' # >= Ruby 1.9
1903 nil until IO.console.getch == 3.chr
1907 threads.each { |t| t.raise Interrupt } unless vim7
1909 threads.each { |t| t.join rescue nil }
1912 refresh = Thread.new {
1915 break unless running
1916 VIM::command('noautocmd normal! a')
1920 } if VIM::evaluate('s:mac_gui') == 1
1922 clone_opt = VIM::evaluate('s:clone_opt')
1923 progress = VIM::evaluate('s:progress_opt(1)')
1926 threads << Thread.new {
1927 while pair = take1.call
1929 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
1930 exists = File.directory? dir
1933 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
1934 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
1935 current_uri = data.lines.to_a.last
1937 if data =~ /^Interrupted|^Timeout/
1940 [false, [data.chomp, "PlugClean required."].join($/)]
1942 elsif !compare_git_uri(current_uri, uri)
1943 [false, ["Invalid URI: #{current_uri}",
1945 "PlugClean required."].join($/)]
1948 log.call name, 'Updating ...', :update
1949 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
1950 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
1956 d = esc dir.sub(%r{[\\/]+$}, '')
1957 log.call name, 'Installing ...', :install
1958 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
1962 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
1963 log.call name, result, ok
1968 threads.each { |t| t.join rescue nil }
1970 refresh.kill if refresh
1975 function! s:shellesc_cmd(arg)
1976 let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g')
1977 let escaped = substitute(escaped, '%', '%%', 'g')
1978 let escaped = substitute(escaped, '"', '\\^&', 'g')
1979 let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g')
1980 return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"'
1983 function! s:shellesc(arg)
1984 if &shell =~# 'cmd.exe$'
1985 return s:shellesc_cmd(a:arg)
1987 return shellescape(a:arg)
1990 function! s:glob_dir(path)
1991 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
1994 function! s:progress_bar(line, bar, total)
1995 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
1998 function! s:compare_git_uri(a, b)
1999 " See `git help clone'
2000 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2001 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2002 " file:// / junegunn/vim-plug [/]
2003 " / junegunn/vim-plug [/]
2004 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2005 let ma = matchlist(a:a, pat)
2006 let mb = matchlist(a:b, pat)
2007 return ma[1:2] ==# mb[1:2]
2010 function! s:format_message(bullet, name, message)
2012 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2014 let lines = map(s:lines(a:message), '" ".v:val')
2015 return extend([printf('x %s:', a:name)], lines)
2019 function! s:with_cd(cmd, dir)
2020 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', s:shellesc(a:dir), a:cmd)
2023 function! s:system(cmd, ...)
2025 let [sh, shellcmdflag, shrd] = s:chsh(1)
2026 let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd
2028 let batchfile = tempname().'.bat'
2029 call writefile(["@echo off\r", cmd . "\r"], batchfile)
2032 return system(s:is_win ? '('.cmd.')' : cmd)
2034 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2036 call delete(batchfile)
2041 function! s:system_chomp(...)
2042 let ret = call('s:system', a:000)
2043 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2046 function! s:git_validate(spec, check_branch)
2048 if isdirectory(a:spec.dir)
2049 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))
2050 let remote = result[-1]
2052 let err = join([remote, 'PlugClean required.'], "\n")
2053 elseif !s:compare_git_uri(remote, a:spec.uri)
2054 let err = join(['Invalid URI: '.remote,
2055 \ 'Expected: '.a:spec.uri,
2056 \ 'PlugClean required.'], "\n")
2057 elseif a:check_branch && has_key(a:spec, 'commit')
2058 let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir))
2059 let sha = result[-1]
2061 let err = join(add(result, 'PlugClean required.'), "\n")
2062 elseif !s:hash_match(sha, a:spec.commit)
2063 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2064 \ a:spec.commit[:6], sha[:6]),
2065 \ 'PlugUpdate required.'], "\n")
2067 elseif a:check_branch
2068 let branch = result[0]
2070 if has_key(a:spec, 'tag')
2071 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2072 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2073 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2074 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2077 elseif a:spec.branch !=# branch
2078 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2079 \ branch, a:spec.branch)
2082 let [ahead, behind] = split(s:lastline(s:system(printf(
2083 \ 'git rev-list --count --left-right HEAD...origin/%s',
2084 \ a:spec.branch), a:spec.dir)), '\t')
2085 if !v:shell_error && ahead
2087 " Only mention PlugClean if diverged, otherwise it's likely to be
2088 " pushable (and probably not that messed up).
2090 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2091 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind)
2093 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2094 \ .'Cannot update until local changes are pushed.',
2095 \ a:spec.branch, ahead)
2101 let err = 'Not found'
2103 return [err, err =~# 'PlugClean']
2106 function! s:rm_rf(dir)
2107 if isdirectory(a:dir)
2108 call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(a:dir))
2112 function! s:clean(force)
2114 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2117 " List of valid directories
2120 let [cnt, total] = [0, len(g:plugs)]
2121 for [name, spec] in items(g:plugs)
2122 if !s:is_managed(name)
2123 call add(dirs, spec.dir)
2125 let [err, clean] = s:git_validate(spec, 1)
2127 let errs[spec.dir] = s:lines(err)[0]
2129 call add(dirs, spec.dir)
2133 call s:progress_bar(2, repeat('=', cnt), total)
2140 let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1
2141 let allowed[dir] = 1
2142 for child in s:glob_dir(dir)
2143 let allowed[child] = 1
2148 let found = sort(s:glob_dir(g:plug_home))
2150 let f = remove(found, 0)
2151 if !has_key(allowed, f) && isdirectory(f)
2153 call append(line('$'), '- ' . f)
2155 call append(line('$'), ' ' . errs[f])
2157 let found = filter(found, 'stridx(v:val, f) != 0')
2164 call append(line('$'), 'Already clean.')
2166 let s:clean_count = 0
2167 call append(3, ['Directories to delete:', ''])
2169 if a:force || s:ask_no_interrupt('Delete all directories?')
2170 call s:delete([6, line('$')], 1)
2172 call setline(4, 'Cancelled.')
2173 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2174 nmap <silent> <buffer> dd d_
2175 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2176 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2180 setlocal nomodifiable
2183 function! s:delete_op(type, ...)
2184 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2187 function! s:delete(range, force)
2188 let [l1, l2] = a:range
2191 let line = getline(l1)
2192 if line =~ '^- ' && isdirectory(line[2:])
2195 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2196 let force = force || answer > 1
2198 call s:rm_rf(line[2:])
2200 call setline(l1, '~'.line[1:])
2201 let s:clean_count += 1
2202 call setline(4, printf('Removed %d directories.', s:clean_count))
2203 setlocal nomodifiable
2210 function! s:upgrade()
2211 echo 'Downloading the latest version of vim-plug'
2213 let tmp = tempname()
2214 let new = tmp . '/plug.vim'
2217 let out = s:system(printf('git clone --depth 1 %s %s', s:plug_src, tmp))
2219 return s:err('Error upgrading vim-plug: '. out)
2222 if readfile(s:me) ==# readfile(new)
2223 echo 'vim-plug is already up-to-date'
2226 call rename(s:me, s:me . '.old')
2227 call rename(new, s:me)
2229 echo 'vim-plug has been upgraded'
2233 silent! call s:rm_rf(tmp)
2237 function! s:upgrade_specs()
2238 for spec in values(g:plugs)
2239 let spec.frozen = get(spec, 'frozen', 0)
2243 function! s:status()
2245 call append(0, 'Checking plugins')
2250 let [cnt, total] = [0, len(g:plugs)]
2251 for [name, spec] in items(g:plugs)
2252 let is_dir = isdirectory(spec.dir)
2253 if has_key(spec, 'uri')
2255 let [err, _] = s:git_validate(spec, 1)
2256 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2258 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2262 let [valid, msg] = [1, 'OK']
2264 let [valid, msg] = [0, 'Not found.']
2269 " `s:loaded` entry can be missing if PlugUpgraded
2270 if is_dir && get(s:loaded, name, -1) == 0
2272 let msg .= ' (not loaded)'
2274 call s:progress_bar(2, repeat('=', cnt), total)
2275 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2279 call setline(1, 'Finished. '.ecnt.' error(s).')
2281 setlocal nomodifiable
2283 echo "Press 'L' on each line to load plugin, or 'U' to update"
2284 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2285 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2289 function! s:extract_name(str, prefix, suffix)
2290 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2293 function! s:status_load(lnum)
2294 let line = getline(a:lnum)
2295 let name = s:extract_name(line, '-', '(not loaded)')
2297 call plug#load(name)
2299 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2300 setlocal nomodifiable
2304 function! s:status_update() range
2305 let lines = getline(a:firstline, a:lastline)
2306 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2309 execute 'PlugUpdate' join(names)
2313 function! s:is_preview_window_open()
2321 function! s:find_name(lnum)
2322 for lnum in reverse(range(1, a:lnum))
2323 let line = getline(lnum)
2327 let name = s:extract_name(line, '-', '')
2335 function! s:preview_commit()
2336 if b:plug_preview < 0
2337 let b:plug_preview = !s:is_preview_window_open()
2340 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2345 let name = s:find_name(line('.'))
2346 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2350 if exists('g:plug_pwindow') && !s:is_preview_window_open()
2351 execute g:plug_pwindow
2357 setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
2359 let [sh, shellcmdflag, shrd] = s:chsh(1)
2360 let cmd = 'cd '.s:shellesc(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
2362 let batchfile = tempname().'.bat'
2363 call writefile(["@echo off\r", cmd . "\r"], batchfile)
2366 execute 'silent %!' cmd
2368 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2370 call delete(batchfile)
2373 setlocal nomodifiable
2374 nnoremap <silent> <buffer> q :q<cr>
2378 function! s:section(flags)
2379 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2382 function! s:format_git_log(line)
2384 let tokens = split(a:line, nr2char(1))
2386 return indent.substitute(a:line, '\s*$', '', '')
2388 let [graph, sha, refs, subject, date] = tokens
2389 let tag = matchstr(refs, 'tag: [^,)]\+')
2390 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2391 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2394 function! s:append_ul(lnum, text)
2395 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2400 call append(0, ['Collecting changes ...', ''])
2403 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2404 call s:progress_bar(2, bar, len(total))
2405 for origin in [1, 0]
2406 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2410 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2412 let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..'
2413 let diff = s:system_chomp('git log --graph --color=never '.join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 's:shellesc(v:val)')), v.dir)
2415 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2416 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2417 let cnts[origin] += 1
2420 call s:progress_bar(2, bar, len(total))
2425 call append(5, ['', 'N/A'])
2428 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2429 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2431 if cnts[0] || cnts[1]
2432 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2433 if empty(maparg("\<cr>", 'n'))
2434 nmap <buffer> <cr> <plug>(plug-preview)
2436 if empty(maparg('o', 'n'))
2437 nmap <buffer> o <plug>(plug-preview)
2441 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2442 echo "Press 'X' on each block to revert the update"
2445 setlocal nomodifiable
2448 function! s:revert()
2449 if search('^Pending updates', 'bnW')
2453 let name = s:find_name(line('.'))
2454 if empty(name) || !has_key(g:plugs, name) ||
2455 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2459 call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch).' --', g:plugs[name].dir)
2462 setlocal nomodifiable
2466 function! s:snapshot(force, ...) abort
2469 call append(0, ['" Generated by vim-plug',
2470 \ '" '.strftime("%c"),
2471 \ '" :source this file in vim to restore the snapshot',
2472 \ '" or execute: vim -S snapshot.vim',
2473 \ '', '', 'PlugUpdate!'])
2475 let anchor = line('$') - 3
2476 let names = sort(keys(filter(copy(g:plugs),
2477 \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
2478 for name in reverse(names)
2479 let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir)
2481 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2487 let fn = expand(a:1)
2488 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2491 call writefile(getline(1, '$'), fn)
2492 echo 'Saved as '.a:1
2493 silent execute 'e' s:esc(fn)
2498 function! s:split_rtp()
2499 return split(&rtp, '\\\@<!,')
2502 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2503 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2505 if exists('g:plugs')
2506 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2507 call s:upgrade_specs()
2508 call s:define_commands()
2511 let &cpo = s:cpo_save