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