plug.vim 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587
  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.githubusercontent.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_src = 'https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
  69. let s:plug_tab = get(s:, 'plug_tab', -1)
  70. let s:plug_buf = get(s:, 'plug_buf', -1)
  71. let s:mac_gui = has('gui_macvim') && has('gui_running')
  72. let s:is_win = has('win32') || has('win64')
  73. let s:nvim = has('nvim') && !s:is_win
  74. let s:me = resolve(expand('<sfile>:p'))
  75. let s:base_spec = { 'branch': 'master', 'frozen': 0 }
  76. let s:TYPE = {
  77. \ 'string': type(''),
  78. \ 'list': type([]),
  79. \ 'dict': type({}),
  80. \ 'funcref': type(function('call'))
  81. \ }
  82. let s:loaded = get(s:, 'loaded', {})
  83. let s:triggers = get(s:, 'triggers', {})
  84. function! plug#begin(...)
  85. if a:0 > 0
  86. let s:plug_home_org = a:1
  87. let home = s:path(fnamemodify(expand(a:1), ':p'))
  88. elseif exists('g:plug_home')
  89. let home = s:path(g:plug_home)
  90. elseif !empty(&rtp)
  91. let home = s:path(split(&rtp, ',')[0]) . '/plugged'
  92. else
  93. return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
  94. endif
  95. let g:plug_home = home
  96. let g:plugs = {}
  97. let g:plugs_order = []
  98. let s:triggers = {}
  99. call s:define_commands()
  100. return 1
  101. endfunction
  102. function! s:define_commands()
  103. command! -nargs=+ -bar Plug call s:add(<args>)
  104. if !executable('git')
  105. return s:err('`git` executable not found. vim-plug requires git.')
  106. endif
  107. command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install('<bang>' == '!', [<f-args>])
  108. command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update('<bang>' == '!', [<f-args>])
  109. command! -nargs=0 -bar -bang PlugClean call s:clean('<bang>' == '!')
  110. command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:me | endif
  111. command! -nargs=0 -bar PlugStatus call s:status()
  112. command! -nargs=0 -bar PlugDiff call s:diff()
  113. command! -nargs=? -bar PlugSnapshot call s:snapshot(<f-args>)
  114. endfunction
  115. function! s:to_a(v)
  116. return type(a:v) == s:TYPE.list ? a:v : [a:v]
  117. endfunction
  118. function! s:to_s(v)
  119. return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
  120. endfunction
  121. function! s:source(from, ...)
  122. for pattern in a:000
  123. for vim in s:lines(globpath(a:from, pattern))
  124. execute 'source' vim
  125. endfor
  126. endfor
  127. endfunction
  128. function! s:assoc(dict, key, val)
  129. let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
  130. endfunction
  131. function! plug#end()
  132. if !exists('g:plugs')
  133. return s:err('Call plug#begin() first')
  134. endif
  135. if exists('#PlugLOD')
  136. augroup PlugLOD
  137. autocmd!
  138. augroup END
  139. augroup! PlugLOD
  140. endif
  141. let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
  142. filetype off
  143. for name in g:plugs_order
  144. let plug = g:plugs[name]
  145. if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for')
  146. let s:loaded[name] = 1
  147. continue
  148. endif
  149. if has_key(plug, 'on')
  150. let s:triggers[name] = { 'map': [], 'cmd': [] }
  151. for cmd in s:to_a(plug.on)
  152. if cmd =~ '^<Plug>.\+'
  153. if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
  154. call s:assoc(lod.map, cmd, name)
  155. endif
  156. call add(s:triggers[name].map, cmd)
  157. elseif cmd =~ '^[A-Z]'
  158. if exists(':'.cmd) != 2
  159. call s:assoc(lod.cmd, cmd, name)
  160. endif
  161. call add(s:triggers[name].cmd, cmd)
  162. endif
  163. endfor
  164. endif
  165. if has_key(plug, 'for')
  166. let types = s:to_a(plug.for)
  167. if !empty(types)
  168. call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
  169. endif
  170. for type in types
  171. call s:assoc(lod.ft, type, name)
  172. endfor
  173. endif
  174. endfor
  175. for [cmd, names] in items(lod.cmd)
  176. execute printf(
  177. \ 'command! -nargs=* -range -bang %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
  178. \ cmd, string(cmd), string(names))
  179. endfor
  180. for [map, names] in items(lod.map)
  181. for [mode, map_prefix, key_prefix] in
  182. \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
  183. execute printf(
  184. \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, "%s")<CR>',
  185. \ mode, map, map_prefix, string(map), string(names), key_prefix)
  186. endfor
  187. endfor
  188. for [ft, names] in items(lod.ft)
  189. augroup PlugLOD
  190. execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
  191. \ ft, string(ft), string(names))
  192. augroup END
  193. endfor
  194. call s:reorg_rtp()
  195. filetype plugin indent on
  196. if has('vim_starting')
  197. syntax enable
  198. else
  199. call s:reload()
  200. endif
  201. endfunction
  202. function! s:loaded_names()
  203. return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
  204. endfunction
  205. function! s:reload()
  206. for name in s:loaded_names()
  207. call s:source(s:rtp(g:plugs[name]), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
  208. endfor
  209. endfunction
  210. function! s:trim(str)
  211. return substitute(a:str, '[\/]\+$', '', '')
  212. endfunction
  213. function! s:git_version_requirement(...)
  214. let s:git_version = get(s:, 'git_version',
  215. \ map(split(split(s:system('git --version'))[-1], '\.'), 'str2nr(v:val)'))
  216. for idx in range(0, a:0 - 1)
  217. let v = get(s:git_version, idx, 0)
  218. if v < a:000[idx] | return 0
  219. elseif v > a:000[idx] | return 1
  220. endif
  221. endfor
  222. return 1
  223. endfunction
  224. function! s:progress_opt(base)
  225. return a:base && !s:is_win &&
  226. \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
  227. endfunction
  228. if s:is_win
  229. function! s:rtp(spec)
  230. return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
  231. endfunction
  232. function! s:path(path)
  233. return s:trim(substitute(a:path, '/', '\', 'g'))
  234. endfunction
  235. function! s:dirpath(path)
  236. return s:path(a:path) . '\'
  237. endfunction
  238. function! s:is_local_plug(repo)
  239. return a:repo =~? '^[a-z]:'
  240. endfunction
  241. else
  242. function! s:rtp(spec)
  243. return s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
  244. endfunction
  245. function! s:path(path)
  246. return s:trim(a:path)
  247. endfunction
  248. function! s:dirpath(path)
  249. return substitute(a:path, '[/\\]*$', '/', '')
  250. endfunction
  251. function! s:is_local_plug(repo)
  252. return a:repo[0] =~ '[/$~]'
  253. endfunction
  254. endif
  255. function! s:err(msg)
  256. echohl ErrorMsg
  257. echom a:msg
  258. echohl None
  259. return 0
  260. endfunction
  261. function! s:esc(path)
  262. return escape(a:path, ' ')
  263. endfunction
  264. function! s:escrtp(path)
  265. return escape(a:path, ' ,')
  266. endfunction
  267. function! s:remove_rtp()
  268. for name in s:loaded_names()
  269. let rtp = s:rtp(g:plugs[name])
  270. execute 'set rtp-='.s:escrtp(rtp)
  271. let after = globpath(rtp, 'after')
  272. if isdirectory(after)
  273. execute 'set rtp-='.s:escrtp(after)
  274. endif
  275. endfor
  276. endfunction
  277. function! s:reorg_rtp()
  278. if !empty(s:first_rtp)
  279. execute 'set rtp-='.s:first_rtp
  280. execute 'set rtp-='.s:last_rtp
  281. endif
  282. " &rtp is modified from outside
  283. if exists('s:prtp') && s:prtp !=# &rtp
  284. call s:remove_rtp()
  285. unlet! s:middle
  286. endif
  287. let s:middle = get(s:, 'middle', &rtp)
  288. let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
  289. let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), 'isdirectory(v:val)')
  290. let rtp = join(map(rtps, 's:escrtp(v:val)'), ',')
  291. \ . ','.s:middle.','
  292. \ . join(map(afters, 's:escrtp(v:val)'), ',')
  293. let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
  294. let s:prtp = &rtp
  295. if !empty(s:first_rtp)
  296. execute 'set rtp^='.s:first_rtp
  297. execute 'set rtp+='.s:last_rtp
  298. endif
  299. endfunction
  300. function! plug#load(...)
  301. if a:0 == 0
  302. return s:err('Argument missing: plugin name(s) required')
  303. endif
  304. if !exists('g:plugs')
  305. return s:err('plug#begin was not called')
  306. endif
  307. let unknowns = filter(copy(a:000), '!has_key(g:plugs, v:val)')
  308. if !empty(unknowns)
  309. let s = len(unknowns) > 1 ? 's' : ''
  310. return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
  311. end
  312. for name in a:000
  313. call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
  314. endfor
  315. doautocmd BufRead
  316. return 1
  317. endfunction
  318. function! s:remove_triggers(name)
  319. if !has_key(s:triggers, a:name)
  320. return
  321. endif
  322. for cmd in s:triggers[a:name].cmd
  323. execute 'silent! delc' cmd
  324. endfor
  325. for map in s:triggers[a:name].map
  326. execute 'silent! unmap' map
  327. execute 'silent! iunmap' map
  328. endfor
  329. call remove(s:triggers, a:name)
  330. endfunction
  331. function! s:lod(names, types)
  332. for name in a:names
  333. call s:remove_triggers(name)
  334. let s:loaded[name] = 1
  335. endfor
  336. call s:reorg_rtp()
  337. for name in a:names
  338. let rtp = s:rtp(g:plugs[name])
  339. for dir in a:types
  340. call s:source(rtp, dir.'/**/*.vim')
  341. endfor
  342. endfor
  343. endfunction
  344. function! s:lod_ft(pat, names)
  345. call s:lod(a:names, ['plugin', 'after/plugin'])
  346. execute 'autocmd! PlugLOD FileType' a:pat
  347. doautocmd filetypeplugin FileType
  348. doautocmd filetypeindent FileType
  349. endfunction
  350. function! s:lod_cmd(cmd, bang, l1, l2, args, names)
  351. call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
  352. execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
  353. endfunction
  354. function! s:lod_map(map, names, prefix)
  355. call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
  356. let extra = ''
  357. while 1
  358. let c = getchar(0)
  359. if c == 0
  360. break
  361. endif
  362. let extra .= nr2char(c)
  363. endwhile
  364. call feedkeys(a:prefix . substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
  365. endfunction
  366. function! s:add(repo, ...)
  367. if a:0 > 1
  368. return s:err('Invalid number of arguments (1..2)')
  369. endif
  370. try
  371. let repo = s:trim(a:repo)
  372. let name = fnamemodify(repo, ':t:s?\.git$??')
  373. let spec = extend(s:infer_properties(name, repo),
  374. \ a:0 == 1 ? s:parse_options(a:1) : s:base_spec)
  375. let g:plugs[name] = spec
  376. let g:plugs_order += [name]
  377. let s:loaded[name] = 0
  378. catch
  379. return s:err(v:exception)
  380. endtry
  381. endfunction
  382. function! s:parse_options(arg)
  383. let opts = copy(s:base_spec)
  384. let type = type(a:arg)
  385. if type == s:TYPE.string
  386. let opts.branch = a:arg
  387. elseif type == s:TYPE.dict
  388. call extend(opts, a:arg)
  389. if has_key(opts, 'tag')
  390. let opts.branch = remove(opts, 'tag')
  391. endif
  392. if has_key(opts, 'dir')
  393. let opts.dir = s:dirpath(expand(opts.dir))
  394. endif
  395. else
  396. throw 'Invalid argument type (expected: string or dictionary)'
  397. endif
  398. return opts
  399. endfunction
  400. function! s:infer_properties(name, repo)
  401. let repo = a:repo
  402. if s:is_local_plug(repo)
  403. return { 'dir': s:dirpath(expand(repo)) }
  404. else
  405. if repo =~ ':'
  406. let uri = repo
  407. else
  408. if repo !~ '/'
  409. let repo = 'vim-scripts/'. repo
  410. endif
  411. let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
  412. let uri = printf(fmt, repo)
  413. endif
  414. let dir = s:dirpath( fnamemodify(join([g:plug_home, a:name], '/'), ':p') )
  415. return { 'dir': dir, 'uri': uri }
  416. endif
  417. endfunction
  418. function! s:install(force, names)
  419. call s:update_impl(0, a:force, a:names)
  420. endfunction
  421. function! s:update(force, names)
  422. call s:update_impl(1, a:force, a:names)
  423. endfunction
  424. function! plug#helptags()
  425. if !exists('g:plugs')
  426. return s:err('plug#begin was not called')
  427. endif
  428. for spec in values(g:plugs)
  429. let docd = join([spec.dir, 'doc'], '/')
  430. if isdirectory(docd)
  431. silent! execute 'helptags' s:esc(docd)
  432. endif
  433. endfor
  434. return 1
  435. endfunction
  436. function! s:syntax()
  437. syntax clear
  438. syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
  439. syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
  440. syn match plugNumber /[0-9]\+[0-9.]*/ contained
  441. syn match plugBracket /[[\]]/ contained
  442. syn match plugX /x/ contained
  443. syn match plugDash /^-/
  444. syn match plugPlus /^+/
  445. syn match plugStar /^*/
  446. syn match plugMessage /\(^- \)\@<=.*/
  447. syn match plugName /\(^- \)\@<=[^ ]*:/
  448. syn match plugInstall /\(^+ \)\@<=[^:]*/
  449. syn match plugUpdate /\(^* \)\@<=[^:]*/
  450. syn match plugCommit /^ [0-9a-z]\{7} .*/ contains=plugRelDate,plugSha
  451. syn match plugSha /\(^ \)\@<=[0-9a-z]\{7}/ contained
  452. syn match plugRelDate /([^)]*)$/ contained
  453. syn match plugNotLoaded /(not loaded)$/
  454. syn match plugError /^x.*/
  455. syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
  456. hi def link plug1 Title
  457. hi def link plug2 Repeat
  458. hi def link plugX Exception
  459. hi def link plugBracket Structure
  460. hi def link plugNumber Number
  461. hi def link plugDash Special
  462. hi def link plugPlus Constant
  463. hi def link plugStar Boolean
  464. hi def link plugMessage Function
  465. hi def link plugName Label
  466. hi def link plugInstall Function
  467. hi def link plugUpdate Type
  468. hi def link plugError Error
  469. hi def link plugRelDate Comment
  470. hi def link plugSha Identifier
  471. hi def link plugNotLoaded Comment
  472. endfunction
  473. function! s:lpad(str, len)
  474. return a:str . repeat(' ', a:len - len(a:str))
  475. endfunction
  476. function! s:lines(msg)
  477. return split(a:msg, "[\r\n]")
  478. endfunction
  479. function! s:lastline(msg)
  480. return get(s:lines(a:msg), -1, '')
  481. endfunction
  482. function! s:new_window()
  483. execute get(g:, 'plug_window', 'vertical topleft new')
  484. endfunction
  485. function! s:plug_window_exists()
  486. let buflist = tabpagebuflist(s:plug_tab)
  487. return !empty(buflist) && index(buflist, s:plug_buf) >= 0
  488. endfunction
  489. function! s:switch_in()
  490. if !s:plug_window_exists()
  491. return 0
  492. endif
  493. if winbufnr(0) != s:plug_buf
  494. let s:pos = [tabpagenr(), winnr(), winsaveview()]
  495. execute 'normal!' s:plug_tab.'gt'
  496. let winnr = bufwinnr(s:plug_buf)
  497. execute winnr.'wincmd w'
  498. call add(s:pos, winsaveview())
  499. else
  500. let s:pos = [winsaveview()]
  501. endif
  502. setlocal modifiable
  503. return 1
  504. endfunction
  505. function! s:switch_out(...)
  506. call winrestview(s:pos[-1])
  507. setlocal nomodifiable
  508. if a:0 > 0
  509. execute a:1
  510. endif
  511. if len(s:pos) > 1
  512. execute 'normal!' s:pos[0].'gt'
  513. execute s:pos[1] 'wincmd w'
  514. call winrestview(s:pos[2])
  515. endif
  516. endfunction
  517. function! s:prepare()
  518. call s:job_abort()
  519. if s:switch_in()
  520. silent %d _
  521. else
  522. call s:new_window()
  523. nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>echo<bar>q<cr>
  524. nnoremap <silent> <buffer> R :silent! call <SID>retry()<cr>
  525. nnoremap <silent> <buffer> D :PlugDiff<cr>
  526. nnoremap <silent> <buffer> S :PlugStatus<cr>
  527. nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
  528. xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
  529. nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
  530. nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
  531. let b:plug_preview = -1
  532. let s:plug_tab = tabpagenr()
  533. let s:plug_buf = winbufnr(0)
  534. call s:assign_name()
  535. endif
  536. silent! unmap <buffer> <cr>
  537. silent! unmap <buffer> L
  538. silent! unmap <buffer> X
  539. setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline modifiable
  540. setf vim-plug
  541. call s:syntax()
  542. endfunction
  543. function! s:assign_name()
  544. " Assign buffer name
  545. let prefix = '[Plugins]'
  546. let name = prefix
  547. let idx = 2
  548. while bufexists(name)
  549. let name = printf('%s (%s)', prefix, idx)
  550. let idx = idx + 1
  551. endwhile
  552. silent! execute 'f' fnameescape(name)
  553. endfunction
  554. function! s:do(pull, force, todo)
  555. for [name, spec] in items(a:todo)
  556. if !isdirectory(spec.dir)
  557. continue
  558. endif
  559. let installed = has_key(s:update.new, name)
  560. let updated = installed ? 0 :
  561. \ (a:pull && !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', spec.dir)))
  562. if a:force || installed || updated
  563. execute 'cd' s:esc(spec.dir)
  564. call append(3, '- Post-update hook for '. name .' ... ')
  565. let type = type(spec.do)
  566. if type == s:TYPE.string
  567. try
  568. " FIXME: Escaping is incomplete. We could use shellescape with eval,
  569. " but it won't work on Windows.
  570. let g:_plug_do = '!'.escape(spec.do, '#!%')
  571. execute "normal! :execute g:_plug_do\<cr>\<cr>"
  572. finally
  573. let result = v:shell_error ? ('Exit status: '.v:shell_error) : 'Done!'
  574. unlet g:_plug_do
  575. endtry
  576. elseif type == s:TYPE.funcref
  577. try
  578. let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
  579. call spec.do({ 'name': name, 'status': status, 'force': a:force })
  580. let result = 'Done!'
  581. catch
  582. let result = 'Error: ' . v:exception
  583. endtry
  584. else
  585. let result = 'Error: Invalid type!'
  586. endif
  587. call setline(4, getline(4) . result)
  588. cd -
  589. endif
  590. endfor
  591. endfunction
  592. function! s:finish(pull)
  593. let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
  594. if new_frozen
  595. let s = new_frozen > 1 ? 's' : ''
  596. call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
  597. endif
  598. call append(3, '- Finishing ... ')
  599. redraw
  600. call plug#helptags()
  601. call plug#end()
  602. call setline(4, getline(4) . 'Done!')
  603. redraw
  604. let msgs = []
  605. if !empty(s:update.errors)
  606. call add(msgs, "Press 'R' to retry.")
  607. endif
  608. if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
  609. \ "v:val =~ '^- ' && stridx(v:val, 'Already up-to-date') < 0"))
  610. call add(msgs, "Press 'D' to see the updated changes.")
  611. endif
  612. echo join(msgs, ' ')
  613. endfunction
  614. function! s:retry()
  615. if empty(s:update.errors)
  616. return
  617. endif
  618. call s:update_impl(s:update.pull, s:update.force,
  619. \ extend(copy(s:update.errors), [s:update.threads]))
  620. endfunction
  621. function! s:is_managed(name)
  622. return has_key(g:plugs[a:name], 'uri')
  623. endfunction
  624. function! s:names(...)
  625. return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
  626. endfunction
  627. function! s:update_impl(pull, force, args) abort
  628. let args = copy(a:args)
  629. let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
  630. \ remove(args, -1) : get(g:, 'plug_threads', 16)
  631. let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
  632. let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
  633. \ filter(managed, 'index(args, v:key) >= 0')
  634. if empty(todo)
  635. echohl WarningMsg
  636. echo 'No plugin to '. (a:pull ? 'update' : 'install') . '.'
  637. echohl None
  638. return
  639. endif
  640. if !isdirectory(g:plug_home)
  641. try
  642. call mkdir(g:plug_home, 'p')
  643. catch
  644. return s:err(printf('Invalid plug directory: %s. '.
  645. \ 'Try to call plug#begin with a valid directory', g:plug_home))
  646. endtry
  647. endif
  648. let s:update = {
  649. \ 'start': reltime(),
  650. \ 'all': todo,
  651. \ 'todo': copy(todo),
  652. \ 'errors': [],
  653. \ 'pull': a:pull,
  654. \ 'force': a:force,
  655. \ 'new': {},
  656. \ 'threads': (has('ruby') || s:nvim) ? min([len(todo), threads]) : 1,
  657. \ 'bar': '',
  658. \ 'fin': 0
  659. \ }
  660. call s:prepare()
  661. call append(0, ['', ''])
  662. normal! 2G
  663. if has('ruby') && s:update.threads > 1
  664. try
  665. let imd = &imd
  666. if s:mac_gui
  667. set noimd
  668. endif
  669. call s:update_ruby()
  670. catch
  671. let lines = getline(4, '$')
  672. let printed = {}
  673. silent! 4,$d _
  674. for line in lines
  675. let name = s:extract_name(line, '.', '')
  676. if empty(name) || !has_key(printed, name)
  677. call append('$', line)
  678. if !empty(name)
  679. let printed[name] = 1
  680. if line[0] == 'x' && index(s:update.errors, name) < 0
  681. call add(s:update.errors, name)
  682. end
  683. endif
  684. endif
  685. endfor
  686. finally
  687. let &imd = imd
  688. call s:update_finish()
  689. endtry
  690. else
  691. call s:update_vim()
  692. endif
  693. endfunction
  694. function! s:update_finish()
  695. if s:switch_in()
  696. call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'has_key(v:val, "do")'))
  697. call s:finish(s:update.pull)
  698. call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
  699. call s:switch_out('normal! gg')
  700. endif
  701. endfunction
  702. function! s:job_abort()
  703. if !s:nvim || !exists('s:jobs')
  704. return
  705. endif
  706. augroup PlugJobControl
  707. autocmd!
  708. augroup END
  709. for [name, j] in items(s:jobs)
  710. silent! call jobstop(j.jobid)
  711. if j.new
  712. call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir))
  713. endif
  714. endfor
  715. let s:jobs = {}
  716. endfunction
  717. function! s:job_handler(name) abort
  718. if !s:plug_window_exists() " plug window closed
  719. return s:job_abort()
  720. endif
  721. if !has_key(s:jobs, a:name)
  722. return
  723. endif
  724. let job = s:jobs[a:name]
  725. if v:job_data[1] == 'exit'
  726. let job.running = 0
  727. if s:lastline(job.result) ==# 'Error'
  728. let job.error = 1
  729. let job.result = substitute(job.result, "Error[\r\n]$", '', '')
  730. endif
  731. call s:reap(a:name)
  732. call s:tick()
  733. else
  734. let job.result .= s:to_s(v:job_data[2])
  735. " To reduce the number of buffer updates
  736. let job.tick = get(job, 'tick', -1) + 1
  737. if job.tick % len(s:jobs) == 0
  738. call s:log(job.new ? '+' : '*', a:name, job.result)
  739. endif
  740. endif
  741. endfunction
  742. function! s:spawn(name, cmd, opts)
  743. let job = { 'running': 1, 'new': get(a:opts, 'new', 0),
  744. \ 'error': 0, 'result': '' }
  745. let s:jobs[a:name] = job
  746. if s:nvim
  747. let x = jobstart(a:name, 'sh', ['-c',
  748. \ (has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd)
  749. \ . ' || echo Error'])
  750. if x > 0
  751. let job.jobid = x
  752. augroup PlugJobControl
  753. execute 'autocmd JobActivity' a:name printf('call s:job_handler(%s)', string(a:name))
  754. augroup END
  755. else
  756. let job.running = 0
  757. let job.error = 1
  758. let job.result = x < 0 ? 'sh is not executable' :
  759. \ 'Invalid arguments (or job table is full)'
  760. endif
  761. else
  762. let params = has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]
  763. let job.result = call('s:system', params)
  764. let job.error = v:shell_error != 0
  765. let job.running = 0
  766. endif
  767. endfunction
  768. function! s:reap(name)
  769. if s:nvim
  770. silent! execute 'autocmd! PlugJobControl JobActivity' a:name
  771. endif
  772. let job = s:jobs[a:name]
  773. if job.error
  774. call add(s:update.errors, a:name)
  775. elseif get(job, 'new', 0)
  776. let s:update.new[a:name] = 1
  777. endif
  778. let s:update.bar .= job.error ? 'x' : '='
  779. call s:log(job.error ? 'x' : '-', a:name, job.result)
  780. call s:bar()
  781. call remove(s:jobs, a:name)
  782. endfunction
  783. function! s:bar()
  784. if s:switch_in()
  785. let total = len(s:update.all)
  786. call setline(1, (s:update.pull ? 'Updating' : 'Installing').
  787. \ ' plugins ('.len(s:update.bar).'/'.total.')')
  788. call s:progress_bar(2, s:update.bar, total)
  789. call s:switch_out()
  790. endif
  791. endfunction
  792. function! s:logpos(name)
  793. for i in range(1, line('$'))
  794. if getline(i) =~# '^[-+x*] '.a:name.':'
  795. return i
  796. endif
  797. endfor
  798. return 0
  799. endfunction
  800. function! s:log(bullet, name, lines)
  801. if s:switch_in()
  802. let pos = s:logpos(a:name)
  803. if pos > 0
  804. execute pos 'd _'
  805. if pos > winheight('.')
  806. let pos = 4
  807. endif
  808. else
  809. let pos = 4
  810. endif
  811. call append(pos - 1, s:format_message(a:bullet, a:name, a:lines))
  812. call s:switch_out()
  813. endif
  814. endfunction
  815. function! s:update_vim()
  816. let s:jobs = {}
  817. call s:bar()
  818. call s:tick()
  819. endfunction
  820. function! s:tick()
  821. let pull = s:update.pull
  822. let prog = s:progress_opt(s:nvim)
  823. while 1 " Without TCO, Vim stack is bound to explode
  824. if empty(s:update.todo)
  825. if empty(s:jobs) && !s:update.fin
  826. let s:update.fin = 1
  827. call s:update_finish()
  828. endif
  829. return
  830. endif
  831. let name = keys(s:update.todo)[0]
  832. let spec = remove(s:update.todo, name)
  833. let new = !isdirectory(spec.dir)
  834. call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
  835. redraw
  836. if !new
  837. let [valid, msg] = s:git_valid(spec, 0)
  838. if valid
  839. if pull
  840. call s:spawn(name,
  841. \ printf('(git fetch %s 2>&1 && git checkout -q %s 2>&1 && git merge --ff-only origin/%s 2>&1 && git submodule update --init --recursive 2>&1)',
  842. \ prog, s:shellesc(spec.branch), s:shellesc(spec.branch)), { 'dir': spec.dir })
  843. else
  844. let s:jobs[name] = { 'running': 0, 'result': 'Already installed', 'error': 0 }
  845. endif
  846. else
  847. let s:jobs[name] = { 'running': 0, 'result': msg, 'error': 1 }
  848. endif
  849. else
  850. call s:spawn(name,
  851. \ printf('git clone %s --recursive %s -b %s %s 2>&1',
  852. \ prog,
  853. \ s:shellesc(spec.uri),
  854. \ s:shellesc(spec.branch),
  855. \ s:shellesc(s:trim(spec.dir))), { 'new': 1 })
  856. endif
  857. if !s:jobs[name].running
  858. call s:reap(name)
  859. endif
  860. if len(s:jobs) >= s:update.threads
  861. break
  862. endif
  863. endwhile
  864. endfunction
  865. function! s:update_ruby()
  866. ruby << EOF
  867. module PlugStream
  868. SEP = ["\r", "\n", nil]
  869. def get_line
  870. buffer = ''
  871. loop do
  872. char = readchar rescue return
  873. if SEP.include? char.chr
  874. buffer << $/
  875. break
  876. else
  877. buffer << char
  878. end
  879. end
  880. buffer
  881. end
  882. end unless defined?(PlugStream)
  883. def esc arg
  884. %["#{arg.gsub('"', '\"')}"]
  885. end
  886. def killall pid
  887. pids = [pid]
  888. unless `which pgrep 2> /dev/null`.empty?
  889. children = pids
  890. until children.empty?
  891. children = children.map { |pid|
  892. `pgrep -P #{pid}`.lines.map { |l| l.chomp }
  893. }.flatten
  894. pids += children
  895. end
  896. end
  897. pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
  898. end
  899. require 'thread'
  900. require 'fileutils'
  901. require 'timeout'
  902. running = true
  903. iswin = VIM::evaluate('s:is_win').to_i == 1
  904. pull = VIM::evaluate('s:update.pull').to_i == 1
  905. base = VIM::evaluate('g:plug_home')
  906. all = VIM::evaluate('s:update.todo')
  907. limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
  908. tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
  909. nthr = VIM::evaluate('s:update.threads').to_i
  910. maxy = VIM::evaluate('winheight(".")').to_i
  911. cd = iswin ? 'cd /d' : 'cd'
  912. tot = VIM::evaluate('len(s:update.todo)') || 0
  913. bar = ''
  914. skip = 'Already installed'
  915. mtx = Mutex.new
  916. take1 = proc { mtx.synchronize { running && all.shift } }
  917. logh = proc {
  918. cnt = bar.length
  919. $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
  920. $curbuf[2] = '[' + bar.ljust(tot) + ']'
  921. VIM::command('normal! 2G')
  922. VIM::command('redraw') unless iswin
  923. }
  924. where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
  925. log = proc { |name, result, type|
  926. mtx.synchronize do
  927. ing = ![true, false].include?(type)
  928. bar += type ? '=' : 'x' unless ing
  929. b = case type
  930. when :install then '+' when :update then '*'
  931. when true, nil then '-' else
  932. VIM::command("call add(s:update.errors, '#{name}')")
  933. 'x'
  934. end
  935. result =
  936. if type || type.nil?
  937. ["#{b} #{name}: #{result.lines.to_a.last}"]
  938. elsif result =~ /^Interrupted|^Timeout/
  939. ["#{b} #{name}: #{result}"]
  940. else
  941. ["#{b} #{name}"] + result.lines.map { |l| " " << l }
  942. end
  943. if lnum = where.call(name)
  944. $curbuf.delete lnum
  945. lnum = 4 if ing && lnum > maxy
  946. end
  947. result.each_with_index do |line, offset|
  948. $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
  949. end
  950. logh.call
  951. end
  952. }
  953. bt = proc { |cmd, name, type, cleanup|
  954. tried = timeout = 0
  955. begin
  956. tried += 1
  957. timeout += limit
  958. fd = nil
  959. data = ''
  960. if iswin
  961. Timeout::timeout(timeout) do
  962. tmp = VIM::evaluate('tempname()')
  963. system("#{cmd} > #{tmp}")
  964. data = File.read(tmp).chomp
  965. File.unlink tmp rescue nil
  966. end
  967. else
  968. fd = IO.popen(cmd).extend(PlugStream)
  969. first_line = true
  970. log_prob = 1.0 / nthr
  971. while line = Timeout::timeout(timeout) { fd.get_line }
  972. data << line
  973. log.call name, line.chomp, type if name && (first_line || rand < log_prob)
  974. first_line = false
  975. end
  976. fd.close
  977. end
  978. [$? == 0, data.chomp]
  979. rescue Timeout::Error, Interrupt => e
  980. if fd && !fd.closed?
  981. killall fd.pid
  982. fd.close
  983. end
  984. cleanup.call if cleanup
  985. if e.is_a?(Timeout::Error) && tried < tries
  986. 3.downto(1) do |countdown|
  987. s = countdown > 1 ? 's' : ''
  988. log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
  989. sleep 1
  990. end
  991. log.call name, 'Retrying ...', type
  992. retry
  993. end
  994. [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
  995. end
  996. }
  997. main = Thread.current
  998. threads = []
  999. watcher = Thread.new {
  1000. while VIM::evaluate('getchar(1)')
  1001. sleep 0.1
  1002. end
  1003. mtx.synchronize do
  1004. running = false
  1005. threads.each { |t| t.raise Interrupt }
  1006. end
  1007. threads.each { |t| t.join rescue nil }
  1008. main.kill
  1009. }
  1010. refresh = Thread.new {
  1011. while true
  1012. mtx.synchronize do
  1013. break unless running
  1014. VIM::command('noautocmd normal! a')
  1015. end
  1016. sleep 0.2
  1017. end
  1018. } if VIM::evaluate('s:mac_gui') == 1
  1019. progress = VIM::evaluate('s:progress_opt(1)')
  1020. nthr.times do
  1021. mtx.synchronize do
  1022. threads << Thread.new {
  1023. while pair = take1.call
  1024. name = pair.first
  1025. dir, uri, branch = pair.last.values_at *%w[dir uri branch]
  1026. branch = esc branch
  1027. subm = "git submodule update --init --recursive 2>&1"
  1028. exists = File.directory? dir
  1029. ok, result =
  1030. if exists
  1031. dir = esc dir
  1032. ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url", nil, nil, nil
  1033. current_uri = data.lines.to_a.last
  1034. if !ret
  1035. if data =~ /^Interrupted|^Timeout/
  1036. [false, data]
  1037. else
  1038. [false, [data.chomp, "PlugClean required."].join($/)]
  1039. end
  1040. elsif current_uri.sub(/git::?@/, '') != uri.sub(/git::?@/, '')
  1041. [false, ["Invalid URI: #{current_uri}",
  1042. "Expected: #{uri}",
  1043. "PlugClean required."].join($/)]
  1044. else
  1045. if pull
  1046. log.call name, 'Updating ...', :update
  1047. bt.call "#{cd} #{dir} && (git fetch #{progress} 2>&1 && git checkout -q #{branch} 2>&1 && git merge --ff-only origin/#{branch} 2>&1 && #{subm})", name, :update, nil
  1048. else
  1049. [true, skip]
  1050. end
  1051. end
  1052. else
  1053. d = esc dir.sub(%r{[\\/]+$}, '')
  1054. log.call name, 'Installing ...', :install
  1055. bt.call "git clone #{progress} --recursive #{uri} -b #{branch} #{d} 2>&1", name, :install, proc {
  1056. FileUtils.rm_rf dir
  1057. }
  1058. end
  1059. mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
  1060. log.call name, result, ok
  1061. end
  1062. } if running
  1063. end
  1064. end
  1065. threads.each { |t| t.join rescue nil }
  1066. logh.call
  1067. refresh.kill if refresh
  1068. watcher.kill
  1069. EOF
  1070. endfunction
  1071. function! s:shellesc(arg)
  1072. return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
  1073. endfunction
  1074. function! s:glob_dir(path)
  1075. return map(filter(s:lines(globpath(a:path, '**')), 'isdirectory(v:val)'), 's:dirpath(v:val)')
  1076. endfunction
  1077. function! s:progress_bar(line, bar, total)
  1078. call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
  1079. endfunction
  1080. function! s:compare_git_uri(a, b)
  1081. let a = substitute(a:a, 'git:\{1,2}@', '', '')
  1082. let b = substitute(a:b, 'git:\{1,2}@', '', '')
  1083. return a ==# b
  1084. endfunction
  1085. function! s:format_message(bullet, name, message)
  1086. if a:bullet != 'x'
  1087. return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
  1088. else
  1089. let lines = map(s:lines(a:message), '" ".v:val')
  1090. return extend([printf('x %s:', a:name)], lines)
  1091. endif
  1092. endfunction
  1093. function! s:with_cd(cmd, dir)
  1094. return (s:is_win ? 'cd /d ' : 'cd ').s:esc(a:dir).' && '.a:cmd
  1095. endfunction
  1096. function! s:system(cmd, ...)
  1097. try
  1098. let sh = &shell
  1099. if !s:is_win
  1100. set shell=sh
  1101. endif
  1102. let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd
  1103. return system(s:is_win ? '('.cmd.')' : cmd)
  1104. finally
  1105. let &shell = sh
  1106. endtry
  1107. endfunction
  1108. function! s:system_chomp(...)
  1109. let ret = call('s:system', a:000)
  1110. return v:shell_error ? '' : substitute(ret, '\n$', '', '')
  1111. endfunction
  1112. function! s:git_valid(spec, check_branch)
  1113. let ret = 1
  1114. let msg = 'OK'
  1115. if isdirectory(a:spec.dir)
  1116. let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url', a:spec.dir))
  1117. let remote = result[-1]
  1118. if v:shell_error
  1119. let msg = join([remote, 'PlugClean required.'], "\n")
  1120. let ret = 0
  1121. elseif !s:compare_git_uri(remote, a:spec.uri)
  1122. let msg = join(['Invalid URI: '.remote,
  1123. \ 'Expected: '.a:spec.uri,
  1124. \ 'PlugClean required.'], "\n")
  1125. let ret = 0
  1126. elseif a:check_branch
  1127. let branch = result[0]
  1128. if a:spec.branch !=# branch
  1129. let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
  1130. if a:spec.branch !=# tag
  1131. let msg = printf('Invalid branch/tag: %s (expected: %s). Try PlugUpdate.',
  1132. \ (empty(tag) ? branch : tag), a:spec.branch)
  1133. let ret = 0
  1134. endif
  1135. endif
  1136. endif
  1137. else
  1138. let msg = 'Not found'
  1139. let ret = 0
  1140. endif
  1141. return [ret, msg]
  1142. endfunction
  1143. function! s:clean(force)
  1144. call s:prepare()
  1145. call append(0, 'Searching for unused plugins in '.g:plug_home)
  1146. call append(1, '')
  1147. " List of valid directories
  1148. let dirs = []
  1149. let [cnt, total] = [0, len(g:plugs)]
  1150. for [name, spec] in items(g:plugs)
  1151. if !s:is_managed(name) || s:git_valid(spec, 0)[0]
  1152. call add(dirs, spec.dir)
  1153. endif
  1154. let cnt += 1
  1155. call s:progress_bar(2, repeat('=', cnt), total)
  1156. normal! 2G
  1157. redraw
  1158. endfor
  1159. let allowed = {}
  1160. for dir in dirs
  1161. let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1
  1162. let allowed[dir] = 1
  1163. for child in s:glob_dir(dir)
  1164. let allowed[child] = 1
  1165. endfor
  1166. endfor
  1167. let todo = []
  1168. let found = sort(s:glob_dir(g:plug_home))
  1169. while !empty(found)
  1170. let f = remove(found, 0)
  1171. if !has_key(allowed, f) && isdirectory(f)
  1172. call add(todo, f)
  1173. call append(line('$'), '- ' . f)
  1174. let found = filter(found, 'stridx(v:val, f) != 0')
  1175. end
  1176. endwhile
  1177. normal! G
  1178. redraw
  1179. if empty(todo)
  1180. call append(line('$'), 'Already clean.')
  1181. else
  1182. call inputsave()
  1183. let yes = a:force || (input('Proceed? (y/N) ') =~? '^y')
  1184. call inputrestore()
  1185. if yes
  1186. for dir in todo
  1187. if isdirectory(dir)
  1188. call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(dir))
  1189. endif
  1190. endfor
  1191. call append(line('$'), 'Removed.')
  1192. else
  1193. call append(line('$'), 'Cancelled.')
  1194. endif
  1195. endif
  1196. normal! G
  1197. endfunction
  1198. function! s:upgrade()
  1199. let new = s:me . '.new'
  1200. echo 'Downloading '. s:plug_src
  1201. redraw
  1202. try
  1203. if executable('curl')
  1204. let output = s:system(printf('curl -fLo %s %s', s:shellesc(new), s:plug_src))
  1205. if v:shell_error
  1206. throw get(s:lines(output), -1, v:shell_error)
  1207. endif
  1208. elseif has('ruby')
  1209. call s:upgrade_using_ruby(new)
  1210. else
  1211. return s:err('curl executable or ruby support not found')
  1212. endif
  1213. catch
  1214. return s:err('Error upgrading vim-plug: '. v:exception)
  1215. endtry
  1216. if readfile(s:me) ==# readfile(new)
  1217. echo 'vim-plug is already up-to-date'
  1218. silent! call delete(new)
  1219. return 0
  1220. else
  1221. call rename(s:me, s:me . '.old')
  1222. call rename(new, s:me)
  1223. unlet g:loaded_plug
  1224. echo 'vim-plug has been upgraded'
  1225. return 1
  1226. endif
  1227. endfunction
  1228. function! s:upgrade_using_ruby(new)
  1229. ruby << EOF
  1230. require 'open-uri'
  1231. File.open(VIM::evaluate('a:new'), 'w') do |f|
  1232. f << open(VIM::evaluate('s:plug_src')).read
  1233. end
  1234. EOF
  1235. endfunction
  1236. function! s:upgrade_specs()
  1237. for spec in values(g:plugs)
  1238. let spec.frozen = get(spec, 'frozen', 0)
  1239. endfor
  1240. endfunction
  1241. function! s:status()
  1242. call s:prepare()
  1243. call append(0, 'Checking plugins')
  1244. call append(1, '')
  1245. let ecnt = 0
  1246. let unloaded = 0
  1247. let [cnt, total] = [0, len(g:plugs)]
  1248. for [name, spec] in items(g:plugs)
  1249. if has_key(spec, 'uri')
  1250. if isdirectory(spec.dir)
  1251. let [valid, msg] = s:git_valid(spec, 1)
  1252. else
  1253. let [valid, msg] = [0, 'Not found. Try PlugInstall.']
  1254. endif
  1255. else
  1256. if isdirectory(spec.dir)
  1257. let [valid, msg] = [1, 'OK']
  1258. else
  1259. let [valid, msg] = [0, 'Not found.']
  1260. endif
  1261. endif
  1262. let cnt += 1
  1263. let ecnt += !valid
  1264. " `s:loaded` entry can be missing if PlugUpgraded
  1265. if valid && get(s:loaded, name, -1) == 0
  1266. let unloaded = 1
  1267. let msg .= ' (not loaded)'
  1268. endif
  1269. call s:progress_bar(2, repeat('=', cnt), total)
  1270. call append(3, s:format_message(valid ? '-' : 'x', name, msg))
  1271. normal! 2G
  1272. redraw
  1273. endfor
  1274. call setline(1, 'Finished. '.ecnt.' error(s).')
  1275. normal! gg
  1276. setlocal nomodifiable
  1277. if unloaded
  1278. echo "Press 'L' on each line to load plugin, or 'U' to update"
  1279. nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
  1280. xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
  1281. end
  1282. endfunction
  1283. function! s:extract_name(str, prefix, suffix)
  1284. return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
  1285. endfunction
  1286. function! s:status_load(lnum)
  1287. let line = getline(a:lnum)
  1288. let name = s:extract_name(line, '-', '(not loaded)')
  1289. if !empty(name)
  1290. call plug#load(name)
  1291. setlocal modifiable
  1292. call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
  1293. setlocal nomodifiable
  1294. endif
  1295. endfunction
  1296. function! s:status_update() range
  1297. let lines = getline(a:firstline, a:lastline)
  1298. let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
  1299. if !empty(names)
  1300. echo
  1301. execute 'PlugUpdate' join(names)
  1302. endif
  1303. endfunction
  1304. function! s:is_preview_window_open()
  1305. silent! wincmd P
  1306. if &previewwindow
  1307. wincmd p
  1308. return 1
  1309. endif
  1310. return 0
  1311. endfunction
  1312. function! s:find_name(lnum)
  1313. for lnum in reverse(range(1, a:lnum))
  1314. let line = getline(lnum)
  1315. if empty(line)
  1316. return ''
  1317. endif
  1318. let name = s:extract_name(line, '-', '')
  1319. if !empty(name)
  1320. return name
  1321. endif
  1322. endfor
  1323. return ''
  1324. endfunction
  1325. function! s:preview_commit()
  1326. if b:plug_preview < 0
  1327. let b:plug_preview = !s:is_preview_window_open()
  1328. endif
  1329. let sha = matchstr(getline('.'), '\(^ \)\@<=[0-9a-z]\{7}')
  1330. if empty(sha)
  1331. return
  1332. endif
  1333. let name = s:find_name(line('.'))
  1334. if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
  1335. return
  1336. endif
  1337. execute 'pedit' sha
  1338. wincmd P
  1339. setlocal filetype=git buftype=nofile nobuflisted
  1340. execute 'silent read !cd' s:esc(g:plugs[name].dir) '&& git show' sha
  1341. normal! gg"_dd
  1342. wincmd p
  1343. endfunction
  1344. function! s:section(flags)
  1345. call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
  1346. endfunction
  1347. function! s:diff()
  1348. call s:prepare()
  1349. call append(0, 'Collecting updated changes ...')
  1350. normal! gg
  1351. redraw
  1352. let cnt = 0
  1353. for [k, v] in items(g:plugs)
  1354. if !isdirectory(v.dir) || !s:is_managed(k)
  1355. continue
  1356. endif
  1357. let diff = s:system_chomp('git log --pretty=format:"%h %s (%cr)" "HEAD...HEAD@{1}"', v.dir)
  1358. if !empty(diff)
  1359. call append(1, '')
  1360. call append(2, '- '.k.':')
  1361. call append(3, map(s:lines(diff), '" ". v:val'))
  1362. let cnt += 1
  1363. normal! gg
  1364. redraw
  1365. endif
  1366. endfor
  1367. call setline(1, cnt == 0 ? 'No updates.' : 'Last update:')
  1368. nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
  1369. nnoremap <silent> <buffer> X :call <SID>revert()<cr>
  1370. normal! gg
  1371. setlocal nomodifiable
  1372. if cnt > 0
  1373. echo "Press 'X' on each block to revert the update"
  1374. endif
  1375. endfunction
  1376. function! s:revert()
  1377. let name = s:find_name(line('.'))
  1378. if empty(name) || !has_key(g:plugs, name) ||
  1379. \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
  1380. return
  1381. endif
  1382. call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch), g:plugs[name].dir)
  1383. setlocal modifiable
  1384. normal! "_dap
  1385. setlocal nomodifiable
  1386. echo 'Reverted.'
  1387. endfunction
  1388. function! s:snapshot(...) abort
  1389. let home = get(s:, 'plug_home_org', g:plug_home)
  1390. let [type, var, header] = s:is_win ?
  1391. \ ['dosbatch', '%PLUG_HOME%',
  1392. \ ['@echo off', ':: Generated by vim-plug', ':: '.strftime("%c"), '',
  1393. \ ':: Make sure to PlugUpdate first', '', 'set PLUG_HOME='.s:esc(home)]] :
  1394. \ ['sh', '$PLUG_HOME',
  1395. \ ['#!/bin/sh', '# Generated by vim-plug', '# '.strftime("%c"), '',
  1396. \ 'vim +PlugUpdate +qa', '', 'PLUG_HOME='.s:esc(home)]]
  1397. call s:prepare()
  1398. execute 'setf' type
  1399. call append(0, header)
  1400. call append('$', '')
  1401. 1
  1402. redraw
  1403. let dirs = sort(map(values(filter(copy(g:plugs),
  1404. \'has_key(v:val, "uri") && isdirectory(v:val.dir)')), 'v:val.dir'))
  1405. let anchor = line('$') - 1
  1406. for dir in reverse(dirs)
  1407. let sha = s:system_chomp('git rev-parse --short HEAD', dir)
  1408. if !empty(sha)
  1409. call append(anchor, printf('cd %s && git reset --hard %s',
  1410. \ substitute(dir, '^'.g:plug_home, var, ''), sha))
  1411. redraw
  1412. endif
  1413. endfor
  1414. if a:0 > 0
  1415. let fn = s:esc(expand(a:1))
  1416. call writefile(getline(1, '$'), fn)
  1417. if !s:is_win | call s:system('chmod +x ' . fn) | endif
  1418. echo 'Saved to '.a:1
  1419. silent execute 'e' fn
  1420. endif
  1421. endfunction
  1422. function! s:split_rtp()
  1423. return split(&rtp, '\\\@<!,')
  1424. endfunction
  1425. let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
  1426. let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
  1427. if exists('g:plugs')
  1428. let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
  1429. call s:upgrade_specs()
  1430. call s:define_commands()
  1431. endif
  1432. let &cpo = s:cpo_save
  1433. unlet s:cpo_save