plug.vim 60 KB

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