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