plug.vim 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151
  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 !executable('git')
  91. return s:err('`git` executable not found. vim-plug requires git.')
  92. endif
  93. let g:plug_home = home
  94. let g:plugs = {}
  95. " we want to keep track of the order plugins where registered.
  96. let g:plugs_order = []
  97. command! -nargs=+ -bar Plug call s:add(<args>)
  98. command! -nargs=* -bang -complete=customlist,s:names PlugInstall call s:install(!empty('<bang>'), <f-args>)
  99. command! -nargs=* -bang -complete=customlist,s:names PlugUpdate call s:update(!empty('<bang>'), <f-args>)
  100. command! -nargs=0 -bang PlugClean call s:clean('<bang>' == '!')
  101. command! -nargs=0 PlugUpgrade if s:upgrade() | call s:upgrade_specs() | execute 'source '. s:me | endif
  102. command! -nargs=0 PlugStatus call s:status()
  103. command! -nargs=0 PlugDiff call s:diff()
  104. return 1
  105. endfunction
  106. function! s:to_a(v)
  107. return type(a:v) == s:TYPE.list ? a:v : [a:v]
  108. endfunction
  109. function! s:source(from, ...)
  110. for pattern in a:000
  111. for vim in split(globpath(a:from, pattern), '\n')
  112. execute 'source '.vim
  113. endfor
  114. endfor
  115. endfunction
  116. function! plug#end()
  117. let reload = !has('vim_starting')
  118. if !exists('g:plugs')
  119. return s:err('Call plug#begin() first')
  120. endif
  121. if exists('#PlugLOD')
  122. augroup PlugLOD
  123. autocmd!
  124. augroup END
  125. augroup! PlugLOD
  126. endif
  127. let lod = {}
  128. filetype off
  129. " we want to make sure the plugin directories are added to rtp in the same
  130. " order that they are registered with the Plug command. since the s:add_rtp
  131. " function uses ^= to add plugin directories to the front of the rtp, we
  132. " need to loop through the plugins in reverse
  133. for name in reverse(copy(g:plugs_order))
  134. let plug = g:plugs[name]
  135. if !has_key(plug, 'on') && !has_key(plug, 'for')
  136. let rtp = s:rtp(plug)
  137. call s:add_rtp(rtp)
  138. if reload
  139. call s:source(rtp, 'plugin/**/*.vim', 'after/plugin/**/*.vim')
  140. endif
  141. continue
  142. endif
  143. if has_key(plug, 'on')
  144. for cmd in s:to_a(plug.on)
  145. if cmd =~ '^<Plug>.\+'
  146. if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
  147. for [mode, map_prefix, key_prefix] in
  148. \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
  149. execute printf(
  150. \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, "%s")<CR>',
  151. \ mode, cmd, map_prefix, string(cmd), string(name), key_prefix)
  152. endfor
  153. endif
  154. elseif !exists(':'.cmd)
  155. execute printf(
  156. \ 'command! -nargs=* -range -bang %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
  157. \ cmd, string(cmd), string(name))
  158. endif
  159. endfor
  160. endif
  161. if has_key(plug, 'for')
  162. call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
  163. for key in s:to_a(plug.for)
  164. if !has_key(lod, key)
  165. let lod[key] = []
  166. endif
  167. call add(lod[key], name)
  168. endfor
  169. endif
  170. endfor
  171. for [key, names] in items(lod)
  172. augroup PlugLOD
  173. execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
  174. \ key, string(key), string(reverse(names)))
  175. augroup END
  176. endfor
  177. call s:reorg_rtp()
  178. filetype plugin indent on
  179. syntax on
  180. endfunction
  181. function! s:trim(str)
  182. return substitute(a:str, '[\/]\+$', '', '')
  183. endfunction
  184. if s:is_win
  185. function! s:rtp(spec)
  186. return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
  187. endfunction
  188. function! s:path(path)
  189. return s:trim(substitute(a:path, '/', '\', 'g'))
  190. endfunction
  191. function! s:dirpath(path)
  192. return s:path(a:path) . '\'
  193. endfunction
  194. function! s:is_local_plug(repo)
  195. return a:repo =~? '^[a-z]:'
  196. endfunction
  197. else
  198. function! s:rtp(spec)
  199. return s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
  200. endfunction
  201. function! s:path(path)
  202. return s:trim(a:path)
  203. endfunction
  204. function! s:dirpath(path)
  205. return substitute(a:path, '[/\\]*$', '/', '')
  206. endfunction
  207. function! s:is_local_plug(repo)
  208. return a:repo[0] =~ '[/$~]'
  209. endfunction
  210. endif
  211. function! s:err(msg)
  212. echohl ErrorMsg
  213. echom a:msg
  214. echohl None
  215. return 0
  216. endfunction
  217. function! s:esc(path)
  218. return substitute(a:path, ' ', '\\ ', 'g')
  219. endfunction
  220. function! s:add_rtp(rtp)
  221. execute 'set rtp^='.s:esc(a:rtp)
  222. let after = globpath(a:rtp, 'after')
  223. if isdirectory(after)
  224. execute 'set rtp+='.s:esc(after)
  225. endif
  226. endfunction
  227. function! s:reorg_rtp()
  228. if !empty(s:first_rtp)
  229. execute 'set rtp-='.s:first_rtp
  230. execute 'set rtp^='.s:first_rtp
  231. endif
  232. if s:last_rtp !=# s:first_rtp
  233. execute 'set rtp-='.s:last_rtp
  234. execute 'set rtp+='.s:last_rtp
  235. endif
  236. endfunction
  237. function! s:lod(plug, types)
  238. let rtp = s:rtp(a:plug)
  239. call s:add_rtp(rtp)
  240. for dir in a:types
  241. call s:source(rtp, dir.'/**/*.vim')
  242. endfor
  243. endfunction
  244. function! s:lod_ft(pat, names)
  245. for name in a:names
  246. call s:lod(g:plugs[name], ['plugin', 'after/plugin'])
  247. endfor
  248. call s:reorg_rtp()
  249. execute 'autocmd! PlugLOD FileType ' . a:pat
  250. silent! doautocmd filetypeplugin FileType
  251. silent! doautocmd filetypeindent FileType
  252. endfunction
  253. function! s:lod_cmd(cmd, bang, l1, l2, args, name)
  254. execute 'delc '.a:cmd
  255. call s:lod(g:plugs[a:name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
  256. call s:reorg_rtp()
  257. execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
  258. endfunction
  259. function! s:lod_map(map, name, prefix)
  260. execute 'unmap '.a:map
  261. execute 'iunmap '.a:map
  262. call s:lod(g:plugs[a:name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
  263. call s:reorg_rtp()
  264. let extra = ''
  265. while 1
  266. let c = getchar(0)
  267. if c == 0
  268. break
  269. endif
  270. let extra .= nr2char(c)
  271. endwhile
  272. call feedkeys(a:prefix . substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
  273. endfunction
  274. function! s:add(repo, ...)
  275. if a:0 > 1
  276. return s:err('Invalid number of arguments (1..2)')
  277. endif
  278. try
  279. let repo = s:trim(a:repo)
  280. let name = fnamemodify(repo, ':t:s?\.git$??')
  281. let spec = extend(s:infer_properties(name, repo),
  282. \ a:0 == 1 ? s:parse_options(a:1) : s:base_spec)
  283. let g:plugs[name] = spec
  284. let g:plugs_order += [name]
  285. catch
  286. return s:err(v:exception)
  287. endtry
  288. endfunction
  289. function! s:parse_options(arg)
  290. let opts = copy(s:base_spec)
  291. let type = type(a:arg)
  292. if type == s:TYPE.string
  293. let opts.branch = a:arg
  294. elseif type == s:TYPE.dict
  295. call extend(opts, a:arg)
  296. if has_key(opts, 'tag')
  297. let opts.branch = remove(opts, 'tag')
  298. endif
  299. if has_key(opts, 'dir')
  300. let opts.dir = s:dirpath(expand(opts.dir))
  301. endif
  302. else
  303. throw 'Invalid argument type (expected: string or dictionary)'
  304. endif
  305. return opts
  306. endfunction
  307. function! s:infer_properties(name, repo)
  308. let repo = a:repo
  309. if s:is_local_plug(repo)
  310. return { 'dir': s:dirpath(expand(repo)) }
  311. else
  312. if repo =~ ':'
  313. let uri = repo
  314. else
  315. if repo !~ '/'
  316. let repo = 'vim-scripts/'. repo
  317. endif
  318. let uri = 'https://git:@github.com/' . repo . '.git'
  319. endif
  320. let dir = s:dirpath( fnamemodify(join([g:plug_home, a:name], '/'), ':p') )
  321. return { 'dir': dir, 'uri': uri }
  322. endif
  323. endfunction
  324. function! s:install(force, ...)
  325. call s:update_impl(0, a:force, a:000)
  326. endfunction
  327. function! s:update(force, ...)
  328. call s:update_impl(1, a:force, a:000)
  329. endfunction
  330. function! s:helptags()
  331. for spec in values(g:plugs)
  332. let docd = join([spec.dir, 'doc'], '/')
  333. if isdirectory(docd)
  334. silent! execute 'helptags '. s:esc(docd)
  335. endif
  336. endfor
  337. endfunction
  338. function! s:syntax()
  339. syntax clear
  340. syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
  341. syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
  342. syn match plugNumber /[0-9]\+[0-9.]*/ contained
  343. syn match plugBracket /[[\]]/ contained
  344. syn match plugX /x/ contained
  345. syn match plugDash /^-/
  346. syn match plugPlus /^+/
  347. syn match plugStar /^*/
  348. syn match plugMessage /\(^- \)\@<=.*/
  349. syn match plugName /\(^- \)\@<=[^ ]*:/
  350. syn match plugInstall /\(^+ \)\@<=[^:]*/
  351. syn match plugUpdate /\(^* \)\@<=[^:]*/
  352. syn match plugCommit /^ [0-9a-z]\{7} .*/ contains=plugRelDate,plugSha
  353. syn match plugSha /\(^ \)\@<=[0-9a-z]\{7}/ contained
  354. syn match plugRelDate /([^)]*)$/ contained
  355. syn match plugError /^x.*/
  356. syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
  357. hi def link plug1 Title
  358. hi def link plug2 Repeat
  359. hi def link plugX Exception
  360. hi def link plugBracket Structure
  361. hi def link plugNumber Number
  362. hi def link plugDash Special
  363. hi def link plugPlus Constant
  364. hi def link plugStar Boolean
  365. hi def link plugMessage Function
  366. hi def link plugName Label
  367. hi def link plugInstall Function
  368. hi def link plugUpdate Type
  369. hi def link plugError Error
  370. hi def link plugRelDate Comment
  371. hi def link plugSha Identifier
  372. endfunction
  373. function! s:lpad(str, len)
  374. return a:str . repeat(' ', a:len - len(a:str))
  375. endfunction
  376. function! s:lastline(msg)
  377. let lines = split(a:msg, '\n')
  378. return get(lines, -1, '')
  379. endfunction
  380. function! s:prepare()
  381. if bufexists(s:plug_buf)
  382. let winnr = bufwinnr(s:plug_buf)
  383. if winnr < 0
  384. vertical topleft new
  385. execute 'buffer ' . s:plug_buf
  386. else
  387. execute winnr . 'wincmd w'
  388. endif
  389. silent %d _
  390. else
  391. vertical topleft new
  392. nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>q<cr>
  393. nnoremap <silent> <buffer> R :silent! call <SID>retry()<cr>
  394. nnoremap <silent> <buffer> D :PlugDiff<cr>
  395. nnoremap <silent> <buffer> S :PlugStatus<cr>
  396. nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
  397. nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
  398. let b:plug_preview = -1
  399. let s:plug_buf = winbufnr(0)
  400. call s:assign_name()
  401. endif
  402. silent! unmap <buffer> <cr>
  403. setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline
  404. setf vim-plug
  405. call s:syntax()
  406. endfunction
  407. function! s:assign_name()
  408. " Assign buffer name
  409. let prefix = '[Plugins]'
  410. let name = prefix
  411. let idx = 2
  412. while bufexists(name)
  413. let name = printf('%s (%s)', prefix, idx)
  414. let idx = idx + 1
  415. endwhile
  416. silent! execute 'f '.fnameescape(name)
  417. endfunction
  418. function! s:do(pull, force, todo)
  419. for [name, spec] in items(a:todo)
  420. if !isdirectory(spec.dir)
  421. continue
  422. endif
  423. execute 'cd '.s:esc(spec.dir)
  424. let installed = has_key(s:prev_update.new, name)
  425. let updated = installed ? 0 :
  426. \ (a:pull && !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"')))
  427. if a:force || installed || updated
  428. call append(3, '- Post-update hook for '. name .' ... ')
  429. let type = type(spec.do)
  430. if type == s:TYPE.string
  431. try
  432. " FIXME: Escaping is incomplete. We could use shellescape with eval,
  433. " but it won't work on Windows.
  434. let g:_plug_do = '!'.escape(spec.do, '#!%')
  435. execute "normal! :execute g:_plug_do\<cr>\<cr>"
  436. finally
  437. let result = v:shell_error ? ('Exit status: '.v:shell_error) : 'Done!'
  438. unlet g:_plug_do
  439. endtry
  440. elseif type == s:TYPE.funcref
  441. try
  442. let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
  443. call spec.do({ 'name': name, 'status': status, 'force': a:force })
  444. let result = 'Done!'
  445. catch
  446. let result = 'Error: ' . v:exception
  447. endtry
  448. else
  449. let result = 'Error: Invalid type!'
  450. endif
  451. call setline(4, getline(4) . result)
  452. endif
  453. cd -
  454. endfor
  455. endfunction
  456. function! s:finish(pull)
  457. call append(3, '- Finishing ... ')
  458. redraw
  459. call s:helptags()
  460. call plug#end()
  461. call setline(4, getline(4) . 'Done!')
  462. normal! gg
  463. call s:syntax()
  464. redraw
  465. let msgs = []
  466. if !empty(s:prev_update.errors)
  467. call add(msgs, "Press 'R' to retry.")
  468. endif
  469. if a:pull
  470. call add(msgs, "Press 'D' to see the updated changes.")
  471. endif
  472. echo join(msgs, ' ')
  473. endfunction
  474. function! s:retry()
  475. if empty(s:prev_update.errors)
  476. return
  477. endif
  478. call s:update_impl(s:prev_update.pull, s:prev_update.force,
  479. \ extend(copy(s:prev_update.errors), [s:prev_update.threads]))
  480. endfunction
  481. function! s:is_managed(name)
  482. return has_key(g:plugs[a:name], 'uri')
  483. endfunction
  484. function! s:names(...)
  485. return filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')
  486. endfunction
  487. function! s:update_impl(pull, force, args) abort
  488. let st = reltime()
  489. let args = copy(a:args)
  490. let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
  491. \ remove(args, -1) : get(g:, 'plug_threads', 16)
  492. let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
  493. let todo = empty(args) ? filter(managed, '!v:val.frozen') :
  494. \ filter(managed, 'index(args, v:key) >= 0')
  495. if empty(todo)
  496. echohl WarningMsg
  497. echo 'No plugin to '. (a:pull ? 'update' : 'install') . '.'
  498. echohl None
  499. return
  500. endif
  501. if !isdirectory(g:plug_home)
  502. try
  503. call mkdir(g:plug_home, 'p')
  504. catch
  505. return s:err(printf('Invalid plug directory: %s.'
  506. \ 'Try to call plug#begin with a valid directory', g:plug_home))
  507. endtry
  508. endif
  509. call s:prepare()
  510. call append(0, a:pull ? 'Updating plugins' : 'Installing plugins')
  511. call append(1, '['. s:lpad('', len(todo)) .']')
  512. normal! 2G
  513. redraw
  514. let s:prev_update = { 'errors': [], 'pull': a:pull, 'force': a:force, '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, a:force, 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