zip.vim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. " zip.vim: Handles browsing zipfiles
  2. " AUTOLOAD PORTION
  3. " Date: 2024 Aug 21
  4. " Version: 34
  5. " Maintainer: This runtime file is looking for a new maintainer.
  6. " Former Maintainer: Charles E Campbell
  7. " Last Change:
  8. " 2024 Jun 16 by Vim Project: handle whitespace on Windows properly (#14998)
  9. " 2024 Jul 23 by Vim Project: fix 'x' command
  10. " 2024 Jul 24 by Vim Project: use delete() function
  11. " 2024 Jul 30 by Vim Project: fix opening remote zipfile
  12. " 2024 Aug 04 by Vim Project: escape '[' in name of file to be extracted
  13. " 2024 Aug 05 by Vim Project: workaround for the FreeBSD's unzip
  14. " 2024 Aug 05 by Vim Project: clean-up and make it work with shellslash on Windows
  15. " 2024 Aug 18 by Vim Project: correctly handle special globbing chars
  16. " 2024 Aug 21 by Vim Project: simplify condition to detect MS-Windows
  17. " License: Vim License (see vim's :help license)
  18. " Copyright: Copyright (C) 2005-2019 Charles E. Campbell {{{1
  19. " Permission is hereby granted to use and distribute this code,
  20. " with or without modifications, provided that this copyright
  21. " notice is copied with it. Like anything else that's free,
  22. " zip.vim and zipPlugin.vim are provided *as is* and comes with
  23. " no warranty of any kind, either expressed or implied. By using
  24. " this plugin, you agree that in no event will the copyright
  25. " holder be liable for any damages resulting from the use
  26. " of this software.
  27. " ---------------------------------------------------------------------
  28. " Load Once: {{{1
  29. if &cp || exists("g:loaded_zip")
  30. finish
  31. endif
  32. let g:loaded_zip= "v34"
  33. let s:keepcpo= &cpo
  34. set cpo&vim
  35. let s:zipfile_escape = ' ?&;\'
  36. let s:ERROR = 2
  37. let s:WARNING = 1
  38. let s:NOTE = 0
  39. " ---------------------------------------------------------------------
  40. " Global Values: {{{1
  41. if !exists("g:zip_shq")
  42. if &shq != ""
  43. let g:zip_shq= &shq
  44. elseif has("unix")
  45. let g:zip_shq= "'"
  46. else
  47. let g:zip_shq= '"'
  48. endif
  49. endif
  50. if !exists("g:zip_zipcmd")
  51. let g:zip_zipcmd= "zip"
  52. endif
  53. if !exists("g:zip_unzipcmd")
  54. let g:zip_unzipcmd= "unzip"
  55. endif
  56. if !exists("g:zip_extractcmd")
  57. let g:zip_extractcmd= g:zip_unzipcmd
  58. endif
  59. " ---------------------------------------------------------------------
  60. " required early
  61. " s:Mess: {{{2
  62. fun! s:Mess(group, msg)
  63. redraw!
  64. exe "echohl " . a:group
  65. echomsg a:msg
  66. echohl Normal
  67. endfun
  68. if v:version < 702
  69. call s:Mess('WarningMsg', "***warning*** this version of zip needs vim 7.2 or later")
  70. finish
  71. endif
  72. " sanity checks
  73. if !executable(g:zip_unzipcmd)
  74. call s:Mess('Error', "***error*** (zip#Browse) unzip not available on your system")
  75. finish
  76. endif
  77. if !dist#vim#IsSafeExecutable('zip', g:zip_unzipcmd)
  78. call s:Mess('Error', "Warning: NOT executing " .. g:zip_unzipcmd .. " from current directory!")
  79. finish
  80. endif
  81. " ----------------
  82. " Functions: {{{1
  83. " ----------------
  84. " ---------------------------------------------------------------------
  85. " zip#Browse: {{{2
  86. fun! zip#Browse(zipfile)
  87. " sanity check: ensure that the zipfile has "PK" as its first two letters
  88. " (zip files have a leading PK as a "magic cookie")
  89. if filereadable(a:zipfile) && readblob(a:zipfile, 0, 2) != 0z50.4B
  90. exe "noswapfile noautocmd e " .. fnameescape(a:zipfile)
  91. return
  92. endif
  93. let dict = s:SetSaneOpts()
  94. defer s:RestoreOpts(dict)
  95. " sanity checks
  96. if !executable(g:zip_unzipcmd)
  97. call s:Mess('Error', "***error*** (zip#Browse) unzip not available on your system")
  98. return
  99. endif
  100. if !filereadable(a:zipfile)
  101. if a:zipfile !~# '^\a\+://'
  102. " if it's an url, don't complain, let url-handlers such as vim do its thing
  103. call s:Mess('Error', "***error*** (zip#Browse) File not readable <".a:zipfile.">")
  104. endif
  105. return
  106. endif
  107. if &ma != 1
  108. set ma
  109. endif
  110. let b:zipfile= a:zipfile
  111. setlocal noswapfile
  112. setlocal buftype=nofile
  113. setlocal bufhidden=hide
  114. setlocal nobuflisted
  115. setlocal nowrap
  116. " Oct 12, 2021: need to re-use Bram's syntax/tar.vim.
  117. " Setting the filetype to zip doesn't do anything (currently),
  118. " but it is perhaps less confusing to curious perusers who do
  119. " a :echo &ft
  120. setf zip
  121. run! syntax/tar.vim
  122. " give header
  123. call append(0, ['" zip.vim version '.g:loaded_zip,
  124. \ '" Browsing zipfile '.a:zipfile,
  125. \ '" Select a file with cursor and press ENTER'])
  126. keepj $
  127. exe $"keepj sil r! {g:zip_unzipcmd} -Z1 -- {s:Escape(a:zipfile, 1)}"
  128. if v:shell_error != 0
  129. call s:Mess('WarningMsg', "***warning*** (zip#Browse) ".fnameescape(a:zipfile)." is not a zip file")
  130. keepj sil! %d
  131. let eikeep= &ei
  132. set ei=BufReadCmd,FileReadCmd
  133. exe "keepj r ".fnameescape(a:zipfile)
  134. let &ei= eikeep
  135. keepj 1d
  136. return
  137. endif
  138. " Maps associated with zip plugin
  139. setlocal noma nomod ro
  140. noremap <silent> <buffer> <cr> :call <SID>ZipBrowseSelect()<cr>
  141. noremap <silent> <buffer> x :call zip#Extract()<cr>
  142. if &mouse != ""
  143. noremap <silent> <buffer> <leftmouse> <leftmouse>:call <SID>ZipBrowseSelect()<cr>
  144. endif
  145. endfun
  146. " ---------------------------------------------------------------------
  147. " ZipBrowseSelect: {{{2
  148. fun! s:ZipBrowseSelect()
  149. let dict = s:SetSaneOpts()
  150. defer s:RestoreOpts(dict)
  151. let fname= getline(".")
  152. if !exists("b:zipfile")
  153. return
  154. endif
  155. " sanity check
  156. if fname =~ '^"'
  157. return
  158. endif
  159. if fname =~ '/$'
  160. call s:Mess('Error', "***error*** (zip#Browse) Please specify a file, not a directory")
  161. return
  162. endif
  163. " get zipfile to the new-window
  164. let zipfile = b:zipfile
  165. let curfile = expand("%")
  166. noswapfile new
  167. if !exists("g:zip_nomax") || g:zip_nomax == 0
  168. wincmd _
  169. endif
  170. let s:zipfile_{winnr()}= curfile
  171. exe "noswapfile e ".fnameescape("zipfile://".zipfile.'::'.fname)
  172. filetype detect
  173. endfun
  174. " ---------------------------------------------------------------------
  175. " zip#Read: {{{2
  176. fun! zip#Read(fname,mode)
  177. let dict = s:SetSaneOpts()
  178. defer s:RestoreOpts(dict)
  179. if has("unix")
  180. let zipfile = substitute(a:fname,'zipfile://\(.\{-}\)::[^\\].*$','\1','')
  181. let fname = substitute(a:fname,'zipfile://.\{-}::\([^\\].*\)$','\1','')
  182. else
  183. let zipfile = substitute(a:fname,'^.\{-}zipfile://\(.\{-}\)::[^\\].*$','\1','')
  184. let fname = substitute(a:fname,'^.\{-}zipfile://.\{-}::\([^\\].*\)$','\1','')
  185. endif
  186. let fname = fname->substitute('[', '[[]', 'g')->escape('?*\\')
  187. " sanity check
  188. if !executable(substitute(g:zip_unzipcmd,'\s\+.*$','',''))
  189. call s:Mess('Error', "***error*** (zip#Read) sorry, your system doesn't appear to have the ".g:zip_unzipcmd." program")
  190. return
  191. endif
  192. " the following code does much the same thing as
  193. " exe "keepj sil! r! ".g:zip_unzipcmd." -p -- ".s:Escape(zipfile,1)." ".s:Escape(fname,1)
  194. " but allows zipfile://... entries in quickfix lists
  195. let temp = tempname()
  196. let fn = expand('%:p')
  197. exe "sil !".g:zip_unzipcmd." -p -- ".s:Escape(zipfile,1)." ".s:Escape(fname,1).' > '.temp
  198. sil exe 'keepalt file '.temp
  199. sil keepj e!
  200. sil exe 'keepalt file '.fnameescape(fn)
  201. call delete(temp)
  202. filetype detect
  203. " cleanup
  204. set nomod
  205. endfun
  206. " ---------------------------------------------------------------------
  207. " zip#Write: {{{2
  208. fun! zip#Write(fname)
  209. let dict = s:SetSaneOpts()
  210. defer s:RestoreOpts(dict)
  211. " sanity checks
  212. if !executable(substitute(g:zip_zipcmd,'\s\+.*$','',''))
  213. call s:Mess('Error', "***error*** (zip#Write) sorry, your system doesn't appear to have the ".g:zip_zipcmd." program")
  214. return
  215. endif
  216. if !exists("*mkdir")
  217. call s:Mess('Error', "***error*** (zip#Write) sorry, mkdir() doesn't work on your system")
  218. return
  219. endif
  220. let curdir= getcwd()
  221. let tmpdir= tempname()
  222. if tmpdir =~ '\.'
  223. let tmpdir= substitute(tmpdir,'\.[^.]*$','','e')
  224. endif
  225. call mkdir(tmpdir,"p")
  226. " attempt to change to the indicated directory
  227. if s:ChgDir(tmpdir,s:ERROR,"(zip#Write) cannot cd to temporary directory")
  228. return
  229. endif
  230. " place temporary files under .../_ZIPVIM_/
  231. if isdirectory("_ZIPVIM_")
  232. call delete("_ZIPVIM_", "rf")
  233. endif
  234. call mkdir("_ZIPVIM_")
  235. cd _ZIPVIM_
  236. if has("unix")
  237. let zipfile = substitute(a:fname,'zipfile://\(.\{-}\)::[^\\].*$','\1','')
  238. let fname = substitute(a:fname,'zipfile://.\{-}::\([^\\].*\)$','\1','')
  239. else
  240. let zipfile = substitute(a:fname,'^.\{-}zipfile://\(.\{-}\)::[^\\].*$','\1','')
  241. let fname = substitute(a:fname,'^.\{-}zipfile://.\{-}::\([^\\].*\)$','\1','')
  242. endif
  243. if fname =~ '/'
  244. let dirpath = substitute(fname,'/[^/]\+$','','e')
  245. if has("win32unix") && executable("cygpath")
  246. let dirpath = substitute(system("cygpath ".s:Escape(dirpath,0)),'\n','','e')
  247. endif
  248. call mkdir(dirpath,"p")
  249. endif
  250. if zipfile !~ '/'
  251. let zipfile= curdir.'/'.zipfile
  252. endif
  253. exe "w! ".fnameescape(fname)
  254. if has("win32unix") && executable("cygpath")
  255. let zipfile = substitute(system("cygpath ".s:Escape(zipfile,0)),'\n','','e')
  256. endif
  257. if (has("win32") || has("win95") || has("win64") || has("win16")) && &shell !~? 'sh$'
  258. let fname = substitute(fname, '[', '[[]', 'g')
  259. endif
  260. call system(g:zip_zipcmd." -u ".s:Escape(fnamemodify(zipfile,":p"),0)." ".s:Escape(fname,0))
  261. if v:shell_error != 0
  262. call s:Mess('Error', "***error*** (zip#Write) sorry, unable to update ".zipfile." with ".fname)
  263. elseif s:zipfile_{winnr()} =~ '^\a\+://'
  264. " support writing zipfiles across a network
  265. let netzipfile= s:zipfile_{winnr()}
  266. 1split|enew
  267. let binkeep= &binary
  268. let eikeep = &ei
  269. set binary ei=all
  270. exe "noswapfile e! ".fnameescape(zipfile)
  271. call netrw#NetWrite(netzipfile)
  272. let &ei = eikeep
  273. let &binary = binkeep
  274. q!
  275. unlet s:zipfile_{winnr()}
  276. endif
  277. " cleanup and restore current directory
  278. cd ..
  279. call delete("_ZIPVIM_", "rf")
  280. call s:ChgDir(curdir,s:WARNING,"(zip#Write) unable to return to ".curdir."!")
  281. call delete(tmpdir, "rf")
  282. setlocal nomod
  283. endfun
  284. " ---------------------------------------------------------------------
  285. " zip#Extract: extract a file from a zip archive {{{2
  286. fun! zip#Extract()
  287. let dict = s:SetSaneOpts()
  288. defer s:RestoreOpts(dict)
  289. let fname= getline(".")
  290. " sanity check
  291. if fname =~ '^"'
  292. return
  293. endif
  294. if fname =~ '/$'
  295. call s:Mess('Error', "***error*** (zip#Extract) Please specify a file, not a directory")
  296. return
  297. endif
  298. if filereadable(fname)
  299. call s:Mess('Error', "***error*** (zip#Extract) <" .. fname .."> already exists in directory, not overwriting!")
  300. return
  301. endif
  302. let target = fname->substitute('\[', '[[]', 'g')
  303. if &shell =~ 'cmd' && has("win32")
  304. let target = target
  305. \ ->substitute('[?*]', '[&]', 'g')
  306. \ ->substitute('[\\]', '?', 'g')
  307. \ ->shellescape()
  308. " there cannot be a file name with '\' in its name, unzip replaces it by _
  309. let fname = fname->substitute('[\\?*]', '_', 'g')
  310. else
  311. let target = target->escape('*?\\')->shellescape()
  312. endif
  313. " extract the file mentioned under the cursor
  314. call system($"{g:zip_extractcmd} -o {shellescape(b:zipfile)} {target}")
  315. if v:shell_error != 0
  316. call s:Mess('Error', "***error*** ".g:zip_extractcmd." ".b:zipfile." ".fname.": failed!")
  317. elseif !filereadable(fname)
  318. call s:Mess('Error', "***error*** attempted to extract ".fname." but it doesn't appear to be present!")
  319. else
  320. echomsg "***note*** successfully extracted ".fname
  321. endif
  322. endfun
  323. " ---------------------------------------------------------------------
  324. " s:Escape: {{{2
  325. fun! s:Escape(fname,isfilt)
  326. if exists("*shellescape")
  327. if a:isfilt
  328. let qnameq= shellescape(a:fname,1)
  329. else
  330. let qnameq= shellescape(a:fname)
  331. endif
  332. else
  333. let qnameq= g:zip_shq.escape(a:fname,g:zip_shq).g:zip_shq
  334. endif
  335. return qnameq
  336. endfun
  337. " ---------------------------------------------------------------------
  338. " s:ChgDir: {{{2
  339. fun! s:ChgDir(newdir,errlvl,errmsg)
  340. try
  341. exe "cd ".fnameescape(a:newdir)
  342. catch /^Vim\%((\a\+)\)\=:E344/
  343. redraw!
  344. if a:errlvl == s:NOTE
  345. echomsg "***note*** ".a:errmsg
  346. elseif a:errlvl == s:WARNING
  347. call s:Mess("WarningMsg", "***warning*** ".a:errmsg)
  348. elseif a:errlvl == s:ERROR
  349. call s:Mess("Error", "***error*** ".a:errmsg)
  350. endif
  351. return 1
  352. endtry
  353. return 0
  354. endfun
  355. " ---------------------------------------------------------------------
  356. " s:SetSaneOpts: {{{2
  357. fun! s:SetSaneOpts()
  358. let dict = {}
  359. let dict.report = &report
  360. let dict.shellslash = &shellslash
  361. let &report = 10
  362. if exists('+shellslash')
  363. let &shellslash = 0
  364. endif
  365. return dict
  366. endfun
  367. " ---------------------------------------------------------------------
  368. " s:RestoreOpts: {{{2
  369. fun! s:RestoreOpts(dict)
  370. for [key, val] in items(a:dict)
  371. exe $"let &{key} = {val}"
  372. endfor
  373. endfun
  374. " ------------------------------------------------------------------------
  375. " Modelines And Restoration: {{{1
  376. let &cpo= s:keepcpo
  377. unlet s:keepcpo
  378. " vim:ts=8 fdm=marker