plug.vim 63 KB

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