plug.vim 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  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. let bar .= error ? 'x' : '='
  290. call append(3, printf('%s %s: %s', error ? 'x' : '-', name, result))
  291. call s:update_progress(a:pull, len(done), bar, total)
  292. endfor
  293. if !empty(s:extend(keys(todo)))
  294. let todo = filter(copy(g:plugs), '!has_key(done, v:key)')
  295. let total += len(todo)
  296. call s:update_progress(a:pull, len(done), bar, total)
  297. else
  298. break
  299. endif
  300. endwhile
  301. call setline(1, "Updated. Elapsed time: " . split(reltimestr(reltime(st)))[0] . ' sec.')
  302. endfunction
  303. function! s:update_parallel(pull, threads)
  304. ruby << EOF
  305. st = Time.now
  306. require 'thread'
  307. require 'fileutils'
  308. require 'timeout'
  309. running = true
  310. iswin = VIM::evaluate('s:is_win').to_i == 1
  311. pull = VIM::evaluate('a:pull').to_i == 1
  312. base = VIM::evaluate('g:plug_home')
  313. all = VIM::evaluate('copy(g:plugs)')
  314. limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
  315. nthr = VIM::evaluate('a:threads').to_i
  316. cd = iswin ? 'cd /d' : 'cd'
  317. done = {}
  318. tot = 0
  319. bar = ''
  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] = '[' + bar.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. bar += ok ? '=' : 'x'
  334. done[name] = true
  335. result = (ok ? '- ' : 'x ') << [name, result].join(': ')
  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, bar, total)
  446. call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
  447. endfunction
  448. function! s:compare_git_uri(a, b)
  449. let a = substitute(a:a, 'git:@', '', '')
  450. let b = substitute(a:b, 'git:@', '', '')
  451. return a ==# b
  452. endfunction
  453. function! s:git_valid(spec, cd)
  454. let ret = 1
  455. let msg = 'OK'
  456. if isdirectory(a:spec.dir)
  457. if a:cd | execute "cd " . a:spec.dir | endif
  458. let remote = s:system("git config remote.origin.url")
  459. if !s:compare_git_uri(remote, a:spec.uri)
  460. let msg = 'Invalid remote: ' . remote . '. Try PlugClean.'
  461. let ret = 0
  462. else
  463. let branch = s:system('git rev-parse --abbrev-ref HEAD')
  464. if v:shell_error != 0
  465. let msg = 'Invalid git repository. Try PlugClean.'
  466. let ret = 0
  467. elseif a:spec.branch != branch
  468. let msg = 'Invalid branch: '.branch.'. Try PlugUpdate.'
  469. let ret = 0
  470. endif
  471. endif
  472. if a:cd | cd - | endif
  473. else
  474. let msg = 'Not found'
  475. let ret = 0
  476. endif
  477. return [ret, msg]
  478. endfunction
  479. function! s:clean(force)
  480. call s:prepare()
  481. call append(0, 'Searching for unused plugins in '.g:plug_home)
  482. call append(1, '')
  483. " List of valid directories
  484. let dirs = []
  485. let [cnt, total] = [0, len(g:plugs)]
  486. for spec in values(g:plugs)
  487. if s:git_valid(spec, 1)[0]
  488. call add(dirs, spec.dir)
  489. endif
  490. let cnt += 1
  491. call s:progress_bar(2, repeat('=', cnt), total)
  492. redraw
  493. endfor
  494. let allowed = {}
  495. for dir in dirs
  496. let allowed[dir] = 1
  497. for child in s:glob_dir(dir)
  498. let allowed[child] = 1
  499. endfor
  500. endfor
  501. let todo = []
  502. let found = sort(s:glob_dir(g:plug_home))
  503. while !empty(found)
  504. let f = remove(found, 0)
  505. if !has_key(allowed, f) && isdirectory(f)
  506. call add(todo, f)
  507. call append(line('$'), '- ' . f)
  508. let found = filter(found, 'stridx(v:val, f) != 0')
  509. end
  510. endwhile
  511. normal! G
  512. redraw
  513. if empty(todo)
  514. call append(line('$'), 'Already clean.')
  515. else
  516. call inputsave()
  517. let yes = a:force || (input("Proceed? (Y/N) ") =~? '^y')
  518. call inputrestore()
  519. if yes
  520. for dir in todo
  521. if isdirectory(dir)
  522. call system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . dir)
  523. endif
  524. endfor
  525. call append(line('$'), 'Removed.')
  526. else
  527. call append(line('$'), 'Cancelled.')
  528. endif
  529. endif
  530. normal! G
  531. endfunction
  532. function! s:upgrade()
  533. if executable('curl')
  534. let mee = shellescape(s:me)
  535. let new = shellescape(s:me . '.new')
  536. echo "Downloading ". s:plug_source
  537. redraw
  538. let mv = s:is_win ? 'move /Y' : 'mv -f'
  539. let cp = s:is_win ? 'copy /Y' : 'cp -f'
  540. call system(printf(
  541. \ "curl -fLo %s %s && ".cp." %s %s.old && ".mv." %s %s",
  542. \ new, s:plug_source, mee, mee, new, mee))
  543. if v:shell_error == 0
  544. unlet g:loaded_plug
  545. echo "Downloaded ". s:plug_source
  546. return 1
  547. else
  548. echoerr "Error upgrading vim-plug"
  549. return 0
  550. endif
  551. elseif has('ruby')
  552. echo "Downloading ". s:plug_source
  553. ruby << EOF
  554. require 'open-uri'
  555. require 'fileutils'
  556. me = VIM::evaluate('s:me')
  557. old = me + '.old'
  558. new = me + '.new'
  559. File.open(new, 'w') do |f|
  560. f << open(VIM::evaluate('s:plug_source')).read
  561. end
  562. FileUtils.cp me, old
  563. File.rename new, me
  564. EOF
  565. unlet g:loaded_plug
  566. echo "Downloaded ". s:plug_source
  567. return 1
  568. else
  569. echoerr "curl executable or ruby support not found"
  570. return 0
  571. endif
  572. endfunction
  573. function! s:status()
  574. call s:prepare()
  575. call append(0, 'Checking plugins')
  576. let ecnt = 0
  577. for [name, spec] in items(g:plugs)
  578. if isdirectory(spec.dir)
  579. execute 'cd '.spec.dir
  580. let [valid, msg] = s:git_valid(spec, 0)
  581. cd -
  582. else
  583. let [valid, msg] = [0, 'Not found. Try PlugInstall.']
  584. endif
  585. let ecnt += !valid
  586. call append(2, printf('%s %s: %s', valid ? '-' : 'x', name, msg))
  587. call cursor(3, 1)
  588. redraw
  589. endfor
  590. call setline(1, 'Finished. '.ecnt.' error(s).')
  591. normal! gg
  592. endfunction
  593. let &cpo = s:cpo_save
  594. unlet s:cpo_save