plug.vim 14 KB

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