plug.vim 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034
  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(force, ...)
  234. let opts = { 'branch': 'master', 'frozen': 0 }
  235. if a:0 == 1
  236. let plugin = a:1
  237. elseif a:0 == 2
  238. let plugin = a:1
  239. if type(a:2) == 1
  240. let opts.branch = a:2
  241. elseif type(a:2) == 4
  242. call extend(opts, a:2)
  243. if has_key(opts, 'tag')
  244. let opts.branch = remove(opts, 'tag')
  245. endif
  246. else
  247. echoerr "Invalid argument type (expected: string or dictionary)"
  248. return
  249. endif
  250. else
  251. echoerr "Invalid number of arguments (1..2)"
  252. return
  253. endif
  254. let plugin = substitute(plugin, '[/\\]*$', '', '')
  255. let name = substitute(split(plugin, '/')[-1], '\.git$', '', '')
  256. if !a:force && has_key(g:plugs, name)
  257. let s:extended[name] = g:plugs[name]
  258. return
  259. endif
  260. if plugin[0] =~ '[/$~]' || plugin =~? '^[a-z]:'
  261. let spec = extend(opts, { 'dir': s:dirpath(expand(plugin)) })
  262. else
  263. if plugin =~ ':'
  264. let uri = plugin
  265. else
  266. if plugin !~ '/'
  267. let plugin = 'vim-scripts/'. plugin
  268. endif
  269. let uri = 'https://git:@github.com/' . plugin . '.git'
  270. endif
  271. let dir = s:dirpath( fnamemodify(join([g:plug_home, name], '/'), ':p') )
  272. let spec = extend(opts, { 'dir': dir, 'uri': uri })
  273. endif
  274. let g:plugs[name] = spec
  275. if !a:force
  276. let s:extended[name] = spec
  277. endif
  278. let g:plugs_order += [name]
  279. endfunction
  280. function! s:install(...)
  281. call s:update_impl(0, a:000)
  282. endfunction
  283. function! s:update(...)
  284. call s:update_impl(1, a:000)
  285. endfunction
  286. function! s:apply()
  287. for spec in values(g:plugs)
  288. let docd = join([spec.dir, 'doc'], '/')
  289. if isdirectory(docd)
  290. execute "helptags ". join([spec.dir, 'doc'], '/')
  291. endif
  292. endfor
  293. runtime! plugin/*.vim
  294. runtime! after/*.vim
  295. silent! source $MYVIMRC
  296. endfunction
  297. function! s:syntax()
  298. syntax clear
  299. syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
  300. syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
  301. syn match plugNumber /[0-9]\+[0-9.]*/ contained
  302. syn match plugBracket /[[\]]/ contained
  303. syn match plugX /x/ contained
  304. syn match plugDash /^-/
  305. syn match plugPlus /^+/
  306. syn match plugStar /^*/
  307. syn match plugName /\(^- \)\@<=[^:]*/
  308. syn match plugInstall /\(^+ \)\@<=[^:]*/
  309. syn match plugUpdate /\(^* \)\@<=[^:]*/
  310. syn match plugCommit /^ [0-9a-z]\{7} .*/ contains=plugRelDate,plugSha
  311. syn match plugSha /\(^ \)\@<=[0-9a-z]\{7}/ contained
  312. syn match plugRelDate /([^)]*)$/ contained
  313. syn match plugError /^x.*/
  314. syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
  315. hi def link plug1 Title
  316. hi def link plug2 Repeat
  317. hi def link plugX Exception
  318. hi def link plugBracket Structure
  319. hi def link plugNumber Number
  320. hi def link plugDash Special
  321. hi def link plugPlus Constant
  322. hi def link plugStar Boolean
  323. hi def link plugName Label
  324. hi def link plugInstall Function
  325. hi def link plugUpdate Type
  326. hi def link plugError Error
  327. hi def link plugRelDate Comment
  328. hi def link plugSha Identifier
  329. endfunction
  330. function! s:lpad(str, len)
  331. return a:str . repeat(' ', a:len - len(a:str))
  332. endfunction
  333. function! s:lastline(msg)
  334. let lines = split(a:msg, '\n')
  335. return get(lines, -1, '')
  336. endfunction
  337. function! s:prepare()
  338. if bufexists(s:plug_buf)
  339. let winnr = bufwinnr(s:plug_buf)
  340. if winnr < 0
  341. vertical topleft new
  342. execute 'buffer ' . s:plug_buf
  343. else
  344. execute winnr . 'wincmd w'
  345. endif
  346. silent %d _
  347. else
  348. vertical topleft new
  349. nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>q<cr>
  350. nnoremap <silent> <buffer> D :PlugDiff<cr>
  351. nnoremap <silent> <buffer> S :PlugStatus<cr>
  352. nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
  353. nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
  354. let b:plug_preview = -1
  355. let s:plug_buf = winbufnr(0)
  356. call s:assign_name()
  357. endif
  358. silent! unmap <buffer> <cr>
  359. setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline
  360. setf vim-plug
  361. call s:syntax()
  362. endfunction
  363. function! s:assign_name()
  364. " Assign buffer name
  365. let prefix = '[Plugins]'
  366. let name = prefix
  367. let idx = 2
  368. while bufexists(name)
  369. let name = printf("%s (%s)", prefix, idx)
  370. let idx = idx + 1
  371. endwhile
  372. silent! execute "f ".fnameescape(name)
  373. endfunction
  374. function! s:finish(pull)
  375. call append(3, '- Finishing ... ')
  376. redraw
  377. call s:apply()
  378. call s:syntax()
  379. call setline(4, getline(4) . 'Done!')
  380. normal! gg
  381. redraw
  382. if a:pull
  383. echo "Press 'D' to see the updated changes."
  384. endif
  385. endfunction
  386. function! s:is_managed(name)
  387. return has_key(g:plugs[a:name], 'uri')
  388. endfunction
  389. function! s:names(...)
  390. return filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')
  391. endfunction
  392. function! s:update_impl(pull, args) abort
  393. let args = copy(a:args)
  394. let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
  395. \ remove(args, -1) : get(g:, 'plug_threads', 16)
  396. let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
  397. let todo = empty(args) ? filter(managed, '!v:val.frozen') :
  398. \ filter(managed, 'index(args, v:key) >= 0')
  399. if empty(todo)
  400. echohl WarningMsg
  401. echo 'No plugin to '. (a:pull ? 'update' : 'install') . '.'
  402. echohl None
  403. return
  404. endif
  405. call s:prepare()
  406. call append(0, a:pull ? 'Updating plugins' : 'Installing plugins')
  407. call append(1, '['. s:lpad('', len(todo)) .']')
  408. normal! 2G
  409. redraw
  410. let len = len(g:plugs)
  411. if has('ruby') && threads > 1
  412. try
  413. call s:update_parallel(a:pull, todo, threads)
  414. catch
  415. let lines = getline(4, '$')
  416. let printed = {}
  417. silent 4,$d
  418. for line in lines
  419. let name = get(matchlist(line, '^. \([^:]\+\):'), 1, '')
  420. if empty(name) || !has_key(printed, name)
  421. let printed[name] = 1
  422. call append('$', line)
  423. endif
  424. endfor
  425. echoerr v:exception
  426. endtry
  427. else
  428. call s:update_serial(a:pull, todo)
  429. endif
  430. if len(g:plugs) > len
  431. call plug#end()
  432. endif
  433. call s:finish(a:pull)
  434. endfunction
  435. function! s:extend(names)
  436. let s:extended = {}
  437. try
  438. command! -nargs=+ Plug call s:add(0, <args>)
  439. for name in a:names
  440. let plugfile = globpath(s:rtp(g:plugs[name]), s:plug_file)
  441. if filereadable(plugfile)
  442. execute "source ". s:esc(plugfile)
  443. endif
  444. endfor
  445. finally
  446. command! -nargs=+ Plug call s:add(1, <args>)
  447. endtry
  448. return s:extended
  449. endfunction
  450. function! s:update_progress(pull, cnt, bar, total)
  451. call setline(1, (a:pull ? 'Updating' : 'Installing').
  452. \ " plugins (".a:cnt."/".a:total.")")
  453. call s:progress_bar(2, a:bar, a:total)
  454. normal! 2G
  455. redraw
  456. endfunction
  457. function! s:update_serial(pull, todo)
  458. let st = reltime()
  459. let base = g:plug_home
  460. let todo = copy(a:todo)
  461. let total = len(todo)
  462. let done = {}
  463. let bar = ''
  464. while !empty(todo)
  465. for [name, spec] in items(todo)
  466. let done[name] = 1
  467. if isdirectory(spec.dir)
  468. execute 'cd '.s:esc(spec.dir)
  469. let [valid, msg] = s:git_valid(spec, 0, 0)
  470. if valid
  471. let result = a:pull ?
  472. \ s:system(
  473. \ printf('git checkout -q %s 2>&1 && git pull origin %s 2>&1 && git submodule update --init --recursive 2>&1',
  474. \ s:shellesc(spec.branch), s:shellesc(spec.branch))) : 'Already installed'
  475. let error = a:pull ? v:shell_error != 0 : 0
  476. else
  477. let result = msg
  478. let error = 1
  479. endif
  480. cd -
  481. else
  482. if !isdirectory(base)
  483. call mkdir(base, 'p')
  484. endif
  485. let result = s:system(
  486. \ printf('git clone --recursive %s -b %s %s 2>&1 && cd %s && git submodule update --init --recursive 2>&1',
  487. \ s:shellesc(spec.uri),
  488. \ s:shellesc(spec.branch),
  489. \ s:shellesc(substitute(spec.dir, '[\/]\+$', '', '')),
  490. \ s:shellesc(spec.dir)))
  491. let error = v:shell_error != 0
  492. endif
  493. let bar .= error ? 'x' : '='
  494. call append(3, s:format_message(!error, name, result))
  495. call s:update_progress(a:pull, len(done), bar, total)
  496. endfor
  497. let extended = s:extend(keys(todo))
  498. if !empty(extended)
  499. let todo = filter(extended, '!has_key(done, v:key)')
  500. let total += len(todo)
  501. call s:update_progress(a:pull, len(done), bar, total)
  502. else
  503. break
  504. endif
  505. endwhile
  506. call setline(1, "Updated. Elapsed time: " . split(reltimestr(reltime(st)))[0] . ' sec.')
  507. endfunction
  508. function! s:update_parallel(pull, todo, threads)
  509. ruby << EOF
  510. module PlugStream
  511. SEP = ["\r", "\n", nil]
  512. def get_line
  513. buffer = ''
  514. loop do
  515. char = readchar rescue return
  516. if SEP.include? char.chr
  517. buffer << $/
  518. break
  519. else
  520. buffer << char
  521. end
  522. end
  523. buffer
  524. end
  525. end unless defined?(PlugStream)
  526. def esc arg
  527. %["#{arg.gsub('"', '\"')}"]
  528. end
  529. require 'set'
  530. require 'thread'
  531. require 'fileutils'
  532. require 'timeout'
  533. running = true
  534. st = Time.now
  535. iswin = VIM::evaluate('s:is_win').to_i == 1
  536. pull = VIM::evaluate('a:pull').to_i == 1
  537. base = VIM::evaluate('g:plug_home')
  538. all = VIM::evaluate('copy(a:todo)')
  539. limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
  540. nthr = VIM::evaluate('a:threads').to_i
  541. maxy = VIM::evaluate('winheight(".")').to_i
  542. cd = iswin ? 'cd /d' : 'cd'
  543. tot = VIM::evaluate('len(a:todo)') || 0
  544. bar = ''
  545. skip = 'Already installed'
  546. mtx = Mutex.new
  547. take1 = proc { mtx.synchronize { running && all.shift } }
  548. logh = proc {
  549. cnt = bar.length
  550. $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
  551. $curbuf[2] = '[' + bar.ljust(tot) + ']'
  552. VIM::command('normal! 2G')
  553. VIM::command('redraw') unless iswin
  554. }
  555. where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
  556. log = proc { |name, result, type|
  557. mtx.synchronize do
  558. ing = ![true, false].include?(type)
  559. bar += type ? '=' : 'x' unless ing
  560. b = case type
  561. when :install then '+' when :update then '*'
  562. when true, nil then '-' else 'x' end
  563. result =
  564. if type || type.nil?
  565. ["#{b} #{name}: #{result.lines.to_a.last}"]
  566. elsif result =~ /^Interrupted|^Timeout/
  567. ["#{b} #{name}: #{result}"]
  568. else
  569. ["#{b} #{name}"] + result.lines.map { |l| " " << l }
  570. end
  571. if lnum = where.call(name)
  572. $curbuf.delete lnum
  573. lnum = 4 if ing && lnum > maxy
  574. end
  575. result.each_with_index do |line, offset|
  576. $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
  577. end
  578. logh.call
  579. end
  580. }
  581. bt = proc { |cmd, name, type|
  582. begin
  583. fd = nil
  584. data = ''
  585. if iswin
  586. Timeout::timeout(limit) do
  587. tmp = VIM::evaluate('tempname()')
  588. system("#{cmd} > #{tmp}")
  589. data = File.read(tmp).chomp
  590. File.unlink tmp rescue nil
  591. end
  592. else
  593. fd = IO.popen(cmd).extend(PlugStream)
  594. first_line = true
  595. log_prob = 1.0 / nthr
  596. while line = Timeout::timeout(limit) { fd.get_line }
  597. data << line
  598. log.call name, line.chomp, type if name && (first_line || rand < log_prob)
  599. first_line = false
  600. end
  601. fd.close
  602. end
  603. [$? == 0, data.chomp]
  604. rescue Timeout::Error, Interrupt => e
  605. if fd && !fd.closed?
  606. pids = [fd.pid]
  607. unless `which pgrep`.empty?
  608. children = pids
  609. until children.empty?
  610. children = children.map { |pid|
  611. `pgrep -P #{pid}`.lines.map { |l| l.chomp }
  612. }.flatten
  613. pids += children
  614. end
  615. end
  616. pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
  617. fd.close
  618. end
  619. [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
  620. end
  621. }
  622. main = Thread.current
  623. threads = []
  624. watcher = Thread.new {
  625. while VIM::evaluate('getchar(1)')
  626. sleep 0.1
  627. end
  628. mtx.synchronize do
  629. running = false
  630. threads.each { |t| t.raise Interrupt }
  631. end
  632. threads.each { |t| t.join rescue nil }
  633. main.kill
  634. }
  635. processed = Set.new
  636. progress = iswin ? '' : '--progress'
  637. until all.empty?
  638. names = all.keys
  639. processed.merge names
  640. [names.length, nthr].min.times do
  641. mtx.synchronize do
  642. threads << Thread.new {
  643. while pair = take1.call
  644. name = pair.first
  645. dir, uri, branch = pair.last.values_at *%w[dir uri branch]
  646. branch = esc branch
  647. subm = "git submodule update --init --recursive 2>&1"
  648. ok, result =
  649. if File.directory? dir
  650. dir = esc dir
  651. ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url", nil, nil
  652. current_uri = data.lines.to_a.last
  653. if !ret
  654. if data =~ /^Interrupted|^Timeout/
  655. [false, data]
  656. else
  657. [false, [data.chomp, "PlugClean required."].join($/)]
  658. end
  659. elsif current_uri.sub(/git:@/, '') != uri.sub(/git:@/, '')
  660. [false, ["Invalid URI: #{current_uri}",
  661. "Expected: #{uri}",
  662. "PlugClean required."].join($/)]
  663. else
  664. if pull
  665. log.call name, 'Updating ...', :update
  666. bt.call "#{cd} #{dir} && git checkout -q #{branch} 2>&1 && (git pull origin #{branch} #{progress} 2>&1 && #{subm})", name, :update
  667. else
  668. [true, skip]
  669. end
  670. end
  671. else
  672. FileUtils.mkdir_p(base)
  673. d = esc dir.sub(%r{[\\/]+$}, '')
  674. log.call name, 'Installing ...', :install
  675. bt.call "(git clone #{progress} --recursive #{uri} -b #{branch} #{d} 2>&1 && cd #{esc dir} && #{subm})", name, :install
  676. end
  677. log.call name, result, ok
  678. end
  679. } if running
  680. end
  681. end
  682. threads.each { |t| t.join rescue nil }
  683. mtx.synchronize { threads.clear }
  684. extended = Hash[(VIM::evaluate("s:extend(#{names.inspect})") || {}).reject { |k, _|
  685. processed.include? k
  686. }]
  687. tot += extended.length
  688. all.merge!(extended)
  689. logh.call
  690. end
  691. watcher.kill
  692. $curbuf[1] = "Updated. Elapsed time: #{"%.6f" % (Time.now - st)} sec."
  693. EOF
  694. endfunction
  695. function! s:shellesc(arg)
  696. return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
  697. endfunction
  698. function! s:glob_dir(path)
  699. return map(filter(split(globpath(a:path, '**'), '\n'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
  700. endfunction
  701. function! s:progress_bar(line, bar, total)
  702. call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
  703. endfunction
  704. function! s:compare_git_uri(a, b)
  705. let a = substitute(a:a, 'git:@', '', '')
  706. let b = substitute(a:b, 'git:@', '', '')
  707. return a ==# b
  708. endfunction
  709. function! s:format_message(ok, name, message)
  710. if a:ok
  711. return [printf('- %s: %s', a:name, s:lastline(a:message))]
  712. else
  713. let lines = map(split(a:message, '\n'), '" ".v:val')
  714. return extend([printf('x %s:', a:name)], lines)
  715. endif
  716. endfunction
  717. function! s:system(cmd)
  718. return system(s:is_win ? '('.a:cmd.')' : a:cmd)
  719. endfunction
  720. function! s:system_chomp(str)
  721. let ret = s:system(a:str)
  722. return v:shell_error ? '' : substitute(ret, '\n$', '', '')
  723. endfunction
  724. function! s:git_valid(spec, check_branch, cd)
  725. let ret = 1
  726. let msg = 'OK'
  727. if isdirectory(a:spec.dir)
  728. if a:cd | execute "cd " . s:esc(a:spec.dir) | endif
  729. let result = split(s:system("git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url"), '\n')
  730. let remote = result[-1]
  731. if v:shell_error
  732. let msg = join([remote, "PlugClean required."], "\n")
  733. let ret = 0
  734. elseif !s:compare_git_uri(remote, a:spec.uri)
  735. let msg = join(['Invalid URI: '.remote,
  736. \ 'Expected: '.a:spec.uri,
  737. \ "PlugClean required."], "\n")
  738. let ret = 0
  739. elseif a:check_branch
  740. let branch = result[0]
  741. if a:spec.branch !=# branch
  742. let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1')
  743. if a:spec.branch !=# tag
  744. let msg = printf('Invalid branch/tag: %s (expected: %s). Try PlugUpdate.',
  745. \ (empty(tag) ? branch : tag), a:spec.branch)
  746. let ret = 0
  747. endif
  748. endif
  749. endif
  750. if a:cd | cd - | endif
  751. else
  752. let msg = 'Not found'
  753. let ret = 0
  754. endif
  755. return [ret, msg]
  756. endfunction
  757. function! s:clean(force)
  758. call s:prepare()
  759. call append(0, 'Searching for unused plugins in '.g:plug_home)
  760. call append(1, '')
  761. " List of valid directories
  762. let dirs = []
  763. let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
  764. let [cnt, total] = [0, len(managed)]
  765. for spec in values(managed)
  766. if s:git_valid(spec, 0, 1)[0]
  767. call add(dirs, spec.dir)
  768. endif
  769. let cnt += 1
  770. call s:progress_bar(2, repeat('=', cnt), total)
  771. normal! 2G
  772. redraw
  773. endfor
  774. let allowed = {}
  775. for dir in dirs
  776. let allowed[dir] = 1
  777. for child in s:glob_dir(dir)
  778. let allowed[child] = 1
  779. endfor
  780. endfor
  781. let todo = []
  782. let found = sort(s:glob_dir(g:plug_home))
  783. while !empty(found)
  784. let f = remove(found, 0)
  785. if !has_key(allowed, f) && isdirectory(f)
  786. call add(todo, f)
  787. call append(line('$'), '- ' . f)
  788. let found = filter(found, 'stridx(v:val, f) != 0')
  789. end
  790. endwhile
  791. normal! G
  792. redraw
  793. if empty(todo)
  794. call append(line('$'), 'Already clean.')
  795. else
  796. call inputsave()
  797. let yes = a:force || (input("Proceed? (Y/N) ") =~? '^y')
  798. call inputrestore()
  799. if yes
  800. for dir in todo
  801. if isdirectory(dir)
  802. call system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(dir))
  803. endif
  804. endfor
  805. call append(line('$'), 'Removed.')
  806. else
  807. call append(line('$'), 'Cancelled.')
  808. endif
  809. endif
  810. normal! G
  811. endfunction
  812. function! s:upgrade()
  813. if executable('curl')
  814. let mee = s:shellesc(s:me)
  815. let new = s:shellesc(s:me . '.new')
  816. echo "Downloading ". s:plug_source
  817. redraw
  818. let mv = s:is_win ? 'move /Y' : 'mv -f'
  819. let cp = s:is_win ? 'copy /Y' : 'cp -f'
  820. call system(printf(
  821. \ "curl -fLo %s %s && ".cp." %s %s.old && ".mv." %s %s",
  822. \ new, s:plug_source, mee, mee, new, mee))
  823. if v:shell_error == 0
  824. unlet g:loaded_plug
  825. echo "Downloaded ". s:plug_source
  826. return 1
  827. else
  828. echoerr "Error upgrading vim-plug"
  829. return 0
  830. endif
  831. elseif has('ruby')
  832. echo "Downloading ". s:plug_source
  833. ruby << EOF
  834. require 'open-uri'
  835. require 'fileutils'
  836. me = VIM::evaluate('s:me')
  837. old = me + '.old'
  838. new = me + '.new'
  839. File.open(new, 'w') do |f|
  840. f << open(VIM::evaluate('s:plug_source')).read
  841. end
  842. FileUtils.cp me, old
  843. File.rename new, me
  844. EOF
  845. unlet g:loaded_plug
  846. echo "Downloaded ". s:plug_source
  847. return 1
  848. else
  849. echoerr "curl executable or ruby support not found"
  850. return 0
  851. endif
  852. endfunction
  853. function! s:status()
  854. call s:prepare()
  855. call append(0, 'Checking plugins')
  856. call append(1, '')
  857. let ecnt = 0
  858. let [cnt, total] = [0, len(g:plugs)]
  859. for [name, spec] in items(g:plugs)
  860. if has_key(spec, 'uri')
  861. if isdirectory(spec.dir)
  862. let [valid, msg] = s:git_valid(spec, 1, 1)
  863. else
  864. let [valid, msg] = [0, 'Not found. Try PlugInstall.']
  865. endif
  866. else
  867. if isdirectory(spec.dir)
  868. let [valid, msg] = [1, 'OK']
  869. else
  870. let [valid, msg] = [0, 'Not found.']
  871. endif
  872. endif
  873. let cnt += 1
  874. let ecnt += !valid
  875. call s:progress_bar(2, repeat('=', cnt), total)
  876. call append(3, s:format_message(valid, name, msg))
  877. normal! 2G
  878. redraw
  879. endfor
  880. call setline(1, 'Finished. '.ecnt.' error(s).')
  881. normal! gg
  882. endfunction
  883. function! s:is_preview_window_open()
  884. silent! wincmd P
  885. if &previewwindow
  886. wincmd p
  887. return 1
  888. endif
  889. return 0
  890. endfunction
  891. function! s:preview_commit()
  892. if b:plug_preview < 0
  893. let b:plug_preview = !s:is_preview_window_open()
  894. endif
  895. let sha = matchstr(getline('.'), '\(^ \)\@<=[0-9a-z]\{7}')
  896. if !empty(sha)
  897. let lnum = line('.')
  898. while lnum > 1
  899. let lnum -= 1
  900. let line = getline(lnum)
  901. let name = matchstr(line, '\(^- \)\@<=[^:]\+')
  902. if !empty(name)
  903. let dir = g:plugs[name].dir
  904. if isdirectory(dir)
  905. execute 'cd '.s:esc(dir)
  906. execute 'pedit '.sha
  907. wincmd P
  908. setlocal filetype=git buftype=nofile nobuflisted
  909. execute 'silent read !git show '.sha
  910. normal! ggdd
  911. wincmd p
  912. cd -
  913. endif
  914. break
  915. endif
  916. endwhile
  917. endif
  918. endfunction
  919. function! s:section(flags)
  920. call search('\(^- \)\@<=.', a:flags)
  921. endfunction
  922. function! s:diff()
  923. call s:prepare()
  924. call append(0, 'Collecting updated changes ...')
  925. normal! gg
  926. redraw
  927. let cnt = 0
  928. for [k, v] in items(g:plugs)
  929. if !isdirectory(v.dir)
  930. continue
  931. endif
  932. execute 'cd '.s:esc(v.dir)
  933. let diff = system('git log --pretty=format:"%h %s (%cr)" "HEAD@{0}...HEAD@{1}"')
  934. if !v:shell_error && !empty(diff)
  935. call append(1, '')
  936. call append(2, '- '.k.':')
  937. call append(3, map(split(diff, '\n'), '" ". v:val'))
  938. let cnt += 1
  939. normal! gg
  940. redraw
  941. endif
  942. cd -
  943. endfor
  944. call setline(1, cnt == 0 ? 'No updates.' : 'Last update:')
  945. nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
  946. normal! gg
  947. endfunction
  948. let &cpo = s:cpo_save
  949. unlet s:cpo_save