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') || has('win64')
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
767 if exists('g:syntax_on')
772 function! s:assign_name()
774 let prefix = '[Plugins]'
777 while bufexists(name)
778 let name = printf('%s (%s)', prefix, idx)
781 silent! execute 'f' fnameescape(name)
784 function! s:chsh(swap)
785 let prev = [&shell, &shellcmdflag, &shellredir]
787 set shell=cmd.exe shellcmdflag=/c shellredir=>%s\ 2>&1
789 set shell=sh shellredir=>%s\ 2>&1
794 function! s:bang(cmd, ...)
796 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
797 " FIXME: Escaping is incomplete. We could use shellescape with eval,
798 " but it won't work on Windows.
799 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
801 let batchfile = tempname().'.bat'
802 call writefile(["@echo off\r", cmd . "\r"], batchfile)
805 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
806 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
809 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
811 call delete(batchfile)
814 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
817 function! s:regress_bar()
818 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
819 call s:progress_bar(2, bar, len(bar))
822 function! s:is_updated(dir)
823 return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir))
826 function! s:do(pull, force, todo)
827 for [name, spec] in items(a:todo)
828 if !isdirectory(spec.dir)
831 let installed = has_key(s:update.new, name)
832 let updated = installed ? 0 :
833 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
834 if a:force || installed || updated
835 execute 'cd' s:esc(spec.dir)
836 call append(3, '- Post-update hook for '. name .' ... ')
838 let type = type(spec.do)
839 if type == s:TYPE.string
841 if !get(s:loaded, name, 0)
842 let s:loaded[name] = 1
845 call s:load_plugin(spec)
849 let error = v:exception
851 if !s:plug_window_exists()
853 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
856 let error = s:bang(spec.do)
858 elseif type == s:TYPE.funcref
860 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
861 call spec.do({ 'name': name, 'status': status, 'force': a:force })
863 let error = v:exception
866 let error = 'Invalid hook type'
869 call setline(4, empty(error) ? (getline(4) . 'OK')
870 \ : ('x' . getline(4)[1:] . error))
872 call add(s:update.errors, name)
880 function! s:hash_match(a, b)
881 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
884 function! s:checkout(spec)
885 let sha = a:spec.commit
886 let output = s:system('git rev-parse HEAD', a:spec.dir)
887 if !v:shell_error && !s:hash_match(sha, s:lines(output)[0])
888 let output = s:system(
889 \ 'git fetch --depth 999999 && git checkout '.s:esc(sha).' --', a:spec.dir)
894 function! s:finish(pull)
895 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
897 let s = new_frozen > 1 ? 's' : ''
898 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
900 call append(3, '- Finishing ... ') | 4
904 call setline(4, getline(4) . 'Done!')
907 if !empty(s:update.errors)
908 call add(msgs, "Press 'R' to retry.")
910 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
911 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
912 call add(msgs, "Press 'D' to see the updated changes.")
915 call s:finish_bindings()
919 if empty(s:update.errors)
923 call s:update_impl(s:update.pull, s:update.force,
924 \ extend(copy(s:update.errors), [s:update.threads]))
927 function! s:is_managed(name)
928 return has_key(g:plugs[a:name], 'uri')
931 function! s:names(...)
932 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
935 function! s:check_ruby()
936 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
937 if !exists('g:plug_ruby')
939 return s:warn('echom', 'Warning: Ruby interface is broken')
941 let ruby_version = split(g:plug_ruby, '\.')
943 return s:version_requirement(ruby_version, [1, 8, 7])
946 function! s:update_impl(pull, force, args) abort
947 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
948 let args = filter(copy(a:args), 'v:val != "--sync"')
949 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
950 \ remove(args, -1) : get(g:, 'plug_threads', 16)
952 let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
953 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
954 \ filter(managed, 'index(args, v:key) >= 0')
957 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
960 if !s:is_win && s:git_version_requirement(2, 3)
961 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
962 let $GIT_TERMINAL_PROMPT = 0
963 for plug in values(todo)
964 let plug.uri = substitute(plug.uri,
965 \ '^https://git::@github\.com', 'https://github.com', '')
969 if !isdirectory(g:plug_home)
971 call mkdir(g:plug_home, 'p')
973 return s:err(printf('Invalid plug directory: %s. '.
974 \ 'Try to call plug#begin with a valid directory', g:plug_home))
978 if has('nvim') && !exists('*jobwait') && threads > 1
979 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
982 let use_job = s:nvim || s:vim8
983 let python = (has('python') || has('python3')) && !use_job
984 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()
987 \ 'start': reltime(),
989 \ 'todo': copy(todo),
994 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1000 call append(0, ['', ''])
1004 let s:clone_opt = get(g:, 'plug_shallow', 1) ?
1005 \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : ''
1008 let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input'
1011 " Python version requirement (>= 2.7)
1012 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1014 silent python import platform; print platform.python_version()
1016 let python = s:version_requirement(
1017 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1020 if (python || ruby) && s:update.threads > 1
1027 call s:update_ruby()
1029 call s:update_python()
1032 let lines = getline(4, '$')
1036 let name = s:extract_name(line, '.', '')
1037 if empty(name) || !has_key(printed, name)
1038 call append('$', line)
1040 let printed[name] = 1
1041 if line[0] == 'x' && index(s:update.errors, name) < 0
1042 call add(s:update.errors, name)
1049 call s:update_finish()
1053 while use_job && sync
1062 function! s:log4(name, msg)
1063 call setline(4, printf('- %s (%s)', a:msg, a:name))
1067 function! s:update_finish()
1068 if exists('s:git_terminal_prompt')
1069 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1072 call append(3, '- Updating ...') | 4
1073 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))'))
1074 let [pos, _] = s:logpos(name)
1078 if has_key(spec, 'commit')
1079 call s:log4(name, 'Checking out '.spec.commit)
1080 let out = s:checkout(spec)
1081 elseif has_key(spec, 'tag')
1084 let tags = s:lines(s:system('git tag --list '.s:shellesc(tag).' --sort -version:refname 2>&1', spec.dir))
1085 if !v:shell_error && !empty(tags)
1087 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1091 call s:log4(name, 'Checking out '.tag)
1092 let out = s:system('git checkout -q '.s:esc(tag).' -- 2>&1', spec.dir)
1094 let branch = s:esc(get(spec, 'branch', 'master'))
1095 call s:log4(name, 'Merging origin/'.branch)
1096 let out = s:system('git checkout -q '.branch.' -- 2>&1'
1097 \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir)
1099 if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1100 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1101 call s:log4(name, 'Updating submodules. This may take a while.')
1102 let out .= s:bang('git submodule update --init --recursive 2>&1', spec.dir)
1104 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1106 call add(s:update.errors, name)
1107 call s:regress_bar()
1108 silent execute pos 'd _'
1109 call append(4, msg) | 4
1111 call setline(pos, msg[0])
1117 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")'))
1119 call s:warn('echom', v:exception)
1120 call s:warn('echo', '')
1123 call s:finish(s:update.pull)
1124 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1125 call s:switch_out('normal! gg')
1129 function! s:job_abort()
1130 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1134 for [name, j] in items(s:jobs)
1136 silent! call jobstop(j.jobid)
1138 silent! call job_stop(j.jobid)
1141 call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir))
1147 function! s:last_non_empty_line(lines)
1148 let len = len(a:lines)
1149 for idx in range(len)
1150 let line = a:lines[len-idx-1]
1158 function! s:job_out_cb(self, data) abort
1160 let data = remove(self.lines, -1) . a:data
1161 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1162 call extend(self.lines, lines)
1163 " To reduce the number of buffer updates
1164 let self.tick = get(self, 'tick', -1) + 1
1165 if !self.running || self.tick % len(s:jobs) == 0
1166 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1167 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1168 call s:log(bullet, self.name, result)
1172 function! s:job_exit_cb(self, data) abort
1173 let a:self.running = 0
1174 let a:self.error = a:data != 0
1175 call s:reap(a:self.name)
1179 function! s:job_cb(fn, job, ch, data)
1180 if !s:plug_window_exists() " plug window closed
1181 return s:job_abort()
1183 call call(a:fn, [a:job, a:data])
1186 function! s:nvim_cb(job_id, data, event) dict abort
1187 return a:event == 'stdout' ?
1188 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1189 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1192 function! s:spawn(name, cmd, opts)
1193 let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1194 \ 'batchfile': (s:is_win && (s:nvim || s:vim8)) ? tempname().'.bat' : '',
1195 \ 'new': get(a:opts, 'new', 0) }
1196 let s:jobs[a:name] = job
1197 let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd
1198 if !empty(job.batchfile)
1199 call writefile(["@echo off\r", cmd . "\r"], job.batchfile)
1200 let cmd = job.batchfile
1202 let argv = add(s:is_win ? ['cmd', '/c'] : ['sh', '-c'], cmd)
1206 \ 'on_stdout': function('s:nvim_cb'),
1207 \ 'on_exit': function('s:nvim_cb'),
1209 let jid = jobstart(argv, job)
1215 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1216 \ 'Invalid arguments (or job table is full)']
1219 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1220 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1221 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1224 if job_status(jid) == 'run'
1229 let job.lines = ['Failed to start job']
1232 let job.lines = s:lines(call('s:system', [cmd]))
1233 let job.error = v:shell_error != 0
1238 function! s:reap(name)
1239 let job = s:jobs[a:name]
1241 call add(s:update.errors, a:name)
1242 elseif get(job, 'new', 0)
1243 let s:update.new[a:name] = 1
1245 let s:update.bar .= job.error ? 'x' : '='
1247 let bullet = job.error ? 'x' : '-'
1248 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1249 call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1252 if has_key(job, 'batchfile') && !empty(job.batchfile)
1253 call delete(job.batchfile)
1255 call remove(s:jobs, a:name)
1260 let total = len(s:update.all)
1261 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1262 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1263 call s:progress_bar(2, s:update.bar, total)
1268 function! s:logpos(name)
1269 for i in range(4, line('$'))
1270 if getline(i) =~# '^[-+x*] '.a:name.':'
1271 for j in range(i + 1, line('$'))
1272 if getline(j) !~ '^ '
1282 function! s:log(bullet, name, lines)
1284 let [b, e] = s:logpos(a:name)
1286 silent execute printf('%d,%d d _', b, e)
1287 if b > winheight('.')
1293 " FIXME For some reason, nomodifiable is set after :d in vim8
1295 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1300 function! s:update_vim()
1308 let pull = s:update.pull
1309 let prog = s:progress_opt(s:nvim || s:vim8)
1310 while 1 " Without TCO, Vim stack is bound to explode
1311 if empty(s:update.todo)
1312 if empty(s:jobs) && !s:update.fin
1313 call s:update_finish()
1314 let s:update.fin = 1
1319 let name = keys(s:update.todo)[0]
1320 let spec = remove(s:update.todo, name)
1321 let new = !isdirectory(spec.dir)
1323 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1326 let has_tag = has_key(spec, 'tag')
1328 let [error, _] = s:git_validate(spec, 0)
1331 let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : ''
1332 call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir })
1334 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1337 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1341 \ printf('git clone %s %s %s %s 2>&1',
1342 \ has_tag ? '' : s:clone_opt,
1344 \ s:shellesc(spec.uri),
1345 \ s:shellesc(s:trim(spec.dir))), { 'new': 1 })
1348 if !s:jobs[name].running
1351 if len(s:jobs) >= s:update.threads
1357 function! s:update_python()
1358 let py_exe = has('python') ? 'python' : 'python3'
1359 execute py_exe "<< EOF"
1366 import Queue as queue
1373 import threading as thr
1378 G_NVIM = vim.eval("has('nvim')") == '1'
1379 G_PULL = vim.eval('s:update.pull') == '1'
1380 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1381 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1382 G_CLONE_OPT = vim.eval('s:clone_opt')
1383 G_PROGRESS = vim.eval('s:progress_opt(1)')
1384 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1385 G_STOP = thr.Event()
1386 G_IS_WIN = vim.eval('s:is_win') == '1'
1388 class PlugError(Exception):
1389 def __init__(self, msg):
1391 class CmdTimedOut(PlugError):
1393 class CmdFailed(PlugError):
1395 class InvalidURI(PlugError):
1397 class Action(object):
1398 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1400 class Buffer(object):
1401 def __init__(self, lock, num_plugs, is_pull):
1403 self.event = 'Updating' if is_pull else 'Installing'
1405 self.maxy = int(vim.eval('winheight(".")'))
1406 self.num_plugs = num_plugs
1408 def __where(self, name):
1409 """ Find first line with name in current buffer. Return line num. """
1410 found, lnum = False, 0
1411 matcher = re.compile('^[-+x*] {0}:'.format(name))
1412 for line in vim.current.buffer:
1413 if matcher.search(line) is not None:
1423 curbuf = vim.current.buffer
1424 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1426 num_spaces = self.num_plugs - len(self.bar)
1427 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1430 vim.command('normal! 2G')
1431 vim.command('redraw')
1433 def write(self, action, name, lines):
1434 first, rest = lines[0], lines[1:]
1435 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1436 msg.extend([' ' + line for line in rest])
1439 if action == Action.ERROR:
1441 vim.command("call add(s:update.errors, '{0}')".format(name))
1442 elif action == Action.DONE:
1445 curbuf = vim.current.buffer
1446 lnum = self.__where(name)
1447 if lnum != -1: # Found matching line num
1449 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1453 curbuf.append(msg, lnum)
1459 class Command(object):
1460 CD = 'cd /d' if G_IS_WIN else 'cd'
1462 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1465 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1466 self.timeout = timeout
1467 self.callback = cb if cb else (lambda msg: None)
1468 self.clean = clean if clean else (lambda: None)
1473 """ Returns true only if command still running. """
1474 return self.proc and self.proc.poll() is None
1476 def execute(self, ntries=3):
1477 """ Execute the command with ntries if CmdTimedOut.
1478 Returns the output of the command if no Exception.
1480 attempt, finished, limit = 0, False, self.timeout
1485 result = self.try_command()
1489 if attempt != ntries:
1491 self.timeout += limit
1495 def notify_retry(self):
1496 """ Retry required for command, notify user. """
1497 for count in range(3, 0, -1):
1499 raise KeyboardInterrupt
1500 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1501 count, 's' if count != 1 else '')
1502 self.callback([msg])
1504 self.callback(['Retrying ...'])
1506 def try_command(self):
1507 """ Execute a cmd & poll for callback. Returns list of output.
1508 Raises CmdFailed -> return code for Popen isn't 0
1509 Raises CmdTimedOut -> command exceeded timeout without new output
1514 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1515 preexec_fn = not G_IS_WIN and os.setsid or None
1516 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1517 stderr=subprocess.STDOUT,
1518 stdin=subprocess.PIPE, shell=True,
1519 preexec_fn=preexec_fn)
1520 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1523 thread_not_started = True
1524 while thread_not_started:
1527 thread_not_started = False
1528 except RuntimeError:
1533 raise KeyboardInterrupt
1535 if first_line or random.random() < G_LOG_PROB:
1537 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1539 self.callback([line])
1541 time_diff = time.time() - os.path.getmtime(tfile.name)
1542 if time_diff > self.timeout:
1543 raise CmdTimedOut(['Timeout!'])
1548 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1550 if self.proc.returncode != 0:
1551 raise CmdFailed([''] + result)
1558 def terminate(self):
1559 """ Terminate process and cleanup. """
1562 os.kill(self.proc.pid, signal.SIGINT)
1564 os.killpg(self.proc.pid, signal.SIGTERM)
1567 class Plugin(object):
1568 def __init__(self, name, args, buf_q, lock):
1573 self.tag = args.get('tag', 0)
1577 if os.path.exists(self.args['dir']):
1582 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1583 except PlugError as exc:
1584 self.write(Action.ERROR, self.name, exc.msg)
1585 except KeyboardInterrupt:
1587 self.write(Action.ERROR, self.name, ['Interrupted!'])
1589 # Any exception except those above print stack trace
1590 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1591 self.write(Action.ERROR, self.name, msg.split('\n'))
1595 target = self.args['dir']
1596 if target[-1] == '\\':
1597 target = target[0:-1]
1602 shutil.rmtree(target)
1607 self.write(Action.INSTALL, self.name, ['Installing ...'])
1608 callback = functools.partial(self.write, Action.INSTALL, self.name)
1609 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1610 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1612 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1613 result = com.execute(G_RETRIES)
1614 self.write(Action.DONE, self.name, result[-1:])
1617 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1618 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1619 result = command.execute(G_RETRIES)
1623 actual_uri = self.repo_uri()
1624 expect_uri = self.args['uri']
1625 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1626 ma = regex.match(actual_uri)
1627 mb = regex.match(expect_uri)
1628 if ma is None or mb is None or ma.groups() != mb.groups():
1630 'Invalid URI: {0}'.format(actual_uri),
1631 'Expected {0}'.format(expect_uri),
1632 'PlugClean required.']
1633 raise InvalidURI(msg)
1636 self.write(Action.UPDATE, self.name, ['Updating ...'])
1637 callback = functools.partial(self.write, Action.UPDATE, self.name)
1638 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1639 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1640 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1641 result = com.execute(G_RETRIES)
1642 self.write(Action.DONE, self.name, result[-1:])
1644 self.write(Action.DONE, self.name, ['Already installed'])
1646 def write(self, action, name, msg):
1647 self.buf_q.put((action, name, msg))
1649 class PlugThread(thr.Thread):
1650 def __init__(self, tname, args):
1651 super(PlugThread, self).__init__()
1656 thr.current_thread().name = self.tname
1657 buf_q, work_q, lock = self.args
1660 while not G_STOP.is_set():
1661 name, args = work_q.get_nowait()
1662 plug = Plugin(name, args, buf_q, lock)
1668 class RefreshThread(thr.Thread):
1669 def __init__(self, lock):
1670 super(RefreshThread, self).__init__()
1677 thread_vim_command('noautocmd normal! a')
1681 self.running = False
1684 def thread_vim_command(cmd):
1685 vim.session.threadsafe_call(lambda: vim.command(cmd))
1687 def thread_vim_command(cmd):
1691 return '"' + name.replace('"', '\"') + '"'
1693 def nonblock_read(fname):
1694 """ Read a file with nonblock flag. Return the last line. """
1695 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1696 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1699 line = buf.rstrip('\r\n')
1700 left = max(line.rfind('\r'), line.rfind('\n'))
1708 thr.current_thread().name = 'main'
1709 nthreads = int(vim.eval('s:update.threads'))
1710 plugs = vim.eval('s:update.todo')
1711 mac_gui = vim.eval('s:mac_gui') == '1'
1714 buf = Buffer(lock, len(plugs), G_PULL)
1715 buf_q, work_q = queue.Queue(), queue.Queue()
1716 for work in plugs.items():
1719 start_cnt = thr.active_count()
1720 for num in range(nthreads):
1721 tname = 'PlugT-{0:02}'.format(num)
1722 thread = PlugThread(tname, (buf_q, work_q, lock))
1725 rthread = RefreshThread(lock)
1728 while not buf_q.empty() or thr.active_count() != start_cnt:
1730 action, name, msg = buf_q.get(True, 0.25)
1731 buf.write(action, name, ['OK'] if not msg else msg)
1735 except KeyboardInterrupt:
1746 function! s:update_ruby()
1749 SEP = ["\r", "\n", nil]
1753 char = readchar rescue return
1754 if SEP.include? char.chr
1763 end unless defined?(PlugStream)
1766 %["#{arg.gsub('"', '\"')}"]
1771 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
1772 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
1774 unless `which pgrep 2> /dev/null`.empty?
1776 until children.empty?
1777 children = children.map { |pid|
1778 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
1783 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
1787 def compare_git_uri a, b
1788 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
1789 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
1796 iswin = VIM::evaluate('s:is_win').to_i == 1
1797 pull = VIM::evaluate('s:update.pull').to_i == 1
1798 base = VIM::evaluate('g:plug_home')
1799 all = VIM::evaluate('s:update.todo')
1800 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
1801 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
1802 nthr = VIM::evaluate('s:update.threads').to_i
1803 maxy = VIM::evaluate('winheight(".")').to_i
1804 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
1805 cd = iswin ? 'cd /d' : 'cd'
1806 tot = VIM::evaluate('len(s:update.todo)') || 0
1808 skip = 'Already installed'
1810 take1 = proc { mtx.synchronize { running && all.shift } }
1813 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
1814 $curbuf[2] = '[' + bar.ljust(tot) + ']'
1815 VIM::command('normal! 2G')
1816 VIM::command('redraw')
1818 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
1819 log = proc { |name, result, type|
1821 ing = ![true, false].include?(type)
1822 bar += type ? '=' : 'x' unless ing
1824 when :install then '+' when :update then '*'
1825 when true, nil then '-' else
1826 VIM::command("call add(s:update.errors, '#{name}')")
1830 if type || type.nil?
1831 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
1832 elsif result =~ /^Interrupted|^Timeout/
1833 ["#{b} #{name}: #{result}"]
1835 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
1837 if lnum = where.call(name)
1839 lnum = 4 if ing && lnum > maxy
1841 result.each_with_index do |line, offset|
1842 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
1847 bt = proc { |cmd, name, type, cleanup|
1855 Timeout::timeout(timeout) do
1856 tmp = VIM::evaluate('tempname()')
1857 system("(#{cmd}) > #{tmp}")
1858 data = File.read(tmp).chomp
1859 File.unlink tmp rescue nil
1862 fd = IO.popen(cmd).extend(PlugStream)
1864 log_prob = 1.0 / nthr
1865 while line = Timeout::timeout(timeout) { fd.get_line }
1867 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
1872 [$? == 0, data.chomp]
1873 rescue Timeout::Error, Interrupt => e
1874 if fd && !fd.closed?
1878 cleanup.call if cleanup
1879 if e.is_a?(Timeout::Error) && tried < tries
1880 3.downto(1) do |countdown|
1881 s = countdown > 1 ? 's' : ''
1882 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
1885 log.call name, 'Retrying ...', type
1888 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
1891 main = Thread.current
1893 watcher = Thread.new {
1895 while VIM::evaluate('getchar(1)')
1899 require 'io/console' # >= Ruby 1.9
1900 nil until IO.console.getch == 3.chr
1904 threads.each { |t| t.raise Interrupt } unless vim7
1906 threads.each { |t| t.join rescue nil }
1909 refresh = Thread.new {
1912 break unless running
1913 VIM::command('noautocmd normal! a')
1917 } if VIM::evaluate('s:mac_gui') == 1
1919 clone_opt = VIM::evaluate('s:clone_opt')
1920 progress = VIM::evaluate('s:progress_opt(1)')
1923 threads << Thread.new {
1924 while pair = take1.call
1926 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
1927 exists = File.directory? dir
1930 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
1931 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
1932 current_uri = data.lines.to_a.last
1934 if data =~ /^Interrupted|^Timeout/
1937 [false, [data.chomp, "PlugClean required."].join($/)]
1939 elsif !compare_git_uri(current_uri, uri)
1940 [false, ["Invalid URI: #{current_uri}",
1942 "PlugClean required."].join($/)]
1945 log.call name, 'Updating ...', :update
1946 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
1947 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
1953 d = esc dir.sub(%r{[\\/]+$}, '')
1954 log.call name, 'Installing ...', :install
1955 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
1959 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
1960 log.call name, result, ok
1965 threads.each { |t| t.join rescue nil }
1967 refresh.kill if refresh
1972 function! s:shellesc_cmd(arg)
1973 let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g')
1974 let escaped = substitute(escaped, '%', '%%', 'g')
1975 let escaped = substitute(escaped, '"', '\\^&', 'g')
1976 let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g')
1977 return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"'
1980 function! s:shellesc(arg)
1981 if &shell =~# 'cmd.exe$'
1982 return s:shellesc_cmd(a:arg)
1984 return shellescape(a:arg)
1987 function! s:glob_dir(path)
1988 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
1991 function! s:progress_bar(line, bar, total)
1992 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
1995 function! s:compare_git_uri(a, b)
1996 " See `git help clone'
1997 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
1998 " [git@] github.com[:port] : junegunn/vim-plug [.git]
1999 " file:// / junegunn/vim-plug [/]
2000 " / junegunn/vim-plug [/]
2001 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2002 let ma = matchlist(a:a, pat)
2003 let mb = matchlist(a:b, pat)
2004 return ma[1:2] ==# mb[1:2]
2007 function! s:format_message(bullet, name, message)
2009 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2011 let lines = map(s:lines(a:message), '" ".v:val')
2012 return extend([printf('x %s:', a:name)], lines)
2016 function! s:with_cd(cmd, dir)
2017 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', s:shellesc(a:dir), a:cmd)
2020 function! s:system(cmd, ...)
2022 let [sh, shellcmdflag, shrd] = s:chsh(1)
2023 let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd
2025 let batchfile = tempname().'.bat'
2026 call writefile(["@echo off\r", cmd . "\r"], batchfile)
2029 return system(s:is_win ? '('.cmd.')' : cmd)
2031 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2033 call delete(batchfile)
2038 function! s:system_chomp(...)
2039 let ret = call('s:system', a:000)
2040 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2043 function! s:git_validate(spec, check_branch)
2045 if isdirectory(a:spec.dir)
2046 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))
2047 let remote = result[-1]
2049 let err = join([remote, 'PlugClean required.'], "\n")
2050 elseif !s:compare_git_uri(remote, a:spec.uri)
2051 let err = join(['Invalid URI: '.remote,
2052 \ 'Expected: '.a:spec.uri,
2053 \ 'PlugClean required.'], "\n")
2054 elseif a:check_branch && has_key(a:spec, 'commit')
2055 let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir))
2056 let sha = result[-1]
2058 let err = join(add(result, 'PlugClean required.'), "\n")
2059 elseif !s:hash_match(sha, a:spec.commit)
2060 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2061 \ a:spec.commit[:6], sha[:6]),
2062 \ 'PlugUpdate required.'], "\n")
2064 elseif a:check_branch
2065 let branch = result[0]
2067 if has_key(a:spec, 'tag')
2068 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2069 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2070 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2071 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2074 elseif a:spec.branch !=# branch
2075 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2076 \ branch, a:spec.branch)
2079 let [ahead, behind] = split(s:lastline(s:system(printf(
2080 \ 'git rev-list --count --left-right HEAD...origin/%s',
2081 \ a:spec.branch), a:spec.dir)), '\t')
2082 if !v:shell_error && ahead
2084 " Only mention PlugClean if diverged, otherwise it's likely to be
2085 " pushable (and probably not that messed up).
2087 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2088 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind)
2090 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2091 \ .'Cannot update until local changes are pushed.',
2092 \ a:spec.branch, ahead)
2098 let err = 'Not found'
2100 return [err, err =~# 'PlugClean']
2103 function! s:rm_rf(dir)
2104 if isdirectory(a:dir)
2105 call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(a:dir))
2109 function! s:clean(force)
2111 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2114 " List of valid directories
2117 let [cnt, total] = [0, len(g:plugs)]
2118 for [name, spec] in items(g:plugs)
2119 if !s:is_managed(name)
2120 call add(dirs, spec.dir)
2122 let [err, clean] = s:git_validate(spec, 1)
2124 let errs[spec.dir] = s:lines(err)[0]
2126 call add(dirs, spec.dir)
2130 call s:progress_bar(2, repeat('=', cnt), total)
2137 let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1
2138 let allowed[dir] = 1
2139 for child in s:glob_dir(dir)
2140 let allowed[child] = 1
2145 let found = sort(s:glob_dir(g:plug_home))
2147 let f = remove(found, 0)
2148 if !has_key(allowed, f) && isdirectory(f)
2150 call append(line('$'), '- ' . f)
2152 call append(line('$'), ' ' . errs[f])
2154 let found = filter(found, 'stridx(v:val, f) != 0')
2161 call append(line('$'), 'Already clean.')
2163 let s:clean_count = 0
2164 call append(3, ['Directories to delete:', ''])
2166 if a:force || s:ask_no_interrupt('Delete all directories?')
2167 call s:delete([6, line('$')], 1)
2169 call setline(4, 'Cancelled.')
2170 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2171 nmap <silent> <buffer> dd d_
2172 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2173 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2177 setlocal nomodifiable
2180 function! s:delete_op(type, ...)
2181 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2184 function! s:delete(range, force)
2185 let [l1, l2] = a:range
2188 let line = getline(l1)
2189 if line =~ '^- ' && isdirectory(line[2:])
2192 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2193 let force = force || answer > 1
2195 call s:rm_rf(line[2:])
2197 call setline(l1, '~'.line[1:])
2198 let s:clean_count += 1
2199 call setline(4, printf('Removed %d directories.', s:clean_count))
2200 setlocal nomodifiable
2207 function! s:upgrade()
2208 echo 'Downloading the latest version of vim-plug'
2210 let tmp = tempname()
2211 let new = tmp . '/plug.vim'
2214 let out = s:system(printf('git clone --depth 1 %s %s', s:plug_src, tmp))
2216 return s:err('Error upgrading vim-plug: '. out)
2219 if readfile(s:me) ==# readfile(new)
2220 echo 'vim-plug is already up-to-date'
2223 call rename(s:me, s:me . '.old')
2224 call rename(new, s:me)
2226 echo 'vim-plug has been upgraded'
2230 silent! call s:rm_rf(tmp)
2234 function! s:upgrade_specs()
2235 for spec in values(g:plugs)
2236 let spec.frozen = get(spec, 'frozen', 0)
2240 function! s:status()
2242 call append(0, 'Checking plugins')
2247 let [cnt, total] = [0, len(g:plugs)]
2248 for [name, spec] in items(g:plugs)
2249 let is_dir = isdirectory(spec.dir)
2250 if has_key(spec, 'uri')
2252 let [err, _] = s:git_validate(spec, 1)
2253 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2255 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2259 let [valid, msg] = [1, 'OK']
2261 let [valid, msg] = [0, 'Not found.']
2266 " `s:loaded` entry can be missing if PlugUpgraded
2267 if is_dir && get(s:loaded, name, -1) == 0
2269 let msg .= ' (not loaded)'
2271 call s:progress_bar(2, repeat('=', cnt), total)
2272 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2276 call setline(1, 'Finished. '.ecnt.' error(s).')
2278 setlocal nomodifiable
2280 echo "Press 'L' on each line to load plugin, or 'U' to update"
2281 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2282 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2286 function! s:extract_name(str, prefix, suffix)
2287 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2290 function! s:status_load(lnum)
2291 let line = getline(a:lnum)
2292 let name = s:extract_name(line, '-', '(not loaded)')
2294 call plug#load(name)
2296 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2297 setlocal nomodifiable
2301 function! s:status_update() range
2302 let lines = getline(a:firstline, a:lastline)
2303 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2306 execute 'PlugUpdate' join(names)
2310 function! s:is_preview_window_open()
2318 function! s:find_name(lnum)
2319 for lnum in reverse(range(1, a:lnum))
2320 let line = getline(lnum)
2324 let name = s:extract_name(line, '-', '')
2332 function! s:preview_commit()
2333 if b:plug_preview < 0
2334 let b:plug_preview = !s:is_preview_window_open()
2337 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2342 let name = s:find_name(line('.'))
2343 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2347 if exists('g:plug_pwindow') && !s:is_preview_window_open()
2348 execute g:plug_pwindow
2354 setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
2356 let [sh, shellcmdflag, shrd] = s:chsh(1)
2357 let cmd = 'cd '.s:shellesc(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
2359 let batchfile = tempname().'.bat'
2360 call writefile(["@echo off\r", cmd . "\r"], batchfile)
2363 execute 'silent %!' cmd
2365 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2367 call delete(batchfile)
2370 setlocal nomodifiable
2371 nnoremap <silent> <buffer> q :q<cr>
2375 function! s:section(flags)
2376 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2379 function! s:format_git_log(line)
2381 let tokens = split(a:line, nr2char(1))
2383 return indent.substitute(a:line, '\s*$', '', '')
2385 let [graph, sha, refs, subject, date] = tokens
2386 let tag = matchstr(refs, 'tag: [^,)]\+')
2387 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2388 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2391 function! s:append_ul(lnum, text)
2392 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2397 call append(0, ['Collecting changes ...', ''])
2400 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2401 call s:progress_bar(2, bar, len(total))
2402 for origin in [1, 0]
2403 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2407 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2409 let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..'
2410 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)
2412 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2413 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2414 let cnts[origin] += 1
2417 call s:progress_bar(2, bar, len(total))
2422 call append(5, ['', 'N/A'])
2425 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2426 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2428 if cnts[0] || cnts[1]
2429 nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
2430 nnoremap <silent> <buffer> o :silent! call <SID>preview_commit()<cr>
2433 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2434 echo "Press 'X' on each block to revert the update"
2437 setlocal nomodifiable
2440 function! s:revert()
2441 if search('^Pending updates', 'bnW')
2445 let name = s:find_name(line('.'))
2446 if empty(name) || !has_key(g:plugs, name) ||
2447 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2451 call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch).' --', g:plugs[name].dir)
2454 setlocal nomodifiable
2458 function! s:snapshot(force, ...) abort
2461 call append(0, ['" Generated by vim-plug',
2462 \ '" '.strftime("%c"),
2463 \ '" :source this file in vim to restore the snapshot',
2464 \ '" or execute: vim -S snapshot.vim',
2465 \ '', '', 'PlugUpdate!'])
2467 let anchor = line('$') - 3
2468 let names = sort(keys(filter(copy(g:plugs),
2469 \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
2470 for name in reverse(names)
2471 let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir)
2473 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2479 let fn = expand(a:1)
2480 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2483 call writefile(getline(1, '$'), fn)
2484 echo 'Saved as '.a:1
2485 silent execute 'e' s:esc(fn)
2490 function! s:split_rtp()
2491 return split(&rtp, '\\\@<!,')
2494 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2495 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2497 if exists('g:plugs')
2498 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2499 call s:upgrade_specs()
2500 call s:define_commands()
2503 let &cpo = s:cpo_save