plug.vim 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  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 'junegunn/goyo.vim', { 'on': 'Goyo' }
  17. " " Plug 'user/repo1', 'branch_or_tag'
  18. " " Plug 'user/repo2', { 'rtp': 'vim/plugin/dir', 'branch': 'branch_or_tag' }
  19. " " ...
  20. "
  21. " call plug#end()
  22. "
  23. " Then :PlugInstall to install plugins. (default: ~/.vim/plugged)
  24. " You can change the location of the plugins with plug#begin(path) call.
  25. "
  26. "
  27. " Copyright (c) 2014 Junegunn Choi
  28. "
  29. " MIT License
  30. "
  31. " Permission is hereby granted, free of charge, to any person obtaining
  32. " a copy of this software and associated documentation files (the
  33. " "Software"), to deal in the Software without restriction, including
  34. " without limitation the rights to use, copy, modify, merge, publish,
  35. " distribute, sublicense, and/or sell copies of the Software, and to
  36. " permit persons to whom the Software is furnished to do so, subject to
  37. " the following conditions:
  38. "
  39. " The above copyright notice and this permission notice shall be
  40. " included in all copies or substantial portions of the Software.
  41. "
  42. " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  43. " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  44. " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  45. " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  46. " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  47. " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  48. " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  49. if exists('g:loaded_plug')
  50. finish
  51. endif
  52. let g:loaded_plug = 1
  53. let s:cpo_save = &cpo
  54. set cpo&vim
  55. let s:plug_source = 'https://raw.github.com/junegunn/vim-plug/master/plug.vim'
  56. let s:plug_file = 'Plugfile'
  57. let s:plug_buf = -1
  58. let s:is_win = has('win32') || has('win64')
  59. let s:me = expand('<sfile>:p')
  60. function! plug#begin(...)
  61. if a:0 > 0
  62. let home = s:path(fnamemodify(a:1, ':p'))
  63. elseif exists('g:plug_home')
  64. let home = s:path(g:plug_home)
  65. elseif !empty(&rtp)
  66. let home = s:path(split(&rtp, ',')[0]) . '/plugged'
  67. else
  68. echoerr "Unable to determine plug home. Try calling plug#begin() with a path argument."
  69. return 0
  70. endif
  71. if !isdirectory(home)
  72. try
  73. call mkdir(home, 'p')
  74. catch
  75. echoerr 'Invalid plug directory: '. home
  76. return 0
  77. endtry
  78. endif
  79. if !executable('git')
  80. echoerr "`git' executable not found. vim-plug requires git."
  81. return 0
  82. endif
  83. let g:plug_home = home
  84. let g:plugs = {}
  85. " we want to keep track of the order plugins where registered.
  86. let g:plugs_order = []
  87. command! -nargs=+ -bar Plug call s:add(1, <args>)
  88. command! -nargs=* -complete=customlist,s:names PlugInstall call s:install(<f-args>)
  89. command! -nargs=* -complete=customlist,s:names PlugUpdate call s:update(<f-args>)
  90. command! -nargs=0 -bang PlugClean call s:clean('<bang>' == '!')
  91. command! -nargs=0 PlugUpgrade if s:upgrade() | execute "source ". s:me | endif
  92. command! -nargs=0 PlugStatus call s:status()
  93. command! -nargs=0 PlugDiff call s:diff()
  94. return 1
  95. endfunction
  96. function! s:to_a(v)
  97. return type(a:v) == 3 ? a:v : [a:v]
  98. endfunction
  99. function! plug#end()
  100. if !exists('g:plugs')
  101. echoerr 'Call plug#begin() first'
  102. return
  103. endif
  104. let keys = keys(g:plugs)
  105. while !empty(keys)
  106. let keys = keys(s:extend(keys))
  107. endwhile
  108. if exists('#PlugLOD')
  109. augroup PlugLOD
  110. autocmd!
  111. augroup END
  112. augroup! PlugLOD
  113. endif
  114. let lod = {}
  115. filetype off
  116. " we want to make sure the plugin directories are added to rtp in the same
  117. " order that they are registered with the Plug command. since the s:add_rtp
  118. " function uses ^= to add plugin directories to the front of the rtp, we
  119. " need to loop through the plugins in reverse
  120. for name in reverse(copy(g:plugs_order))
  121. let plug = g:plugs[name]
  122. if !has_key(plug, 'on') && !has_key(plug, 'for')
  123. call s:add_rtp(s:rtp(plug))
  124. continue
  125. endif
  126. if has_key(plug, 'on')
  127. let commands = s:to_a(plug.on)
  128. for cmd in commands
  129. if cmd =~ '^<Plug>.\+'
  130. if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
  131. for [mode, map_prefix, key_prefix] in
  132. \ [['i', "<C-O>", ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
  133. execute printf(
  134. \ "%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, '%s')<CR>",
  135. \ mode, cmd, map_prefix, string(cmd), string(name), key_prefix)
  136. endfor
  137. endif
  138. elseif !exists(':'.cmd)
  139. execute printf(
  140. \ "command! -nargs=* -range -bang %s call s:lod_cmd(%s, '<bang>', <line1>, <line2>, <q-args>, %s)",
  141. \ cmd, string(cmd), string(name))
  142. endif
  143. endfor
  144. endif
  145. if has_key(plug, 'for')
  146. for vim in split(globpath(s:rtp(plug), 'ftdetect/**/*.vim'), '\n')
  147. execute 'source '.vim
  148. endfor
  149. for key in s:to_a(plug.for)
  150. if !has_key(lod, key)
  151. let lod[key] = []
  152. endif
  153. call add(lod[key], name)
  154. endfor
  155. endif
  156. endfor
  157. for [key, names] in items(lod)
  158. augroup PlugLOD
  159. execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
  160. \ key, string(key), string(reverse(names)))
  161. augroup END
  162. endfor
  163. filetype plugin indent on
  164. syntax on
  165. endfunction
  166. if s:is_win
  167. function! s:rtp(spec)
  168. let rtp = s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
  169. return substitute(rtp, '\\*$', '', '')
  170. endfunction
  171. function! s:path(path)
  172. return substitute(substitute(a:path, '/', '\', 'g'), '[/\\]*$', '', '')
  173. endfunction
  174. function! s:dirpath(path)
  175. return s:path(a:path) . '\'
  176. endfunction
  177. else
  178. function! s:rtp(spec)
  179. return s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
  180. endfunction
  181. function! s:path(path)
  182. return substitute(a:path, '[/\\]*$', '', '')
  183. endfunction
  184. function! s:dirpath(path)
  185. return s:path(a:path) . '/'
  186. endfunction
  187. endif
  188. function! s:esc(path)
  189. return substitute(a:path, ' ', '\\ ', 'g')
  190. endfunction
  191. function! s:add_rtp(rtp)
  192. execute "set rtp^=".s:esc(a:rtp)
  193. let after = globpath(a:rtp, 'after')
  194. if isdirectory(after)
  195. execute "set rtp+=".s:esc(after)
  196. endif
  197. endfunction
  198. function! s:lod(plug, types)
  199. let rtp = s:rtp(a:plug)
  200. call s:add_rtp(rtp)
  201. for dir in a:types
  202. for vim in split(globpath(rtp, dir.'/**/*.vim'), '\n')
  203. execute 'source '.vim
  204. endfor
  205. endfor
  206. endfunction
  207. function! s:lod_ft(pat, names)
  208. for name in a:names
  209. call s:lod(g:plugs[name], ['plugin', 'after'])
  210. endfor
  211. execute 'autocmd! PlugLOD FileType ' . a:pat
  212. silent! doautocmd filetypeplugin FileType
  213. endfunction
  214. function! s:lod_cmd(cmd, bang, l1, l2, args, name)
  215. execute 'delc '.a:cmd
  216. call s:lod(g:plugs[a:name], ['plugin', 'ftdetect', 'after'])
  217. execute printf("%s%s%s %s", (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
  218. endfunction
  219. function! s:lod_map(map, name, prefix)
  220. execute 'unmap '.a:map
  221. execute 'iunmap '.a:map
  222. call s:lod(g:plugs[a:name], ['plugin', 'ftdetect', 'after'])
  223. let extra = ''
  224. while 1
  225. let c = getchar(0)
  226. if c == 0
  227. break
  228. endif
  229. let extra .= nr2char(c)
  230. endwhile
  231. call feedkeys(a:prefix . substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
  232. endfunction
  233. function! s:add(...)
  234. let force = a:1
  235. let opts = { 'branch': 'master' }
  236. if a:0 == 2
  237. let plugin = a:2
  238. elseif a:0 == 3
  239. let plugin = a:2
  240. if type(a:3) == 1
  241. let opts.branch = a:3
  242. elseif type(a:3) == 4
  243. call extend(opts, a:3)
  244. else
  245. echoerr "Invalid argument type (expected: string or dictionary)"
  246. return
  247. endif
  248. else
  249. echoerr "Invalid number of arguments (1..2)"
  250. return
  251. endif
  252. let name = substitute(split(plugin, '/')[-1], '\.git$', '', '')
  253. if !force && has_key(g:plugs, name) | return | endif
  254. if plugin =~ ':'
  255. let uri = plugin
  256. else
  257. if plugin !~ '/'
  258. let plugin = 'vim-scripts/'. plugin
  259. endif
  260. let uri = 'https://git:@github.com/' . plugin . '.git'
  261. endif
  262. let dir = s:dirpath( fnamemodify(join([g:plug_home, name], '/'), ':p') )
  263. let spec = extend(opts, { 'dir': dir, 'uri': uri })
  264. let g:plugs[name] = spec
  265. let g:plugs_order += [name]
  266. endfunction
  267. function! s:install(...)
  268. call s:update_impl(0, a:000)
  269. endfunction
  270. function! s:update(...)
  271. call s:update_impl(1, a:000)
  272. endfunction
  273. function! s:apply()
  274. for spec in values(g:plugs)
  275. let docd = join([spec.dir, 'doc'], '/')
  276. if isdirectory(docd)
  277. execute "helptags ". join([spec.dir, 'doc'], '/')
  278. endif
  279. endfor
  280. runtime! plugin/*.vim
  281. runtime! after/*.vim
  282. silent! source $MYVIMRC
  283. endfunction
  284. function! s:syntax()
  285. syntax clear
  286. syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
  287. syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
  288. syn match plugNumber /[0-9]\+[0-9.]*/ contained
  289. syn match plugBracket /[[\]]/ contained
  290. syn match plugX /x/ contained
  291. syn match plugDash /^-/
  292. syn match plugPlus /^+/
  293. syn match plugStar /^*/
  294. syn match plugName /\(^- \)\@<=[^:]*/
  295. syn match plugInstall /\(^+ \)\@<=[^:]*/
  296. syn match plugUpdate /\(^* \)\@<=[^:]*/
  297. syn match plugCommit /^ [0-9a-z]\{7} .*/ contains=plugRelDate,plugSha
  298. syn match plugSha /\(^ \)\@<=[0-9a-z]\{7}/ contained
  299. syn match plugRelDate /([^)]*)$/ contained
  300. syn match plugError /^x.*/
  301. syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
  302. hi def link plug1 Title
  303. hi def link plug2 Repeat
  304. hi def link plugX Exception
  305. hi def link plugBracket Structure
  306. hi def link plugNumber Number
  307. hi def link plugDash Special
  308. hi def link plugPlus Constant
  309. hi def link plugStar Boolean
  310. hi def link plugName Label
  311. hi def link plugInstall Function
  312. hi def link plugUpdate Type
  313. hi def link plugError Error
  314. hi def link plugRelDate Comment
  315. hi def link plugSha Identifier
  316. endfunction
  317. function! s:lpad(str, len)
  318. return a:str . repeat(' ', a:len - len(a:str))
  319. endfunction
  320. function! s:lastline(msg)
  321. let lines = split(a:msg, '\n')
  322. return get(lines, -1, '')
  323. endfunction
  324. function! s:prepare()
  325. if bufexists(s:plug_buf)
  326. let winnr = bufwinnr(s:plug_buf)
  327. if winnr < 0
  328. vertical topleft new
  329. execute 'buffer ' . s:plug_buf
  330. else
  331. execute winnr . 'wincmd w'
  332. endif
  333. silent %d _
  334. else
  335. vertical topleft new
  336. nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>q<cr>
  337. nnoremap <silent> <buffer> D :PlugDiff<cr>
  338. nnoremap <silent> <buffer> S :PlugStatus<cr>
  339. nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
  340. nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
  341. let b:plug_preview = -1
  342. let s:plug_buf = winbufnr(0)
  343. call s:assign_name()
  344. endif
  345. silent! unmap <buffer> <cr>
  346. setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline
  347. setf vim-plug
  348. call s:syntax()
  349. endfunction
  350. function! s:assign_name()
  351. " Assign buffer name
  352. let prefix = '[Plugins]'
  353. let name = prefix
  354. let idx = 2
  355. while bufexists(name)
  356. let name = printf("%s (%s)", prefix, idx)
  357. let idx = idx + 1
  358. endwhile
  359. silent! execute "f ".fnameescape(name)
  360. endfunction
  361. function! s:finish(pull)
  362. call append(3, '- Finishing ... ')
  363. redraw
  364. call s:apply()
  365. call s:syntax()
  366. call setline(4, getline(4) . 'Done!')
  367. normal! gg
  368. redraw
  369. if a:pull
  370. echo "Press 'D' to see the updated changes."
  371. endif
  372. endfunction
  373. function! s:names(...)
  374. return filter(keys(g:plugs), 'stridx(v:val, a:1) == 0')
  375. endfunction
  376. function! s:update_impl(pull, args) abort
  377. let args = copy(a:args)
  378. let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
  379. \ remove(args, -1) : get(g:, 'plug_threads', 16)
  380. let todo = empty(args) ? g:plugs :
  381. \ filter(copy(g:plugs), 'index(args, v:key) >= 0')
  382. if empty(todo)
  383. echohl WarningMsg
  384. echo 'No plugin to '. (a:pull ? 'update' : 'install') . '.'
  385. echohl None
  386. return
  387. endif
  388. call s:prepare()
  389. call append(0, a:pull ? 'Updating plugins' : 'Installing plugins')
  390. call append(1, '['. s:lpad('', len(todo)) .']')
  391. normal! 2G
  392. redraw
  393. let len = len(g:plugs)
  394. if has('ruby') && threads > 1
  395. try
  396. call s:update_parallel(a:pull, todo, threads)
  397. catch
  398. let lines = getline(4, '$')
  399. let printed = {}
  400. silent 4,$d
  401. for line in lines
  402. let name = get(matchlist(line, '^. \([^:]\+\):'), 1, '')
  403. if empty(name) || !has_key(printed, name)
  404. let printed[name] = 1
  405. call append('$', line)
  406. endif
  407. endfor
  408. echoerr v:exception
  409. endtry
  410. else
  411. call s:update_serial(a:pull, todo)
  412. endif
  413. if len(g:plugs) > len
  414. call plug#end()
  415. endif
  416. call s:finish(a:pull)
  417. endfunction
  418. function! s:extend(names)
  419. let prev = copy(g:plugs)
  420. try
  421. command! -nargs=+ Plug call s:add(0, <args>)
  422. for name in a:names
  423. let plugfile = globpath(s:rtp(g:plugs[name]), s:plug_file)
  424. if filereadable(plugfile)
  425. execute "source ". s:esc(plugfile)
  426. endif
  427. endfor
  428. finally
  429. command! -nargs=+ Plug call s:add(1, <args>)
  430. endtry
  431. return filter(copy(g:plugs), '!has_key(prev, v:key)')
  432. endfunction
  433. function! s:update_progress(pull, cnt, bar, total)
  434. call setline(1, (a:pull ? 'Updating' : 'Installing').
  435. \ " plugins (".a:cnt."/".a:total.")")
  436. call s:progress_bar(2, a:bar, a:total)
  437. normal! 2G
  438. redraw
  439. endfunction
  440. function! s:update_serial(pull, todo)
  441. let st = reltime()
  442. let base = g:plug_home
  443. let todo = copy(a:todo)
  444. let total = len(todo)
  445. let done = {}
  446. let bar = ''
  447. while !empty(todo)
  448. for [name, spec] in items(todo)
  449. let done[name] = 1
  450. if isdirectory(spec.dir)
  451. execute 'cd '.s:esc(spec.dir)
  452. let [valid, msg] = s:git_valid(spec, 0, 0)
  453. if valid
  454. let result = a:pull ?
  455. \ s:system(
  456. \ printf('git checkout -q %s 2>&1 && git pull origin %s 2>&1 && git submodule update --init --recursive 2>&1',
  457. \ s:shellesc(spec.branch), s:shellesc(spec.branch))) : 'Already installed'
  458. let error = a:pull ? v:shell_error != 0 : 0
  459. else
  460. let result = msg
  461. let error = 1
  462. endif
  463. else
  464. if !isdirectory(base)
  465. call mkdir(base, 'p')
  466. endif
  467. let result = s:system(
  468. \ printf('git clone --recursive %s -b %s %s 2>&1 && cd %s && git submodule update --init --recursive 2>&1',
  469. \ s:shellesc(spec.uri),
  470. \ s:shellesc(spec.branch),
  471. \ s:shellesc(substitute(spec.dir, '[\/]\+$', '', '')),
  472. \ s:shellesc(spec.dir)))
  473. let error = v:shell_error != 0
  474. endif
  475. cd -
  476. let bar .= error ? 'x' : '='
  477. call append(3, s:format_message(!error, name, result))
  478. call s:update_progress(a:pull, len(done), bar, total)
  479. endfor
  480. if !empty(s:extend(keys(todo)))
  481. let todo = filter(copy(g:plugs), '!has_key(done, v:key)')
  482. let total += len(todo)
  483. call s:update_progress(a:pull, len(done), bar, total)
  484. else
  485. break
  486. endif
  487. endwhile
  488. call setline(1, "Updated. Elapsed time: " . split(reltimestr(reltime(st)))[0] . ' sec.')
  489. endfunction
  490. function! s:update_parallel(pull, todo, threads)
  491. ruby << EOF
  492. module PlugStream
  493. SEP = ["\r", "\n", nil]
  494. def get_line
  495. buffer = ''
  496. loop do
  497. char = readchar rescue return
  498. if SEP.include? char
  499. buffer << $/
  500. break
  501. else
  502. buffer << char
  503. end
  504. end
  505. buffer
  506. end
  507. end unless defined?(PlugStream)
  508. def esc arg
  509. %["#{arg.gsub('"', '\"')}"]
  510. end
  511. st = Time.now
  512. require 'thread'
  513. require 'fileutils'
  514. require 'timeout'
  515. running = true
  516. iswin = VIM::evaluate('s:is_win').to_i == 1
  517. pull = VIM::evaluate('a:pull').to_i == 1
  518. base = VIM::evaluate('g:plug_home')
  519. all = VIM::evaluate('copy(a:todo)')
  520. limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
  521. nthr = VIM::evaluate('a:threads').to_i
  522. maxy = VIM::evaluate('winheight(".")').to_i
  523. cd = iswin ? 'cd /d' : 'cd'
  524. tot = 0
  525. bar = ''
  526. skip = 'Already installed'
  527. mtx = Mutex.new
  528. take1 = proc { mtx.synchronize { running && all.shift } }
  529. logh = proc {
  530. cnt = $curbuf[2][1...-1].strip.length
  531. tot = VIM::evaluate('len(a:todo)') || tot
  532. $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
  533. $curbuf[2] = '[' + bar.ljust(tot) + ']'
  534. VIM::command('normal! 2G')
  535. VIM::command('redraw') unless iswin
  536. }
  537. where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
  538. log = proc { |name, result, type|
  539. mtx.synchronize do
  540. ing = ![true, false].include?(type)
  541. bar += type ? '=' : 'x' unless ing
  542. b = case type
  543. when :install then '+' when :update then '*'
  544. when true, nil then '-' else 'x' end
  545. result =
  546. if type || type.nil?
  547. ["#{b} #{name}: #{result.lines.to_a.last}"]
  548. elsif result =~ /^Interrupted|^Timeout/
  549. ["#{b} #{name}: #{result}"]
  550. else
  551. ["#{b} #{name}"] + result.lines.map { |l| " " << l }
  552. end
  553. if lnum = where.call(name)
  554. $curbuf.delete lnum
  555. lnum = 4 if ing && lnum > maxy
  556. end
  557. result.each_with_index do |line, offset|
  558. $curbuf.append (lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp
  559. end
  560. logh.call
  561. end
  562. }
  563. bt = proc { |cmd, name, type|
  564. begin
  565. fd = nil
  566. data = ''
  567. if iswin
  568. Timeout::timeout(limit) do
  569. tmp = VIM::evaluate('tempname()')
  570. system("#{cmd} > #{tmp}")
  571. data = File.read(tmp).chomp
  572. File.unlink tmp rescue nil
  573. end
  574. else
  575. fd = IO.popen(cmd).extend(PlugStream)
  576. while line = Timeout::timeout(limit) { fd.get_line }
  577. data << line
  578. log.call name, line.chomp, type if name
  579. end
  580. fd.close
  581. end
  582. [$? == 0, data.chomp]
  583. rescue Timeout::Error, Interrupt => e
  584. if fd && !fd.closed?
  585. pids = [fd.pid]
  586. unless `which pgrep`.empty?
  587. children = pids
  588. until children.empty?
  589. children = children.map { |pid|
  590. `pgrep -P #{pid}`.lines.map(&:chomp)
  591. }.flatten
  592. pids += children
  593. end
  594. end
  595. pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
  596. fd.close
  597. end
  598. [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
  599. end
  600. }
  601. main = Thread.current
  602. threads = []
  603. watcher = Thread.new {
  604. while VIM::evaluate('getchar(1)')
  605. sleep 0.1
  606. end
  607. mtx.synchronize do
  608. running = false
  609. threads.each { |t| t.raise Interrupt }
  610. end
  611. threads.each { |t| t.join rescue nil }
  612. main.kill
  613. }
  614. progress = iswin ? '' : '--progress'
  615. until all.empty?
  616. names = all.keys
  617. [names.length, nthr].min.times do
  618. mtx.synchronize do
  619. threads << Thread.new {
  620. while pair = take1.call
  621. name = pair.first
  622. dir, uri, branch = pair.last.values_at *%w[dir uri branch]
  623. branch = esc branch
  624. subm = "git submodule update --init --recursive 2>&1"
  625. ok, result =
  626. if File.directory? dir
  627. dir = esc dir
  628. ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url", nil, nil
  629. current_uri = data.lines.to_a.last
  630. if !ret
  631. if data =~ /^Interrupted|^Timeout/
  632. [false, data]
  633. else
  634. [false, [data.chomp, "PlugClean required."].join($/)]
  635. end
  636. elsif current_uri.sub(/git:@/, '') != uri.sub(/git:@/, '')
  637. [false, ["Invalid URI: #{current_uri}",
  638. "Expected: #{uri}",
  639. "PlugClean required."].join($/)]
  640. else
  641. if pull
  642. log.call name, 'Updating ...', :update
  643. bt.call "#{cd} #{dir} && git checkout -q #{branch} 2>&1 && (git pull origin #{branch} #{progress} 2>&1 && #{subm})", name, :update
  644. else
  645. [true, skip]
  646. end
  647. end
  648. else
  649. FileUtils.mkdir_p(base)
  650. d = esc dir.sub(%r{[\\/]+$}, '')
  651. log.call name, 'Installing ...', :install
  652. bt.call "(git clone #{progress} --recursive #{uri} -b #{branch} #{d} 2>&1 && cd #{esc dir} && #{subm})", name, :install
  653. end
  654. log.call name, result, ok
  655. end
  656. } if running
  657. end
  658. end
  659. threads.each(&:join)
  660. mtx.synchronize { threads.clear }
  661. all.merge!(VIM::evaluate("s:extend(#{names.inspect})") || {})
  662. logh.call
  663. end
  664. watcher.kill
  665. $curbuf[1] = "Updated. Elapsed time: #{"%.6f" % (Time.now - st)} sec."
  666. EOF
  667. endfunction
  668. function! s:shellesc(arg)
  669. return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
  670. endfunction
  671. function! s:glob_dir(path)
  672. return map(filter(split(globpath(a:path, '**'), '\n'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
  673. endfunction
  674. function! s:progress_bar(line, bar, total)
  675. call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
  676. endfunction
  677. function! s:compare_git_uri(a, b)
  678. let a = substitute(a:a, 'git:@', '', '')
  679. let b = substitute(a:b, 'git:@', '', '')
  680. return a ==# b
  681. endfunction
  682. function! s:format_message(ok, name, message)
  683. if a:ok
  684. return [printf('- %s: %s', a:name, s:lastline(a:message))]
  685. else
  686. let lines = map(split(a:message, '\n'), '" ".v:val')
  687. return extend([printf('x %s:', a:name)], lines)
  688. endif
  689. endfunction
  690. function! s:system(cmd)
  691. return system(s:is_win ? '('.a:cmd.')' : a:cmd)
  692. endfunction
  693. function! s:system_chomp(str)
  694. let ret = s:system(a:str)
  695. return v:shell_error ? '' : substitute(ret, '\n$', '', '')
  696. endfunction
  697. function! s:git_valid(spec, check_branch, cd)
  698. let ret = 1
  699. let msg = 'OK'
  700. if isdirectory(a:spec.dir)
  701. if a:cd | execute "cd " . s:esc(a:spec.dir) | endif
  702. let result = split(s:system("git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url"), '\n')
  703. let remote = result[-1]
  704. if v:shell_error
  705. let msg = join([remote, "PlugClean required."], "\n")
  706. let ret = 0
  707. elseif !s:compare_git_uri(remote, a:spec.uri)
  708. let msg = join(['Invalid URI: '.remote,
  709. \ 'Expected: '.a:spec.uri,
  710. \ "PlugClean required."], "\n")
  711. let ret = 0
  712. elseif a:check_branch
  713. let branch = result[0]
  714. if a:spec.branch !=# branch
  715. let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1')
  716. if a:spec.branch !=# tag
  717. let msg = printf('Invalid branch/tag: %s (expected: %s). Try PlugUpdate.',
  718. \ (empty(tag) ? branch : tag), a:spec.branch)
  719. let ret = 0
  720. endif
  721. endif
  722. endif
  723. if a:cd | cd - | endif
  724. else
  725. let msg = 'Not found'
  726. let ret = 0
  727. endif
  728. return [ret, msg]
  729. endfunction
  730. function! s:clean(force)
  731. call s:prepare()
  732. call append(0, 'Searching for unused plugins in '.g:plug_home)
  733. call append(1, '')
  734. " List of valid directories
  735. let dirs = []
  736. let [cnt, total] = [0, len(g:plugs)]
  737. for spec in values(g:plugs)
  738. if s:git_valid(spec, 0, 1)[0]
  739. call add(dirs, spec.dir)
  740. endif
  741. let cnt += 1
  742. call s:progress_bar(2, repeat('=', cnt), total)
  743. normal! 2G
  744. redraw
  745. endfor
  746. let allowed = {}
  747. for dir in dirs
  748. let allowed[dir] = 1
  749. for child in s:glob_dir(dir)
  750. let allowed[child] = 1
  751. endfor
  752. endfor
  753. let todo = []
  754. let found = sort(s:glob_dir(g:plug_home))
  755. while !empty(found)
  756. let f = remove(found, 0)
  757. if !has_key(allowed, f) && isdirectory(f)
  758. call add(todo, f)
  759. call append(line('$'), '- ' . f)
  760. let found = filter(found, 'stridx(v:val, f) != 0')
  761. end
  762. endwhile
  763. normal! G
  764. redraw
  765. if empty(todo)
  766. call append(line('$'), 'Already clean.')
  767. else
  768. call inputsave()
  769. let yes = a:force || (input("Proceed? (Y/N) ") =~? '^y')
  770. call inputrestore()
  771. if yes
  772. for dir in todo
  773. if isdirectory(dir)
  774. call system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(dir))
  775. endif
  776. endfor
  777. call append(line('$'), 'Removed.')
  778. else
  779. call append(line('$'), 'Cancelled.')
  780. endif
  781. endif
  782. normal! G
  783. endfunction
  784. function! s:upgrade()
  785. if executable('curl')
  786. let mee = s:shellesc(s:me)
  787. let new = s:shellesc(s:me . '.new')
  788. echo "Downloading ". s:plug_source
  789. redraw
  790. let mv = s:is_win ? 'move /Y' : 'mv -f'
  791. let cp = s:is_win ? 'copy /Y' : 'cp -f'
  792. call system(printf(
  793. \ "curl -fLo %s %s && ".cp." %s %s.old && ".mv." %s %s",
  794. \ new, s:plug_source, mee, mee, new, mee))
  795. if v:shell_error == 0
  796. unlet g:loaded_plug
  797. echo "Downloaded ". s:plug_source
  798. return 1
  799. else
  800. echoerr "Error upgrading vim-plug"
  801. return 0
  802. endif
  803. elseif has('ruby')
  804. echo "Downloading ". s:plug_source
  805. ruby << EOF
  806. require 'open-uri'
  807. require 'fileutils'
  808. me = VIM::evaluate('s:me')
  809. old = me + '.old'
  810. new = me + '.new'
  811. File.open(new, 'w') do |f|
  812. f << open(VIM::evaluate('s:plug_source')).read
  813. end
  814. FileUtils.cp me, old
  815. File.rename new, me
  816. EOF
  817. unlet g:loaded_plug
  818. echo "Downloaded ". s:plug_source
  819. return 1
  820. else
  821. echoerr "curl executable or ruby support not found"
  822. return 0
  823. endif
  824. endfunction
  825. function! s:status()
  826. call s:prepare()
  827. call append(0, 'Checking plugins')
  828. call append(1, '')
  829. let ecnt = 0
  830. let [cnt, total] = [0, len(g:plugs)]
  831. for [name, spec] in items(g:plugs)
  832. if isdirectory(spec.dir)
  833. let [valid, msg] = s:git_valid(spec, 1, 1)
  834. else
  835. let [valid, msg] = [0, 'Not found. Try PlugInstall.']
  836. endif
  837. let cnt += 1
  838. let ecnt += !valid
  839. call s:progress_bar(2, repeat('=', cnt), total)
  840. call append(3, s:format_message(valid, name, msg))
  841. normal! 2G
  842. redraw
  843. endfor
  844. call setline(1, 'Finished. '.ecnt.' error(s).')
  845. normal! gg
  846. endfunction
  847. function! s:is_preview_window_open()
  848. silent! wincmd P
  849. if &previewwindow
  850. wincmd p
  851. return 1
  852. endif
  853. return 0
  854. endfunction
  855. function! s:preview_commit()
  856. if b:plug_preview < 0
  857. let b:plug_preview = !s:is_preview_window_open()
  858. endif
  859. let sha = matchstr(getline('.'), '\(^ \)\@<=[0-9a-z]\{7}')
  860. if !empty(sha)
  861. let lnum = line('.')
  862. while lnum > 1
  863. let lnum -= 1
  864. let line = getline(lnum)
  865. let name = matchstr(line, '\(^- \)\@<=[^:]\+')
  866. if !empty(name)
  867. let dir = g:plugs[name].dir
  868. if isdirectory(dir)
  869. execute 'cd '.s:esc(dir)
  870. execute 'pedit '.sha
  871. wincmd P
  872. setlocal filetype=git buftype=nofile nobuflisted
  873. execute 'silent read !git show '.sha
  874. normal! ggdd
  875. wincmd p
  876. cd -
  877. endif
  878. break
  879. endif
  880. endwhile
  881. endif
  882. endfunction
  883. function! s:section(flags)
  884. call search('\(^- \)\@<=.', a:flags)
  885. endfunction
  886. function! s:diff()
  887. call s:prepare()
  888. call append(0, 'Collecting updated changes ...')
  889. normal! gg
  890. redraw
  891. let cnt = 0
  892. for [k, v] in items(g:plugs)
  893. if !isdirectory(v.dir)
  894. continue
  895. endif
  896. execute 'cd '.s:esc(v.dir)
  897. let diff = system('git log --pretty=format:"%h %s (%cr)" "HEAD@{0}...HEAD@{1}"')
  898. if !v:shell_error && !empty(diff)
  899. call append(1, '')
  900. call append(2, '- '.k.':')
  901. call append(3, map(split(diff, '\n'), '" ". v:val'))
  902. let cnt += 1
  903. normal! gg
  904. redraw
  905. endif
  906. cd -
  907. endfor
  908. call setline(1, cnt == 0 ? 'No updates.' : 'Last update:')
  909. nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
  910. normal! gg
  911. endfunction
  912. let &cpo = s:cpo_save
  913. unlet s:cpo_save