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