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