plug.vim 75 KB

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