plug.vim 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069
  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. nthr = VIM::evaluate('a:threads').to_i
  562. maxy = VIM::evaluate('winheight(".")').to_i
  563. cd = iswin ? 'cd /d' : 'cd'
  564. tot = VIM::evaluate('len(a:todo)') || 0
  565. bar = ''
  566. skip = 'Already installed'
  567. mtx = Mutex.new
  568. take1 = proc { mtx.synchronize { running && all.shift } }
  569. logh = proc {
  570. cnt = bar.length
  571. $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
  572. $curbuf[2] = '[' + bar.ljust(tot) + ']'
  573. VIM::command('normal! 2G')
  574. VIM::command('redraw') unless iswin
  575. }
  576. where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
  577. log = proc { |name, result, type|
  578. mtx.synchronize do
  579. ing = ![true, false].include?(type)
  580. bar += type ? '=' : 'x' unless ing
  581. b = case type
  582. when :install then '+' when :update then '*'
  583. when true, nil then '-' else 'x' end
  584. result =
  585. if type || type.nil?
  586. ["#{b} #{name}: #{result.lines.to_a.last}"]
  587. elsif result =~ /^Interrupted|^Timeout/
  588. ["#{b} #{name}: #{result}"]
  589. else
  590. ["#{b} #{name}"] + result.lines.map { |l| " " << l }
  591. end
  592. if lnum = where.call(name)
  593. $curbuf.delete lnum
  594. lnum = 4 if ing && lnum > maxy
  595. end
  596. result.each_with_index do |line, offset|
  597. $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
  598. end
  599. logh.call
  600. end
  601. }
  602. bt = proc { |cmd, name, type|
  603. begin
  604. fd = nil
  605. data = ''
  606. if iswin
  607. Timeout::timeout(limit) do
  608. tmp = VIM::evaluate('tempname()')
  609. system("#{cmd} > #{tmp}")
  610. data = File.read(tmp).chomp
  611. File.unlink tmp rescue nil
  612. end
  613. else
  614. fd = IO.popen(cmd).extend(PlugStream)
  615. first_line = true
  616. log_prob = 1.0 / nthr
  617. while line = Timeout::timeout(limit) { fd.get_line }
  618. data << line
  619. log.call name, line.chomp, type if name && (first_line || rand < log_prob)
  620. first_line = false
  621. end
  622. fd.close
  623. end
  624. [$? == 0, data.chomp]
  625. rescue Timeout::Error, Interrupt => e
  626. if fd && !fd.closed?
  627. pids = [fd.pid]
  628. unless `which pgrep`.empty?
  629. children = pids
  630. until children.empty?
  631. children = children.map { |pid|
  632. `pgrep -P #{pid}`.lines.map { |l| l.chomp }
  633. }.flatten
  634. pids += children
  635. end
  636. end
  637. pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
  638. fd.close
  639. end
  640. [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
  641. end
  642. }
  643. main = Thread.current
  644. threads = []
  645. watcher = Thread.new {
  646. while VIM::evaluate('getchar(1)')
  647. sleep 0.1
  648. end
  649. mtx.synchronize do
  650. running = false
  651. threads.each { |t| t.raise Interrupt }
  652. end
  653. threads.each { |t| t.join rescue nil }
  654. main.kill
  655. }
  656. refresh = Thread.new {
  657. while true
  658. mtx.synchronize do
  659. break unless running
  660. VIM::command('normal! a')
  661. end
  662. sleep 0.2
  663. end
  664. } if VIM::evaluate('s:mac_gui') == 1
  665. processed = Set.new
  666. progress = iswin ? '' : '--progress'
  667. until all.empty?
  668. names = all.keys
  669. processed.merge names
  670. [names.length, nthr].min.times do
  671. mtx.synchronize do
  672. threads << Thread.new {
  673. while pair = take1.call
  674. name = pair.first
  675. dir, uri, branch = pair.last.values_at *%w[dir uri branch]
  676. branch = esc branch
  677. subm = "git submodule update --init --recursive 2>&1"
  678. ok, result =
  679. if File.directory? dir
  680. dir = esc dir
  681. ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url", nil, nil
  682. current_uri = data.lines.to_a.last
  683. if !ret
  684. if data =~ /^Interrupted|^Timeout/
  685. [false, data]
  686. else
  687. [false, [data.chomp, "PlugClean required."].join($/)]
  688. end
  689. elsif current_uri.sub(/git:@/, '') != uri.sub(/git:@/, '')
  690. [false, ["Invalid URI: #{current_uri}",
  691. "Expected: #{uri}",
  692. "PlugClean required."].join($/)]
  693. else
  694. if pull
  695. log.call name, 'Updating ...', :update
  696. bt.call "#{cd} #{dir} && git checkout -q #{branch} 2>&1 && (git pull origin #{branch} #{progress} 2>&1 && #{subm})", name, :update
  697. else
  698. [true, skip]
  699. end
  700. end
  701. else
  702. FileUtils.mkdir_p(base)
  703. d = esc dir.sub(%r{[\\/]+$}, '')
  704. log.call name, 'Installing ...', :install
  705. bt.call "(git clone #{progress} --recursive #{uri} -b #{branch} #{d} 2>&1 && cd #{esc dir} && #{subm})", name, :install
  706. end
  707. log.call name, result, ok
  708. end
  709. } if running
  710. end
  711. end
  712. threads.each { |t| t.join rescue nil }
  713. mtx.synchronize { threads.clear }
  714. extended = Hash[(VIM::evaluate("s:extend(#{names.inspect})") || {}).reject { |k, _|
  715. processed.include? k
  716. }]
  717. tot += extended.length
  718. all.merge!(extended)
  719. logh.call
  720. end
  721. refresh.kill if refresh
  722. watcher.kill
  723. $curbuf[1] = "Updated. Elapsed time: #{"%.6f" % (Time.now - st)} sec."
  724. EOF
  725. endfunction
  726. function! s:shellesc(arg)
  727. return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
  728. endfunction
  729. function! s:glob_dir(path)
  730. return map(filter(split(globpath(a:path, '**'), '\n'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
  731. endfunction
  732. function! s:progress_bar(line, bar, total)
  733. call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
  734. endfunction
  735. function! s:compare_git_uri(a, b)
  736. let a = substitute(a:a, 'git:@', '', '')
  737. let b = substitute(a:b, 'git:@', '', '')
  738. return a ==# b
  739. endfunction
  740. function! s:format_message(ok, name, message)
  741. if a:ok
  742. return [printf('- %s: %s', a:name, s:lastline(a:message))]
  743. else
  744. let lines = map(split(a:message, '\n'), '" ".v:val')
  745. return extend([printf('x %s:', a:name)], lines)
  746. endif
  747. endfunction
  748. function! s:system(cmd)
  749. return system(s:is_win ? '('.a:cmd.')' : a:cmd)
  750. endfunction
  751. function! s:system_chomp(str)
  752. let ret = s:system(a:str)
  753. return v:shell_error ? '' : substitute(ret, '\n$', '', '')
  754. endfunction
  755. function! s:git_valid(spec, check_branch, cd)
  756. let ret = 1
  757. let msg = 'OK'
  758. if isdirectory(a:spec.dir)
  759. if a:cd | execute "cd " . s:esc(a:spec.dir) | endif
  760. let result = split(s:system("git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url"), '\n')
  761. let remote = result[-1]
  762. if v:shell_error
  763. let msg = join([remote, "PlugClean required."], "\n")
  764. let ret = 0
  765. elseif !s:compare_git_uri(remote, a:spec.uri)
  766. let msg = join(['Invalid URI: '.remote,
  767. \ 'Expected: '.a:spec.uri,
  768. \ "PlugClean required."], "\n")
  769. let ret = 0
  770. elseif a:check_branch
  771. let branch = result[0]
  772. if a:spec.branch !=# branch
  773. let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1')
  774. if a:spec.branch !=# tag
  775. let msg = printf('Invalid branch/tag: %s (expected: %s). Try PlugUpdate.',
  776. \ (empty(tag) ? branch : tag), a:spec.branch)
  777. let ret = 0
  778. endif
  779. endif
  780. endif
  781. if a:cd | cd - | endif
  782. else
  783. let msg = 'Not found'
  784. let ret = 0
  785. endif
  786. return [ret, msg]
  787. endfunction
  788. function! s:clean(force)
  789. call s:prepare()
  790. call append(0, 'Searching for unused plugins in '.g:plug_home)
  791. call append(1, '')
  792. " List of valid directories
  793. let dirs = []
  794. let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
  795. let [cnt, total] = [0, len(managed)]
  796. for spec in values(managed)
  797. if s:git_valid(spec, 0, 1)[0]
  798. call add(dirs, spec.dir)
  799. endif
  800. let cnt += 1
  801. call s:progress_bar(2, repeat('=', cnt), total)
  802. normal! 2G
  803. redraw
  804. endfor
  805. let allowed = {}
  806. for dir in dirs
  807. let allowed[dir] = 1
  808. for child in s:glob_dir(dir)
  809. let allowed[child] = 1
  810. endfor
  811. endfor
  812. let todo = []
  813. let found = sort(s:glob_dir(g:plug_home))
  814. while !empty(found)
  815. let f = remove(found, 0)
  816. if !has_key(allowed, f) && isdirectory(f)
  817. call add(todo, f)
  818. call append(line('$'), '- ' . f)
  819. let found = filter(found, 'stridx(v:val, f) != 0')
  820. end
  821. endwhile
  822. normal! G
  823. redraw
  824. if empty(todo)
  825. call append(line('$'), 'Already clean.')
  826. else
  827. call inputsave()
  828. let yes = a:force || (input("Proceed? (Y/N) ") =~? '^y')
  829. call inputrestore()
  830. if yes
  831. for dir in todo
  832. if isdirectory(dir)
  833. call system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(dir))
  834. endif
  835. endfor
  836. call append(line('$'), 'Removed.')
  837. else
  838. call append(line('$'), 'Cancelled.')
  839. endif
  840. endif
  841. normal! G
  842. endfunction
  843. function! s:upgrade()
  844. if executable('curl')
  845. let mee = s:shellesc(s:me)
  846. let new = s:shellesc(s:me . '.new')
  847. echo "Downloading ". s:plug_source
  848. redraw
  849. let mv = s:is_win ? 'move /Y' : 'mv -f'
  850. let cp = s:is_win ? 'copy /Y' : 'cp -f'
  851. call system(printf(
  852. \ "curl -fLo %s %s && ".cp." %s %s.old && ".mv." %s %s",
  853. \ new, s:plug_source, mee, mee, new, mee))
  854. if v:shell_error == 0
  855. unlet g:loaded_plug
  856. echo "Downloaded ". s:plug_source
  857. return 1
  858. else
  859. echoerr "Error upgrading vim-plug"
  860. return 0
  861. endif
  862. elseif has('ruby')
  863. echo "Downloading ". s:plug_source
  864. ruby << EOF
  865. require 'open-uri'
  866. require 'fileutils'
  867. me = VIM::evaluate('s:me')
  868. old = me + '.old'
  869. new = me + '.new'
  870. File.open(new, 'w') do |f|
  871. f << open(VIM::evaluate('s:plug_source')).read
  872. end
  873. FileUtils.cp me, old
  874. File.rename new, me
  875. EOF
  876. unlet g:loaded_plug
  877. echo "Downloaded ". s:plug_source
  878. return 1
  879. else
  880. echoerr "curl executable or ruby support not found"
  881. return 0
  882. endif
  883. endfunction
  884. function! s:status()
  885. call s:prepare()
  886. call append(0, 'Checking plugins')
  887. call append(1, '')
  888. let ecnt = 0
  889. let [cnt, total] = [0, len(g:plugs)]
  890. for [name, spec] in items(g:plugs)
  891. if has_key(spec, 'uri')
  892. if isdirectory(spec.dir)
  893. let [valid, msg] = s:git_valid(spec, 1, 1)
  894. else
  895. let [valid, msg] = [0, 'Not found. Try PlugInstall.']
  896. endif
  897. else
  898. if isdirectory(spec.dir)
  899. let [valid, msg] = [1, 'OK']
  900. else
  901. let [valid, msg] = [0, 'Not found.']
  902. endif
  903. endif
  904. let cnt += 1
  905. let ecnt += !valid
  906. call s:progress_bar(2, repeat('=', cnt), total)
  907. call append(3, s:format_message(valid, name, msg))
  908. normal! 2G
  909. redraw
  910. endfor
  911. call setline(1, 'Finished. '.ecnt.' error(s).')
  912. normal! gg
  913. endfunction
  914. function! s:is_preview_window_open()
  915. silent! wincmd P
  916. if &previewwindow
  917. wincmd p
  918. return 1
  919. endif
  920. return 0
  921. endfunction
  922. function! s:preview_commit()
  923. if b:plug_preview < 0
  924. let b:plug_preview = !s:is_preview_window_open()
  925. endif
  926. let sha = matchstr(getline('.'), '\(^ \)\@<=[0-9a-z]\{7}')
  927. if !empty(sha)
  928. let lnum = line('.')
  929. while lnum > 1
  930. let lnum -= 1
  931. let line = getline(lnum)
  932. let name = matchstr(line, '\(^- \)\@<=[^:]\+')
  933. if !empty(name)
  934. let dir = g:plugs[name].dir
  935. if isdirectory(dir)
  936. execute 'cd '.s:esc(dir)
  937. execute 'pedit '.sha
  938. wincmd P
  939. setlocal filetype=git buftype=nofile nobuflisted
  940. execute 'silent read !git show '.sha
  941. normal! ggdd
  942. wincmd p
  943. cd -
  944. endif
  945. break
  946. endif
  947. endwhile
  948. endif
  949. endfunction
  950. function! s:section(flags)
  951. call search('\(^- \)\@<=.', a:flags)
  952. endfunction
  953. function! s:diff()
  954. call s:prepare()
  955. call append(0, 'Collecting updated changes ...')
  956. normal! gg
  957. redraw
  958. let cnt = 0
  959. for [k, v] in items(g:plugs)
  960. if !isdirectory(v.dir) || !s:is_managed(k)
  961. continue
  962. endif
  963. execute 'cd '.s:esc(v.dir)
  964. let diff = system('git log --pretty=format:"%h %s (%cr)" "HEAD@{0}...HEAD@{1}"')
  965. if !v:shell_error && !empty(diff)
  966. call append(1, '')
  967. call append(2, '- '.k.':')
  968. call append(3, map(split(diff, '\n'), '" ". v:val'))
  969. let cnt += 1
  970. normal! gg
  971. redraw
  972. endif
  973. cd -
  974. endfor
  975. call setline(1, cnt == 0 ? 'No updates.' : 'Last update:')
  976. nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
  977. normal! gg
  978. endfunction
  979. let s:first_rtp = s:esc(get(split(&rtp, ','), 0, ''))
  980. let s:last_rtp = s:esc(get(split(&rtp, ','), -1, ''))
  981. let &cpo = s:cpo_save
  982. unlet s:cpo_save