plug.vim 82 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867
  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,plugAbort
  703. syn match plugNumber /[0-9]\+[0-9.]*/ contained
  704. syn match plugBracket /[[\]]/ contained
  705. syn match plugX /x/ contained
  706. syn match plugAbort /\~/ contained
  707. syn match plugDash /^-\{1}\ /
  708. syn match plugPlus /^+/
  709. syn match plugStar /^*/
  710. syn match plugMessage /\(^- \)\@<=.*/
  711. syn match plugName /\(^- \)\@<=[^ ]*:/
  712. syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
  713. syn match plugTag /(tag: [^)]\+)/
  714. syn match plugInstall /\(^+ \)\@<=[^:]*/
  715. syn match plugUpdate /\(^* \)\@<=[^:]*/
  716. syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
  717. syn match plugEdge /^ \X\+$/
  718. syn match plugEdge /^ \X*/ contained nextgroup=plugSha
  719. syn match plugSha /[0-9a-f]\{7,9}/ contained
  720. syn match plugRelDate /([^)]*)$/ contained
  721. syn match plugNotLoaded /(not loaded)$/
  722. syn match plugError /^x.*/
  723. syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
  724. syn match plugH2 /^.*:\n-\+$/
  725. syn match plugH2 /^-\{2,}/
  726. syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
  727. hi def link plug1 Title
  728. hi def link plug2 Repeat
  729. hi def link plugH2 Type
  730. hi def link plugX Exception
  731. hi def link plugAbort Ignore
  732. hi def link plugBracket Structure
  733. hi def link plugNumber Number
  734. hi def link plugDash Special
  735. hi def link plugPlus Constant
  736. hi def link plugStar Boolean
  737. hi def link plugMessage Function
  738. hi def link plugName Label
  739. hi def link plugInstall Function
  740. hi def link plugUpdate Type
  741. hi def link plugError Error
  742. hi def link plugDeleted Ignore
  743. hi def link plugRelDate Comment
  744. hi def link plugEdge PreProc
  745. hi def link plugSha Identifier
  746. hi def link plugTag Constant
  747. hi def link plugNotLoaded Comment
  748. endfunction
  749. function! s:lpad(str, len)
  750. return a:str . repeat(' ', a:len - len(a:str))
  751. endfunction
  752. function! s:lines(msg)
  753. return split(a:msg, "[\r\n]")
  754. endfunction
  755. function! s:lastline(msg)
  756. return get(s:lines(a:msg), -1, '')
  757. endfunction
  758. function! s:new_window()
  759. execute get(g:, 'plug_window', '-tabnew')
  760. endfunction
  761. function! s:plug_window_exists()
  762. let buflist = tabpagebuflist(s:plug_tab)
  763. return !empty(buflist) && index(buflist, s:plug_buf) >= 0
  764. endfunction
  765. function! s:switch_in()
  766. if !s:plug_window_exists()
  767. return 0
  768. endif
  769. if winbufnr(0) != s:plug_buf
  770. let s:pos = [tabpagenr(), winnr(), winsaveview()]
  771. execute 'normal!' s:plug_tab.'gt'
  772. let winnr = bufwinnr(s:plug_buf)
  773. execute winnr.'wincmd w'
  774. call add(s:pos, winsaveview())
  775. else
  776. let s:pos = [winsaveview()]
  777. endif
  778. setlocal modifiable
  779. return 1
  780. endfunction
  781. function! s:switch_out(...)
  782. call winrestview(s:pos[-1])
  783. setlocal nomodifiable
  784. if a:0 > 0
  785. execute a:1
  786. endif
  787. if len(s:pos) > 1
  788. execute 'normal!' s:pos[0].'gt'
  789. execute s:pos[1] 'wincmd w'
  790. call winrestview(s:pos[2])
  791. endif
  792. endfunction
  793. function! s:finish_bindings()
  794. nnoremap <silent> <buffer> R :call <SID>retry()<cr>
  795. nnoremap <silent> <buffer> D :PlugDiff<cr>
  796. nnoremap <silent> <buffer> S :PlugStatus<cr>
  797. nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
  798. xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
  799. nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
  800. nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
  801. endfunction
  802. function! s:prepare(...)
  803. if empty(s:plug_getcwd())
  804. throw 'Invalid current working directory. Cannot proceed.'
  805. endif
  806. for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
  807. if exists(evar)
  808. throw evar.' detected. Cannot proceed.'
  809. endif
  810. endfor
  811. call s:job_abort(0)
  812. if s:switch_in()
  813. if b:plug_preview == 1
  814. pc
  815. endif
  816. enew
  817. else
  818. call s:new_window()
  819. endif
  820. nnoremap <silent> <buffer> q :call <SID>close_pane()<cr>
  821. if a:0 == 0
  822. call s:finish_bindings()
  823. endif
  824. let b:plug_preview = -1
  825. let s:plug_tab = tabpagenr()
  826. let s:plug_buf = winbufnr(0)
  827. call s:assign_name()
  828. for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
  829. execute 'silent! unmap <buffer>' k
  830. endfor
  831. setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
  832. if exists('+colorcolumn')
  833. setlocal colorcolumn=
  834. endif
  835. setf vim-plug
  836. if exists('g:syntax_on')
  837. call s:syntax()
  838. endif
  839. endfunction
  840. function! s:close_pane()
  841. if b:plug_preview == 1
  842. pc
  843. let b:plug_preview = -1
  844. elseif exists('s:jobs') && !empty(s:jobs)
  845. call s:job_abort(1)
  846. else
  847. bd
  848. endif
  849. endfunction
  850. function! s:assign_name()
  851. " Assign buffer name
  852. let prefix = '[Plugins]'
  853. let name = prefix
  854. let idx = 2
  855. while bufexists(name)
  856. let name = printf('%s (%s)', prefix, idx)
  857. let idx = idx + 1
  858. endwhile
  859. silent! execute 'f' fnameescape(name)
  860. endfunction
  861. function! s:chsh(swap)
  862. let prev = [&shell, &shellcmdflag, &shellredir]
  863. if !s:is_win
  864. set shell=sh
  865. endif
  866. if a:swap
  867. if s:is_powershell(&shell)
  868. let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
  869. elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$'
  870. set shellredir=>%s\ 2>&1
  871. endif
  872. endif
  873. return prev
  874. endfunction
  875. function! s:bang(cmd, ...)
  876. let batchfile = ''
  877. try
  878. let [sh, shellcmdflag, shrd] = s:chsh(a:0)
  879. " FIXME: Escaping is incomplete. We could use shellescape with eval,
  880. " but it won't work on Windows.
  881. let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
  882. if s:is_win
  883. let [batchfile, cmd] = s:batchfile(cmd)
  884. endif
  885. let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
  886. execute "normal! :execute g:_plug_bang\<cr>\<cr>"
  887. finally
  888. unlet g:_plug_bang
  889. let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
  890. if s:is_win && filereadable(batchfile)
  891. call delete(batchfile)
  892. endif
  893. endtry
  894. return v:shell_error ? 'Exit status: ' . v:shell_error : ''
  895. endfunction
  896. function! s:regress_bar()
  897. let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
  898. call s:progress_bar(2, bar, len(bar))
  899. endfunction
  900. function! s:is_updated(dir)
  901. return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
  902. endfunction
  903. function! s:do(pull, force, todo)
  904. if has('nvim')
  905. " Reset &rtp to invalidate Neovim cache of loaded Lua modules
  906. " See https://github.com/junegunn/vim-plug/pull/1157#issuecomment-1809226110
  907. let &rtp = &rtp
  908. endif
  909. for [name, spec] in items(a:todo)
  910. if !isdirectory(spec.dir)
  911. continue
  912. endif
  913. let installed = has_key(s:update.new, name)
  914. let updated = installed ? 0 :
  915. \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
  916. if a:force || installed || updated
  917. execute 'cd' s:esc(spec.dir)
  918. call append(3, '- Post-update hook for '. name .' ... ')
  919. let error = ''
  920. let type = type(spec.do)
  921. if type == s:TYPE.string
  922. if spec.do[0] == ':'
  923. if !get(s:loaded, name, 0)
  924. let s:loaded[name] = 1
  925. call s:reorg_rtp()
  926. endif
  927. call s:load_plugin(spec)
  928. try
  929. execute spec.do[1:]
  930. catch
  931. let error = v:exception
  932. endtry
  933. if !s:plug_window_exists()
  934. cd -
  935. throw 'Warning: vim-plug was terminated by the post-update hook of '.name
  936. endif
  937. else
  938. let error = s:bang(spec.do)
  939. endif
  940. elseif type == s:TYPE.funcref
  941. try
  942. call s:load_plugin(spec)
  943. let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
  944. call spec.do({ 'name': name, 'status': status, 'force': a:force })
  945. catch
  946. let error = v:exception
  947. endtry
  948. else
  949. let error = 'Invalid hook type'
  950. endif
  951. call s:switch_in()
  952. call setline(4, empty(error) ? (getline(4) . 'OK')
  953. \ : ('x' . getline(4)[1:] . error))
  954. if !empty(error)
  955. call add(s:update.errors, name)
  956. call s:regress_bar()
  957. endif
  958. cd -
  959. endif
  960. endfor
  961. endfunction
  962. function! s:hash_match(a, b)
  963. return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
  964. endfunction
  965. function! s:disable_credential_helper()
  966. return s:git_version_requirement(2) && get(g:, 'plug_disable_credential_helper', 1)
  967. endfunction
  968. function! s:checkout(spec)
  969. let sha = a:spec.commit
  970. let output = s:git_revision(a:spec.dir)
  971. let error = 0
  972. if !empty(output) && !s:hash_match(sha, s:lines(output)[0])
  973. let credential_helper = s:disable_credential_helper() ? '-c credential.helper= ' : ''
  974. let output = s:system(
  975. \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
  976. let error = v:shell_error
  977. endif
  978. return [output, error]
  979. endfunction
  980. function! s:finish(pull)
  981. let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
  982. if new_frozen
  983. let s = new_frozen > 1 ? 's' : ''
  984. call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
  985. endif
  986. call append(3, '- Finishing ... ') | 4
  987. redraw
  988. call plug#helptags()
  989. call plug#end()
  990. call setline(4, getline(4) . 'Done!')
  991. redraw
  992. let msgs = []
  993. if !empty(s:update.errors)
  994. call add(msgs, "Press 'R' to retry.")
  995. endif
  996. if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
  997. \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
  998. call add(msgs, "Press 'D' to see the updated changes.")
  999. endif
  1000. echo join(msgs, ' ')
  1001. call s:finish_bindings()
  1002. endfunction
  1003. function! s:retry()
  1004. if empty(s:update.errors)
  1005. return
  1006. endif
  1007. echo
  1008. call s:update_impl(s:update.pull, s:update.force,
  1009. \ extend(copy(s:update.errors), [s:update.threads]))
  1010. endfunction
  1011. function! s:is_managed(name)
  1012. return has_key(g:plugs[a:name], 'uri')
  1013. endfunction
  1014. function! s:names(...)
  1015. return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
  1016. endfunction
  1017. function! s:check_ruby()
  1018. silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
  1019. if !exists('g:plug_ruby')
  1020. redraw!
  1021. return s:warn('echom', 'Warning: Ruby interface is broken')
  1022. endif
  1023. let ruby_version = split(g:plug_ruby, '\.')
  1024. unlet g:plug_ruby
  1025. return s:version_requirement(ruby_version, [1, 8, 7])
  1026. endfunction
  1027. function! s:update_impl(pull, force, args) abort
  1028. let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
  1029. let args = filter(copy(a:args), 'v:val != "--sync"')
  1030. let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
  1031. \ remove(args, -1) : get(g:, 'plug_threads', 16)
  1032. let managed = filter(deepcopy(g:plugs), 's:is_managed(v:key)')
  1033. let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
  1034. \ filter(managed, 'index(args, v:key) >= 0')
  1035. if empty(todo)
  1036. return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
  1037. endif
  1038. if !s:is_win && s:git_version_requirement(2, 3)
  1039. let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
  1040. let $GIT_TERMINAL_PROMPT = 0
  1041. for plug in values(todo)
  1042. let plug.uri = substitute(plug.uri,
  1043. \ '^https://git::@github\.com', 'https://github.com', '')
  1044. endfor
  1045. endif
  1046. if !isdirectory(g:plug_home)
  1047. try
  1048. call mkdir(g:plug_home, 'p')
  1049. catch
  1050. return s:err(printf('Invalid plug directory: %s. '.
  1051. \ 'Try to call plug#begin with a valid directory', g:plug_home))
  1052. endtry
  1053. endif
  1054. if has('nvim') && !exists('*jobwait') && threads > 1
  1055. call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
  1056. endif
  1057. let use_job = s:nvim || s:vim8
  1058. let python = (has('python') || has('python3')) && !use_job
  1059. 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()
  1060. let s:update = {
  1061. \ 'start': reltime(),
  1062. \ 'all': todo,
  1063. \ 'todo': copy(todo),
  1064. \ 'errors': [],
  1065. \ 'pull': a:pull,
  1066. \ 'force': a:force,
  1067. \ 'new': {},
  1068. \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
  1069. \ 'bar': '',
  1070. \ 'fin': 0
  1071. \ }
  1072. call s:prepare(1)
  1073. call append(0, ['', ''])
  1074. normal! 2G
  1075. silent! redraw
  1076. " Set remote name, overriding a possible user git config's clone.defaultRemoteName
  1077. let s:clone_opt = ['--origin', 'origin']
  1078. if get(g:, 'plug_shallow', 1)
  1079. call extend(s:clone_opt, ['--depth', '1'])
  1080. if s:git_version_requirement(1, 7, 10)
  1081. call add(s:clone_opt, '--no-single-branch')
  1082. endif
  1083. endif
  1084. if has('win32unix') || has('wsl')
  1085. call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
  1086. endif
  1087. let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
  1088. " Python version requirement (>= 2.7)
  1089. if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
  1090. redir => pyv
  1091. silent python import platform; print platform.python_version()
  1092. redir END
  1093. let python = s:version_requirement(
  1094. \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
  1095. endif
  1096. if (python || ruby) && s:update.threads > 1
  1097. try
  1098. let imd = &imd
  1099. if s:mac_gui
  1100. set noimd
  1101. endif
  1102. if ruby
  1103. call s:update_ruby()
  1104. else
  1105. call s:update_python()
  1106. endif
  1107. catch
  1108. let lines = getline(4, '$')
  1109. let printed = {}
  1110. silent! 4,$d _
  1111. for line in lines
  1112. let name = s:extract_name(line, '.', '')
  1113. if empty(name) || !has_key(printed, name)
  1114. call append('$', line)
  1115. if !empty(name)
  1116. let printed[name] = 1
  1117. if line[0] == 'x' && index(s:update.errors, name) < 0
  1118. call add(s:update.errors, name)
  1119. end
  1120. endif
  1121. endif
  1122. endfor
  1123. finally
  1124. let &imd = imd
  1125. call s:update_finish()
  1126. endtry
  1127. else
  1128. call s:update_vim()
  1129. while use_job && sync
  1130. sleep 100m
  1131. if s:update.fin
  1132. break
  1133. endif
  1134. endwhile
  1135. endif
  1136. endfunction
  1137. function! s:log4(name, msg)
  1138. call setline(4, printf('- %s (%s)', a:msg, a:name))
  1139. redraw
  1140. endfunction
  1141. function! s:update_finish()
  1142. if exists('s:git_terminal_prompt')
  1143. let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
  1144. endif
  1145. if s:switch_in()
  1146. call append(3, '- Updating ...') | 4
  1147. 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))'))
  1148. let [pos, _] = s:logpos(name)
  1149. if !pos
  1150. continue
  1151. endif
  1152. let out = ''
  1153. let error = 0
  1154. if has_key(spec, 'commit')
  1155. call s:log4(name, 'Checking out '.spec.commit)
  1156. let [out, error] = s:checkout(spec)
  1157. elseif has_key(spec, 'tag')
  1158. let tag = spec.tag
  1159. if tag =~ '\*'
  1160. let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
  1161. if !v:shell_error && !empty(tags)
  1162. let tag = tags[0]
  1163. call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
  1164. call append(3, '')
  1165. endif
  1166. endif
  1167. call s:log4(name, 'Checking out '.tag)
  1168. let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
  1169. let error = v:shell_error
  1170. endif
  1171. if !error && filereadable(spec.dir.'/.gitmodules') &&
  1172. \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
  1173. call s:log4(name, 'Updating submodules. This may take a while.')
  1174. let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
  1175. let error = v:shell_error
  1176. endif
  1177. let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
  1178. if error
  1179. call add(s:update.errors, name)
  1180. call s:regress_bar()
  1181. silent execute pos 'd _'
  1182. call append(4, msg) | 4
  1183. elseif !empty(out)
  1184. call setline(pos, msg[0])
  1185. endif
  1186. redraw
  1187. endfor
  1188. silent 4 d _
  1189. try
  1190. 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")'))
  1191. catch
  1192. call s:warn('echom', v:exception)
  1193. call s:warn('echo', '')
  1194. return
  1195. endtry
  1196. call s:finish(s:update.pull)
  1197. call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
  1198. call s:switch_out('normal! gg')
  1199. endif
  1200. endfunction
  1201. function! s:mark_aborted(name, message)
  1202. let attrs = { 'running': 0, 'error': 1, 'abort': 1, 'lines': [a:message] }
  1203. let s:jobs[a:name] = extend(get(s:jobs, a:name, {}), attrs)
  1204. endfunction
  1205. function! s:job_abort(cancel)
  1206. if (!s:nvim && !s:vim8) || !exists('s:jobs')
  1207. return
  1208. endif
  1209. for [name, j] in items(s:jobs)
  1210. if s:nvim
  1211. silent! call jobstop(j.jobid)
  1212. elseif s:vim8
  1213. silent! call job_stop(j.jobid)
  1214. endif
  1215. if j.new
  1216. call s:rm_rf(g:plugs[name].dir)
  1217. endif
  1218. if a:cancel
  1219. call s:mark_aborted(name, 'Aborted')
  1220. endif
  1221. endfor
  1222. if a:cancel
  1223. for todo in values(s:update.todo)
  1224. let todo.abort = 1
  1225. endfor
  1226. else
  1227. let s:jobs = {}
  1228. endif
  1229. endfunction
  1230. function! s:last_non_empty_line(lines)
  1231. let len = len(a:lines)
  1232. for idx in range(len)
  1233. let line = a:lines[len-idx-1]
  1234. if !empty(line)
  1235. return line
  1236. endif
  1237. endfor
  1238. return ''
  1239. endfunction
  1240. function! s:bullet_for(job, ...)
  1241. if a:job.running
  1242. return a:job.new ? '+' : '*'
  1243. endif
  1244. if get(a:job, 'abort', 0)
  1245. return '~'
  1246. endif
  1247. return a:job.error ? 'x' : get(a:000, 0, '-')
  1248. endfunction
  1249. function! s:job_out_cb(self, data) abort
  1250. let self = a:self
  1251. let data = remove(self.lines, -1) . a:data
  1252. let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
  1253. call extend(self.lines, lines)
  1254. " To reduce the number of buffer updates
  1255. let self.tick = get(self, 'tick', -1) + 1
  1256. if !self.running || self.tick % len(s:jobs) == 0
  1257. let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
  1258. if len(result)
  1259. call s:log(s:bullet_for(self), self.name, result)
  1260. endif
  1261. endif
  1262. endfunction
  1263. function! s:job_exit_cb(self, data) abort
  1264. let a:self.running = 0
  1265. let a:self.error = a:data != 0
  1266. call s:reap(a:self.name)
  1267. call s:tick()
  1268. endfunction
  1269. function! s:job_cb(fn, job, ch, data)
  1270. if !s:plug_window_exists() " plug window closed
  1271. return s:job_abort(0)
  1272. endif
  1273. call call(a:fn, [a:job, a:data])
  1274. endfunction
  1275. function! s:nvim_cb(job_id, data, event) dict abort
  1276. return (a:event == 'stdout' || a:event == 'stderr') ?
  1277. \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
  1278. \ s:job_cb('s:job_exit_cb', self, 0, a:data)
  1279. endfunction
  1280. function! s:spawn(name, spec, queue, opts)
  1281. let job = { 'name': a:name, 'spec': a:spec, 'running': 1, 'error': 0, 'lines': [''],
  1282. \ 'new': get(a:opts, 'new', 0), 'queue': copy(a:queue) }
  1283. let Item = remove(job.queue, 0)
  1284. let argv = type(Item) == s:TYPE.funcref ? call(Item, [a:spec]) : Item
  1285. let s:jobs[a:name] = job
  1286. if s:nvim
  1287. if has_key(a:opts, 'dir')
  1288. let job.cwd = a:opts.dir
  1289. endif
  1290. call extend(job, {
  1291. \ 'on_stdout': function('s:nvim_cb'),
  1292. \ 'on_stderr': function('s:nvim_cb'),
  1293. \ 'on_exit': function('s:nvim_cb'),
  1294. \ })
  1295. let jid = s:plug_call('jobstart', argv, job)
  1296. if jid > 0
  1297. let job.jobid = jid
  1298. else
  1299. let job.running = 0
  1300. let job.error = 1
  1301. let job.lines = [jid < 0 ? argv[0].' is not executable' :
  1302. \ 'Invalid arguments (or job table is full)']
  1303. endif
  1304. elseif s:vim8
  1305. let cmd = join(map(copy(argv), 'plug#shellescape(v:val, {"script": 0})'))
  1306. if has_key(a:opts, 'dir')
  1307. let cmd = s:with_cd(cmd, a:opts.dir, 0)
  1308. endif
  1309. let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
  1310. let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
  1311. \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
  1312. \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
  1313. \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
  1314. \ 'err_mode': 'raw',
  1315. \ 'out_mode': 'raw'
  1316. \})
  1317. if job_status(jid) == 'run'
  1318. let job.jobid = jid
  1319. else
  1320. let job.running = 0
  1321. let job.error = 1
  1322. let job.lines = ['Failed to start job']
  1323. endif
  1324. else
  1325. let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [argv, a:opts.dir] : [argv]))
  1326. let job.error = v:shell_error != 0
  1327. let job.running = 0
  1328. endif
  1329. endfunction
  1330. function! s:reap(name)
  1331. let job = remove(s:jobs, a:name)
  1332. if job.error
  1333. call add(s:update.errors, a:name)
  1334. elseif get(job, 'new', 0)
  1335. let s:update.new[a:name] = 1
  1336. endif
  1337. let more = len(get(job, 'queue', []))
  1338. let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
  1339. if len(result)
  1340. call s:log(s:bullet_for(job), a:name, result)
  1341. endif
  1342. if !job.error && more
  1343. let job.spec.queue = job.queue
  1344. let s:update.todo[a:name] = job.spec
  1345. else
  1346. let s:update.bar .= s:bullet_for(job, '=')
  1347. call s:bar()
  1348. endif
  1349. endfunction
  1350. function! s:bar()
  1351. if s:switch_in()
  1352. let total = len(s:update.all)
  1353. call setline(1, (s:update.pull ? 'Updating' : 'Installing').
  1354. \ ' plugins ('.len(s:update.bar).'/'.total.')')
  1355. call s:progress_bar(2, s:update.bar, total)
  1356. call s:switch_out()
  1357. endif
  1358. endfunction
  1359. function! s:logpos(name)
  1360. let max = line('$')
  1361. for i in range(4, max > 4 ? max : 4)
  1362. if getline(i) =~# '^[-+x*] '.a:name.':'
  1363. for j in range(i + 1, max > 5 ? max : 5)
  1364. if getline(j) !~ '^ '
  1365. return [i, j - 1]
  1366. endif
  1367. endfor
  1368. return [i, i]
  1369. endif
  1370. endfor
  1371. return [0, 0]
  1372. endfunction
  1373. function! s:log(bullet, name, lines)
  1374. if s:switch_in()
  1375. let [b, e] = s:logpos(a:name)
  1376. if b > 0
  1377. silent execute printf('%d,%d d _', b, e)
  1378. if b > winheight('.')
  1379. let b = 4
  1380. endif
  1381. else
  1382. let b = 4
  1383. endif
  1384. " FIXME For some reason, nomodifiable is set after :d in vim8
  1385. setlocal modifiable
  1386. call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
  1387. call s:switch_out()
  1388. endif
  1389. endfunction
  1390. function! s:update_vim()
  1391. let s:jobs = {}
  1392. call s:bar()
  1393. call s:tick()
  1394. endfunction
  1395. function! s:checkout_command(spec)
  1396. let a:spec.branch = s:git_origin_branch(a:spec)
  1397. return ['git', 'checkout', '-q', a:spec.branch, '--']
  1398. endfunction
  1399. function! s:merge_command(spec)
  1400. let a:spec.branch = s:git_origin_branch(a:spec)
  1401. return ['git', 'merge', '--ff-only', 'origin/'.a:spec.branch]
  1402. endfunction
  1403. function! s:tick()
  1404. let pull = s:update.pull
  1405. let prog = s:progress_opt(s:nvim || s:vim8)
  1406. while 1 " Without TCO, Vim stack is bound to explode
  1407. if empty(s:update.todo)
  1408. if empty(s:jobs) && !s:update.fin
  1409. call s:update_finish()
  1410. let s:update.fin = 1
  1411. endif
  1412. return
  1413. endif
  1414. let name = keys(s:update.todo)[0]
  1415. let spec = remove(s:update.todo, name)
  1416. if get(spec, 'abort', 0)
  1417. call s:mark_aborted(name, 'Skipped')
  1418. call s:reap(name)
  1419. continue
  1420. endif
  1421. let queue = get(spec, 'queue', [])
  1422. let new = empty(globpath(spec.dir, '.git', 1))
  1423. if empty(queue)
  1424. call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
  1425. redraw
  1426. endif
  1427. let has_tag = has_key(spec, 'tag')
  1428. if len(queue)
  1429. call s:spawn(name, spec, queue, { 'dir': spec.dir })
  1430. elseif !new
  1431. let [error, _] = s:git_validate(spec, 0)
  1432. if empty(error)
  1433. if pull
  1434. let cmd = s:disable_credential_helper() ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
  1435. if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
  1436. call extend(cmd, ['--depth', '99999999'])
  1437. endif
  1438. if !empty(prog)
  1439. call add(cmd, prog)
  1440. endif
  1441. let queue = [cmd, split('git remote set-head origin -a')]
  1442. if !has_tag && !has_key(spec, 'commit')
  1443. call extend(queue, [function('s:checkout_command'), function('s:merge_command')])
  1444. endif
  1445. call s:spawn(name, spec, queue, { 'dir': spec.dir })
  1446. else
  1447. let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
  1448. endif
  1449. else
  1450. let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
  1451. endif
  1452. else
  1453. let cmd = ['git', 'clone']
  1454. if !has_tag
  1455. call extend(cmd, s:clone_opt)
  1456. endif
  1457. if !empty(prog)
  1458. call add(cmd, prog)
  1459. endif
  1460. call s:spawn(name, spec, [extend(cmd, [spec.uri, s:trim(spec.dir)]), function('s:checkout_command'), function('s:merge_command')], { 'new': 1 })
  1461. endif
  1462. if !s:jobs[name].running
  1463. call s:reap(name)
  1464. endif
  1465. if len(s:jobs) >= s:update.threads
  1466. break
  1467. endif
  1468. endwhile
  1469. endfunction
  1470. function! s:update_python()
  1471. let py_exe = has('python') ? 'python' : 'python3'
  1472. execute py_exe "<< EOF"
  1473. import datetime
  1474. import functools
  1475. import os
  1476. try:
  1477. import queue
  1478. except ImportError:
  1479. import Queue as queue
  1480. import random
  1481. import re
  1482. import shutil
  1483. import signal
  1484. import subprocess
  1485. import tempfile
  1486. import threading as thr
  1487. import time
  1488. import traceback
  1489. import vim
  1490. G_NVIM = vim.eval("has('nvim')") == '1'
  1491. G_PULL = vim.eval('s:update.pull') == '1'
  1492. G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
  1493. G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
  1494. G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
  1495. G_PROGRESS = vim.eval('s:progress_opt(1)')
  1496. G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
  1497. G_STOP = thr.Event()
  1498. G_IS_WIN = vim.eval('s:is_win') == '1'
  1499. class PlugError(Exception):
  1500. def __init__(self, msg):
  1501. self.msg = msg
  1502. class CmdTimedOut(PlugError):
  1503. pass
  1504. class CmdFailed(PlugError):
  1505. pass
  1506. class InvalidURI(PlugError):
  1507. pass
  1508. class Action(object):
  1509. INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
  1510. class Buffer(object):
  1511. def __init__(self, lock, num_plugs, is_pull):
  1512. self.bar = ''
  1513. self.event = 'Updating' if is_pull else 'Installing'
  1514. self.lock = lock
  1515. self.maxy = int(vim.eval('winheight(".")'))
  1516. self.num_plugs = num_plugs
  1517. def __where(self, name):
  1518. """ Find first line with name in current buffer. Return line num. """
  1519. found, lnum = False, 0
  1520. matcher = re.compile('^[-+x*] {0}:'.format(name))
  1521. for line in vim.current.buffer:
  1522. if matcher.search(line) is not None:
  1523. found = True
  1524. break
  1525. lnum += 1
  1526. if not found:
  1527. lnum = -1
  1528. return lnum
  1529. def header(self):
  1530. curbuf = vim.current.buffer
  1531. curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
  1532. num_spaces = self.num_plugs - len(self.bar)
  1533. curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
  1534. with self.lock:
  1535. vim.command('normal! 2G')
  1536. vim.command('redraw')
  1537. def write(self, action, name, lines):
  1538. first, rest = lines[0], lines[1:]
  1539. msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
  1540. msg.extend([' ' + line for line in rest])
  1541. try:
  1542. if action == Action.ERROR:
  1543. self.bar += 'x'
  1544. vim.command("call add(s:update.errors, '{0}')".format(name))
  1545. elif action == Action.DONE:
  1546. self.bar += '='
  1547. curbuf = vim.current.buffer
  1548. lnum = self.__where(name)
  1549. if lnum != -1: # Found matching line num
  1550. del curbuf[lnum]
  1551. if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
  1552. lnum = 3
  1553. else:
  1554. lnum = 3
  1555. curbuf.append(msg, lnum)
  1556. self.header()
  1557. except vim.error:
  1558. pass
  1559. class Command(object):
  1560. CD = 'cd /d' if G_IS_WIN else 'cd'
  1561. def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
  1562. self.cmd = cmd
  1563. if cmd_dir:
  1564. self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
  1565. self.timeout = timeout
  1566. self.callback = cb if cb else (lambda msg: None)
  1567. self.clean = clean if clean else (lambda: None)
  1568. self.proc = None
  1569. @property
  1570. def alive(self):
  1571. """ Returns true only if command still running. """
  1572. return self.proc and self.proc.poll() is None
  1573. def execute(self, ntries=3):
  1574. """ Execute the command with ntries if CmdTimedOut.
  1575. Returns the output of the command if no Exception.
  1576. """
  1577. attempt, finished, limit = 0, False, self.timeout
  1578. while not finished:
  1579. try:
  1580. attempt += 1
  1581. result = self.try_command()
  1582. finished = True
  1583. return result
  1584. except CmdTimedOut:
  1585. if attempt != ntries:
  1586. self.notify_retry()
  1587. self.timeout += limit
  1588. else:
  1589. raise
  1590. def notify_retry(self):
  1591. """ Retry required for command, notify user. """
  1592. for count in range(3, 0, -1):
  1593. if G_STOP.is_set():
  1594. raise KeyboardInterrupt
  1595. msg = 'Timeout. Will retry in {0} second{1} ...'.format(
  1596. count, 's' if count != 1 else '')
  1597. self.callback([msg])
  1598. time.sleep(1)
  1599. self.callback(['Retrying ...'])
  1600. def try_command(self):
  1601. """ Execute a cmd & poll for callback. Returns list of output.
  1602. Raises CmdFailed -> return code for Popen isn't 0
  1603. Raises CmdTimedOut -> command exceeded timeout without new output
  1604. """
  1605. first_line = True
  1606. try:
  1607. tfile = tempfile.NamedTemporaryFile(mode='w+b')
  1608. preexec_fn = not G_IS_WIN and os.setsid or None
  1609. self.proc = subprocess.Popen(self.cmd, stdout=tfile,
  1610. stderr=subprocess.STDOUT,
  1611. stdin=subprocess.PIPE, shell=True,
  1612. preexec_fn=preexec_fn)
  1613. thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
  1614. thrd.start()
  1615. thread_not_started = True
  1616. while thread_not_started:
  1617. try:
  1618. thrd.join(0.1)
  1619. thread_not_started = False
  1620. except RuntimeError:
  1621. pass
  1622. while self.alive:
  1623. if G_STOP.is_set():
  1624. raise KeyboardInterrupt
  1625. if first_line or random.random() < G_LOG_PROB:
  1626. first_line = False
  1627. line = '' if G_IS_WIN else nonblock_read(tfile.name)
  1628. if line:
  1629. self.callback([line])
  1630. time_diff = time.time() - os.path.getmtime(tfile.name)
  1631. if time_diff > self.timeout:
  1632. raise CmdTimedOut(['Timeout!'])
  1633. thrd.join(0.5)
  1634. tfile.seek(0)
  1635. result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
  1636. if self.proc.returncode != 0:
  1637. raise CmdFailed([''] + result)
  1638. return result
  1639. except:
  1640. self.terminate()
  1641. raise
  1642. def terminate(self):
  1643. """ Terminate process and cleanup. """
  1644. if self.alive:
  1645. if G_IS_WIN:
  1646. os.kill(self.proc.pid, signal.SIGINT)
  1647. else:
  1648. os.killpg(self.proc.pid, signal.SIGTERM)
  1649. self.clean()
  1650. class Plugin(object):
  1651. def __init__(self, name, args, buf_q, lock):
  1652. self.name = name
  1653. self.args = args
  1654. self.buf_q = buf_q
  1655. self.lock = lock
  1656. self.tag = args.get('tag', 0)
  1657. def manage(self):
  1658. try:
  1659. if os.path.exists(self.args['dir']):
  1660. self.update()
  1661. else:
  1662. self.install()
  1663. with self.lock:
  1664. thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
  1665. except PlugError as exc:
  1666. self.write(Action.ERROR, self.name, exc.msg)
  1667. except KeyboardInterrupt:
  1668. G_STOP.set()
  1669. self.write(Action.ERROR, self.name, ['Interrupted!'])
  1670. except:
  1671. # Any exception except those above print stack trace
  1672. msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
  1673. self.write(Action.ERROR, self.name, msg.split('\n'))
  1674. raise
  1675. def install(self):
  1676. target = self.args['dir']
  1677. if target[-1] == '\\':
  1678. target = target[0:-1]
  1679. def clean(target):
  1680. def _clean():
  1681. try:
  1682. shutil.rmtree(target)
  1683. except OSError:
  1684. pass
  1685. return _clean
  1686. self.write(Action.INSTALL, self.name, ['Installing ...'])
  1687. callback = functools.partial(self.write, Action.INSTALL, self.name)
  1688. cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
  1689. '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
  1690. esc(target))
  1691. com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
  1692. result = com.execute(G_RETRIES)
  1693. self.write(Action.DONE, self.name, result[-1:])
  1694. def repo_uri(self):
  1695. cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
  1696. command = Command(cmd, self.args['dir'], G_TIMEOUT,)
  1697. result = command.execute(G_RETRIES)
  1698. return result[-1]
  1699. def update(self):
  1700. actual_uri = self.repo_uri()
  1701. expect_uri = self.args['uri']
  1702. regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
  1703. ma = regex.match(actual_uri)
  1704. mb = regex.match(expect_uri)
  1705. if ma is None or mb is None or ma.groups() != mb.groups():
  1706. msg = ['',
  1707. 'Invalid URI: {0}'.format(actual_uri),
  1708. 'Expected {0}'.format(expect_uri),
  1709. 'PlugClean required.']
  1710. raise InvalidURI(msg)
  1711. if G_PULL:
  1712. self.write(Action.UPDATE, self.name, ['Updating ...'])
  1713. callback = functools.partial(self.write, Action.UPDATE, self.name)
  1714. fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
  1715. cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
  1716. com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
  1717. result = com.execute(G_RETRIES)
  1718. self.write(Action.DONE, self.name, result[-1:])
  1719. else:
  1720. self.write(Action.DONE, self.name, ['Already installed'])
  1721. def write(self, action, name, msg):
  1722. self.buf_q.put((action, name, msg))
  1723. class PlugThread(thr.Thread):
  1724. def __init__(self, tname, args):
  1725. super(PlugThread, self).__init__()
  1726. self.tname = tname
  1727. self.args = args
  1728. def run(self):
  1729. thr.current_thread().name = self.tname
  1730. buf_q, work_q, lock = self.args
  1731. try:
  1732. while not G_STOP.is_set():
  1733. name, args = work_q.get_nowait()
  1734. plug = Plugin(name, args, buf_q, lock)
  1735. plug.manage()
  1736. work_q.task_done()
  1737. except queue.Empty:
  1738. pass
  1739. class RefreshThread(thr.Thread):
  1740. def __init__(self, lock):
  1741. super(RefreshThread, self).__init__()
  1742. self.lock = lock
  1743. self.running = True
  1744. def run(self):
  1745. while self.running:
  1746. with self.lock:
  1747. thread_vim_command('noautocmd normal! a')
  1748. time.sleep(0.33)
  1749. def stop(self):
  1750. self.running = False
  1751. if G_NVIM:
  1752. def thread_vim_command(cmd):
  1753. vim.session.threadsafe_call(lambda: vim.command(cmd))
  1754. else:
  1755. def thread_vim_command(cmd):
  1756. vim.command(cmd)
  1757. def esc(name):
  1758. return '"' + name.replace('"', '\"') + '"'
  1759. def nonblock_read(fname):
  1760. """ Read a file with nonblock flag. Return the last line. """
  1761. fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
  1762. buf = os.read(fread, 100000).decode('utf-8', 'replace')
  1763. os.close(fread)
  1764. line = buf.rstrip('\r\n')
  1765. left = max(line.rfind('\r'), line.rfind('\n'))
  1766. if left != -1:
  1767. left += 1
  1768. line = line[left:]
  1769. return line
  1770. def main():
  1771. thr.current_thread().name = 'main'
  1772. nthreads = int(vim.eval('s:update.threads'))
  1773. plugs = vim.eval('s:update.todo')
  1774. mac_gui = vim.eval('s:mac_gui') == '1'
  1775. lock = thr.Lock()
  1776. buf = Buffer(lock, len(plugs), G_PULL)
  1777. buf_q, work_q = queue.Queue(), queue.Queue()
  1778. for work in plugs.items():
  1779. work_q.put(work)
  1780. start_cnt = thr.active_count()
  1781. for num in range(nthreads):
  1782. tname = 'PlugT-{0:02}'.format(num)
  1783. thread = PlugThread(tname, (buf_q, work_q, lock))
  1784. thread.start()
  1785. if mac_gui:
  1786. rthread = RefreshThread(lock)
  1787. rthread.start()
  1788. while not buf_q.empty() or thr.active_count() != start_cnt:
  1789. try:
  1790. action, name, msg = buf_q.get(True, 0.25)
  1791. buf.write(action, name, ['OK'] if not msg else msg)
  1792. buf_q.task_done()
  1793. except queue.Empty:
  1794. pass
  1795. except KeyboardInterrupt:
  1796. G_STOP.set()
  1797. if mac_gui:
  1798. rthread.stop()
  1799. rthread.join()
  1800. main()
  1801. EOF
  1802. endfunction
  1803. function! s:update_ruby()
  1804. ruby << EOF
  1805. module PlugStream
  1806. SEP = ["\r", "\n", nil]
  1807. def get_line
  1808. buffer = ''
  1809. loop do
  1810. char = readchar rescue return
  1811. if SEP.include? char.chr
  1812. buffer << $/
  1813. break
  1814. else
  1815. buffer << char
  1816. end
  1817. end
  1818. buffer
  1819. end
  1820. end unless defined?(PlugStream)
  1821. def esc arg
  1822. %["#{arg.gsub('"', '\"')}"]
  1823. end
  1824. def killall pid
  1825. pids = [pid]
  1826. if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
  1827. pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
  1828. else
  1829. unless `which pgrep 2> /dev/null`.empty?
  1830. children = pids
  1831. until children.empty?
  1832. children = children.map { |pid|
  1833. `pgrep -P #{pid}`.lines.map { |l| l.chomp }
  1834. }.flatten
  1835. pids += children
  1836. end
  1837. end
  1838. pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
  1839. end
  1840. end
  1841. def compare_git_uri a, b
  1842. regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
  1843. regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
  1844. end
  1845. require 'thread'
  1846. require 'fileutils'
  1847. require 'timeout'
  1848. running = true
  1849. iswin = VIM::evaluate('s:is_win').to_i == 1
  1850. pull = VIM::evaluate('s:update.pull').to_i == 1
  1851. base = VIM::evaluate('g:plug_home')
  1852. all = VIM::evaluate('s:update.todo')
  1853. limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
  1854. tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
  1855. nthr = VIM::evaluate('s:update.threads').to_i
  1856. maxy = VIM::evaluate('winheight(".")').to_i
  1857. vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
  1858. cd = iswin ? 'cd /d' : 'cd'
  1859. tot = VIM::evaluate('len(s:update.todo)') || 0
  1860. bar = ''
  1861. skip = 'Already installed'
  1862. mtx = Mutex.new
  1863. take1 = proc { mtx.synchronize { running && all.shift } }
  1864. logh = proc {
  1865. cnt = bar.length
  1866. $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
  1867. $curbuf[2] = '[' + bar.ljust(tot) + ']'
  1868. VIM::command('normal! 2G')
  1869. VIM::command('redraw')
  1870. }
  1871. where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
  1872. log = proc { |name, result, type|
  1873. mtx.synchronize do
  1874. ing = ![true, false].include?(type)
  1875. bar += type ? '=' : 'x' unless ing
  1876. b = case type
  1877. when :install then '+' when :update then '*'
  1878. when true, nil then '-' else
  1879. VIM::command("call add(s:update.errors, '#{name}')")
  1880. 'x'
  1881. end
  1882. result =
  1883. if type || type.nil?
  1884. ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
  1885. elsif result =~ /^Interrupted|^Timeout/
  1886. ["#{b} #{name}: #{result}"]
  1887. else
  1888. ["#{b} #{name}"] + result.lines.map { |l| " " << l }
  1889. end
  1890. if lnum = where.call(name)
  1891. $curbuf.delete lnum
  1892. lnum = 4 if ing && lnum > maxy
  1893. end
  1894. result.each_with_index do |line, offset|
  1895. $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
  1896. end
  1897. logh.call
  1898. end
  1899. }
  1900. bt = proc { |cmd, name, type, cleanup|
  1901. tried = timeout = 0
  1902. begin
  1903. tried += 1
  1904. timeout += limit
  1905. fd = nil
  1906. data = ''
  1907. if iswin
  1908. Timeout::timeout(timeout) do
  1909. tmp = VIM::evaluate('tempname()')
  1910. system("(#{cmd}) > #{tmp}")
  1911. data = File.read(tmp).chomp
  1912. File.unlink tmp rescue nil
  1913. end
  1914. else
  1915. fd = IO.popen(cmd).extend(PlugStream)
  1916. first_line = true
  1917. log_prob = 1.0 / nthr
  1918. while line = Timeout::timeout(timeout) { fd.get_line }
  1919. data << line
  1920. log.call name, line.chomp, type if name && (first_line || rand < log_prob)
  1921. first_line = false
  1922. end
  1923. fd.close
  1924. end
  1925. [$? == 0, data.chomp]
  1926. rescue Timeout::Error, Interrupt => e
  1927. if fd && !fd.closed?
  1928. killall fd.pid
  1929. fd.close
  1930. end
  1931. cleanup.call if cleanup
  1932. if e.is_a?(Timeout::Error) && tried < tries
  1933. 3.downto(1) do |countdown|
  1934. s = countdown > 1 ? 's' : ''
  1935. log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
  1936. sleep 1
  1937. end
  1938. log.call name, 'Retrying ...', type
  1939. retry
  1940. end
  1941. [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
  1942. end
  1943. }
  1944. main = Thread.current
  1945. threads = []
  1946. watcher = Thread.new {
  1947. if vim7
  1948. while VIM::evaluate('getchar(1)')
  1949. sleep 0.1
  1950. end
  1951. else
  1952. require 'io/console' # >= Ruby 1.9
  1953. nil until IO.console.getch == 3.chr
  1954. end
  1955. mtx.synchronize do
  1956. running = false
  1957. threads.each { |t| t.raise Interrupt } unless vim7
  1958. end
  1959. threads.each { |t| t.join rescue nil }
  1960. main.kill
  1961. }
  1962. refresh = Thread.new {
  1963. while true
  1964. mtx.synchronize do
  1965. break unless running
  1966. VIM::command('noautocmd normal! a')
  1967. end
  1968. sleep 0.2
  1969. end
  1970. } if VIM::evaluate('s:mac_gui') == 1
  1971. clone_opt = VIM::evaluate('s:clone_opt').join(' ')
  1972. progress = VIM::evaluate('s:progress_opt(1)')
  1973. nthr.times do
  1974. mtx.synchronize do
  1975. threads << Thread.new {
  1976. while pair = take1.call
  1977. name = pair.first
  1978. dir, uri, tag = pair.last.values_at *%w[dir uri tag]
  1979. exists = File.directory? dir
  1980. ok, result =
  1981. if exists
  1982. chdir = "#{cd} #{iswin ? dir : esc(dir)}"
  1983. ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
  1984. current_uri = data.lines.to_a.last
  1985. if !ret
  1986. if data =~ /^Interrupted|^Timeout/
  1987. [false, data]
  1988. else
  1989. [false, [data.chomp, "PlugClean required."].join($/)]
  1990. end
  1991. elsif !compare_git_uri(current_uri, uri)
  1992. [false, ["Invalid URI: #{current_uri}",
  1993. "Expected: #{uri}",
  1994. "PlugClean required."].join($/)]
  1995. else
  1996. if pull
  1997. log.call name, 'Updating ...', :update
  1998. fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
  1999. bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
  2000. else
  2001. [true, skip]
  2002. end
  2003. end
  2004. else
  2005. d = esc dir.sub(%r{[\\/]+$}, '')
  2006. log.call name, 'Installing ...', :install
  2007. bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
  2008. FileUtils.rm_rf dir
  2009. }
  2010. end
  2011. mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
  2012. log.call name, result, ok
  2013. end
  2014. } if running
  2015. end
  2016. end
  2017. threads.each { |t| t.join rescue nil }
  2018. logh.call
  2019. refresh.kill if refresh
  2020. watcher.kill
  2021. EOF
  2022. endfunction
  2023. function! s:shellesc_cmd(arg, script)
  2024. let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
  2025. return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
  2026. endfunction
  2027. function! s:shellesc_ps1(arg)
  2028. return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
  2029. endfunction
  2030. function! s:shellesc_sh(arg)
  2031. return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
  2032. endfunction
  2033. " Escape the shell argument based on the shell.
  2034. " Vim and Neovim's shellescape() are insufficient.
  2035. " 1. shellslash determines whether to use single/double quotes.
  2036. " Double-quote escaping is fragile for cmd.exe.
  2037. " 2. It does not work for powershell.
  2038. " 3. It does not work for *sh shells if the command is executed
  2039. " via cmd.exe (ie. cmd.exe /c sh -c command command_args)
  2040. " 4. It does not support batchfile syntax.
  2041. "
  2042. " Accepts an optional dictionary with the following keys:
  2043. " - shell: same as Vim/Neovim 'shell' option.
  2044. " If unset, fallback to 'cmd.exe' on Windows or 'sh'.
  2045. " - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
  2046. function! plug#shellescape(arg, ...)
  2047. if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
  2048. return a:arg
  2049. endif
  2050. let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
  2051. let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
  2052. let script = get(opts, 'script', 1)
  2053. if shell =~# 'cmd\(\.exe\)\?$'
  2054. return s:shellesc_cmd(a:arg, script)
  2055. elseif s:is_powershell(shell)
  2056. return s:shellesc_ps1(a:arg)
  2057. endif
  2058. return s:shellesc_sh(a:arg)
  2059. endfunction
  2060. function! s:glob_dir(path)
  2061. return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
  2062. endfunction
  2063. function! s:progress_bar(line, bar, total)
  2064. call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
  2065. endfunction
  2066. function! s:compare_git_uri(a, b)
  2067. " See `git help clone'
  2068. " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
  2069. " [git@] github.com[:port] : junegunn/vim-plug [.git]
  2070. " file:// / junegunn/vim-plug [/]
  2071. " / junegunn/vim-plug [/]
  2072. let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
  2073. let ma = matchlist(a:a, pat)
  2074. let mb = matchlist(a:b, pat)
  2075. return ma[1:2] ==# mb[1:2]
  2076. endfunction
  2077. function! s:format_message(bullet, name, message)
  2078. if a:bullet != 'x'
  2079. return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
  2080. else
  2081. let lines = map(s:lines(a:message), '" ".v:val')
  2082. return extend([printf('x %s:', a:name)], lines)
  2083. endif
  2084. endfunction
  2085. function! s:with_cd(cmd, dir, ...)
  2086. let script = a:0 > 0 ? a:1 : 1
  2087. let pwsh = s:is_powershell(&shell)
  2088. let cd = s:is_win && !pwsh ? 'cd /d' : 'cd'
  2089. let sep = pwsh ? ';' : '&&'
  2090. return printf('%s %s %s %s', cd, plug#shellescape(a:dir, {'script': script, 'shell': &shell}), sep, a:cmd)
  2091. endfunction
  2092. function! s:system(cmd, ...)
  2093. let batchfile = ''
  2094. try
  2095. let [sh, shellcmdflag, shrd] = s:chsh(1)
  2096. if type(a:cmd) == s:TYPE.list
  2097. " Neovim's system() supports list argument to bypass the shell
  2098. " but it cannot set the working directory for the command.
  2099. " Assume that the command does not rely on the shell.
  2100. if has('nvim') && a:0 == 0
  2101. return system(a:cmd)
  2102. endif
  2103. let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
  2104. if s:is_powershell(&shell)
  2105. let cmd = '& ' . cmd
  2106. endif
  2107. else
  2108. let cmd = a:cmd
  2109. endif
  2110. if a:0 > 0
  2111. let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
  2112. endif
  2113. if s:is_win && type(a:cmd) != s:TYPE.list
  2114. let [batchfile, cmd] = s:batchfile(cmd)
  2115. endif
  2116. return system(cmd)
  2117. finally
  2118. let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
  2119. if s:is_win && filereadable(batchfile)
  2120. call delete(batchfile)
  2121. endif
  2122. endtry
  2123. endfunction
  2124. function! s:system_chomp(...)
  2125. let ret = call('s:system', a:000)
  2126. return v:shell_error ? '' : substitute(ret, '\n$', '', '')
  2127. endfunction
  2128. function! s:git_validate(spec, check_branch)
  2129. let err = ''
  2130. if isdirectory(a:spec.dir)
  2131. let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
  2132. let remote = result[-1]
  2133. if empty(remote)
  2134. let err = join([remote, 'PlugClean required.'], "\n")
  2135. elseif !s:compare_git_uri(remote, a:spec.uri)
  2136. let err = join(['Invalid URI: '.remote,
  2137. \ 'Expected: '.a:spec.uri,
  2138. \ 'PlugClean required.'], "\n")
  2139. elseif a:check_branch && has_key(a:spec, 'commit')
  2140. let sha = s:git_revision(a:spec.dir)
  2141. if empty(sha)
  2142. let err = join(add(result, 'PlugClean required.'), "\n")
  2143. elseif !s:hash_match(sha, a:spec.commit)
  2144. let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
  2145. \ a:spec.commit[:6], sha[:6]),
  2146. \ 'PlugUpdate required.'], "\n")
  2147. endif
  2148. elseif a:check_branch
  2149. let current_branch = result[0]
  2150. " Check tag
  2151. let origin_branch = s:git_origin_branch(a:spec)
  2152. if has_key(a:spec, 'tag')
  2153. let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
  2154. if a:spec.tag !=# tag && a:spec.tag !~ '\*'
  2155. let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
  2156. \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
  2157. endif
  2158. " Check branch
  2159. elseif origin_branch !=# current_branch
  2160. let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
  2161. \ current_branch, origin_branch)
  2162. endif
  2163. if empty(err)
  2164. let ahead_behind = split(s:lastline(s:system([
  2165. \ 'git', 'rev-list', '--count', '--left-right',
  2166. \ printf('HEAD...origin/%s', origin_branch)
  2167. \ ], a:spec.dir)), '\t')
  2168. if v:shell_error || len(ahead_behind) != 2
  2169. let err = "Failed to compare with the origin. The default branch might have changed.\nPlugClean required."
  2170. else
  2171. let [ahead, behind] = ahead_behind
  2172. if ahead && behind
  2173. " Only mention PlugClean if diverged, otherwise it's likely to be
  2174. " pushable (and probably not that messed up).
  2175. let err = printf(
  2176. \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
  2177. \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
  2178. elseif ahead
  2179. let err = printf("Ahead of origin/%s by %d commit(s).\n"
  2180. \ .'Cannot update until local changes are pushed.',
  2181. \ origin_branch, ahead)
  2182. endif
  2183. endif
  2184. endif
  2185. endif
  2186. else
  2187. let err = 'Not found'
  2188. endif
  2189. return [err, err =~# 'PlugClean']
  2190. endfunction
  2191. function! s:rm_rf(dir)
  2192. if isdirectory(a:dir)
  2193. return s:system(s:is_win
  2194. \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
  2195. \ : ['rm', '-rf', a:dir])
  2196. endif
  2197. endfunction
  2198. function! s:clean(force)
  2199. call s:prepare()
  2200. call append(0, 'Searching for invalid plugins in '.g:plug_home)
  2201. call append(1, '')
  2202. " List of valid directories
  2203. let dirs = []
  2204. let errs = {}
  2205. let [cnt, total] = [0, len(g:plugs)]
  2206. for [name, spec] in items(g:plugs)
  2207. if !s:is_managed(name) || get(spec, 'frozen', 0)
  2208. call add(dirs, spec.dir)
  2209. else
  2210. let [err, clean] = s:git_validate(spec, 1)
  2211. if clean
  2212. let errs[spec.dir] = s:lines(err)[0]
  2213. else
  2214. call add(dirs, spec.dir)
  2215. endif
  2216. endif
  2217. let cnt += 1
  2218. call s:progress_bar(2, repeat('=', cnt), total)
  2219. normal! 2G
  2220. redraw
  2221. endfor
  2222. let allowed = {}
  2223. for dir in dirs
  2224. let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
  2225. let allowed[dir] = 1
  2226. for child in s:glob_dir(dir)
  2227. let allowed[child] = 1
  2228. endfor
  2229. endfor
  2230. let todo = []
  2231. let found = sort(s:glob_dir(g:plug_home))
  2232. while !empty(found)
  2233. let f = remove(found, 0)
  2234. if !has_key(allowed, f) && isdirectory(f)
  2235. call add(todo, f)
  2236. call append(line('$'), '- ' . f)
  2237. if has_key(errs, f)
  2238. call append(line('$'), ' ' . errs[f])
  2239. endif
  2240. let found = filter(found, 'stridx(v:val, f) != 0')
  2241. end
  2242. endwhile
  2243. 4
  2244. redraw
  2245. if empty(todo)
  2246. call append(line('$'), 'Already clean.')
  2247. else
  2248. let s:clean_count = 0
  2249. call append(3, ['Directories to delete:', ''])
  2250. redraw!
  2251. if a:force || s:ask_no_interrupt('Delete all directories?')
  2252. call s:delete([6, line('$')], 1)
  2253. else
  2254. call setline(4, 'Cancelled.')
  2255. nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
  2256. nmap <silent> <buffer> dd d_
  2257. xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
  2258. echo 'Delete the lines (d{motion}) to delete the corresponding directories'
  2259. endif
  2260. endif
  2261. 4
  2262. setlocal nomodifiable
  2263. endfunction
  2264. function! s:delete_op(type, ...)
  2265. call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
  2266. endfunction
  2267. function! s:delete(range, force)
  2268. let [l1, l2] = a:range
  2269. let force = a:force
  2270. let err_count = 0
  2271. while l1 <= l2
  2272. let line = getline(l1)
  2273. if line =~ '^- ' && isdirectory(line[2:])
  2274. execute l1
  2275. redraw!
  2276. let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
  2277. let force = force || answer > 1
  2278. if answer
  2279. let err = s:rm_rf(line[2:])
  2280. setlocal modifiable
  2281. if empty(err)
  2282. call setline(l1, '~'.line[1:])
  2283. let s:clean_count += 1
  2284. else
  2285. delete _
  2286. call append(l1 - 1, s:format_message('x', line[1:], err))
  2287. let l2 += len(s:lines(err))
  2288. let err_count += 1
  2289. endif
  2290. let msg = printf('Removed %d directories.', s:clean_count)
  2291. if err_count > 0
  2292. let msg .= printf(' Failed to remove %d directories.', err_count)
  2293. endif
  2294. call setline(4, msg)
  2295. setlocal nomodifiable
  2296. endif
  2297. endif
  2298. let l1 += 1
  2299. endwhile
  2300. endfunction
  2301. function! s:upgrade()
  2302. echo 'Downloading the latest version of vim-plug'
  2303. redraw
  2304. let tmp = s:plug_tempname()
  2305. let new = tmp . '/plug.vim'
  2306. try
  2307. let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
  2308. if v:shell_error
  2309. return s:err('Error upgrading vim-plug: '. out)
  2310. endif
  2311. if readfile(s:me) ==# readfile(new)
  2312. echo 'vim-plug is already up-to-date'
  2313. return 0
  2314. else
  2315. call rename(s:me, s:me . '.old')
  2316. call rename(new, s:me)
  2317. unlet g:loaded_plug
  2318. echo 'vim-plug has been upgraded'
  2319. return 1
  2320. endif
  2321. finally
  2322. silent! call s:rm_rf(tmp)
  2323. endtry
  2324. endfunction
  2325. function! s:upgrade_specs()
  2326. for spec in values(g:plugs)
  2327. let spec.frozen = get(spec, 'frozen', 0)
  2328. endfor
  2329. endfunction
  2330. function! s:status()
  2331. call s:prepare()
  2332. call append(0, 'Checking plugins')
  2333. call append(1, '')
  2334. let ecnt = 0
  2335. let unloaded = 0
  2336. let [cnt, total] = [0, len(g:plugs)]
  2337. for [name, spec] in items(g:plugs)
  2338. let is_dir = isdirectory(spec.dir)
  2339. if has_key(spec, 'uri')
  2340. if is_dir
  2341. let [err, _] = s:git_validate(spec, 1)
  2342. let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
  2343. else
  2344. let [valid, msg] = [0, 'Not found. Try PlugInstall.']
  2345. endif
  2346. else
  2347. if is_dir
  2348. let [valid, msg] = [1, 'OK']
  2349. else
  2350. let [valid, msg] = [0, 'Not found.']
  2351. endif
  2352. endif
  2353. let cnt += 1
  2354. let ecnt += !valid
  2355. " `s:loaded` entry can be missing if PlugUpgraded
  2356. if is_dir && get(s:loaded, name, -1) == 0
  2357. let unloaded = 1
  2358. let msg .= ' (not loaded)'
  2359. endif
  2360. call s:progress_bar(2, repeat('=', cnt), total)
  2361. call append(3, s:format_message(valid ? '-' : 'x', name, msg))
  2362. normal! 2G
  2363. redraw
  2364. endfor
  2365. call setline(1, 'Finished. '.ecnt.' error(s).')
  2366. normal! gg
  2367. setlocal nomodifiable
  2368. if unloaded
  2369. echo "Press 'L' on each line to load plugin, or 'U' to update"
  2370. nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
  2371. xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
  2372. end
  2373. endfunction
  2374. function! s:extract_name(str, prefix, suffix)
  2375. return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
  2376. endfunction
  2377. function! s:status_load(lnum)
  2378. let line = getline(a:lnum)
  2379. let name = s:extract_name(line, '-', '(not loaded)')
  2380. if !empty(name)
  2381. call plug#load(name)
  2382. setlocal modifiable
  2383. call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
  2384. setlocal nomodifiable
  2385. endif
  2386. endfunction
  2387. function! s:status_update() range
  2388. let lines = getline(a:firstline, a:lastline)
  2389. let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
  2390. if !empty(names)
  2391. echo
  2392. execute 'PlugUpdate' join(names)
  2393. endif
  2394. endfunction
  2395. function! s:is_preview_window_open()
  2396. silent! wincmd P
  2397. if &previewwindow
  2398. wincmd p
  2399. return 1
  2400. endif
  2401. endfunction
  2402. function! s:find_name(lnum)
  2403. for lnum in reverse(range(1, a:lnum))
  2404. let line = getline(lnum)
  2405. if empty(line)
  2406. return ''
  2407. endif
  2408. let name = s:extract_name(line, '-', '')
  2409. if !empty(name)
  2410. return name
  2411. endif
  2412. endfor
  2413. return ''
  2414. endfunction
  2415. function! s:preview_commit()
  2416. if b:plug_preview < 0
  2417. let b:plug_preview = !s:is_preview_window_open()
  2418. endif
  2419. let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
  2420. if empty(sha)
  2421. let name = matchstr(getline('.'), '^- \zs[^:]*\ze:$')
  2422. if empty(name)
  2423. return
  2424. endif
  2425. let title = 'HEAD@{1}..'
  2426. let command = 'git diff --no-color HEAD@{1}'
  2427. else
  2428. let title = sha
  2429. let command = 'git show --no-color --pretty=medium '.sha
  2430. let name = s:find_name(line('.'))
  2431. endif
  2432. if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
  2433. return
  2434. endif
  2435. if !s:is_preview_window_open()
  2436. execute get(g:, 'plug_pwindow', 'vertical rightbelow new')
  2437. execute 'e' title
  2438. else
  2439. execute 'pedit' title
  2440. wincmd P
  2441. endif
  2442. setlocal previewwindow filetype=git buftype=nofile bufhidden=wipe nobuflisted modifiable
  2443. let batchfile = ''
  2444. try
  2445. let [sh, shellcmdflag, shrd] = s:chsh(1)
  2446. let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && '.command
  2447. if s:is_win
  2448. let [batchfile, cmd] = s:batchfile(cmd)
  2449. endif
  2450. execute 'silent %!' cmd
  2451. finally
  2452. let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
  2453. if s:is_win && filereadable(batchfile)
  2454. call delete(batchfile)
  2455. endif
  2456. endtry
  2457. setlocal nomodifiable
  2458. nnoremap <silent> <buffer> q :q<cr>
  2459. wincmd p
  2460. endfunction
  2461. function! s:section(flags)
  2462. call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
  2463. endfunction
  2464. function! s:format_git_log(line)
  2465. let indent = ' '
  2466. let tokens = split(a:line, nr2char(1))
  2467. if len(tokens) != 5
  2468. return indent.substitute(a:line, '\s*$', '', '')
  2469. endif
  2470. let [graph, sha, refs, subject, date] = tokens
  2471. let tag = matchstr(refs, 'tag: [^,)]\+')
  2472. let tag = empty(tag) ? ' ' : ' ('.tag.') '
  2473. return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
  2474. endfunction
  2475. function! s:append_ul(lnum, text)
  2476. call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
  2477. endfunction
  2478. function! s:diff()
  2479. call s:prepare()
  2480. call append(0, ['Collecting changes ...', ''])
  2481. let cnts = [0, 0]
  2482. let bar = ''
  2483. let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
  2484. call s:progress_bar(2, bar, len(total))
  2485. for origin in [1, 0]
  2486. let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
  2487. if empty(plugs)
  2488. continue
  2489. endif
  2490. call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
  2491. for [k, v] in plugs
  2492. let branch = s:git_origin_branch(v)
  2493. if len(branch)
  2494. let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
  2495. let cmd = ['git', 'log', '--graph', '--color=never']
  2496. if s:git_version_requirement(2, 10, 0)
  2497. call add(cmd, '--no-show-signature')
  2498. endif
  2499. call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
  2500. if has_key(v, 'rtp')
  2501. call extend(cmd, ['--', v.rtp])
  2502. endif
  2503. let diff = s:system_chomp(cmd, v.dir)
  2504. if !empty(diff)
  2505. let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
  2506. call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
  2507. let cnts[origin] += 1
  2508. endif
  2509. endif
  2510. let bar .= '='
  2511. call s:progress_bar(2, bar, len(total))
  2512. normal! 2G
  2513. redraw
  2514. endfor
  2515. if !cnts[origin]
  2516. call append(5, ['', 'N/A'])
  2517. endif
  2518. endfor
  2519. call setline(1, printf('%d plugin(s) updated.', cnts[0])
  2520. \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
  2521. if cnts[0] || cnts[1]
  2522. nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
  2523. if empty(maparg("\<cr>", 'n'))
  2524. nmap <buffer> <cr> <plug>(plug-preview)
  2525. endif
  2526. if empty(maparg('o', 'n'))
  2527. nmap <buffer> o <plug>(plug-preview)
  2528. endif
  2529. endif
  2530. if cnts[0]
  2531. nnoremap <silent> <buffer> X :call <SID>revert()<cr>
  2532. echo "Press 'X' on each block to revert the update"
  2533. endif
  2534. normal! gg
  2535. setlocal nomodifiable
  2536. endfunction
  2537. function! s:revert()
  2538. if search('^Pending updates', 'bnW')
  2539. return
  2540. endif
  2541. let name = s:find_name(line('.'))
  2542. if empty(name) || !has_key(g:plugs, name) ||
  2543. \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
  2544. return
  2545. endif
  2546. call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
  2547. setlocal modifiable
  2548. normal! "_dap
  2549. setlocal nomodifiable
  2550. echo 'Reverted'
  2551. endfunction
  2552. function! s:snapshot(force, ...) abort
  2553. call s:prepare()
  2554. setf vim
  2555. call append(0, ['" Generated by vim-plug',
  2556. \ '" '.strftime("%c"),
  2557. \ '" :source this file in vim to restore the snapshot',
  2558. \ '" or execute: vim -S snapshot.vim',
  2559. \ '', '', 'PlugUpdate!'])
  2560. 1
  2561. let anchor = line('$') - 3
  2562. let names = sort(keys(filter(copy(g:plugs),
  2563. \'has_key(v:val, "uri") && isdirectory(v:val.dir)')))
  2564. for name in reverse(names)
  2565. let sha = has_key(g:plugs[name], 'commit') ? g:plugs[name].commit : s:git_revision(g:plugs[name].dir)
  2566. if !empty(sha)
  2567. call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
  2568. redraw
  2569. endif
  2570. endfor
  2571. if a:0 > 0
  2572. let fn = s:plug_expand(a:1)
  2573. if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
  2574. return
  2575. endif
  2576. call writefile(getline(1, '$'), fn)
  2577. echo 'Saved as '.a:1
  2578. silent execute 'e' s:esc(fn)
  2579. setf vim
  2580. endif
  2581. endfunction
  2582. function! s:split_rtp()
  2583. return split(&rtp, '\\\@<!,')
  2584. endfunction
  2585. let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
  2586. let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
  2587. if exists('g:plugs')
  2588. let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
  2589. call s:upgrade_specs()
  2590. call s:define_commands()
  2591. endif
  2592. let &cpo = s:cpo_save
  2593. unlet s:cpo_save