plug.vim 16 KB

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