plug.vim 55 KB

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