plug.vim 17 KB

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