plug.vim 16 KB

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