comparison vim/autoload/plug.vim @ 187:e3639166a8ab

Remove pathogen in favour of Vim-plug
author zegervdv <zegervdv@me.com>
date Thu, 06 Nov 2014 21:40:28 +0100
parents
children 757b5e22e2ea
comparison
equal deleted inserted replaced
186:7bc5adc5ce62 187:e3639166a8ab
1 " vim-plug: Vim plugin manager
2 " ============================
3 "
4 " Download plug.vim and put it in ~/.vim/autoload
5 "
6 " mkdir -p ~/.vim/autoload
7 " curl -fLo ~/.vim/autoload/plug.vim \
8 " https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
9 "
10 " Edit your .vimrc
11 "
12 " call plug#begin('~/.vim/plugged')
13 "
14 " " Make sure you use single quotes
15 " Plug 'junegunn/seoul256.vim'
16 " Plug 'junegunn/vim-easy-align'
17 "
18 " " On-demand loading
19 " Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' }
20 " Plug 'tpope/vim-fireplace', { 'for': 'clojure' }
21 "
22 " " Using git URL
23 " Plug 'https://github.com/junegunn/vim-github-dashboard.git'
24 "
25 " " Plugin options
26 " Plug 'nsf/gocode', { 'tag': 'go.weekly.2012-03-13', 'rtp': 'vim' }
27 "
28 " " Plugin outside ~/.vim/plugged with post-update hook
29 " Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': 'yes \| ./install' }
30 "
31 " " Unmanaged plugin (manually installed and updated)
32 " Plug '~/my-prototype-plugin'
33 "
34 " call plug#end()
35 "
36 " Then reload .vimrc and :PlugInstall to install plugins.
37 " Visit https://github.com/junegunn/vim-plug for more information.
38 "
39 "
40 " Copyright (c) 2014 Junegunn Choi
41 "
42 " MIT License
43 "
44 " Permission is hereby granted, free of charge, to any person obtaining
45 " a copy of this software and associated documentation files (the
46 " "Software"), to deal in the Software without restriction, including
47 " without limitation the rights to use, copy, modify, merge, publish,
48 " distribute, sublicense, and/or sell copies of the Software, and to
49 " permit persons to whom the Software is furnished to do so, subject to
50 " the following conditions:
51 "
52 " The above copyright notice and this permission notice shall be
53 " included in all copies or substantial portions of the Software.
54 "
55 " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
56 " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
57 " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
58 " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
59 " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
60 " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
61 " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
62
63 if exists('g:loaded_plug')
64 finish
65 endif
66 let g:loaded_plug = 1
67
68 let s:cpo_save = &cpo
69 set cpo&vim
70
71 let s:plug_src = 'https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
72 let s:plug_tab = get(s:, 'plug_tab', -1)
73 let s:plug_buf = get(s:, 'plug_buf', -1)
74 let s:mac_gui = has('gui_macvim') && has('gui_running')
75 let s:is_win = has('win32') || has('win64')
76 let s:nvim = exists('##JobActivity') && !s:is_win
77 let s:me = resolve(expand('<sfile>:p'))
78 let s:base_spec = { 'branch': 'master', 'frozen': 0 }
79 let s:TYPE = {
80 \ 'string': type(''),
81 \ 'list': type([]),
82 \ 'dict': type({}),
83 \ 'funcref': type(function('call'))
84 \ }
85 let s:loaded = get(s:, 'loaded', {})
86
87 function! plug#begin(...)
88 if a:0 > 0
89 let s:plug_home_org = a:1
90 let home = s:path(fnamemodify(expand(a:1), ':p'))
91 elseif exists('g:plug_home')
92 let home = s:path(g:plug_home)
93 elseif !empty(&rtp)
94 let home = s:path(split(&rtp, ',')[0]) . '/plugged'
95 else
96 return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
97 endif
98
99 let g:plug_home = home
100 let g:plugs = {}
101 let g:plugs_order = []
102
103 call s:define_commands()
104 return 1
105 endfunction
106
107 function! s:define_commands()
108 command! -nargs=+ -bar Plug call s:add(<args>)
109 if !executable('git')
110 return s:err('`git` executable not found. vim-plug requires git.')
111 endif
112 command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install('<bang>' == '!', [<f-args>])
113 command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update('<bang>' == '!', [<f-args>])
114 command! -nargs=0 -bar -bang PlugClean call s:clean('<bang>' == '!')
115 command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:me | endif
116 command! -nargs=0 -bar PlugStatus call s:status()
117 command! -nargs=0 -bar PlugDiff call s:diff()
118 command! -nargs=? -bar PlugSnapshot call s:snapshot(<f-args>)
119 endfunction
120
121 function! s:to_a(v)
122 return type(a:v) == s:TYPE.list ? a:v : [a:v]
123 endfunction
124
125 function! s:source(from, ...)
126 for pattern in a:000
127 for vim in s:lines(globpath(a:from, pattern))
128 execute 'source' vim
129 endfor
130 endfor
131 endfunction
132
133 function! plug#end()
134 if !exists('g:plugs')
135 return s:err('Call plug#begin() first')
136 endif
137
138 if exists('#PlugLOD')
139 augroup PlugLOD
140 autocmd!
141 augroup END
142 augroup! PlugLOD
143 endif
144 let lod = {}
145
146 filetype off
147 for name in g:plugs_order
148 let plug = g:plugs[name]
149 if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for')
150 let s:loaded[name] = 1
151 continue
152 endif
153
154 if has_key(plug, 'on')
155 for cmd in s:to_a(plug.on)
156 if cmd =~ '^<Plug>.\+'
157 if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
158 for [mode, map_prefix, key_prefix] in
159 \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
160 execute printf(
161 \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, "%s")<CR>',
162 \ mode, cmd, map_prefix, string(cmd), string(name), key_prefix)
163 endfor
164 endif
165 elseif !exists(':'.cmd)
166 execute printf(
167 \ 'command! -nargs=* -range -bang %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
168 \ cmd, string(cmd), string(name))
169 endif
170 endfor
171 endif
172
173 if has_key(plug, 'for')
174 let types = s:to_a(plug.for)
175 if !empty(types)
176 call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
177 endif
178 for key in types
179 if !has_key(lod, key)
180 let lod[key] = []
181 endif
182 call add(lod[key], name)
183 endfor
184 endif
185 endfor
186
187 for [key, names] in items(lod)
188 augroup PlugLOD
189 execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
190 \ key, string(key), string(names))
191 augroup END
192 endfor
193
194 call s:reorg_rtp()
195 filetype plugin indent on
196 if has('vim_starting')
197 syntax enable
198 else
199 call s:reload()
200 endif
201 endfunction
202
203 function! s:loaded_names()
204 return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
205 endfunction
206
207 function! s:reload()
208 for name in s:loaded_names()
209 call s:source(s:rtp(g:plugs[name]), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
210 endfor
211 endfunction
212
213 function! s:trim(str)
214 return substitute(a:str, '[\/]\+$', '', '')
215 endfunction
216
217 if s:is_win
218 function! s:rtp(spec)
219 return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
220 endfunction
221
222 function! s:path(path)
223 return s:trim(substitute(a:path, '/', '\', 'g'))
224 endfunction
225
226 function! s:dirpath(path)
227 return s:path(a:path) . '\'
228 endfunction
229
230 function! s:is_local_plug(repo)
231 return a:repo =~? '^[a-z]:'
232 endfunction
233 else
234 function! s:rtp(spec)
235 return s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
236 endfunction
237
238 function! s:path(path)
239 return s:trim(a:path)
240 endfunction
241
242 function! s:dirpath(path)
243 return substitute(a:path, '[/\\]*$', '/', '')
244 endfunction
245
246 function! s:is_local_plug(repo)
247 return a:repo[0] =~ '[/$~]'
248 endfunction
249 endif
250
251 function! s:err(msg)
252 echohl ErrorMsg
253 echom a:msg
254 echohl None
255 return 0
256 endfunction
257
258 function! s:esc(path)
259 return escape(a:path, ' ')
260 endfunction
261
262 function! s:escrtp(path)
263 return escape(a:path, ' ,')
264 endfunction
265
266 function! s:remove_rtp()
267 for name in s:loaded_names()
268 let rtp = s:rtp(g:plugs[name])
269 execute 'set rtp-='.s:escrtp(rtp)
270 let after = globpath(rtp, 'after')
271 if isdirectory(after)
272 execute 'set rtp-='.s:escrtp(after)
273 endif
274 endfor
275 endfunction
276
277 function! s:reorg_rtp()
278 if !empty(s:first_rtp)
279 execute 'set rtp-='.s:first_rtp
280 execute 'set rtp-='.s:last_rtp
281 endif
282
283 " &rtp is modified from outside
284 if exists('s:prtp') && s:prtp !=# &rtp
285 call s:remove_rtp()
286 unlet! s:middle
287 endif
288
289 let s:middle = get(s:, 'middle', &rtp)
290 let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
291 let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), 'isdirectory(v:val)')
292 let rtp = join(map(rtps, 's:escrtp(v:val)'), ',')
293 \ . ','.s:middle.','
294 \ . join(map(afters, 's:escrtp(v:val)'), ',')
295 let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
296 let s:prtp = &rtp
297
298 if !empty(s:first_rtp)
299 execute 'set rtp^='.s:first_rtp
300 execute 'set rtp+='.s:last_rtp
301 endif
302 endfunction
303
304 function! plug#load(...)
305 if a:0 == 0
306 return s:err('Argument missing: plugin name(s) required')
307 endif
308 if !exists('g:plugs')
309 return s:err('plug#begin was not called')
310 endif
311 let unknowns = filter(copy(a:000), '!has_key(g:plugs, v:val)')
312 if !empty(unknowns)
313 let s = len(unknowns) > 1 ? 's' : ''
314 return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
315 end
316 for name in a:000
317 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
318 endfor
319 doautocmd BufRead
320 return 1
321 endfunction
322
323 function! s:lod(names, types)
324 for name in a:names
325 let s:loaded[name] = 1
326 endfor
327 call s:reorg_rtp()
328
329 for name in a:names
330 let rtp = s:rtp(g:plugs[name])
331 for dir in a:types
332 call s:source(rtp, dir.'/**/*.vim')
333 endfor
334 endfor
335 endfunction
336
337 function! s:lod_ft(pat, names)
338 call s:lod(a:names, ['plugin', 'after/plugin'])
339 execute 'autocmd! PlugLOD FileType' a:pat
340 doautocmd filetypeplugin FileType
341 doautocmd filetypeindent FileType
342 endfunction
343
344 function! s:lod_cmd(cmd, bang, l1, l2, args, name)
345 execute 'delc' a:cmd
346 call s:lod([a:name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
347 execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
348 endfunction
349
350 function! s:lod_map(map, name, prefix)
351 execute 'unmap' a:map
352 execute 'iunmap' a:map
353 call s:lod([a:name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
354 let extra = ''
355 while 1
356 let c = getchar(0)
357 if c == 0
358 break
359 endif
360 let extra .= nr2char(c)
361 endwhile
362 call feedkeys(a:prefix . substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
363 endfunction
364
365 function! s:add(repo, ...)
366 if a:0 > 1
367 return s:err('Invalid number of arguments (1..2)')
368 endif
369
370 try
371 let repo = s:trim(a:repo)
372 let name = fnamemodify(repo, ':t:s?\.git$??')
373 let spec = extend(s:infer_properties(name, repo),
374 \ a:0 == 1 ? s:parse_options(a:1) : s:base_spec)
375 let g:plugs[name] = spec
376 let g:plugs_order += [name]
377 let s:loaded[name] = 0
378 catch
379 return s:err(v:exception)
380 endtry
381 endfunction
382
383 function! s:parse_options(arg)
384 let opts = copy(s:base_spec)
385 let type = type(a:arg)
386 if type == s:TYPE.string
387 let opts.branch = a:arg
388 elseif type == s:TYPE.dict
389 call extend(opts, a:arg)
390 if has_key(opts, 'tag')
391 let opts.branch = remove(opts, 'tag')
392 endif
393 if has_key(opts, 'dir')
394 let opts.dir = s:dirpath(expand(opts.dir))
395 endif
396 else
397 throw 'Invalid argument type (expected: string or dictionary)'
398 endif
399 return opts
400 endfunction
401
402 function! s:infer_properties(name, repo)
403 let repo = a:repo
404 if s:is_local_plug(repo)
405 return { 'dir': s:dirpath(expand(repo)) }
406 else
407 if repo =~ ':'
408 let uri = repo
409 else
410 if repo !~ '/'
411 let repo = 'vim-scripts/'. repo
412 endif
413 let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
414 let uri = printf(fmt, repo)
415 endif
416 let dir = s:dirpath( fnamemodify(join([g:plug_home, a:name], '/'), ':p') )
417 return { 'dir': dir, 'uri': uri }
418 endif
419 endfunction
420
421 function! s:install(force, names)
422 call s:update_impl(0, a:force, a:names)
423 endfunction
424
425 function! s:update(force, names)
426 call s:update_impl(1, a:force, a:names)
427 endfunction
428
429 function! plug#helptags()
430 if !exists('g:plugs')
431 return s:err('plug#begin was not called')
432 endif
433 for spec in values(g:plugs)
434 let docd = join([spec.dir, 'doc'], '/')
435 if isdirectory(docd)
436 silent! execute 'helptags' s:esc(docd)
437 endif
438 endfor
439 return 1
440 endfunction
441
442 function! s:syntax()
443 syntax clear
444 syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
445 syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
446 syn match plugNumber /[0-9]\+[0-9.]*/ contained
447 syn match plugBracket /[[\]]/ contained
448 syn match plugX /x/ contained
449 syn match plugDash /^-/
450 syn match plugPlus /^+/
451 syn match plugStar /^*/
452 syn match plugMessage /\(^- \)\@<=.*/
453 syn match plugName /\(^- \)\@<=[^ ]*:/
454 syn match plugInstall /\(^+ \)\@<=[^:]*/
455 syn match plugUpdate /\(^* \)\@<=[^:]*/
456 syn match plugCommit /^ [0-9a-z]\{7} .*/ contains=plugRelDate,plugSha
457 syn match plugSha /\(^ \)\@<=[0-9a-z]\{7}/ contained
458 syn match plugRelDate /([^)]*)$/ contained
459 syn match plugNotLoaded /(not loaded)$/
460 syn match plugError /^x.*/
461 syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
462 hi def link plug1 Title
463 hi def link plug2 Repeat
464 hi def link plugX Exception
465 hi def link plugBracket Structure
466 hi def link plugNumber Number
467
468 hi def link plugDash Special
469 hi def link plugPlus Constant
470 hi def link plugStar Boolean
471
472 hi def link plugMessage Function
473 hi def link plugName Label
474 hi def link plugInstall Function
475 hi def link plugUpdate Type
476
477 hi def link plugError Error
478 hi def link plugRelDate Comment
479 hi def link plugSha Identifier
480
481 hi def link plugNotLoaded Comment
482 endfunction
483
484 function! s:lpad(str, len)
485 return a:str . repeat(' ', a:len - len(a:str))
486 endfunction
487
488 function! s:lines(msg)
489 return split(a:msg, "[\r\n]")
490 endfunction
491
492 function! s:lastline(msg)
493 return get(s:lines(a:msg), -1, '')
494 endfunction
495
496 function! s:new_window()
497 execute get(g:, 'plug_window', 'vertical topleft new')
498 endfunction
499
500 function! s:plug_window_exists()
501 let buflist = tabpagebuflist(s:plug_tab)
502 return !empty(buflist) && index(buflist, s:plug_buf) >= 0
503 endfunction
504
505 function! s:switch_in()
506 if !s:plug_window_exists()
507 return 0
508 endif
509
510 if winbufnr(0) != s:plug_buf
511 let s:pos = [tabpagenr(), winnr(), winsaveview()]
512 execute 'normal!' s:plug_tab.'gt'
513 let winnr = bufwinnr(s:plug_buf)
514 execute winnr.'wincmd w'
515 call add(s:pos, winsaveview())
516 else
517 let s:pos = [winsaveview()]
518 endif
519
520 setlocal modifiable
521 return 1
522 endfunction
523
524 function! s:switch_out(...)
525 call winrestview(s:pos[-1])
526 setlocal nomodifiable
527 if a:0 > 0
528 execute a:1
529 endif
530
531 if len(s:pos) > 1
532 execute 'normal!' s:pos[0].'gt'
533 execute s:pos[1] 'wincmd w'
534 call winrestview(s:pos[2])
535 endif
536 endfunction
537
538 function! s:prepare()
539 call s:job_abort()
540 if s:switch_in()
541 silent %d _
542 else
543 call s:new_window()
544 nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>echo<bar>q<cr>
545 nnoremap <silent> <buffer> R :silent! call <SID>retry()<cr>
546 nnoremap <silent> <buffer> D :PlugDiff<cr>
547 nnoremap <silent> <buffer> S :PlugStatus<cr>
548 nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
549 xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
550 nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
551 nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
552 let b:plug_preview = -1
553 let s:plug_tab = tabpagenr()
554 let s:plug_buf = winbufnr(0)
555 call s:assign_name()
556 endif
557 silent! unmap <buffer> <cr>
558 silent! unmap <buffer> L
559 silent! unmap <buffer> X
560 setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline modifiable
561 setf vim-plug
562 call s:syntax()
563 endfunction
564
565 function! s:assign_name()
566 " Assign buffer name
567 let prefix = '[Plugins]'
568 let name = prefix
569 let idx = 2
570 while bufexists(name)
571 let name = printf('%s (%s)', prefix, idx)
572 let idx = idx + 1
573 endwhile
574 silent! execute 'f' fnameescape(name)
575 endfunction
576
577 function! s:do(pull, force, todo)
578 for [name, spec] in items(a:todo)
579 if !isdirectory(spec.dir)
580 continue
581 endif
582 let installed = has_key(s:update.new, name)
583 let updated = installed ? 0 :
584 \ (a:pull && !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', spec.dir)))
585 if a:force || installed || updated
586 execute 'cd' s:esc(spec.dir)
587 call append(3, '- Post-update hook for '. name .' ... ')
588 let type = type(spec.do)
589 if type == s:TYPE.string
590 try
591 " FIXME: Escaping is incomplete. We could use shellescape with eval,
592 " but it won't work on Windows.
593 let g:_plug_do = '!'.escape(spec.do, '#!%')
594 execute "normal! :execute g:_plug_do\<cr>\<cr>"
595 finally
596 let result = v:shell_error ? ('Exit status: '.v:shell_error) : 'Done!'
597 unlet g:_plug_do
598 endtry
599 elseif type == s:TYPE.funcref
600 try
601 let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
602 call spec.do({ 'name': name, 'status': status, 'force': a:force })
603 let result = 'Done!'
604 catch
605 let result = 'Error: ' . v:exception
606 endtry
607 else
608 let result = 'Error: Invalid type!'
609 endif
610 call setline(4, getline(4) . result)
611 cd -
612 endif
613 endfor
614 endfunction
615
616 function! s:finish(pull)
617 let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
618 if new_frozen
619 let s = new_frozen > 1 ? 's' : ''
620 call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
621 endif
622 call append(3, '- Finishing ... ')
623 redraw
624 call plug#helptags()
625 call plug#end()
626 call setline(4, getline(4) . 'Done!')
627 redraw
628 let msgs = []
629 if !empty(s:update.errors)
630 call add(msgs, "Press 'R' to retry.")
631 endif
632 if a:pull && !empty(filter(getline(5, '$'),
633 \ "v:val =~ '^- ' && stridx(v:val, 'Already up-to-date') < 0"))
634 call add(msgs, "Press 'D' to see the updated changes.")
635 endif
636 echo join(msgs, ' ')
637 endfunction
638
639 function! s:retry()
640 if empty(s:update.errors)
641 return
642 endif
643 call s:update_impl(s:update.pull, s:update.force,
644 \ extend(copy(s:update.errors), [s:update.threads]))
645 endfunction
646
647 function! s:is_managed(name)
648 return has_key(g:plugs[a:name], 'uri')
649 endfunction
650
651 function! s:names(...)
652 return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
653 endfunction
654
655 function! s:update_impl(pull, force, args) abort
656 let args = copy(a:args)
657 let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
658 \ remove(args, -1) : get(g:, 'plug_threads', 16)
659
660 let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
661 let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
662 \ filter(managed, 'index(args, v:key) >= 0')
663
664 if empty(todo)
665 echohl WarningMsg
666 echo 'No plugin to '. (a:pull ? 'update' : 'install') . '.'
667 echohl None
668 return
669 endif
670
671 if !isdirectory(g:plug_home)
672 try
673 call mkdir(g:plug_home, 'p')
674 catch
675 return s:err(printf('Invalid plug directory: %s.'
676 \ 'Try to call plug#begin with a valid directory', g:plug_home))
677 endtry
678 endif
679
680 let s:update = {
681 \ 'start': reltime(),
682 \ 'all': todo,
683 \ 'todo': copy(todo),
684 \ 'errors': [],
685 \ 'pull': a:pull,
686 \ 'force': a:force,
687 \ 'new': {},
688 \ 'threads': (has('ruby') || s:nvim) ? min([len(todo), threads]) : 1,
689 \ 'bar': '',
690 \ 'fin': 0
691 \ }
692
693 call s:prepare()
694 call append(0, ['', ''])
695 normal! 2G
696
697 if has('ruby') && s:update.threads > 1
698 try
699 let imd = &imd
700 if s:mac_gui
701 set noimd
702 endif
703 call s:update_ruby()
704 catch
705 let lines = getline(4, '$')
706 let printed = {}
707 silent 4,$d _
708 for line in lines
709 let name = s:extract_name(line, '.', '')
710 if empty(name) || !has_key(printed, name)
711 call append('$', line)
712 if !empty(name)
713 let printed[name] = 1
714 if line[0] == 'x' && index(s:update.errors, name) < 0
715 call add(s:update.errors, name)
716 end
717 endif
718 endif
719 endfor
720 finally
721 let &imd = imd
722 call s:update_finish()
723 endtry
724 else
725 call s:update_vim()
726 endif
727 endfunction
728
729 function! s:update_finish()
730 if s:switch_in()
731 call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'has_key(v:val, "do")'))
732 call s:finish(s:update.pull)
733 call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
734 call s:switch_out('normal! gg')
735 endif
736 endfunction
737
738 function! s:job_abort()
739 if !s:nvim || !exists('s:jobs')
740 return
741 endif
742 augroup PlugJobControl
743 autocmd!
744 augroup END
745 for [name, j] in items(s:jobs)
746 silent! call jobstop(j.jobid)
747 if j.new
748 call system('rm -rf ' . s:shellesc(g:plugs[name].dir))
749 endif
750 endfor
751 let s:jobs = {}
752 let s:jobs_idx = {}
753 endfunction
754
755 function! s:job_handler() abort
756 if !s:plug_window_exists() " plug window closed
757 return s:job_abort()
758 endif
759
760 let name = get(s:jobs_idx, v:job_data[0], '')
761 if empty(name) " stale task
762 return
763 endif
764 let job = s:jobs[name]
765
766 if v:job_data[1] == 'exit'
767 let job.running = 0
768 if s:lastline(job.result) ==# 'Error'
769 let job.error = 1
770 let job.result = substitute(job.result, "Error[\r\n]$", '', '')
771 endif
772 call s:reap(name)
773 call s:tick()
774 else
775 let job.result .= v:job_data[2]
776 " To reduce the number of buffer updates
777 let job.tick = get(job, 'tick', -1) + 1
778 if job.tick % len(s:jobs) == 0
779 call s:log(job.new ? '+' : '*', name, job.result)
780 endif
781 endif
782 endfunction
783
784 function! s:spawn(name, cmd, opts)
785 let job = { 'running': 1, 'new': get(a:opts, 'new', 0),
786 \ 'error': 0, 'result': '' }
787 let s:jobs[a:name] = job
788
789 if s:nvim
790 let x = jobstart(a:name, 'sh', ['-c',
791 \ (has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd)
792 \ . ' || echo Error'])
793 if x > 0
794 let s:jobs_idx[x] = a:name
795 let job.jobid = x
796 augroup PlugJobControl
797 execute 'autocmd JobActivity' a:name 'call s:job_handler()'
798 augroup END
799 else
800 let job.running = 0
801 let job.error = 1
802 let job.result = x < 0 ? 'sh is not executable' :
803 \ 'Invalid arguments (or job table is full)'
804 endif
805 else
806 let params = has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]
807 let job.result = call('s:system', params)
808 let job.error = v:shell_error != 0
809 let job.running = 0
810 endif
811 endfunction
812
813 function! s:reap(name)
814 if s:nvim
815 silent! execute 'autocmd! PlugJobControl JobActivity' a:name
816 endif
817
818 let job = s:jobs[a:name]
819 if job.error
820 call add(s:update.errors, a:name)
821 elseif get(job, 'new', 0)
822 let s:update.new[a:name] = 1
823 endif
824 let s:update.bar .= job.error ? 'x' : '='
825
826 call s:log(job.error ? 'x' : '-', a:name, job.result)
827 call s:bar()
828
829 call remove(s:jobs, a:name)
830 endfunction
831
832 function! s:bar()
833 if s:switch_in()
834 let total = len(s:update.all)
835 call setline(1, (s:update.pull ? 'Updating' : 'Installing').
836 \ ' plugins ('.len(s:update.bar).'/'.total.')')
837 call s:progress_bar(2, s:update.bar, total)
838 call s:switch_out()
839 endif
840 endfunction
841
842 function! s:logpos(name)
843 for i in range(1, line('$'))
844 if getline(i) =~# '^[-+x*] '.a:name.':'
845 return i
846 endif
847 endfor
848 return 0
849 endfunction
850
851 function! s:log(bullet, name, lines)
852 if s:switch_in()
853 let pos = s:logpos(a:name)
854 if pos > 0
855 execute pos 'd _'
856 if pos > winheight('.')
857 let pos = 4
858 endif
859 else
860 let pos = 4
861 endif
862 call append(pos - 1, s:format_message(a:bullet, a:name, a:lines))
863 call s:switch_out()
864 endif
865 endfunction
866
867 function! s:update_vim()
868 let s:jobs = {}
869 let s:jobs_idx = {}
870
871 call s:bar()
872 call s:tick()
873 endfunction
874
875 function! s:tick()
876 while 1 " Without TCO, Vim stack is bound to explode
877 if empty(s:update.todo)
878 if empty(s:jobs) && !s:update.fin
879 let s:update.fin = 1
880 call s:update_finish()
881 endif
882 return
883 endif
884
885 let name = keys(s:update.todo)[0]
886 let spec = remove(s:update.todo, name)
887 let pull = s:update.pull
888 let new = !isdirectory(spec.dir)
889
890 call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
891 redraw
892
893 if !new
894 let [valid, msg] = s:git_valid(spec, 0)
895 if valid
896 if pull
897 call s:spawn(name,
898 \ printf('git checkout -q %s 2>&1 && git pull --progress --no-rebase origin %s 2>&1 && git submodule update --init --recursive 2>&1',
899 \ s:shellesc(spec.branch), s:shellesc(spec.branch)), { 'dir': spec.dir })
900 else
901 let s:jobs[name] = { 'running': 0, 'result': 'Already installed', 'error': 0 }
902 endif
903 else
904 let s:jobs[name] = { 'running': 0, 'result': msg, 'error': 1 }
905 endif
906 else
907 call s:spawn(name,
908 \ printf('git clone --progress --recursive %s -b %s %s 2>&1',
909 \ s:shellesc(spec.uri),
910 \ s:shellesc(spec.branch),
911 \ s:shellesc(s:trim(spec.dir))), { 'new': 1 })
912 endif
913
914 if !s:jobs[name].running
915 call s:reap(name)
916 endif
917 if len(s:jobs) >= s:update.threads
918 break
919 endif
920 endwhile
921 endfunction
922
923 function! s:update_ruby()
924 ruby << EOF
925 module PlugStream
926 SEP = ["\r", "\n", nil]
927 def get_line
928 buffer = ''
929 loop do
930 char = readchar rescue return
931 if SEP.include? char.chr
932 buffer << $/
933 break
934 else
935 buffer << char
936 end
937 end
938 buffer
939 end
940 end unless defined?(PlugStream)
941
942 def esc arg
943 %["#{arg.gsub('"', '\"')}"]
944 end
945
946 def killall pid
947 pids = [pid]
948 unless `which pgrep 2> /dev/null`.empty?
949 children = pids
950 until children.empty?
951 children = children.map { |pid|
952 `pgrep -P #{pid}`.lines.map { |l| l.chomp }
953 }.flatten
954 pids += children
955 end
956 end
957 pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
958 end
959
960 require 'thread'
961 require 'fileutils'
962 require 'timeout'
963 running = true
964 iswin = VIM::evaluate('s:is_win').to_i == 1
965 pull = VIM::evaluate('s:update.pull').to_i == 1
966 base = VIM::evaluate('g:plug_home')
967 all = VIM::evaluate('s:update.todo')
968 limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
969 tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
970 nthr = VIM::evaluate('s:update.threads').to_i
971 maxy = VIM::evaluate('winheight(".")').to_i
972 cd = iswin ? 'cd /d' : 'cd'
973 tot = VIM::evaluate('len(s:update.todo)') || 0
974 bar = ''
975 skip = 'Already installed'
976 mtx = Mutex.new
977 take1 = proc { mtx.synchronize { running && all.shift } }
978 logh = proc {
979 cnt = bar.length
980 $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
981 $curbuf[2] = '[' + bar.ljust(tot) + ']'
982 VIM::command('normal! 2G')
983 VIM::command('redraw') unless iswin
984 }
985 where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
986 log = proc { |name, result, type|
987 mtx.synchronize do
988 ing = ![true, false].include?(type)
989 bar += type ? '=' : 'x' unless ing
990 b = case type
991 when :install then '+' when :update then '*'
992 when true, nil then '-' else
993 VIM::command("call add(s:update.errors, '#{name}')")
994 'x'
995 end
996 result =
997 if type || type.nil?
998 ["#{b} #{name}: #{result.lines.to_a.last}"]
999 elsif result =~ /^Interrupted|^Timeout/
1000 ["#{b} #{name}: #{result}"]
1001 else
1002 ["#{b} #{name}"] + result.lines.map { |l| " " << l }
1003 end
1004 if lnum = where.call(name)
1005 $curbuf.delete lnum
1006 lnum = 4 if ing && lnum > maxy
1007 end
1008 result.each_with_index do |line, offset|
1009 $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
1010 end
1011 logh.call
1012 end
1013 }
1014 bt = proc { |cmd, name, type, cleanup|
1015 tried = timeout = 0
1016 begin
1017 tried += 1
1018 timeout += limit
1019 fd = nil
1020 data = ''
1021 if iswin
1022 Timeout::timeout(timeout) do
1023 tmp = VIM::evaluate('tempname()')
1024 system("#{cmd} > #{tmp}")
1025 data = File.read(tmp).chomp
1026 File.unlink tmp rescue nil
1027 end
1028 else
1029 fd = IO.popen(cmd).extend(PlugStream)
1030 first_line = true
1031 log_prob = 1.0 / nthr
1032 while line = Timeout::timeout(timeout) { fd.get_line }
1033 data << line
1034 log.call name, line.chomp, type if name && (first_line || rand < log_prob)
1035 first_line = false
1036 end
1037 fd.close
1038 end
1039 [$? == 0, data.chomp]
1040 rescue Timeout::Error, Interrupt => e
1041 if fd && !fd.closed?
1042 killall fd.pid
1043 fd.close
1044 end
1045 cleanup.call if cleanup
1046 if e.is_a?(Timeout::Error) && tried < tries
1047 3.downto(1) do |countdown|
1048 s = countdown > 1 ? 's' : ''
1049 log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
1050 sleep 1
1051 end
1052 log.call name, 'Retrying ...', type
1053 retry
1054 end
1055 [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
1056 end
1057 }
1058 main = Thread.current
1059 threads = []
1060 watcher = Thread.new {
1061 while VIM::evaluate('getchar(1)')
1062 sleep 0.1
1063 end
1064 mtx.synchronize do
1065 running = false
1066 threads.each { |t| t.raise Interrupt }
1067 end
1068 threads.each { |t| t.join rescue nil }
1069 main.kill
1070 }
1071 refresh = Thread.new {
1072 while true
1073 mtx.synchronize do
1074 break unless running
1075 VIM::command('noautocmd normal! a')
1076 end
1077 sleep 0.2
1078 end
1079 } if VIM::evaluate('s:mac_gui') == 1
1080
1081 progress = iswin ? '' : '--progress'
1082 nthr.times do
1083 mtx.synchronize do
1084 threads << Thread.new {
1085 while pair = take1.call
1086 name = pair.first
1087 dir, uri, branch = pair.last.values_at *%w[dir uri branch]
1088 branch = esc branch
1089 subm = "git submodule update --init --recursive 2>&1"
1090 exists = File.directory? dir
1091 ok, result =
1092 if exists
1093 dir = esc dir
1094 ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url", nil, nil, nil
1095 current_uri = data.lines.to_a.last
1096 if !ret
1097 if data =~ /^Interrupted|^Timeout/
1098 [false, data]
1099 else
1100 [false, [data.chomp, "PlugClean required."].join($/)]
1101 end
1102 elsif current_uri.sub(/git::?@/, '') != uri.sub(/git::?@/, '')
1103 [false, ["Invalid URI: #{current_uri}",
1104 "Expected: #{uri}",
1105 "PlugClean required."].join($/)]
1106 else
1107 if pull
1108 log.call name, 'Updating ...', :update
1109 bt.call "#{cd} #{dir} && git checkout -q #{branch} 2>&1 && (git pull --no-rebase origin #{branch} #{progress} 2>&1 && #{subm})", name, :update, nil
1110 else
1111 [true, skip]
1112 end
1113 end
1114 else
1115 d = esc dir.sub(%r{[\\/]+$}, '')
1116 log.call name, 'Installing ...', :install
1117 bt.call "git clone #{progress} --recursive #{uri} -b #{branch} #{d} 2>&1", name, :install, proc {
1118 FileUtils.rm_rf dir
1119 }
1120 end
1121 mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
1122 log.call name, result, ok
1123 end
1124 } if running
1125 end
1126 end
1127 threads.each { |t| t.join rescue nil }
1128 logh.call
1129 refresh.kill if refresh
1130 watcher.kill
1131 EOF
1132 endfunction
1133
1134 function! s:shellesc(arg)
1135 return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
1136 endfunction
1137
1138 function! s:glob_dir(path)
1139 return map(filter(s:lines(globpath(a:path, '**')), 'isdirectory(v:val)'), 's:dirpath(v:val)')
1140 endfunction
1141
1142 function! s:progress_bar(line, bar, total)
1143 call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
1144 endfunction
1145
1146 function! s:compare_git_uri(a, b)
1147 let a = substitute(a:a, 'git:\{1,2}@', '', '')
1148 let b = substitute(a:b, 'git:\{1,2}@', '', '')
1149 return a ==# b
1150 endfunction
1151
1152 function! s:format_message(bullet, name, message)
1153 if a:bullet != 'x'
1154 return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
1155 else
1156 let lines = map(s:lines(a:message), '" ".v:val')
1157 return extend([printf('x %s:', a:name)], lines)
1158 endif
1159 endfunction
1160
1161 function! s:with_cd(cmd, dir)
1162 return 'cd '.s:esc(a:dir).' && '.a:cmd
1163 endfunction
1164
1165 function! s:system(cmd, ...)
1166 let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd
1167 return system(s:is_win ? '('.cmd.')' : cmd)
1168 endfunction
1169
1170 function! s:system_chomp(...)
1171 let ret = call('s:system', a:000)
1172 return v:shell_error ? '' : substitute(ret, '\n$', '', '')
1173 endfunction
1174
1175 function! s:git_valid(spec, check_branch)
1176 let ret = 1
1177 let msg = 'OK'
1178 if isdirectory(a:spec.dir)
1179 let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url', a:spec.dir))
1180 let remote = result[-1]
1181 if v:shell_error
1182 let msg = join([remote, 'PlugClean required.'], "\n")
1183 let ret = 0
1184 elseif !s:compare_git_uri(remote, a:spec.uri)
1185 let msg = join(['Invalid URI: '.remote,
1186 \ 'Expected: '.a:spec.uri,
1187 \ 'PlugClean required.'], "\n")
1188 let ret = 0
1189 elseif a:check_branch
1190 let branch = result[0]
1191 if a:spec.branch !=# branch
1192 let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
1193 if a:spec.branch !=# tag
1194 let msg = printf('Invalid branch/tag: %s (expected: %s). Try PlugUpdate.',
1195 \ (empty(tag) ? branch : tag), a:spec.branch)
1196 let ret = 0
1197 endif
1198 endif
1199 endif
1200 else
1201 let msg = 'Not found'
1202 let ret = 0
1203 endif
1204 return [ret, msg]
1205 endfunction
1206
1207 function! s:clean(force)
1208 call s:prepare()
1209 call append(0, 'Searching for unused plugins in '.g:plug_home)
1210 call append(1, '')
1211
1212 " List of valid directories
1213 let dirs = []
1214 let [cnt, total] = [0, len(g:plugs)]
1215 for [name, spec] in items(g:plugs)
1216 if !s:is_managed(name) || s:git_valid(spec, 0)[0]
1217 call add(dirs, spec.dir)
1218 endif
1219 let cnt += 1
1220 call s:progress_bar(2, repeat('=', cnt), total)
1221 normal! 2G
1222 redraw
1223 endfor
1224
1225 let allowed = {}
1226 for dir in dirs
1227 let allowed[dir] = 1
1228 for child in s:glob_dir(dir)
1229 let allowed[child] = 1
1230 endfor
1231 endfor
1232
1233 let todo = []
1234 let found = sort(s:glob_dir(g:plug_home))
1235 while !empty(found)
1236 let f = remove(found, 0)
1237 if !has_key(allowed, f) && isdirectory(f)
1238 call add(todo, f)
1239 call append(line('$'), '- ' . f)
1240 let found = filter(found, 'stridx(v:val, f) != 0')
1241 end
1242 endwhile
1243
1244 normal! G
1245 redraw
1246 if empty(todo)
1247 call append(line('$'), 'Already clean.')
1248 else
1249 call inputsave()
1250 let yes = a:force || (input('Proceed? (Y/N) ') =~? '^y')
1251 call inputrestore()
1252 if yes
1253 for dir in todo
1254 if isdirectory(dir)
1255 call system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(dir))
1256 endif
1257 endfor
1258 call append(line('$'), 'Removed.')
1259 else
1260 call append(line('$'), 'Cancelled.')
1261 endif
1262 endif
1263 normal! G
1264 endfunction
1265
1266 function! s:upgrade()
1267 let new = s:me . '.new'
1268 echo 'Downloading '. s:plug_src
1269 redraw
1270 try
1271 if executable('curl')
1272 let output = system(printf('curl -fLo %s %s', s:shellesc(new), s:plug_src))
1273 if v:shell_error
1274 throw get(s:lines(output), -1, v:shell_error)
1275 endif
1276 elseif has('ruby')
1277 call s:upgrade_using_ruby(new)
1278 else
1279 return s:err('curl executable or ruby support not found')
1280 endif
1281 catch
1282 return s:err('Error upgrading vim-plug: '. v:exception)
1283 endtry
1284
1285 if readfile(s:me) ==# readfile(new)
1286 echo 'vim-plug is up-to-date'
1287 silent! call delete(new)
1288 return 0
1289 else
1290 call rename(s:me, s:me . '.old')
1291 call rename(new, s:me)
1292 unlet g:loaded_plug
1293 echo 'vim-plug is upgraded'
1294 return 1
1295 endif
1296 endfunction
1297
1298 function! s:upgrade_using_ruby(new)
1299 ruby << EOF
1300 require 'open-uri'
1301 File.open(VIM::evaluate('a:new'), 'w') do |f|
1302 f << open(VIM::evaluate('s:plug_src')).read
1303 end
1304 EOF
1305 endfunction
1306
1307 function! s:upgrade_specs()
1308 for spec in values(g:plugs)
1309 let spec.frozen = get(spec, 'frozen', 0)
1310 endfor
1311 endfunction
1312
1313 function! s:status()
1314 call s:prepare()
1315 call append(0, 'Checking plugins')
1316 call append(1, '')
1317
1318 let ecnt = 0
1319 let unloaded = 0
1320 let [cnt, total] = [0, len(g:plugs)]
1321 for [name, spec] in items(g:plugs)
1322 if has_key(spec, 'uri')
1323 if isdirectory(spec.dir)
1324 let [valid, msg] = s:git_valid(spec, 1)
1325 else
1326 let [valid, msg] = [0, 'Not found. Try PlugInstall.']
1327 endif
1328 else
1329 if isdirectory(spec.dir)
1330 let [valid, msg] = [1, 'OK']
1331 else
1332 let [valid, msg] = [0, 'Not found.']
1333 endif
1334 endif
1335 let cnt += 1
1336 let ecnt += !valid
1337 " `s:loaded` entry can be missing if PlugUpgraded
1338 if valid && get(s:loaded, name, -1) == 0
1339 let unloaded = 1
1340 let msg .= ' (not loaded)'
1341 endif
1342 call s:progress_bar(2, repeat('=', cnt), total)
1343 call append(3, s:format_message(valid ? '-' : 'x', name, msg))
1344 normal! 2G
1345 redraw
1346 endfor
1347 call setline(1, 'Finished. '.ecnt.' error(s).')
1348 normal! gg
1349 setlocal nomodifiable
1350 if unloaded
1351 echo "Press 'L' on each line to load plugin, or 'U' to update"
1352 nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
1353 xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
1354 end
1355 endfunction
1356
1357 function! s:extract_name(str, prefix, suffix)
1358 return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
1359 endfunction
1360
1361 function! s:status_load(lnum)
1362 let line = getline(a:lnum)
1363 let name = s:extract_name(line, '-', '(not loaded)')
1364 if !empty(name)
1365 call plug#load(name)
1366 setlocal modifiable
1367 call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
1368 setlocal nomodifiable
1369 endif
1370 endfunction
1371
1372 function! s:status_update() range
1373 let lines = getline(a:firstline, a:lastline)
1374 let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
1375 if !empty(names)
1376 echo
1377 execute 'PlugUpdate' join(names)
1378 endif
1379 endfunction
1380
1381 function! s:is_preview_window_open()
1382 silent! wincmd P
1383 if &previewwindow
1384 wincmd p
1385 return 1
1386 endif
1387 return 0
1388 endfunction
1389
1390 function! s:find_name(lnum)
1391 for lnum in reverse(range(1, a:lnum))
1392 let line = getline(lnum)
1393 if empty(line)
1394 return ''
1395 endif
1396 let name = s:extract_name(line, '-', '')
1397 if !empty(name)
1398 return name
1399 endif
1400 endfor
1401 return ''
1402 endfunction
1403
1404 function! s:preview_commit()
1405 if b:plug_preview < 0
1406 let b:plug_preview = !s:is_preview_window_open()
1407 endif
1408
1409 let sha = matchstr(getline('.'), '\(^ \)\@<=[0-9a-z]\{7}')
1410 if empty(sha)
1411 return
1412 endif
1413
1414 let name = s:find_name(line('.'))
1415 if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
1416 return
1417 endif
1418
1419 execute 'pedit' sha
1420 wincmd P
1421 setlocal filetype=git buftype=nofile nobuflisted
1422 execute 'silent read !cd' s:esc(g:plugs[name].dir) '&& git show' sha
1423 normal! gg"_dd
1424 wincmd p
1425 endfunction
1426
1427 function! s:section(flags)
1428 call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
1429 endfunction
1430
1431 function! s:diff()
1432 call s:prepare()
1433 call append(0, 'Collecting updated changes ...')
1434 normal! gg
1435 redraw
1436
1437 let cnt = 0
1438 for [k, v] in items(g:plugs)
1439 if !isdirectory(v.dir) || !s:is_managed(k)
1440 continue
1441 endif
1442
1443 let diff = s:system_chomp('git log --pretty=format:"%h %s (%cr)" "HEAD...HEAD@{1}"', v.dir)
1444 if !empty(diff)
1445 call append(1, '')
1446 call append(2, '- '.k.':')
1447 call append(3, map(s:lines(diff), '" ". v:val'))
1448 let cnt += 1
1449 normal! gg
1450 redraw
1451 endif
1452 endfor
1453
1454 call setline(1, cnt == 0 ? 'No updates.' : 'Last update:')
1455 nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
1456 nnoremap <silent> <buffer> X :call <SID>revert()<cr>
1457 normal! gg
1458 setlocal nomodifiable
1459 if cnt > 0
1460 echo "Press 'X' on each block to revert the update"
1461 endif
1462 endfunction
1463
1464 function! s:revert()
1465 let name = s:find_name(line('.'))
1466 if empty(name) || !has_key(g:plugs, name) ||
1467 \ input(printf('Revert the update of %s? (Y/N) ', name)) !~? '^y'
1468 return
1469 endif
1470
1471 call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch), g:plugs[name].dir)
1472 setlocal modifiable
1473 normal! "_dap
1474 setlocal nomodifiable
1475 echo 'Reverted.'
1476 endfunction
1477
1478 function! s:snapshot(...) abort
1479 let home = get(s:, 'plug_home_org', g:plug_home)
1480 let [type, var, header] = s:is_win ?
1481 \ ['dosbatch', '%PLUG_HOME%',
1482 \ ['@echo off', ':: Generated by vim-plug', ':: '.strftime("%c"), '',
1483 \ ':: Make sure to PlugUpdate first', '', 'set PLUG_HOME='.s:esc(home)]] :
1484 \ ['sh', '$PLUG_HOME',
1485 \ ['#!/bin/bash', '# Generated by vim-plug', '# '.strftime("%c"), '',
1486 \ 'vim +PlugUpdate +qa', '', 'PLUG_HOME='.s:esc(home)]]
1487
1488 call s:prepare()
1489 execute 'setf' type
1490 call append(0, header)
1491 call append('$', '')
1492 1
1493 redraw
1494
1495 let dirs = sort(map(values(filter(copy(g:plugs),
1496 \'has_key(v:val, "uri") && isdirectory(v:val.dir)')), 'v:val.dir'))
1497 let anchor = line('$') - 1
1498 for dir in reverse(dirs)
1499 let sha = s:system_chomp('git rev-parse --short HEAD', dir)
1500 if !empty(sha)
1501 call append(anchor, printf('cd %s && git reset --hard %s',
1502 \ substitute(dir, '^'.g:plug_home, var, ''), sha))
1503 redraw
1504 endif
1505 endfor
1506
1507 if a:0 > 0
1508 let fn = s:esc(expand(a:1))
1509 call writefile(getline(1, '$'), fn)
1510 if !s:is_win | call system('chmod +x ' . fn) | endif
1511 echo 'Saved to '.a:1
1512 silent execute 'e' fn
1513 endif
1514 endfunction
1515
1516 function! s:split_rtp()
1517 return split(&rtp, '\\\@<!,')
1518 endfunction
1519
1520 let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
1521 let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
1522
1523 if exists('g:plugs')
1524 let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
1525 call s:upgrade_specs()
1526 call s:define_commands()
1527 endif
1528
1529 let &cpo = s:cpo_save
1530 unlet s:cpo_save
1531