plug.vim 34 KB

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