comparison .shared/.vim/autoload/plug.vim @ 265:df0b24d4fabd

Think I'm ready to check this all in now; shared dotfiles that can be "sent" through an SSH connection to be used and cleaned up when finished.
author Steve Huston <huston@srhuston.net>
date Wed, 24 Apr 2024 16:15:40 -0400
parents .vim/autoload/plug.vim@6af02446233f
children ce9014cb4240
comparison
equal deleted inserted replaced
264:bb69763716a7 265:df0b24d4fabd
1 " vim-plug: Vim plugin manager
2 " ============================
3 "
4 " Download plug.vim and put it in ~/.vim/autoload
5 "
6 " curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
7 " https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
8 "
9 " Edit your .vimrc
10 "
11 " call plug#begin('~/.vim/plugged')
12 "
13 " " Make sure you use single quotes
14 "
15 " " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align
16 " Plug 'junegunn/vim-easy-align'
17 "
18 " " Any valid git URL is allowed
19 " Plug 'https://github.com/junegunn/vim-github-dashboard.git'
20 "
21 " " Multiple Plug commands can be written in a single line using | separators
22 " Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets'
23 "
24 " " On-demand loading
25 " Plug 'preservim/nerdtree', { 'on': 'NERDTreeToggle' }
26 " Plug 'tpope/vim-fireplace', { 'for': 'clojure' }
27 "
28 " " Using a non-default branch
29 " Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' }
30 "
31 " " Using a tagged release; wildcard allowed (requires git 1.9.2 or above)
32 " Plug 'fatih/vim-go', { 'tag': '*' }
33 "
34 " " Plugin options
35 " Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' }
36 "
37 " " Plugin outside ~/.vim/plugged with post-update hook
38 " Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
39 "
40 " " Unmanaged plugin (manually installed and updated)
41 " Plug '~/my-prototype-plugin'
42 "
43 " " Initialize plugin system
44 " call plug#end()
45 "
46 " Then reload .vimrc and :PlugInstall to install plugins.
47 "
48 " Plug options:
49 "
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 |
60 "
61 " More information: https://github.com/junegunn/vim-plug
62 "
63 "
64 " Copyright (c) 2017 Junegunn Choi
65 "
66 " MIT License
67 "
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:
75 "
76 " The above copyright notice and this permission notice shall be
77 " included in all copies or substantial portions of the Software.
78 "
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.
86
87 if exists('g:loaded_plug')
88 finish
89 endif
90 let g:loaded_plug = 1
91
92 let s:cpo_save = &cpo
93 set cpo&vim
94
95 let s:plug_src = 'https://github.com/junegunn/vim-plug.git'
96 let s:plug_tab = get(s:, 'plug_tab', -1)
97 let s:plug_buf = get(s:, 'plug_buf', -1)
98 let s:mac_gui = has('gui_macvim') && has('gui_running')
99 let s:is_win = has('win32')
100 let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win)
101 let s:vim8 = has('patch-8.0.0039') && exists('*job_start')
102 if s:is_win && &shellslash
103 set noshellslash
104 let s:me = resolve(expand('<sfile>:p'))
105 set shellslash
106 else
107 let s:me = resolve(expand('<sfile>:p'))
108 endif
109 let s:base_spec = { 'branch': '', 'frozen': 0 }
110 let s:TYPE = {
111 \ 'string': type(''),
112 \ 'list': type([]),
113 \ 'dict': type({}),
114 \ 'funcref': type(function('call'))
115 \ }
116 let s:loaded = get(s:, 'loaded', {})
117 let s:triggers = get(s:, 'triggers', {})
118
119 function! s:is_powershell(shell)
120 return a:shell =~# 'powershell\(\.exe\)\?$' || a:shell =~# 'pwsh\(\.exe\)\?$'
121 endfunction
122
123 function! s:isabsolute(dir) abort
124 return a:dir =~# '^/' || (has('win32') && a:dir =~? '^\%(\\\|[A-Z]:\)')
125 endfunction
126
127 function! s:git_dir(dir) abort
128 let gitdir = s:trim(a:dir) . '/.git'
129 if isdirectory(gitdir)
130 return gitdir
131 endif
132 if !filereadable(gitdir)
133 return ''
134 endif
135 let gitdir = matchstr(get(readfile(gitdir), 0, ''), '^gitdir: \zs.*')
136 if len(gitdir) && !s:isabsolute(gitdir)
137 let gitdir = a:dir . '/' . gitdir
138 endif
139 return isdirectory(gitdir) ? gitdir : ''
140 endfunction
141
142 function! s:git_origin_url(dir) abort
143 let gitdir = s:git_dir(a:dir)
144 let config = gitdir . '/config'
145 if empty(gitdir) || !filereadable(config)
146 return ''
147 endif
148 return matchstr(join(readfile(config)), '\[remote "origin"\].\{-}url\s*=\s*\zs\S*\ze')
149 endfunction
150
151 function! s:git_revision(dir) abort
152 let gitdir = s:git_dir(a:dir)
153 let head = gitdir . '/HEAD'
154 if empty(gitdir) || !filereadable(head)
155 return ''
156 endif
157
158 let line = get(readfile(head), 0, '')
159 let ref = matchstr(line, '^ref: \zs.*')
160 if empty(ref)
161 return line
162 endif
163
164 if filereadable(gitdir . '/' . ref)
165 return get(readfile(gitdir . '/' . ref), 0, '')
166 endif
167
168 if filereadable(gitdir . '/packed-refs')
169 for line in readfile(gitdir . '/packed-refs')
170 if line =~# ' ' . ref
171 return matchstr(line, '^[0-9a-f]*')
172 endif
173 endfor
174 endif
175
176 return ''
177 endfunction
178
179 function! s:git_local_branch(dir) abort
180 let gitdir = s:git_dir(a:dir)
181 let head = gitdir . '/HEAD'
182 if empty(gitdir) || !filereadable(head)
183 return ''
184 endif
185 let branch = matchstr(get(readfile(head), 0, ''), '^ref: refs/heads/\zs.*')
186 return len(branch) ? branch : 'HEAD'
187 endfunction
188
189 function! s:git_origin_branch(spec)
190 if len(a:spec.branch)
191 return a:spec.branch
192 endif
193
194 " The file may not be present if this is a local repository
195 let gitdir = s:git_dir(a:spec.dir)
196 let origin_head = gitdir.'/refs/remotes/origin/HEAD'
197 if len(gitdir) && filereadable(origin_head)
198 return matchstr(get(readfile(origin_head), 0, ''),
199 \ '^ref: refs/remotes/origin/\zs.*')
200 endif
201
202 " The command may not return the name of a branch in detached HEAD state
203 let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir))
204 return v:shell_error ? '' : result[-1]
205 endfunction
206
207 if s:is_win
208 function! s:plug_call(fn, ...)
209 let shellslash = &shellslash
210 try
211 set noshellslash
212 return call(a:fn, a:000)
213 finally
214 let &shellslash = shellslash
215 endtry
216 endfunction
217 else
218 function! s:plug_call(fn, ...)
219 return call(a:fn, a:000)
220 endfunction
221 endif
222
223 function! s:plug_getcwd()
224 return s:plug_call('getcwd')
225 endfunction
226
227 function! s:plug_fnamemodify(fname, mods)
228 return s:plug_call('fnamemodify', a:fname, a:mods)
229 endfunction
230
231 function! s:plug_expand(fmt)
232 return s:plug_call('expand', a:fmt, 1)
233 endfunction
234
235 function! s:plug_tempname()
236 return s:plug_call('tempname')
237 endfunction
238
239 function! plug#begin(...)
240 if a:0 > 0
241 let s:plug_home_org = a:1
242 let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p'))
243 elseif exists('g:plug_home')
244 let home = s:path(g:plug_home)
245 elseif has('nvim')
246 let home = stdpath('data') . '/plugged'
247 elseif !empty(&rtp)
248 let home = s:path(split(&rtp, ',')[0]) . '/plugged'
249 else
250 return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
251 endif
252 if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp
253 return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.')
254 endif
255
256 let g:plug_home = home
257 let g:plugs = {}
258 let g:plugs_order = []
259 let s:triggers = {}
260
261 call s:define_commands()
262 return 1
263 endfunction
264
265 function! s:define_commands()
266 command! -nargs=+ -bar Plug call plug#(<args>)
267 if !executable('git')
268 return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
269 endif
270 if has('win32')
271 \ && &shellslash
272 \ && (&shell =~# 'cmd\(\.exe\)\?$' || s:is_powershell(&shell))
273 return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.')
274 endif
275 if !has('nvim')
276 \ && (has('win32') || has('win32unix'))
277 \ && !has('multi_byte')
278 return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.')
279 endif
280 command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>])
281 command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(<bang>0, [<f-args>])
282 command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0)
283 command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
284 command! -nargs=0 -bar PlugStatus call s:status()
285 command! -nargs=0 -bar PlugDiff call s:diff()
286 command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>)
287 endfunction
288
289 function! s:to_a(v)
290 return type(a:v) == s:TYPE.list ? a:v : [a:v]
291 endfunction
292
293 function! s:to_s(v)
294 return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
295 endfunction
296
297 function! s:glob(from, pattern)
298 return s:lines(globpath(a:from, a:pattern))
299 endfunction
300
301 function! s:source(from, ...)
302 let found = 0
303 for pattern in a:000
304 for vim in s:glob(a:from, pattern)
305 execute 'source' s:esc(vim)
306 let found = 1
307 endfor
308 endfor
309 return found
310 endfunction
311
312 function! s:assoc(dict, key, val)
313 let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
314 endfunction
315
316 function! s:ask(message, ...)
317 call inputsave()
318 echohl WarningMsg
319 let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
320 echohl None
321 call inputrestore()
322 echo "\r"
323 return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
324 endfunction
325
326 function! s:ask_no_interrupt(...)
327 try
328 return call('s:ask', a:000)
329 catch
330 return 0
331 endtry
332 endfunction
333
334 function! s:lazy(plug, opt)
335 return has_key(a:plug, a:opt) &&
336 \ (empty(s:to_a(a:plug[a:opt])) ||
337 \ !isdirectory(a:plug.dir) ||
338 \ len(s:glob(s:rtp(a:plug), 'plugin')) ||
339 \ len(s:glob(s:rtp(a:plug), 'after/plugin')))
340 endfunction
341
342 function! plug#end()
343 if !exists('g:plugs')
344 return s:err('plug#end() called without calling plug#begin() first')
345 endif
346
347 if exists('#PlugLOD')
348 augroup PlugLOD
349 autocmd!
350 augroup END
351 augroup! PlugLOD
352 endif
353 let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
354
355 if get(g:, 'did_load_filetypes', 0)
356 filetype off
357 endif
358 for name in g:plugs_order
359 if !has_key(g:plugs, name)
360 continue
361 endif
362 let plug = g:plugs[name]
363 if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for')
364 let s:loaded[name] = 1
365 continue
366 endif
367
368 if has_key(plug, 'on')
369 let s:triggers[name] = { 'map': [], 'cmd': [] }
370 for cmd in s:to_a(plug.on)
371 if cmd =~? '^<Plug>.\+'
372 if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
373 call s:assoc(lod.map, cmd, name)
374 endif
375 call add(s:triggers[name].map, cmd)
376 elseif cmd =~# '^[A-Z]'
377 let cmd = substitute(cmd, '!*$', '', '')
378 if exists(':'.cmd) != 2
379 call s:assoc(lod.cmd, cmd, name)
380 endif
381 call add(s:triggers[name].cmd, cmd)
382 else
383 call s:err('Invalid `on` option: '.cmd.
384 \ '. Should start with an uppercase letter or `<Plug>`.')
385 endif
386 endfor
387 endif
388
389 if has_key(plug, 'for')
390 let types = s:to_a(plug.for)
391 if !empty(types)
392 augroup filetypedetect
393 call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
394 if has('nvim-0.5.0')
395 call s:source(s:rtp(plug), 'ftdetect/**/*.lua', 'after/ftdetect/**/*.lua')
396 endif
397 augroup END
398 endif
399 for type in types
400 call s:assoc(lod.ft, type, name)
401 endfor
402 endif
403 endfor
404
405 for [cmd, names] in items(lod.cmd)
406 execute printf(
407 \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
408 \ cmd, string(cmd), string(names))
409 endfor
410
411 for [map, names] in items(lod.map)
412 for [mode, map_prefix, key_prefix] in
413 \ [['i', '<C-\><C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
414 execute printf(
415 \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
416 \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
417 endfor
418 endfor
419
420 for [ft, names] in items(lod.ft)
421 augroup PlugLOD
422 execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
423 \ ft, string(ft), string(names))
424 augroup END
425 endfor
426
427 call s:reorg_rtp()
428 filetype plugin indent on
429 if has('vim_starting')
430 if has('syntax') && !exists('g:syntax_on')
431 syntax enable
432 end
433 else
434 call s:reload_plugins()
435 endif
436 endfunction
437
438 function! s:loaded_names()
439 return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
440 endfunction
441
442 function! s:load_plugin(spec)
443 call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
444 if has('nvim-0.5.0')
445 call s:source(s:rtp(a:spec), 'plugin/**/*.lua', 'after/plugin/**/*.lua')
446 endif
447 endfunction
448
449 function! s:reload_plugins()
450 for name in s:loaded_names()
451 call s:load_plugin(g:plugs[name])
452 endfor
453 endfunction
454
455 function! s:trim(str)
456 return substitute(a:str, '[\/]\+$', '', '')
457 endfunction
458
459 function! s:version_requirement(val, min)
460 for idx in range(0, len(a:min) - 1)
461 let v = get(a:val, idx, 0)
462 if v < a:min[idx] | return 0
463 elseif v > a:min[idx] | return 1
464 endif
465 endfor
466 return 1
467 endfunction
468
469 function! s:git_version_requirement(...)
470 if !exists('s:git_version')
471 let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)')
472 endif
473 return s:version_requirement(s:git_version, a:000)
474 endfunction
475
476 function! s:progress_opt(base)
477 return a:base && !s:is_win &&
478 \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
479 endfunction
480
481 function! s:rtp(spec)
482 return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
483 endfunction
484
485 if s:is_win
486 function! s:path(path)
487 return s:trim(substitute(a:path, '/', '\', 'g'))
488 endfunction
489
490 function! s:dirpath(path)
491 return s:path(a:path) . '\'
492 endfunction
493
494 function! s:is_local_plug(repo)
495 return a:repo =~? '^[a-z]:\|^[%~]'
496 endfunction
497
498 " Copied from fzf
499 function! s:wrap_cmds(cmds)
500 let cmds = [
501 \ '@echo off',
502 \ 'setlocal enabledelayedexpansion']
503 \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
504 \ + ['endlocal']
505 if has('iconv')
506 if !exists('s:codepage')
507 let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
508 endif
509 return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage))
510 endif
511 return map(cmds, 'v:val."\r"')
512 endfunction
513
514 function! s:batchfile(cmd)
515 let batchfile = s:plug_tempname().'.bat'
516 call writefile(s:wrap_cmds(a:cmd), batchfile)
517 let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0})
518 if s:is_powershell(&shell)
519 let cmd = '& ' . cmd
520 endif
521 return [batchfile, cmd]
522 endfunction
523 else
524 function! s:path(path)
525 return s:trim(a:path)
526 endfunction
527
528 function! s:dirpath(path)
529 return substitute(a:path, '[/\\]*$', '/', '')
530 endfunction
531
532 function! s:is_local_plug(repo)
533 return a:repo[0] =~ '[/$~]'
534 endfunction
535 endif
536
537 function! s:err(msg)
538 echohl ErrorMsg
539 echom '[vim-plug] '.a:msg
540 echohl None
541 endfunction
542
543 function! s:warn(cmd, msg)
544 echohl WarningMsg
545 execute a:cmd 'a:msg'
546 echohl None
547 endfunction
548
549 function! s:esc(path)
550 return escape(a:path, ' ')
551 endfunction
552
553 function! s:escrtp(path)
554 return escape(a:path, ' ,')
555 endfunction
556
557 function! s:remove_rtp()
558 for name in s:loaded_names()
559 let rtp = s:rtp(g:plugs[name])
560 execute 'set rtp-='.s:escrtp(rtp)
561 let after = globpath(rtp, 'after')
562 if isdirectory(after)
563 execute 'set rtp-='.s:escrtp(after)
564 endif
565 endfor
566 endfunction
567
568 function! s:reorg_rtp()
569 if !empty(s:first_rtp)
570 execute 'set rtp-='.s:first_rtp
571 execute 'set rtp-='.s:last_rtp
572 endif
573
574 " &rtp is modified from outside
575 if exists('s:prtp') && s:prtp !=# &rtp
576 call s:remove_rtp()
577 unlet! s:middle
578 endif
579
580 let s:middle = get(s:, 'middle', &rtp)
581 let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
582 let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
583 let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
584 \ . ','.s:middle.','
585 \ . join(map(afters, 'escape(v:val, ",")'), ',')
586 let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
587 let s:prtp = &rtp
588
589 if !empty(s:first_rtp)
590 execute 'set rtp^='.s:first_rtp
591 execute 'set rtp+='.s:last_rtp
592 endif
593 endfunction
594
595 function! s:doautocmd(...)
596 if exists('#'.join(a:000, '#'))
597 execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
598 endif
599 endfunction
600
601 function! s:dobufread(names)
602 for name in a:names
603 let path = s:rtp(g:plugs[name])
604 for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin']
605 if len(finddir(dir, path))
606 if exists('#BufRead')
607 doautocmd BufRead
608 endif
609 return
610 endif
611 endfor
612 endfor
613 endfunction
614
615 function! plug#load(...)
616 if a:0 == 0
617 return s:err('Argument missing: plugin name(s) required')
618 endif
619 if !exists('g:plugs')
620 return s:err('plug#begin was not called')
621 endif
622 let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
623 let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
624 if !empty(unknowns)
625 let s = len(unknowns) > 1 ? 's' : ''
626 return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
627 end
628 let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
629 if !empty(unloaded)
630 for name in unloaded
631 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
632 endfor
633 call s:dobufread(unloaded)
634 return 1
635 end
636 return 0
637 endfunction
638
639 function! s:remove_triggers(name)
640 if !has_key(s:triggers, a:name)
641 return
642 endif
643 for cmd in s:triggers[a:name].cmd
644 execute 'silent! delc' cmd
645 endfor
646 for map in s:triggers[a:name].map
647 execute 'silent! unmap' map
648 execute 'silent! iunmap' map
649 endfor
650 call remove(s:triggers, a:name)
651 endfunction
652
653 function! s:lod(names, types, ...)
654 for name in a:names
655 call s:remove_triggers(name)
656 let s:loaded[name] = 1
657 endfor
658 call s:reorg_rtp()
659
660 for name in a:names
661 let rtp = s:rtp(g:plugs[name])
662 for dir in a:types
663 call s:source(rtp, dir.'/**/*.vim')
664 if has('nvim-0.5.0') " see neovim#14686
665 call s:source(rtp, dir.'/**/*.lua')
666 endif
667 endfor
668 if a:0
669 if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
670 execute 'runtime' a:1
671 endif
672 call s:source(rtp, a:2)
673 endif
674 call s:doautocmd('User', name)
675 endfor
676 endfunction
677
678 function! s:lod_ft(pat, names)
679 let syn = 'syntax/'.a:pat.'.vim'
680 call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
681 execute 'autocmd! PlugLOD FileType' a:pat
682 call s:doautocmd('filetypeplugin', 'FileType')
683 call s:doautocmd('filetypeindent', 'FileType')
684 endfunction
685
686 function! s:lod_cmd(cmd, bang, l1, l2, args, names)
687 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
688 call s:dobufread(a:names)
689 execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
690 endfunction
691
692 function! s:lod_map(map, names, with_prefix, prefix)
693 call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
694 call s:dobufread(a:names)
695 let extra = ''
696 while 1
697 let c = getchar(0)
698 if c == 0
699 break
700 endif
701 let extra .= nr2char(c)
702 endwhile
703
704 if a:with_prefix
705 let prefix = v:count ? v:count : ''
706 let prefix .= '"'.v:register.a:prefix
707 if mode(1) == 'no'
708 if v:operator == 'c'
709 let prefix = "\<esc>" . prefix
710 endif
711 let prefix .= v:operator
712 endif
713 call feedkeys(prefix, 'n')
714 endif
715 call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
716 endfunction
717
718 function! plug#(repo, ...)
719 if a:0 > 1
720 return s:err('Invalid number of arguments (1..2)')
721 endif
722
723 try
724 let repo = s:trim(a:repo)
725 let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
726 let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??'))
727 let spec = extend(s:infer_properties(name, repo), opts)
728 if !has_key(g:plugs, name)
729 call add(g:plugs_order, name)
730 endif
731 let g:plugs[name] = spec
732 let s:loaded[name] = get(s:loaded, name, 0)
733 catch
734 return s:err(repo . ' ' . v:exception)
735 endtry
736 endfunction
737
738 function! s:parse_options(arg)
739 let opts = copy(s:base_spec)
740 let type = type(a:arg)
741 let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)'
742 if type == s:TYPE.string
743 if empty(a:arg)
744 throw printf(opt_errfmt, 'tag', 'string')
745 endif
746 let opts.tag = a:arg
747 elseif type == s:TYPE.dict
748 for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as']
749 if has_key(a:arg, opt)
750 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
751 throw printf(opt_errfmt, opt, 'string')
752 endif
753 endfor
754 for opt in ['on', 'for']
755 if has_key(a:arg, opt)
756 \ && type(a:arg[opt]) != s:TYPE.list
757 \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
758 throw printf(opt_errfmt, opt, 'string or list')
759 endif
760 endfor
761 if has_key(a:arg, 'do')
762 \ && type(a:arg.do) != s:TYPE.funcref
763 \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do))
764 throw printf(opt_errfmt, 'do', 'string or funcref')
765 endif
766 call extend(opts, a:arg)
767 if has_key(opts, 'dir')
768 let opts.dir = s:dirpath(s:plug_expand(opts.dir))
769 endif
770 else
771 throw 'Invalid argument type (expected: string or dictionary)'
772 endif
773 return opts
774 endfunction
775
776 function! s:infer_properties(name, repo)
777 let repo = a:repo
778 if s:is_local_plug(repo)
779 return { 'dir': s:dirpath(s:plug_expand(repo)) }
780 else
781 if repo =~ ':'
782 let uri = repo
783 else
784 if repo !~ '/'
785 throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
786 endif
787 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
788 let uri = printf(fmt, repo)
789 endif
790 return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
791 endif
792 endfunction
793
794 function! s:install(force, names)
795 call s:update_impl(0, a:force, a:names)
796 endfunction
797
798 function! s:update(force, names)
799 call s:update_impl(1, a:force, a:names)
800 endfunction
801
802 function! plug#helptags()
803 if !exists('g:plugs')
804 return s:err('plug#begin was not called')
805 endif
806 for spec in values(g:plugs)
807 let docd = join([s:rtp(spec), 'doc'], '/')
808 if isdirectory(docd)
809 silent! execute 'helptags' s:esc(docd)
810 endif
811 endfor
812 return 1
813 endfunction
814
815 function! s:syntax()
816 syntax clear
817 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
818 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
819 syn match plugNumber /[0-9]\+[0-9.]*/ contained
820 syn match plugBracket /[[\]]/ contained
821 syn match plugX /x/ contained
822 syn match plugDash /^-\{1}\ /
823 syn match plugPlus /^+/
824 syn match plugStar /^*/
825 syn match plugMessage /\(^- \)\@<=.*/
826 syn match plugName /\(^- \)\@<=[^ ]*:/
827 syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
828 syn match plugTag /(tag: [^)]\+)/
829 syn match plugInstall /\(^+ \)\@<=[^:]*/
830 syn match plugUpdate /\(^* \)\@<=[^:]*/
831 syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
832 syn match plugEdge /^ \X\+$/
833 syn match plugEdge /^ \X*/ contained nextgroup=plugSha
834 syn match plugSha /[0-9a-f]\{7,9}/ contained
835 syn match plugRelDate /([^)]*)$/ contained
836 syn match plugNotLoaded /(not loaded)$/
837 syn match plugError /^x.*/
838 syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
839 syn match plugH2 /^.*:\n-\+$/
840 syn match plugH2 /^-\{2,}/
841 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
842 hi def link plug1 Title
843 hi def link plug2 Repeat
844 hi def link plugH2 Type
845 hi def link plugX Exception
846 hi def link plugBracket Structure
847 hi def link plugNumber Number
848
849 hi def link plugDash Special
850 hi def link plugPlus Constant
851 hi def link plugStar Boolean
852
853 hi def link plugMessage Function
854 hi def link plugName Label
855 hi def link plugInstall Function
856 hi def link plugUpdate Type
857
858 hi def link plugError Error
859 hi def link plugDeleted Ignore
860 hi def link plugRelDate Comment
861 hi def link plugEdge PreProc
862 hi def link plugSha Identifier
863 hi def link plugTag Constant
864
865 hi def link plugNotLoaded Comment
866 endfunction
867
868 function! s:lpad(str, len)
869 return a:str . repeat(' ', a:len - len(a:str))
870 endfunction
871
872 function! s:lines(msg)
873 return split(a:msg, "[\r\n]")
874 endfunction
875
876 function! s:lastline(msg)
877 return get(s:lines(a:msg), -1, '')
878 endfunction
879
880 function! s:new_window()
881 execute get(g:, 'plug_window', '-tabnew')
882 endfunction
883
884 function! s:plug_window_exists()
885 let buflist = tabpagebuflist(s:plug_tab)
886 return !empty(buflist) && index(buflist, s:plug_buf) >= 0
887 endfunction
888
889 function! s:switch_in()
890 if !s:plug_window_exists()
891 return 0
892 endif
893
894 if winbufnr(0) != s:plug_buf
895 let s:pos = [tabpagenr(), winnr(), winsaveview()]
896 execute 'normal!' s:plug_tab.'gt'
897 let winnr = bufwinnr(s:plug_buf)
898 execute winnr.'wincmd w'
899 call add(s:pos, winsaveview())
900 else
901 let s:pos = [winsaveview()]
902 endif
903
904 setlocal modifiable
905 return 1
906 endfunction
907
908 function! s:switch_out(...)
909 call winrestview(s:pos[-1])
910 setlocal nomodifiable
911 if a:0 > 0
912 execute a:1
913 endif
914
915 if len(s:pos) > 1
916 execute 'normal!' s:pos[0].'gt'
917 execute s:pos[1] 'wincmd w'
918 call winrestview(s:pos[2])
919 endif
920 endfunction
921
922 function! s:finish_bindings()
923 nnoremap <silent> <buffer> R :call <SID>retry()<cr>
924 nnoremap <silent> <buffer> D :PlugDiff<cr>
925 nnoremap <silent> <buffer> S :PlugStatus<cr>
926 nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
927 xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
928 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
929 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
930 endfunction
931
932 function! s:prepare(...)
933 if empty(s:plug_getcwd())
934 throw 'Invalid current working directory. Cannot proceed.'
935 endif
936
937 for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
938 if exists(evar)
939 throw evar.' detected. Cannot proceed.'
940 endif
941 endfor
942
943 call s:job_abort()
944 if s:switch_in()
945 if b:plug_preview == 1
946 pc
947 endif
948 enew
949 else
950 call s:new_window()
951 endif
952
953 nnoremap <silent> <buffer> q :call <SID>close_pane()<cr>
954 if a:0 == 0
955 call s:finish_bindings()
956 endif
957 let b:plug_preview = -1
958 let s:plug_tab = tabpagenr()
959 let s:plug_buf = winbufnr(0)
960 call s:assign_name()
961
962 for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
963 execute 'silent! unmap <buffer>' k
964 endfor
965 setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
966 if exists('+colorcolumn')
967 setlocal colorcolumn=
968 endif
969 setf vim-plug
970 if exists('g:syntax_on')
971 call s:syntax()
972 endif
973 endfunction
974
975 function! s:close_pane()
976 if b:plug_preview == 1
977 pc
978 let b:plug_preview = -1
979 else
980 bd
981 endif
982 endfunction
983
984 function! s:assign_name()
985 " Assign buffer name
986 let prefix = '[Plugins]'
987 let name = prefix
988 let idx = 2
989 while bufexists(name)
990 let name = printf('%s (%s)', prefix, idx)
991 let idx = idx + 1
992 endwhile
993 silent! execute 'f' fnameescape(name)
994 endfunction
995
996 function! s:chsh(swap)
997 let prev = [&shell, &shellcmdflag, &shellredir]
998 if !s:is_win
999 set shell=sh
1000 endif
1001 if a:swap
1002 if s:is_powershell(&shell)
1003 let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
1004 elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$'
1005 set shellredir=>%s\ 2>&1
1006 endif
1007 endif
1008 return prev
1009 endfunction
1010
1011 function! s:bang(cmd, ...)
1012 let batchfile = ''
1013 try
1014 let [sh, shellcmdflag, shrd] = s:chsh(a:0)
1015 " FIXME: Escaping is incomplete. We could use shellescape with eval,
1016 " but it won't work on Windows.
1017 let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
1018 if s:is_win
1019 let [batchfile, cmd] = s:batchfile(cmd)
1020 endif
1021 let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
1022 execute "normal! :execute g:_plug_bang\<cr>\<cr>"
1023 finally
1024 unlet g:_plug_bang
1025 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
1026 if s:is_win && filereadable(batchfile)
1027 call delete(batchfile)
1028 endif
1029 endtry
1030 return v:shell_error ? 'Exit status: ' . v:shell_error : ''
1031 endfunction
1032
1033 function! s:regress_bar()
1034 let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
1035 call s:progress_bar(2, bar, len(bar))
1036 endfunction
1037
1038 function! s:is_updated(dir)
1039 return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
1040 endfunction
1041
1042 function! s:do(pull, force, todo)
1043 if has('nvim')
1044 " Reset &rtp to invalidate Neovim cache of loaded Lua modules
1045 " See https://github.com/junegunn/vim-plug/pull/1157#issuecomment-1809226110
1046 let &rtp = &rtp
1047 endif
1048 for [name, spec] in items(a:todo)
1049 if !isdirectory(spec.dir)
1050 continue
1051 endif
1052 let installed = has_key(s:update.new, name)
1053 let updated = installed ? 0 :
1054 \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
1055 if a:force || installed || updated
1056 execute 'cd' s:esc(spec.dir)
1057 call append(3, '- Post-update hook for '. name .' ... ')
1058 let error = ''
1059 let type = type(spec.do)
1060 if type == s:TYPE.string
1061 if spec.do[0] == ':'
1062 if !get(s:loaded, name, 0)
1063 let s:loaded[name] = 1
1064 call s:reorg_rtp()
1065 endif
1066 call s:load_plugin(spec)
1067 try
1068 execute spec.do[1:]
1069 catch
1070 let error = v:exception
1071 endtry
1072 if !s:plug_window_exists()
1073 cd -
1074 throw 'Warning: vim-plug was terminated by the post-update hook of '.name
1075 endif
1076 else
1077 let error = s:bang(spec.do)
1078 endif
1079 elseif type == s:TYPE.funcref
1080 try
1081 call s:load_plugin(spec)
1082 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
1083 call spec.do({ 'name': name, 'status': status, 'force': a:force })
1084 catch
1085 let error = v:exception
1086 endtry
1087 else
1088 let error = 'Invalid hook type'
1089 endif
1090 call s:switch_in()
1091 call setline(4, empty(error) ? (getline(4) . 'OK')
1092 \ : ('x' . getline(4)[1:] . error))
1093 if !empty(error)
1094 call add(s:update.errors, name)
1095 call s:regress_bar()
1096 endif
1097 cd -
1098 endif
1099 endfor
1100 endfunction
1101
1102 function! s:hash_match(a, b)
1103 return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
1104 endfunction
1105
1106 function! s:checkout(spec)
1107 let sha = a:spec.commit
1108 let output = s:git_revision(a:spec.dir)
1109 let error = 0
1110 if !empty(output) && !s:hash_match(sha, s:lines(output)[0])
1111 let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : ''
1112 let output = s:system(
1113 \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
1114 let error = v:shell_error
1115 endif
1116 return [output, error]
1117 endfunction
1118
1119 function! s:finish(pull)
1120 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
1121 if new_frozen
1122 let s = new_frozen > 1 ? 's' : ''
1123 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
1124 endif
1125 call append(3, '- Finishing ... ') | 4
1126 redraw
1127 call plug#helptags()
1128 call plug#end()
1129 call setline(4, getline(4) . 'Done!')
1130 redraw
1131 let msgs = []
1132 if !empty(s:update.errors)
1133 call add(msgs, "Press 'R' to retry.")
1134 endif
1135 if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
1136 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
1137 call add(msgs, "Press 'D' to see the updated changes.")
1138 endif
1139 echo join(msgs, ' ')
1140 call s:finish_bindings()
1141 endfunction
1142
1143 function! s:retry()
1144 if empty(s:update.errors)
1145 return
1146 endif
1147 echo
1148 call s:update_impl(s:update.pull, s:update.force,
1149 \ extend(copy(s:update.errors), [s:update.threads]))
1150 endfunction
1151
1152 function! s:is_managed(name)
1153 return has_key(g:plugs[a:name], 'uri')
1154 endfunction
1155
1156 function! s:names(...)
1157 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
1158 endfunction
1159
1160 function! s:check_ruby()
1161 silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
1162 if !exists('g:plug_ruby')
1163 redraw!
1164 return s:warn('echom', 'Warning: Ruby interface is broken')
1165 endif
1166 let ruby_version = split(g:plug_ruby, '\.')
1167 unlet g:plug_ruby
1168 return s:version_requirement(ruby_version, [1, 8, 7])
1169 endfunction
1170
1171 function! s:update_impl(pull, force, args) abort
1172 let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
1173 let args = filter(copy(a:args), 'v:val != "--sync"')
1174 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
1175 \ remove(args, -1) : get(g:, 'plug_threads', 16)
1176
1177 let managed = filter(deepcopy(g:plugs), 's:is_managed(v:key)')
1178 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
1179 \ filter(managed, 'index(args, v:key) >= 0')
1180
1181 if empty(todo)
1182 return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
1183 endif
1184
1185 if !s:is_win && s:git_version_requirement(2, 3)
1186 let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
1187 let $GIT_TERMINAL_PROMPT = 0
1188 for plug in values(todo)
1189 let plug.uri = substitute(plug.uri,
1190 \ '^https://git::@github\.com', 'https://github.com', '')
1191 endfor
1192 endif
1193
1194 if !isdirectory(g:plug_home)
1195 try
1196 call mkdir(g:plug_home, 'p')
1197 catch
1198 return s:err(printf('Invalid plug directory: %s. '.
1199 \ 'Try to call plug#begin with a valid directory', g:plug_home))
1200 endtry
1201 endif
1202
1203 if has('nvim') && !exists('*jobwait') && threads > 1
1204 call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
1205 endif
1206
1207 let use_job = s:nvim || s:vim8
1208 let python = (has('python') || has('python3')) && !use_job
1209 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()
1210
1211 let s:update = {
1212 \ 'start': reltime(),
1213 \ 'all': todo,
1214 \ 'todo': copy(todo),
1215 \ 'errors': [],
1216 \ 'pull': a:pull,
1217 \ 'force': a:force,
1218 \ 'new': {},
1219 \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1220 \ 'bar': '',
1221 \ 'fin': 0
1222 \ }
1223
1224 call s:prepare(1)
1225 call append(0, ['', ''])
1226 normal! 2G
1227 silent! redraw
1228
1229 " Set remote name, overriding a possible user git config's clone.defaultRemoteName
1230 let s:clone_opt = ['--origin', 'origin']
1231 if get(g:, 'plug_shallow', 1)
1232 call extend(s:clone_opt, ['--depth', '1'])
1233 if s:git_version_requirement(1, 7, 10)
1234 call add(s:clone_opt, '--no-single-branch')
1235 endif
1236 endif
1237
1238 if has('win32unix') || has('wsl')
1239 call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
1240 endif
1241
1242 let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1243
1244 " Python version requirement (>= 2.7)
1245 if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1246 redir => pyv
1247 silent python import platform; print platform.python_version()
1248 redir END
1249 let python = s:version_requirement(
1250 \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1251 endif
1252
1253 if (python || ruby) && s:update.threads > 1
1254 try
1255 let imd = &imd
1256 if s:mac_gui
1257 set noimd
1258 endif
1259 if ruby
1260 call s:update_ruby()
1261 else
1262 call s:update_python()
1263 endif
1264 catch
1265 let lines = getline(4, '$')
1266 let printed = {}
1267 silent! 4,$d _
1268 for line in lines
1269 let name = s:extract_name(line, '.', '')
1270 if empty(name) || !has_key(printed, name)
1271 call append('$', line)
1272 if !empty(name)
1273 let printed[name] = 1
1274 if line[0] == 'x' && index(s:update.errors, name) < 0
1275 call add(s:update.errors, name)
1276 end
1277 endif
1278 endif
1279 endfor
1280 finally
1281 let &imd = imd
1282 call s:update_finish()
1283 endtry
1284 else
1285 call s:update_vim()
1286 while use_job && sync
1287 sleep 100m
1288 if s:update.fin
1289 break
1290 endif
1291 endwhile
1292 endif
1293 endfunction
1294
1295 function! s:log4(name, msg)
1296 call setline(4, printf('- %s (%s)', a:msg, a:name))
1297 redraw
1298 endfunction
1299
1300 function! s:update_finish()
1301 if exists('s:git_terminal_prompt')
1302 let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1303 endif
1304 if s:switch_in()
1305 call append(3, '- Updating ...') | 4
1306 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))'))
1307 let [pos, _] = s:logpos(name)
1308 if !pos
1309 continue
1310 endif
1311 let out = ''
1312 let error = 0
1313 if has_key(spec, 'commit')
1314 call s:log4(name, 'Checking out '.spec.commit)
1315 let [out, error] = s:checkout(spec)
1316 elseif has_key(spec, 'tag')
1317 let tag = spec.tag
1318 if tag =~ '\*'
1319 let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1320 if !v:shell_error && !empty(tags)
1321 let tag = tags[0]
1322 call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1323 call append(3, '')
1324 endif
1325 endif
1326 call s:log4(name, 'Checking out '.tag)
1327 let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1328 let error = v:shell_error
1329 endif
1330 if !error && filereadable(spec.dir.'/.gitmodules') &&
1331 \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1332 call s:log4(name, 'Updating submodules. This may take a while.')
1333 let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1334 let error = v:shell_error
1335 endif
1336 let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1337 if error
1338 call add(s:update.errors, name)
1339 call s:regress_bar()
1340 silent execute pos 'd _'
1341 call append(4, msg) | 4
1342 elseif !empty(out)
1343 call setline(pos, msg[0])
1344 endif
1345 redraw
1346 endfor
1347 silent 4 d _
1348 try
1349 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")'))
1350 catch
1351 call s:warn('echom', v:exception)
1352 call s:warn('echo', '')
1353 return
1354 endtry
1355 call s:finish(s:update.pull)
1356 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1357 call s:switch_out('normal! gg')
1358 endif
1359 endfunction
1360
1361 function! s:job_abort()
1362 if (!s:nvim && !s:vim8) || !exists('s:jobs')
1363 return
1364 endif
1365
1366 for [name, j] in items(s:jobs)
1367 if s:nvim
1368 silent! call jobstop(j.jobid)
1369 elseif s:vim8
1370 silent! call job_stop(j.jobid)
1371 endif
1372 if j.new
1373 call s:rm_rf(g:plugs[name].dir)
1374 endif
1375 endfor
1376 let s:jobs = {}
1377 endfunction
1378
1379 function! s:last_non_empty_line(lines)
1380 let len = len(a:lines)
1381 for idx in range(len)
1382 let line = a:lines[len-idx-1]
1383 if !empty(line)
1384 return line
1385 endif
1386 endfor
1387 return ''
1388 endfunction
1389
1390 function! s:job_out_cb(self, data) abort
1391 let self = a:self
1392 let data = remove(self.lines, -1) . a:data
1393 let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1394 call extend(self.lines, lines)
1395 " To reduce the number of buffer updates
1396 let self.tick = get(self, 'tick', -1) + 1
1397 if !self.running || self.tick % len(s:jobs) == 0
1398 let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1399 let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1400 if len(result)
1401 call s:log(bullet, self.name, result)
1402 endif
1403 endif
1404 endfunction
1405
1406 function! s:job_exit_cb(self, data) abort
1407 let a:self.running = 0
1408 let a:self.error = a:data != 0
1409 call s:reap(a:self.name)
1410 call s:tick()
1411 endfunction
1412
1413 function! s:job_cb(fn, job, ch, data)
1414 if !s:plug_window_exists() " plug window closed
1415 return s:job_abort()
1416 endif
1417 call call(a:fn, [a:job, a:data])
1418 endfunction
1419
1420 function! s:nvim_cb(job_id, data, event) dict abort
1421 return (a:event == 'stdout' || a:event == 'stderr') ?
1422 \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
1423 \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1424 endfunction
1425
1426 function! s:spawn(name, spec, queue, opts)
1427 let job = { 'name': a:name, 'spec': a:spec, 'running': 1, 'error': 0, 'lines': [''],
1428 \ 'new': get(a:opts, 'new', 0), 'queue': copy(a:queue) }
1429 let Item = remove(job.queue, 0)
1430 let argv = type(Item) == s:TYPE.funcref ? call(Item, [a:spec]) : Item
1431 let s:jobs[a:name] = job
1432
1433 if s:nvim
1434 if has_key(a:opts, 'dir')
1435 let job.cwd = a:opts.dir
1436 endif
1437 call extend(job, {
1438 \ 'on_stdout': function('s:nvim_cb'),
1439 \ 'on_stderr': function('s:nvim_cb'),
1440 \ 'on_exit': function('s:nvim_cb'),
1441 \ })
1442 let jid = s:plug_call('jobstart', argv, job)
1443 if jid > 0
1444 let job.jobid = jid
1445 else
1446 let job.running = 0
1447 let job.error = 1
1448 let job.lines = [jid < 0 ? argv[0].' is not executable' :
1449 \ 'Invalid arguments (or job table is full)']
1450 endif
1451 elseif s:vim8
1452 let cmd = join(map(copy(argv), 'plug#shellescape(v:val, {"script": 0})'))
1453 if has_key(a:opts, 'dir')
1454 let cmd = s:with_cd(cmd, a:opts.dir, 0)
1455 endif
1456 let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1457 let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1458 \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
1459 \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
1460 \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
1461 \ 'err_mode': 'raw',
1462 \ 'out_mode': 'raw'
1463 \})
1464 if job_status(jid) == 'run'
1465 let job.jobid = jid
1466 else
1467 let job.running = 0
1468 let job.error = 1
1469 let job.lines = ['Failed to start job']
1470 endif
1471 else
1472 let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [argv, a:opts.dir] : [argv]))
1473 let job.error = v:shell_error != 0
1474 let job.running = 0
1475 endif
1476 endfunction
1477
1478 function! s:reap(name)
1479 let job = remove(s:jobs, a:name)
1480 if job.error
1481 call add(s:update.errors, a:name)
1482 elseif get(job, 'new', 0)
1483 let s:update.new[a:name] = 1
1484 endif
1485
1486 let more = len(get(job, 'queue', []))
1487 let bullet = job.error ? 'x' : more ? (job.new ? '+' : '*') : '-'
1488 let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1489 if len(result)
1490 call s:log(bullet, a:name, result)
1491 endif
1492
1493 if !job.error && more
1494 let job.spec.queue = job.queue
1495 let s:update.todo[a:name] = job.spec
1496 else
1497 let s:update.bar .= job.error ? 'x' : '='
1498 call s:bar()
1499 endif
1500 endfunction
1501
1502 function! s:bar()
1503 if s:switch_in()
1504 let total = len(s:update.all)
1505 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1506 \ ' plugins ('.len(s:update.bar).'/'.total.')')
1507 call s:progress_bar(2, s:update.bar, total)
1508 call s:switch_out()
1509 endif
1510 endfunction
1511
1512 function! s:logpos(name)
1513 let max = line('$')
1514 for i in range(4, max > 4 ? max : 4)
1515 if getline(i) =~# '^[-+x*] '.a:name.':'
1516 for j in range(i + 1, max > 5 ? max : 5)
1517 if getline(j) !~ '^ '
1518 return [i, j - 1]
1519 endif
1520 endfor
1521 return [i, i]
1522 endif
1523 endfor
1524 return [0, 0]
1525 endfunction
1526
1527 function! s:log(bullet, name, lines)
1528 if s:switch_in()
1529 let [b, e] = s:logpos(a:name)
1530 if b > 0
1531 silent execute printf('%d,%d d _', b, e)
1532 if b > winheight('.')
1533 let b = 4
1534 endif
1535 else
1536 let b = 4
1537 endif
1538 " FIXME For some reason, nomodifiable is set after :d in vim8
1539 setlocal modifiable
1540 call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1541 call s:switch_out()
1542 endif
1543 endfunction
1544
1545 function! s:update_vim()
1546 let s:jobs = {}
1547
1548 call s:bar()
1549 call s:tick()
1550 endfunction
1551
1552 function! s:checkout_command(spec)
1553 let a:spec.branch = s:git_origin_branch(a:spec)
1554 return ['git', 'checkout', '-q', a:spec.branch, '--']
1555 endfunction
1556
1557 function! s:merge_command(spec)
1558 let a:spec.branch = s:git_origin_branch(a:spec)
1559 return ['git', 'merge', '--ff-only', 'origin/'.a:spec.branch]
1560 endfunction
1561
1562 function! s:tick()
1563 let pull = s:update.pull
1564 let prog = s:progress_opt(s:nvim || s:vim8)
1565 while 1 " Without TCO, Vim stack is bound to explode
1566 if empty(s:update.todo)
1567 if empty(s:jobs) && !s:update.fin
1568 call s:update_finish()
1569 let s:update.fin = 1
1570 endif
1571 return
1572 endif
1573
1574 let name = keys(s:update.todo)[0]
1575 let spec = remove(s:update.todo, name)
1576 let queue = get(spec, 'queue', [])
1577 let new = empty(globpath(spec.dir, '.git', 1))
1578
1579 if empty(queue)
1580 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1581 redraw
1582 endif
1583
1584 let has_tag = has_key(spec, 'tag')
1585 if len(queue)
1586 call s:spawn(name, spec, queue, { 'dir': spec.dir })
1587 elseif !new
1588 let [error, _] = s:git_validate(spec, 0)
1589 if empty(error)
1590 if pull
1591 let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
1592 if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
1593 call extend(cmd, ['--depth', '99999999'])
1594 endif
1595 if !empty(prog)
1596 call add(cmd, prog)
1597 endif
1598 let queue = [cmd, split('git remote set-head origin -a')]
1599 if !has_tag && !has_key(spec, 'commit')
1600 call extend(queue, [function('s:checkout_command'), function('s:merge_command')])
1601 endif
1602 call s:spawn(name, spec, queue, { 'dir': spec.dir })
1603 else
1604 let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1605 endif
1606 else
1607 let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1608 endif
1609 else
1610 let cmd = ['git', 'clone']
1611 if !has_tag
1612 call extend(cmd, s:clone_opt)
1613 endif
1614 if !empty(prog)
1615 call add(cmd, prog)
1616 endif
1617 call s:spawn(name, spec, [extend(cmd, [spec.uri, s:trim(spec.dir)]), function('s:checkout_command'), function('s:merge_command')], { 'new': 1 })
1618 endif
1619
1620 if !s:jobs[name].running
1621 call s:reap(name)
1622 endif
1623 if len(s:jobs) >= s:update.threads
1624 break
1625 endif
1626 endwhile
1627 endfunction
1628
1629 function! s:update_python()
1630 let py_exe = has('python') ? 'python' : 'python3'
1631 execute py_exe "<< EOF"
1632 import datetime
1633 import functools
1634 import os
1635 try:
1636 import queue
1637 except ImportError:
1638 import Queue as queue
1639 import random
1640 import re
1641 import shutil
1642 import signal
1643 import subprocess
1644 import tempfile
1645 import threading as thr
1646 import time
1647 import traceback
1648 import vim
1649
1650 G_NVIM = vim.eval("has('nvim')") == '1'
1651 G_PULL = vim.eval('s:update.pull') == '1'
1652 G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1653 G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1654 G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
1655 G_PROGRESS = vim.eval('s:progress_opt(1)')
1656 G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1657 G_STOP = thr.Event()
1658 G_IS_WIN = vim.eval('s:is_win') == '1'
1659
1660 class PlugError(Exception):
1661 def __init__(self, msg):
1662 self.msg = msg
1663 class CmdTimedOut(PlugError):
1664 pass
1665 class CmdFailed(PlugError):
1666 pass
1667 class InvalidURI(PlugError):
1668 pass
1669 class Action(object):
1670 INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1671
1672 class Buffer(object):
1673 def __init__(self, lock, num_plugs, is_pull):
1674 self.bar = ''
1675 self.event = 'Updating' if is_pull else 'Installing'
1676 self.lock = lock
1677 self.maxy = int(vim.eval('winheight(".")'))
1678 self.num_plugs = num_plugs
1679
1680 def __where(self, name):
1681 """ Find first line with name in current buffer. Return line num. """
1682 found, lnum = False, 0
1683 matcher = re.compile('^[-+x*] {0}:'.format(name))
1684 for line in vim.current.buffer:
1685 if matcher.search(line) is not None:
1686 found = True
1687 break
1688 lnum += 1
1689
1690 if not found:
1691 lnum = -1
1692 return lnum
1693
1694 def header(self):
1695 curbuf = vim.current.buffer
1696 curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1697
1698 num_spaces = self.num_plugs - len(self.bar)
1699 curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1700
1701 with self.lock:
1702 vim.command('normal! 2G')
1703 vim.command('redraw')
1704
1705 def write(self, action, name, lines):
1706 first, rest = lines[0], lines[1:]
1707 msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1708 msg.extend([' ' + line for line in rest])
1709
1710 try:
1711 if action == Action.ERROR:
1712 self.bar += 'x'
1713 vim.command("call add(s:update.errors, '{0}')".format(name))
1714 elif action == Action.DONE:
1715 self.bar += '='
1716
1717 curbuf = vim.current.buffer
1718 lnum = self.__where(name)
1719 if lnum != -1: # Found matching line num
1720 del curbuf[lnum]
1721 if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1722 lnum = 3
1723 else:
1724 lnum = 3
1725 curbuf.append(msg, lnum)
1726
1727 self.header()
1728 except vim.error:
1729 pass
1730
1731 class Command(object):
1732 CD = 'cd /d' if G_IS_WIN else 'cd'
1733
1734 def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1735 self.cmd = cmd
1736 if cmd_dir:
1737 self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1738 self.timeout = timeout
1739 self.callback = cb if cb else (lambda msg: None)
1740 self.clean = clean if clean else (lambda: None)
1741 self.proc = None
1742
1743 @property
1744 def alive(self):
1745 """ Returns true only if command still running. """
1746 return self.proc and self.proc.poll() is None
1747
1748 def execute(self, ntries=3):
1749 """ Execute the command with ntries if CmdTimedOut.
1750 Returns the output of the command if no Exception.
1751 """
1752 attempt, finished, limit = 0, False, self.timeout
1753
1754 while not finished:
1755 try:
1756 attempt += 1
1757 result = self.try_command()
1758 finished = True
1759 return result
1760 except CmdTimedOut:
1761 if attempt != ntries:
1762 self.notify_retry()
1763 self.timeout += limit
1764 else:
1765 raise
1766
1767 def notify_retry(self):
1768 """ Retry required for command, notify user. """
1769 for count in range(3, 0, -1):
1770 if G_STOP.is_set():
1771 raise KeyboardInterrupt
1772 msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1773 count, 's' if count != 1 else '')
1774 self.callback([msg])
1775 time.sleep(1)
1776 self.callback(['Retrying ...'])
1777
1778 def try_command(self):
1779 """ Execute a cmd & poll for callback. Returns list of output.
1780 Raises CmdFailed -> return code for Popen isn't 0
1781 Raises CmdTimedOut -> command exceeded timeout without new output
1782 """
1783 first_line = True
1784
1785 try:
1786 tfile = tempfile.NamedTemporaryFile(mode='w+b')
1787 preexec_fn = not G_IS_WIN and os.setsid or None
1788 self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1789 stderr=subprocess.STDOUT,
1790 stdin=subprocess.PIPE, shell=True,
1791 preexec_fn=preexec_fn)
1792 thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1793 thrd.start()
1794
1795 thread_not_started = True
1796 while thread_not_started:
1797 try:
1798 thrd.join(0.1)
1799 thread_not_started = False
1800 except RuntimeError:
1801 pass
1802
1803 while self.alive:
1804 if G_STOP.is_set():
1805 raise KeyboardInterrupt
1806
1807 if first_line or random.random() < G_LOG_PROB:
1808 first_line = False
1809 line = '' if G_IS_WIN else nonblock_read(tfile.name)
1810 if line:
1811 self.callback([line])
1812
1813 time_diff = time.time() - os.path.getmtime(tfile.name)
1814 if time_diff > self.timeout:
1815 raise CmdTimedOut(['Timeout!'])
1816
1817 thrd.join(0.5)
1818
1819 tfile.seek(0)
1820 result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1821
1822 if self.proc.returncode != 0:
1823 raise CmdFailed([''] + result)
1824
1825 return result
1826 except:
1827 self.terminate()
1828 raise
1829
1830 def terminate(self):
1831 """ Terminate process and cleanup. """
1832 if self.alive:
1833 if G_IS_WIN:
1834 os.kill(self.proc.pid, signal.SIGINT)
1835 else:
1836 os.killpg(self.proc.pid, signal.SIGTERM)
1837 self.clean()
1838
1839 class Plugin(object):
1840 def __init__(self, name, args, buf_q, lock):
1841 self.name = name
1842 self.args = args
1843 self.buf_q = buf_q
1844 self.lock = lock
1845 self.tag = args.get('tag', 0)
1846
1847 def manage(self):
1848 try:
1849 if os.path.exists(self.args['dir']):
1850 self.update()
1851 else:
1852 self.install()
1853 with self.lock:
1854 thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1855 except PlugError as exc:
1856 self.write(Action.ERROR, self.name, exc.msg)
1857 except KeyboardInterrupt:
1858 G_STOP.set()
1859 self.write(Action.ERROR, self.name, ['Interrupted!'])
1860 except:
1861 # Any exception except those above print stack trace
1862 msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1863 self.write(Action.ERROR, self.name, msg.split('\n'))
1864 raise
1865
1866 def install(self):
1867 target = self.args['dir']
1868 if target[-1] == '\\':
1869 target = target[0:-1]
1870
1871 def clean(target):
1872 def _clean():
1873 try:
1874 shutil.rmtree(target)
1875 except OSError:
1876 pass
1877 return _clean
1878
1879 self.write(Action.INSTALL, self.name, ['Installing ...'])
1880 callback = functools.partial(self.write, Action.INSTALL, self.name)
1881 cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1882 '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1883 esc(target))
1884 com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1885 result = com.execute(G_RETRIES)
1886 self.write(Action.DONE, self.name, result[-1:])
1887
1888 def repo_uri(self):
1889 cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1890 command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1891 result = command.execute(G_RETRIES)
1892 return result[-1]
1893
1894 def update(self):
1895 actual_uri = self.repo_uri()
1896 expect_uri = self.args['uri']
1897 regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1898 ma = regex.match(actual_uri)
1899 mb = regex.match(expect_uri)
1900 if ma is None or mb is None or ma.groups() != mb.groups():
1901 msg = ['',
1902 'Invalid URI: {0}'.format(actual_uri),
1903 'Expected {0}'.format(expect_uri),
1904 'PlugClean required.']
1905 raise InvalidURI(msg)
1906
1907 if G_PULL:
1908 self.write(Action.UPDATE, self.name, ['Updating ...'])
1909 callback = functools.partial(self.write, Action.UPDATE, self.name)
1910 fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1911 cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1912 com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1913 result = com.execute(G_RETRIES)
1914 self.write(Action.DONE, self.name, result[-1:])
1915 else:
1916 self.write(Action.DONE, self.name, ['Already installed'])
1917
1918 def write(self, action, name, msg):
1919 self.buf_q.put((action, name, msg))
1920
1921 class PlugThread(thr.Thread):
1922 def __init__(self, tname, args):
1923 super(PlugThread, self).__init__()
1924 self.tname = tname
1925 self.args = args
1926
1927 def run(self):
1928 thr.current_thread().name = self.tname
1929 buf_q, work_q, lock = self.args
1930
1931 try:
1932 while not G_STOP.is_set():
1933 name, args = work_q.get_nowait()
1934 plug = Plugin(name, args, buf_q, lock)
1935 plug.manage()
1936 work_q.task_done()
1937 except queue.Empty:
1938 pass
1939
1940 class RefreshThread(thr.Thread):
1941 def __init__(self, lock):
1942 super(RefreshThread, self).__init__()
1943 self.lock = lock
1944 self.running = True
1945
1946 def run(self):
1947 while self.running:
1948 with self.lock:
1949 thread_vim_command('noautocmd normal! a')
1950 time.sleep(0.33)
1951
1952 def stop(self):
1953 self.running = False
1954
1955 if G_NVIM:
1956 def thread_vim_command(cmd):
1957 vim.session.threadsafe_call(lambda: vim.command(cmd))
1958 else:
1959 def thread_vim_command(cmd):
1960 vim.command(cmd)
1961
1962 def esc(name):
1963 return '"' + name.replace('"', '\"') + '"'
1964
1965 def nonblock_read(fname):
1966 """ Read a file with nonblock flag. Return the last line. """
1967 fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1968 buf = os.read(fread, 100000).decode('utf-8', 'replace')
1969 os.close(fread)
1970
1971 line = buf.rstrip('\r\n')
1972 left = max(line.rfind('\r'), line.rfind('\n'))
1973 if left != -1:
1974 left += 1
1975 line = line[left:]
1976
1977 return line
1978
1979 def main():
1980 thr.current_thread().name = 'main'
1981 nthreads = int(vim.eval('s:update.threads'))
1982 plugs = vim.eval('s:update.todo')
1983 mac_gui = vim.eval('s:mac_gui') == '1'
1984
1985 lock = thr.Lock()
1986 buf = Buffer(lock, len(plugs), G_PULL)
1987 buf_q, work_q = queue.Queue(), queue.Queue()
1988 for work in plugs.items():
1989 work_q.put(work)
1990
1991 start_cnt = thr.active_count()
1992 for num in range(nthreads):
1993 tname = 'PlugT-{0:02}'.format(num)
1994 thread = PlugThread(tname, (buf_q, work_q, lock))
1995 thread.start()
1996 if mac_gui:
1997 rthread = RefreshThread(lock)
1998 rthread.start()
1999
2000 while not buf_q.empty() or thr.active_count() != start_cnt:
2001 try:
2002 action, name, msg = buf_q.get(True, 0.25)
2003 buf.write(action, name, ['OK'] if not msg else msg)
2004 buf_q.task_done()
2005 except queue.Empty:
2006 pass
2007 except KeyboardInterrupt:
2008 G_STOP.set()
2009
2010 if mac_gui:
2011 rthread.stop()
2012 rthread.join()
2013
2014 main()
2015 EOF
2016 endfunction
2017
2018 function! s:update_ruby()
2019 ruby << EOF
2020 module PlugStream
2021 SEP = ["\r", "\n", nil]
2022 def get_line
2023 buffer = ''
2024 loop do
2025 char = readchar rescue return
2026 if SEP.include? char.chr
2027 buffer << $/
2028 break
2029 else
2030 buffer << char
2031 end
2032 end
2033 buffer
2034 end
2035 end unless defined?(PlugStream)
2036
2037 def esc arg
2038 %["#{arg.gsub('"', '\"')}"]
2039 end
2040
2041 def killall pid
2042 pids = [pid]
2043 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
2044 pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
2045 else
2046 unless `which pgrep 2> /dev/null`.empty?
2047 children = pids
2048 until children.empty?
2049 children = children.map { |pid|
2050 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
2051 }.flatten
2052 pids += children
2053 end
2054 end
2055 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
2056 end
2057 end
2058
2059 def compare_git_uri a, b
2060 regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
2061 regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
2062 end
2063
2064 require 'thread'
2065 require 'fileutils'
2066 require 'timeout'
2067 running = true
2068 iswin = VIM::evaluate('s:is_win').to_i == 1
2069 pull = VIM::evaluate('s:update.pull').to_i == 1
2070 base = VIM::evaluate('g:plug_home')
2071 all = VIM::evaluate('s:update.todo')
2072 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
2073 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
2074 nthr = VIM::evaluate('s:update.threads').to_i
2075 maxy = VIM::evaluate('winheight(".")').to_i
2076 vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
2077 cd = iswin ? 'cd /d' : 'cd'
2078 tot = VIM::evaluate('len(s:update.todo)') || 0
2079 bar = ''
2080 skip = 'Already installed'
2081 mtx = Mutex.new
2082 take1 = proc { mtx.synchronize { running && all.shift } }
2083 logh = proc {
2084 cnt = bar.length
2085 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
2086 $curbuf[2] = '[' + bar.ljust(tot) + ']'
2087 VIM::command('normal! 2G')
2088 VIM::command('redraw')
2089 }
2090 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
2091 log = proc { |name, result, type|
2092 mtx.synchronize do
2093 ing = ![true, false].include?(type)
2094 bar += type ? '=' : 'x' unless ing
2095 b = case type
2096 when :install then '+' when :update then '*'
2097 when true, nil then '-' else
2098 VIM::command("call add(s:update.errors, '#{name}')")
2099 'x'
2100 end
2101 result =
2102 if type || type.nil?
2103 ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
2104 elsif result =~ /^Interrupted|^Timeout/
2105 ["#{b} #{name}: #{result}"]
2106 else
2107 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
2108 end
2109 if lnum = where.call(name)
2110 $curbuf.delete lnum
2111 lnum = 4 if ing && lnum > maxy
2112 end
2113 result.each_with_index do |line, offset|
2114 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
2115 end
2116 logh.call
2117 end
2118 }
2119 bt = proc { |cmd, name, type, cleanup|
2120 tried = timeout = 0
2121 begin
2122 tried += 1
2123 timeout += limit
2124 fd = nil
2125 data = ''
2126 if iswin
2127 Timeout::timeout(timeout) do
2128 tmp = VIM::evaluate('tempname()')
2129 system("(#{cmd}) > #{tmp}")
2130 data = File.read(tmp).chomp
2131 File.unlink tmp rescue nil
2132 end
2133 else
2134 fd = IO.popen(cmd).extend(PlugStream)
2135 first_line = true
2136 log_prob = 1.0 / nthr
2137 while line = Timeout::timeout(timeout) { fd.get_line }
2138 data << line
2139 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
2140 first_line = false
2141 end
2142 fd.close
2143 end
2144 [$? == 0, data.chomp]
2145 rescue Timeout::Error, Interrupt => e
2146 if fd && !fd.closed?
2147 killall fd.pid
2148 fd.close
2149 end
2150 cleanup.call if cleanup
2151 if e.is_a?(Timeout::Error) && tried < tries
2152 3.downto(1) do |countdown|
2153 s = countdown > 1 ? 's' : ''
2154 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
2155 sleep 1
2156 end
2157 log.call name, 'Retrying ...', type
2158 retry
2159 end
2160 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
2161 end
2162 }
2163 main = Thread.current
2164 threads = []
2165 watcher = Thread.new {
2166 if vim7
2167 while VIM::evaluate('getchar(1)')
2168 sleep 0.1
2169 end
2170 else
2171 require 'io/console' # >= Ruby 1.9
2172 nil until IO.console.getch == 3.chr
2173 end
2174 mtx.synchronize do
2175 running = false
2176 threads.each { |t| t.raise Interrupt } unless vim7
2177 end
2178 threads.each { |t| t.join rescue nil }
2179 main.kill
2180 }
2181 refresh = Thread.new {
2182 while true
2183 mtx.synchronize do
2184 break unless running
2185 VIM::command('noautocmd normal! a')
2186 end
2187 sleep 0.2
2188 end
2189 } if VIM::evaluate('s:mac_gui') == 1
2190
2191 clone_opt = VIM::evaluate('s:clone_opt').join(' ')
2192 progress = VIM::evaluate('s:progress_opt(1)')
2193 nthr.times do
2194 mtx.synchronize do
2195 threads << Thread.new {
2196 while pair = take1.call
2197 name = pair.first
2198 dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2199 exists = File.directory? dir
2200 ok, result =
2201 if exists
2202 chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2203 ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2204 current_uri = data.lines.to_a.last
2205 if !ret
2206 if data =~ /^Interrupted|^Timeout/
2207 [false, data]
2208 else
2209 [false, [data.chomp, "PlugClean required."].join($/)]
2210 end
2211 elsif !compare_git_uri(current_uri, uri)
2212 [false, ["Invalid URI: #{current_uri}",
2213 "Expected: #{uri}",
2214 "PlugClean required."].join($/)]
2215 else
2216 if pull
2217 log.call name, 'Updating ...', :update
2218 fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2219 bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2220 else
2221 [true, skip]
2222 end
2223 end
2224 else
2225 d = esc dir.sub(%r{[\\/]+$}, '')
2226 log.call name, 'Installing ...', :install
2227 bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2228 FileUtils.rm_rf dir
2229 }
2230 end
2231 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2232 log.call name, result, ok
2233 end
2234 } if running
2235 end
2236 end
2237 threads.each { |t| t.join rescue nil }
2238 logh.call
2239 refresh.kill if refresh
2240 watcher.kill
2241 EOF
2242 endfunction
2243
2244 function! s:shellesc_cmd(arg, script)
2245 let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2246 return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2247 endfunction
2248
2249 function! s:shellesc_ps1(arg)
2250 return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2251 endfunction
2252
2253 function! s:shellesc_sh(arg)
2254 return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2255 endfunction
2256
2257 " Escape the shell argument based on the shell.
2258 " Vim and Neovim's shellescape() are insufficient.
2259 " 1. shellslash determines whether to use single/double quotes.
2260 " Double-quote escaping is fragile for cmd.exe.
2261 " 2. It does not work for powershell.
2262 " 3. It does not work for *sh shells if the command is executed
2263 " via cmd.exe (ie. cmd.exe /c sh -c command command_args)
2264 " 4. It does not support batchfile syntax.
2265 "
2266 " Accepts an optional dictionary with the following keys:
2267 " - shell: same as Vim/Neovim 'shell' option.
2268 " If unset, fallback to 'cmd.exe' on Windows or 'sh'.
2269 " - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
2270 function! plug#shellescape(arg, ...)
2271 if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
2272 return a:arg
2273 endif
2274 let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2275 let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2276 let script = get(opts, 'script', 1)
2277 if shell =~# 'cmd\(\.exe\)\?$'
2278 return s:shellesc_cmd(a:arg, script)
2279 elseif s:is_powershell(shell)
2280 return s:shellesc_ps1(a:arg)
2281 endif
2282 return s:shellesc_sh(a:arg)
2283 endfunction
2284
2285 function! s:glob_dir(path)
2286 return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2287 endfunction
2288
2289 function! s:progress_bar(line, bar, total)
2290 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2291 endfunction
2292
2293 function! s:compare_git_uri(a, b)
2294 " See `git help clone'
2295 " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2296 " [git@] github.com[:port] : junegunn/vim-plug [.git]
2297 " file:// / junegunn/vim-plug [/]
2298 " / junegunn/vim-plug [/]
2299 let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2300 let ma = matchlist(a:a, pat)
2301 let mb = matchlist(a:b, pat)
2302 return ma[1:2] ==# mb[1:2]
2303 endfunction
2304
2305 function! s:format_message(bullet, name, message)
2306 if a:bullet != 'x'
2307 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2308 else
2309 let lines = map(s:lines(a:message), '" ".v:val')
2310 return extend([printf('x %s:', a:name)], lines)
2311 endif
2312 endfunction
2313
2314 function! s:with_cd(cmd, dir, ...)
2315 let script = a:0 > 0 ? a:1 : 1
2316 return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
2317 endfunction
2318
2319 function! s:system(cmd, ...)
2320 let batchfile = ''
2321 try
2322 let [sh, shellcmdflag, shrd] = s:chsh(1)
2323 if type(a:cmd) == s:TYPE.list
2324 " Neovim's system() supports list argument to bypass the shell
2325 " but it cannot set the working directory for the command.
2326 " Assume that the command does not rely on the shell.
2327 if has('nvim') && a:0 == 0
2328 return system(a:cmd)
2329 endif
2330 let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
2331 if s:is_powershell(&shell)
2332 let cmd = '& ' . cmd
2333 endif
2334 else
2335 let cmd = a:cmd
2336 endif
2337 if a:0 > 0
2338 let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
2339 endif
2340 if s:is_win && type(a:cmd) != s:TYPE.list
2341 let [batchfile, cmd] = s:batchfile(cmd)
2342 endif
2343 return system(cmd)
2344 finally
2345 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2346 if s:is_win && filereadable(batchfile)
2347 call delete(batchfile)
2348 endif
2349 endtry
2350 endfunction
2351
2352 function! s:system_chomp(...)
2353 let ret = call('s:system', a:000)
2354 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2355 endfunction
2356
2357 function! s:git_validate(spec, check_branch)
2358 let err = ''
2359 if isdirectory(a:spec.dir)
2360 let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
2361 let remote = result[-1]
2362 if empty(remote)
2363 let err = join([remote, 'PlugClean required.'], "\n")
2364 elseif !s:compare_git_uri(remote, a:spec.uri)
2365 let err = join(['Invalid URI: '.remote,
2366 \ 'Expected: '.a:spec.uri,
2367 \ 'PlugClean required.'], "\n")
2368 elseif a:check_branch && has_key(a:spec, 'commit')
2369 let sha = s:git_revision(a:spec.dir)
2370 if empty(sha)
2371 let err = join(add(result, 'PlugClean required.'), "\n")
2372 elseif !s:hash_match(sha, a:spec.commit)
2373 let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2374 \ a:spec.commit[:6], sha[:6]),
2375 \ 'PlugUpdate required.'], "\n")
2376 endif
2377 elseif a:check_branch
2378 let current_branch = result[0]
2379 " Check tag
2380 let origin_branch = s:git_origin_branch(a:spec)
2381 if has_key(a:spec, 'tag')
2382 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2383 if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2384 let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2385 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2386 endif
2387 " Check branch
2388 elseif origin_branch !=# current_branch
2389 let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2390 \ current_branch, origin_branch)
2391 endif
2392 if empty(err)
2393 let ahead_behind = split(s:lastline(s:system([
2394 \ 'git', 'rev-list', '--count', '--left-right',
2395 \ printf('HEAD...origin/%s', origin_branch)
2396 \ ], a:spec.dir)), '\t')
2397 if v:shell_error || len(ahead_behind) != 2
2398 let err = "Failed to compare with the origin. The default branch might have changed.\nPlugClean required."
2399 else
2400 let [ahead, behind] = ahead_behind
2401 if ahead && behind
2402 " Only mention PlugClean if diverged, otherwise it's likely to be
2403 " pushable (and probably not that messed up).
2404 let err = printf(
2405 \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2406 \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
2407 elseif ahead
2408 let err = printf("Ahead of origin/%s by %d commit(s).\n"
2409 \ .'Cannot update until local changes are pushed.',
2410 \ origin_branch, ahead)
2411 endif
2412 endif
2413 endif
2414 endif
2415 else
2416 let err = 'Not found'
2417 endif
2418 return [err, err =~# 'PlugClean']
2419 endfunction
2420
2421 function! s:rm_rf(dir)
2422 if isdirectory(a:dir)
2423 return s:system(s:is_win
2424 \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
2425 \ : ['rm', '-rf', a:dir])
2426 endif
2427 endfunction
2428
2429 function! s:clean(force)
2430 call s:prepare()
2431 call append(0, 'Searching for invalid plugins in '.g:plug_home)
2432 call append(1, '')
2433
2434 " List of valid directories
2435 let dirs = []
2436 let errs = {}
2437 let [cnt, total] = [0, len(g:plugs)]
2438 for [name, spec] in items(g:plugs)
2439 if !s:is_managed(name)
2440 call add(dirs, spec.dir)
2441 else
2442 let [err, clean] = s:git_validate(spec, 1)
2443 if clean
2444 let errs[spec.dir] = s:lines(err)[0]
2445 else
2446 call add(dirs, spec.dir)
2447 endif
2448 endif
2449 let cnt += 1
2450 call s:progress_bar(2, repeat('=', cnt), total)
2451 normal! 2G
2452 redraw
2453 endfor
2454
2455 let allowed = {}
2456 for dir in dirs
2457 let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2458 let allowed[dir] = 1
2459 for child in s:glob_dir(dir)
2460 let allowed[child] = 1
2461 endfor
2462 endfor
2463
2464 let todo = []
2465 let found = sort(s:glob_dir(g:plug_home))
2466 while !empty(found)
2467 let f = remove(found, 0)
2468 if !has_key(allowed, f) && isdirectory(f)
2469 call add(todo, f)
2470 call append(line('$'), '- ' . f)
2471 if has_key(errs, f)
2472 call append(line('$'), ' ' . errs[f])
2473 endif
2474 let found = filter(found, 'stridx(v:val, f) != 0')
2475 end
2476 endwhile
2477
2478 4
2479 redraw
2480 if empty(todo)
2481 call append(line('$'), 'Already clean.')
2482 else
2483 let s:clean_count = 0
2484 call append(3, ['Directories to delete:', ''])
2485 redraw!
2486 if a:force || s:ask_no_interrupt('Delete all directories?')
2487 call s:delete([6, line('$')], 1)
2488 else
2489 call setline(4, 'Cancelled.')
2490 nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
2491 nmap <silent> <buffer> dd d_
2492 xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
2493 echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2494 endif
2495 endif
2496 4
2497 setlocal nomodifiable
2498 endfunction
2499
2500 function! s:delete_op(type, ...)
2501 call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2502 endfunction
2503
2504 function! s:delete(range, force)
2505 let [l1, l2] = a:range
2506 let force = a:force
2507 let err_count = 0
2508 while l1 <= l2
2509 let line = getline(l1)
2510 if line =~ '^- ' && isdirectory(line[2:])
2511 execute l1
2512 redraw!
2513 let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2514 let force = force || answer > 1
2515 if answer
2516 let err = s:rm_rf(line[2:])
2517 setlocal modifiable
2518 if empty(err)
2519 call setline(l1, '~'.line[1:])
2520 let s:clean_count += 1
2521 else
2522 delete _
2523 call append(l1 - 1, s:format_message('x', line[1:], err))
2524 let l2 += len(s:lines(err))
2525 let err_count += 1
2526 endif
2527 let msg = printf('Removed %d directories.', s:clean_count)
2528 if err_count > 0
2529 let msg .= printf(' Failed to remove %d directories.', err_count)
2530 endif
2531 call setline(4, msg)
2532 setlocal nomodifiable
2533 endif
2534 endif
2535 let l1 += 1
2536 endwhile
2537 endfunction
2538
2539 function! s:upgrade()
2540 echo 'Downloading the latest version of vim-plug'
2541 redraw
2542 let tmp = s:plug_tempname()
2543 let new = tmp . '/plug.vim'
2544
2545 try
2546 let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
2547 if v:shell_error
2548 return s:err('Error upgrading vim-plug: '. out)
2549 endif
2550
2551 if readfile(s:me) ==# readfile(new)
2552 echo 'vim-plug is already up-to-date'
2553 return 0
2554 else
2555 call rename(s:me, s:me . '.old')
2556 call rename(new, s:me)
2557 unlet g:loaded_plug
2558 echo 'vim-plug has been upgraded'
2559 return 1
2560 endif
2561 finally
2562 silent! call s:rm_rf(tmp)
2563 endtry
2564 endfunction
2565
2566 function! s:upgrade_specs()
2567 for spec in values(g:plugs)
2568 let spec.frozen = get(spec, 'frozen', 0)
2569 endfor
2570 endfunction
2571
2572 function! s:status()
2573 call s:prepare()
2574 call append(0, 'Checking plugins')
2575 call append(1, '')
2576
2577 let ecnt = 0
2578 let unloaded = 0
2579 let [cnt, total] = [0, len(g:plugs)]
2580 for [name, spec] in items(g:plugs)
2581 let is_dir = isdirectory(spec.dir)
2582 if has_key(spec, 'uri')
2583 if is_dir
2584 let [err, _] = s:git_validate(spec, 1)
2585 let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2586 else
2587 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2588 endif
2589 else
2590 if is_dir
2591 let [valid, msg] = [1, 'OK']
2592 else
2593 let [valid, msg] = [0, 'Not found.']
2594 endif
2595 endif
2596 let cnt += 1
2597 let ecnt += !valid
2598 " `s:loaded` entry can be missing if PlugUpgraded
2599 if is_dir && get(s:loaded, name, -1) == 0
2600 let unloaded = 1
2601 let msg .= ' (not loaded)'
2602 endif
2603 call s:progress_bar(2, repeat('=', cnt), total)
2604 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2605 normal! 2G
2606 redraw
2607 endfor
2608 call setline(1, 'Finished. '.ecnt.' error(s).')
2609 normal! gg
2610 setlocal nomodifiable
2611 if unloaded
2612 echo "Press 'L' on each line to load plugin, or 'U' to update"
2613 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2614 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
2615 end
2616 endfunction
2617
2618 function! s:extract_name(str, prefix, suffix)
2619 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2620 endfunction
2621
2622 function! s:status_load(lnum)
2623 let line = getline(a:lnum)
2624 let name = s:extract_name(line, '-', '(not loaded)')
2625 if !empty(name)
2626 call plug#load(name)
2627 setlocal modifiable
2628 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2629 setlocal nomodifiable
2630 endif
2631 endfunction
2632
2633 function! s:status_update() range
2634 let lines = getline(a:firstline, a:lastline)
2635 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2636 if !empty(names)
2637 echo
2638 execute 'PlugUpdate' join(names)
2639 endif
2640 endfunction
2641
2642 function! s:is_preview_window_open()
2643 silent! wincmd P
2644 if &previewwindow
2645 wincmd p
2646 return 1
2647 endif
2648 endfunction
2649
2650 function! s:find_name(lnum)
2651 for lnum in reverse(range(1, a:lnum))
2652 let line = getline(lnum)
2653 if empty(line)
2654 return ''
2655 endif
2656 let name = s:extract_name(line, '-', '')
2657 if !empty(name)
2658 return name
2659 endif
2660 endfor
2661 return ''
2662 endfunction
2663
2664 function! s:preview_commit()
2665 if b:plug_preview < 0
2666 let b:plug_preview = !s:is_preview_window_open()
2667 endif
2668
2669 let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
2670 if empty(sha)
2671 let name = matchstr(getline('.'), '^- \zs[^:]*\ze:$')
2672 if empty(name)
2673 return
2674 endif
2675 let title = 'HEAD@{1}..'
2676 let command = 'git diff --no-color HEAD@{1}'
2677 else
2678 let title = sha
2679 let command = 'git show --no-color --pretty=medium '.sha
2680 let name = s:find_name(line('.'))
2681 endif
2682
2683 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2684 return
2685 endif
2686
2687 if !s:is_preview_window_open()
2688 execute get(g:, 'plug_pwindow', 'vertical rightbelow new')
2689 execute 'e' title
2690 else
2691 execute 'pedit' title
2692 wincmd P
2693 endif
2694 setlocal previewwindow filetype=git buftype=nofile bufhidden=wipe nobuflisted modifiable
2695 let batchfile = ''
2696 try
2697 let [sh, shellcmdflag, shrd] = s:chsh(1)
2698 let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && '.command
2699 if s:is_win
2700 let [batchfile, cmd] = s:batchfile(cmd)
2701 endif
2702 execute 'silent %!' cmd
2703 finally
2704 let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2705 if s:is_win && filereadable(batchfile)
2706 call delete(batchfile)
2707 endif
2708 endtry
2709 setlocal nomodifiable
2710 nnoremap <silent> <buffer> q :q<cr>
2711 wincmd p
2712 endfunction
2713
2714 function! s:section(flags)
2715 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2716 endfunction
2717
2718 function! s:format_git_log(line)
2719 let indent = ' '
2720 let tokens = split(a:line, nr2char(1))
2721 if len(tokens) != 5
2722 return indent.substitute(a:line, '\s*$', '', '')
2723 endif
2724 let [graph, sha, refs, subject, date] = tokens
2725 let tag = matchstr(refs, 'tag: [^,)]\+')
2726 let tag = empty(tag) ? ' ' : ' ('.tag.') '
2727 return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2728 endfunction
2729
2730 function! s:append_ul(lnum, text)
2731 call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2732 endfunction
2733
2734 function! s:diff()
2735 call s:prepare()
2736 call append(0, ['Collecting changes ...', ''])
2737 let cnts = [0, 0]
2738 let bar = ''
2739 let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2740 call s:progress_bar(2, bar, len(total))
2741 for origin in [1, 0]
2742 let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2743 if empty(plugs)
2744 continue
2745 endif
2746 call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2747 for [k, v] in plugs
2748 let branch = s:git_origin_branch(v)
2749 if len(branch)
2750 let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
2751 let cmd = ['git', 'log', '--graph', '--color=never']
2752 if s:git_version_requirement(2, 10, 0)
2753 call add(cmd, '--no-show-signature')
2754 endif
2755 call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
2756 if has_key(v, 'rtp')
2757 call extend(cmd, ['--', v.rtp])
2758 endif
2759 let diff = s:system_chomp(cmd, v.dir)
2760 if !empty(diff)
2761 let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2762 call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2763 let cnts[origin] += 1
2764 endif
2765 endif
2766 let bar .= '='
2767 call s:progress_bar(2, bar, len(total))
2768 normal! 2G
2769 redraw
2770 endfor
2771 if !cnts[origin]
2772 call append(5, ['', 'N/A'])
2773 endif
2774 endfor
2775 call setline(1, printf('%d plugin(s) updated.', cnts[0])
2776 \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2777
2778 if cnts[0] || cnts[1]
2779 nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
2780 if empty(maparg("\<cr>", 'n'))
2781 nmap <buffer> <cr> <plug>(plug-preview)
2782 endif
2783 if empty(maparg('o', 'n'))
2784 nmap <buffer> o <plug>(plug-preview)
2785 endif
2786 endif
2787 if cnts[0]
2788 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2789 echo "Press 'X' on each block to revert the update"
2790 endif
2791 normal! gg
2792 setlocal nomodifiable
2793 endfunction
2794
2795 function! s:revert()
2796 if search('^Pending updates', 'bnW')
2797 return
2798 endif
2799
2800 let name = s:find_name(line('.'))
2801 if empty(name) || !has_key(g:plugs, name) ||
2802 \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2803 return
2804 endif
2805
2806 call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2807 setlocal modifiable
2808 normal! "_dap
2809 setlocal nomodifiable
2810 echo 'Reverted'
2811 endfunction
2812
2813 function! s:snapshot(force, ...) abort
2814 call s:prepare()
2815 setf vim
2816 call append(0, ['" Generated by vim-plug',
2817 \ '" '.strftime("%c"),
2818 \ '" :source this file in vim to restore the snapshot',
2819 \ '" or execute: vim -S snapshot.vim',
2820 \ '', '', 'PlugUpdate!'])
2821 1
2822 let anchor = line('$') - 3
2823 let names = sort(keys(filter(copy(g:plugs),
2824 \'has_key(v:val, "uri") && isdirectory(v:val.dir)')))
2825 for name in reverse(names)
2826 let sha = has_key(g:plugs[name], 'commit') ? g:plugs[name].commit : s:git_revision(g:plugs[name].dir)
2827 if !empty(sha)
2828 call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2829 redraw
2830 endif
2831 endfor
2832
2833 if a:0 > 0
2834 let fn = s:plug_expand(a:1)
2835 if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2836 return
2837 endif
2838 call writefile(getline(1, '$'), fn)
2839 echo 'Saved as '.a:1
2840 silent execute 'e' s:esc(fn)
2841 setf vim
2842 endif
2843 endfunction
2844
2845 function! s:split_rtp()
2846 return split(&rtp, '\\\@<!,')
2847 endfunction
2848
2849 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
2850 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2851
2852 if exists('g:plugs')
2853 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
2854 call s:upgrade_specs()
2855 call s:define_commands()
2856 endif
2857
2858 let &cpo = s:cpo_save
2859 unlet s:cpo_save