plug.vim 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  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 plugName /\(^- \)\@<=[^:]*/
  293. syn match plugCommit /^ [0-9a-z]\{7} .*/ contains=plugRelDate,plugSha
  294. syn match plugSha /\(^ \)\@<=[0-9a-z]\{7}/ contained
  295. syn match plugRelDate /([^)]*)$/ contained
  296. syn match plugError /^x.*/
  297. syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
  298. hi def link plug1 Title
  299. hi def link plug2 Repeat
  300. hi def link plugX Exception
  301. hi def link plugBracket Structure
  302. hi def link plugNumber Number
  303. hi def link plugDash Special
  304. hi def link plugName Label
  305. hi def link plugError Error
  306. hi def link plugRelDate Comment
  307. hi def link plugSha Identifier
  308. endfunction
  309. function! s:lpad(str, len)
  310. return a:str . repeat(' ', a:len - len(a:str))
  311. endfunction
  312. function! s:lastline(msg)
  313. let lines = split(a:msg, '\n')
  314. return get(lines, -1, '')
  315. endfunction
  316. function! s:prepare()
  317. if bufexists(s:plug_buf)
  318. let winnr = bufwinnr(s:plug_buf)
  319. if winnr < 0
  320. vertical topleft new
  321. execute 'buffer ' . s:plug_buf
  322. else
  323. execute winnr . 'wincmd w'
  324. endif
  325. silent %d _
  326. else
  327. vertical topleft new
  328. nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>q<cr>
  329. nnoremap <silent> <buffer> D :PlugDiff<cr>
  330. nnoremap <silent> <buffer> S :PlugStatus<cr>
  331. nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
  332. nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
  333. let b:plug_preview = -1
  334. let s:plug_buf = winbufnr(0)
  335. call s:assign_name()
  336. endif
  337. silent! unmap <buffer> <cr>
  338. setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline
  339. setf vim-plug
  340. call s:syntax()
  341. endfunction
  342. function! s:assign_name()
  343. " Assign buffer name
  344. let prefix = '[Plugins]'
  345. let name = prefix
  346. let idx = 2
  347. while bufexists(name)
  348. let name = printf("%s (%s)", prefix, idx)
  349. let idx = idx + 1
  350. endwhile
  351. silent! execute "f ".fnameescape(name)
  352. endfunction
  353. function! s:finish(pull)
  354. call append(3, '- Finishing ... ')
  355. redraw
  356. call s:apply()
  357. call s:syntax()
  358. call setline(4, getline(4) . 'Done!')
  359. normal! gg
  360. redraw
  361. if a:pull
  362. echo "Press 'D' to see the updated changes."
  363. endif
  364. endfunction
  365. function! s:names(...)
  366. return filter(keys(g:plugs), 'stridx(v:val, a:1) == 0')
  367. endfunction
  368. function! s:update_impl(pull, args) abort
  369. let args = copy(a:args)
  370. let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
  371. \ remove(args, -1) : get(g:, 'plug_threads', 16)
  372. let todo = empty(args) ? g:plugs :
  373. \ filter(copy(g:plugs), 'index(args, v:key) >= 0')
  374. if empty(todo)
  375. echohl WarningMsg
  376. echo 'No plugin to '. (a:pull ? 'update' : 'install') . '.'
  377. echohl None
  378. return
  379. endif
  380. call s:prepare()
  381. call append(0, a:pull ? 'Updating plugins' : 'Installing plugins')
  382. call append(1, '['. s:lpad('', len(todo)) .']')
  383. normal! 2G
  384. redraw
  385. let len = len(g:plugs)
  386. if has('ruby') && threads > 1
  387. call s:update_parallel(a:pull, todo, threads)
  388. else
  389. call s:update_serial(a:pull, todo)
  390. endif
  391. if len(g:plugs) > len
  392. call plug#end()
  393. endif
  394. call s:finish(a:pull)
  395. endfunction
  396. function! s:extend(names)
  397. let prev = copy(g:plugs)
  398. try
  399. command! -nargs=+ Plug call s:add(0, <args>)
  400. for name in a:names
  401. let plugfile = globpath(s:rtp(g:plugs[name]), s:plug_file)
  402. if filereadable(plugfile)
  403. execute "source ". s:esc(plugfile)
  404. endif
  405. endfor
  406. finally
  407. command! -nargs=+ Plug call s:add(1, <args>)
  408. endtry
  409. return filter(copy(g:plugs), '!has_key(prev, v:key)')
  410. endfunction
  411. function! s:update_progress(pull, cnt, bar, total)
  412. call setline(1, (a:pull ? 'Updating' : 'Installing').
  413. \ " plugins (".a:cnt."/".a:total.")")
  414. call s:progress_bar(2, a:bar, a:total)
  415. normal! 2G
  416. redraw
  417. endfunction
  418. function! s:update_serial(pull, todo)
  419. let st = reltime()
  420. let base = g:plug_home
  421. let todo = copy(a:todo)
  422. let total = len(todo)
  423. let done = {}
  424. let bar = ''
  425. while !empty(todo)
  426. for [name, spec] in items(todo)
  427. let done[name] = 1
  428. if isdirectory(spec.dir)
  429. execute 'cd '.s:esc(spec.dir)
  430. let [valid, msg] = s:git_valid(spec, 0, 0)
  431. if valid
  432. let result = a:pull ?
  433. \ s:system(
  434. \ printf('git checkout -q %s 2>&1 && git pull origin %s 2>&1 && git submodule update --init --recursive 2>&1',
  435. \ s:shellesc(spec.branch), s:shellesc(spec.branch))) : 'Already installed'
  436. let error = a:pull ? v:shell_error != 0 : 0
  437. else
  438. let result = msg
  439. let error = 1
  440. endif
  441. else
  442. if !isdirectory(base)
  443. call mkdir(base, 'p')
  444. endif
  445. execute 'cd '.base
  446. let result = s:system(
  447. \ printf('git clone --recursive %s -b %s %s 2>&1 && cd %s && git submodule update --init --recursive 2>&1',
  448. \ s:shellesc(spec.uri),
  449. \ s:shellesc(spec.branch),
  450. \ s:shellesc(substitute(spec.dir, '[\/]\+$', '', '')),
  451. \ s:shellesc(spec.dir)))
  452. let error = v:shell_error != 0
  453. endif
  454. cd -
  455. let bar .= error ? 'x' : '='
  456. call append(3, s:format_message(!error, name, result))
  457. call s:update_progress(a:pull, len(done), bar, total)
  458. endfor
  459. if !empty(s:extend(keys(todo)))
  460. let todo = filter(copy(g:plugs), '!has_key(done, v:key)')
  461. let total += len(todo)
  462. call s:update_progress(a:pull, len(done), bar, total)
  463. else
  464. break
  465. endif
  466. endwhile
  467. call setline(1, "Updated. Elapsed time: " . split(reltimestr(reltime(st)))[0] . ' sec.')
  468. endfunction
  469. function! s:update_parallel(pull, todo, threads)
  470. ruby << EOF
  471. def esc arg
  472. %["#{arg.gsub('"', '\"')}"]
  473. end
  474. st = Time.now
  475. require 'thread'
  476. require 'fileutils'
  477. require 'timeout'
  478. running = true
  479. iswin = VIM::evaluate('s:is_win').to_i == 1
  480. pull = VIM::evaluate('a:pull').to_i == 1
  481. base = VIM::evaluate('g:plug_home')
  482. all = VIM::evaluate('copy(a:todo)')
  483. limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
  484. nthr = VIM::evaluate('a:threads').to_i
  485. cd = iswin ? 'cd /d' : 'cd'
  486. done = {}
  487. tot = 0
  488. bar = ''
  489. skip = 'Already installed'
  490. mtx = Mutex.new
  491. take1 = proc { mtx.synchronize { running && all.shift } }
  492. logh = proc {
  493. cnt = done.length
  494. tot = VIM::evaluate('len(a:todo)') || tot
  495. $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
  496. $curbuf[2] = '[' + bar.ljust(tot) + ']'
  497. VIM::command('normal! 2G')
  498. VIM::command('redraw') unless iswin
  499. }
  500. log = proc { |name, result, ok|
  501. mtx.synchronize do
  502. bar += ok ? '=' : 'x'
  503. done[name] = true
  504. result =
  505. if ok
  506. ["- #{name}: #{result.lines.to_a.last.strip}"]
  507. elsif result =~ /^Interrupted|^Timeout/
  508. ["x #{name}: #{result}"]
  509. else
  510. ["x #{name}"] + result.lines.map { |l| " " << l }
  511. end
  512. result.each_with_index do |line, offset|
  513. $curbuf.append 3 + offset, line.chomp
  514. end
  515. logh.call
  516. end
  517. }
  518. bt = proc { |cmd|
  519. begin
  520. fd = nil
  521. Timeout::timeout(limit) do
  522. if iswin
  523. tmp = VIM::evaluate('tempname()')
  524. system("#{cmd} > #{tmp}")
  525. data = File.read(tmp).chomp
  526. File.unlink tmp rescue nil
  527. else
  528. fd = IO.popen(cmd)
  529. data = fd.read.chomp
  530. fd.close
  531. end
  532. [$? == 0, data]
  533. end
  534. rescue Timeout::Error, Interrupt => e
  535. if fd && !fd.closed?
  536. pids = [fd.pid]
  537. unless `which pgrep`.empty?
  538. children = pids
  539. until children.empty?
  540. children = children.map { |pid|
  541. `pgrep -P #{pid}`.lines.map(&:chomp)
  542. }.flatten
  543. pids += children
  544. end
  545. end
  546. pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
  547. fd.close
  548. end
  549. [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
  550. end
  551. }
  552. main = Thread.current
  553. threads = []
  554. watcher = Thread.new {
  555. while VIM::evaluate('getchar(1)')
  556. sleep 0.1
  557. end
  558. mtx.synchronize do
  559. running = false
  560. threads.each { |t| t.raise Interrupt }
  561. end
  562. threads.each { |t| t.join rescue nil }
  563. main.kill
  564. }
  565. until all.empty?
  566. names = all.keys
  567. [names.length, nthr].min.times do
  568. mtx.synchronize do
  569. threads << Thread.new {
  570. while pair = take1.call
  571. name = pair.first
  572. dir, uri, branch = pair.last.values_at *%w[dir uri branch]
  573. branch = esc branch
  574. ok, result =
  575. if File.directory? dir
  576. dir = esc dir
  577. ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url"
  578. current_uri = data.lines.to_a.last
  579. if !ret
  580. if data =~ /^Interrupted|^Timeout/
  581. [false, data]
  582. else
  583. [false, [data.chomp, "PlugClean required."].join($/)]
  584. end
  585. elsif current_uri.sub(/git:@/, '') != uri.sub(/git:@/, '')
  586. [false, ["Invalid URI: #{current_uri}",
  587. "Expected: #{uri}",
  588. "PlugClean required."].join($/)]
  589. else
  590. if pull
  591. bt.call "#{cd} #{dir} && git checkout -q #{branch} 2>&1 && git pull origin #{branch} 2>&1 && git submodule update --init --recursive 2>&1"
  592. else
  593. [true, skip]
  594. end
  595. end
  596. else
  597. FileUtils.mkdir_p(base)
  598. d = esc dir.sub(%r{[\\/]+$}, '')
  599. bt.call "#{cd} #{base} && git clone --recursive #{uri} -b #{branch} #{d} 2>&1 && cd #{esc dir} && git submodule update --init --recursive 2>&1"
  600. end
  601. log.call name, result, ok
  602. end
  603. } if running
  604. end
  605. end
  606. threads.each(&:join)
  607. mtx.synchronize { threads.clear }
  608. all.merge!(VIM::evaluate("s:extend(#{names.inspect})") || {})
  609. logh.call
  610. end
  611. watcher.kill
  612. $curbuf[1] = "Updated. Elapsed time: #{"%.6f" % (Time.now - st)} sec."
  613. EOF
  614. endfunction
  615. function! s:shellesc(arg)
  616. return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
  617. endfunction
  618. function! s:glob_dir(path)
  619. return map(filter(split(globpath(a:path, '**'), '\n'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
  620. endfunction
  621. function! s:progress_bar(line, bar, total)
  622. call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
  623. endfunction
  624. function! s:compare_git_uri(a, b)
  625. let a = substitute(a:a, 'git:@', '', '')
  626. let b = substitute(a:b, 'git:@', '', '')
  627. return a ==# b
  628. endfunction
  629. function! s:format_message(ok, name, message)
  630. if a:ok
  631. return [printf('- %s: %s', a:name, s:lastline(a:message))]
  632. else
  633. let lines = map(split(a:message, '\n'), '" ".v:val')
  634. return extend([printf('x %s:', a:name)], lines)
  635. endif
  636. endfunction
  637. function! s:system(cmd)
  638. return system(s:is_win ? '('.a:cmd.')' : a:cmd)
  639. endfunction
  640. function! s:system_chomp(str)
  641. let ret = s:system(a:str)
  642. return v:shell_error ? '' : substitute(ret, '\n$', '', '')
  643. endfunction
  644. function! s:git_valid(spec, check_branch, cd)
  645. let ret = 1
  646. let msg = 'OK'
  647. if isdirectory(a:spec.dir)
  648. if a:cd | execute "cd " . s:esc(a:spec.dir) | endif
  649. let result = split(s:system("git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url"), '\n')
  650. let remote = result[-1]
  651. if v:shell_error
  652. let msg = join([remote, "PlugClean required."], "\n")
  653. let ret = 0
  654. elseif !s:compare_git_uri(remote, a:spec.uri)
  655. let msg = join(['Invalid URI: '.remote,
  656. \ 'Expected: '.a:spec.uri,
  657. \ "PlugClean required."], "\n")
  658. let ret = 0
  659. elseif a:check_branch
  660. let branch = result[0]
  661. if a:spec.branch !=# branch
  662. let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1')
  663. if a:spec.branch !=# tag
  664. let msg = printf('Invalid branch/tag: %s (expected: %s). Try PlugUpdate.',
  665. \ (empty(tag) ? branch : tag), a:spec.branch)
  666. let ret = 0
  667. endif
  668. endif
  669. endif
  670. if a:cd | cd - | endif
  671. else
  672. let msg = 'Not found'
  673. let ret = 0
  674. endif
  675. return [ret, msg]
  676. endfunction
  677. function! s:clean(force)
  678. call s:prepare()
  679. call append(0, 'Searching for unused plugins in '.g:plug_home)
  680. call append(1, '')
  681. " List of valid directories
  682. let dirs = []
  683. let [cnt, total] = [0, len(g:plugs)]
  684. for spec in values(g:plugs)
  685. if s:git_valid(spec, 0, 1)[0]
  686. call add(dirs, spec.dir)
  687. endif
  688. let cnt += 1
  689. call s:progress_bar(2, repeat('=', cnt), total)
  690. normal! 2G
  691. redraw
  692. endfor
  693. let allowed = {}
  694. for dir in dirs
  695. let allowed[dir] = 1
  696. for child in s:glob_dir(dir)
  697. let allowed[child] = 1
  698. endfor
  699. endfor
  700. let todo = []
  701. let found = sort(s:glob_dir(g:plug_home))
  702. while !empty(found)
  703. let f = remove(found, 0)
  704. if !has_key(allowed, f) && isdirectory(f)
  705. call add(todo, f)
  706. call append(line('$'), '- ' . f)
  707. let found = filter(found, 'stridx(v:val, f) != 0')
  708. end
  709. endwhile
  710. normal! G
  711. redraw
  712. if empty(todo)
  713. call append(line('$'), 'Already clean.')
  714. else
  715. call inputsave()
  716. let yes = a:force || (input("Proceed? (Y/N) ") =~? '^y')
  717. call inputrestore()
  718. if yes
  719. for dir in todo
  720. if isdirectory(dir)
  721. call system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(dir))
  722. endif
  723. endfor
  724. call append(line('$'), 'Removed.')
  725. else
  726. call append(line('$'), 'Cancelled.')
  727. endif
  728. endif
  729. normal! G
  730. endfunction
  731. function! s:upgrade()
  732. if executable('curl')
  733. let mee = s:shellesc(s:me)
  734. let new = s:shellesc(s:me . '.new')
  735. echo "Downloading ". s:plug_source
  736. redraw
  737. let mv = s:is_win ? 'move /Y' : 'mv -f'
  738. let cp = s:is_win ? 'copy /Y' : 'cp -f'
  739. call system(printf(
  740. \ "curl -fLo %s %s && ".cp." %s %s.old && ".mv." %s %s",
  741. \ new, s:plug_source, mee, mee, new, mee))
  742. if v:shell_error == 0
  743. unlet g:loaded_plug
  744. echo "Downloaded ". s:plug_source
  745. return 1
  746. else
  747. echoerr "Error upgrading vim-plug"
  748. return 0
  749. endif
  750. elseif has('ruby')
  751. echo "Downloading ". s:plug_source
  752. ruby << EOF
  753. require 'open-uri'
  754. require 'fileutils'
  755. me = VIM::evaluate('s:me')
  756. old = me + '.old'
  757. new = me + '.new'
  758. File.open(new, 'w') do |f|
  759. f << open(VIM::evaluate('s:plug_source')).read
  760. end
  761. FileUtils.cp me, old
  762. File.rename new, me
  763. EOF
  764. unlet g:loaded_plug
  765. echo "Downloaded ". s:plug_source
  766. return 1
  767. else
  768. echoerr "curl executable or ruby support not found"
  769. return 0
  770. endif
  771. endfunction
  772. function! s:status()
  773. call s:prepare()
  774. call append(0, 'Checking plugins')
  775. call append(1, '')
  776. let ecnt = 0
  777. let [cnt, total] = [0, len(g:plugs)]
  778. for [name, spec] in items(g:plugs)
  779. if isdirectory(spec.dir)
  780. let [valid, msg] = s:git_valid(spec, 1, 1)
  781. else
  782. let [valid, msg] = [0, 'Not found. Try PlugInstall.']
  783. endif
  784. let cnt += 1
  785. let ecnt += !valid
  786. call s:progress_bar(2, repeat('=', cnt), total)
  787. call append(3, s:format_message(valid, name, msg))
  788. normal! 2G
  789. redraw
  790. endfor
  791. call setline(1, 'Finished. '.ecnt.' error(s).')
  792. normal! gg
  793. endfunction
  794. function! s:is_preview_window_open()
  795. silent! wincmd P
  796. if &previewwindow
  797. wincmd p
  798. return 1
  799. endif
  800. return 0
  801. endfunction
  802. function! s:preview_commit()
  803. if b:plug_preview < 0
  804. let b:plug_preview = !s:is_preview_window_open()
  805. endif
  806. let sha = matchstr(getline('.'), '\(^ \)\@<=[0-9a-z]\{7}')
  807. if !empty(sha)
  808. let lnum = line('.')
  809. while lnum > 1
  810. let lnum -= 1
  811. let line = getline(lnum)
  812. let name = matchstr(line, '\(^- \)\@<=[^:]\+')
  813. if !empty(name)
  814. let dir = g:plugs[name].dir
  815. if isdirectory(dir)
  816. execute 'cd '.s:esc(dir)
  817. execute 'pedit '.sha
  818. wincmd P
  819. setlocal filetype=git buftype=nofile nobuflisted
  820. execute 'silent read !git show '.sha
  821. normal! ggdd
  822. wincmd p
  823. cd -
  824. endif
  825. break
  826. endif
  827. endwhile
  828. endif
  829. endfunction
  830. function! s:section(flags)
  831. call search('\(^- \)\@<=.', a:flags)
  832. endfunction
  833. function! s:diff()
  834. call s:prepare()
  835. call append(0, 'Collecting updated changes ...')
  836. normal! gg
  837. redraw
  838. let cnt = 0
  839. for [k, v] in items(g:plugs)
  840. if !isdirectory(v.dir)
  841. continue
  842. endif
  843. execute 'cd '.s:esc(v.dir)
  844. let diff = system('git log --pretty=format:"%h %s (%cr)" "HEAD@{0}...HEAD@{1}"')
  845. if !v:shell_error && !empty(diff)
  846. call append(1, '')
  847. call append(2, '- '.k.':')
  848. call append(3, map(split(diff, '\n'), '" ". v:val'))
  849. let cnt += 1
  850. normal! gg
  851. redraw
  852. endif
  853. cd -
  854. endfor
  855. call setline(1, cnt == 0 ? 'No updates.' : 'Last update:')
  856. nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
  857. normal! gg
  858. endfunction
  859. let &cpo = s:cpo_save
  860. unlet s:cpo_save