plug.vim 75 KB

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