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