plug.vim 57 KB

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