plug.vim 32 KB

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