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]:\|^[%~]'
355 function! s:wrap_cmds(cmds)
356 return map(['@echo off', 'for /f "tokens=4" %%a in (''chcp'') do set origchcp=%%a', 'chcp 65001 > nul'] +
357 \ (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) +
358 \ ['chcp %origchcp% > nul'], 'v:val."\r"')
361 function! s:batchfile(cmd)
362 let batchfile = tempname().'.bat'
363 call writefile(s:wrap_cmds(a:cmd), batchfile)
364 let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 1})
365 if &shell =~# 'powershell\.exe$'
368 return [batchfile, cmd]
371 function! s:rtp(spec)
372 return s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
375 function! s:path(path)
376 return s:trim(a:path)
379 function! s:dirpath(path)
380 return substitute(a:path, '[/\\]*$', '/', '')
383 function! s:is_local_plug(repo)
384 return a:repo[0] =~ '[/$~]'
390 echom '[vim-plug] '.a:msg
394 function! s:warn(cmd, msg)
396 execute a:cmd 'a:msg'
400 function! s:esc(path)
401 return escape(a:path, ' ')
404 function! s:escrtp(path)
405 return escape(a:path, ' ,')
408 function! s:remove_rtp()
409 for name in s:loaded_names()
410 let rtp = s:rtp(g:plugs[name])
411 execute 'set rtp-='.s:escrtp(rtp)
412 let after = globpath(rtp, 'after')
413 if isdirectory(after)
414 execute 'set rtp-='.s:escrtp(after)
419 function! s:reorg_rtp()
420 if !empty(s:first_rtp)
421 execute 'set rtp-='.s:first_rtp
422 execute 'set rtp-='.s:last_rtp
425 " &rtp is modified from outside
426 if exists('s:prtp') && s:prtp !=# &rtp
431 let s:middle = get(s:, 'middle', &rtp)
432 let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
433 let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
434 let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
436 \ . join(map(afters, 'escape(v:val, ",")'), ',')
437 let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
440 if !empty(s:first_rtp)
441 execute 'set rtp^='.s:first_rtp
442 execute 'set rtp+='.s:last_rtp
446 function! s:doautocmd(...)
447 if exists('#'.join(a:000, '#'))
448 execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
452 function! s:dobufread(names)
454 let path = s:rtp(g:plugs[name])
455 for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin']
456 if len(finddir(dir, path))
457 if exists('#BufRead')
466 function! plug#load(...)
468 return s:err('Argument missing: plugin name(s) required')
470 if !exists('g:plugs')
471 return s:err('plug#begin was not called')
473 let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
474 let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
476 let s = len(unknowns) > 1 ? 's' : ''
477 return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
479 let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
482 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
484 call s:dobufread(unloaded)
490 function! s:remove_triggers(name)
491 if !has_key(s:triggers, a:name)
494 for cmd in s:triggers[a:name].cmd
495 execute 'silent! delc' cmd
497 for map in s:triggers[a:name].map
498 execute 'silent! unmap' map
499 execute 'silent! iunmap' map
501 call remove(s:triggers, a:name)
504 function! s:lod(names, types, ...)
506 call s:remove_triggers(name)
507 let s:loaded[name] = 1
512 let rtp = s:rtp(g:plugs[name])
514 call s:source(rtp, dir.'/**/*.vim')
517 if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
518 execute 'runtime' a:1
520 call s:source(rtp, a:2)
522 call s:doautocmd('User', name)
526 function! s:lod_ft(pat, names)
527 let syn = 'syntax/'.a:pat.'.vim'
528 call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
529 execute 'autocmd! PlugLOD FileType' a:pat
530 call s:doautocmd('filetypeplugin', 'FileType')
531 call s:doautocmd('filetypeindent', 'FileType')
534 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
535 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
536 call s:dobufread(a:names)
537 execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
540 function! s:lod_map(map, names, with_prefix, prefix)
541 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
542 call s:dobufread(a:names)
549 let extra .= nr2char(c)
553 let prefix = v:count ? v:count : ''
554 let prefix .= '"'.v:register.a:prefix
557 let prefix = "\<esc>" . prefix
559 let prefix .= v:operator
561 call feedkeys(prefix, 'n')
563 call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
566 function! plug#(repo, ...)
568 return s:err('Invalid number of arguments (1..2)')
572 let repo = s:trim(a:repo)
573 let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
574 let name = get(opts, 'as', fnamemodify(repo, ':t:s?\.git$??'))
575 let spec = extend(s:infer_properties(name, repo), opts)
576 if !has_key(g:plugs, name)
577 call add(g:plugs_order, name)
579 let g:plugs[name] = spec
580 let s:loaded[name] = get(s:loaded, name, 0)
582 return s:err(v:exception)
586 function! s:parse_options(arg)
587 let opts = copy(s:base_spec)
588 let type = type(a:arg)
589 if type == s:TYPE.string
591 elseif type == s:TYPE.dict
592 call extend(opts, a:arg)
593 if has_key(opts, 'dir')
594 let opts.dir = s:dirpath(expand(opts.dir))
597 throw 'Invalid argument type (expected: string or dictionary)'
602 function! s:infer_properties(name, repo)
604 if s:is_local_plug(repo)
605 return { 'dir': s:dirpath(expand(repo)) }
611 throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
613 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
614 let uri = printf(fmt, repo)
616 return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
620 function! s:install(force, names)
621 call s:update_impl(0, a:force, a:names)
624 function! s:update(force, names)
625 call s:update_impl(1, a:force, a:names)
628 function! plug#helptags()
629 if !exists('g:plugs')
630 return s:err('plug#begin was not called')
632 for spec in values(g:plugs)
633 let docd = join([s:rtp(spec), 'doc'], '/')
635 silent! execute 'helptags' s:esc(docd)
643 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
644 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
645 syn match plugNumber /[0-9]\+[0-9.]*/ contained
646 syn match plugBracket /[[\]]/ contained
647 syn match plugX /x/ contained
648 syn match plugDash /^-/
649 syn match plugPlus /^+/
650 syn match plugStar /^*/
651 syn match plugMessage /\(^- \)\@<=.*/
652 syn match plugName /\(^- \)\@<=[^ ]*:/
653 syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
654 syn match plugTag /(tag: [^)]\+)/
655 syn match plugInstall /\(^+ \)\@<=[^:]*/
656 syn match plugUpdate /\(^* \)\@<=[^:]*/
657 syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
658 syn match plugEdge /^ \X\+$/
659 syn match plugEdge /^ \X*/ contained nextgroup=plugSha
660 syn match plugSha /[0-9a-f]\{7,9}/ contained
661 syn match plugRelDate /([^)]*)$/ contained
662 syn match plugNotLoaded /(not loaded)$/
663 syn match plugError /^x.*/
664 syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
665 syn match plugH2 /^.*:\n-\+$/
666 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
667 hi def link plug1 Title
668 hi def link plug2 Repeat
669 hi def link plugH2 Type
670 hi def link plugX Exception
671 hi def link plugBracket Structure
672 hi def link plugNumber Number
674 hi def link plugDash Special
675 hi def link plugPlus Constant
676 hi def link plugStar Boolean
678 hi def link plugMessage Function
679 hi def link plugName Label
680 hi def link plugInstall Function
681 hi def link plugUpdate Type
683 hi def link plugError Error
684 hi def link plugDeleted Ignore
685 hi def link plugRelDate Comment
686 hi def link plugEdge PreProc
687 hi def link plugSha Identifier
688 hi def link plugTag Constant
690 hi def link plugNotLoaded Comment
693 function! s:lpad(str, len)
694 return a:str . repeat(' ', a:len - len(a:str))
697 function! s:lines(msg)
698 return split(a:msg, "[\r\n]")
701 function! s:lastline(msg)
702 return get(s:lines(a:msg), -1, '')
705 function! s:new_window()
706 execute get(g:, 'plug_window', 'vertical topleft new')
709 function! s:plug_window_exists()
710 let buflist = tabpagebuflist(s:plug_tab)
711 return !empty(buflist) && index(buflist, s:plug_buf) >= 0
714 function! s:switch_in()
715 if !s:plug_window_exists()
719 if winbufnr(0) != s:plug_buf
720 let s:pos = [tabpagenr(), winnr(), winsaveview()]
721 execute 'normal!' s:plug_tab.'gt'
722 let winnr = bufwinnr(s:plug_buf)
723 execute winnr.'wincmd w'
724 call add(s:pos, winsaveview())
726 let s:pos = [winsaveview()]
733 function! s:switch_out(...)
734 call winrestview(s:pos[-1])
735 setlocal nomodifiable
741 execute 'normal!' s:pos[0].'gt'
742 execute s:pos[1] 'wincmd w'
743 call winrestview(s:pos[2])
747 function! s:finish_bindings()
748 nnoremap <silent> <buffer> R :call <SID>retry()<cr>
749 nnoremap <silent> <buffer> D :PlugDiff<cr>
750 nnoremap <silent> <buffer> S :PlugStatus<cr>
751 nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
752 xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
753 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
754 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
757 function! s:prepare(...)
759 throw 'Invalid current working directory. Cannot proceed.'
762 for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
764 throw evar.' detected. Cannot proceed.'
770 if b:plug_preview == 1
778 nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>bd<cr>
780 call s:finish_bindings()
782 let b:plug_preview = -1
783 let s:plug_tab = tabpagenr()
784 let s:plug_buf = winbufnr(0)
787 for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
788 execute 'silent! unmap <buffer>' k
790 setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
791 if exists('+colorcolumn')
792 setlocal colorcolumn=
795 if exists('g:syntax_on')
800 function! s:assign_name()
802 let prefix = '[Plugins]'
805 while bufexists(name)
806 let name = printf('%s (%s)', prefix, idx)
809 silent! execute 'f' fnameescape(name)
812 function! s:chsh(swap)
813 let prev = [&shell, &shellcmdflag, &shellredir]
814 if !s:is_win && a:swap
815 set shell=sh shellredir=>%s\ 2>&1
820 function! s:bang(cmd, ...)
822 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
823 " FIXME: Escaping is incomplete. We could use shellescape with eval,
824 " but it won't work on Windows.
825 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
827 let [batchfile, cmd] = s:batchfile(cmd)
829 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
830 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
833 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
835 call delete(batchfile)
838 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
841 function! s:regress_bar()
842 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
843 call s:progress_bar(2, bar, len(bar))
846 function! s:is_updated(dir)
847 return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir))
850 function! s:do(pull, force, todo)
851 for [name, spec] in items(a:todo)
852 if !isdirectory(spec.dir)
855 let installed = has_key(s:update.new, name)
856 let updated = installed ? 0 :
857 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
858 if a:force || installed || updated
859 execute 'cd' s:esc(spec.dir)
860 call append(3, '- Post-update hook for '. name .' ... ')
862 let type = type(spec.do)
863 if type == s:TYPE.string
865 if !get(s:loaded, name, 0)
866 let s:loaded[name] = 1
869 call s:load_plugin(spec)
873 let error = v:exception
875 if !s:plug_window_exists()
877 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
880 let error = s:bang(spec.do)
882 elseif type == s:TYPE.funcref
884 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
885 call spec.do({ 'name': name, 'status': status, 'force': a:force })
887 let error = v:exception
890 let error = 'Invalid hook type'
893 call setline(4, empty(error) ? (getline(4) . 'OK')
894 \ : ('x' . getline(4)[1:] . error))
896 call add(s:update.errors, name)
904 function! s:hash_match(a, b)
905 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
908 function! s:checkout(spec)
909 let sha = a:spec.commit
910 let output = s:system('git rev-parse HEAD', a:spec.dir)
911 if !v:shell_error && !s:hash_match(sha, s:lines(output)[0])
912 let output = s:system(
913 \ 'git fetch --depth 999999 && git checkout '.s:esc(sha).' --', a:spec.dir)
918 function! s:finish(pull)
919 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
921 let s = new_frozen > 1 ? 's' : ''
922 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
924 call append(3, '- Finishing ... ') | 4
928 call setline(4, getline(4) . 'Done!')
931 if !empty(s:update.errors)
932 call add(msgs, "Press 'R' to retry.")
934 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
935 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
936 call add(msgs, "Press 'D' to see the updated changes.")
939 call s:finish_bindings()
943 if empty(s:update.errors)
947 call s:update_impl(s:update.pull, s:update.force,
948 \ extend(copy(s:update.errors), [s:update.threads]))
951 function! s:is_managed(name)
952 return has_key(g:plugs[a:name], 'uri')
955 function! s:names(...)
956 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
959 function! s:check_ruby()
960 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
961 if !exists('g:plug_ruby')
963 return s:warn('echom', 'Warning: Ruby interface is broken')
965 let ruby_version = split(g:plug_ruby, '\.')
967 return s:version_requirement(ruby_version, [1, 8, 7])
970 function! s:update_impl(pull, force, args) abort
971 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
972 let args = filter(copy(a:args), 'v:val != "--sync"')
973 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
974 \ remove(args, -1) : get(g:, 'plug_threads', 16)
976 let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
977 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
978 \ filter(managed, 'index(args, v:key) >= 0')
981 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
984 if !s:is_win && s:git_version_requirement(2, 3)
985 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
986 let $GIT_TERMINAL_PROMPT = 0
987 for plug in values(todo)
988 let plug.uri = substitute(plug.uri,
989 \ '^https://git::@github\.com', 'https://github.com', '')
993 if !isdirectory(g:plug_home)
995 call mkdir(g:plug_home, 'p')
997 return s:err(printf('Invalid plug directory: %s. '.
998 \ 'Try to call plug#begin with a valid directory', g:plug_home))
1002 if has('nvim') && !exists('*jobwait') && threads > 1
1003 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
1006 let use_job = s:nvim || s:vim8
1007 let python = (has('python') || has('python3')) && !use_job
1008 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()
1011 \ 'start': reltime(),
1013 \ 'todo': copy(todo),
1018 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1024 call append(0, ['', ''])
1028 let s:clone_opt = get(g:, 'plug_shallow', 1) ?
1029 \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : ''
1032 let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input'
1035 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1037 " Python version requirement (>= 2.7)
1038 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1040 silent python import platform; print platform.python_version()
1042 let python = s:version_requirement(
1043 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1046 if (python || ruby) && s:update.threads > 1
1053 call s:update_ruby()
1055 call s:update_python()
1058 let lines = getline(4, '$')
1062 let name = s:extract_name(line, '.', '')
1063 if empty(name) || !has_key(printed, name)
1064 call append('$', line)
1066 let printed[name] = 1
1067 if line[0] == 'x' && index(s:update.errors, name) < 0
1068 call add(s:update.errors, name)
1075 call s:update_finish()
1079 while use_job && sync
1088 function! s:log4(name, msg)
1089 call setline(4, printf('- %s (%s)', a:msg, a:name))
1093 function! s:update_finish()
1094 if exists('s:git_terminal_prompt')
1095 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1098 call append(3, '- Updating ...') | 4
1099 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))'))
1100 let [pos, _] = s:logpos(name)
1104 if has_key(spec, 'commit')
1105 call s:log4(name, 'Checking out '.spec.commit)
1106 let out = s:checkout(spec)
1107 elseif has_key(spec, 'tag')
1110 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1111 if !v:shell_error && !empty(tags)
1113 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1117 call s:log4(name, 'Checking out '.tag)
1118 let out = s:system('git checkout -q '.s:esc(tag).' -- 2>&1', spec.dir)
1120 let branch = s:esc(get(spec, 'branch', 'master'))
1121 call s:log4(name, 'Merging origin/'.branch)
1122 let out = s:system('git checkout -q '.branch.' -- 2>&1'
1123 \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir)
1125 if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1126 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1127 call s:log4(name, 'Updating submodules. This may take a while.')
1128 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1130 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1132 call add(s:update.errors, name)
1133 call s:regress_bar()
1134 silent execute pos 'd _'
1135 call append(4, msg) | 4
1137 call setline(pos, msg[0])
1143 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")'))
1145 call s:warn('echom', v:exception)
1146 call s:warn('echo', '')
1149 call s:finish(s:update.pull)
1150 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1151 call s:switch_out('normal! gg')
1155 function! s:job_abort()
1156 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1160 for [name, j] in items(s:jobs)
1162 silent! call jobstop(j.jobid)
1164 silent! call job_stop(j.jobid)
1167 call s:system('rm -rf ' . plug#shellescape(g:plugs[name].dir))
1173 function! s:last_non_empty_line(lines)
1174 let len = len(a:lines)
1175 for idx in range(len)
1176 let line = a:lines[len-idx-1]
1184 function! s:job_out_cb(self, data) abort
1186 let data = remove(self.lines, -1) . a:data
1187 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1188 call extend(self.lines, lines)
1189 " To reduce the number of buffer updates
1190 let self.tick = get(self, 'tick', -1) + 1
1191 if !self.running || self.tick % len(s:jobs) == 0
1192 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1193 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1194 call s:log(bullet, self.name, result)
1198 function! s:job_exit_cb(self, data) abort
1199 let a:self.running = 0
1200 let a:self.error = a:data != 0
1201 call s:reap(a:self.name)
1205 function! s:job_cb(fn, job, ch, data)
1206 if !s:plug_window_exists() " plug window closed
1207 return s:job_abort()
1209 call call(a:fn, [a:job, a:data])
1212 function! s:nvim_cb(job_id, data, event) dict abort
1213 return a:event == 'stdout' ?
1214 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1215 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1218 function! s:spawn(name, cmd, opts)
1219 let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1220 \ 'new': get(a:opts, 'new', 0) }
1221 let s:jobs[a:name] = job
1222 let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir, 0) : a:cmd
1223 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1227 \ 'on_stdout': function('s:nvim_cb'),
1228 \ 'on_exit': function('s:nvim_cb'),
1230 let jid = jobstart(argv, job)
1236 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1237 \ 'Invalid arguments (or job table is full)']
1240 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1241 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1242 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1245 if job_status(jid) == 'run'
1250 let job.lines = ['Failed to start job']
1253 let job.lines = s:lines(call('s:system', [cmd]))
1254 let job.error = v:shell_error != 0
1259 function! s:reap(name)
1260 let job = s:jobs[a:name]
1262 call add(s:update.errors, a:name)
1263 elseif get(job, 'new', 0)
1264 let s:update.new[a:name] = 1
1266 let s:update.bar .= job.error ? 'x' : '='
1268 let bullet = job.error ? 'x' : '-'
1269 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1270 call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1273 call remove(s:jobs, a:name)
1278 let total = len(s:update.all)
1279 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1280 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1281 call s:progress_bar(2, s:update.bar, total)
1286 function! s:logpos(name)
1287 for i in range(4, line('$'))
1288 if getline(i) =~# '^[-+x*] '.a:name.':'
1289 for j in range(i + 1, line('$'))
1290 if getline(j) !~ '^ '
1300 function! s:log(bullet, name, lines)
1302 let [b, e] = s:logpos(a:name)
1304 silent execute printf('%d,%d d _', b, e)
1305 if b > winheight('.')
1311 " FIXME For some reason, nomodifiable is set after :d in vim8
1313 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1318 function! s:update_vim()
1326 let pull = s:update.pull
1327 let prog = s:progress_opt(s:nvim || s:vim8)
1328 while 1 " Without TCO, Vim stack is bound to explode
1329 if empty(s:update.todo)
1330 if empty(s:jobs) && !s:update.fin
1331 call s:update_finish()
1332 let s:update.fin = 1
1337 let name = keys(s:update.todo)[0]
1338 let spec = remove(s:update.todo, name)
1339 let new = empty(globpath(spec.dir, '.git', 1))
1341 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1344 let has_tag = has_key(spec, 'tag')
1346 let [error, _] = s:git_validate(spec, 0)
1349 let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : ''
1350 call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir })
1352 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1355 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1359 \ printf('git clone %s %s %s %s 2>&1',
1360 \ has_tag ? '' : s:clone_opt,
1362 \ plug#shellescape(spec.uri, {'script': 0}),
1363 \ plug#shellescape(s:trim(spec.dir), {'script': 0})), { 'new': 1 })
1366 if !s:jobs[name].running
1369 if len(s:jobs) >= s:update.threads
1375 function! s:update_python()
1376 let py_exe = has('python') ? 'python' : 'python3'
1377 execute py_exe "<< EOF"
1384 import Queue as queue
1391 import threading as thr
1396 G_NVIM = vim.eval("has('nvim')") == '1'
1397 G_PULL = vim.eval('s:update.pull') == '1'
1398 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1399 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1400 G_CLONE_OPT = vim.eval('s:clone_opt')
1401 G_PROGRESS = vim.eval('s:progress_opt(1)')
1402 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1403 G_STOP = thr.Event()
1404 G_IS_WIN = vim.eval('s:is_win') == '1'
1406 class PlugError(Exception):
1407 def __init__(self, msg):
1409 class CmdTimedOut(PlugError):
1411 class CmdFailed(PlugError):
1413 class InvalidURI(PlugError):
1415 class Action(object):
1416 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1418 class Buffer(object):
1419 def __init__(self, lock, num_plugs, is_pull):
1421 self.event = 'Updating' if is_pull else 'Installing'
1423 self.maxy = int(vim.eval('winheight(".")'))
1424 self.num_plugs = num_plugs
1426 def __where(self, name):
1427 """ Find first line with name in current buffer. Return line num. """
1428 found, lnum = False, 0
1429 matcher = re.compile('^[-+x*] {0}:'.format(name))
1430 for line in vim.current.buffer:
1431 if matcher.search(line) is not None:
1441 curbuf = vim.current.buffer
1442 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1444 num_spaces = self.num_plugs - len(self.bar)
1445 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1448 vim.command('normal! 2G')
1449 vim.command('redraw')
1451 def write(self, action, name, lines):
1452 first, rest = lines[0], lines[1:]
1453 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1454 msg.extend([' ' + line for line in rest])
1457 if action == Action.ERROR:
1459 vim.command("call add(s:update.errors, '{0}')".format(name))
1460 elif action == Action.DONE:
1463 curbuf = vim.current.buffer
1464 lnum = self.__where(name)
1465 if lnum != -1: # Found matching line num
1467 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1471 curbuf.append(msg, lnum)
1477 class Command(object):
1478 CD = 'cd /d' if G_IS_WIN else 'cd'
1480 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1483 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1484 self.timeout = timeout
1485 self.callback = cb if cb else (lambda msg: None)
1486 self.clean = clean if clean else (lambda: None)
1491 """ Returns true only if command still running. """
1492 return self.proc and self.proc.poll() is None
1494 def execute(self, ntries=3):
1495 """ Execute the command with ntries if CmdTimedOut.
1496 Returns the output of the command if no Exception.
1498 attempt, finished, limit = 0, False, self.timeout
1503 result = self.try_command()
1507 if attempt != ntries:
1509 self.timeout += limit
1513 def notify_retry(self):
1514 """ Retry required for command, notify user. """
1515 for count in range(3, 0, -1):
1517 raise KeyboardInterrupt
1518 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1519 count, 's' if count != 1 else '')
1520 self.callback([msg])
1522 self.callback(['Retrying ...'])
1524 def try_command(self):
1525 """ Execute a cmd & poll for callback. Returns list of output.
1526 Raises CmdFailed -> return code for Popen isn't 0
1527 Raises CmdTimedOut -> command exceeded timeout without new output
1532 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1533 preexec_fn = not G_IS_WIN and os.setsid or None
1534 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1535 stderr=subprocess.STDOUT,
1536 stdin=subprocess.PIPE, shell=True,
1537 preexec_fn=preexec_fn)
1538 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1541 thread_not_started = True
1542 while thread_not_started:
1545 thread_not_started = False
1546 except RuntimeError:
1551 raise KeyboardInterrupt
1553 if first_line or random.random() < G_LOG_PROB:
1555 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1557 self.callback([line])
1559 time_diff = time.time() - os.path.getmtime(tfile.name)
1560 if time_diff > self.timeout:
1561 raise CmdTimedOut(['Timeout!'])
1566 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1568 if self.proc.returncode != 0:
1569 raise CmdFailed([''] + result)
1576 def terminate(self):
1577 """ Terminate process and cleanup. """
1580 os.kill(self.proc.pid, signal.SIGINT)
1582 os.killpg(self.proc.pid, signal.SIGTERM)
1585 class Plugin(object):
1586 def __init__(self, name, args, buf_q, lock):
1591 self.tag = args.get('tag', 0)
1595 if os.path.exists(self.args['dir']):
1600 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1601 except PlugError as exc:
1602 self.write(Action.ERROR, self.name, exc.msg)
1603 except KeyboardInterrupt:
1605 self.write(Action.ERROR, self.name, ['Interrupted!'])
1607 # Any exception except those above print stack trace
1608 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1609 self.write(Action.ERROR, self.name, msg.split('\n'))
1613 target = self.args['dir']
1614 if target[-1] == '\\':
1615 target = target[0:-1]
1620 shutil.rmtree(target)
1625 self.write(Action.INSTALL, self.name, ['Installing ...'])
1626 callback = functools.partial(self.write, Action.INSTALL, self.name)
1627 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1628 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1630 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1631 result = com.execute(G_RETRIES)
1632 self.write(Action.DONE, self.name, result[-1:])
1635 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1636 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1637 result = command.execute(G_RETRIES)
1641 actual_uri = self.repo_uri()
1642 expect_uri = self.args['uri']
1643 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1644 ma = regex.match(actual_uri)
1645 mb = regex.match(expect_uri)
1646 if ma is None or mb is None or ma.groups() != mb.groups():
1648 'Invalid URI: {0}'.format(actual_uri),
1649 'Expected {0}'.format(expect_uri),
1650 'PlugClean required.']
1651 raise InvalidURI(msg)
1654 self.write(Action.UPDATE, self.name, ['Updating ...'])
1655 callback = functools.partial(self.write, Action.UPDATE, self.name)
1656 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1657 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1658 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1659 result = com.execute(G_RETRIES)
1660 self.write(Action.DONE, self.name, result[-1:])
1662 self.write(Action.DONE, self.name, ['Already installed'])
1664 def write(self, action, name, msg):
1665 self.buf_q.put((action, name, msg))
1667 class PlugThread(thr.Thread):
1668 def __init__(self, tname, args):
1669 super(PlugThread, self).__init__()
1674 thr.current_thread().name = self.tname
1675 buf_q, work_q, lock = self.args
1678 while not G_STOP.is_set():
1679 name, args = work_q.get_nowait()
1680 plug = Plugin(name, args, buf_q, lock)
1686 class RefreshThread(thr.Thread):
1687 def __init__(self, lock):
1688 super(RefreshThread, self).__init__()
1695 thread_vim_command('noautocmd normal! a')
1699 self.running = False
1702 def thread_vim_command(cmd):
1703 vim.session.threadsafe_call(lambda: vim.command(cmd))
1705 def thread_vim_command(cmd):
1709 return '"' + name.replace('"', '\"') + '"'
1711 def nonblock_read(fname):
1712 """ Read a file with nonblock flag. Return the last line. """
1713 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1714 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1717 line = buf.rstrip('\r\n')
1718 left = max(line.rfind('\r'), line.rfind('\n'))
1726 thr.current_thread().name = 'main'
1727 nthreads = int(vim.eval('s:update.threads'))
1728 plugs = vim.eval('s:update.todo')
1729 mac_gui = vim.eval('s:mac_gui') == '1'
1732 buf = Buffer(lock, len(plugs), G_PULL)
1733 buf_q, work_q = queue.Queue(), queue.Queue()
1734 for work in plugs.items():
1737 start_cnt = thr.active_count()
1738 for num in range(nthreads):
1739 tname = 'PlugT-{0:02}'.format(num)
1740 thread = PlugThread(tname, (buf_q, work_q, lock))
1743 rthread = RefreshThread(lock)
1746 while not buf_q.empty() or thr.active_count() != start_cnt:
1748 action, name, msg = buf_q.get(True, 0.25)
1749 buf.write(action, name, ['OK'] if not msg else msg)
1753 except KeyboardInterrupt:
1764 function! s:update_ruby()
1767 SEP = ["\r", "\n", nil]
1771 char = readchar rescue return
1772 if SEP.include? char.chr
1781 end unless defined?(PlugStream)
1784 %["#{arg.gsub('"', '\"')}"]
1789 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
1790 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
1792 unless `which pgrep 2> /dev/null`.empty?
1794 until children.empty?
1795 children = children.map { |pid|
1796 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
1801 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
1805 def compare_git_uri a, b
1806 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
1807 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
1814 iswin = VIM::evaluate('s:is_win').to_i == 1
1815 pull = VIM::evaluate('s:update.pull').to_i == 1
1816 base = VIM::evaluate('g:plug_home')
1817 all = VIM::evaluate('s:update.todo')
1818 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
1819 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
1820 nthr = VIM::evaluate('s:update.threads').to_i
1821 maxy = VIM::evaluate('winheight(".")').to_i
1822 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
1823 cd = iswin ? 'cd /d' : 'cd'
1824 tot = VIM::evaluate('len(s:update.todo)') || 0
1826 skip = 'Already installed'
1828 take1 = proc { mtx.synchronize { running && all.shift } }
1831 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
1832 $curbuf[2] = '[' + bar.ljust(tot) + ']'
1833 VIM::command('normal! 2G')
1834 VIM::command('redraw')
1836 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
1837 log = proc { |name, result, type|
1839 ing = ![true, false].include?(type)
1840 bar += type ? '=' : 'x' unless ing
1842 when :install then '+' when :update then '*'
1843 when true, nil then '-' else
1844 VIM::command("call add(s:update.errors, '#{name}')")
1848 if type || type.nil?
1849 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
1850 elsif result =~ /^Interrupted|^Timeout/
1851 ["#{b} #{name}: #{result}"]
1853 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
1855 if lnum = where.call(name)
1857 lnum = 4 if ing && lnum > maxy
1859 result.each_with_index do |line, offset|
1860 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
1865 bt = proc { |cmd, name, type, cleanup|
1873 Timeout::timeout(timeout) do
1874 tmp = VIM::evaluate('tempname()')
1875 system("(#{cmd}) > #{tmp}")
1876 data = File.read(tmp).chomp
1877 File.unlink tmp rescue nil
1880 fd = IO.popen(cmd).extend(PlugStream)
1882 log_prob = 1.0 / nthr
1883 while line = Timeout::timeout(timeout) { fd.get_line }
1885 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
1890 [$? == 0, data.chomp]
1891 rescue Timeout::Error, Interrupt => e
1892 if fd && !fd.closed?
1896 cleanup.call if cleanup
1897 if e.is_a?(Timeout::Error) && tried < tries
1898 3.downto(1) do |countdown|
1899 s = countdown > 1 ? 's' : ''
1900 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
1903 log.call name, 'Retrying ...', type
1906 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
1909 main = Thread.current
1911 watcher = Thread.new {
1913 while VIM::evaluate('getchar(1)')
1917 require 'io/console' # >= Ruby 1.9
1918 nil until IO.console.getch == 3.chr
1922 threads.each { |t| t.raise Interrupt } unless vim7
1924 threads.each { |t| t.join rescue nil }
1927 refresh = Thread.new {
1930 break unless running
1931 VIM::command('noautocmd normal! a')
1935 } if VIM::evaluate('s:mac_gui') == 1
1937 clone_opt = VIM::evaluate('s:clone_opt')
1938 progress = VIM::evaluate('s:progress_opt(1)')
1941 threads << Thread.new {
1942 while pair = take1.call
1944 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
1945 exists = File.directory? dir
1948 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
1949 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
1950 current_uri = data.lines.to_a.last
1952 if data =~ /^Interrupted|^Timeout/
1955 [false, [data.chomp, "PlugClean required."].join($/)]
1957 elsif !compare_git_uri(current_uri, uri)
1958 [false, ["Invalid URI: #{current_uri}",
1960 "PlugClean required."].join($/)]
1963 log.call name, 'Updating ...', :update
1964 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
1965 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
1971 d = esc dir.sub(%r{[\\/]+$}, '')
1972 log.call name, 'Installing ...', :install
1973 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
1977 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
1978 log.call name, result, ok
1983 threads.each { |t| t.join rescue nil }
1985 refresh.kill if refresh
1990 function! s:shellesc_cmd(arg, script)
1991 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
1992 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
1995 function! s:shellesc_ps1(arg)
1996 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
1999 function! plug#shellescape(arg, ...)
2000 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2001 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2002 let script = get(opts, 'script', 1)
2003 if shell =~# 'cmd\.exe$'
2004 return s:shellesc_cmd(a:arg, script)
2005 elseif shell =~# 'powershell\.exe$' || shell =~# 'pwsh$'
2006 return s:shellesc_ps1(a:arg)
2008 return shellescape(a:arg)
2011 function! s:glob_dir(path)
2012 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2015 function! s:progress_bar(line, bar, total)
2016 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2019 function! s:compare_git_uri(a, b)
2020 " See `git help clone'
2021 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2022 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2023 " file:// / junegunn/vim-plug [/]
2024 " / junegunn/vim-plug [/]
2025 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2026 let ma = matchlist(a:a, pat)
2027 let mb = matchlist(a:b, pat)
2028 return ma[1:2] ==# mb[1:2]
2031 function! s:format_message(bullet, name, message)
2033 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2035 let lines = map(s:lines(a:message), '" ".v:val')
2036 return extend([printf('x %s:', a:name)], lines)
2040 function! s:with_cd(cmd, dir, ...)
2041 let script = a:0 > 0 ? a:1 : 1
2042 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
2045 function! s:system(cmd, ...)
2047 let [sh, shellcmdflag, shrd] = s:chsh(1)
2048 let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd
2050 let [batchfile, cmd] = s:batchfile(cmd)
2054 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2056 call delete(batchfile)
2061 function! s:system_chomp(...)
2062 let ret = call('s:system', a:000)
2063 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2066 function! s:git_validate(spec, check_branch)
2068 if isdirectory(a:spec.dir)
2069 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))
2070 let remote = result[-1]
2072 let err = join([remote, 'PlugClean required.'], "\n")
2073 elseif !s:compare_git_uri(remote, a:spec.uri)
2074 let err = join(['Invalid URI: '.remote,
2075 \ 'Expected: '.a:spec.uri,
2076 \ 'PlugClean required.'], "\n")
2077 elseif a:check_branch && has_key(a:spec, 'commit')
2078 let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir))
2079 let sha = result[-1]
2081 let err = join(add(result, 'PlugClean required.'), "\n")
2082 elseif !s:hash_match(sha, a:spec.commit)
2083 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2084 \ a:spec.commit[:6], sha[:6]),
2085 \ 'PlugUpdate required.'], "\n")
2087 elseif a:check_branch
2088 let branch = result[0]
2090 if has_key(a:spec, 'tag')
2091 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2092 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2093 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2094 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2097 elseif a:spec.branch !=# branch
2098 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2099 \ branch, a:spec.branch)
2102 let [ahead, behind] = split(s:lastline(s:system(printf(
2103 \ 'git rev-list --count --left-right HEAD...origin/%s',
2104 \ a:spec.branch), a:spec.dir)), '\t')
2105 if !v:shell_error && ahead
2107 " Only mention PlugClean if diverged, otherwise it's likely to be
2108 " pushable (and probably not that messed up).
2110 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2111 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind)
2113 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2114 \ .'Cannot update until local changes are pushed.',
2115 \ a:spec.branch, ahead)
2121 let err = 'Not found'
2123 return [err, err =~# 'PlugClean']
2126 function! s:rm_rf(dir)
2127 if isdirectory(a:dir)
2128 call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . plug#shellescape(a:dir))
2132 function! s:clean(force)
2134 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2137 " List of valid directories
2140 let [cnt, total] = [0, len(g:plugs)]
2141 for [name, spec] in items(g:plugs)
2142 if !s:is_managed(name)
2143 call add(dirs, spec.dir)
2145 let [err, clean] = s:git_validate(spec, 1)
2147 let errs[spec.dir] = s:lines(err)[0]
2149 call add(dirs, spec.dir)
2153 call s:progress_bar(2, repeat('=', cnt), total)
2160 let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1
2161 let allowed[dir] = 1
2162 for child in s:glob_dir(dir)
2163 let allowed[child] = 1
2168 let found = sort(s:glob_dir(g:plug_home))
2170 let f = remove(found, 0)
2171 if !has_key(allowed, f) && isdirectory(f)
2173 call append(line('$'), '- ' . f)
2175 call append(line('$'), ' ' . errs[f])
2177 let found = filter(found, 'stridx(v:val, f) != 0')
2184 call append(line('$'), 'Already clean.')
2186 let s:clean_count = 0
2187 call append(3, ['Directories to delete:', ''])
2189 if a:force || s:ask_no_interrupt('Delete all directories?')
2190 call s:delete([6, line('$')], 1)
2192 call setline(4, 'Cancelled.')
2193 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2194 nmap <silent> <buffer> dd d_
2195 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2196 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2200 setlocal nomodifiable
2203 function! s:delete_op(type, ...)
2204 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2207 function! s:delete(range, force)
2208 let [l1, l2] = a:range
2211 let line = getline(l1)
2212 if line =~ '^- ' && isdirectory(line[2:])
2215 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2216 let force = force || answer > 1
2218 call s:rm_rf(line[2:])
2220 call setline(l1, '~'.line[1:])
2221 let s:clean_count += 1
2222 call setline(4, printf('Removed %d directories.', s:clean_count))
2223 setlocal nomodifiable
2230 function! s:upgrade()
2231 echo 'Downloading the latest version of vim-plug'
2233 let tmp = tempname()
2234 let new = tmp . '/plug.vim'
2237 let out = s:system(printf('git clone --depth 1 %s %s', plug#shellescape(s:plug_src), plug#shellescape(tmp)))
2239 return s:err('Error upgrading vim-plug: '. out)
2242 if readfile(s:me) ==# readfile(new)
2243 echo 'vim-plug is already up-to-date'
2246 call rename(s:me, s:me . '.old')
2247 call rename(new, s:me)
2249 echo 'vim-plug has been upgraded'
2253 silent! call s:rm_rf(tmp)
2257 function! s:upgrade_specs()
2258 for spec in values(g:plugs)
2259 let spec.frozen = get(spec, 'frozen', 0)
2263 function! s:status()
2265 call append(0, 'Checking plugins')
2270 let [cnt, total] = [0, len(g:plugs)]
2271 for [name, spec] in items(g:plugs)
2272 let is_dir = isdirectory(spec.dir)
2273 if has_key(spec, 'uri')
2275 let [err, _] = s:git_validate(spec, 1)
2276 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2278 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2282 let [valid, msg] = [1, 'OK']
2284 let [valid, msg] = [0, 'Not found.']
2289 " `s:loaded` entry can be missing if PlugUpgraded
2290 if is_dir && get(s:loaded, name, -1) == 0
2292 let msg .= ' (not loaded)'
2294 call s:progress_bar(2, repeat('=', cnt), total)
2295 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2299 call setline(1, 'Finished. '.ecnt.' error(s).')
2301 setlocal nomodifiable
2303 echo "Press 'L' on each line to load plugin, or 'U' to update"
2304 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2305 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2309 function! s:extract_name(str, prefix, suffix)
2310 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2313 function! s:status_load(lnum)
2314 let line = getline(a:lnum)
2315 let name = s:extract_name(line, '-', '(not loaded)')
2317 call plug#load(name)
2319 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2320 setlocal nomodifiable
2324 function! s:status_update() range
2325 let lines = getline(a:firstline, a:lastline)
2326 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2329 execute 'PlugUpdate' join(names)
2333 function! s:is_preview_window_open()
2341 function! s:find_name(lnum)
2342 for lnum in reverse(range(1, a:lnum))
2343 let line = getline(lnum)
2347 let name = s:extract_name(line, '-', '')
2355 function! s:preview_commit()
2356 if b:plug_preview < 0
2357 let b:plug_preview = !s:is_preview_window_open()
2360 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2365 let name = s:find_name(line('.'))
2366 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2370 if exists('g:plug_pwindow') && !s:is_preview_window_open()
2371 execute g:plug_pwindow
2377 setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
2379 let [sh, shellcmdflag, shrd] = s:chsh(1)
2380 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
2382 let [batchfile, cmd] = s:batchfile(cmd)
2384 execute 'silent %!' cmd
2386 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2388 call delete(batchfile)
2391 setlocal nomodifiable
2392 nnoremap <silent> <buffer> q :q<cr>
2396 function! s:section(flags)
2397 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2400 function! s:format_git_log(line)
2402 let tokens = split(a:line, nr2char(1))
2404 return indent.substitute(a:line, '\s*$', '', '')
2406 let [graph, sha, refs, subject, date] = tokens
2407 let tag = matchstr(refs, 'tag: [^,)]\+')
2408 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2409 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2412 function! s:append_ul(lnum, text)
2413 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2418 call append(0, ['Collecting changes ...', ''])
2421 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2422 call s:progress_bar(2, bar, len(total))
2423 for origin in [1, 0]
2424 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2428 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2430 let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..'
2431 let cmd = 'git log --graph --color=never '.join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 'plug#shellescape(v:val)'))
2432 if has_key(v, 'rtp')
2433 let cmd .= ' -- '.plug#shellescape(v.rtp)
2435 let diff = s:system_chomp(cmd, v.dir)
2437 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2438 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2439 let cnts[origin] += 1
2442 call s:progress_bar(2, bar, len(total))
2447 call append(5, ['', 'N/A'])
2450 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2451 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2453 if cnts[0] || cnts[1]
2454 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2455 if empty(maparg("\<cr>", 'n'))
2456 nmap <buffer> <cr> <plug>(plug-preview)
2458 if empty(maparg('o', 'n'))
2459 nmap <buffer> o <plug>(plug-preview)
2463 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2464 echo "Press 'X' on each block to revert the update"
2467 setlocal nomodifiable
2470 function! s:revert()
2471 if search('^Pending updates', 'bnW')
2475 let name = s:find_name(line('.'))
2476 if empty(name) || !has_key(g:plugs, name) ||
2477 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2481 call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch).' --', g:plugs[name].dir)
2484 setlocal nomodifiable
2488 function! s:snapshot(force, ...) abort
2491 call append(0, ['" Generated by vim-plug',
2492 \ '" '.strftime("%c"),
2493 \ '" :source this file in vim to restore the snapshot',
2494 \ '" or execute: vim -S snapshot.vim',
2495 \ '', '', 'PlugUpdate!'])
2497 let anchor = line('$') - 3
2498 let names = sort(keys(filter(copy(g:plugs),
2499 \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
2500 for name in reverse(names)
2501 let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir)
2503 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2509 let fn = expand(a:1)
2510 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2513 call writefile(getline(1, '$'), fn)
2514 echo 'Saved as '.a:1
2515 silent execute 'e' s:esc(fn)
2520 function! s:split_rtp()
2521 return split(&rtp, '\\\@<!,')
2524 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2525 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2527 if exists('g:plugs')
2528 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2529 call s:upgrade_specs()
2530 call s:define_commands()
2533 let &cpo = s:cpo_save