plug.vim 17 KB

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