plug.vim 60 KB

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