plug.vim 29 KB

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