plug.vim 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065
  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.github.com/junegunn/vim-plug/master/plug.vim
  9. "
  10. " Edit your .vimrc
  11. "
  12. " call plug#begin()
  13. "
  14. " Plug 'junegunn/seoul256.vim'
  15. " Plug 'junegunn/vim-easy-align'
  16. " Plug 'junegunn/goyo.vim', { 'on': 'Goyo' }
  17. " " Plug 'user/repo1', 'branch_or_tag'
  18. " " Plug 'user/repo2', { 'rtp': 'vim/plugin/dir', 'branch': 'branch_or_tag' }
  19. " " ...
  20. "
  21. " call plug#end()
  22. "
  23. " Then :PlugInstall to install plugins. (default: ~/.vim/plugged)
  24. " You can change the location of the plugins with plug#begin(path) call.
  25. "
  26. "
  27. " Copyright (c) 2014 Junegunn Choi
  28. "
  29. " MIT License
  30. "
  31. " Permission is hereby granted, free of charge, to any person obtaining
  32. " a copy of this software and associated documentation files (the
  33. " "Software"), to deal in the Software without restriction, including
  34. " without limitation the rights to use, copy, modify, merge, publish,
  35. " distribute, sublicense, and/or sell copies of the Software, and to
  36. " permit persons to whom the Software is furnished to do so, subject to
  37. " the following conditions:
  38. "
  39. " The above copyright notice and this permission notice shall be
  40. " included in all copies or substantial portions of the Software.
  41. "
  42. " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  43. " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  44. " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  45. " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  46. " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  47. " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  48. " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  49. if exists('g:loaded_plug')
  50. finish
  51. endif
  52. let g:loaded_plug = 1
  53. let s:cpo_save = &cpo
  54. set cpo&vim
  55. let s:plug_source = 'https://raw.github.com/junegunn/vim-plug/master/plug.vim'
  56. let s:plug_file = 'Plugfile'
  57. let s:plug_buf = -1
  58. let s:is_win = has('win32') || has('win64')
  59. let s:me = expand('<sfile>:p')
  60. function! plug#begin(...)
  61. if a:0 > 0
  62. let home = s:path(fnamemodify(a:1, ':p'))
  63. elseif exists('g:plug_home')
  64. let home = s:path(g:plug_home)
  65. elseif !empty(&rtp)
  66. let home = s:path(split(&rtp, ',')[0]) . '/plugged'
  67. else
  68. echoerr "Unable to determine plug home. Try calling plug#begin() with a path argument."
  69. return 0
  70. endif
  71. if !isdirectory(home)
  72. try
  73. call mkdir(home, 'p')
  74. catch
  75. echoerr 'Invalid plug directory: '. home
  76. return 0
  77. endtry
  78. endif
  79. if !executable('git')
  80. echoerr "`git' executable not found. vim-plug requires git."
  81. return 0
  82. endif
  83. let g:plug_home = home
  84. let g:plugs = {}
  85. " we want to keep track of the order plugins where registered.
  86. let g:plugs_order = []
  87. command! -nargs=+ -bar Plug call s:add(1, <args>)
  88. command! -nargs=* -complete=customlist,s:names PlugInstall call s:install(<f-args>)
  89. command! -nargs=* -complete=customlist,s:names PlugUpdate call s:update(<f-args>)
  90. command! -nargs=0 -bang PlugClean call s:clean('<bang>' == '!')
  91. command! -nargs=0 PlugUpgrade if s:upgrade() | execute "source ". s:me | endif
  92. command! -nargs=0 PlugStatus call s:status()
  93. command! -nargs=0 PlugDiff call s:diff()
  94. return 1
  95. endfunction
  96. function! s:to_a(v)
  97. return type(a:v) == 3 ? a:v : [a:v]
  98. endfunction
  99. function! plug#end()
  100. if !exists('g:plugs')
  101. echoerr 'Call plug#begin() first'
  102. return
  103. endif
  104. let keys = keys(g:plugs)
  105. while !empty(keys)
  106. let keys = keys(s:extend(keys))
  107. endwhile
  108. if exists('#PlugLOD')
  109. augroup PlugLOD
  110. autocmd!
  111. augroup END
  112. augroup! PlugLOD
  113. endif
  114. let lod = {}
  115. filetype off
  116. " we want to make sure the plugin directories are added to rtp in the same
  117. " order that they are registered with the Plug command. since the s:add_rtp
  118. " function uses ^= to add plugin directories to the front of the rtp, we
  119. " need to loop through the plugins in reverse
  120. for name in reverse(copy(g:plugs_order))
  121. let plug = g:plugs[name]
  122. if !has_key(plug, 'on') && !has_key(plug, 'for')
  123. call s:add_rtp(s:rtp(plug))
  124. continue
  125. endif
  126. if has_key(plug, 'on')
  127. let commands = s:to_a(plug.on)
  128. for cmd in commands
  129. if cmd =~ '^<Plug>.\+'
  130. if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
  131. for [mode, map_prefix, key_prefix] in
  132. \ [['i', "<C-O>", ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
  133. execute printf(
  134. \ "%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, '%s')<CR>",
  135. \ mode, cmd, map_prefix, string(cmd), string(name), key_prefix)
  136. endfor
  137. endif
  138. elseif !exists(':'.cmd)
  139. execute printf(
  140. \ "command! -nargs=* -range -bang %s call s:lod_cmd(%s, '<bang>', <line1>, <line2>, <q-args>, %s)",
  141. \ cmd, string(cmd), string(name))
  142. endif
  143. endfor
  144. endif
  145. if has_key(plug, 'for')
  146. for vim in split(globpath(s:rtp(plug), 'ftdetect/**/*.vim'), '\n')
  147. execute 'source '.vim
  148. endfor
  149. for key in s:to_a(plug.for)
  150. if !has_key(lod, key)
  151. let lod[key] = []
  152. endif
  153. call add(lod[key], name)
  154. endfor
  155. endif
  156. endfor
  157. for [key, names] in items(lod)
  158. augroup PlugLOD
  159. execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
  160. \ key, string(key), string(reverse(names)))
  161. augroup END
  162. endfor
  163. call s:reorg_rtp()
  164. filetype plugin indent on
  165. syntax on
  166. endfunction
  167. if s:is_win
  168. function! s:rtp(spec)
  169. let rtp = s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
  170. return substitute(rtp, '\\*$', '', '')
  171. endfunction
  172. function! s:path(path)
  173. return substitute(substitute(a:path, '/', '\', 'g'), '[/\\]*$', '', '')
  174. endfunction
  175. function! s:dirpath(path)
  176. return s:path(a:path) . '\'
  177. endfunction
  178. else
  179. function! s:rtp(spec)
  180. return s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
  181. endfunction
  182. function! s:path(path)
  183. return substitute(a:path, '[/\\]*$', '', '')
  184. endfunction
  185. function! s:dirpath(path)
  186. return s:path(a:path) . '/'
  187. endfunction
  188. endif
  189. function! s:esc(path)
  190. return substitute(a:path, ' ', '\\ ', 'g')
  191. endfunction
  192. function! s:add_rtp(rtp)
  193. execute "set rtp^=".s:esc(a:rtp)
  194. let after = globpath(a:rtp, 'after')
  195. if isdirectory(after)
  196. execute "set rtp+=".s:esc(after)
  197. endif
  198. endfunction
  199. function! s:reorg_rtp()
  200. if !empty(s:first_rtp)
  201. execute 'set rtp-='.s:first_rtp
  202. execute 'set rtp^='.s:first_rtp
  203. endif
  204. if s:last_rtp !=# s:first_rtp
  205. execute 'set rtp-='.s:last_rtp
  206. execute 'set rtp+='.s:last_rtp
  207. endif
  208. endfunction
  209. function! s:lod(plug, types)
  210. let rtp = s:rtp(a:plug)
  211. call s:add_rtp(rtp)
  212. for dir in a:types
  213. for vim in split(globpath(rtp, dir.'/**/*.vim'), '\n')
  214. execute 'source '.vim
  215. endfor
  216. endfor
  217. endfunction
  218. function! s:lod_ft(pat, names)
  219. for name in a:names
  220. call s:lod(g:plugs[name], ['plugin', 'after'])
  221. endfor
  222. call s:reorg_rtp()
  223. execute 'autocmd! PlugLOD FileType ' . a:pat
  224. silent! doautocmd filetypeplugin FileType
  225. endfunction
  226. function! s:lod_cmd(cmd, bang, l1, l2, args, name)
  227. execute 'delc '.a:cmd
  228. call s:lod(g:plugs[a:name], ['plugin', 'ftdetect', 'after'])
  229. call s:reorg_rtp()
  230. execute printf("%s%s%s %s", (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
  231. endfunction
  232. function! s:lod_map(map, name, prefix)
  233. execute 'unmap '.a:map
  234. execute 'iunmap '.a:map
  235. call s:lod(g:plugs[a:name], ['plugin', 'ftdetect', 'after'])
  236. call s:reorg_rtp()
  237. let extra = ''
  238. while 1
  239. let c = getchar(0)
  240. if c == 0
  241. break
  242. endif
  243. let extra .= nr2char(c)
  244. endwhile
  245. call feedkeys(a:prefix . substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
  246. endfunction
  247. function! s:add(force, ...)
  248. let opts = { 'branch': 'master', 'frozen': 0 }
  249. if a:0 == 1
  250. let plugin = a:1
  251. elseif a:0 == 2
  252. let plugin = a:1
  253. if type(a:2) == 1
  254. let opts.branch = a:2
  255. elseif type(a:2) == 4
  256. call extend(opts, a:2)
  257. if has_key(opts, 'tag')
  258. let opts.branch = remove(opts, 'tag')
  259. endif
  260. else
  261. echoerr "Invalid argument type (expected: string or dictionary)"
  262. return
  263. endif
  264. else
  265. echoerr "Invalid number of arguments (1..2)"
  266. return
  267. endif
  268. let plugin = substitute(plugin, '[/\\]*$', '', '')
  269. let name = substitute(split(plugin, '/')[-1], '\.git$', '', '')
  270. if !a:force && has_key(g:plugs, name)
  271. let s:extended[name] = g:plugs[name]
  272. return
  273. endif
  274. if plugin[0] =~ '[/$~]' || plugin =~? '^[a-z]:'
  275. let spec = extend(opts, { 'dir': s:dirpath(expand(plugin)) })
  276. else
  277. if plugin =~ ':'
  278. let uri = plugin
  279. else
  280. if plugin !~ '/'
  281. let plugin = 'vim-scripts/'. plugin
  282. endif
  283. let uri = 'https://git:@github.com/' . plugin . '.git'
  284. endif
  285. let dir = s:dirpath( fnamemodify(join([g:plug_home, name], '/'), ':p') )
  286. let spec = extend(opts, { 'dir': dir, 'uri': uri })
  287. endif
  288. let g:plugs[name] = spec
  289. if !a:force
  290. let s:extended[name] = spec
  291. endif
  292. let g:plugs_order += [name]
  293. endfunction
  294. function! s:install(...)
  295. call s:update_impl(0, a:000)
  296. endfunction
  297. function! s:update(...)
  298. call s:update_impl(1, a:000)
  299. endfunction
  300. function! s:apply()
  301. for spec in values(g:plugs)
  302. let docd = join([spec.dir, 'doc'], '/')
  303. if isdirectory(docd)
  304. silent! execute "helptags ". join([spec.dir, 'doc'], '/')
  305. endif
  306. endfor
  307. runtime! plugin/*.vim
  308. runtime! after/*.vim
  309. silent! source $MYVIMRC
  310. endfunction
  311. function! s:syntax()
  312. syntax clear
  313. syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
  314. syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
  315. syn match plugNumber /[0-9]\+[0-9.]*/ contained
  316. syn match plugBracket /[[\]]/ contained
  317. syn match plugX /x/ contained
  318. syn match plugDash /^-/
  319. syn match plugPlus /^+/
  320. syn match plugStar /^*/
  321. syn match plugName /\(^- \)\@<=[^:]*/
  322. syn match plugInstall /\(^+ \)\@<=[^:]*/
  323. syn match plugUpdate /\(^* \)\@<=[^:]*/
  324. syn match plugCommit /^ [0-9a-z]\{7} .*/ contains=plugRelDate,plugSha
  325. syn match plugSha /\(^ \)\@<=[0-9a-z]\{7}/ contained
  326. syn match plugRelDate /([^)]*)$/ contained
  327. syn match plugError /^x.*/
  328. syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
  329. hi def link plug1 Title
  330. hi def link plug2 Repeat
  331. hi def link plugX Exception
  332. hi def link plugBracket Structure
  333. hi def link plugNumber Number
  334. hi def link plugDash Special
  335. hi def link plugPlus Constant
  336. hi def link plugStar Boolean
  337. hi def link plugName Label
  338. hi def link plugInstall Function
  339. hi def link plugUpdate Type
  340. hi def link plugError Error
  341. hi def link plugRelDate Comment
  342. hi def link plugSha Identifier
  343. endfunction
  344. function! s:lpad(str, len)
  345. return a:str . repeat(' ', a:len - len(a:str))
  346. endfunction
  347. function! s:lastline(msg)
  348. let lines = split(a:msg, '\n')
  349. return get(lines, -1, '')
  350. endfunction
  351. function! s:prepare()
  352. if bufexists(s:plug_buf)
  353. let winnr = bufwinnr(s:plug_buf)
  354. if winnr < 0
  355. vertical topleft new
  356. execute 'buffer ' . s:plug_buf
  357. else
  358. execute winnr . 'wincmd w'
  359. endif
  360. silent %d _
  361. else
  362. vertical topleft new
  363. nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>q<cr>
  364. nnoremap <silent> <buffer> D :PlugDiff<cr>
  365. nnoremap <silent> <buffer> S :PlugStatus<cr>
  366. nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
  367. nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
  368. let b:plug_preview = -1
  369. let s:plug_buf = winbufnr(0)
  370. call s:assign_name()
  371. endif
  372. silent! unmap <buffer> <cr>
  373. setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline
  374. setf vim-plug
  375. call s:syntax()
  376. endfunction
  377. function! s:assign_name()
  378. " Assign buffer name
  379. let prefix = '[Plugins]'
  380. let name = prefix
  381. let idx = 2
  382. while bufexists(name)
  383. let name = printf("%s (%s)", prefix, idx)
  384. let idx = idx + 1
  385. endwhile
  386. silent! execute "f ".fnameescape(name)
  387. endfunction
  388. function! s:finish(pull)
  389. call append(3, '- Finishing ... ')
  390. redraw
  391. call s:apply()
  392. call s:syntax()
  393. call setline(4, getline(4) . 'Done!')
  394. normal! gg
  395. redraw
  396. if a:pull
  397. echo "Press 'D' to see the updated changes."
  398. endif
  399. endfunction
  400. function! s:is_managed(name)
  401. return has_key(g:plugs[a:name], 'uri')
  402. endfunction
  403. function! s:names(...)
  404. return filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')
  405. endfunction
  406. function! s:update_impl(pull, args) abort
  407. let args = copy(a:args)
  408. let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
  409. \ remove(args, -1) : get(g:, 'plug_threads', 16)
  410. let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
  411. let todo = empty(args) ? filter(managed, '!get(v:val, "frozen", 0)') :
  412. \ filter(managed, 'index(args, v:key) >= 0')
  413. if empty(todo)
  414. echohl WarningMsg
  415. echo 'No plugin to '. (a:pull ? 'update' : 'install') . '.'
  416. echohl None
  417. return
  418. endif
  419. call s:prepare()
  420. call append(0, a:pull ? 'Updating plugins' : 'Installing plugins')
  421. call append(1, '['. s:lpad('', len(todo)) .']')
  422. normal! 2G
  423. redraw
  424. let len = len(g:plugs)
  425. if has('ruby') && threads > 1
  426. try
  427. call s:update_parallel(a:pull, todo, threads)
  428. catch
  429. let lines = getline(4, '$')
  430. let printed = {}
  431. silent 4,$d
  432. for line in lines
  433. let name = get(matchlist(line, '^. \([^:]\+\):'), 1, '')
  434. if empty(name) || !has_key(printed, name)
  435. let printed[name] = 1
  436. call append('$', line)
  437. endif
  438. endfor
  439. echoerr v:exception
  440. endtry
  441. else
  442. call s:update_serial(a:pull, todo)
  443. endif
  444. if len(g:plugs) > len
  445. call plug#end()
  446. endif
  447. call s:finish(a:pull)
  448. endfunction
  449. function! s:extend(names)
  450. let s:extended = {}
  451. try
  452. command! -nargs=+ Plug call s:add(0, <args>)
  453. for name in a:names
  454. let plugfile = globpath(s:rtp(g:plugs[name]), s:plug_file)
  455. if filereadable(plugfile)
  456. execute "source ". s:esc(plugfile)
  457. endif
  458. endfor
  459. finally
  460. command! -nargs=+ Plug call s:add(1, <args>)
  461. endtry
  462. return s:extended
  463. endfunction
  464. function! s:update_progress(pull, cnt, bar, total)
  465. call setline(1, (a:pull ? 'Updating' : 'Installing').
  466. \ " plugins (".a:cnt."/".a:total.")")
  467. call s:progress_bar(2, a:bar, a:total)
  468. normal! 2G
  469. redraw
  470. endfunction
  471. function! s:update_serial(pull, todo)
  472. let st = reltime()
  473. let base = g:plug_home
  474. let todo = copy(a:todo)
  475. let total = len(todo)
  476. let done = {}
  477. let bar = ''
  478. while !empty(todo)
  479. for [name, spec] in items(todo)
  480. let done[name] = 1
  481. if isdirectory(spec.dir)
  482. execute 'cd '.s:esc(spec.dir)
  483. let [valid, msg] = s:git_valid(spec, 0, 0)
  484. if valid
  485. let result = a:pull ?
  486. \ s:system(
  487. \ printf('git checkout -q %s 2>&1 && git pull origin %s 2>&1 && git submodule update --init --recursive 2>&1',
  488. \ s:shellesc(spec.branch), s:shellesc(spec.branch))) : 'Already installed'
  489. let error = a:pull ? v:shell_error != 0 : 0
  490. else
  491. let result = msg
  492. let error = 1
  493. endif
  494. cd -
  495. else
  496. if !isdirectory(base)
  497. call mkdir(base, 'p')
  498. endif
  499. let result = s:system(
  500. \ printf('git clone --recursive %s -b %s %s 2>&1 && cd %s && git submodule update --init --recursive 2>&1',
  501. \ s:shellesc(spec.uri),
  502. \ s:shellesc(spec.branch),
  503. \ s:shellesc(substitute(spec.dir, '[\/]\+$', '', '')),
  504. \ s:shellesc(spec.dir)))
  505. let error = v:shell_error != 0
  506. endif
  507. let bar .= error ? 'x' : '='
  508. call append(3, s:format_message(!error, name, result))
  509. call s:update_progress(a:pull, len(done), bar, total)
  510. endfor
  511. let extended = s:extend(keys(todo))
  512. if !empty(extended)
  513. let todo = filter(extended, '!has_key(done, v:key)')
  514. let total += len(todo)
  515. call s:update_progress(a:pull, len(done), bar, total)
  516. else
  517. break
  518. endif
  519. endwhile
  520. call setline(1, "Updated. Elapsed time: " . split(reltimestr(reltime(st)))[0] . ' sec.')
  521. endfunction
  522. function! s:update_parallel(pull, todo, threads)
  523. ruby << EOF
  524. module PlugStream
  525. SEP = ["\r", "\n", nil]
  526. def get_line
  527. buffer = ''
  528. loop do
  529. char = readchar rescue return
  530. if SEP.include? char.chr
  531. buffer << $/
  532. break
  533. else
  534. buffer << char
  535. end
  536. end
  537. buffer
  538. end
  539. end unless defined?(PlugStream)
  540. def esc arg
  541. %["#{arg.gsub('"', '\"')}"]
  542. end
  543. require 'set'
  544. require 'thread'
  545. require 'fileutils'
  546. require 'timeout'
  547. running = true
  548. st = Time.now
  549. iswin = VIM::evaluate('s:is_win').to_i == 1
  550. pull = VIM::evaluate('a:pull').to_i == 1
  551. base = VIM::evaluate('g:plug_home')
  552. all = VIM::evaluate('copy(a:todo)')
  553. limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
  554. tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
  555. nthr = VIM::evaluate('a:threads').to_i
  556. maxy = VIM::evaluate('winheight(".")').to_i
  557. cd = iswin ? 'cd /d' : 'cd'
  558. tot = VIM::evaluate('len(a:todo)') || 0
  559. bar = ''
  560. skip = 'Already installed'
  561. mtx = Mutex.new
  562. take1 = proc { mtx.synchronize { running && all.shift } }
  563. logh = proc {
  564. cnt = bar.length
  565. $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
  566. $curbuf[2] = '[' + bar.ljust(tot) + ']'
  567. VIM::command('normal! 2G')
  568. VIM::command('redraw') unless iswin
  569. }
  570. where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
  571. log = proc { |name, result, type|
  572. mtx.synchronize do
  573. ing = ![true, false].include?(type)
  574. bar += type ? '=' : 'x' unless ing
  575. b = case type
  576. when :install then '+' when :update then '*'
  577. when true, nil then '-' else 'x' end
  578. result =
  579. if type || type.nil?
  580. ["#{b} #{name}: #{result.lines.to_a.last}"]
  581. elsif result =~ /^Interrupted|^Timeout/
  582. ["#{b} #{name}: #{result}"]
  583. else
  584. ["#{b} #{name}"] + result.lines.map { |l| " " << l }
  585. end
  586. if lnum = where.call(name)
  587. $curbuf.delete lnum
  588. lnum = 4 if ing && lnum > maxy
  589. end
  590. result.each_with_index do |line, offset|
  591. $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
  592. end
  593. logh.call
  594. end
  595. }
  596. bt = proc { |cmd, name, type|
  597. tried = timeout = 0
  598. begin
  599. tried += 1
  600. timeout += limit
  601. fd = nil
  602. data = ''
  603. if iswin
  604. Timeout::timeout(timeout) do
  605. tmp = VIM::evaluate('tempname()')
  606. system("#{cmd} > #{tmp}")
  607. data = File.read(tmp).chomp
  608. File.unlink tmp rescue nil
  609. end
  610. else
  611. fd = IO.popen(cmd).extend(PlugStream)
  612. first_line = true
  613. log_prob = 1.0 / nthr
  614. while line = Timeout::timeout(timeout) { fd.get_line }
  615. data << line
  616. log.call name, line.chomp, type if name && (first_line || rand < log_prob)
  617. first_line = false
  618. end
  619. fd.close
  620. end
  621. [$? == 0, data.chomp]
  622. rescue Timeout::Error, Interrupt => e
  623. if fd && !fd.closed?
  624. pids = [fd.pid]
  625. unless `which pgrep`.empty?
  626. children = pids
  627. until children.empty?
  628. children = children.map { |pid|
  629. `pgrep -P #{pid}`.lines.map { |l| l.chomp }
  630. }.flatten
  631. pids += children
  632. end
  633. end
  634. pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
  635. fd.close
  636. end
  637. if e.is_a?(Timeout::Error) && tried < tries
  638. 3.downto(1) do |countdown|
  639. s = countdown > 1 ? 's' : ''
  640. log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
  641. sleep 1
  642. end
  643. log.call name, 'Retrying ...', type
  644. retry
  645. end
  646. [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
  647. end
  648. }
  649. main = Thread.current
  650. threads = []
  651. watcher = Thread.new {
  652. while VIM::evaluate('getchar(1)')
  653. sleep 0.1
  654. end
  655. mtx.synchronize do
  656. running = false
  657. threads.each { |t| t.raise Interrupt }
  658. end
  659. threads.each { |t| t.join rescue nil }
  660. main.kill
  661. }
  662. processed = Set.new
  663. progress = iswin ? '' : '--progress'
  664. until all.empty?
  665. names = all.keys
  666. processed.merge names
  667. [names.length, nthr].min.times do
  668. mtx.synchronize do
  669. threads << Thread.new {
  670. while pair = take1.call
  671. name = pair.first
  672. dir, uri, branch = pair.last.values_at *%w[dir uri branch]
  673. branch = esc branch
  674. subm = "git submodule update --init --recursive 2>&1"
  675. ok, result =
  676. if File.directory? dir
  677. dir = esc dir
  678. ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url", nil, nil
  679. current_uri = data.lines.to_a.last
  680. if !ret
  681. if data =~ /^Interrupted|^Timeout/
  682. [false, data]
  683. else
  684. [false, [data.chomp, "PlugClean required."].join($/)]
  685. end
  686. elsif current_uri.sub(/git:@/, '') != uri.sub(/git:@/, '')
  687. [false, ["Invalid URI: #{current_uri}",
  688. "Expected: #{uri}",
  689. "PlugClean required."].join($/)]
  690. else
  691. if pull
  692. log.call name, 'Updating ...', :update
  693. bt.call "#{cd} #{dir} && git checkout -q #{branch} 2>&1 && (git pull origin #{branch} #{progress} 2>&1 && #{subm})", name, :update
  694. else
  695. [true, skip]
  696. end
  697. end
  698. else
  699. FileUtils.mkdir_p(base)
  700. d = esc dir.sub(%r{[\\/]+$}, '')
  701. log.call name, 'Installing ...', :install
  702. bt.call "(git clone #{progress} --recursive #{uri} -b #{branch} #{d} 2>&1 && cd #{esc dir} && #{subm})", name, :install
  703. end
  704. log.call name, result, ok
  705. end
  706. } if running
  707. end
  708. end
  709. threads.each { |t| t.join rescue nil }
  710. mtx.synchronize { threads.clear }
  711. extended = Hash[(VIM::evaluate("s:extend(#{names.inspect})") || {}).reject { |k, _|
  712. processed.include? k
  713. }]
  714. tot += extended.length
  715. all.merge!(extended)
  716. logh.call
  717. end
  718. watcher.kill
  719. $curbuf[1] = "Updated. Elapsed time: #{"%.6f" % (Time.now - st)} sec."
  720. EOF
  721. endfunction
  722. function! s:shellesc(arg)
  723. return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
  724. endfunction
  725. function! s:glob_dir(path)
  726. return map(filter(split(globpath(a:path, '**'), '\n'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
  727. endfunction
  728. function! s:progress_bar(line, bar, total)
  729. call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
  730. endfunction
  731. function! s:compare_git_uri(a, b)
  732. let a = substitute(a:a, 'git:@', '', '')
  733. let b = substitute(a:b, 'git:@', '', '')
  734. return a ==# b
  735. endfunction
  736. function! s:format_message(ok, name, message)
  737. if a:ok
  738. return [printf('- %s: %s', a:name, s:lastline(a:message))]
  739. else
  740. let lines = map(split(a:message, '\n'), '" ".v:val')
  741. return extend([printf('x %s:', a:name)], lines)
  742. endif
  743. endfunction
  744. function! s:system(cmd)
  745. return system(s:is_win ? '('.a:cmd.')' : a:cmd)
  746. endfunction
  747. function! s:system_chomp(str)
  748. let ret = s:system(a:str)
  749. return v:shell_error ? '' : substitute(ret, '\n$', '', '')
  750. endfunction
  751. function! s:git_valid(spec, check_branch, cd)
  752. let ret = 1
  753. let msg = 'OK'
  754. if isdirectory(a:spec.dir)
  755. if a:cd | execute "cd " . s:esc(a:spec.dir) | endif
  756. let result = split(s:system("git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url"), '\n')
  757. let remote = result[-1]
  758. if v:shell_error
  759. let msg = join([remote, "PlugClean required."], "\n")
  760. let ret = 0
  761. elseif !s:compare_git_uri(remote, a:spec.uri)
  762. let msg = join(['Invalid URI: '.remote,
  763. \ 'Expected: '.a:spec.uri,
  764. \ "PlugClean required."], "\n")
  765. let ret = 0
  766. elseif a:check_branch
  767. let branch = result[0]
  768. if a:spec.branch !=# branch
  769. let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1')
  770. if a:spec.branch !=# tag
  771. let msg = printf('Invalid branch/tag: %s (expected: %s). Try PlugUpdate.',
  772. \ (empty(tag) ? branch : tag), a:spec.branch)
  773. let ret = 0
  774. endif
  775. endif
  776. endif
  777. if a:cd | cd - | endif
  778. else
  779. let msg = 'Not found'
  780. let ret = 0
  781. endif
  782. return [ret, msg]
  783. endfunction
  784. function! s:clean(force)
  785. call s:prepare()
  786. call append(0, 'Searching for unused plugins in '.g:plug_home)
  787. call append(1, '')
  788. " List of valid directories
  789. let dirs = []
  790. let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
  791. let [cnt, total] = [0, len(managed)]
  792. for spec in values(managed)
  793. if s:git_valid(spec, 0, 1)[0]
  794. call add(dirs, spec.dir)
  795. endif
  796. let cnt += 1
  797. call s:progress_bar(2, repeat('=', cnt), total)
  798. normal! 2G
  799. redraw
  800. endfor
  801. let allowed = {}
  802. for dir in dirs
  803. let allowed[dir] = 1
  804. for child in s:glob_dir(dir)
  805. let allowed[child] = 1
  806. endfor
  807. endfor
  808. let todo = []
  809. let found = sort(s:glob_dir(g:plug_home))
  810. while !empty(found)
  811. let f = remove(found, 0)
  812. if !has_key(allowed, f) && isdirectory(f)
  813. call add(todo, f)
  814. call append(line('$'), '- ' . f)
  815. let found = filter(found, 'stridx(v:val, f) != 0')
  816. end
  817. endwhile
  818. normal! G
  819. redraw
  820. if empty(todo)
  821. call append(line('$'), 'Already clean.')
  822. else
  823. call inputsave()
  824. let yes = a:force || (input("Proceed? (Y/N) ") =~? '^y')
  825. call inputrestore()
  826. if yes
  827. for dir in todo
  828. if isdirectory(dir)
  829. call system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(dir))
  830. endif
  831. endfor
  832. call append(line('$'), 'Removed.')
  833. else
  834. call append(line('$'), 'Cancelled.')
  835. endif
  836. endif
  837. normal! G
  838. endfunction
  839. function! s:upgrade()
  840. if executable('curl')
  841. let mee = s:shellesc(s:me)
  842. let new = s:shellesc(s:me . '.new')
  843. echo "Downloading ". s:plug_source
  844. redraw
  845. let mv = s:is_win ? 'move /Y' : 'mv -f'
  846. let cp = s:is_win ? 'copy /Y' : 'cp -f'
  847. call system(printf(
  848. \ "curl -fLo %s %s && ".cp." %s %s.old && ".mv." %s %s",
  849. \ new, s:plug_source, mee, mee, new, mee))
  850. if v:shell_error == 0
  851. unlet g:loaded_plug
  852. echo "Downloaded ". s:plug_source
  853. return 1
  854. else
  855. echoerr "Error upgrading vim-plug"
  856. return 0
  857. endif
  858. elseif has('ruby')
  859. echo "Downloading ". s:plug_source
  860. ruby << EOF
  861. require 'open-uri'
  862. require 'fileutils'
  863. me = VIM::evaluate('s:me')
  864. old = me + '.old'
  865. new = me + '.new'
  866. File.open(new, 'w') do |f|
  867. f << open(VIM::evaluate('s:plug_source')).read
  868. end
  869. FileUtils.cp me, old
  870. File.rename new, me
  871. EOF
  872. unlet g:loaded_plug
  873. echo "Downloaded ". s:plug_source
  874. return 1
  875. else
  876. echoerr "curl executable or ruby support not found"
  877. return 0
  878. endif
  879. endfunction
  880. function! s:status()
  881. call s:prepare()
  882. call append(0, 'Checking plugins')
  883. call append(1, '')
  884. let ecnt = 0
  885. let [cnt, total] = [0, len(g:plugs)]
  886. for [name, spec] in items(g:plugs)
  887. if has_key(spec, 'uri')
  888. if isdirectory(spec.dir)
  889. let [valid, msg] = s:git_valid(spec, 1, 1)
  890. else
  891. let [valid, msg] = [0, 'Not found. Try PlugInstall.']
  892. endif
  893. else
  894. if isdirectory(spec.dir)
  895. let [valid, msg] = [1, 'OK']
  896. else
  897. let [valid, msg] = [0, 'Not found.']
  898. endif
  899. endif
  900. let cnt += 1
  901. let ecnt += !valid
  902. call s:progress_bar(2, repeat('=', cnt), total)
  903. call append(3, s:format_message(valid, name, msg))
  904. normal! 2G
  905. redraw
  906. endfor
  907. call setline(1, 'Finished. '.ecnt.' error(s).')
  908. normal! gg
  909. endfunction
  910. function! s:is_preview_window_open()
  911. silent! wincmd P
  912. if &previewwindow
  913. wincmd p
  914. return 1
  915. endif
  916. return 0
  917. endfunction
  918. function! s:preview_commit()
  919. if b:plug_preview < 0
  920. let b:plug_preview = !s:is_preview_window_open()
  921. endif
  922. let sha = matchstr(getline('.'), '\(^ \)\@<=[0-9a-z]\{7}')
  923. if !empty(sha)
  924. let lnum = line('.')
  925. while lnum > 1
  926. let lnum -= 1
  927. let line = getline(lnum)
  928. let name = matchstr(line, '\(^- \)\@<=[^:]\+')
  929. if !empty(name)
  930. let dir = g:plugs[name].dir
  931. if isdirectory(dir)
  932. execute 'cd '.s:esc(dir)
  933. execute 'pedit '.sha
  934. wincmd P
  935. setlocal filetype=git buftype=nofile nobuflisted
  936. execute 'silent read !git show '.sha
  937. normal! ggdd
  938. wincmd p
  939. cd -
  940. endif
  941. break
  942. endif
  943. endwhile
  944. endif
  945. endfunction
  946. function! s:section(flags)
  947. call search('\(^- \)\@<=.', a:flags)
  948. endfunction
  949. function! s:diff()
  950. call s:prepare()
  951. call append(0, 'Collecting updated changes ...')
  952. normal! gg
  953. redraw
  954. let cnt = 0
  955. for [k, v] in items(g:plugs)
  956. if !isdirectory(v.dir) || !s:is_managed(k)
  957. continue
  958. endif
  959. execute 'cd '.s:esc(v.dir)
  960. let diff = system('git log --pretty=format:"%h %s (%cr)" "HEAD@{0}...HEAD@{1}"')
  961. if !v:shell_error && !empty(diff)
  962. call append(1, '')
  963. call append(2, '- '.k.':')
  964. call append(3, map(split(diff, '\n'), '" ". v:val'))
  965. let cnt += 1
  966. normal! gg
  967. redraw
  968. endif
  969. cd -
  970. endfor
  971. call setline(1, cnt == 0 ? 'No updates.' : 'Last update:')
  972. nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
  973. normal! gg
  974. endfunction
  975. let s:first_rtp = s:esc(get(split(&rtp, ','), 0, ''))
  976. let s:last_rtp = s:esc(get(split(&rtp, ','), -1, ''))
  977. let &cpo = s:cpo_save
  978. unlet s:cpo_save