Xinqi Bao's Git

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