plug.vim 17 KB

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