plug.vim 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152
  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=* -bang -complete=customlist,s:names PlugInstall call s:install(!empty('<bang>'), <f-args>)
  106. command! -nargs=* -bang -complete=customlist,s:names PlugUpdate call s:update(!empty('<bang>'), <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(force, ...)
  332. call s:update_impl(0, a:force, a:000)
  333. endfunction
  334. function! s:update(force, ...)
  335. call s:update_impl(1, a:force, 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 '. s:esc(docd)
  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, force, 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. let updated = installed ? 0 :
  433. \ (a:pull && !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"')))
  434. if a:force || installed || updated
  435. call append(3, '- Post-update hook for '. name .' ... ')
  436. let type = type(spec.do)
  437. if type == s:TYPE.string
  438. try
  439. " FIXME: Escaping is incomplete. We could use shellescape with eval,
  440. " but it won't work on Windows.
  441. let g:_plug_do = '!'.escape(spec.do, '#!%')
  442. execute "normal! :execute g:_plug_do\<cr>\<cr>"
  443. finally
  444. let result = v:shell_error ? ('Exit status: '.v:shell_error) : 'Done!'
  445. unlet g:_plug_do
  446. endtry
  447. elseif type == s:TYPE.funcref
  448. try
  449. let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
  450. call spec.do({ 'name': name, 'status': status, 'force': a:force })
  451. let result = 'Done!'
  452. catch
  453. let result = 'Error: ' . v:exception
  454. endtry
  455. else
  456. let result = 'Error: Invalid type!'
  457. endif
  458. call setline(4, getline(4) . result)
  459. endif
  460. cd -
  461. endfor
  462. endfunction
  463. function! s:finish(pull)
  464. call append(3, '- Finishing ... ')
  465. redraw
  466. call s:helptags()
  467. call plug#end()
  468. call setline(4, getline(4) . 'Done!')
  469. normal! gg
  470. call s:syntax()
  471. redraw
  472. let msgs = []
  473. if !empty(s:prev_update.errors)
  474. call add(msgs, "Press 'R' to retry.")
  475. endif
  476. if a:pull
  477. call add(msgs, "Press 'D' to see the updated changes.")
  478. endif
  479. echo join(msgs, ' ')
  480. endfunction
  481. function! s:retry()
  482. if empty(s:prev_update.errors)
  483. return
  484. endif
  485. call s:update_impl(s:prev_update.pull, s:prev_update.force,
  486. \ extend(copy(s:prev_update.errors), [s:prev_update.threads]))
  487. endfunction
  488. function! s:is_managed(name)
  489. return has_key(g:plugs[a:name], 'uri')
  490. endfunction
  491. function! s:names(...)
  492. return filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')
  493. endfunction
  494. function! s:update_impl(pull, force, args) abort
  495. let st = reltime()
  496. let args = copy(a:args)
  497. let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
  498. \ remove(args, -1) : get(g:, 'plug_threads', 16)
  499. let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
  500. let todo = empty(args) ? filter(managed, '!v:val.frozen') :
  501. \ filter(managed, 'index(args, v:key) >= 0')
  502. if empty(todo)
  503. echohl WarningMsg
  504. echo 'No plugin to '. (a:pull ? 'update' : 'install') . '.'
  505. echohl None
  506. return
  507. endif
  508. call s:prepare()
  509. call append(0, a:pull ? 'Updating plugins' : 'Installing plugins')
  510. call append(1, '['. s:lpad('', len(todo)) .']')
  511. normal! 2G
  512. redraw
  513. if !isdirectory(g:plug_home)
  514. call mkdir(g:plug_home, 'p')
  515. endif
  516. let s:prev_update = { 'errors': [], 'pull': a:pull, 'force': a:force, 'new': {}, 'threads': threads }
  517. if has('ruby') && threads > 1
  518. try
  519. let imd = &imd
  520. if s:mac_gui
  521. set noimd
  522. endif
  523. call s:update_parallel(a:pull, todo, threads)
  524. catch
  525. let lines = getline(4, '$')
  526. let printed = {}
  527. silent 4,$d
  528. for line in lines
  529. let name = get(matchlist(line, '^. \([^:]\+\):'), 1, '')
  530. if empty(name) || !has_key(printed, name)
  531. call append('$', line)
  532. if !empty(name)
  533. let printed[name] = 1
  534. if line[0] == 'x' && index(s:prev_update.errors, name) < 0
  535. call add(s:prev_update.errors, name)
  536. end
  537. endif
  538. endif
  539. endfor
  540. finally
  541. let &imd = imd
  542. endtry
  543. else
  544. call s:update_serial(a:pull, todo)
  545. endif
  546. call s:do(a:pull, a:force, filter(copy(todo), 'has_key(v:val, "do")'))
  547. call s:finish(a:pull)
  548. call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(st)))[0] . ' sec.')
  549. endfunction
  550. function! s:update_progress(pull, cnt, bar, total)
  551. call setline(1, (a:pull ? 'Updating' : 'Installing').
  552. \ ' plugins ('.a:cnt.'/'.a:total.')')
  553. call s:progress_bar(2, a:bar, a:total)
  554. normal! 2G
  555. redraw
  556. endfunction
  557. function! s:update_serial(pull, todo)
  558. let base = g:plug_home
  559. let todo = copy(a:todo)
  560. let total = len(todo)
  561. let done = {}
  562. let bar = ''
  563. for [name, spec] in items(todo)
  564. let done[name] = 1
  565. if isdirectory(spec.dir)
  566. execute 'cd '.s:esc(spec.dir)
  567. let [valid, msg] = s:git_valid(spec, 0, 0)
  568. if valid
  569. let result = a:pull ?
  570. \ s:system(
  571. \ printf('git checkout -q %s 2>&1 && git pull origin %s 2>&1 && git submodule update --init --recursive 2>&1',
  572. \ s:shellesc(spec.branch), s:shellesc(spec.branch))) : 'Already installed'
  573. let error = a:pull ? v:shell_error != 0 : 0
  574. else
  575. let result = msg
  576. let error = 1
  577. endif
  578. cd -
  579. else
  580. let result = s:system(
  581. \ printf('git clone --recursive %s -b %s %s 2>&1 && cd %s && git submodule update --init --recursive 2>&1',
  582. \ s:shellesc(spec.uri),
  583. \ s:shellesc(spec.branch),
  584. \ s:shellesc(s:trim(spec.dir)),
  585. \ s:shellesc(spec.dir)))
  586. let error = v:shell_error != 0
  587. if !error | let s:prev_update.new[name] = 1 | endif
  588. endif
  589. let bar .= error ? 'x' : '='
  590. if error
  591. call add(s:prev_update.errors, name)
  592. endif
  593. call append(3, s:format_message(!error, name, result))
  594. call s:update_progress(a:pull, len(done), bar, total)
  595. endfor
  596. endfunction
  597. function! s:update_parallel(pull, todo, threads)
  598. ruby << EOF
  599. module PlugStream
  600. SEP = ["\r", "\n", nil]
  601. def get_line
  602. buffer = ''
  603. loop do
  604. char = readchar rescue return
  605. if SEP.include? char.chr
  606. buffer << $/
  607. break
  608. else
  609. buffer << char
  610. end
  611. end
  612. buffer
  613. end
  614. end unless defined?(PlugStream)
  615. def esc arg
  616. %["#{arg.gsub('"', '\"')}"]
  617. end
  618. def killall pid
  619. pids = [pid]
  620. unless `which pgrep`.empty?
  621. children = pids
  622. until children.empty?
  623. children = children.map { |pid|
  624. `pgrep -P #{pid}`.lines.map { |l| l.chomp }
  625. }.flatten
  626. pids += children
  627. end
  628. end
  629. pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
  630. end
  631. require 'thread'
  632. require 'fileutils'
  633. require 'timeout'
  634. running = true
  635. iswin = VIM::evaluate('s:is_win').to_i == 1
  636. pull = VIM::evaluate('a:pull').to_i == 1
  637. base = VIM::evaluate('g:plug_home')
  638. all = VIM::evaluate('a:todo')
  639. limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
  640. tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
  641. nthr = VIM::evaluate('a:threads').to_i
  642. maxy = VIM::evaluate('winheight(".")').to_i
  643. cd = iswin ? 'cd /d' : 'cd'
  644. tot = VIM::evaluate('len(a:todo)') || 0
  645. bar = ''
  646. skip = 'Already installed'
  647. mtx = Mutex.new
  648. take1 = proc { mtx.synchronize { running && all.shift } }
  649. logh = proc {
  650. cnt = bar.length
  651. $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
  652. $curbuf[2] = '[' + bar.ljust(tot) + ']'
  653. VIM::command('normal! 2G')
  654. VIM::command('redraw') unless iswin
  655. }
  656. where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
  657. log = proc { |name, result, type|
  658. mtx.synchronize do
  659. ing = ![true, false].include?(type)
  660. bar += type ? '=' : 'x' unless ing
  661. b = case type
  662. when :install then '+' when :update then '*'
  663. when true, nil then '-' else
  664. VIM::command("call add(s:prev_update.errors, '#{name}')")
  665. 'x'
  666. end
  667. result =
  668. if type || type.nil?
  669. ["#{b} #{name}: #{result.lines.to_a.last}"]
  670. elsif result =~ /^Interrupted|^Timeout/
  671. ["#{b} #{name}: #{result}"]
  672. else
  673. ["#{b} #{name}"] + result.lines.map { |l| " " << l }
  674. end
  675. if lnum = where.call(name)
  676. $curbuf.delete lnum
  677. lnum = 4 if ing && lnum > maxy
  678. end
  679. result.each_with_index do |line, offset|
  680. $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
  681. end
  682. logh.call
  683. end
  684. }
  685. bt = proc { |cmd, name, type|
  686. tried = timeout = 0
  687. begin
  688. tried += 1
  689. timeout += limit
  690. fd = nil
  691. data = ''
  692. if iswin
  693. Timeout::timeout(timeout) do
  694. tmp = VIM::evaluate('tempname()')
  695. system("#{cmd} > #{tmp}")
  696. data = File.read(tmp).chomp
  697. File.unlink tmp rescue nil
  698. end
  699. else
  700. fd = IO.popen(cmd).extend(PlugStream)
  701. first_line = true
  702. log_prob = 1.0 / nthr
  703. while line = Timeout::timeout(timeout) { fd.get_line }
  704. data << line
  705. log.call name, line.chomp, type if name && (first_line || rand < log_prob)
  706. first_line = false
  707. end
  708. fd.close
  709. end
  710. [$? == 0, data.chomp]
  711. rescue Timeout::Error, Interrupt => e
  712. if fd && !fd.closed?
  713. killall fd.pid
  714. fd.close
  715. end
  716. if e.is_a?(Timeout::Error) && tried < tries
  717. 3.downto(1) do |countdown|
  718. s = countdown > 1 ? 's' : ''
  719. log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
  720. sleep 1
  721. end
  722. log.call name, 'Retrying ...', type
  723. retry
  724. end
  725. [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
  726. end
  727. }
  728. main = Thread.current
  729. threads = []
  730. watcher = Thread.new {
  731. while VIM::evaluate('getchar(1)')
  732. sleep 0.1
  733. end
  734. mtx.synchronize do
  735. running = false
  736. threads.each { |t| t.raise Interrupt }
  737. end
  738. threads.each { |t| t.join rescue nil }
  739. main.kill
  740. }
  741. refresh = Thread.new {
  742. while true
  743. mtx.synchronize do
  744. break unless running
  745. VIM::command('noautocmd normal! a')
  746. end
  747. sleep 0.2
  748. end
  749. } if VIM::evaluate('s:mac_gui') == 1
  750. progress = iswin ? '' : '--progress'
  751. [all.length, nthr].min.times do
  752. mtx.synchronize do
  753. threads << Thread.new {
  754. while pair = take1.call
  755. name = pair.first
  756. dir, uri, branch = pair.last.values_at *%w[dir uri branch]
  757. branch = esc branch
  758. subm = "git submodule update --init --recursive 2>&1"
  759. exists = File.directory? dir
  760. ok, result =
  761. if exists
  762. dir = esc dir
  763. ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url", nil, nil
  764. current_uri = data.lines.to_a.last
  765. if !ret
  766. if data =~ /^Interrupted|^Timeout/
  767. [false, data]
  768. else
  769. [false, [data.chomp, "PlugClean required."].join($/)]
  770. end
  771. elsif current_uri.sub(/git:@/, '') != uri.sub(/git:@/, '')
  772. [false, ["Invalid URI: #{current_uri}",
  773. "Expected: #{uri}",
  774. "PlugClean required."].join($/)]
  775. else
  776. if pull
  777. log.call name, 'Updating ...', :update
  778. bt.call "#{cd} #{dir} && git checkout -q #{branch} 2>&1 && (git pull origin #{branch} #{progress} 2>&1 && #{subm})", name, :update
  779. else
  780. [true, skip]
  781. end
  782. end
  783. else
  784. d = esc dir.sub(%r{[\\/]+$}, '')
  785. log.call name, 'Installing ...', :install
  786. bt.call "(git clone #{progress} --recursive #{uri} -b #{branch} #{d} 2>&1 && cd #{esc dir} && #{subm})", name, :install
  787. end
  788. mtx.synchronize { VIM::command("let s:prev_update.new['#{name}'] = 1") } if !exists && ok
  789. log.call name, result, ok
  790. end
  791. } if running
  792. end
  793. end
  794. threads.each { |t| t.join rescue nil }
  795. logh.call
  796. refresh.kill if refresh
  797. watcher.kill
  798. EOF
  799. endfunction
  800. function! s:shellesc(arg)
  801. return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
  802. endfunction
  803. function! s:glob_dir(path)
  804. return map(filter(split(globpath(a:path, '**'), '\n'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
  805. endfunction
  806. function! s:progress_bar(line, bar, total)
  807. call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
  808. endfunction
  809. function! s:compare_git_uri(a, b)
  810. let a = substitute(a:a, 'git:@', '', '')
  811. let b = substitute(a:b, 'git:@', '', '')
  812. return a ==# b
  813. endfunction
  814. function! s:format_message(ok, name, message)
  815. if a:ok
  816. return [printf('- %s: %s', a:name, s:lastline(a:message))]
  817. else
  818. let lines = map(split(a:message, '\n'), '" ".v:val')
  819. return extend([printf('x %s:', a:name)], lines)
  820. endif
  821. endfunction
  822. function! s:system(cmd)
  823. return system(s:is_win ? '('.a:cmd.')' : a:cmd)
  824. endfunction
  825. function! s:system_chomp(str)
  826. let ret = s:system(a:str)
  827. return v:shell_error ? '' : substitute(ret, '\n$', '', '')
  828. endfunction
  829. function! s:git_valid(spec, check_branch, cd)
  830. let ret = 1
  831. let msg = 'OK'
  832. if isdirectory(a:spec.dir)
  833. if a:cd | execute 'cd ' . s:esc(a:spec.dir) | endif
  834. let result = split(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url'), '\n')
  835. let remote = result[-1]
  836. if v:shell_error
  837. let msg = join([remote, 'PlugClean required.'], "\n")
  838. let ret = 0
  839. elseif !s:compare_git_uri(remote, a:spec.uri)
  840. let msg = join(['Invalid URI: '.remote,
  841. \ 'Expected: '.a:spec.uri,
  842. \ 'PlugClean required.'], "\n")
  843. let ret = 0
  844. elseif a:check_branch
  845. let branch = result[0]
  846. if a:spec.branch !=# branch
  847. let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1')
  848. if a:spec.branch !=# tag
  849. let msg = printf('Invalid branch/tag: %s (expected: %s). Try PlugUpdate.',
  850. \ (empty(tag) ? branch : tag), a:spec.branch)
  851. let ret = 0
  852. endif
  853. endif
  854. endif
  855. if a:cd | cd - | endif
  856. else
  857. let msg = 'Not found'
  858. let ret = 0
  859. endif
  860. return [ret, msg]
  861. endfunction
  862. function! s:clean(force)
  863. call s:prepare()
  864. call append(0, 'Searching for unused plugins in '.g:plug_home)
  865. call append(1, '')
  866. " List of valid directories
  867. let dirs = []
  868. let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
  869. let [cnt, total] = [0, len(managed)]
  870. for spec in values(managed)
  871. if s:git_valid(spec, 0, 1)[0]
  872. call add(dirs, spec.dir)
  873. endif
  874. let cnt += 1
  875. call s:progress_bar(2, repeat('=', cnt), total)
  876. normal! 2G
  877. redraw
  878. endfor
  879. let allowed = {}
  880. for dir in dirs
  881. let allowed[dir] = 1
  882. for child in s:glob_dir(dir)
  883. let allowed[child] = 1
  884. endfor
  885. endfor
  886. let todo = []
  887. let found = sort(s:glob_dir(g:plug_home))
  888. while !empty(found)
  889. let f = remove(found, 0)
  890. if !has_key(allowed, f) && isdirectory(f)
  891. call add(todo, f)
  892. call append(line('$'), '- ' . f)
  893. let found = filter(found, 'stridx(v:val, f) != 0')
  894. end
  895. endwhile
  896. normal! G
  897. redraw
  898. if empty(todo)
  899. call append(line('$'), 'Already clean.')
  900. else
  901. call inputsave()
  902. let yes = a:force || (input('Proceed? (Y/N) ') =~? '^y')
  903. call inputrestore()
  904. if yes
  905. for dir in todo
  906. if isdirectory(dir)
  907. call system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(dir))
  908. endif
  909. endfor
  910. call append(line('$'), 'Removed.')
  911. else
  912. call append(line('$'), 'Cancelled.')
  913. endif
  914. endif
  915. normal! G
  916. endfunction
  917. function! s:upgrade()
  918. if executable('curl')
  919. let mee = s:shellesc(s:me)
  920. let new = s:shellesc(s:me . '.new')
  921. echo 'Downloading '. s:plug_source
  922. redraw
  923. let mv = s:is_win ? 'move /Y' : 'mv -f'
  924. let cp = s:is_win ? 'copy /Y' : 'cp -f'
  925. call system(printf(
  926. \ 'curl -fLo %s %s && '.cp.' %s %s.old && '.mv.' %s %s',
  927. \ new, s:plug_source, mee, mee, new, mee))
  928. if v:shell_error == 0
  929. unlet g:loaded_plug
  930. echo 'Downloaded '. s:plug_source
  931. return 1
  932. else
  933. return s:err('Error upgrading vim-plug')
  934. endif
  935. elseif has('ruby')
  936. echo 'Downloading '. s:plug_source
  937. ruby << EOF
  938. require 'open-uri'
  939. require 'fileutils'
  940. me = VIM::evaluate('s:me')
  941. old = me + '.old'
  942. new = me + '.new'
  943. File.open(new, 'w') do |f|
  944. f << open(VIM::evaluate('s:plug_source')).read
  945. end
  946. FileUtils.cp me, old
  947. File.rename new, me
  948. EOF
  949. unlet g:loaded_plug
  950. echo 'Downloaded '. s:plug_source
  951. return 1
  952. else
  953. return s:err('curl executable or ruby support not found')
  954. endif
  955. endfunction
  956. function! s:upgrade_specs()
  957. for spec in values(g:plugs)
  958. let spec.frozen = get(spec, 'frozen', 0)
  959. endfor
  960. endfunction
  961. function! s:status()
  962. call s:prepare()
  963. call append(0, 'Checking plugins')
  964. call append(1, '')
  965. let ecnt = 0
  966. let [cnt, total] = [0, len(g:plugs)]
  967. for [name, spec] in items(g:plugs)
  968. if has_key(spec, 'uri')
  969. if isdirectory(spec.dir)
  970. let [valid, msg] = s:git_valid(spec, 1, 1)
  971. else
  972. let [valid, msg] = [0, 'Not found. Try PlugInstall.']
  973. endif
  974. else
  975. if isdirectory(spec.dir)
  976. let [valid, msg] = [1, 'OK']
  977. else
  978. let [valid, msg] = [0, 'Not found.']
  979. endif
  980. endif
  981. let cnt += 1
  982. let ecnt += !valid
  983. call s:progress_bar(2, repeat('=', cnt), total)
  984. call append(3, s:format_message(valid, name, msg))
  985. normal! 2G
  986. redraw
  987. endfor
  988. call setline(1, 'Finished. '.ecnt.' error(s).')
  989. normal! gg
  990. endfunction
  991. function! s:is_preview_window_open()
  992. silent! wincmd P
  993. if &previewwindow
  994. wincmd p
  995. return 1
  996. endif
  997. return 0
  998. endfunction
  999. function! s:preview_commit()
  1000. if b:plug_preview < 0
  1001. let b:plug_preview = !s:is_preview_window_open()
  1002. endif
  1003. let sha = matchstr(getline('.'), '\(^ \)\@<=[0-9a-z]\{7}')
  1004. if !empty(sha)
  1005. let lnum = line('.')
  1006. while lnum > 1
  1007. let lnum -= 1
  1008. let line = getline(lnum)
  1009. let name = matchstr(line, '\(^- \)\@<=[^:]\+')
  1010. if !empty(name)
  1011. let dir = g:plugs[name].dir
  1012. if isdirectory(dir)
  1013. execute 'cd '.s:esc(dir)
  1014. execute 'pedit '.sha
  1015. wincmd P
  1016. setlocal filetype=git buftype=nofile nobuflisted
  1017. execute 'silent read !git show '.sha
  1018. normal! ggdd
  1019. wincmd p
  1020. cd -
  1021. endif
  1022. break
  1023. endif
  1024. endwhile
  1025. endif
  1026. endfunction
  1027. function! s:section(flags)
  1028. call search('\(^- \)\@<=.', a:flags)
  1029. endfunction
  1030. function! s:diff()
  1031. call s:prepare()
  1032. call append(0, 'Collecting updated changes ...')
  1033. normal! gg
  1034. redraw
  1035. let cnt = 0
  1036. for [k, v] in items(g:plugs)
  1037. if !isdirectory(v.dir) || !s:is_managed(k)
  1038. continue
  1039. endif
  1040. execute 'cd '.s:esc(v.dir)
  1041. let diff = system('git log --pretty=format:"%h %s (%cr)" "HEAD...HEAD@{1}"')
  1042. if !v:shell_error && !empty(diff)
  1043. call append(1, '')
  1044. call append(2, '- '.k.':')
  1045. call append(3, map(split(diff, '\n'), '" ". v:val'))
  1046. let cnt += 1
  1047. normal! gg
  1048. redraw
  1049. endif
  1050. cd -
  1051. endfor
  1052. call setline(1, cnt == 0 ? 'No updates.' : 'Last update:')
  1053. nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
  1054. normal! gg
  1055. endfunction
  1056. let s:first_rtp = s:esc(get(split(&rtp, ','), 0, ''))
  1057. let s:last_rtp = s:esc(get(split(&rtp, ','), -1, ''))
  1058. let &cpo = s:cpo_save
  1059. unlet s:cpo_save