plug.vim 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  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. if s:git_valid(spec, 0)
  259. let result = a:pull ?
  260. \ s:system(
  261. \ printf('git checkout -q %s 2>&1 && git pull origin %s 2>&1',
  262. \ spec.branch, spec.branch)) : 'Already installed'
  263. let error = a:pull ? v:shell_error != 0 : 0
  264. else
  265. let result = "PlugClean required. Invalid remote."
  266. let error = 1
  267. endif
  268. else
  269. if !isdirectory(base)
  270. call mkdir(base, 'p')
  271. endif
  272. execute 'cd '.base
  273. let d = shellescape(substitute(spec.dir, '[\/]\+$', '', ''))
  274. let result = s:system(
  275. \ printf('git clone --recursive %s -b %s %s 2>&1',
  276. \ shellescape(spec.uri), shellescape(spec.branch), d))
  277. let error = v:shell_error != 0
  278. endif
  279. cd -
  280. if error
  281. let result = '(x) ' . result
  282. endif
  283. call append(3, '- ' . name . ': ' . result)
  284. call s:update_progress(a:pull, len(done), total)
  285. endfor
  286. if !empty(s:extend(keys(todo)))
  287. let todo = filter(copy(g:plugs), '!has_key(done, v:key)')
  288. let total += len(todo)
  289. call s:update_progress(a:pull, len(done), total)
  290. else
  291. break
  292. endif
  293. endwhile
  294. call setline(1, "Updated. Elapsed time: " . split(reltimestr(reltime(st)))[0] . ' sec.')
  295. endfunction
  296. function! s:update_parallel(pull, threads)
  297. ruby << EOF
  298. require 'thread'
  299. require 'fileutils'
  300. st = Time.now
  301. iswin = VIM::evaluate('s:is_win').to_i == 1
  302. cd = iswin ? 'cd /d' : 'cd'
  303. pull = VIM::evaluate('a:pull').to_i == 1
  304. base = VIM::evaluate('g:plug_home')
  305. all = VIM::evaluate('copy(g:plugs)')
  306. done = {}
  307. skip = 'Already installed'
  308. mtx = Mutex.new
  309. take1 = proc { mtx.synchronize { all.shift } }
  310. logh = proc {
  311. cnt, tot = done.length, VIM::evaluate('len(g:plugs)')
  312. $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
  313. $curbuf[2] = '[' + ('=' * cnt).ljust(tot) + ']'
  314. VIM::command('normal! 2G')
  315. VIM::command('redraw') unless iswin
  316. }
  317. log = proc { |name, result, ok|
  318. mtx.synchronize do
  319. done[name] = true
  320. result = '(x) ' + result unless ok
  321. result = "- #{name}: #{result}"
  322. $curbuf.append 3, result
  323. logh.call
  324. end
  325. }
  326. bt = iswin ? proc { |cmd|
  327. tmp = VIM::evaluate('tempname()')
  328. system("#{cmd} > #{tmp}")
  329. data = File.read(tmp).chomp
  330. File.unlink tmp rescue nil
  331. data
  332. } : proc { |cmd| `#{cmd}`.chomp }
  333. until all.empty?
  334. names = all.keys
  335. [names.length, VIM::evaluate('a:threads').to_i].min.times.map { |i|
  336. Thread.new(i) do
  337. while pair = take1.call
  338. name = pair.first
  339. dir, uri, branch = pair.last.values_at *%w[dir uri branch]
  340. ok, result =
  341. if File.directory? dir
  342. current_uri = bt.call "#{cd} #{dir} && git config remote.origin.url"
  343. if $? == 0 && current_uri == uri
  344. if pull
  345. output = bt.call "#{cd} #{dir} && git checkout -q #{branch} 2>&1 && git pull origin #{branch} 2>&1"
  346. [$? == 0, output]
  347. else
  348. [true, skip]
  349. end
  350. else
  351. [false, "PlugClean required. Invalid remote."]
  352. end
  353. else
  354. FileUtils.mkdir_p(base)
  355. d = dir.sub(%r{[\\/]+$}, '')
  356. r = bt.call "#{cd} #{base} && git clone --recursive #{uri} -b #{branch} #{d} 2>&1"
  357. [$? == 0, r]
  358. end
  359. result = result.lines.to_a.last
  360. log.call name, (result && result.strip), ok
  361. end
  362. end
  363. }.each(&:join)
  364. all.merge! VIM::evaluate("s:extend(#{names.inspect})")
  365. logh.call
  366. end
  367. $curbuf[1] = "Updated. Elapsed time: #{"%.6f" % (Time.now - st)} sec."
  368. EOF
  369. endfunction
  370. function! s:path(path)
  371. return substitute(s:is_win ? substitute(a:path, '/', '\', 'g') : a:path,
  372. \ '[/\\]*$', '', '')
  373. endfunction
  374. function! s:dirpath(path)
  375. let path = s:path(a:path)
  376. if s:is_win
  377. return path !~ '\\$' ? path.'\' : path
  378. else
  379. return path !~ '/$' ? path.'/' : path
  380. endif
  381. endfunction
  382. function! s:glob_dir(path)
  383. return map(filter(split(globpath(a:path, '**'), '\n'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
  384. endfunction
  385. function! s:progress_bar(line, cnt, total)
  386. call setline(a:line, '[' . s:lpad(repeat('=', a:cnt), a:total) . ']')
  387. endfunction
  388. function! s:git_valid(spec, cd)
  389. if isdirectory(a:spec.dir)
  390. if a:cd | execute "cd " . a:spec.dir | endif
  391. let ret = s:system("git config remote.origin.url") == a:spec.uri
  392. if a:cd | cd - | endif
  393. else
  394. let ret = 0
  395. endif
  396. return ret
  397. endfunction
  398. function! s:clean(force)
  399. call s:prepare()
  400. call append(0, 'Searching for unused plugins in '.g:plug_home)
  401. call append(1, '')
  402. " List of valid directories
  403. let dirs = []
  404. let [cnt, total] = [0, len(g:plugs)]
  405. for spec in values(g:plugs)
  406. if s:git_valid(spec, 1)
  407. call add(dirs, spec.dir)
  408. endif
  409. let cnt += 1
  410. call s:progress_bar(2, cnt, total)
  411. redraw
  412. endfor
  413. let allowed = {}
  414. for dir in dirs
  415. let allowed[dir] = 1
  416. for child in s:glob_dir(dir)
  417. let allowed[child] = 1
  418. endfor
  419. endfor
  420. let todo = []
  421. let found = sort(s:glob_dir(g:plug_home))
  422. while !empty(found)
  423. let f = remove(found, 0)
  424. if !has_key(allowed, f) && isdirectory(f)
  425. call add(todo, f)
  426. call append(line('$'), '- ' . f)
  427. let found = filter(found, 'stridx(v:val, f) != 0')
  428. end
  429. endwhile
  430. normal! G
  431. redraw
  432. if empty(todo)
  433. call append(line('$'), 'Already clean.')
  434. else
  435. call inputsave()
  436. let yes = a:force || (input("Proceed? (Y/N) ") =~? '^y')
  437. call inputrestore()
  438. if yes
  439. for dir in todo
  440. if isdirectory(dir)
  441. call system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . dir)
  442. endif
  443. endfor
  444. call append(line('$'), 'Removed.')
  445. else
  446. call append(line('$'), 'Cancelled.')
  447. endif
  448. endif
  449. normal! G
  450. endfunction
  451. function! s:upgrade()
  452. if executable('curl')
  453. let mee = shellescape(s:me)
  454. let new = shellescape(s:me . '.new')
  455. echo "Downloading ". s:plug_source
  456. redraw
  457. let mv = s:is_win ? 'move /Y' : 'mv -f'
  458. call system(printf(
  459. \ "curl -fLo %s %s && ".mv." %s %s.old && ".mv." %s %s",
  460. \ new, s:plug_source, mee, mee, new, mee))
  461. if v:shell_error == 0
  462. unlet g:loaded_plug
  463. echo "Downloaded ". s:plug_source
  464. return 1
  465. else
  466. echoerr "Error upgrading vim-plug"
  467. return 0
  468. endif
  469. else
  470. echoerr "`curl' not found"
  471. return 0
  472. endif
  473. endfunction
  474. function! s:status()
  475. call s:prepare()
  476. call append(0, 'Checking plugins')
  477. let errs = 0
  478. for [name, spec] in items(g:plugs)
  479. let err = 'OK'
  480. if isdirectory(spec.dir)
  481. execute 'cd '.spec.dir
  482. if s:git_valid(spec, 0)
  483. let branch = s:system('git rev-parse --abbrev-ref HEAD')
  484. if spec.branch != branch
  485. let err = '(x) Invalid branch: '.branch.'. Try PlugUpdate.'
  486. endif
  487. else
  488. let err = '(x) Invalid remote. Try PlugClean.'
  489. endif
  490. cd -
  491. else
  492. let err = '(x) Not found. Try PlugInstall.'
  493. endif
  494. let errs += err != 'OK'
  495. call append(2, printf('- %s: %s', name, err))
  496. call cursor(3, 1)
  497. redraw
  498. endfor
  499. call setline(1, 'Finished. '.errs.' error(s).')
  500. endfunction