plug.vim 82 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830
  1. " vim-plug: Vim plugin manager
  2. " ============================
  3. "
  4. " 1. Download plug.vim and put it in 'autoload' directory
  5. "
  6. " # Vim
  7. " curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
  8. " https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
  9. "
  10. " # Neovim
  11. " sh -c 'curl -fLo "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/autoload/plug.vim --create-dirs \
  12. " https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
  13. "
  14. " 2. Add a vim-plug section to your ~/.vimrc (or ~/.config/nvim/init.vim for Neovim)
  15. "
  16. " call plug#begin()
  17. "
  18. " " List your plugins here
  19. " Plug 'tpope/vim-sensible'
  20. "
  21. " call plug#end()
  22. "
  23. " 3. Reload the file or restart Vim, then you can,
  24. "
  25. " :PlugInstall to install plugins
  26. " :PlugUpdate to update plugins
  27. " :PlugDiff to review the changes from the last update
  28. " :PlugClean to remove plugins no longer in the list
  29. "
  30. " For more information, see https://github.com/junegunn/vim-plug
  31. "
  32. "
  33. " Copyright (c) 2024 Junegunn Choi
  34. "
  35. " MIT License
  36. "
  37. " Permission is hereby granted, free of charge, to any person obtaining
  38. " a copy of this software and associated documentation files (the
  39. " "Software"), to deal in the Software without restriction, including
  40. " without limitation the rights to use, copy, modify, merge, publish,
  41. " distribute, sublicense, and/or sell copies of the Software, and to
  42. " permit persons to whom the Software is furnished to do so, subject to
  43. " the following conditions:
  44. "
  45. " The above copyright notice and this permission notice shall be
  46. " included in all copies or substantial portions of the Software.
  47. "
  48. " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  49. " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  50. " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  51. " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  52. " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  53. " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  54. " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  55. if exists('g:loaded_plug')
  56. finish
  57. endif
  58. let g:loaded_plug = 1
  59. let s:cpo_save = &cpo
  60. set cpo&vim
  61. let s:plug_src = 'https://github.com/junegunn/vim-plug.git'
  62. let s:plug_tab = get(s:, 'plug_tab', -1)
  63. let s:plug_buf = get(s:, 'plug_buf', -1)
  64. let s:mac_gui = has('gui_macvim') && has('gui_running')
  65. let s:is_win = has('win32')
  66. let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win)
  67. let s:vim8 = has('patch-8.0.0039') && exists('*job_start')
  68. if s:is_win && &shellslash
  69. set noshellslash
  70. let s:me = resolve(expand('<sfile>:p'))
  71. set shellslash
  72. else
  73. let s:me = resolve(expand('<sfile>:p'))
  74. endif
  75. let s:base_spec = { 'branch': '', '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! s:is_powershell(shell)
  85. return a:shell =~# 'powershell\(\.exe\)\?$' || a:shell =~# 'pwsh\(\.exe\)\?$'
  86. endfunction
  87. function! s:isabsolute(dir) abort
  88. return a:dir =~# '^/' || (has('win32') && a:dir =~? '^\%(\\\|[A-Z]:\)')
  89. endfunction
  90. function! s:git_dir(dir) abort
  91. let gitdir = s:trim(a:dir) . '/.git'
  92. if isdirectory(gitdir)
  93. return gitdir
  94. endif
  95. if !filereadable(gitdir)
  96. return ''
  97. endif
  98. let gitdir = matchstr(get(readfile(gitdir), 0, ''), '^gitdir: \zs.*')
  99. if len(gitdir) && !s:isabsolute(gitdir)
  100. let gitdir = a:dir . '/' . gitdir
  101. endif
  102. return isdirectory(gitdir) ? gitdir : ''
  103. endfunction
  104. function! s:git_origin_url(dir) abort
  105. let gitdir = s:git_dir(a:dir)
  106. let config = gitdir . '/config'
  107. if empty(gitdir) || !filereadable(config)
  108. return ''
  109. endif
  110. return matchstr(join(readfile(config)), '\[remote "origin"\].\{-}url\s*=\s*\zs\S*\ze')
  111. endfunction
  112. function! s:git_revision(dir) abort
  113. let gitdir = s:git_dir(a:dir)
  114. let head = gitdir . '/HEAD'
  115. if empty(gitdir) || !filereadable(head)
  116. return ''
  117. endif
  118. let line = get(readfile(head), 0, '')
  119. let ref = matchstr(line, '^ref: \zs.*')
  120. if empty(ref)
  121. return line
  122. endif
  123. if filereadable(gitdir . '/' . ref)
  124. return get(readfile(gitdir . '/' . ref), 0, '')
  125. endif
  126. if filereadable(gitdir . '/packed-refs')
  127. for line in readfile(gitdir . '/packed-refs')
  128. if line =~# ' ' . ref
  129. return matchstr(line, '^[0-9a-f]*')
  130. endif
  131. endfor
  132. endif
  133. return ''
  134. endfunction
  135. function! s:git_local_branch(dir) abort
  136. let gitdir = s:git_dir(a:dir)
  137. let head = gitdir . '/HEAD'
  138. if empty(gitdir) || !filereadable(head)
  139. return ''
  140. endif
  141. let branch = matchstr(get(readfile(head), 0, ''), '^ref: refs/heads/\zs.*')
  142. return len(branch) ? branch : 'HEAD'
  143. endfunction
  144. function! s:git_origin_branch(spec)
  145. if len(a:spec.branch)
  146. return a:spec.branch
  147. endif
  148. " The file may not be present if this is a local repository
  149. let gitdir = s:git_dir(a:spec.dir)
  150. let origin_head = gitdir.'/refs/remotes/origin/HEAD'
  151. if len(gitdir) && filereadable(origin_head)
  152. return matchstr(get(readfile(origin_head), 0, ''),
  153. \ '^ref: refs/remotes/origin/\zs.*')
  154. endif
  155. " The command may not return the name of a branch in detached HEAD state
  156. let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir))
  157. return v:shell_error ? '' : result[-1]
  158. endfunction
  159. if s:is_win
  160. function! s:plug_call(fn, ...)
  161. let shellslash = &shellslash
  162. try
  163. set noshellslash
  164. return call(a:fn, a:000)
  165. finally
  166. let &shellslash = shellslash
  167. endtry
  168. endfunction
  169. else
  170. function! s:plug_call(fn, ...)
  171. return call(a:fn, a:000)
  172. endfunction
  173. endif
  174. function! s:plug_getcwd()
  175. return s:plug_call('getcwd')
  176. endfunction
  177. function! s:plug_fnamemodify(fname, mods)
  178. return s:plug_call('fnamemodify', a:fname, a:mods)
  179. endfunction
  180. function! s:plug_expand(fmt)
  181. return s:plug_call('expand', a:fmt, 1)
  182. endfunction
  183. function! s:plug_tempname()
  184. return s:plug_call('tempname')
  185. endfunction
  186. function! plug#begin(...)
  187. if a:0 > 0
  188. let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p'))
  189. elseif exists('g:plug_home')
  190. let home = s:path(g:plug_home)
  191. elseif has('nvim')
  192. let home = stdpath('data') . '/plugged'
  193. elseif !empty(&rtp)
  194. let home = s:path(split(&rtp, ',')[0]) . '/plugged'
  195. else
  196. return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
  197. endif
  198. if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp
  199. return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.')
  200. endif
  201. let g:plug_home = home
  202. let g:plugs = {}
  203. let g:plugs_order = []
  204. let s:triggers = {}
  205. call s:define_commands()
  206. return 1
  207. endfunction
  208. function! s:define_commands()
  209. command! -nargs=+ -bar Plug call plug#(<args>)
  210. if !executable('git')
  211. return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
  212. endif
  213. if has('win32')
  214. \ && &shellslash
  215. \ && (&shell =~# 'cmd\(\.exe\)\?$' || s:is_powershell(&shell))
  216. return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.')
  217. endif
  218. if !has('nvim')
  219. \ && (has('win32') || has('win32unix'))
  220. \ && !has('multi_byte')
  221. return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.')
  222. endif
  223. command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>])
  224. command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(<bang>0, [<f-args>])
  225. command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0)
  226. command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
  227. command! -nargs=0 -bar PlugStatus call s:status()
  228. command! -nargs=0 -bar PlugDiff call s:diff()
  229. command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>)
  230. endfunction
  231. function! s:to_a(v)
  232. return type(a:v) == s:TYPE.list ? a:v : [a:v]
  233. endfunction
  234. function! s:to_s(v)
  235. return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
  236. endfunction
  237. function! s:glob(from, pattern)
  238. return s:lines(globpath(a:from, a:pattern))
  239. endfunction
  240. function! s:source(from, ...)
  241. let found = 0
  242. for pattern in a:000
  243. for vim in s:glob(a:from, pattern)
  244. execute 'source' s:esc(vim)
  245. let found = 1
  246. endfor
  247. endfor
  248. return found
  249. endfunction
  250. function! s:assoc(dict, key, val)
  251. let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
  252. endfunction
  253. function! s:ask(message, ...)
  254. call inputsave()
  255. echohl WarningMsg
  256. let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
  257. echohl None
  258. call inputrestore()
  259. echo "\r"
  260. return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
  261. endfunction
  262. function! s:ask_no_interrupt(...)
  263. try
  264. return call('s:ask', a:000)
  265. catch
  266. return 0
  267. endtry
  268. endfunction
  269. function! s:lazy(plug, opt)
  270. return has_key(a:plug, a:opt) &&
  271. \ (empty(s:to_a(a:plug[a:opt])) ||
  272. \ !isdirectory(a:plug.dir) ||
  273. \ len(s:glob(s:rtp(a:plug), 'plugin')) ||
  274. \ len(s:glob(s:rtp(a:plug), 'after/plugin')))
  275. endfunction
  276. function! plug#end()
  277. if !exists('g:plugs')
  278. return s:err('plug#end() called without calling plug#begin() first')
  279. endif
  280. if exists('#PlugLOD')
  281. augroup PlugLOD
  282. autocmd!
  283. augroup END
  284. augroup! PlugLOD
  285. endif
  286. let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
  287. if get(g:, 'did_load_filetypes', 0)
  288. filetype off
  289. endif
  290. for name in g:plugs_order
  291. if !has_key(g:plugs, name)
  292. continue
  293. endif
  294. let plug = g:plugs[name]
  295. if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for')
  296. let s:loaded[name] = 1
  297. continue
  298. endif
  299. if has_key(plug, 'on')
  300. let s:triggers[name] = { 'map': [], 'cmd': [] }
  301. for cmd in s:to_a(plug.on)
  302. if cmd =~? '^<Plug>.\+'
  303. if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
  304. call s:assoc(lod.map, cmd, name)
  305. endif
  306. call add(s:triggers[name].map, cmd)
  307. elseif cmd =~# '^[A-Z]'
  308. let cmd = substitute(cmd, '!*$', '', '')
  309. if exists(':'.cmd) != 2
  310. call s:assoc(lod.cmd, cmd, name)
  311. endif
  312. call add(s:triggers[name].cmd, cmd)
  313. else
  314. call s:err('Invalid `on` option: '.cmd.
  315. \ '. Should start with an uppercase letter or `<Plug>`.')
  316. endif
  317. endfor
  318. endif
  319. if has_key(plug, 'for')
  320. let types = s:to_a(plug.for)
  321. if !empty(types)
  322. augroup filetypedetect
  323. call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
  324. if has('nvim-0.5.0')
  325. call s:source(s:rtp(plug), 'ftdetect/**/*.lua', 'after/ftdetect/**/*.lua')
  326. endif
  327. augroup END
  328. endif
  329. for type in types
  330. call s:assoc(lod.ft, type, name)
  331. endfor
  332. endif
  333. endfor
  334. for [cmd, names] in items(lod.cmd)
  335. execute printf(
  336. \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
  337. \ cmd, string(cmd), string(names))
  338. endfor
  339. for [map, names] in items(lod.map)
  340. for [mode, map_prefix, key_prefix] in
  341. \ [['i', '<C-\><C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
  342. execute printf(
  343. \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
  344. \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
  345. endfor
  346. endfor
  347. for [ft, names] in items(lod.ft)
  348. augroup PlugLOD
  349. execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
  350. \ ft, string(ft), string(names))
  351. augroup END
  352. endfor
  353. call s:reorg_rtp()
  354. filetype plugin indent on
  355. if has('vim_starting')
  356. if has('syntax') && !exists('g:syntax_on')
  357. syntax enable
  358. end
  359. else
  360. call s:reload_plugins()
  361. endif
  362. endfunction
  363. function! s:loaded_names()
  364. return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
  365. endfunction
  366. function! s:load_plugin(spec)
  367. call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
  368. if has('nvim-0.5.0')
  369. call s:source(s:rtp(a:spec), 'plugin/**/*.lua', 'after/plugin/**/*.lua')
  370. endif
  371. endfunction
  372. function! s:reload_plugins()
  373. for name in s:loaded_names()
  374. call s:load_plugin(g:plugs[name])
  375. endfor
  376. endfunction
  377. function! s:trim(str)
  378. return substitute(a:str, '[\/]\+$', '', '')
  379. endfunction
  380. function! s:version_requirement(val, min)
  381. for idx in range(0, len(a:min) - 1)
  382. let v = get(a:val, idx, 0)
  383. if v < a:min[idx] | return 0
  384. elseif v > a:min[idx] | return 1
  385. endif
  386. endfor
  387. return 1
  388. endfunction
  389. function! s:git_version_requirement(...)
  390. if !exists('s:git_version')
  391. let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)')
  392. endif
  393. return s:version_requirement(s:git_version, a:000)
  394. endfunction
  395. function! s:progress_opt(base)
  396. return a:base && !s:is_win &&
  397. \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
  398. endfunction
  399. function! s:rtp(spec)
  400. return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
  401. endfunction
  402. if s:is_win
  403. function! s:path(path)
  404. return s:trim(substitute(a:path, '/', '\', 'g'))
  405. endfunction
  406. function! s:dirpath(path)
  407. return s:path(a:path) . '\'
  408. endfunction
  409. function! s:is_local_plug(repo)
  410. return a:repo =~? '^[a-z]:\|^[%~]'
  411. endfunction
  412. " Copied from fzf
  413. function! s:wrap_cmds(cmds)
  414. let cmds = [
  415. \ '@echo off',
  416. \ 'setlocal enabledelayedexpansion']
  417. \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
  418. \ + ['endlocal']
  419. if has('iconv')
  420. if !exists('s:codepage')
  421. let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
  422. endif
  423. return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage))
  424. endif
  425. return map(cmds, 'v:val."\r"')
  426. endfunction
  427. function! s:batchfile(cmd)
  428. let batchfile = s:plug_tempname().'.bat'
  429. call writefile(s:wrap_cmds(a:cmd), batchfile)
  430. let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0})
  431. if s:is_powershell(&shell)
  432. let cmd = '& ' . cmd
  433. endif
  434. return [batchfile, cmd]
  435. endfunction
  436. else
  437. function! s:path(path)
  438. return s:trim(a:path)
  439. endfunction
  440. function! s:dirpath(path)
  441. return substitute(a:path, '[/\\]*$', '/', '')
  442. endfunction
  443. function! s:is_local_plug(repo)
  444. return a:repo[0] =~ '[/$~]'
  445. endfunction
  446. endif
  447. function! s:err(msg)
  448. echohl ErrorMsg
  449. echom '[vim-plug] '.a:msg
  450. echohl None
  451. endfunction
  452. function! s:warn(cmd, msg)
  453. echohl WarningMsg
  454. execute a:cmd 'a:msg'
  455. echohl None
  456. endfunction
  457. function! s:esc(path)
  458. return escape(a:path, ' ')
  459. endfunction
  460. function! s:escrtp(path)
  461. return escape(a:path, ' ,')
  462. endfunction
  463. function! s:remove_rtp()
  464. for name in s:loaded_names()
  465. let rtp = s:rtp(g:plugs[name])
  466. execute 'set rtp-='.s:escrtp(rtp)
  467. let after = globpath(rtp, 'after')
  468. if isdirectory(after)
  469. execute 'set rtp-='.s:escrtp(after)
  470. endif
  471. endfor
  472. endfunction
  473. function! s:reorg_rtp()
  474. if !empty(s:first_rtp)
  475. execute 'set rtp-='.s:first_rtp
  476. execute 'set rtp-='.s:last_rtp
  477. endif
  478. " &rtp is modified from outside
  479. if exists('s:prtp') && s:prtp !=# &rtp
  480. call s:remove_rtp()
  481. unlet! s:middle
  482. endif
  483. let s:middle = get(s:, 'middle', &rtp)
  484. let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
  485. let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
  486. let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
  487. \ . ','.s:middle.','
  488. \ . join(map(afters, 'escape(v:val, ",")'), ',')
  489. let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
  490. let s:prtp = &rtp
  491. if !empty(s:first_rtp)
  492. execute 'set rtp^='.s:first_rtp
  493. execute 'set rtp+='.s:last_rtp
  494. endif
  495. endfunction
  496. function! s:doautocmd(...)
  497. if exists('#'.join(a:000, '#'))
  498. execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
  499. endif
  500. endfunction
  501. function! s:dobufread(names)
  502. for name in a:names
  503. let path = s:rtp(g:plugs[name])
  504. for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin']
  505. if len(finddir(dir, path))
  506. if exists('#BufRead')
  507. doautocmd BufRead
  508. endif
  509. return
  510. endif
  511. endfor
  512. endfor
  513. endfunction
  514. function! plug#load(...)
  515. if a:0 == 0
  516. return s:err('Argument missing: plugin name(s) required')
  517. endif
  518. if !exists('g:plugs')
  519. return s:err('plug#begin was not called')
  520. endif
  521. let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
  522. let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
  523. if !empty(unknowns)
  524. let s = len(unknowns) > 1 ? 's' : ''
  525. return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
  526. end
  527. let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
  528. if !empty(unloaded)
  529. for name in unloaded
  530. call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
  531. endfor
  532. call s:dobufread(unloaded)
  533. return 1
  534. end
  535. return 0
  536. endfunction
  537. function! s:remove_triggers(name)
  538. if !has_key(s:triggers, a:name)
  539. return
  540. endif
  541. for cmd in s:triggers[a:name].cmd
  542. execute 'silent! delc' cmd
  543. endfor
  544. for map in s:triggers[a:name].map
  545. execute 'silent! unmap' map
  546. execute 'silent! iunmap' map
  547. endfor
  548. call remove(s:triggers, a:name)
  549. endfunction
  550. function! s:lod(names, types, ...)
  551. for name in a:names
  552. call s:remove_triggers(name)
  553. let s:loaded[name] = 1
  554. endfor
  555. call s:reorg_rtp()
  556. for name in a:names
  557. let rtp = s:rtp(g:plugs[name])
  558. for dir in a:types
  559. call s:source(rtp, dir.'/**/*.vim')
  560. if has('nvim-0.5.0') " see neovim#14686
  561. call s:source(rtp, dir.'/**/*.lua')
  562. endif
  563. endfor
  564. if a:0
  565. if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
  566. execute 'runtime' a:1
  567. endif
  568. call s:source(rtp, a:2)
  569. endif
  570. call s:doautocmd('User', name)
  571. endfor
  572. endfunction
  573. function! s:lod_ft(pat, names)
  574. let syn = 'syntax/'.a:pat.'.vim'
  575. call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
  576. execute 'autocmd! PlugLOD FileType' a:pat
  577. call s:doautocmd('filetypeplugin', 'FileType')
  578. call s:doautocmd('filetypeindent', 'FileType')
  579. endfunction
  580. function! s:lod_cmd(cmd, bang, l1, l2, args, names)
  581. call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
  582. call s:dobufread(a:names)
  583. execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
  584. endfunction
  585. function! s:lod_map(map, names, with_prefix, prefix)
  586. call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
  587. call s:dobufread(a:names)
  588. let extra = ''
  589. while 1
  590. let c = getchar(0)
  591. if c == 0
  592. break
  593. endif
  594. let extra .= nr2char(c)
  595. endwhile
  596. if a:with_prefix
  597. let prefix = v:count ? v:count : ''
  598. let prefix .= '"'.v:register.a:prefix
  599. if mode(1) == 'no'
  600. if v:operator == 'c'
  601. let prefix = "\<esc>" . prefix
  602. endif
  603. let prefix .= v:operator
  604. endif
  605. call feedkeys(prefix, 'n')
  606. endif
  607. call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
  608. endfunction
  609. function! plug#(repo, ...)
  610. if a:0 > 1
  611. return s:err('Invalid number of arguments (1..2)')
  612. endif
  613. try
  614. let repo = s:trim(a:repo)
  615. let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
  616. let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??'))
  617. let spec = extend(s:infer_properties(name, repo), opts)
  618. if !has_key(g:plugs, name)
  619. call add(g:plugs_order, name)
  620. endif
  621. let g:plugs[name] = spec
  622. let s:loaded[name] = get(s:loaded, name, 0)
  623. catch
  624. return s:err(repo . ' ' . v:exception)
  625. endtry
  626. endfunction
  627. function! s:parse_options(arg)
  628. let opts = copy(s:base_spec)
  629. let type = type(a:arg)
  630. let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)'
  631. if type == s:TYPE.string
  632. if empty(a:arg)
  633. throw printf(opt_errfmt, 'tag', 'string')
  634. endif
  635. let opts.tag = a:arg
  636. elseif type == s:TYPE.dict
  637. for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as']
  638. if has_key(a:arg, opt)
  639. \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
  640. throw printf(opt_errfmt, opt, 'string')
  641. endif
  642. endfor
  643. for opt in ['on', 'for']
  644. if has_key(a:arg, opt)
  645. \ && type(a:arg[opt]) != s:TYPE.list
  646. \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
  647. throw printf(opt_errfmt, opt, 'string or list')
  648. endif
  649. endfor
  650. if has_key(a:arg, 'do')
  651. \ && type(a:arg.do) != s:TYPE.funcref
  652. \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do))
  653. throw printf(opt_errfmt, 'do', 'string or funcref')
  654. endif
  655. call extend(opts, a:arg)
  656. if has_key(opts, 'dir')
  657. let opts.dir = s:dirpath(s:plug_expand(opts.dir))
  658. endif
  659. else
  660. throw 'Invalid argument type (expected: string or dictionary)'
  661. endif
  662. return opts
  663. endfunction
  664. function! s:infer_properties(name, repo)
  665. let repo = a:repo
  666. if s:is_local_plug(repo)
  667. return { 'dir': s:dirpath(s:plug_expand(repo)) }
  668. else
  669. if repo =~ ':'
  670. let uri = repo
  671. else
  672. if repo !~ '/'
  673. throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
  674. endif
  675. let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
  676. let uri = printf(fmt, repo)
  677. endif
  678. return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
  679. endif
  680. endfunction
  681. function! s:install(force, names)
  682. call s:update_impl(0, a:force, a:names)
  683. endfunction
  684. function! s:update(force, names)
  685. call s:update_impl(1, a:force, a:names)
  686. endfunction
  687. function! plug#helptags()
  688. if !exists('g:plugs')
  689. return s:err('plug#begin was not called')
  690. endif
  691. for spec in values(g:plugs)
  692. let docd = join([s:rtp(spec), 'doc'], '/')
  693. if isdirectory(docd)
  694. silent! execute 'helptags' s:esc(docd)
  695. endif
  696. endfor
  697. return 1
  698. endfunction
  699. function! s:syntax()
  700. syntax clear
  701. syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
  702. syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
  703. syn match plugNumber /[0-9]\+[0-9.]*/ contained
  704. syn match plugBracket /[[\]]/ contained
  705. syn match plugX /x/ contained
  706. syn match plugDash /^-\{1}\ /
  707. syn match plugPlus /^+/
  708. syn match plugStar /^*/
  709. syn match plugMessage /\(^- \)\@<=.*/
  710. syn match plugName /\(^- \)\@<=[^ ]*:/
  711. syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
  712. syn match plugTag /(tag: [^)]\+)/
  713. syn match plugInstall /\(^+ \)\@<=[^:]*/
  714. syn match plugUpdate /\(^* \)\@<=[^:]*/
  715. syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
  716. syn match plugEdge /^ \X\+$/
  717. syn match plugEdge /^ \X*/ contained nextgroup=plugSha
  718. syn match plugSha /[0-9a-f]\{7,9}/ contained
  719. syn match plugRelDate /([^)]*)$/ contained
  720. syn match plugNotLoaded /(not loaded)$/
  721. syn match plugError /^x.*/
  722. syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
  723. syn match plugH2 /^.*:\n-\+$/
  724. syn match plugH2 /^-\{2,}/
  725. syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
  726. hi def link plug1 Title
  727. hi def link plug2 Repeat
  728. hi def link plugH2 Type
  729. hi def link plugX Exception
  730. hi def link plugBracket Structure
  731. hi def link plugNumber Number
  732. hi def link plugDash Special
  733. hi def link plugPlus Constant
  734. hi def link plugStar Boolean
  735. hi def link plugMessage Function
  736. hi def link plugName Label
  737. hi def link plugInstall Function
  738. hi def link plugUpdate Type
  739. hi def link plugError Error
  740. hi def link plugDeleted Ignore
  741. hi def link plugRelDate Comment
  742. hi def link plugEdge PreProc
  743. hi def link plugSha Identifier
  744. hi def link plugTag Constant
  745. hi def link plugNotLoaded Comment
  746. endfunction
  747. function! s:lpad(str, len)
  748. return a:str . repeat(' ', a:len - len(a:str))
  749. endfunction
  750. function! s:lines(msg)
  751. return split(a:msg, "[\r\n]")
  752. endfunction
  753. function! s:lastline(msg)
  754. return get(s:lines(a:msg), -1, '')
  755. endfunction
  756. function! s:new_window()
  757. execute get(g:, 'plug_window', '-tabnew')
  758. endfunction
  759. function! s:plug_window_exists()
  760. let buflist = tabpagebuflist(s:plug_tab)
  761. return !empty(buflist) && index(buflist, s:plug_buf) >= 0
  762. endfunction
  763. function! s:switch_in()
  764. if !s:plug_window_exists()
  765. return 0
  766. endif
  767. if winbufnr(0) != s:plug_buf
  768. let s:pos = [tabpagenr(), winnr(), winsaveview()]
  769. execute 'normal!' s:plug_tab.'gt'
  770. let winnr = bufwinnr(s:plug_buf)
  771. execute winnr.'wincmd w'
  772. call add(s:pos, winsaveview())
  773. else
  774. let s:pos = [winsaveview()]
  775. endif
  776. setlocal modifiable
  777. return 1
  778. endfunction
  779. function! s:switch_out(...)
  780. call winrestview(s:pos[-1])
  781. setlocal nomodifiable
  782. if a:0 > 0
  783. execute a:1
  784. endif
  785. if len(s:pos) > 1
  786. execute 'normal!' s:pos[0].'gt'
  787. execute s:pos[1] 'wincmd w'
  788. call winrestview(s:pos[2])
  789. endif
  790. endfunction
  791. function! s:finish_bindings()
  792. nnoremap <silent> <buffer> R :call <SID>retry()<cr>
  793. nnoremap <silent> <buffer> D :PlugDiff<cr>
  794. nnoremap <silent> <buffer> S :PlugStatus<cr>
  795. nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
  796. xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
  797. nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
  798. nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
  799. endfunction
  800. function! s:prepare(...)
  801. if empty(s:plug_getcwd())
  802. throw 'Invalid current working directory. Cannot proceed.'
  803. endif
  804. for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
  805. if exists(evar)
  806. throw evar.' detected. Cannot proceed.'
  807. endif
  808. endfor
  809. call s:job_abort()
  810. if s:switch_in()
  811. if b:plug_preview == 1
  812. pc
  813. endif
  814. enew
  815. else
  816. call s:new_window()
  817. endif
  818. nnoremap <silent> <buffer> q :call <SID>close_pane()<cr>
  819. if a:0 == 0
  820. call s:finish_bindings()
  821. endif
  822. let b:plug_preview = -1
  823. let s:plug_tab = tabpagenr()
  824. let s:plug_buf = winbufnr(0)
  825. call s:assign_name()
  826. for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
  827. execute 'silent! unmap <buffer>' k
  828. endfor
  829. setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
  830. if exists('+colorcolumn')
  831. setlocal colorcolumn=
  832. endif
  833. setf vim-plug
  834. if exists('g:syntax_on')
  835. call s:syntax()
  836. endif
  837. endfunction
  838. function! s:close_pane()
  839. if b:plug_preview == 1
  840. pc
  841. let b:plug_preview = -1
  842. else
  843. bd
  844. endif
  845. endfunction
  846. function! s:assign_name()
  847. " Assign buffer name
  848. let prefix = '[Plugins]'
  849. let name = prefix
  850. let idx = 2
  851. while bufexists(name)
  852. let name = printf('%s (%s)', prefix, idx)
  853. let idx = idx + 1
  854. endwhile
  855. silent! execute 'f' fnameescape(name)
  856. endfunction
  857. function! s:chsh(swap)
  858. let prev = [&shell, &shellcmdflag, &shellredir]
  859. if !s:is_win
  860. set shell=sh
  861. endif
  862. if a:swap
  863. if s:is_powershell(&shell)
  864. let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
  865. elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$'
  866. set shellredir=>%s\ 2>&1
  867. endif
  868. endif
  869. return prev
  870. endfunction
  871. function! s:bang(cmd, ...)
  872. let batchfile = ''
  873. try
  874. let [sh, shellcmdflag, shrd] = s:chsh(a:0)
  875. " FIXME: Escaping is incomplete. We could use shellescape with eval,
  876. " but it won't work on Windows.
  877. let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
  878. if s:is_win
  879. let [batchfile, cmd] = s:batchfile(cmd)
  880. endif
  881. let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
  882. execute "normal! :execute g:_plug_bang\<cr>\<cr>"
  883. finally
  884. unlet g:_plug_bang
  885. let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
  886. if s:is_win && filereadable(batchfile)
  887. call delete(batchfile)
  888. endif
  889. endtry
  890. return v:shell_error ? 'Exit status: ' . v:shell_error : ''
  891. endfunction
  892. function! s:regress_bar()
  893. let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
  894. call s:progress_bar(2, bar, len(bar))
  895. endfunction
  896. function! s:is_updated(dir)
  897. return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
  898. endfunction
  899. function! s:do(pull, force, todo)
  900. if has('nvim')
  901. " Reset &rtp to invalidate Neovim cache of loaded Lua modules
  902. " See https://github.com/junegunn/vim-plug/pull/1157#issuecomment-1809226110
  903. let &rtp = &rtp
  904. endif
  905. for [name, spec] in items(a:todo)
  906. if !isdirectory(spec.dir)
  907. continue
  908. endif
  909. let installed = has_key(s:update.new, name)
  910. let updated = installed ? 0 :
  911. \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
  912. if a:force || installed || updated
  913. execute 'cd' s:esc(spec.dir)
  914. call append(3, '- Post-update hook for '. name .' ... ')
  915. let error = ''
  916. let type = type(spec.do)
  917. if type == s:TYPE.string
  918. if spec.do[0] == ':'
  919. if !get(s:loaded, name, 0)
  920. let s:loaded[name] = 1
  921. call s:reorg_rtp()
  922. endif
  923. call s:load_plugin(spec)
  924. try
  925. execute spec.do[1:]
  926. catch
  927. let error = v:exception
  928. endtry
  929. if !s:plug_window_exists()
  930. cd -
  931. throw 'Warning: vim-plug was terminated by the post-update hook of '.name
  932. endif
  933. else
  934. let error = s:bang(spec.do)
  935. endif
  936. elseif type == s:TYPE.funcref
  937. try
  938. call s:load_plugin(spec)
  939. let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
  940. call spec.do({ 'name': name, 'status': status, 'force': a:force })
  941. catch
  942. let error = v:exception
  943. endtry
  944. else
  945. let error = 'Invalid hook type'
  946. endif
  947. call s:switch_in()
  948. call setline(4, empty(error) ? (getline(4) . 'OK')
  949. \ : ('x' . getline(4)[1:] . error))
  950. if !empty(error)
  951. call add(s:update.errors, name)
  952. call s:regress_bar()
  953. endif
  954. cd -
  955. endif
  956. endfor
  957. endfunction
  958. function! s:hash_match(a, b)
  959. return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
  960. endfunction
  961. function! s:checkout(spec)
  962. let sha = a:spec.commit
  963. let output = s:git_revision(a:spec.dir)
  964. let error = 0
  965. if !empty(output) && !s:hash_match(sha, s:lines(output)[0])
  966. let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : ''
  967. let output = s:system(
  968. \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
  969. let error = v:shell_error
  970. endif
  971. return [output, error]
  972. endfunction
  973. function! s:finish(pull)
  974. let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
  975. if new_frozen
  976. let s = new_frozen > 1 ? 's' : ''
  977. call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
  978. endif
  979. call append(3, '- Finishing ... ') | 4
  980. redraw
  981. call plug#helptags()
  982. call plug#end()
  983. call setline(4, getline(4) . 'Done!')
  984. redraw
  985. let msgs = []
  986. if !empty(s:update.errors)
  987. call add(msgs, "Press 'R' to retry.")
  988. endif
  989. if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
  990. \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
  991. call add(msgs, "Press 'D' to see the updated changes.")
  992. endif
  993. echo join(msgs, ' ')
  994. call s:finish_bindings()
  995. endfunction
  996. function! s:retry()
  997. if empty(s:update.errors)
  998. return
  999. endif
  1000. echo
  1001. call s:update_impl(s:update.pull, s:update.force,
  1002. \ extend(copy(s:update.errors), [s:update.threads]))
  1003. endfunction
  1004. function! s:is_managed(name)
  1005. return has_key(g:plugs[a:name], 'uri')
  1006. endfunction
  1007. function! s:names(...)
  1008. return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
  1009. endfunction
  1010. function! s:check_ruby()
  1011. silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
  1012. if !exists('g:plug_ruby')
  1013. redraw!
  1014. return s:warn('echom', 'Warning: Ruby interface is broken')
  1015. endif
  1016. let ruby_version = split(g:plug_ruby, '\.')
  1017. unlet g:plug_ruby
  1018. return s:version_requirement(ruby_version, [1, 8, 7])
  1019. endfunction
  1020. function! s:update_impl(pull, force, args) abort
  1021. let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
  1022. let args = filter(copy(a:args), 'v:val != "--sync"')
  1023. let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
  1024. \ remove(args, -1) : get(g:, 'plug_threads', 16)
  1025. let managed = filter(deepcopy(g:plugs), 's:is_managed(v:key)')
  1026. let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
  1027. \ filter(managed, 'index(args, v:key) >= 0')
  1028. if empty(todo)
  1029. return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
  1030. endif
  1031. if !s:is_win && s:git_version_requirement(2, 3)
  1032. let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
  1033. let $GIT_TERMINAL_PROMPT = 0
  1034. for plug in values(todo)
  1035. let plug.uri = substitute(plug.uri,
  1036. \ '^https://git::@github\.com', 'https://github.com', '')
  1037. endfor
  1038. endif
  1039. if !isdirectory(g:plug_home)
  1040. try
  1041. call mkdir(g:plug_home, 'p')
  1042. catch
  1043. return s:err(printf('Invalid plug directory: %s. '.
  1044. \ 'Try to call plug#begin with a valid directory', g:plug_home))
  1045. endtry
  1046. endif
  1047. if has('nvim') && !exists('*jobwait') && threads > 1
  1048. call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
  1049. endif
  1050. let use_job = s:nvim || s:vim8
  1051. let python = (has('python') || has('python3')) && !use_job
  1052. let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby()
  1053. let s:update = {
  1054. \ 'start': reltime(),
  1055. \ 'all': todo,
  1056. \ 'todo': copy(todo),
  1057. \ 'errors': [],
  1058. \ 'pull': a:pull,
  1059. \ 'force': a:force,
  1060. \ 'new': {},
  1061. \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
  1062. \ 'bar': '',
  1063. \ 'fin': 0
  1064. \ }
  1065. call s:prepare(1)
  1066. call append(0, ['', ''])
  1067. normal! 2G
  1068. silent! redraw
  1069. " Set remote name, overriding a possible user git config's clone.defaultRemoteName
  1070. let s:clone_opt = ['--origin', 'origin']
  1071. if get(g:, 'plug_shallow', 1)
  1072. call extend(s:clone_opt, ['--depth', '1'])
  1073. if s:git_version_requirement(1, 7, 10)
  1074. call add(s:clone_opt, '--no-single-branch')
  1075. endif
  1076. endif
  1077. if has('win32unix') || has('wsl')
  1078. call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
  1079. endif
  1080. let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
  1081. " Python version requirement (>= 2.7)
  1082. if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
  1083. redir => pyv
  1084. silent python import platform; print platform.python_version()
  1085. redir END
  1086. let python = s:version_requirement(
  1087. \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
  1088. endif
  1089. if (python || ruby) && s:update.threads > 1
  1090. try
  1091. let imd = &imd
  1092. if s:mac_gui
  1093. set noimd
  1094. endif
  1095. if ruby
  1096. call s:update_ruby()
  1097. else
  1098. call s:update_python()
  1099. endif
  1100. catch
  1101. let lines = getline(4, '$')
  1102. let printed = {}
  1103. silent! 4,$d _
  1104. for line in lines
  1105. let name = s:extract_name(line, '.', '')
  1106. if empty(name) || !has_key(printed, name)
  1107. call append('$', line)
  1108. if !empty(name)
  1109. let printed[name] = 1
  1110. if line[0] == 'x' && index(s:update.errors, name) < 0
  1111. call add(s:update.errors, name)
  1112. end
  1113. endif
  1114. endif
  1115. endfor
  1116. finally
  1117. let &imd = imd
  1118. call s:update_finish()
  1119. endtry
  1120. else
  1121. call s:update_vim()
  1122. while use_job && sync
  1123. sleep 100m
  1124. if s:update.fin
  1125. break
  1126. endif
  1127. endwhile
  1128. endif
  1129. endfunction
  1130. function! s:log4(name, msg)
  1131. call setline(4, printf('- %s (%s)', a:msg, a:name))
  1132. redraw
  1133. endfunction
  1134. function! s:update_finish()
  1135. if exists('s:git_terminal_prompt')
  1136. let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
  1137. endif
  1138. if s:switch_in()
  1139. call append(3, '- Updating ...') | 4
  1140. for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))'))
  1141. let [pos, _] = s:logpos(name)
  1142. if !pos
  1143. continue
  1144. endif
  1145. let out = ''
  1146. let error = 0
  1147. if has_key(spec, 'commit')
  1148. call s:log4(name, 'Checking out '.spec.commit)
  1149. let [out, error] = s:checkout(spec)
  1150. elseif has_key(spec, 'tag')
  1151. let tag = spec.tag
  1152. if tag =~ '\*'
  1153. let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
  1154. if !v:shell_error && !empty(tags)
  1155. let tag = tags[0]
  1156. call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
  1157. call append(3, '')
  1158. endif
  1159. endif
  1160. call s:log4(name, 'Checking out '.tag)
  1161. let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
  1162. let error = v:shell_error
  1163. endif
  1164. if !error && filereadable(spec.dir.'/.gitmodules') &&
  1165. \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
  1166. call s:log4(name, 'Updating submodules. This may take a while.')
  1167. let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
  1168. let error = v:shell_error
  1169. endif
  1170. let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
  1171. if error
  1172. call add(s:update.errors, name)
  1173. call s:regress_bar()
  1174. silent execute pos 'd _'
  1175. call append(4, msg) | 4
  1176. elseif !empty(out)
  1177. call setline(pos, msg[0])
  1178. endif
  1179. redraw
  1180. endfor
  1181. silent 4 d _
  1182. try
  1183. call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")'))
  1184. catch
  1185. call s:warn('echom', v:exception)
  1186. call s:warn('echo', '')
  1187. return
  1188. endtry
  1189. call s:finish(s:update.pull)
  1190. call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
  1191. call s:switch_out('normal! gg')
  1192. endif
  1193. endfunction
  1194. function! s:job_abort()
  1195. if (!s:nvim && !s:vim8) || !exists('s:jobs')
  1196. return
  1197. endif
  1198. for [name, j] in items(s:jobs)
  1199. if s:nvim
  1200. silent! call jobstop(j.jobid)
  1201. elseif s:vim8
  1202. silent! call job_stop(j.jobid)
  1203. endif
  1204. if j.new
  1205. call s:rm_rf(g:plugs[name].dir)
  1206. endif
  1207. endfor
  1208. let s:jobs = {}
  1209. endfunction
  1210. function! s:last_non_empty_line(lines)
  1211. let len = len(a:lines)
  1212. for idx in range(len)
  1213. let line = a:lines[len-idx-1]
  1214. if !empty(line)
  1215. return line
  1216. endif
  1217. endfor
  1218. return ''
  1219. endfunction
  1220. function! s:job_out_cb(self, data) abort
  1221. let self = a:self
  1222. let data = remove(self.lines, -1) . a:data
  1223. let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
  1224. call extend(self.lines, lines)
  1225. " To reduce the number of buffer updates
  1226. let self.tick = get(self, 'tick', -1) + 1
  1227. if !self.running || self.tick % len(s:jobs) == 0
  1228. let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
  1229. let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
  1230. if len(result)
  1231. call s:log(bullet, self.name, result)
  1232. endif
  1233. endif
  1234. endfunction
  1235. function! s:job_exit_cb(self, data) abort
  1236. let a:self.running = 0
  1237. let a:self.error = a:data != 0
  1238. call s:reap(a:self.name)
  1239. call s:tick()
  1240. endfunction
  1241. function! s:job_cb(fn, job, ch, data)
  1242. if !s:plug_window_exists() " plug window closed
  1243. return s:job_abort()
  1244. endif
  1245. call call(a:fn, [a:job, a:data])
  1246. endfunction
  1247. function! s:nvim_cb(job_id, data, event) dict abort
  1248. return (a:event == 'stdout' || a:event == 'stderr') ?
  1249. \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
  1250. \ s:job_cb('s:job_exit_cb', self, 0, a:data)
  1251. endfunction
  1252. function! s:spawn(name, spec, queue, opts)
  1253. let job = { 'name': a:name, 'spec': a:spec, 'running': 1, 'error': 0, 'lines': [''],
  1254. \ 'new': get(a:opts, 'new', 0), 'queue': copy(a:queue) }
  1255. let Item = remove(job.queue, 0)
  1256. let argv = type(Item) == s:TYPE.funcref ? call(Item, [a:spec]) : Item
  1257. let s:jobs[a:name] = job
  1258. if s:nvim
  1259. if has_key(a:opts, 'dir')
  1260. let job.cwd = a:opts.dir
  1261. endif
  1262. call extend(job, {
  1263. \ 'on_stdout': function('s:nvim_cb'),
  1264. \ 'on_stderr': function('s:nvim_cb'),
  1265. \ 'on_exit': function('s:nvim_cb'),
  1266. \ })
  1267. let jid = s:plug_call('jobstart', argv, job)
  1268. if jid > 0
  1269. let job.jobid = jid
  1270. else
  1271. let job.running = 0
  1272. let job.error = 1
  1273. let job.lines = [jid < 0 ? argv[0].' is not executable' :
  1274. \ 'Invalid arguments (or job table is full)']
  1275. endif
  1276. elseif s:vim8
  1277. let cmd = join(map(copy(argv), 'plug#shellescape(v:val, {"script": 0})'))
  1278. if has_key(a:opts, 'dir')
  1279. let cmd = s:with_cd(cmd, a:opts.dir, 0)
  1280. endif
  1281. let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
  1282. let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
  1283. \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
  1284. \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
  1285. \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
  1286. \ 'err_mode': 'raw',
  1287. \ 'out_mode': 'raw'
  1288. \})
  1289. if job_status(jid) == 'run'
  1290. let job.jobid = jid
  1291. else
  1292. let job.running = 0
  1293. let job.error = 1
  1294. let job.lines = ['Failed to start job']
  1295. endif
  1296. else
  1297. let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [argv, a:opts.dir] : [argv]))
  1298. let job.error = v:shell_error != 0
  1299. let job.running = 0
  1300. endif
  1301. endfunction
  1302. function! s:reap(name)
  1303. let job = remove(s:jobs, a:name)
  1304. if job.error
  1305. call add(s:update.errors, a:name)
  1306. elseif get(job, 'new', 0)
  1307. let s:update.new[a:name] = 1
  1308. endif
  1309. let more = len(get(job, 'queue', []))
  1310. let bullet = job.error ? 'x' : more ? (job.new ? '+' : '*') : '-'
  1311. let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
  1312. if len(result)
  1313. call s:log(bullet, a:name, result)
  1314. endif
  1315. if !job.error && more
  1316. let job.spec.queue = job.queue
  1317. let s:update.todo[a:name] = job.spec
  1318. else
  1319. let s:update.bar .= job.error ? 'x' : '='
  1320. call s:bar()
  1321. endif
  1322. endfunction
  1323. function! s:bar()
  1324. if s:switch_in()
  1325. let total = len(s:update.all)
  1326. call setline(1, (s:update.pull ? 'Updating' : 'Installing').
  1327. \ ' plugins ('.len(s:update.bar).'/'.total.')')
  1328. call s:progress_bar(2, s:update.bar, total)
  1329. call s:switch_out()
  1330. endif
  1331. endfunction
  1332. function! s:logpos(name)
  1333. let max = line('$')
  1334. for i in range(4, max > 4 ? max : 4)
  1335. if getline(i) =~# '^[-+x*] '.a:name.':'
  1336. for j in range(i + 1, max > 5 ? max : 5)
  1337. if getline(j) !~ '^ '
  1338. return [i, j - 1]
  1339. endif
  1340. endfor
  1341. return [i, i]
  1342. endif
  1343. endfor
  1344. return [0, 0]
  1345. endfunction
  1346. function! s:log(bullet, name, lines)
  1347. if s:switch_in()
  1348. let [b, e] = s:logpos(a:name)
  1349. if b > 0
  1350. silent execute printf('%d,%d d _', b, e)
  1351. if b > winheight('.')
  1352. let b = 4
  1353. endif
  1354. else
  1355. let b = 4
  1356. endif
  1357. " FIXME For some reason, nomodifiable is set after :d in vim8
  1358. setlocal modifiable
  1359. call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
  1360. call s:switch_out()
  1361. endif
  1362. endfunction
  1363. function! s:update_vim()
  1364. let s:jobs = {}
  1365. call s:bar()
  1366. call s:tick()
  1367. endfunction
  1368. function! s:checkout_command(spec)
  1369. let a:spec.branch = s:git_origin_branch(a:spec)
  1370. return ['git', 'checkout', '-q', a:spec.branch, '--']
  1371. endfunction
  1372. function! s:merge_command(spec)
  1373. let a:spec.branch = s:git_origin_branch(a:spec)
  1374. return ['git', 'merge', '--ff-only', 'origin/'.a:spec.branch]
  1375. endfunction
  1376. function! s:tick()
  1377. let pull = s:update.pull
  1378. let prog = s:progress_opt(s:nvim || s:vim8)
  1379. while 1 " Without TCO, Vim stack is bound to explode
  1380. if empty(s:update.todo)
  1381. if empty(s:jobs) && !s:update.fin
  1382. call s:update_finish()
  1383. let s:update.fin = 1
  1384. endif
  1385. return
  1386. endif
  1387. let name = keys(s:update.todo)[0]
  1388. let spec = remove(s:update.todo, name)
  1389. let queue = get(spec, 'queue', [])
  1390. let new = empty(globpath(spec.dir, '.git', 1))
  1391. if empty(queue)
  1392. call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
  1393. redraw
  1394. endif
  1395. let has_tag = has_key(spec, 'tag')
  1396. if len(queue)
  1397. call s:spawn(name, spec, queue, { 'dir': spec.dir })
  1398. elseif !new
  1399. let [error, _] = s:git_validate(spec, 0)
  1400. if empty(error)
  1401. if pull
  1402. let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
  1403. if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
  1404. call extend(cmd, ['--depth', '99999999'])
  1405. endif
  1406. if !empty(prog)
  1407. call add(cmd, prog)
  1408. endif
  1409. let queue = [cmd, split('git remote set-head origin -a')]
  1410. if !has_tag && !has_key(spec, 'commit')
  1411. call extend(queue, [function('s:checkout_command'), function('s:merge_command')])
  1412. endif
  1413. call s:spawn(name, spec, queue, { 'dir': spec.dir })
  1414. else
  1415. let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
  1416. endif
  1417. else
  1418. let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
  1419. endif
  1420. else
  1421. let cmd = ['git', 'clone']
  1422. if !has_tag
  1423. call extend(cmd, s:clone_opt)
  1424. endif
  1425. if !empty(prog)
  1426. call add(cmd, prog)
  1427. endif
  1428. call s:spawn(name, spec, [extend(cmd, [spec.uri, s:trim(spec.dir)]), function('s:checkout_command'), function('s:merge_command')], { 'new': 1 })
  1429. endif
  1430. if !s:jobs[name].running
  1431. call s:reap(name)
  1432. endif
  1433. if len(s:jobs) >= s:update.threads
  1434. break
  1435. endif
  1436. endwhile
  1437. endfunction
  1438. function! s:update_python()
  1439. let py_exe = has('python') ? 'python' : 'python3'
  1440. execute py_exe "<< EOF"
  1441. import datetime
  1442. import functools
  1443. import os
  1444. try:
  1445. import queue
  1446. except ImportError:
  1447. import Queue as queue
  1448. import random
  1449. import re
  1450. import shutil
  1451. import signal
  1452. import subprocess
  1453. import tempfile
  1454. import threading as thr
  1455. import time
  1456. import traceback
  1457. import vim
  1458. G_NVIM = vim.eval("has('nvim')") == '1'
  1459. G_PULL = vim.eval('s:update.pull') == '1'
  1460. G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
  1461. G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
  1462. G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
  1463. G_PROGRESS = vim.eval('s:progress_opt(1)')
  1464. G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
  1465. G_STOP = thr.Event()
  1466. G_IS_WIN = vim.eval('s:is_win') == '1'
  1467. class PlugError(Exception):
  1468. def __init__(self, msg):
  1469. self.msg = msg
  1470. class CmdTimedOut(PlugError):
  1471. pass
  1472. class CmdFailed(PlugError):
  1473. pass
  1474. class InvalidURI(PlugError):
  1475. pass
  1476. class Action(object):
  1477. INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
  1478. class Buffer(object):
  1479. def __init__(self, lock, num_plugs, is_pull):
  1480. self.bar = ''
  1481. self.event = 'Updating' if is_pull else 'Installing'
  1482. self.lock = lock
  1483. self.maxy = int(vim.eval('winheight(".")'))
  1484. self.num_plugs = num_plugs
  1485. def __where(self, name):
  1486. """ Find first line with name in current buffer. Return line num. """
  1487. found, lnum = False, 0
  1488. matcher = re.compile('^[-+x*] {0}:'.format(name))
  1489. for line in vim.current.buffer:
  1490. if matcher.search(line) is not None:
  1491. found = True
  1492. break
  1493. lnum += 1
  1494. if not found:
  1495. lnum = -1
  1496. return lnum
  1497. def header(self):
  1498. curbuf = vim.current.buffer
  1499. curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
  1500. num_spaces = self.num_plugs - len(self.bar)
  1501. curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
  1502. with self.lock:
  1503. vim.command('normal! 2G')
  1504. vim.command('redraw')
  1505. def write(self, action, name, lines):
  1506. first, rest = lines[0], lines[1:]
  1507. msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
  1508. msg.extend([' ' + line for line in rest])
  1509. try:
  1510. if action == Action.ERROR:
  1511. self.bar += 'x'
  1512. vim.command("call add(s:update.errors, '{0}')".format(name))
  1513. elif action == Action.DONE:
  1514. self.bar += '='
  1515. curbuf = vim.current.buffer
  1516. lnum = self.__where(name)
  1517. if lnum != -1: # Found matching line num
  1518. del curbuf[lnum]
  1519. if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
  1520. lnum = 3
  1521. else:
  1522. lnum = 3
  1523. curbuf.append(msg, lnum)
  1524. self.header()
  1525. except vim.error:
  1526. pass
  1527. class Command(object):
  1528. CD = 'cd /d' if G_IS_WIN else 'cd'
  1529. def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
  1530. self.cmd = cmd
  1531. if cmd_dir:
  1532. self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
  1533. self.timeout = timeout
  1534. self.callback = cb if cb else (lambda msg: None)
  1535. self.clean = clean if clean else (lambda: None)
  1536. self.proc = None
  1537. @property
  1538. def alive(self):
  1539. """ Returns true only if command still running. """
  1540. return self.proc and self.proc.poll() is None
  1541. def execute(self, ntries=3):
  1542. """ Execute the command with ntries if CmdTimedOut.
  1543. Returns the output of the command if no Exception.
  1544. """
  1545. attempt, finished, limit = 0, False, self.timeout
  1546. while not finished:
  1547. try:
  1548. attempt += 1
  1549. result = self.try_command()
  1550. finished = True
  1551. return result
  1552. except CmdTimedOut:
  1553. if attempt != ntries:
  1554. self.notify_retry()
  1555. self.timeout += limit
  1556. else:
  1557. raise
  1558. def notify_retry(self):
  1559. """ Retry required for command, notify user. """
  1560. for count in range(3, 0, -1):
  1561. if G_STOP.is_set():
  1562. raise KeyboardInterrupt
  1563. msg = 'Timeout. Will retry in {0} second{1} ...'.format(
  1564. count, 's' if count != 1 else '')
  1565. self.callback([msg])
  1566. time.sleep(1)
  1567. self.callback(['Retrying ...'])
  1568. def try_command(self):
  1569. """ Execute a cmd & poll for callback. Returns list of output.
  1570. Raises CmdFailed -> return code for Popen isn't 0
  1571. Raises CmdTimedOut -> command exceeded timeout without new output
  1572. """
  1573. first_line = True
  1574. try:
  1575. tfile = tempfile.NamedTemporaryFile(mode='w+b')
  1576. preexec_fn = not G_IS_WIN and os.setsid or None
  1577. self.proc = subprocess.Popen(self.cmd, stdout=tfile,
  1578. stderr=subprocess.STDOUT,
  1579. stdin=subprocess.PIPE, shell=True,
  1580. preexec_fn=preexec_fn)
  1581. thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
  1582. thrd.start()
  1583. thread_not_started = True
  1584. while thread_not_started:
  1585. try:
  1586. thrd.join(0.1)
  1587. thread_not_started = False
  1588. except RuntimeError:
  1589. pass
  1590. while self.alive:
  1591. if G_STOP.is_set():
  1592. raise KeyboardInterrupt
  1593. if first_line or random.random() < G_LOG_PROB:
  1594. first_line = False
  1595. line = '' if G_IS_WIN else nonblock_read(tfile.name)
  1596. if line:
  1597. self.callback([line])
  1598. time_diff = time.time() - os.path.getmtime(tfile.name)
  1599. if time_diff > self.timeout:
  1600. raise CmdTimedOut(['Timeout!'])
  1601. thrd.join(0.5)
  1602. tfile.seek(0)
  1603. result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
  1604. if self.proc.returncode != 0:
  1605. raise CmdFailed([''] + result)
  1606. return result
  1607. except:
  1608. self.terminate()
  1609. raise
  1610. def terminate(self):
  1611. """ Terminate process and cleanup. """
  1612. if self.alive:
  1613. if G_IS_WIN:
  1614. os.kill(self.proc.pid, signal.SIGINT)
  1615. else:
  1616. os.killpg(self.proc.pid, signal.SIGTERM)
  1617. self.clean()
  1618. class Plugin(object):
  1619. def __init__(self, name, args, buf_q, lock):
  1620. self.name = name
  1621. self.args = args
  1622. self.buf_q = buf_q
  1623. self.lock = lock
  1624. self.tag = args.get('tag', 0)
  1625. def manage(self):
  1626. try:
  1627. if os.path.exists(self.args['dir']):
  1628. self.update()
  1629. else:
  1630. self.install()
  1631. with self.lock:
  1632. thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
  1633. except PlugError as exc:
  1634. self.write(Action.ERROR, self.name, exc.msg)
  1635. except KeyboardInterrupt:
  1636. G_STOP.set()
  1637. self.write(Action.ERROR, self.name, ['Interrupted!'])
  1638. except:
  1639. # Any exception except those above print stack trace
  1640. msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
  1641. self.write(Action.ERROR, self.name, msg.split('\n'))
  1642. raise
  1643. def install(self):
  1644. target = self.args['dir']
  1645. if target[-1] == '\\':
  1646. target = target[0:-1]
  1647. def clean(target):
  1648. def _clean():
  1649. try:
  1650. shutil.rmtree(target)
  1651. except OSError:
  1652. pass
  1653. return _clean
  1654. self.write(Action.INSTALL, self.name, ['Installing ...'])
  1655. callback = functools.partial(self.write, Action.INSTALL, self.name)
  1656. cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
  1657. '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
  1658. esc(target))
  1659. com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
  1660. result = com.execute(G_RETRIES)
  1661. self.write(Action.DONE, self.name, result[-1:])
  1662. def repo_uri(self):
  1663. cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
  1664. command = Command(cmd, self.args['dir'], G_TIMEOUT,)
  1665. result = command.execute(G_RETRIES)
  1666. return result[-1]
  1667. def update(self):
  1668. actual_uri = self.repo_uri()
  1669. expect_uri = self.args['uri']
  1670. regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
  1671. ma = regex.match(actual_uri)
  1672. mb = regex.match(expect_uri)
  1673. if ma is None or mb is None or ma.groups() != mb.groups():
  1674. msg = ['',
  1675. 'Invalid URI: {0}'.format(actual_uri),
  1676. 'Expected {0}'.format(expect_uri),
  1677. 'PlugClean required.']
  1678. raise InvalidURI(msg)
  1679. if G_PULL:
  1680. self.write(Action.UPDATE, self.name, ['Updating ...'])
  1681. callback = functools.partial(self.write, Action.UPDATE, self.name)
  1682. fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
  1683. cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
  1684. com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
  1685. result = com.execute(G_RETRIES)
  1686. self.write(Action.DONE, self.name, result[-1:])
  1687. else:
  1688. self.write(Action.DONE, self.name, ['Already installed'])
  1689. def write(self, action, name, msg):
  1690. self.buf_q.put((action, name, msg))
  1691. class PlugThread(thr.Thread):
  1692. def __init__(self, tname, args):
  1693. super(PlugThread, self).__init__()
  1694. self.tname = tname
  1695. self.args = args
  1696. def run(self):
  1697. thr.current_thread().name = self.tname
  1698. buf_q, work_q, lock = self.args
  1699. try:
  1700. while not G_STOP.is_set():
  1701. name, args = work_q.get_nowait()
  1702. plug = Plugin(name, args, buf_q, lock)
  1703. plug.manage()
  1704. work_q.task_done()
  1705. except queue.Empty:
  1706. pass
  1707. class RefreshThread(thr.Thread):
  1708. def __init__(self, lock):
  1709. super(RefreshThread, self).__init__()
  1710. self.lock = lock
  1711. self.running = True
  1712. def run(self):
  1713. while self.running:
  1714. with self.lock:
  1715. thread_vim_command('noautocmd normal! a')
  1716. time.sleep(0.33)
  1717. def stop(self):
  1718. self.running = False
  1719. if G_NVIM:
  1720. def thread_vim_command(cmd):
  1721. vim.session.threadsafe_call(lambda: vim.command(cmd))
  1722. else:
  1723. def thread_vim_command(cmd):
  1724. vim.command(cmd)
  1725. def esc(name):
  1726. return '"' + name.replace('"', '\"') + '"'
  1727. def nonblock_read(fname):
  1728. """ Read a file with nonblock flag. Return the last line. """
  1729. fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
  1730. buf = os.read(fread, 100000).decode('utf-8', 'replace')
  1731. os.close(fread)
  1732. line = buf.rstrip('\r\n')
  1733. left = max(line.rfind('\r'), line.rfind('\n'))
  1734. if left != -1:
  1735. left += 1
  1736. line = line[left:]
  1737. return line
  1738. def main():
  1739. thr.current_thread().name = 'main'
  1740. nthreads = int(vim.eval('s:update.threads'))
  1741. plugs = vim.eval('s:update.todo')
  1742. mac_gui = vim.eval('s:mac_gui') == '1'
  1743. lock = thr.Lock()
  1744. buf = Buffer(lock, len(plugs), G_PULL)
  1745. buf_q, work_q = queue.Queue(), queue.Queue()
  1746. for work in plugs.items():
  1747. work_q.put(work)
  1748. start_cnt = thr.active_count()
  1749. for num in range(nthreads):
  1750. tname = 'PlugT-{0:02}'.format(num)
  1751. thread = PlugThread(tname, (buf_q, work_q, lock))
  1752. thread.start()
  1753. if mac_gui:
  1754. rthread = RefreshThread(lock)
  1755. rthread.start()
  1756. while not buf_q.empty() or thr.active_count() != start_cnt:
  1757. try:
  1758. action, name, msg = buf_q.get(True, 0.25)
  1759. buf.write(action, name, ['OK'] if not msg else msg)
  1760. buf_q.task_done()
  1761. except queue.Empty:
  1762. pass
  1763. except KeyboardInterrupt:
  1764. G_STOP.set()
  1765. if mac_gui:
  1766. rthread.stop()
  1767. rthread.join()
  1768. main()
  1769. EOF
  1770. endfunction
  1771. function! s:update_ruby()
  1772. ruby << EOF
  1773. module PlugStream
  1774. SEP = ["\r", "\n", nil]
  1775. def get_line
  1776. buffer = ''
  1777. loop do
  1778. char = readchar rescue return
  1779. if SEP.include? char.chr
  1780. buffer << $/
  1781. break
  1782. else
  1783. buffer << char
  1784. end
  1785. end
  1786. buffer
  1787. end
  1788. end unless defined?(PlugStream)
  1789. def esc arg
  1790. %["#{arg.gsub('"', '\"')}"]
  1791. end
  1792. def killall pid
  1793. pids = [pid]
  1794. if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
  1795. pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
  1796. else
  1797. unless `which pgrep 2> /dev/null`.empty?
  1798. children = pids
  1799. until children.empty?
  1800. children = children.map { |pid|
  1801. `pgrep -P #{pid}`.lines.map { |l| l.chomp }
  1802. }.flatten
  1803. pids += children
  1804. end
  1805. end
  1806. pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
  1807. end
  1808. end
  1809. def compare_git_uri a, b
  1810. regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
  1811. regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
  1812. end
  1813. require 'thread'
  1814. require 'fileutils'
  1815. require 'timeout'
  1816. running = true
  1817. iswin = VIM::evaluate('s:is_win').to_i == 1
  1818. pull = VIM::evaluate('s:update.pull').to_i == 1
  1819. base = VIM::evaluate('g:plug_home')
  1820. all = VIM::evaluate('s:update.todo')
  1821. limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
  1822. tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
  1823. nthr = VIM::evaluate('s:update.threads').to_i
  1824. maxy = VIM::evaluate('winheight(".")').to_i
  1825. vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
  1826. cd = iswin ? 'cd /d' : 'cd'
  1827. tot = VIM::evaluate('len(s:update.todo)') || 0
  1828. bar = ''
  1829. skip = 'Already installed'
  1830. mtx = Mutex.new
  1831. take1 = proc { mtx.synchronize { running && all.shift } }
  1832. logh = proc {
  1833. cnt = bar.length
  1834. $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
  1835. $curbuf[2] = '[' + bar.ljust(tot) + ']'
  1836. VIM::command('normal! 2G')
  1837. VIM::command('redraw')
  1838. }
  1839. where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
  1840. log = proc { |name, result, type|
  1841. mtx.synchronize do
  1842. ing = ![true, false].include?(type)
  1843. bar += type ? '=' : 'x' unless ing
  1844. b = case type
  1845. when :install then '+' when :update then '*'
  1846. when true, nil then '-' else
  1847. VIM::command("call add(s:update.errors, '#{name}')")
  1848. 'x'
  1849. end
  1850. result =
  1851. if type || type.nil?
  1852. ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
  1853. elsif result =~ /^Interrupted|^Timeout/
  1854. ["#{b} #{name}: #{result}"]
  1855. else
  1856. ["#{b} #{name}"] + result.lines.map { |l| " " << l }
  1857. end
  1858. if lnum = where.call(name)
  1859. $curbuf.delete lnum
  1860. lnum = 4 if ing && lnum > maxy
  1861. end
  1862. result.each_with_index do |line, offset|
  1863. $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
  1864. end
  1865. logh.call
  1866. end
  1867. }
  1868. bt = proc { |cmd, name, type, cleanup|
  1869. tried = timeout = 0
  1870. begin
  1871. tried += 1
  1872. timeout += limit
  1873. fd = nil
  1874. data = ''
  1875. if iswin
  1876. Timeout::timeout(timeout) do
  1877. tmp = VIM::evaluate('tempname()')
  1878. system("(#{cmd}) > #{tmp}")
  1879. data = File.read(tmp).chomp
  1880. File.unlink tmp rescue nil
  1881. end
  1882. else
  1883. fd = IO.popen(cmd).extend(PlugStream)
  1884. first_line = true
  1885. log_prob = 1.0 / nthr
  1886. while line = Timeout::timeout(timeout) { fd.get_line }
  1887. data << line
  1888. log.call name, line.chomp, type if name && (first_line || rand < log_prob)
  1889. first_line = false
  1890. end
  1891. fd.close
  1892. end
  1893. [$? == 0, data.chomp]
  1894. rescue Timeout::Error, Interrupt => e
  1895. if fd && !fd.closed?
  1896. killall fd.pid
  1897. fd.close
  1898. end
  1899. cleanup.call if cleanup
  1900. if e.is_a?(Timeout::Error) && tried < tries
  1901. 3.downto(1) do |countdown|
  1902. s = countdown > 1 ? 's' : ''
  1903. log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
  1904. sleep 1
  1905. end
  1906. log.call name, 'Retrying ...', type
  1907. retry
  1908. end
  1909. [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
  1910. end
  1911. }
  1912. main = Thread.current
  1913. threads = []
  1914. watcher = Thread.new {
  1915. if vim7
  1916. while VIM::evaluate('getchar(1)')
  1917. sleep 0.1
  1918. end
  1919. else
  1920. require 'io/console' # >= Ruby 1.9
  1921. nil until IO.console.getch == 3.chr
  1922. end
  1923. mtx.synchronize do
  1924. running = false
  1925. threads.each { |t| t.raise Interrupt } unless vim7
  1926. end
  1927. threads.each { |t| t.join rescue nil }
  1928. main.kill
  1929. }
  1930. refresh = Thread.new {
  1931. while true
  1932. mtx.synchronize do
  1933. break unless running
  1934. VIM::command('noautocmd normal! a')
  1935. end
  1936. sleep 0.2
  1937. end
  1938. } if VIM::evaluate('s:mac_gui') == 1
  1939. clone_opt = VIM::evaluate('s:clone_opt').join(' ')
  1940. progress = VIM::evaluate('s:progress_opt(1)')
  1941. nthr.times do
  1942. mtx.synchronize do
  1943. threads << Thread.new {
  1944. while pair = take1.call
  1945. name = pair.first
  1946. dir, uri, tag = pair.last.values_at *%w[dir uri tag]
  1947. exists = File.directory? dir
  1948. ok, result =
  1949. if exists
  1950. chdir = "#{cd} #{iswin ? dir : esc(dir)}"
  1951. ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
  1952. current_uri = data.lines.to_a.last
  1953. if !ret
  1954. if data =~ /^Interrupted|^Timeout/
  1955. [false, data]
  1956. else
  1957. [false, [data.chomp, "PlugClean required."].join($/)]
  1958. end
  1959. elsif !compare_git_uri(current_uri, uri)
  1960. [false, ["Invalid URI: #{current_uri}",
  1961. "Expected: #{uri}",
  1962. "PlugClean required."].join($/)]
  1963. else
  1964. if pull
  1965. log.call name, 'Updating ...', :update
  1966. fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
  1967. bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
  1968. else
  1969. [true, skip]
  1970. end
  1971. end
  1972. else
  1973. d = esc dir.sub(%r{[\\/]+$}, '')
  1974. log.call name, 'Installing ...', :install
  1975. bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
  1976. FileUtils.rm_rf dir
  1977. }
  1978. end
  1979. mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
  1980. log.call name, result, ok
  1981. end
  1982. } if running
  1983. end
  1984. end
  1985. threads.each { |t| t.join rescue nil }
  1986. logh.call
  1987. refresh.kill if refresh
  1988. watcher.kill
  1989. EOF
  1990. endfunction
  1991. function! s:shellesc_cmd(arg, script)
  1992. let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
  1993. return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
  1994. endfunction
  1995. function! s:shellesc_ps1(arg)
  1996. return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
  1997. endfunction
  1998. function! s:shellesc_sh(arg)
  1999. return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
  2000. endfunction
  2001. " Escape the shell argument based on the shell.
  2002. " Vim and Neovim's shellescape() are insufficient.
  2003. " 1. shellslash determines whether to use single/double quotes.
  2004. " Double-quote escaping is fragile for cmd.exe.
  2005. " 2. It does not work for powershell.
  2006. " 3. It does not work for *sh shells if the command is executed
  2007. " via cmd.exe (ie. cmd.exe /c sh -c command command_args)
  2008. " 4. It does not support batchfile syntax.
  2009. "
  2010. " Accepts an optional dictionary with the following keys:
  2011. " - shell: same as Vim/Neovim 'shell' option.
  2012. " If unset, fallback to 'cmd.exe' on Windows or 'sh'.
  2013. " - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
  2014. function! plug#shellescape(arg, ...)
  2015. if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
  2016. return a:arg
  2017. endif
  2018. let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
  2019. let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
  2020. let script = get(opts, 'script', 1)
  2021. if shell =~# 'cmd\(\.exe\)\?$'
  2022. return s:shellesc_cmd(a:arg, script)
  2023. elseif s:is_powershell(shell)
  2024. return s:shellesc_ps1(a:arg)
  2025. endif
  2026. return s:shellesc_sh(a:arg)
  2027. endfunction
  2028. function! s:glob_dir(path)
  2029. return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
  2030. endfunction
  2031. function! s:progress_bar(line, bar, total)
  2032. call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
  2033. endfunction
  2034. function! s:compare_git_uri(a, b)
  2035. " See `git help clone'
  2036. " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
  2037. " [git@] github.com[:port] : junegunn/vim-plug [.git]
  2038. " file:// / junegunn/vim-plug [/]
  2039. " / junegunn/vim-plug [/]
  2040. let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
  2041. let ma = matchlist(a:a, pat)
  2042. let mb = matchlist(a:b, pat)
  2043. return ma[1:2] ==# mb[1:2]
  2044. endfunction
  2045. function! s:format_message(bullet, name, message)
  2046. if a:bullet != 'x'
  2047. return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
  2048. else
  2049. let lines = map(s:lines(a:message), '" ".v:val')
  2050. return extend([printf('x %s:', a:name)], lines)
  2051. endif
  2052. endfunction
  2053. function! s:with_cd(cmd, dir, ...)
  2054. let script = a:0 > 0 ? a:1 : 1
  2055. let pwsh = s:is_powershell(&shell)
  2056. let cd = s:is_win && !pwsh ? 'cd /d' : 'cd'
  2057. let sep = pwsh ? ';' : '&&'
  2058. return printf('%s %s %s %s', cd, plug#shellescape(a:dir, {'script': script, 'shell': &shell}), sep, a:cmd)
  2059. endfunction
  2060. function! s:system(cmd, ...)
  2061. let batchfile = ''
  2062. try
  2063. let [sh, shellcmdflag, shrd] = s:chsh(1)
  2064. if type(a:cmd) == s:TYPE.list
  2065. " Neovim's system() supports list argument to bypass the shell
  2066. " but it cannot set the working directory for the command.
  2067. " Assume that the command does not rely on the shell.
  2068. if has('nvim') && a:0 == 0
  2069. return system(a:cmd)
  2070. endif
  2071. let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
  2072. if s:is_powershell(&shell)
  2073. let cmd = '& ' . cmd
  2074. endif
  2075. else
  2076. let cmd = a:cmd
  2077. endif
  2078. if a:0 > 0
  2079. let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
  2080. endif
  2081. if s:is_win && type(a:cmd) != s:TYPE.list
  2082. let [batchfile, cmd] = s:batchfile(cmd)
  2083. endif
  2084. return system(cmd)
  2085. finally
  2086. let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
  2087. if s:is_win && filereadable(batchfile)
  2088. call delete(batchfile)
  2089. endif
  2090. endtry
  2091. endfunction
  2092. function! s:system_chomp(...)
  2093. let ret = call('s:system', a:000)
  2094. return v:shell_error ? '' : substitute(ret, '\n$', '', '')
  2095. endfunction
  2096. function! s:git_validate(spec, check_branch)
  2097. let err = ''
  2098. if isdirectory(a:spec.dir)
  2099. let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
  2100. let remote = result[-1]
  2101. if empty(remote)
  2102. let err = join([remote, 'PlugClean required.'], "\n")
  2103. elseif !s:compare_git_uri(remote, a:spec.uri)
  2104. let err = join(['Invalid URI: '.remote,
  2105. \ 'Expected: '.a:spec.uri,
  2106. \ 'PlugClean required.'], "\n")
  2107. elseif a:check_branch && has_key(a:spec, 'commit')
  2108. let sha = s:git_revision(a:spec.dir)
  2109. if empty(sha)
  2110. let err = join(add(result, 'PlugClean required.'), "\n")
  2111. elseif !s:hash_match(sha, a:spec.commit)
  2112. let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
  2113. \ a:spec.commit[:6], sha[:6]),
  2114. \ 'PlugUpdate required.'], "\n")
  2115. endif
  2116. elseif a:check_branch
  2117. let current_branch = result[0]
  2118. " Check tag
  2119. let origin_branch = s:git_origin_branch(a:spec)
  2120. if has_key(a:spec, 'tag')
  2121. let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
  2122. if a:spec.tag !=# tag && a:spec.tag !~ '\*'
  2123. let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
  2124. \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
  2125. endif
  2126. " Check branch
  2127. elseif origin_branch !=# current_branch
  2128. let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
  2129. \ current_branch, origin_branch)
  2130. endif
  2131. if empty(err)
  2132. let ahead_behind = split(s:lastline(s:system([
  2133. \ 'git', 'rev-list', '--count', '--left-right',
  2134. \ printf('HEAD...origin/%s', origin_branch)
  2135. \ ], a:spec.dir)), '\t')
  2136. if v:shell_error || len(ahead_behind) != 2
  2137. let err = "Failed to compare with the origin. The default branch might have changed.\nPlugClean required."
  2138. else
  2139. let [ahead, behind] = ahead_behind
  2140. if ahead && behind
  2141. " Only mention PlugClean if diverged, otherwise it's likely to be
  2142. " pushable (and probably not that messed up).
  2143. let err = printf(
  2144. \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
  2145. \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
  2146. elseif ahead
  2147. let err = printf("Ahead of origin/%s by %d commit(s).\n"
  2148. \ .'Cannot update until local changes are pushed.',
  2149. \ origin_branch, ahead)
  2150. endif
  2151. endif
  2152. endif
  2153. endif
  2154. else
  2155. let err = 'Not found'
  2156. endif
  2157. return [err, err =~# 'PlugClean']
  2158. endfunction
  2159. function! s:rm_rf(dir)
  2160. if isdirectory(a:dir)
  2161. return s:system(s:is_win
  2162. \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
  2163. \ : ['rm', '-rf', a:dir])
  2164. endif
  2165. endfunction
  2166. function! s:clean(force)
  2167. call s:prepare()
  2168. call append(0, 'Searching for invalid plugins in '.g:plug_home)
  2169. call append(1, '')
  2170. " List of valid directories
  2171. let dirs = []
  2172. let errs = {}
  2173. let [cnt, total] = [0, len(g:plugs)]
  2174. for [name, spec] in items(g:plugs)
  2175. if !s:is_managed(name) || get(spec, 'frozen', 0)
  2176. call add(dirs, spec.dir)
  2177. else
  2178. let [err, clean] = s:git_validate(spec, 1)
  2179. if clean
  2180. let errs[spec.dir] = s:lines(err)[0]
  2181. else
  2182. call add(dirs, spec.dir)
  2183. endif
  2184. endif
  2185. let cnt += 1
  2186. call s:progress_bar(2, repeat('=', cnt), total)
  2187. normal! 2G
  2188. redraw
  2189. endfor
  2190. let allowed = {}
  2191. for dir in dirs
  2192. let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
  2193. let allowed[dir] = 1
  2194. for child in s:glob_dir(dir)
  2195. let allowed[child] = 1
  2196. endfor
  2197. endfor
  2198. let todo = []
  2199. let found = sort(s:glob_dir(g:plug_home))
  2200. while !empty(found)
  2201. let f = remove(found, 0)
  2202. if !has_key(allowed, f) && isdirectory(f)
  2203. call add(todo, f)
  2204. call append(line('$'), '- ' . f)
  2205. if has_key(errs, f)
  2206. call append(line('$'), ' ' . errs[f])
  2207. endif
  2208. let found = filter(found, 'stridx(v:val, f) != 0')
  2209. end
  2210. endwhile
  2211. 4
  2212. redraw
  2213. if empty(todo)
  2214. call append(line('$'), 'Already clean.')
  2215. else
  2216. let s:clean_count = 0
  2217. call append(3, ['Directories to delete:', ''])
  2218. redraw!
  2219. if a:force || s:ask_no_interrupt('Delete all directories?')
  2220. call s:delete([6, line('$')], 1)
  2221. else
  2222. call setline(4, 'Cancelled.')
  2223. nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
  2224. nmap <silent> <buffer> dd d_
  2225. xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
  2226. echo 'Delete the lines (d{motion}) to delete the corresponding directories'
  2227. endif
  2228. endif
  2229. 4
  2230. setlocal nomodifiable
  2231. endfunction
  2232. function! s:delete_op(type, ...)
  2233. call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
  2234. endfunction
  2235. function! s:delete(range, force)
  2236. let [l1, l2] = a:range
  2237. let force = a:force
  2238. let err_count = 0
  2239. while l1 <= l2
  2240. let line = getline(l1)
  2241. if line =~ '^- ' && isdirectory(line[2:])
  2242. execute l1
  2243. redraw!
  2244. let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
  2245. let force = force || answer > 1
  2246. if answer
  2247. let err = s:rm_rf(line[2:])
  2248. setlocal modifiable
  2249. if empty(err)
  2250. call setline(l1, '~'.line[1:])
  2251. let s:clean_count += 1
  2252. else
  2253. delete _
  2254. call append(l1 - 1, s:format_message('x', line[1:], err))
  2255. let l2 += len(s:lines(err))
  2256. let err_count += 1
  2257. endif
  2258. let msg = printf('Removed %d directories.', s:clean_count)
  2259. if err_count > 0
  2260. let msg .= printf(' Failed to remove %d directories.', err_count)
  2261. endif
  2262. call setline(4, msg)
  2263. setlocal nomodifiable
  2264. endif
  2265. endif
  2266. let l1 += 1
  2267. endwhile
  2268. endfunction
  2269. function! s:upgrade()
  2270. echo 'Downloading the latest version of vim-plug'
  2271. redraw
  2272. let tmp = s:plug_tempname()
  2273. let new = tmp . '/plug.vim'
  2274. try
  2275. let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
  2276. if v:shell_error
  2277. return s:err('Error upgrading vim-plug: '. out)
  2278. endif
  2279. if readfile(s:me) ==# readfile(new)
  2280. echo 'vim-plug is already up-to-date'
  2281. return 0
  2282. else
  2283. call rename(s:me, s:me . '.old')
  2284. call rename(new, s:me)
  2285. unlet g:loaded_plug
  2286. echo 'vim-plug has been upgraded'
  2287. return 1
  2288. endif
  2289. finally
  2290. silent! call s:rm_rf(tmp)
  2291. endtry
  2292. endfunction
  2293. function! s:upgrade_specs()
  2294. for spec in values(g:plugs)
  2295. let spec.frozen = get(spec, 'frozen', 0)
  2296. endfor
  2297. endfunction
  2298. function! s:status()
  2299. call s:prepare()
  2300. call append(0, 'Checking plugins')
  2301. call append(1, '')
  2302. let ecnt = 0
  2303. let unloaded = 0
  2304. let [cnt, total] = [0, len(g:plugs)]
  2305. for [name, spec] in items(g:plugs)
  2306. let is_dir = isdirectory(spec.dir)
  2307. if has_key(spec, 'uri')
  2308. if is_dir
  2309. let [err, _] = s:git_validate(spec, 1)
  2310. let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
  2311. else
  2312. let [valid, msg] = [0, 'Not found. Try PlugInstall.']
  2313. endif
  2314. else
  2315. if is_dir
  2316. let [valid, msg] = [1, 'OK']
  2317. else
  2318. let [valid, msg] = [0, 'Not found.']
  2319. endif
  2320. endif
  2321. let cnt += 1
  2322. let ecnt += !valid
  2323. " `s:loaded` entry can be missing if PlugUpgraded
  2324. if is_dir && get(s:loaded, name, -1) == 0
  2325. let unloaded = 1
  2326. let msg .= ' (not loaded)'
  2327. endif
  2328. call s:progress_bar(2, repeat('=', cnt), total)
  2329. call append(3, s:format_message(valid ? '-' : 'x', name, msg))
  2330. normal! 2G
  2331. redraw
  2332. endfor
  2333. call setline(1, 'Finished. '.ecnt.' error(s).')
  2334. normal! gg
  2335. setlocal nomodifiable
  2336. if unloaded
  2337. echo "Press 'L' on each line to load plugin, or 'U' to update"
  2338. nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
  2339. xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
  2340. end
  2341. endfunction
  2342. function! s:extract_name(str, prefix, suffix)
  2343. return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
  2344. endfunction
  2345. function! s:status_load(lnum)
  2346. let line = getline(a:lnum)
  2347. let name = s:extract_name(line, '-', '(not loaded)')
  2348. if !empty(name)
  2349. call plug#load(name)
  2350. setlocal modifiable
  2351. call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
  2352. setlocal nomodifiable
  2353. endif
  2354. endfunction
  2355. function! s:status_update() range
  2356. let lines = getline(a:firstline, a:lastline)
  2357. let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
  2358. if !empty(names)
  2359. echo
  2360. execute 'PlugUpdate' join(names)
  2361. endif
  2362. endfunction
  2363. function! s:is_preview_window_open()
  2364. silent! wincmd P
  2365. if &previewwindow
  2366. wincmd p
  2367. return 1
  2368. endif
  2369. endfunction
  2370. function! s:find_name(lnum)
  2371. for lnum in reverse(range(1, a:lnum))
  2372. let line = getline(lnum)
  2373. if empty(line)
  2374. return ''
  2375. endif
  2376. let name = s:extract_name(line, '-', '')
  2377. if !empty(name)
  2378. return name
  2379. endif
  2380. endfor
  2381. return ''
  2382. endfunction
  2383. function! s:preview_commit()
  2384. if b:plug_preview < 0
  2385. let b:plug_preview = !s:is_preview_window_open()
  2386. endif
  2387. let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
  2388. if empty(sha)
  2389. let name = matchstr(getline('.'), '^- \zs[^:]*\ze:$')
  2390. if empty(name)
  2391. return
  2392. endif
  2393. let title = 'HEAD@{1}..'
  2394. let command = 'git diff --no-color HEAD@{1}'
  2395. else
  2396. let title = sha
  2397. let command = 'git show --no-color --pretty=medium '.sha
  2398. let name = s:find_name(line('.'))
  2399. endif
  2400. if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
  2401. return
  2402. endif
  2403. if !s:is_preview_window_open()
  2404. execute get(g:, 'plug_pwindow', 'vertical rightbelow new')
  2405. execute 'e' title
  2406. else
  2407. execute 'pedit' title
  2408. wincmd P
  2409. endif
  2410. setlocal previewwindow filetype=git buftype=nofile bufhidden=wipe nobuflisted modifiable
  2411. let batchfile = ''
  2412. try
  2413. let [sh, shellcmdflag, shrd] = s:chsh(1)
  2414. let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && '.command
  2415. if s:is_win
  2416. let [batchfile, cmd] = s:batchfile(cmd)
  2417. endif
  2418. execute 'silent %!' cmd
  2419. finally
  2420. let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
  2421. if s:is_win && filereadable(batchfile)
  2422. call delete(batchfile)
  2423. endif
  2424. endtry
  2425. setlocal nomodifiable
  2426. nnoremap <silent> <buffer> q :q<cr>
  2427. wincmd p
  2428. endfunction
  2429. function! s:section(flags)
  2430. call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
  2431. endfunction
  2432. function! s:format_git_log(line)
  2433. let indent = ' '
  2434. let tokens = split(a:line, nr2char(1))
  2435. if len(tokens) != 5
  2436. return indent.substitute(a:line, '\s*$', '', '')
  2437. endif
  2438. let [graph, sha, refs, subject, date] = tokens
  2439. let tag = matchstr(refs, 'tag: [^,)]\+')
  2440. let tag = empty(tag) ? ' ' : ' ('.tag.') '
  2441. return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
  2442. endfunction
  2443. function! s:append_ul(lnum, text)
  2444. call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
  2445. endfunction
  2446. function! s:diff()
  2447. call s:prepare()
  2448. call append(0, ['Collecting changes ...', ''])
  2449. let cnts = [0, 0]
  2450. let bar = ''
  2451. let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
  2452. call s:progress_bar(2, bar, len(total))
  2453. for origin in [1, 0]
  2454. let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
  2455. if empty(plugs)
  2456. continue
  2457. endif
  2458. call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
  2459. for [k, v] in plugs
  2460. let branch = s:git_origin_branch(v)
  2461. if len(branch)
  2462. let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
  2463. let cmd = ['git', 'log', '--graph', '--color=never']
  2464. if s:git_version_requirement(2, 10, 0)
  2465. call add(cmd, '--no-show-signature')
  2466. endif
  2467. call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
  2468. if has_key(v, 'rtp')
  2469. call extend(cmd, ['--', v.rtp])
  2470. endif
  2471. let diff = s:system_chomp(cmd, v.dir)
  2472. if !empty(diff)
  2473. let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
  2474. call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
  2475. let cnts[origin] += 1
  2476. endif
  2477. endif
  2478. let bar .= '='
  2479. call s:progress_bar(2, bar, len(total))
  2480. normal! 2G
  2481. redraw
  2482. endfor
  2483. if !cnts[origin]
  2484. call append(5, ['', 'N/A'])
  2485. endif
  2486. endfor
  2487. call setline(1, printf('%d plugin(s) updated.', cnts[0])
  2488. \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
  2489. if cnts[0] || cnts[1]
  2490. nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
  2491. if empty(maparg("\<cr>", 'n'))
  2492. nmap <buffer> <cr> <plug>(plug-preview)
  2493. endif
  2494. if empty(maparg('o', 'n'))
  2495. nmap <buffer> o <plug>(plug-preview)
  2496. endif
  2497. endif
  2498. if cnts[0]
  2499. nnoremap <silent> <buffer> X :call <SID>revert()<cr>
  2500. echo "Press 'X' on each block to revert the update"
  2501. endif
  2502. normal! gg
  2503. setlocal nomodifiable
  2504. endfunction
  2505. function! s:revert()
  2506. if search('^Pending updates', 'bnW')
  2507. return
  2508. endif
  2509. let name = s:find_name(line('.'))
  2510. if empty(name) || !has_key(g:plugs, name) ||
  2511. \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
  2512. return
  2513. endif
  2514. call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
  2515. setlocal modifiable
  2516. normal! "_dap
  2517. setlocal nomodifiable
  2518. echo 'Reverted'
  2519. endfunction
  2520. function! s:snapshot(force, ...) abort
  2521. call s:prepare()
  2522. setf vim
  2523. call append(0, ['" Generated by vim-plug',
  2524. \ '" '.strftime("%c"),
  2525. \ '" :source this file in vim to restore the snapshot',
  2526. \ '" or execute: vim -S snapshot.vim',
  2527. \ '', '', 'PlugUpdate!'])
  2528. 1
  2529. let anchor = line('$') - 3
  2530. let names = sort(keys(filter(copy(g:plugs),
  2531. \'has_key(v:val, "uri") && isdirectory(v:val.dir)')))
  2532. for name in reverse(names)
  2533. let sha = has_key(g:plugs[name], 'commit') ? g:plugs[name].commit : s:git_revision(g:plugs[name].dir)
  2534. if !empty(sha)
  2535. call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
  2536. redraw
  2537. endif
  2538. endfor
  2539. if a:0 > 0
  2540. let fn = s:plug_expand(a:1)
  2541. if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
  2542. return
  2543. endif
  2544. call writefile(getline(1, '$'), fn)
  2545. echo 'Saved as '.a:1
  2546. silent execute 'e' s:esc(fn)
  2547. setf vim
  2548. endif
  2549. endfunction
  2550. function! s:split_rtp()
  2551. return split(&rtp, '\\\@<!,')
  2552. endfunction
  2553. let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
  2554. let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
  2555. if exists('g:plugs')
  2556. let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
  2557. call s:upgrade_specs()
  2558. call s:define_commands()
  2559. endif
  2560. let &cpo = s:cpo_save
  2561. unlet s:cpo_save