plug.vim 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  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. filetype plugin indent on
  164. syntax on
  165. endfunction
  166. if s:is_win
  167. function! s:rtp(spec)
  168. let rtp = s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
  169. return substitute(rtp, '\\*$', '', '')
  170. endfunction
  171. function! s:path(path)
  172. return substitute(substitute(a:path, '/', '\', 'g'), '[/\\]*$', '', '')
  173. endfunction
  174. function! s:dirpath(path)
  175. return s:path(a:path) . '\'
  176. endfunction
  177. else
  178. function! s:rtp(spec)
  179. return s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
  180. endfunction
  181. function! s:path(path)
  182. return substitute(a:path, '[/\\]*$', '', '')
  183. endfunction
  184. function! s:dirpath(path)
  185. return s:path(a:path) . '/'
  186. endfunction
  187. endif
  188. function! s:esc(path)
  189. return substitute(a:path, ' ', '\\ ', 'g')
  190. endfunction
  191. function! s:add_rtp(rtp)
  192. execute "set rtp^=".s:esc(a:rtp)
  193. let after = globpath(a:rtp, 'after')
  194. if isdirectory(after)
  195. execute "set rtp+=".s:esc(after)
  196. endif
  197. endfunction
  198. function! s:lod(plug, types)
  199. let rtp = s:rtp(a:plug)
  200. call s:add_rtp(rtp)
  201. for dir in a:types
  202. for vim in split(globpath(rtp, dir.'/**/*.vim'), '\n')
  203. execute 'source '.vim
  204. endfor
  205. endfor
  206. endfunction
  207. function! s:lod_ft(pat, names)
  208. for name in a:names
  209. call s:lod(g:plugs[name], ['plugin', 'after'])
  210. endfor
  211. execute 'autocmd! PlugLOD FileType ' . a:pat
  212. silent! doautocmd filetypeplugin FileType
  213. endfunction
  214. function! s:lod_cmd(cmd, bang, l1, l2, args, name)
  215. execute 'delc '.a:cmd
  216. call s:lod(g:plugs[a:name], ['plugin', 'ftdetect', 'after'])
  217. execute printf("%s%s%s %s", (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
  218. endfunction
  219. function! s:lod_map(map, name, prefix)
  220. execute 'unmap '.a:map
  221. execute 'iunmap '.a:map
  222. call s:lod(g:plugs[a:name], ['plugin', 'ftdetect', 'after'])
  223. let extra = ''
  224. while 1
  225. let c = getchar(0)
  226. if c == 0
  227. break
  228. endif
  229. let extra .= nr2char(c)
  230. endwhile
  231. call feedkeys(a:prefix . substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
  232. endfunction
  233. function! s:add(...)
  234. let force = a:1
  235. let opts = { 'branch': 'master' }
  236. if a:0 == 2
  237. let plugin = a:2
  238. elseif a:0 == 3
  239. let plugin = a:2
  240. if type(a:3) == 1
  241. let opts.branch = a:3
  242. elseif type(a:3) == 4
  243. call extend(opts, a:3)
  244. else
  245. echoerr "Invalid argument type (expected: string or dictionary)"
  246. return
  247. endif
  248. else
  249. echoerr "Invalid number of arguments (1..2)"
  250. return
  251. endif
  252. let name = substitute(split(plugin, '/')[-1], '\.git$', '', '')
  253. if !force && has_key(g:plugs, name) | return | endif
  254. if plugin =~ ':'
  255. let uri = plugin
  256. else
  257. if plugin !~ '/'
  258. let plugin = 'vim-scripts/'. plugin
  259. endif
  260. let uri = 'https://git:@github.com/' . plugin . '.git'
  261. endif
  262. let dir = s:dirpath( fnamemodify(join([g:plug_home, name], '/'), ':p') )
  263. let spec = extend(opts, { 'dir': dir, 'uri': uri })
  264. let g:plugs[name] = spec
  265. let g:plugs_order += [name]
  266. endfunction
  267. function! s:install(...)
  268. call s:update_impl(0, a:000)
  269. endfunction
  270. function! s:update(...)
  271. call s:update_impl(1, a:000)
  272. endfunction
  273. function! s:apply()
  274. for spec in values(g:plugs)
  275. let docd = join([spec.dir, 'doc'], '/')
  276. if isdirectory(docd)
  277. execute "helptags ". join([spec.dir, 'doc'], '/')
  278. endif
  279. endfor
  280. runtime! plugin/*.vim
  281. runtime! after/*.vim
  282. silent! source $MYVIMRC
  283. endfunction
  284. function! s:syntax()
  285. syntax clear
  286. syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
  287. syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
  288. syn match plugNumber /[0-9]\+[0-9.]*/ contained
  289. syn match plugBracket /[[\]]/ contained
  290. syn match plugX /x/ contained
  291. syn match plugDash /^-/
  292. syn match plugPlus /^+/
  293. syn match plugStar /^*/
  294. syn match plugName /\(^- \)\@<=[^:]*/
  295. syn match plugInstall /\(^+ \)\@<=[^:]*/
  296. syn match plugUpdate /\(^* \)\@<=[^:]*/
  297. syn match plugCommit /^ [0-9a-z]\{7} .*/ contains=plugRelDate,plugSha
  298. syn match plugSha /\(^ \)\@<=[0-9a-z]\{7}/ contained
  299. syn match plugRelDate /([^)]*)$/ contained
  300. syn match plugError /^x.*/
  301. syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
  302. hi def link plug1 Title
  303. hi def link plug2 Repeat
  304. hi def link plugX Exception
  305. hi def link plugBracket Structure
  306. hi def link plugNumber Number
  307. hi def link plugDash Special
  308. hi def link plugPlus Constant
  309. hi def link plugStar Boolean
  310. hi def link plugName Label
  311. hi def link plugInstall Function
  312. hi def link plugUpdate Type
  313. hi def link plugError Error
  314. hi def link plugRelDate Comment
  315. hi def link plugSha Identifier
  316. endfunction
  317. function! s:lpad(str, len)
  318. return a:str . repeat(' ', a:len - len(a:str))
  319. endfunction
  320. function! s:lastline(msg)
  321. let lines = split(a:msg, '\n')
  322. return get(lines, -1, '')
  323. endfunction
  324. function! s:prepare()
  325. if bufexists(s:plug_buf)
  326. let winnr = bufwinnr(s:plug_buf)
  327. if winnr < 0
  328. vertical topleft new
  329. execute 'buffer ' . s:plug_buf
  330. else
  331. execute winnr . 'wincmd w'
  332. endif
  333. silent %d _
  334. else
  335. vertical topleft new
  336. nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>q<cr>
  337. nnoremap <silent> <buffer> D :PlugDiff<cr>
  338. nnoremap <silent> <buffer> S :PlugStatus<cr>
  339. nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
  340. nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
  341. let b:plug_preview = -1
  342. let s:plug_buf = winbufnr(0)
  343. call s:assign_name()
  344. endif
  345. silent! unmap <buffer> <cr>
  346. setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline
  347. setf vim-plug
  348. call s:syntax()
  349. endfunction
  350. function! s:assign_name()
  351. " Assign buffer name
  352. let prefix = '[Plugins]'
  353. let name = prefix
  354. let idx = 2
  355. while bufexists(name)
  356. let name = printf("%s (%s)", prefix, idx)
  357. let idx = idx + 1
  358. endwhile
  359. silent! execute "f ".fnameescape(name)
  360. endfunction
  361. function! s:finish(pull)
  362. call append(3, '- Finishing ... ')
  363. redraw
  364. call s:apply()
  365. call s:syntax()
  366. call setline(4, getline(4) . 'Done!')
  367. normal! gg
  368. redraw
  369. if a:pull
  370. echo "Press 'D' to see the updated changes."
  371. endif
  372. endfunction
  373. function! s:names(...)
  374. return filter(keys(g:plugs), 'stridx(v:val, a:1) == 0')
  375. endfunction
  376. function! s:update_impl(pull, args) abort
  377. let args = copy(a:args)
  378. let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
  379. \ remove(args, -1) : get(g:, 'plug_threads', 16)
  380. let todo = empty(args) ? g:plugs :
  381. \ filter(copy(g:plugs), 'index(args, v:key) >= 0')
  382. if empty(todo)
  383. echohl WarningMsg
  384. echo 'No plugin to '. (a:pull ? 'update' : 'install') . '.'
  385. echohl None
  386. return
  387. endif
  388. call s:prepare()
  389. call append(0, a:pull ? 'Updating plugins' : 'Installing plugins')
  390. call append(1, '['. s:lpad('', len(todo)) .']')
  391. normal! 2G
  392. redraw
  393. let len = len(g:plugs)
  394. if has('ruby') && threads > 1
  395. try
  396. call s:update_parallel(a:pull, todo, threads)
  397. catch
  398. let lines = getline(4, '$')
  399. let printed = {}
  400. silent 4,$d
  401. for line in lines
  402. let name = get(matchlist(line, '^. \([^:]\+\):'), 1, '')
  403. if empty(name) || !has_key(printed, name)
  404. let printed[name] = 1
  405. call append('$', line)
  406. endif
  407. endfor
  408. echoerr v:exception
  409. endtry
  410. else
  411. call s:update_serial(a:pull, todo)
  412. endif
  413. if len(g:plugs) > len
  414. call plug#end()
  415. endif
  416. call s:finish(a:pull)
  417. endfunction
  418. function! s:extend(names)
  419. let prev = copy(g:plugs)
  420. try
  421. command! -nargs=+ Plug call s:add(0, <args>)
  422. for name in a:names
  423. let plugfile = globpath(s:rtp(g:plugs[name]), s:plug_file)
  424. if filereadable(plugfile)
  425. execute "source ". s:esc(plugfile)
  426. endif
  427. endfor
  428. finally
  429. command! -nargs=+ Plug call s:add(1, <args>)
  430. endtry
  431. return filter(copy(g:plugs), '!has_key(prev, v:key)')
  432. endfunction
  433. function! s:update_progress(pull, cnt, bar, total)
  434. call setline(1, (a:pull ? 'Updating' : 'Installing').
  435. \ " plugins (".a:cnt."/".a:total.")")
  436. call s:progress_bar(2, a:bar, a:total)
  437. normal! 2G
  438. redraw
  439. endfunction
  440. function! s:update_serial(pull, todo)
  441. let st = reltime()
  442. let base = g:plug_home
  443. let todo = copy(a:todo)
  444. let total = len(todo)
  445. let done = {}
  446. let bar = ''
  447. while !empty(todo)
  448. for [name, spec] in items(todo)
  449. let done[name] = 1
  450. if isdirectory(spec.dir)
  451. execute 'cd '.s:esc(spec.dir)
  452. let [valid, msg] = s:git_valid(spec, 0, 0)
  453. if valid
  454. let result = a:pull ?
  455. \ s:system(
  456. \ printf('git checkout -q %s 2>&1 && git pull origin %s 2>&1 && git submodule update --init --recursive 2>&1',
  457. \ s:shellesc(spec.branch), s:shellesc(spec.branch))) : 'Already installed'
  458. let error = a:pull ? v:shell_error != 0 : 0
  459. else
  460. let result = msg
  461. let error = 1
  462. endif
  463. else
  464. if !isdirectory(base)
  465. call mkdir(base, 'p')
  466. endif
  467. let result = s:system(
  468. \ printf('git clone --recursive %s -b %s %s 2>&1 && cd %s && git submodule update --init --recursive 2>&1',
  469. \ s:shellesc(spec.uri),
  470. \ s:shellesc(spec.branch),
  471. \ s:shellesc(substitute(spec.dir, '[\/]\+$', '', '')),
  472. \ s:shellesc(spec.dir)))
  473. let error = v:shell_error != 0
  474. endif
  475. cd -
  476. let bar .= error ? 'x' : '='
  477. call append(3, s:format_message(!error, name, result))
  478. call s:update_progress(a:pull, len(done), bar, total)
  479. endfor
  480. if !empty(s:extend(keys(todo)))
  481. let todo = filter(copy(g:plugs), '!has_key(done, v:key)')
  482. let total += len(todo)
  483. call s:update_progress(a:pull, len(done), bar, total)
  484. else
  485. break
  486. endif
  487. endwhile
  488. call setline(1, "Updated. Elapsed time: " . split(reltimestr(reltime(st)))[0] . ' sec.')
  489. endfunction
  490. function! s:update_parallel(pull, todo, threads)
  491. ruby << EOF
  492. module PlugStream
  493. SEP = ["\r", "\n", nil]
  494. def get_line
  495. buffer = ''
  496. loop do
  497. char = readchar rescue return
  498. if SEP.include? char.chr
  499. buffer << $/
  500. break
  501. else
  502. buffer << char
  503. end
  504. end
  505. buffer
  506. end
  507. end unless defined?(PlugStream)
  508. def esc arg
  509. %["#{arg.gsub('"', '\"')}"]
  510. end
  511. st = Time.now
  512. require 'thread'
  513. require 'fileutils'
  514. require 'timeout'
  515. running = true
  516. iswin = VIM::evaluate('s:is_win').to_i == 1
  517. pull = VIM::evaluate('a:pull').to_i == 1
  518. base = VIM::evaluate('g:plug_home')
  519. all = VIM::evaluate('copy(a:todo)')
  520. limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
  521. nthr = VIM::evaluate('a:threads').to_i
  522. maxy = VIM::evaluate('winheight(".")').to_i
  523. cd = iswin ? 'cd /d' : 'cd'
  524. tot = 0
  525. bar = ''
  526. skip = 'Already installed'
  527. mtx = Mutex.new
  528. take1 = proc { mtx.synchronize { running && all.shift } }
  529. logh = proc {
  530. cnt = $curbuf[2][1...-1].strip.length
  531. tot = VIM::evaluate('len(a:todo)') || tot
  532. $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
  533. $curbuf[2] = '[' + bar.ljust(tot) + ']'
  534. VIM::command('normal! 2G')
  535. VIM::command('redraw') unless iswin
  536. }
  537. where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
  538. log = proc { |name, result, type|
  539. mtx.synchronize do
  540. ing = ![true, false].include?(type)
  541. bar += type ? '=' : 'x' unless ing
  542. b = case type
  543. when :install then '+' when :update then '*'
  544. when true, nil then '-' else 'x' end
  545. result =
  546. if type || type.nil?
  547. ["#{b} #{name}: #{result.lines.to_a.last}"]
  548. elsif result =~ /^Interrupted|^Timeout/
  549. ["#{b} #{name}: #{result}"]
  550. else
  551. ["#{b} #{name}"] + result.lines.map { |l| " " << l }
  552. end
  553. if lnum = where.call(name)
  554. $curbuf.delete lnum
  555. lnum = 4 if ing && lnum > maxy
  556. end
  557. result.each_with_index do |line, offset|
  558. $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
  559. end
  560. logh.call
  561. end
  562. }
  563. bt = proc { |cmd, name, type|
  564. begin
  565. fd = nil
  566. data = ''
  567. if iswin
  568. Timeout::timeout(limit) do
  569. tmp = VIM::evaluate('tempname()')
  570. system("#{cmd} > #{tmp}")
  571. data = File.read(tmp).chomp
  572. File.unlink tmp rescue nil
  573. end
  574. else
  575. fd = IO.popen(cmd).extend(PlugStream)
  576. first_line = true
  577. log_prob = 1.0 / nthr
  578. while line = Timeout::timeout(limit) { fd.get_line }
  579. data << line
  580. log.call name, line.chomp, type if name && (first_line || rand < log_prob)
  581. first_line = false
  582. end
  583. fd.close
  584. end
  585. [$? == 0, data.chomp]
  586. rescue Timeout::Error, Interrupt => e
  587. if fd && !fd.closed?
  588. pids = [fd.pid]
  589. unless `which pgrep`.empty?
  590. children = pids
  591. until children.empty?
  592. children = children.map { |pid|
  593. `pgrep -P #{pid}`.lines.map { |l| l.chomp }
  594. }.flatten
  595. pids += children
  596. end
  597. end
  598. pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
  599. fd.close
  600. end
  601. [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
  602. end
  603. }
  604. main = Thread.current
  605. threads = []
  606. watcher = Thread.new {
  607. while VIM::evaluate('getchar(1)')
  608. sleep 0.1
  609. end
  610. mtx.synchronize do
  611. running = false
  612. threads.each { |t| t.raise Interrupt }
  613. end
  614. threads.each { |t| t.join rescue nil }
  615. main.kill
  616. }
  617. progress = iswin ? '' : '--progress'
  618. until all.empty?
  619. names = all.keys
  620. [names.length, nthr].min.times do
  621. mtx.synchronize do
  622. threads << Thread.new {
  623. while pair = take1.call
  624. name = pair.first
  625. dir, uri, branch = pair.last.values_at *%w[dir uri branch]
  626. branch = esc branch
  627. subm = "git submodule update --init --recursive 2>&1"
  628. ok, result =
  629. if File.directory? dir
  630. dir = esc dir
  631. ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url", nil, nil
  632. current_uri = data.lines.to_a.last
  633. if !ret
  634. if data =~ /^Interrupted|^Timeout/
  635. [false, data]
  636. else
  637. [false, [data.chomp, "PlugClean required."].join($/)]
  638. end
  639. elsif current_uri.sub(/git:@/, '') != uri.sub(/git:@/, '')
  640. [false, ["Invalid URI: #{current_uri}",
  641. "Expected: #{uri}",
  642. "PlugClean required."].join($/)]
  643. else
  644. if pull
  645. log.call name, 'Updating ...', :update
  646. bt.call "#{cd} #{dir} && git checkout -q #{branch} 2>&1 && (git pull origin #{branch} #{progress} 2>&1 && #{subm})", name, :update
  647. else
  648. [true, skip]
  649. end
  650. end
  651. else
  652. FileUtils.mkdir_p(base)
  653. d = esc dir.sub(%r{[\\/]+$}, '')
  654. log.call name, 'Installing ...', :install
  655. bt.call "(git clone #{progress} --recursive #{uri} -b #{branch} #{d} 2>&1 && cd #{esc dir} && #{subm})", name, :install
  656. end
  657. log.call name, result, ok
  658. end
  659. } if running
  660. end
  661. end
  662. threads.each { |t| t.join }
  663. mtx.synchronize { threads.clear }
  664. all.merge!(VIM::evaluate("s:extend(#{names.inspect})") || {})
  665. logh.call
  666. end
  667. watcher.kill
  668. $curbuf[1] = "Updated. Elapsed time: #{"%.6f" % (Time.now - st)} sec."
  669. EOF
  670. endfunction
  671. function! s:shellesc(arg)
  672. return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
  673. endfunction
  674. function! s:glob_dir(path)
  675. return map(filter(split(globpath(a:path, '**'), '\n'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
  676. endfunction
  677. function! s:progress_bar(line, bar, total)
  678. call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
  679. endfunction
  680. function! s:compare_git_uri(a, b)
  681. let a = substitute(a:a, 'git:@', '', '')
  682. let b = substitute(a:b, 'git:@', '', '')
  683. return a ==# b
  684. endfunction
  685. function! s:format_message(ok, name, message)
  686. if a:ok
  687. return [printf('- %s: %s', a:name, s:lastline(a:message))]
  688. else
  689. let lines = map(split(a:message, '\n'), '" ".v:val')
  690. return extend([printf('x %s:', a:name)], lines)
  691. endif
  692. endfunction
  693. function! s:system(cmd)
  694. return system(s:is_win ? '('.a:cmd.')' : a:cmd)
  695. endfunction
  696. function! s:system_chomp(str)
  697. let ret = s:system(a:str)
  698. return v:shell_error ? '' : substitute(ret, '\n$', '', '')
  699. endfunction
  700. function! s:git_valid(spec, check_branch, cd)
  701. let ret = 1
  702. let msg = 'OK'
  703. if isdirectory(a:spec.dir)
  704. if a:cd | execute "cd " . s:esc(a:spec.dir) | endif
  705. let result = split(s:system("git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url"), '\n')
  706. let remote = result[-1]
  707. if v:shell_error
  708. let msg = join([remote, "PlugClean required."], "\n")
  709. let ret = 0
  710. elseif !s:compare_git_uri(remote, a:spec.uri)
  711. let msg = join(['Invalid URI: '.remote,
  712. \ 'Expected: '.a:spec.uri,
  713. \ "PlugClean required."], "\n")
  714. let ret = 0
  715. elseif a:check_branch
  716. let branch = result[0]
  717. if a:spec.branch !=# branch
  718. let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1')
  719. if a:spec.branch !=# tag
  720. let msg = printf('Invalid branch/tag: %s (expected: %s). Try PlugUpdate.',
  721. \ (empty(tag) ? branch : tag), a:spec.branch)
  722. let ret = 0
  723. endif
  724. endif
  725. endif
  726. if a:cd | cd - | endif
  727. else
  728. let msg = 'Not found'
  729. let ret = 0
  730. endif
  731. return [ret, msg]
  732. endfunction
  733. function! s:clean(force)
  734. call s:prepare()
  735. call append(0, 'Searching for unused plugins in '.g:plug_home)
  736. call append(1, '')
  737. " List of valid directories
  738. let dirs = []
  739. let [cnt, total] = [0, len(g:plugs)]
  740. for spec in values(g:plugs)
  741. if s:git_valid(spec, 0, 1)[0]
  742. call add(dirs, spec.dir)
  743. endif
  744. let cnt += 1
  745. call s:progress_bar(2, repeat('=', cnt), total)
  746. normal! 2G
  747. redraw
  748. endfor
  749. let allowed = {}
  750. for dir in dirs
  751. let allowed[dir] = 1
  752. for child in s:glob_dir(dir)
  753. let allowed[child] = 1
  754. endfor
  755. endfor
  756. let todo = []
  757. let found = sort(s:glob_dir(g:plug_home))
  758. while !empty(found)
  759. let f = remove(found, 0)
  760. if !has_key(allowed, f) && isdirectory(f)
  761. call add(todo, f)
  762. call append(line('$'), '- ' . f)
  763. let found = filter(found, 'stridx(v:val, f) != 0')
  764. end
  765. endwhile
  766. normal! G
  767. redraw
  768. if empty(todo)
  769. call append(line('$'), 'Already clean.')
  770. else
  771. call inputsave()
  772. let yes = a:force || (input("Proceed? (Y/N) ") =~? '^y')
  773. call inputrestore()
  774. if yes
  775. for dir in todo
  776. if isdirectory(dir)
  777. call system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(dir))
  778. endif
  779. endfor
  780. call append(line('$'), 'Removed.')
  781. else
  782. call append(line('$'), 'Cancelled.')
  783. endif
  784. endif
  785. normal! G
  786. endfunction
  787. function! s:upgrade()
  788. if executable('curl')
  789. let mee = s:shellesc(s:me)
  790. let new = s:shellesc(s:me . '.new')
  791. echo "Downloading ". s:plug_source
  792. redraw
  793. let mv = s:is_win ? 'move /Y' : 'mv -f'
  794. let cp = s:is_win ? 'copy /Y' : 'cp -f'
  795. call system(printf(
  796. \ "curl -fLo %s %s && ".cp." %s %s.old && ".mv." %s %s",
  797. \ new, s:plug_source, mee, mee, new, mee))
  798. if v:shell_error == 0
  799. unlet g:loaded_plug
  800. echo "Downloaded ". s:plug_source
  801. return 1
  802. else
  803. echoerr "Error upgrading vim-plug"
  804. return 0
  805. endif
  806. elseif has('ruby')
  807. echo "Downloading ". s:plug_source
  808. ruby << EOF
  809. require 'open-uri'
  810. require 'fileutils'
  811. me = VIM::evaluate('s:me')
  812. old = me + '.old'
  813. new = me + '.new'
  814. File.open(new, 'w') do |f|
  815. f << open(VIM::evaluate('s:plug_source')).read
  816. end
  817. FileUtils.cp me, old
  818. File.rename new, me
  819. EOF
  820. unlet g:loaded_plug
  821. echo "Downloaded ". s:plug_source
  822. return 1
  823. else
  824. echoerr "curl executable or ruby support not found"
  825. return 0
  826. endif
  827. endfunction
  828. function! s:status()
  829. call s:prepare()
  830. call append(0, 'Checking plugins')
  831. call append(1, '')
  832. let ecnt = 0
  833. let [cnt, total] = [0, len(g:plugs)]
  834. for [name, spec] in items(g:plugs)
  835. if isdirectory(spec.dir)
  836. let [valid, msg] = s:git_valid(spec, 1, 1)
  837. else
  838. let [valid, msg] = [0, 'Not found. Try PlugInstall.']
  839. endif
  840. let cnt += 1
  841. let ecnt += !valid
  842. call s:progress_bar(2, repeat('=', cnt), total)
  843. call append(3, s:format_message(valid, name, msg))
  844. normal! 2G
  845. redraw
  846. endfor
  847. call setline(1, 'Finished. '.ecnt.' error(s).')
  848. normal! gg
  849. endfunction
  850. function! s:is_preview_window_open()
  851. silent! wincmd P
  852. if &previewwindow
  853. wincmd p
  854. return 1
  855. endif
  856. return 0
  857. endfunction
  858. function! s:preview_commit()
  859. if b:plug_preview < 0
  860. let b:plug_preview = !s:is_preview_window_open()
  861. endif
  862. let sha = matchstr(getline('.'), '\(^ \)\@<=[0-9a-z]\{7}')
  863. if !empty(sha)
  864. let lnum = line('.')
  865. while lnum > 1
  866. let lnum -= 1
  867. let line = getline(lnum)
  868. let name = matchstr(line, '\(^- \)\@<=[^:]\+')
  869. if !empty(name)
  870. let dir = g:plugs[name].dir
  871. if isdirectory(dir)
  872. execute 'cd '.s:esc(dir)
  873. execute 'pedit '.sha
  874. wincmd P
  875. setlocal filetype=git buftype=nofile nobuflisted
  876. execute 'silent read !git show '.sha
  877. normal! ggdd
  878. wincmd p
  879. cd -
  880. endif
  881. break
  882. endif
  883. endwhile
  884. endif
  885. endfunction
  886. function! s:section(flags)
  887. call search('\(^- \)\@<=.', a:flags)
  888. endfunction
  889. function! s:diff()
  890. call s:prepare()
  891. call append(0, 'Collecting updated changes ...')
  892. normal! gg
  893. redraw
  894. let cnt = 0
  895. for [k, v] in items(g:plugs)
  896. if !isdirectory(v.dir)
  897. continue
  898. endif
  899. execute 'cd '.s:esc(v.dir)
  900. let diff = system('git log --pretty=format:"%h %s (%cr)" "HEAD@{0}...HEAD@{1}"')
  901. if !v:shell_error && !empty(diff)
  902. call append(1, '')
  903. call append(2, '- '.k.':')
  904. call append(3, map(split(diff, '\n'), '" ". v:val'))
  905. let cnt += 1
  906. normal! gg
  907. redraw
  908. endif
  909. cd -
  910. endfor
  911. call setline(1, cnt == 0 ? 'No updates.' : 'Last update:')
  912. nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
  913. normal! gg
  914. endfunction
  915. let &cpo = s:cpo_save
  916. unlet s:cpo_save