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