plug.vim 64 KB

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