plug.vim 29 KB

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