plug.vim 17 KB

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