plug.vim 32 KB

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