plug.vim 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  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'
  15. " Plug 'junegunn/vim-easy-align'
  16. " " Plug 'user/repo1', 'branch_or_tag'
  17. " " Plug 'user/repo2', { 'rtp': 'vim/plugin/dir', 'branch': 'devel' }
  18. " " ...
  19. "
  20. " call plug#end()
  21. "
  22. " Then :PlugInstall to install plugins. (default: ~/.vim/plugged)
  23. " You can change the location of the plugins with plug#begin(path) call.
  24. "
  25. "
  26. " Copyright (c) 2013 Junegunn Choi
  27. "
  28. " MIT License
  29. "
  30. " Permission is hereby granted, free of charge, to any person obtaining
  31. " a copy of this software and associated documentation files (the
  32. " "Software"), to deal in the Software without restriction, including
  33. " without limitation the rights to use, copy, modify, merge, publish,
  34. " distribute, sublicense, and/or sell copies of the Software, and to
  35. " permit persons to whom the Software is furnished to do so, subject to
  36. " the following conditions:
  37. "
  38. " The above copyright notice and this permission notice shall be
  39. " included in all copies or substantial portions of the Software.
  40. "
  41. " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  42. " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  43. " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  44. " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  45. " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  46. " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  47. " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  48. if exists('g:loaded_plug')
  49. finish
  50. endif
  51. let g:loaded_plug = 1
  52. let s:plug_source = 'https://raw.github.com/junegunn/vim-plug/master/plug.vim'
  53. let s:plug_file = 'Plugfile'
  54. let s:plug_win = 0
  55. let s:is_win = has('win32') || has('win64')
  56. let s:me = expand('<sfile>:p')
  57. function! plug#begin(...)
  58. let home = a:0 > 0 ? fnamemodify(a:1, ':p') :
  59. \ get(g:, 'plug_home', split(&rtp, ',')[0].'/plugged')
  60. if !isdirectory(home)
  61. try
  62. call mkdir(home, 'p')
  63. catch
  64. echoerr 'Invalid plug directory: '. home
  65. return 0
  66. endtry
  67. endif
  68. if !executable('git')
  69. echoerr "`git' executable not found. vim-plug requires git."
  70. return 0
  71. endif
  72. let g:plug_home = home
  73. let g:plugs = {}
  74. command! -nargs=+ Plug call s:add(1, <args>)
  75. command! -nargs=* PlugInstall call s:install(<f-args>)
  76. command! -nargs=* PlugUpdate call s:update(<f-args>)
  77. command! -nargs=0 -bang PlugClean call s:clean('<bang>' == '!')
  78. command! -nargs=0 PlugUpgrade if s:upgrade() | execute "source ". s:me | endif
  79. command! -nargs=0 PlugStatus call s:status()
  80. return 1
  81. endfunction
  82. function! plug#end()
  83. let keys = keys(g:plugs)
  84. while !empty(keys)
  85. let keys = keys(s:extend(keys))
  86. endwhile
  87. set nocompatible
  88. filetype off
  89. for plug in values(g:plugs)
  90. let rtp = s:rtp(plug)
  91. execute "set rtp^=".rtp
  92. if isdirectory(rtp.'after')
  93. execute "set rtp+=".rtp.'after'
  94. endif
  95. endfor
  96. filetype plugin indent on
  97. syntax on
  98. endfunction
  99. function! s:rtp(spec)
  100. return s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
  101. endfunction
  102. function! s:add(...)
  103. let force = a:1
  104. let opts = { 'branch': 'master' }
  105. if a:0 == 2
  106. let plugin = a:2
  107. elseif a:0 == 3
  108. let plugin = a:2
  109. if type(a:3) == 1
  110. let opts.branch = a:3
  111. elseif type(a:3) == 4
  112. call extend(opts, a:3)
  113. else
  114. echoerr "Invalid argument type (expected: string or dictionary)"
  115. return
  116. endif
  117. else
  118. echoerr "Invalid number of arguments (1..2)"
  119. return
  120. endif
  121. if plugin =~ ':'
  122. let uri = plugin
  123. else
  124. if plugin !~ '/'
  125. let plugin = 'vim-scripts/'. plugin
  126. endif
  127. let uri = 'https://git:@github.com/' . plugin . '.git'
  128. endif
  129. let name = substitute(split(plugin, '/')[-1], '\.git$', '', '')
  130. if !force && has_key(g:plugs, name) | return | endif
  131. let dir = s:dirpath( fnamemodify(join([g:plug_home, name], '/'), ':p') )
  132. let spec = extend(opts, { 'dir': dir, 'uri': uri })
  133. let g:plugs[name] = spec
  134. endfunction
  135. function! s:install(...)
  136. call s:update_impl(0, a:000)
  137. endfunction
  138. function! s:update(...)
  139. call s:update_impl(1, a:000)
  140. endfunction
  141. function! s:apply()
  142. for spec in values(g:plugs)
  143. let docd = join([spec.dir, 'doc'], '/')
  144. if isdirectory(docd)
  145. execute "helptags ". join([spec.dir, 'doc'], '/')
  146. endif
  147. endfor
  148. runtime! plugin/*.vim
  149. runtime! after/*.vim
  150. silent! source $MYVIMRC
  151. endfunction
  152. function! s:syntax()
  153. syntax clear
  154. syntax region plug1 start=/\%1l/ end=/\%2l/ contains=ALL
  155. syntax region plug2 start=/\%2l/ end=/\%3l/ contains=ALL
  156. syn match plugNumber /[0-9]\+[0-9.]*/ containedin=plug1 contained
  157. syn match plugBracket /[[\]]/ containedin=plug2 contained
  158. syn match plugDash /^-/
  159. syn match plugName /\(^- \)\@<=[^:]*/
  160. syn match plugError /^- [^:]\+: (x).*/
  161. hi def link plug1 Title
  162. hi def link plug2 Repeat
  163. hi def link plugBracket Structure
  164. hi def link plugNumber Number
  165. hi def link plugDash Special
  166. hi def link plugName Label
  167. hi def link plugError Error
  168. endfunction
  169. function! s:lpad(str, len)
  170. return a:str . repeat(' ', a:len - len(a:str))
  171. endfunction
  172. function! s:system(cmd)
  173. let lines = split(system(a:cmd), '\n')
  174. return get(lines, -1, '')
  175. endfunction
  176. function! s:prepare()
  177. execute s:plug_win . 'wincmd w'
  178. if exists('b:plug')
  179. %d
  180. else
  181. vertical topleft new
  182. noremap <silent> <buffer> q :q<cr>
  183. let b:plug = 1
  184. let s:plug_win = winnr()
  185. call s:assign_name()
  186. endif
  187. setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline
  188. setf vim-plug
  189. call s:syntax()
  190. endfunction
  191. function! s:assign_name()
  192. " Assign buffer name
  193. let prefix = '[Plugins]'
  194. let name = prefix
  195. let idx = 2
  196. while bufexists(name)
  197. let name = printf("%s (%s)", prefix, idx)
  198. let idx = idx + 1
  199. endwhile
  200. silent! execute "f ".fnameescape(name)
  201. endfunction
  202. function! s:finish()
  203. call append(line('$'), '')
  204. call append(line('$'), 'Finishing ... ')
  205. redraw
  206. call s:apply()
  207. call s:syntax()
  208. call setline(line('$'), getline(line('$')) . 'Done!')
  209. normal! G
  210. endfunction
  211. function! s:update_impl(pull, args)
  212. let threads = len(a:args) > 0 ? a:args[0] : get(g:, 'plug_threads', 16)
  213. call s:prepare()
  214. call append(0, a:pull ? 'Updating plugins' : 'Installing plugins')
  215. call append(1, '['. s:lpad('', len(g:plugs)) .']')
  216. normal! 2G
  217. redraw
  218. if has('ruby') && threads > 1
  219. call s:update_parallel(a:pull, threads)
  220. else
  221. call s:update_serial(a:pull)
  222. endif
  223. call s:finish()
  224. endfunction
  225. function! s:extend(names)
  226. let prev = copy(g:plugs)
  227. try
  228. command! -nargs=+ Plug call s:add(0, <args>)
  229. for name in a:names
  230. let plugfile = s:rtp(g:plugs[name]) . s:plug_file
  231. if filereadable(plugfile)
  232. execute "source ". plugfile
  233. endif
  234. endfor
  235. finally
  236. command! -nargs=+ Plug call s:add(1, <args>)
  237. endtry
  238. return filter(copy(g:plugs), '!has_key(prev, v:key)')
  239. endfunction
  240. function! s:update_progress(pull, cnt, total)
  241. call setline(1, (a:pull ? 'Updating' : 'Installing').
  242. \ " plugins (".a:cnt."/".a:total.")")
  243. call s:progress_bar(2, a:cnt, a:total)
  244. normal! 2G
  245. redraw
  246. endfunction
  247. function! s:update_serial(pull)
  248. let st = reltime()
  249. let base = g:plug_home
  250. let todo = copy(g:plugs)
  251. let total = len(todo)
  252. let done = {}
  253. while !empty(todo)
  254. for [name, spec] in items(todo)
  255. let done[name] = 1
  256. if isdirectory(spec.dir)
  257. execute 'cd '.spec.dir
  258. let [valid, msg] = s:git_valid(spec, 0)
  259. if valid
  260. let result = a:pull ?
  261. \ s:system(
  262. \ printf('git checkout -q %s 2>&1 && git pull origin %s 2>&1',
  263. \ spec.branch, spec.branch)) : 'Already installed'
  264. let error = a:pull ? v:shell_error != 0 : 0
  265. else
  266. let result = msg
  267. let error = 1
  268. endif
  269. else
  270. if !isdirectory(base)
  271. call mkdir(base, 'p')
  272. endif
  273. execute 'cd '.base
  274. let d = shellescape(substitute(spec.dir, '[\/]\+$', '', ''))
  275. let result = s:system(
  276. \ printf('git clone --recursive %s -b %s %s 2>&1',
  277. \ shellescape(spec.uri), shellescape(spec.branch), d))
  278. let error = v:shell_error != 0
  279. endif
  280. cd -
  281. if error
  282. let result = '(x) ' . result
  283. endif
  284. call append(3, '- ' . name . ': ' . result)
  285. call s:update_progress(a:pull, len(done), total)
  286. endfor
  287. if !empty(s:extend(keys(todo)))
  288. let todo = filter(copy(g:plugs), '!has_key(done, v:key)')
  289. let total += len(todo)
  290. call s:update_progress(a:pull, len(done), total)
  291. else
  292. break
  293. endif
  294. endwhile
  295. call setline(1, "Updated. Elapsed time: " . split(reltimestr(reltime(st)))[0] . ' sec.')
  296. endfunction
  297. function! s:update_parallel(pull, threads)
  298. ruby << EOF
  299. st = Time.now
  300. require 'thread'
  301. require 'fileutils'
  302. require 'timeout'
  303. running = true
  304. iswin = VIM::evaluate('s:is_win').to_i == 1
  305. pull = VIM::evaluate('a:pull').to_i == 1
  306. base = VIM::evaluate('g:plug_home')
  307. all = VIM::evaluate('copy(g:plugs)')
  308. limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
  309. nthr = VIM::evaluate('a:threads').to_i
  310. cd = iswin ? 'cd /d' : 'cd'
  311. done = {}
  312. tot = 0
  313. skip = 'Already installed'
  314. mtx = Mutex.new
  315. take1 = proc { mtx.synchronize { running && all.shift } }
  316. logh = proc {
  317. cnt = done.length
  318. tot = VIM::evaluate('len(g:plugs)') || tot
  319. $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
  320. $curbuf[2] = '[' + ('=' * cnt).ljust(tot) + ']'
  321. VIM::command('normal! 2G')
  322. VIM::command('redraw') unless iswin
  323. }
  324. log = proc { |name, result, ok|
  325. mtx.synchronize do
  326. done[name] = true
  327. result = '(x) ' + result unless ok
  328. result = "- #{name}: #{result}"
  329. $curbuf.append 3, result
  330. logh.call
  331. end
  332. }
  333. bt = proc { |cmd|
  334. begin
  335. fd = nil
  336. Timeout::timeout(limit) do
  337. if iswin
  338. tmp = VIM::evaluate('tempname()')
  339. system("#{cmd} > #{tmp}")
  340. data = File.read(tmp).chomp
  341. File.unlink tmp rescue nil
  342. else
  343. fd = IO.popen(cmd)
  344. data = fd.read.chomp
  345. fd.close
  346. end
  347. [$? == 0, data]
  348. end
  349. rescue Timeout::Error, Interrupt => e
  350. if fd && !fd.closed?
  351. pids = [fd.pid]
  352. unless `which pgrep`.empty?
  353. children = pids
  354. until children.empty?
  355. children = children.map { |pid|
  356. `pgrep -P #{pid}`.lines.map(&:chomp)
  357. }.flatten
  358. pids += children
  359. end
  360. end
  361. pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
  362. fd.close
  363. end
  364. [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
  365. end
  366. }
  367. main = Thread.current
  368. threads = []
  369. watcher = Thread.new {
  370. while VIM::evaluate('getchar(1)')
  371. sleep 0.1
  372. end
  373. mtx.synchronize do
  374. running = false
  375. threads.each { |t| t.raise Interrupt }
  376. end
  377. threads.each { |t| t.join rescue nil }
  378. main.kill
  379. }
  380. until all.empty?
  381. names = all.keys
  382. [names.length, nthr].min.times do
  383. mtx.synchronize do
  384. threads << Thread.new {
  385. while pair = take1.call
  386. name = pair.first
  387. dir, uri, branch = pair.last.values_at *%w[dir uri branch]
  388. ok, result =
  389. if File.directory? dir
  390. ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url"
  391. current_uri = data.lines.to_a.last
  392. if ret && current_uri == uri
  393. if pull
  394. bt.call "#{cd} #{dir} && git checkout -q #{branch} 2>&1 && git pull origin #{branch} 2>&1"
  395. else
  396. [true, skip]
  397. end
  398. elsif current_uri =~ /^Interrupted|^Timeout/
  399. [false, current_uri]
  400. else
  401. [false, "PlugClean required: #{current_uri}"]
  402. end
  403. else
  404. FileUtils.mkdir_p(base)
  405. d = dir.sub(%r{[\\/]+$}, '')
  406. bt.call "#{cd} #{base} && git clone --recursive #{uri} -b #{branch} #{d} 2>&1"
  407. end
  408. result = result.lines.to_a.last
  409. log.call name, (result && result.strip), ok
  410. end
  411. } if running
  412. end
  413. end
  414. threads.each(&:join)
  415. mtx.synchronize { threads.clear }
  416. all.merge!(VIM::evaluate("s:extend(#{names.inspect})") || {})
  417. logh.call
  418. end
  419. watcher.kill
  420. $curbuf[1] = "Updated. Elapsed time: #{"%.6f" % (Time.now - st)} sec."
  421. EOF
  422. endfunction
  423. function! s:path(path)
  424. return substitute(s:is_win ? substitute(a:path, '/', '\', 'g') : a:path,
  425. \ '[/\\]*$', '', '')
  426. endfunction
  427. function! s:dirpath(path)
  428. let path = s:path(a:path)
  429. if s:is_win
  430. return path !~ '\\$' ? path.'\' : path
  431. else
  432. return path !~ '/$' ? path.'/' : path
  433. endif
  434. endfunction
  435. function! s:glob_dir(path)
  436. return map(filter(split(globpath(a:path, '**'), '\n'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
  437. endfunction
  438. function! s:progress_bar(line, cnt, total)
  439. call setline(a:line, '[' . s:lpad(repeat('=', a:cnt), a:total) . ']')
  440. endfunction
  441. function! s:git_valid(spec, cd)
  442. let ret = 1
  443. let msg = 'OK'
  444. if isdirectory(a:spec.dir)
  445. if a:cd | execute "cd " . a:spec.dir | endif
  446. let remote = s:system("git config remote.origin.url")
  447. if remote != a:spec.uri
  448. let msg = 'Invalid remote: ' . remote . '. Try PlugClean.'
  449. let ret = 0
  450. else
  451. let branch = s:system('git rev-parse --abbrev-ref HEAD')
  452. if v:shell_error != 0
  453. let msg = 'Invalid git repository. Try PlugClean.'
  454. let ret = 0
  455. elseif a:spec.branch != branch
  456. let msg = 'Invalid branch: '.branch.'. Try PlugUpdate.'
  457. let ret = 0
  458. endif
  459. endif
  460. if a:cd | cd - | endif
  461. else
  462. let msg = 'Not found'
  463. let ret = 0
  464. endif
  465. return [ret, msg]
  466. endfunction
  467. function! s:clean(force)
  468. call s:prepare()
  469. call append(0, 'Searching for unused plugins in '.g:plug_home)
  470. call append(1, '')
  471. " List of valid directories
  472. let dirs = []
  473. let [cnt, total] = [0, len(g:plugs)]
  474. for spec in values(g:plugs)
  475. if s:git_valid(spec, 1)[0]
  476. call add(dirs, spec.dir)
  477. endif
  478. let cnt += 1
  479. call s:progress_bar(2, cnt, total)
  480. redraw
  481. endfor
  482. let allowed = {}
  483. for dir in dirs
  484. let allowed[dir] = 1
  485. for child in s:glob_dir(dir)
  486. let allowed[child] = 1
  487. endfor
  488. endfor
  489. let todo = []
  490. let found = sort(s:glob_dir(g:plug_home))
  491. while !empty(found)
  492. let f = remove(found, 0)
  493. if !has_key(allowed, f) && isdirectory(f)
  494. call add(todo, f)
  495. call append(line('$'), '- ' . f)
  496. let found = filter(found, 'stridx(v:val, f) != 0')
  497. end
  498. endwhile
  499. normal! G
  500. redraw
  501. if empty(todo)
  502. call append(line('$'), 'Already clean.')
  503. else
  504. call inputsave()
  505. let yes = a:force || (input("Proceed? (Y/N) ") =~? '^y')
  506. call inputrestore()
  507. if yes
  508. for dir in todo
  509. if isdirectory(dir)
  510. call system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . dir)
  511. endif
  512. endfor
  513. call append(line('$'), 'Removed.')
  514. else
  515. call append(line('$'), 'Cancelled.')
  516. endif
  517. endif
  518. normal! G
  519. endfunction
  520. function! s:upgrade()
  521. if executable('curl')
  522. let mee = shellescape(s:me)
  523. let new = shellescape(s:me . '.new')
  524. echo "Downloading ". s:plug_source
  525. redraw
  526. let mv = s:is_win ? 'move /Y' : 'mv -f'
  527. call system(printf(
  528. \ "curl -fLo %s %s && ".mv." %s %s.old && ".mv." %s %s",
  529. \ new, s:plug_source, mee, mee, new, mee))
  530. if v:shell_error == 0
  531. unlet g:loaded_plug
  532. echo "Downloaded ". s:plug_source
  533. return 1
  534. else
  535. echoerr "Error upgrading vim-plug"
  536. return 0
  537. endif
  538. else
  539. echoerr "`curl' not found"
  540. return 0
  541. endif
  542. endfunction
  543. function! s:status()
  544. call s:prepare()
  545. call append(0, 'Checking plugins')
  546. let errs = 0
  547. for [name, spec] in items(g:plugs)
  548. let err = 'OK'
  549. if isdirectory(spec.dir)
  550. execute 'cd '.spec.dir
  551. let [valid, msg] = s:git_valid(spec, 0)
  552. if !valid
  553. let err = '(x) '. msg
  554. endif
  555. cd -
  556. else
  557. let err = '(x) Not found. Try PlugInstall.'
  558. endif
  559. let errs += err != 'OK'
  560. call append(2, printf('- %s: %s', name, err))
  561. call cursor(3, 1)
  562. redraw
  563. endfor
  564. call setline(1, 'Finished. '.errs.' error(s).')
  565. endfunction