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)
196 function! s:lazy(plug, opt)
197 return has_key(a:plug, a:opt) &&
198 \ (empty(s:to_a(a:plug[a:opt])) ||
199 \ !isdirectory(a:plug.dir) ||
200 \ len(s:glob(s:rtp(a:plug), 'plugin')) ||
201 \ len(s:glob(s:rtp(a:plug), 'after/plugin')))
205 if !exists('g:plugs')
206 return s:err('Call plug#begin() first')
209 if exists('#PlugLOD')
215 let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
217 if exists('g:did_load_filetypes')
220 for name in g:plugs_order
221 if !has_key(g:plugs, name)
224 let plug = g:plugs[name]
225 if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for')
226 let s:loaded[name] = 1
230 if has_key(plug, 'on')
231 let s:triggers[name] = { 'map': [], 'cmd': [] }
232 for cmd in s:to_a(plug.on)
233 if cmd =~? '^<Plug>.\+'
234 if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
235 call s:assoc(lod.map, cmd, name)
237 call add(s:triggers[name].map, cmd)
238 elseif cmd =~# '^[A-Z]'
239 let cmd = substitute(cmd, '!*$', '', '')
240 if exists(':'.cmd) != 2
241 call s:assoc(lod.cmd, cmd, name)
243 call add(s:triggers[name].cmd, cmd)
245 call s:err('Invalid `on` option: '.cmd.
246 \ '. Should start with an uppercase letter or `<Plug>`.')
251 if has_key(plug, 'for')
252 let types = s:to_a(plug.for)
254 augroup filetypedetect
255 call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
259 call s:assoc(lod.ft, type, name)
264 for [cmd, names] in items(lod.cmd)
266 \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
267 \ cmd, string(cmd), string(names))
270 for [map, names] in items(lod.map)
271 for [mode, map_prefix, key_prefix] in
272 \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
274 \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
275 \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
279 for [ft, names] in items(lod.ft)
281 execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
282 \ ft, string(ft), string(names))
287 filetype plugin indent on
288 if has('vim_starting')
289 if has('syntax') && !exists('g:syntax_on')
293 call s:reload_plugins()
297 function! s:loaded_names()
298 return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
301 function! s:load_plugin(spec)
302 call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
305 function! s:reload_plugins()
306 for name in s:loaded_names()
307 call s:load_plugin(g:plugs[name])
311 function! s:trim(str)
312 return substitute(a:str, '[\/]\+$', '', '')
315 function! s:version_requirement(val, min)
316 for idx in range(0, len(a:min) - 1)
317 let v = get(a:val, idx, 0)
318 if v < a:min[idx] | return 0
319 elseif v > a:min[idx] | return 1
325 function! s:git_version_requirement(...)
326 if !exists('s:git_version')
327 let s:git_version = map(split(split(s:system('git --version'))[2], '\.'), 'str2nr(v:val)')
329 return s:version_requirement(s:git_version, a:000)
332 function! s:progress_opt(base)
333 return a:base && !s:is_win &&
334 \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
338 function! s:rtp(spec)
339 return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
342 function! s:path(path)
343 return s:trim(substitute(a:path, '/', '\', 'g'))
346 function! s:dirpath(path)
347 return s:path(a:path) . '\'
350 function! s:is_local_plug(repo)
351 return a:repo =~? '^[a-z]:\|^[%~]'
354 function! s:rtp(spec)
355 return s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
358 function! s:path(path)
359 return s:trim(a:path)
362 function! s:dirpath(path)
363 return substitute(a:path, '[/\\]*$', '/', '')
366 function! s:is_local_plug(repo)
367 return a:repo[0] =~ '[/$~]'
373 echom '[vim-plug] '.a:msg
377 function! s:warn(cmd, msg)
379 execute a:cmd 'a:msg'
383 function! s:esc(path)
384 return escape(a:path, ' ')
387 function! s:escrtp(path)
388 return escape(a:path, ' ,')
391 function! s:remove_rtp()
392 for name in s:loaded_names()
393 let rtp = s:rtp(g:plugs[name])
394 execute 'set rtp-='.s:escrtp(rtp)
395 let after = globpath(rtp, 'after')
396 if isdirectory(after)
397 execute 'set rtp-='.s:escrtp(after)
402 function! s:reorg_rtp()
403 if !empty(s:first_rtp)
404 execute 'set rtp-='.s:first_rtp
405 execute 'set rtp-='.s:last_rtp
408 " &rtp is modified from outside
409 if exists('s:prtp') && s:prtp !=# &rtp
414 let s:middle = get(s:, 'middle', &rtp)
415 let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
416 let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
417 let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
419 \ . join(map(afters, 'escape(v:val, ",")'), ',')
420 let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
423 if !empty(s:first_rtp)
424 execute 'set rtp^='.s:first_rtp
425 execute 'set rtp+='.s:last_rtp
429 function! s:doautocmd(...)
430 if exists('#'.join(a:000, '#'))
431 execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
435 function! s:dobufread(names)
437 let path = s:rtp(g:plugs[name]).'/**'
438 for dir in ['ftdetect', 'ftplugin']
439 if len(finddir(dir, path))
440 if exists('#BufRead')
449 function! plug#load(...)
451 return s:err('Argument missing: plugin name(s) required')
453 if !exists('g:plugs')
454 return s:err('plug#begin was not called')
456 let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
457 let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
459 let s = len(unknowns) > 1 ? 's' : ''
460 return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
462 let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
465 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
467 call s:dobufread(unloaded)
473 function! s:remove_triggers(name)
474 if !has_key(s:triggers, a:name)
477 for cmd in s:triggers[a:name].cmd
478 execute 'silent! delc' cmd
480 for map in s:triggers[a:name].map
481 execute 'silent! unmap' map
482 execute 'silent! iunmap' map
484 call remove(s:triggers, a:name)
487 function! s:lod(names, types, ...)
489 call s:remove_triggers(name)
490 let s:loaded[name] = 1
495 let rtp = s:rtp(g:plugs[name])
497 call s:source(rtp, dir.'/**/*.vim')
500 if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
501 execute 'runtime' a:1
503 call s:source(rtp, a:2)
505 call s:doautocmd('User', name)
509 function! s:lod_ft(pat, names)
510 let syn = 'syntax/'.a:pat.'.vim'
511 call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
512 execute 'autocmd! PlugLOD FileType' a:pat
513 call s:doautocmd('filetypeplugin', 'FileType')
514 call s:doautocmd('filetypeindent', 'FileType')
517 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
518 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
519 call s:dobufread(a:names)
520 execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
523 function! s:lod_map(map, names, with_prefix, prefix)
524 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
525 call s:dobufread(a:names)
532 let extra .= nr2char(c)
536 let prefix = v:count ? v:count : ''
537 let prefix .= '"'.v:register.a:prefix
540 let prefix = "\<esc>" . prefix
542 let prefix .= v:operator
544 call feedkeys(prefix, 'n')
546 call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
549 function! plug#(repo, ...)
551 return s:err('Invalid number of arguments (1..2)')
555 let repo = s:trim(a:repo)
556 let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
557 let name = get(opts, 'as', fnamemodify(repo, ':t:s?\.git$??'))
558 let spec = extend(s:infer_properties(name, repo), opts)
559 if !has_key(g:plugs, name)
560 call add(g:plugs_order, name)
562 let g:plugs[name] = spec
563 let s:loaded[name] = get(s:loaded, name, 0)
565 return s:err(v:exception)
569 function! s:parse_options(arg)
570 let opts = copy(s:base_spec)
571 let type = type(a:arg)
572 if type == s:TYPE.string
574 elseif type == s:TYPE.dict
575 call extend(opts, a:arg)
576 if has_key(opts, 'dir')
577 let opts.dir = s:dirpath(expand(opts.dir))
580 throw 'Invalid argument type (expected: string or dictionary)'
585 function! s:infer_properties(name, repo)
587 if s:is_local_plug(repo)
588 return { 'dir': s:dirpath(expand(repo)) }
594 throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
596 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
597 let uri = printf(fmt, repo)
599 return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
603 function! s:install(force, names)
604 call s:update_impl(0, a:force, a:names)
607 function! s:update(force, names)
608 call s:update_impl(1, a:force, a:names)
611 function! plug#helptags()
612 if !exists('g:plugs')
613 return s:err('plug#begin was not called')
615 for spec in values(g:plugs)
616 let docd = join([s:rtp(spec), 'doc'], '/')
618 silent! execute 'helptags' s:esc(docd)
626 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
627 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
628 syn match plugNumber /[0-9]\+[0-9.]*/ contained
629 syn match plugBracket /[[\]]/ contained
630 syn match plugX /x/ contained
631 syn match plugDash /^-/
632 syn match plugPlus /^+/
633 syn match plugStar /^*/
634 syn match plugMessage /\(^- \)\@<=.*/
635 syn match plugName /\(^- \)\@<=[^ ]*:/
636 syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
637 syn match plugTag /(tag: [^)]\+)/
638 syn match plugInstall /\(^+ \)\@<=[^:]*/
639 syn match plugUpdate /\(^* \)\@<=[^:]*/
640 syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
641 syn match plugEdge /^ \X\+$/
642 syn match plugEdge /^ \X*/ contained nextgroup=plugSha
643 syn match plugSha /[0-9a-f]\{7,9}/ contained
644 syn match plugRelDate /([^)]*)$/ contained
645 syn match plugNotLoaded /(not loaded)$/
646 syn match plugError /^x.*/
647 syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
648 syn match plugH2 /^.*:\n-\+$/
649 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
650 hi def link plug1 Title
651 hi def link plug2 Repeat
652 hi def link plugH2 Type
653 hi def link plugX Exception
654 hi def link plugBracket Structure
655 hi def link plugNumber Number
657 hi def link plugDash Special
658 hi def link plugPlus Constant
659 hi def link plugStar Boolean
661 hi def link plugMessage Function
662 hi def link plugName Label
663 hi def link plugInstall Function
664 hi def link plugUpdate Type
666 hi def link plugError Error
667 hi def link plugDeleted Ignore
668 hi def link plugRelDate Comment
669 hi def link plugEdge PreProc
670 hi def link plugSha Identifier
671 hi def link plugTag Constant
673 hi def link plugNotLoaded Comment
676 function! s:lpad(str, len)
677 return a:str . repeat(' ', a:len - len(a:str))
680 function! s:lines(msg)
681 return split(a:msg, "[\r\n]")
684 function! s:lastline(msg)
685 return get(s:lines(a:msg), -1, '')
688 function! s:new_window()
689 execute get(g:, 'plug_window', 'vertical topleft new')
692 function! s:plug_window_exists()
693 let buflist = tabpagebuflist(s:plug_tab)
694 return !empty(buflist) && index(buflist, s:plug_buf) >= 0
697 function! s:switch_in()
698 if !s:plug_window_exists()
702 if winbufnr(0) != s:plug_buf
703 let s:pos = [tabpagenr(), winnr(), winsaveview()]
704 execute 'normal!' s:plug_tab.'gt'
705 let winnr = bufwinnr(s:plug_buf)
706 execute winnr.'wincmd w'
707 call add(s:pos, winsaveview())
709 let s:pos = [winsaveview()]
716 function! s:switch_out(...)
717 call winrestview(s:pos[-1])
718 setlocal nomodifiable
724 execute 'normal!' s:pos[0].'gt'
725 execute s:pos[1] 'wincmd w'
726 call winrestview(s:pos[2])
730 function! s:finish_bindings()
731 nnoremap <silent> <buffer> R :call <SID>retry()<cr>
732 nnoremap <silent> <buffer> D :PlugDiff<cr>
733 nnoremap <silent> <buffer> S :PlugStatus<cr>
734 nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
735 xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
736 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
737 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
740 function! s:prepare(...)
742 throw 'Invalid current working directory. Cannot proceed.'
745 for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
747 throw evar.' detected. Cannot proceed.'
753 if b:plug_preview == 1
761 nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>bd<cr>
763 call s:finish_bindings()
765 let b:plug_preview = -1
766 let s:plug_tab = tabpagenr()
767 let s:plug_buf = winbufnr(0)
770 for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
771 execute 'silent! unmap <buffer>' k
773 setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
774 if exists('+colorcolumn')
775 setlocal colorcolumn=
778 if exists('g:syntax_on')
783 function! s:assign_name()
785 let prefix = '[Plugins]'
788 while bufexists(name)
789 let name = printf('%s (%s)', prefix, idx)
792 silent! execute 'f' fnameescape(name)
795 function! s:chsh(swap)
796 let prev = [&shell, &shellcmdflag, &shellredir]
798 set shell=cmd.exe shellcmdflag=/c shellredir=>%s\ 2>&1
800 set shell=sh shellredir=>%s\ 2>&1
805 function! s:bang(cmd, ...)
807 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
808 " FIXME: Escaping is incomplete. We could use shellescape with eval,
809 " but it won't work on Windows.
810 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
812 let batchfile = tempname().'.bat'
813 call writefile(["@echo off\r", cmd . "\r"], batchfile)
816 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
817 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
820 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
822 call delete(batchfile)
825 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
828 function! s:regress_bar()
829 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
830 call s:progress_bar(2, bar, len(bar))
833 function! s:is_updated(dir)
834 return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir))
837 function! s:do(pull, force, todo)
838 for [name, spec] in items(a:todo)
839 if !isdirectory(spec.dir)
842 let installed = has_key(s:update.new, name)
843 let updated = installed ? 0 :
844 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
845 if a:force || installed || updated
846 execute 'cd' s:esc(spec.dir)
847 call append(3, '- Post-update hook for '. name .' ... ')
849 let type = type(spec.do)
850 if type == s:TYPE.string
852 if !get(s:loaded, name, 0)
853 let s:loaded[name] = 1
856 call s:load_plugin(spec)
860 let error = v:exception
862 if !s:plug_window_exists()
864 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
867 let error = s:bang(spec.do)
869 elseif type == s:TYPE.funcref
871 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
872 call spec.do({ 'name': name, 'status': status, 'force': a:force })
874 let error = v:exception
877 let error = 'Invalid hook type'
880 call setline(4, empty(error) ? (getline(4) . 'OK')
881 \ : ('x' . getline(4)[1:] . error))
883 call add(s:update.errors, name)
891 function! s:hash_match(a, b)
892 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
895 function! s:checkout(spec)
896 let sha = a:spec.commit
897 let output = s:system('git rev-parse HEAD', a:spec.dir)
898 if !v:shell_error && !s:hash_match(sha, s:lines(output)[0])
899 let output = s:system(
900 \ 'git fetch --depth 999999 && git checkout '.s:esc(sha).' --', a:spec.dir)
905 function! s:finish(pull)
906 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
908 let s = new_frozen > 1 ? 's' : ''
909 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
911 call append(3, '- Finishing ... ') | 4
915 call setline(4, getline(4) . 'Done!')
918 if !empty(s:update.errors)
919 call add(msgs, "Press 'R' to retry.")
921 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
922 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
923 call add(msgs, "Press 'D' to see the updated changes.")
926 call s:finish_bindings()
930 if empty(s:update.errors)
934 call s:update_impl(s:update.pull, s:update.force,
935 \ extend(copy(s:update.errors), [s:update.threads]))
938 function! s:is_managed(name)
939 return has_key(g:plugs[a:name], 'uri')
942 function! s:names(...)
943 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
946 function! s:check_ruby()
947 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
948 if !exists('g:plug_ruby')
950 return s:warn('echom', 'Warning: Ruby interface is broken')
952 let ruby_version = split(g:plug_ruby, '\.')
954 return s:version_requirement(ruby_version, [1, 8, 7])
957 function! s:update_impl(pull, force, args) abort
958 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
959 let args = filter(copy(a:args), 'v:val != "--sync"')
960 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
961 \ remove(args, -1) : get(g:, 'plug_threads', 16)
963 let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
964 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
965 \ filter(managed, 'index(args, v:key) >= 0')
968 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
971 if !s:is_win && s:git_version_requirement(2, 3)
972 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
973 let $GIT_TERMINAL_PROMPT = 0
974 for plug in values(todo)
975 let plug.uri = substitute(plug.uri,
976 \ '^https://git::@github\.com', 'https://github.com', '')
980 if !isdirectory(g:plug_home)
982 call mkdir(g:plug_home, 'p')
984 return s:err(printf('Invalid plug directory: %s. '.
985 \ 'Try to call plug#begin with a valid directory', g:plug_home))
989 if has('nvim') && !exists('*jobwait') && threads > 1
990 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
993 let use_job = s:nvim || s:vim8
994 let python = (has('python') || has('python3')) && !use_job
995 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()
998 \ 'start': reltime(),
1000 \ 'todo': copy(todo),
1005 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1011 call append(0, ['', ''])
1015 let s:clone_opt = get(g:, 'plug_shallow', 1) ?
1016 \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : ''
1019 let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input'
1022 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1024 " Python version requirement (>= 2.7)
1025 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1027 silent python import platform; print platform.python_version()
1029 let python = s:version_requirement(
1030 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1033 if (python || ruby) && s:update.threads > 1
1040 call s:update_ruby()
1042 call s:update_python()
1045 let lines = getline(4, '$')
1049 let name = s:extract_name(line, '.', '')
1050 if empty(name) || !has_key(printed, name)
1051 call append('$', line)
1053 let printed[name] = 1
1054 if line[0] == 'x' && index(s:update.errors, name) < 0
1055 call add(s:update.errors, name)
1062 call s:update_finish()
1066 while use_job && sync
1075 function! s:log4(name, msg)
1076 call setline(4, printf('- %s (%s)', a:msg, a:name))
1080 function! s:update_finish()
1081 if exists('s:git_terminal_prompt')
1082 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1085 call append(3, '- Updating ...') | 4
1086 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))'))
1087 let [pos, _] = s:logpos(name)
1091 if has_key(spec, 'commit')
1092 call s:log4(name, 'Checking out '.spec.commit)
1093 let out = s:checkout(spec)
1094 elseif has_key(spec, 'tag')
1097 let tags = s:lines(s:system('git tag --list '.s:shellesc(tag).' --sort -version:refname 2>&1', spec.dir))
1098 if !v:shell_error && !empty(tags)
1100 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1104 call s:log4(name, 'Checking out '.tag)
1105 let out = s:system('git checkout -q '.s:esc(tag).' -- 2>&1', spec.dir)
1107 let branch = s:esc(get(spec, 'branch', 'master'))
1108 call s:log4(name, 'Merging origin/'.branch)
1109 let out = s:system('git checkout -q '.branch.' -- 2>&1'
1110 \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir)
1112 if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1113 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1114 call s:log4(name, 'Updating submodules. This may take a while.')
1115 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1117 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1119 call add(s:update.errors, name)
1120 call s:regress_bar()
1121 silent execute pos 'd _'
1122 call append(4, msg) | 4
1124 call setline(pos, msg[0])
1130 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")'))
1132 call s:warn('echom', v:exception)
1133 call s:warn('echo', '')
1136 call s:finish(s:update.pull)
1137 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1138 call s:switch_out('normal! gg')
1142 function! s:job_abort()
1143 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1147 for [name, j] in items(s:jobs)
1149 silent! call jobstop(j.jobid)
1151 silent! call job_stop(j.jobid)
1154 call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir))
1160 function! s:last_non_empty_line(lines)
1161 let len = len(a:lines)
1162 for idx in range(len)
1163 let line = a:lines[len-idx-1]
1171 function! s:job_out_cb(self, data) abort
1173 let data = remove(self.lines, -1) . a:data
1174 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1175 call extend(self.lines, lines)
1176 " To reduce the number of buffer updates
1177 let self.tick = get(self, 'tick', -1) + 1
1178 if !self.running || self.tick % len(s:jobs) == 0
1179 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1180 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1181 call s:log(bullet, self.name, result)
1185 function! s:job_exit_cb(self, data) abort
1186 let a:self.running = 0
1187 let a:self.error = a:data != 0
1188 call s:reap(a:self.name)
1192 function! s:job_cb(fn, job, ch, data)
1193 if !s:plug_window_exists() " plug window closed
1194 return s:job_abort()
1196 call call(a:fn, [a:job, a:data])
1199 function! s:nvim_cb(job_id, data, event) dict abort
1200 return a:event == 'stdout' ?
1201 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1202 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1205 function! s:spawn(name, cmd, opts)
1206 let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1207 \ 'batchfile': (s:is_win && (s:nvim || s:vim8)) ? tempname().'.bat' : '',
1208 \ 'new': get(a:opts, 'new', 0) }
1209 let s:jobs[a:name] = job
1210 let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd
1211 if !empty(job.batchfile)
1212 call writefile(["@echo off\r", cmd . "\r"], job.batchfile)
1213 let cmd = job.batchfile
1215 let argv = add(s:is_win ? ['cmd', '/c'] : ['sh', '-c'], cmd)
1219 \ 'on_stdout': function('s:nvim_cb'),
1220 \ 'on_exit': function('s:nvim_cb'),
1222 let jid = jobstart(argv, job)
1228 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1229 \ 'Invalid arguments (or job table is full)']
1232 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1233 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1234 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1237 if job_status(jid) == 'run'
1242 let job.lines = ['Failed to start job']
1245 let job.lines = s:lines(call('s:system', [cmd]))
1246 let job.error = v:shell_error != 0
1251 function! s:reap(name)
1252 let job = s:jobs[a:name]
1254 call add(s:update.errors, a:name)
1255 elseif get(job, 'new', 0)
1256 let s:update.new[a:name] = 1
1258 let s:update.bar .= job.error ? 'x' : '='
1260 let bullet = job.error ? 'x' : '-'
1261 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1262 call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1265 if has_key(job, 'batchfile') && !empty(job.batchfile)
1266 call delete(job.batchfile)
1268 call remove(s:jobs, a:name)
1273 let total = len(s:update.all)
1274 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1275 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1276 call s:progress_bar(2, s:update.bar, total)
1281 function! s:logpos(name)
1282 for i in range(4, line('$'))
1283 if getline(i) =~# '^[-+x*] '.a:name.':'
1284 for j in range(i + 1, line('$'))
1285 if getline(j) !~ '^ '
1295 function! s:log(bullet, name, lines)
1297 let [b, e] = s:logpos(a:name)
1299 silent execute printf('%d,%d d _', b, e)
1300 if b > winheight('.')
1306 " FIXME For some reason, nomodifiable is set after :d in vim8
1308 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1313 function! s:update_vim()
1321 let pull = s:update.pull
1322 let prog = s:progress_opt(s:nvim || s:vim8)
1323 while 1 " Without TCO, Vim stack is bound to explode
1324 if empty(s:update.todo)
1325 if empty(s:jobs) && !s:update.fin
1326 call s:update_finish()
1327 let s:update.fin = 1
1332 let name = keys(s:update.todo)[0]
1333 let spec = remove(s:update.todo, name)
1334 let new = empty(globpath(spec.dir, '.git', 1))
1336 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1339 let has_tag = has_key(spec, 'tag')
1341 let [error, _] = s:git_validate(spec, 0)
1344 let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : ''
1345 call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir })
1347 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1350 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1354 \ printf('git clone %s %s %s %s 2>&1',
1355 \ has_tag ? '' : s:clone_opt,
1357 \ s:shellesc(spec.uri),
1358 \ s:shellesc(s:trim(spec.dir))), { 'new': 1 })
1361 if !s:jobs[name].running
1364 if len(s:jobs) >= s:update.threads
1370 function! s:update_python()
1371 let py_exe = has('python') ? 'python' : 'python3'
1372 execute py_exe "<< EOF"
1379 import Queue as queue
1386 import threading as thr
1391 G_NVIM = vim.eval("has('nvim')") == '1'
1392 G_PULL = vim.eval('s:update.pull') == '1'
1393 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1394 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1395 G_CLONE_OPT = vim.eval('s:clone_opt')
1396 G_PROGRESS = vim.eval('s:progress_opt(1)')
1397 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1398 G_STOP = thr.Event()
1399 G_IS_WIN = vim.eval('s:is_win') == '1'
1401 class PlugError(Exception):
1402 def __init__(self, msg):
1404 class CmdTimedOut(PlugError):
1406 class CmdFailed(PlugError):
1408 class InvalidURI(PlugError):
1410 class Action(object):
1411 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1413 class Buffer(object):
1414 def __init__(self, lock, num_plugs, is_pull):
1416 self.event = 'Updating' if is_pull else 'Installing'
1418 self.maxy = int(vim.eval('winheight(".")'))
1419 self.num_plugs = num_plugs
1421 def __where(self, name):
1422 """ Find first line with name in current buffer. Return line num. """
1423 found, lnum = False, 0
1424 matcher = re.compile('^[-+x*] {0}:'.format(name))
1425 for line in vim.current.buffer:
1426 if matcher.search(line) is not None:
1436 curbuf = vim.current.buffer
1437 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1439 num_spaces = self.num_plugs - len(self.bar)
1440 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1443 vim.command('normal! 2G')
1444 vim.command('redraw')
1446 def write(self, action, name, lines):
1447 first, rest = lines[0], lines[1:]
1448 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1449 msg.extend([' ' + line for line in rest])
1452 if action == Action.ERROR:
1454 vim.command("call add(s:update.errors, '{0}')".format(name))
1455 elif action == Action.DONE:
1458 curbuf = vim.current.buffer
1459 lnum = self.__where(name)
1460 if lnum != -1: # Found matching line num
1462 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1466 curbuf.append(msg, lnum)
1472 class Command(object):
1473 CD = 'cd /d' if G_IS_WIN else 'cd'
1475 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1478 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1479 self.timeout = timeout
1480 self.callback = cb if cb else (lambda msg: None)
1481 self.clean = clean if clean else (lambda: None)
1486 """ Returns true only if command still running. """
1487 return self.proc and self.proc.poll() is None
1489 def execute(self, ntries=3):
1490 """ Execute the command with ntries if CmdTimedOut.
1491 Returns the output of the command if no Exception.
1493 attempt, finished, limit = 0, False, self.timeout
1498 result = self.try_command()
1502 if attempt != ntries:
1504 self.timeout += limit
1508 def notify_retry(self):
1509 """ Retry required for command, notify user. """
1510 for count in range(3, 0, -1):
1512 raise KeyboardInterrupt
1513 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1514 count, 's' if count != 1 else '')
1515 self.callback([msg])
1517 self.callback(['Retrying ...'])
1519 def try_command(self):
1520 """ Execute a cmd & poll for callback. Returns list of output.
1521 Raises CmdFailed -> return code for Popen isn't 0
1522 Raises CmdTimedOut -> command exceeded timeout without new output
1527 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1528 preexec_fn = not G_IS_WIN and os.setsid or None
1529 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1530 stderr=subprocess.STDOUT,
1531 stdin=subprocess.PIPE, shell=True,
1532 preexec_fn=preexec_fn)
1533 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1536 thread_not_started = True
1537 while thread_not_started:
1540 thread_not_started = False
1541 except RuntimeError:
1546 raise KeyboardInterrupt
1548 if first_line or random.random() < G_LOG_PROB:
1550 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1552 self.callback([line])
1554 time_diff = time.time() - os.path.getmtime(tfile.name)
1555 if time_diff > self.timeout:
1556 raise CmdTimedOut(['Timeout!'])
1561 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1563 if self.proc.returncode != 0:
1564 raise CmdFailed([''] + result)
1571 def terminate(self):
1572 """ Terminate process and cleanup. """
1575 os.kill(self.proc.pid, signal.SIGINT)
1577 os.killpg(self.proc.pid, signal.SIGTERM)
1580 class Plugin(object):
1581 def __init__(self, name, args, buf_q, lock):
1586 self.tag = args.get('tag', 0)
1590 if os.path.exists(self.args['dir']):
1595 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1596 except PlugError as exc:
1597 self.write(Action.ERROR, self.name, exc.msg)
1598 except KeyboardInterrupt:
1600 self.write(Action.ERROR, self.name, ['Interrupted!'])
1602 # Any exception except those above print stack trace
1603 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1604 self.write(Action.ERROR, self.name, msg.split('\n'))
1608 target = self.args['dir']
1609 if target[-1] == '\\':
1610 target = target[0:-1]
1615 shutil.rmtree(target)
1620 self.write(Action.INSTALL, self.name, ['Installing ...'])
1621 callback = functools.partial(self.write, Action.INSTALL, self.name)
1622 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1623 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1625 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1626 result = com.execute(G_RETRIES)
1627 self.write(Action.DONE, self.name, result[-1:])
1630 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1631 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1632 result = command.execute(G_RETRIES)
1636 actual_uri = self.repo_uri()
1637 expect_uri = self.args['uri']
1638 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1639 ma = regex.match(actual_uri)
1640 mb = regex.match(expect_uri)
1641 if ma is None or mb is None or ma.groups() != mb.groups():
1643 'Invalid URI: {0}'.format(actual_uri),
1644 'Expected {0}'.format(expect_uri),
1645 'PlugClean required.']
1646 raise InvalidURI(msg)
1649 self.write(Action.UPDATE, self.name, ['Updating ...'])
1650 callback = functools.partial(self.write, Action.UPDATE, self.name)
1651 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1652 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1653 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1654 result = com.execute(G_RETRIES)
1655 self.write(Action.DONE, self.name, result[-1:])
1657 self.write(Action.DONE, self.name, ['Already installed'])
1659 def write(self, action, name, msg):
1660 self.buf_q.put((action, name, msg))
1662 class PlugThread(thr.Thread):
1663 def __init__(self, tname, args):
1664 super(PlugThread, self).__init__()
1669 thr.current_thread().name = self.tname
1670 buf_q, work_q, lock = self.args
1673 while not G_STOP.is_set():
1674 name, args = work_q.get_nowait()
1675 plug = Plugin(name, args, buf_q, lock)
1681 class RefreshThread(thr.Thread):
1682 def __init__(self, lock):
1683 super(RefreshThread, self).__init__()
1690 thread_vim_command('noautocmd normal! a')
1694 self.running = False
1697 def thread_vim_command(cmd):
1698 vim.session.threadsafe_call(lambda: vim.command(cmd))
1700 def thread_vim_command(cmd):
1704 return '"' + name.replace('"', '\"') + '"'
1706 def nonblock_read(fname):
1707 """ Read a file with nonblock flag. Return the last line. """
1708 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1709 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1712 line = buf.rstrip('\r\n')
1713 left = max(line.rfind('\r'), line.rfind('\n'))
1721 thr.current_thread().name = 'main'
1722 nthreads = int(vim.eval('s:update.threads'))
1723 plugs = vim.eval('s:update.todo')
1724 mac_gui = vim.eval('s:mac_gui') == '1'
1727 buf = Buffer(lock, len(plugs), G_PULL)
1728 buf_q, work_q = queue.Queue(), queue.Queue()
1729 for work in plugs.items():
1732 start_cnt = thr.active_count()
1733 for num in range(nthreads):
1734 tname = 'PlugT-{0:02}'.format(num)
1735 thread = PlugThread(tname, (buf_q, work_q, lock))
1738 rthread = RefreshThread(lock)
1741 while not buf_q.empty() or thr.active_count() != start_cnt:
1743 action, name, msg = buf_q.get(True, 0.25)
1744 buf.write(action, name, ['OK'] if not msg else msg)
1748 except KeyboardInterrupt:
1759 function! s:update_ruby()
1762 SEP = ["\r", "\n", nil]
1766 char = readchar rescue return
1767 if SEP.include? char.chr
1776 end unless defined?(PlugStream)
1779 %["#{arg.gsub('"', '\"')}"]
1784 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
1785 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
1787 unless `which pgrep 2> /dev/null`.empty?
1789 until children.empty?
1790 children = children.map { |pid|
1791 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
1796 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
1800 def compare_git_uri a, b
1801 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
1802 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
1809 iswin = VIM::evaluate('s:is_win').to_i == 1
1810 pull = VIM::evaluate('s:update.pull').to_i == 1
1811 base = VIM::evaluate('g:plug_home')
1812 all = VIM::evaluate('s:update.todo')
1813 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
1814 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
1815 nthr = VIM::evaluate('s:update.threads').to_i
1816 maxy = VIM::evaluate('winheight(".")').to_i
1817 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
1818 cd = iswin ? 'cd /d' : 'cd'
1819 tot = VIM::evaluate('len(s:update.todo)') || 0
1821 skip = 'Already installed'
1823 take1 = proc { mtx.synchronize { running && all.shift } }
1826 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
1827 $curbuf[2] = '[' + bar.ljust(tot) + ']'
1828 VIM::command('normal! 2G')
1829 VIM::command('redraw')
1831 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
1832 log = proc { |name, result, type|
1834 ing = ![true, false].include?(type)
1835 bar += type ? '=' : 'x' unless ing
1837 when :install then '+' when :update then '*'
1838 when true, nil then '-' else
1839 VIM::command("call add(s:update.errors, '#{name}')")
1843 if type || type.nil?
1844 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
1845 elsif result =~ /^Interrupted|^Timeout/
1846 ["#{b} #{name}: #{result}"]
1848 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
1850 if lnum = where.call(name)
1852 lnum = 4 if ing && lnum > maxy
1854 result.each_with_index do |line, offset|
1855 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
1860 bt = proc { |cmd, name, type, cleanup|
1868 Timeout::timeout(timeout) do
1869 tmp = VIM::evaluate('tempname()')
1870 system("(#{cmd}) > #{tmp}")
1871 data = File.read(tmp).chomp
1872 File.unlink tmp rescue nil
1875 fd = IO.popen(cmd).extend(PlugStream)
1877 log_prob = 1.0 / nthr
1878 while line = Timeout::timeout(timeout) { fd.get_line }
1880 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
1885 [$? == 0, data.chomp]
1886 rescue Timeout::Error, Interrupt => e
1887 if fd && !fd.closed?
1891 cleanup.call if cleanup
1892 if e.is_a?(Timeout::Error) && tried < tries
1893 3.downto(1) do |countdown|
1894 s = countdown > 1 ? 's' : ''
1895 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
1898 log.call name, 'Retrying ...', type
1901 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
1904 main = Thread.current
1906 watcher = Thread.new {
1908 while VIM::evaluate('getchar(1)')
1912 require 'io/console' # >= Ruby 1.9
1913 nil until IO.console.getch == 3.chr
1917 threads.each { |t| t.raise Interrupt } unless vim7
1919 threads.each { |t| t.join rescue nil }
1922 refresh = Thread.new {
1925 break unless running
1926 VIM::command('noautocmd normal! a')
1930 } if VIM::evaluate('s:mac_gui') == 1
1932 clone_opt = VIM::evaluate('s:clone_opt')
1933 progress = VIM::evaluate('s:progress_opt(1)')
1936 threads << Thread.new {
1937 while pair = take1.call
1939 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
1940 exists = File.directory? dir
1943 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
1944 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
1945 current_uri = data.lines.to_a.last
1947 if data =~ /^Interrupted|^Timeout/
1950 [false, [data.chomp, "PlugClean required."].join($/)]
1952 elsif !compare_git_uri(current_uri, uri)
1953 [false, ["Invalid URI: #{current_uri}",
1955 "PlugClean required."].join($/)]
1958 log.call name, 'Updating ...', :update
1959 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
1960 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
1966 d = esc dir.sub(%r{[\\/]+$}, '')
1967 log.call name, 'Installing ...', :install
1968 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
1972 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
1973 log.call name, result, ok
1978 threads.each { |t| t.join rescue nil }
1980 refresh.kill if refresh
1985 function! s:shellesc_cmd(arg)
1986 let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g')
1987 let escaped = substitute(escaped, '%', '%%', 'g')
1988 let escaped = substitute(escaped, '"', '\\^&', 'g')
1989 let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g')
1990 return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"'
1993 function! s:shellesc(arg)
1994 if &shell =~# 'cmd.exe$'
1995 return s:shellesc_cmd(a:arg)
1997 return shellescape(a:arg)
2000 function! s:glob_dir(path)
2001 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2004 function! s:progress_bar(line, bar, total)
2005 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2008 function! s:compare_git_uri(a, b)
2009 " See `git help clone'
2010 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2011 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2012 " file:// / junegunn/vim-plug [/]
2013 " / junegunn/vim-plug [/]
2014 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2015 let ma = matchlist(a:a, pat)
2016 let mb = matchlist(a:b, pat)
2017 return ma[1:2] ==# mb[1:2]
2020 function! s:format_message(bullet, name, message)
2022 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2024 let lines = map(s:lines(a:message), '" ".v:val')
2025 return extend([printf('x %s:', a:name)], lines)
2029 function! s:with_cd(cmd, dir)
2030 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', s:shellesc(a:dir), a:cmd)
2033 function! s:system(cmd, ...)
2035 let [sh, shellcmdflag, shrd] = s:chsh(1)
2036 let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd
2038 let batchfile = tempname().'.bat'
2039 call writefile(["@echo off\r", cmd . "\r"], batchfile)
2042 return system(s:is_win ? '('.cmd.')' : cmd)
2044 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2046 call delete(batchfile)
2051 function! s:system_chomp(...)
2052 let ret = call('s:system', a:000)
2053 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2056 function! s:git_validate(spec, check_branch)
2058 if isdirectory(a:spec.dir)
2059 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))
2060 let remote = result[-1]
2062 let err = join([remote, 'PlugClean required.'], "\n")
2063 elseif !s:compare_git_uri(remote, a:spec.uri)
2064 let err = join(['Invalid URI: '.remote,
2065 \ 'Expected: '.a:spec.uri,
2066 \ 'PlugClean required.'], "\n")
2067 elseif a:check_branch && has_key(a:spec, 'commit')
2068 let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir))
2069 let sha = result[-1]
2071 let err = join(add(result, 'PlugClean required.'), "\n")
2072 elseif !s:hash_match(sha, a:spec.commit)
2073 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2074 \ a:spec.commit[:6], sha[:6]),
2075 \ 'PlugUpdate required.'], "\n")
2077 elseif a:check_branch
2078 let branch = result[0]
2080 if has_key(a:spec, 'tag')
2081 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2082 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2083 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2084 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2087 elseif a:spec.branch !=# branch
2088 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2089 \ branch, a:spec.branch)
2092 let [ahead, behind] = split(s:lastline(s:system(printf(
2093 \ 'git rev-list --count --left-right HEAD...origin/%s',
2094 \ a:spec.branch), a:spec.dir)), '\t')
2095 if !v:shell_error && ahead
2097 " Only mention PlugClean if diverged, otherwise it's likely to be
2098 " pushable (and probably not that messed up).
2100 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2101 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind)
2103 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2104 \ .'Cannot update until local changes are pushed.',
2105 \ a:spec.branch, ahead)
2111 let err = 'Not found'
2113 return [err, err =~# 'PlugClean']
2116 function! s:rm_rf(dir)
2117 if isdirectory(a:dir)
2118 call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(a:dir))
2122 function! s:clean(force)
2124 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2127 " List of valid directories
2130 let [cnt, total] = [0, len(g:plugs)]
2131 for [name, spec] in items(g:plugs)
2132 if !s:is_managed(name)
2133 call add(dirs, spec.dir)
2135 let [err, clean] = s:git_validate(spec, 1)
2137 let errs[spec.dir] = s:lines(err)[0]
2139 call add(dirs, spec.dir)
2143 call s:progress_bar(2, repeat('=', cnt), total)
2150 let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1
2151 let allowed[dir] = 1
2152 for child in s:glob_dir(dir)
2153 let allowed[child] = 1
2158 let found = sort(s:glob_dir(g:plug_home))
2160 let f = remove(found, 0)
2161 if !has_key(allowed, f) && isdirectory(f)
2163 call append(line('$'), '- ' . f)
2165 call append(line('$'), ' ' . errs[f])
2167 let found = filter(found, 'stridx(v:val, f) != 0')
2174 call append(line('$'), 'Already clean.')
2176 let s:clean_count = 0
2177 call append(3, ['Directories to delete:', ''])
2179 if a:force || s:ask_no_interrupt('Delete all directories?')
2180 call s:delete([6, line('$')], 1)
2182 call setline(4, 'Cancelled.')
2183 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2184 nmap <silent> <buffer> dd d_
2185 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2186 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2190 setlocal nomodifiable
2193 function! s:delete_op(type, ...)
2194 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2197 function! s:delete(range, force)
2198 let [l1, l2] = a:range
2201 let line = getline(l1)
2202 if line =~ '^- ' && isdirectory(line[2:])
2205 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2206 let force = force || answer > 1
2208 call s:rm_rf(line[2:])
2210 call setline(l1, '~'.line[1:])
2211 let s:clean_count += 1
2212 call setline(4, printf('Removed %d directories.', s:clean_count))
2213 setlocal nomodifiable
2220 function! s:upgrade()
2221 echo 'Downloading the latest version of vim-plug'
2223 let tmp = tempname()
2224 let new = tmp . '/plug.vim'
2227 let out = s:system(printf('git clone --depth 1 %s %s', s:plug_src, tmp))
2229 return s:err('Error upgrading vim-plug: '. out)
2232 if readfile(s:me) ==# readfile(new)
2233 echo 'vim-plug is already up-to-date'
2236 call rename(s:me, s:me . '.old')
2237 call rename(new, s:me)
2239 echo 'vim-plug has been upgraded'
2243 silent! call s:rm_rf(tmp)
2247 function! s:upgrade_specs()
2248 for spec in values(g:plugs)
2249 let spec.frozen = get(spec, 'frozen', 0)
2253 function! s:status()
2255 call append(0, 'Checking plugins')
2260 let [cnt, total] = [0, len(g:plugs)]
2261 for [name, spec] in items(g:plugs)
2262 let is_dir = isdirectory(spec.dir)
2263 if has_key(spec, 'uri')
2265 let [err, _] = s:git_validate(spec, 1)
2266 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2268 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2272 let [valid, msg] = [1, 'OK']
2274 let [valid, msg] = [0, 'Not found.']
2279 " `s:loaded` entry can be missing if PlugUpgraded
2280 if is_dir && get(s:loaded, name, -1) == 0
2282 let msg .= ' (not loaded)'
2284 call s:progress_bar(2, repeat('=', cnt), total)
2285 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2289 call setline(1, 'Finished. '.ecnt.' error(s).')
2291 setlocal nomodifiable
2293 echo "Press 'L' on each line to load plugin, or 'U' to update"
2294 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2295 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2299 function! s:extract_name(str, prefix, suffix)
2300 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2303 function! s:status_load(lnum)
2304 let line = getline(a:lnum)
2305 let name = s:extract_name(line, '-', '(not loaded)')
2307 call plug#load(name)
2309 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2310 setlocal nomodifiable
2314 function! s:status_update() range
2315 let lines = getline(a:firstline, a:lastline)
2316 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2319 execute 'PlugUpdate' join(names)
2323 function! s:is_preview_window_open()
2331 function! s:find_name(lnum)
2332 for lnum in reverse(range(1, a:lnum))
2333 let line = getline(lnum)
2337 let name = s:extract_name(line, '-', '')
2345 function! s:preview_commit()
2346 if b:plug_preview < 0
2347 let b:plug_preview = !s:is_preview_window_open()
2350 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2355 let name = s:find_name(line('.'))
2356 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2360 if exists('g:plug_pwindow') && !s:is_preview_window_open()
2361 execute g:plug_pwindow
2367 setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
2369 let [sh, shellcmdflag, shrd] = s:chsh(1)
2370 let cmd = 'cd '.s:shellesc(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
2372 let batchfile = tempname().'.bat'
2373 call writefile(["@echo off\r", cmd . "\r"], batchfile)
2376 execute 'silent %!' cmd
2378 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2380 call delete(batchfile)
2383 setlocal nomodifiable
2384 nnoremap <silent> <buffer> q :q<cr>
2388 function! s:section(flags)
2389 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2392 function! s:format_git_log(line)
2394 let tokens = split(a:line, nr2char(1))
2396 return indent.substitute(a:line, '\s*$', '', '')
2398 let [graph, sha, refs, subject, date] = tokens
2399 let tag = matchstr(refs, 'tag: [^,)]\+')
2400 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2401 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2404 function! s:append_ul(lnum, text)
2405 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2410 call append(0, ['Collecting changes ...', ''])
2413 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2414 call s:progress_bar(2, bar, len(total))
2415 for origin in [1, 0]
2416 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2420 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2422 let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..'
2423 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)
2425 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2426 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2427 let cnts[origin] += 1
2430 call s:progress_bar(2, bar, len(total))
2435 call append(5, ['', 'N/A'])
2438 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2439 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2441 if cnts[0] || cnts[1]
2442 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2443 if empty(maparg("\<cr>", 'n'))
2444 nmap <buffer> <cr> <plug>(plug-preview)
2446 if empty(maparg('o', 'n'))
2447 nmap <buffer> o <plug>(plug-preview)
2451 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2452 echo "Press 'X' on each block to revert the update"
2455 setlocal nomodifiable
2458 function! s:revert()
2459 if search('^Pending updates', 'bnW')
2463 let name = s:find_name(line('.'))
2464 if empty(name) || !has_key(g:plugs, name) ||
2465 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2469 call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch).' --', g:plugs[name].dir)
2472 setlocal nomodifiable
2476 function! s:snapshot(force, ...) abort
2479 call append(0, ['" Generated by vim-plug',
2480 \ '" '.strftime("%c"),
2481 \ '" :source this file in vim to restore the snapshot',
2482 \ '" or execute: vim -S snapshot.vim',
2483 \ '', '', 'PlugUpdate!'])
2485 let anchor = line('$') - 3
2486 let names = sort(keys(filter(copy(g:plugs),
2487 \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
2488 for name in reverse(names)
2489 let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir)
2491 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2497 let fn = expand(a:1)
2498 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2501 call writefile(getline(1, '$'), fn)
2502 echo 'Saved as '.a:1
2503 silent execute 'e' s:esc(fn)
2508 function! s:split_rtp()
2509 return split(&rtp, '\\\@<!,')
2512 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2513 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2515 if exists('g:plugs')
2516 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2517 call s:upgrade_specs()
2518 call s:define_commands()
2521 let &cpo = s:cpo_save