plug.vim 30 KB

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