plug.vim 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057
  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. retry if e.is_a?(Timeout::Error) && tried < tries
  638. [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
  639. end
  640. }
  641. main = Thread.current
  642. threads = []
  643. watcher = Thread.new {
  644. while VIM::evaluate('getchar(1)')
  645. sleep 0.1
  646. end
  647. mtx.synchronize do
  648. running = false
  649. threads.each { |t| t.raise Interrupt }
  650. end
  651. threads.each { |t| t.join rescue nil }
  652. main.kill
  653. }
  654. processed = Set.new
  655. progress = iswin ? '' : '--progress'
  656. until all.empty?
  657. names = all.keys
  658. processed.merge names
  659. [names.length, nthr].min.times do
  660. mtx.synchronize do
  661. threads << Thread.new {
  662. while pair = take1.call
  663. name = pair.first
  664. dir, uri, branch = pair.last.values_at *%w[dir uri branch]
  665. branch = esc branch
  666. subm = "git submodule update --init --recursive 2>&1"
  667. ok, result =
  668. if File.directory? dir
  669. dir = esc dir
  670. ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url", nil, nil
  671. current_uri = data.lines.to_a.last
  672. if !ret
  673. if data =~ /^Interrupted|^Timeout/
  674. [false, data]
  675. else
  676. [false, [data.chomp, "PlugClean required."].join($/)]
  677. end
  678. elsif current_uri.sub(/git:@/, '') != uri.sub(/git:@/, '')
  679. [false, ["Invalid URI: #{current_uri}",
  680. "Expected: #{uri}",
  681. "PlugClean required."].join($/)]
  682. else
  683. if pull
  684. log.call name, 'Updating ...', :update
  685. bt.call "#{cd} #{dir} && git checkout -q #{branch} 2>&1 && (git pull origin #{branch} #{progress} 2>&1 && #{subm})", name, :update
  686. else
  687. [true, skip]
  688. end
  689. end
  690. else
  691. FileUtils.mkdir_p(base)
  692. d = esc dir.sub(%r{[\\/]+$}, '')
  693. log.call name, 'Installing ...', :install
  694. bt.call "(git clone #{progress} --recursive #{uri} -b #{branch} #{d} 2>&1 && cd #{esc dir} && #{subm})", name, :install
  695. end
  696. log.call name, result, ok
  697. end
  698. } if running
  699. end
  700. end
  701. threads.each { |t| t.join rescue nil }
  702. mtx.synchronize { threads.clear }
  703. extended = Hash[(VIM::evaluate("s:extend(#{names.inspect})") || {}).reject { |k, _|
  704. processed.include? k
  705. }]
  706. tot += extended.length
  707. all.merge!(extended)
  708. logh.call
  709. end
  710. watcher.kill
  711. $curbuf[1] = "Updated. Elapsed time: #{"%.6f" % (Time.now - st)} sec."
  712. EOF
  713. endfunction
  714. function! s:shellesc(arg)
  715. return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
  716. endfunction
  717. function! s:glob_dir(path)
  718. return map(filter(split(globpath(a:path, '**'), '\n'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
  719. endfunction
  720. function! s:progress_bar(line, bar, total)
  721. call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
  722. endfunction
  723. function! s:compare_git_uri(a, b)
  724. let a = substitute(a:a, 'git:@', '', '')
  725. let b = substitute(a:b, 'git:@', '', '')
  726. return a ==# b
  727. endfunction
  728. function! s:format_message(ok, name, message)
  729. if a:ok
  730. return [printf('- %s: %s', a:name, s:lastline(a:message))]
  731. else
  732. let lines = map(split(a:message, '\n'), '" ".v:val')
  733. return extend([printf('x %s:', a:name)], lines)
  734. endif
  735. endfunction
  736. function! s:system(cmd)
  737. return system(s:is_win ? '('.a:cmd.')' : a:cmd)
  738. endfunction
  739. function! s:system_chomp(str)
  740. let ret = s:system(a:str)
  741. return v:shell_error ? '' : substitute(ret, '\n$', '', '')
  742. endfunction
  743. function! s:git_valid(spec, check_branch, cd)
  744. let ret = 1
  745. let msg = 'OK'
  746. if isdirectory(a:spec.dir)
  747. if a:cd | execute "cd " . s:esc(a:spec.dir) | endif
  748. let result = split(s:system("git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url"), '\n')
  749. let remote = result[-1]
  750. if v:shell_error
  751. let msg = join([remote, "PlugClean required."], "\n")
  752. let ret = 0
  753. elseif !s:compare_git_uri(remote, a:spec.uri)
  754. let msg = join(['Invalid URI: '.remote,
  755. \ 'Expected: '.a:spec.uri,
  756. \ "PlugClean required."], "\n")
  757. let ret = 0
  758. elseif a:check_branch
  759. let branch = result[0]
  760. if a:spec.branch !=# branch
  761. let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1')
  762. if a:spec.branch !=# tag
  763. let msg = printf('Invalid branch/tag: %s (expected: %s). Try PlugUpdate.',
  764. \ (empty(tag) ? branch : tag), a:spec.branch)
  765. let ret = 0
  766. endif
  767. endif
  768. endif
  769. if a:cd | cd - | endif
  770. else
  771. let msg = 'Not found'
  772. let ret = 0
  773. endif
  774. return [ret, msg]
  775. endfunction
  776. function! s:clean(force)
  777. call s:prepare()
  778. call append(0, 'Searching for unused plugins in '.g:plug_home)
  779. call append(1, '')
  780. " List of valid directories
  781. let dirs = []
  782. let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
  783. let [cnt, total] = [0, len(managed)]
  784. for spec in values(managed)
  785. if s:git_valid(spec, 0, 1)[0]
  786. call add(dirs, spec.dir)
  787. endif
  788. let cnt += 1
  789. call s:progress_bar(2, repeat('=', cnt), total)
  790. normal! 2G
  791. redraw
  792. endfor
  793. let allowed = {}
  794. for dir in dirs
  795. let allowed[dir] = 1
  796. for child in s:glob_dir(dir)
  797. let allowed[child] = 1
  798. endfor
  799. endfor
  800. let todo = []
  801. let found = sort(s:glob_dir(g:plug_home))
  802. while !empty(found)
  803. let f = remove(found, 0)
  804. if !has_key(allowed, f) && isdirectory(f)
  805. call add(todo, f)
  806. call append(line('$'), '- ' . f)
  807. let found = filter(found, 'stridx(v:val, f) != 0')
  808. end
  809. endwhile
  810. normal! G
  811. redraw
  812. if empty(todo)
  813. call append(line('$'), 'Already clean.')
  814. else
  815. call inputsave()
  816. let yes = a:force || (input("Proceed? (Y/N) ") =~? '^y')
  817. call inputrestore()
  818. if yes
  819. for dir in todo
  820. if isdirectory(dir)
  821. call system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(dir))
  822. endif
  823. endfor
  824. call append(line('$'), 'Removed.')
  825. else
  826. call append(line('$'), 'Cancelled.')
  827. endif
  828. endif
  829. normal! G
  830. endfunction
  831. function! s:upgrade()
  832. if executable('curl')
  833. let mee = s:shellesc(s:me)
  834. let new = s:shellesc(s:me . '.new')
  835. echo "Downloading ". s:plug_source
  836. redraw
  837. let mv = s:is_win ? 'move /Y' : 'mv -f'
  838. let cp = s:is_win ? 'copy /Y' : 'cp -f'
  839. call system(printf(
  840. \ "curl -fLo %s %s && ".cp." %s %s.old && ".mv." %s %s",
  841. \ new, s:plug_source, mee, mee, new, mee))
  842. if v:shell_error == 0
  843. unlet g:loaded_plug
  844. echo "Downloaded ". s:plug_source
  845. return 1
  846. else
  847. echoerr "Error upgrading vim-plug"
  848. return 0
  849. endif
  850. elseif has('ruby')
  851. echo "Downloading ". s:plug_source
  852. ruby << EOF
  853. require 'open-uri'
  854. require 'fileutils'
  855. me = VIM::evaluate('s:me')
  856. old = me + '.old'
  857. new = me + '.new'
  858. File.open(new, 'w') do |f|
  859. f << open(VIM::evaluate('s:plug_source')).read
  860. end
  861. FileUtils.cp me, old
  862. File.rename new, me
  863. EOF
  864. unlet g:loaded_plug
  865. echo "Downloaded ". s:plug_source
  866. return 1
  867. else
  868. echoerr "curl executable or ruby support not found"
  869. return 0
  870. endif
  871. endfunction
  872. function! s:status()
  873. call s:prepare()
  874. call append(0, 'Checking plugins')
  875. call append(1, '')
  876. let ecnt = 0
  877. let [cnt, total] = [0, len(g:plugs)]
  878. for [name, spec] in items(g:plugs)
  879. if has_key(spec, 'uri')
  880. if isdirectory(spec.dir)
  881. let [valid, msg] = s:git_valid(spec, 1, 1)
  882. else
  883. let [valid, msg] = [0, 'Not found. Try PlugInstall.']
  884. endif
  885. else
  886. if isdirectory(spec.dir)
  887. let [valid, msg] = [1, 'OK']
  888. else
  889. let [valid, msg] = [0, 'Not found.']
  890. endif
  891. endif
  892. let cnt += 1
  893. let ecnt += !valid
  894. call s:progress_bar(2, repeat('=', cnt), total)
  895. call append(3, s:format_message(valid, name, msg))
  896. normal! 2G
  897. redraw
  898. endfor
  899. call setline(1, 'Finished. '.ecnt.' error(s).')
  900. normal! gg
  901. endfunction
  902. function! s:is_preview_window_open()
  903. silent! wincmd P
  904. if &previewwindow
  905. wincmd p
  906. return 1
  907. endif
  908. return 0
  909. endfunction
  910. function! s:preview_commit()
  911. if b:plug_preview < 0
  912. let b:plug_preview = !s:is_preview_window_open()
  913. endif
  914. let sha = matchstr(getline('.'), '\(^ \)\@<=[0-9a-z]\{7}')
  915. if !empty(sha)
  916. let lnum = line('.')
  917. while lnum > 1
  918. let lnum -= 1
  919. let line = getline(lnum)
  920. let name = matchstr(line, '\(^- \)\@<=[^:]\+')
  921. if !empty(name)
  922. let dir = g:plugs[name].dir
  923. if isdirectory(dir)
  924. execute 'cd '.s:esc(dir)
  925. execute 'pedit '.sha
  926. wincmd P
  927. setlocal filetype=git buftype=nofile nobuflisted
  928. execute 'silent read !git show '.sha
  929. normal! ggdd
  930. wincmd p
  931. cd -
  932. endif
  933. break
  934. endif
  935. endwhile
  936. endif
  937. endfunction
  938. function! s:section(flags)
  939. call search('\(^- \)\@<=.', a:flags)
  940. endfunction
  941. function! s:diff()
  942. call s:prepare()
  943. call append(0, 'Collecting updated changes ...')
  944. normal! gg
  945. redraw
  946. let cnt = 0
  947. for [k, v] in items(g:plugs)
  948. if !isdirectory(v.dir) || !s:is_managed(k)
  949. continue
  950. endif
  951. execute 'cd '.s:esc(v.dir)
  952. let diff = system('git log --pretty=format:"%h %s (%cr)" "HEAD@{0}...HEAD@{1}"')
  953. if !v:shell_error && !empty(diff)
  954. call append(1, '')
  955. call append(2, '- '.k.':')
  956. call append(3, map(split(diff, '\n'), '" ". v:val'))
  957. let cnt += 1
  958. normal! gg
  959. redraw
  960. endif
  961. cd -
  962. endfor
  963. call setline(1, cnt == 0 ? 'No updates.' : 'Last update:')
  964. nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
  965. normal! gg
  966. endfunction
  967. let s:first_rtp = s:esc(get(split(&rtp, ','), 0, ''))
  968. let s:last_rtp = s:esc(get(split(&rtp, ','), -1, ''))
  969. let &cpo = s:cpo_save
  970. unlet s:cpo_save