plug.vim 17 KB

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