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