view runtime/autoload/zip.vim @ 35419:f445860fd390

runtime(zip): MS-Windows: handle files with spaces properly Commit: https://github.com/vim/vim/commit/1c6734291295bf8aa39577840b40bb21a7f27120 Author: Christian Brabandt <cb@256bit.org> Date: Sat Jun 15 14:49:24 2024 +0200 runtime(zip): MS-Windows: handle files with spaces properly This change does the following 3 things: 1) non need to quote the file to be extracted The zipfile plugin used to quote and fnameescape() the path to the file to be extracted. However testing with unzip showed, that while this works on Linux on Windows you shall not escape the blanks in filenames. As long as the pathname is properly quoted, this words on Linux and Windows. 2) reset shellslash (MS-Windows only) When shellslash is set, filenames to the zip archive will be forward quoted. However since the filename is eventually handed over to the unzip command, we need to make sure to use native paths so that the command will understand what file to open. Therefore, if shellslash is set (and the shell is cmd.exe), replace any forward slashes by the expected backslashes 3) style: Use tabs for the Header, remove a few comments in the s:Escape() and zip#read() functions fixes: #14998 Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Sat, 15 Jun 2024 15:00:06 +0200
parents 02bd0fe77c68
children 30d1a3a62135
line wrap: on
line source

" zip.vim: Handles browsing zipfiles
"            AUTOLOAD PORTION
" Date:		Mar 12, 2023
" Version:	33
" Maintainer:	This runtime file is looking for a new maintainer.
" Former Maintainer:	Charles E Campbell
" Last Change:
"		2024 Jun 16 by Vim Project: handle whitespace on Windows properly (#14998)
" License:	Vim License  (see vim's :help license)
" Copyright:	Copyright (C) 2005-2019 Charles E. Campbell {{{1
"		Permission is hereby granted to use and distribute this code,
"		with or without modifications, provided that this copyright
"		notice is copied with it. Like anything else that's free,
"		zip.vim and zipPlugin.vim are provided *as is* and comes with
"		no warranty of any kind, either expressed or implied. By using
"		this plugin, you agree that in no event will the copyright
"		holder be liable for any damages resulting from the use
"		of this software.

" ---------------------------------------------------------------------
" Load Once: {{{1
if &cp || exists("g:loaded_zip")
 finish
endif
let g:loaded_zip= "v33"
if v:version < 702
 echohl WarningMsg
 echo "***warning*** this version of zip needs vim 7.2 or later"
 echohl Normal
 finish
endif
let s:keepcpo= &cpo
set cpo&vim
"DechoTabOn

let s:zipfile_escape = ' ?&;\'
let s:ERROR          = 2
let s:WARNING        = 1
let s:NOTE           = 0

" ---------------------------------------------------------------------
"  Global Values: {{{1
if !exists("g:zip_shq")
 if &shq != ""
  let g:zip_shq= &shq
 elseif has("unix")
  let g:zip_shq= "'"
 else
  let g:zip_shq= '"'
 endif
endif
if !exists("g:zip_zipcmd")
 let g:zip_zipcmd= "zip"
endif
if !exists("g:zip_unzipcmd")
 let g:zip_unzipcmd= "unzip"
endif
if !exists("g:zip_extractcmd")
 let g:zip_extractcmd= g:zip_unzipcmd
endif

if !dist#vim#IsSafeExecutable('zip', g:zip_unzipcmd)
 echoerr "Warning: NOT executing " .. g:zip_unzipcmd .. " from current directory!"
 finish
endif

" ----------------
"  Functions: {{{1
" ----------------

" ---------------------------------------------------------------------
" zip#Browse: {{{2
fun! zip#Browse(zipfile)
"  call Dfunc("zip#Browse(zipfile<".a:zipfile.">)")
  " sanity check: insure that the zipfile has "PK" as its first two letters
  "               (zipped files have a leading PK as a "magic cookie")
  if !filereadable(a:zipfile) || readfile(a:zipfile, "", 1)[0] !~ '^PK'
   exe "noswapfile noautocmd noswapfile e ".fnameescape(a:zipfile)
"   call Dret("zip#Browse : not a zipfile<".a:zipfile.">")
   return
"  else        " Decho
"   call Decho("zip#Browse: a:zipfile<".a:zipfile."> passed PK test - it's a zip file")
  endif

  let repkeep= &report
  set report=10

  " sanity checks
  if !exists("*fnameescape")
   if &verbose > 1
    echoerr "the zip plugin is not available (your vim doesn't support fnameescape())"
   endif
   return
  endif
  if !executable(g:zip_unzipcmd)
   redraw!
   echohl Error | echo "***error*** (zip#Browse) unzip not available on your system"
"   call inputsave()|call input("Press <cr> to continue")|call inputrestore()
   let &report= repkeep
"   call Dret("zip#Browse")
   return
  endif
  if !filereadable(a:zipfile)
   if a:zipfile !~# '^\a\+://'
    " if it's an url, don't complain, let url-handlers such as vim do its thing
    redraw!
    echohl Error | echo "***error*** (zip#Browse) File not readable<".a:zipfile.">" | echohl None
"    call inputsave()|call input("Press <cr> to continue")|call inputrestore()
   endif
   let &report= repkeep
"   call Dret("zip#Browse : file<".a:zipfile."> not readable")
   return
  endif
"  call Decho("passed sanity checks")
  if &ma != 1
   set ma
  endif
  let b:zipfile= a:zipfile

  setlocal noswapfile
  setlocal buftype=nofile
  setlocal bufhidden=hide
  setlocal nobuflisted
  setlocal nowrap

  " Oct 12, 2021: need to re-use Bram's syntax/tar.vim.
  " Setting the filetype to zip doesn't do anything (currently),
  " but it is perhaps less confusing to curious perusers who do
  " a :echo &ft
  setf zip
  run! syntax/tar.vim

  " give header
  call append(0, ['" zip.vim version '.g:loaded_zip,
 \                '" Browsing zipfile '.a:zipfile,
 \                '" Select a file with cursor and press ENTER'])
  keepj $

"  call Decho("exe silent r! ".g:zip_unzipcmd." -l -- ".s:Escape(a:zipfile,1))
  exe "keepj sil! r! ".g:zip_unzipcmd." -Z -1 -- ".s:Escape(a:zipfile,1)
  if v:shell_error != 0
   redraw!
   echohl WarningMsg | echo "***warning*** (zip#Browse) ".fnameescape(a:zipfile)." is not a zip file" | echohl None
"   call inputsave()|call input("Press <cr> to continue")|call inputrestore()
   keepj sil! %d
   let eikeep= &ei
   set ei=BufReadCmd,FileReadCmd
   exe "keepj r ".fnameescape(a:zipfile)
   let &ei= eikeep
   keepj 1d
"   call Dret("zip#Browse")
   return
  endif

  " Maps associated with zip plugin
  setlocal noma nomod ro
  noremap <silent> <buffer>	<cr>		:call <SID>ZipBrowseSelect()<cr>
  noremap <silent> <buffer>	x		:call zip#Extract()<cr>
  if &mouse != ""
   noremap <silent> <buffer>	<leftmouse>	<leftmouse>:call <SID>ZipBrowseSelect()<cr>
  endif

  let &report= repkeep
"  call Dret("zip#Browse")
endfun

" ---------------------------------------------------------------------
" ZipBrowseSelect: {{{2
fun! s:ZipBrowseSelect()
  "  call Dfunc("ZipBrowseSelect() zipfile<".((exists("b:zipfile"))? b:zipfile : "n/a")."> curfile<".expand("%").">")
  let repkeep= &report
  set report=10
  let fname= getline(".")
  if !exists("b:zipfile")
"   call Dret("ZipBrowseSelect : b:zipfile doesn't exist!")
   return
  endif

  " sanity check
  if fname =~ '^"'
   let &report= repkeep
"   call Dret("ZipBrowseSelect")
   return
  endif
  if fname =~ '/$'
   redraw!
   echohl Error | echo "***error*** (zip#Browse) Please specify a file, not a directory" | echohl None
"   call inputsave()|call input("Press <cr> to continue")|call inputrestore()
   let &report= repkeep
"   call Dret("ZipBrowseSelect")
   return
  endif

"  call Decho("fname<".fname.">")

  " get zipfile to the new-window
  let zipfile = b:zipfile
  let curfile = expand("%")
"  call Decho("zipfile<".zipfile.">")
"  call Decho("curfile<".curfile.">")

  noswapfile new
  if !exists("g:zip_nomax") || g:zip_nomax == 0
   wincmd _
  endif
  let s:zipfile_{winnr()}= curfile
"  call Decho("exe e ".fnameescape("zipfile://".zipfile.'::'.fname))
  exe "noswapfile e ".fnameescape("zipfile://".zipfile.'::'.fname)
  filetype detect

  let &report= repkeep
"  call Dret("ZipBrowseSelect : s:zipfile_".winnr()."<".s:zipfile_{winnr()}.">")
endfun

" ---------------------------------------------------------------------
" zip#Read: {{{2
fun! zip#Read(fname,mode)
  let repkeep= &report
  set report=10

  if has("unix")
   let zipfile = substitute(a:fname,'zipfile://\(.\{-}\)::[^\\].*$','\1','')
   let fname   = substitute(a:fname,'zipfile://.\{-}::\([^\\].*\)$','\1','')
  else
   let zipfile = substitute(a:fname,'^.\{-}zipfile://\(.\{-}\)::[^\\].*$','\1','')
   let fname   = substitute(a:fname,'^.\{-}zipfile://.\{-}::\([^\\].*\)$','\1','')
   let fname   = substitute(fname, '[', '[[]', 'g')
  endif
  " sanity check
  if !executable(substitute(g:zip_unzipcmd,'\s\+.*$','',''))
   redraw!
   echohl Error | echo "***error*** (zip#Read) sorry, your system doesn't appear to have the ".g:zip_unzipcmd." program" | echohl None
"   call inputsave()|call input("Press <cr> to continue")|call inputrestore()
   let &report= repkeep
   return
  endif

  " the following code does much the same thing as
  "   exe "keepj sil! r! ".g:zip_unzipcmd." -p -- ".s:Escape(zipfile,1)." ".s:Escape(fnameescape(fname),1)
  " but allows zipfile://... entries in quickfix lists
  let temp = tempname()
  let fn   = expand('%:p')
  exe "sil! !".g:zip_unzipcmd." -p -- ".s:Escape(zipfile,1)." ".s:Escape(fname,1).' > '.temp
  sil exe 'keepalt file '.temp
  sil keepj e!
  sil exe 'keepalt file '.fnameescape(fn)
  call delete(temp)

  filetype detect

  " cleanup
  set nomod

  let &report= repkeep
endfun

" ---------------------------------------------------------------------
" zip#Write: {{{2
fun! zip#Write(fname)
"  call Dfunc("zip#Write(fname<".a:fname.">) zipfile_".winnr()."<".s:zipfile_{winnr()}.">")
  let repkeep= &report
  set report=10

  " sanity checks
  if !executable(substitute(g:zip_zipcmd,'\s\+.*$','',''))
   redraw!
   echohl Error | echo "***error*** (zip#Write) sorry, your system doesn't appear to have the ".g:zip_zipcmd." program" | echohl None
"   call inputsave()|call input("Press <cr> to continue")|call inputrestore()
   let &report= repkeep
"   call Dret("zip#Write")
   return
  endif
  if !exists("*mkdir")
   redraw!
   echohl Error | echo "***error*** (zip#Write) sorry, mkdir() doesn't work on your system" | echohl None
"   call inputsave()|call input("Press <cr> to continue")|call inputrestore()
   let &report= repkeep
"   call Dret("zip#Write")
   return
  endif

  let curdir= getcwd()
  let tmpdir= tempname()
"  call Decho("orig tempname<".tmpdir.">")
  if tmpdir =~ '\.'
   let tmpdir= substitute(tmpdir,'\.[^.]*$','','e')
  endif
"  call Decho("tmpdir<".tmpdir.">")
  call mkdir(tmpdir,"p")

  " attempt to change to the indicated directory
  if s:ChgDir(tmpdir,s:ERROR,"(zip#Write) cannot cd to temporary directory")
   let &report= repkeep
"   call Dret("zip#Write")
   return
  endif
"  call Decho("current directory now: ".getcwd())

  " place temporary files under .../_ZIPVIM_/
  if isdirectory("_ZIPVIM_")
   call s:Rmdir("_ZIPVIM_")
  endif
  call mkdir("_ZIPVIM_")
  cd _ZIPVIM_
"  call Decho("current directory now: ".getcwd())

  if has("unix")
   let zipfile = substitute(a:fname,'zipfile://\(.\{-}\)::[^\\].*$','\1','')
   let fname   = substitute(a:fname,'zipfile://.\{-}::\([^\\].*\)$','\1','')
   let fname   = fnameescape(fname)
  else
   let zipfile = substitute(a:fname,'^.\{-}zipfile://\(.\{-}\)::[^\\].*$','\1','')
   let fname   = substitute(a:fname,'^.\{-}zipfile://.\{-}::\([^\\].*\)$','\1','')
  endif
"  call Decho("zipfile<".zipfile.">")
"  call Decho("fname  <".fname.">")

  if fname =~ '/'
   let dirpath = substitute(fname,'/[^/]\+$','','e')
   if has("win32unix") && executable("cygpath")
    let dirpath = substitute(system("cygpath ".s:Escape(dirpath,0)),'\n','','e')
   endif
"   call Decho("mkdir(dirpath<".dirpath.">,p)")
   call mkdir(dirpath,"p")
  endif
  if zipfile !~ '/'
   let zipfile= curdir.'/'.zipfile
  endif
"  call Decho("zipfile<".zipfile."> fname<".fname.">")

  exe "w! ".fnameescape(fname)
  if has("win32unix") && executable("cygpath")
   let zipfile = substitute(system("cygpath ".s:Escape(zipfile,0)),'\n','','e')
  endif

  if (has("win32") || has("win95") || has("win64") || has("win16")) && &shell !~? 'sh$'
    let fname = substitute(fname, '[', '[[]', 'g')
  endif

"  call Decho(g:zip_zipcmd." -u ".s:Escape(fnamemodify(zipfile,":p"),0)." ".s:Escape(fname,0))
  call system(g:zip_zipcmd." -u ".s:Escape(fnamemodify(zipfile,":p"),0)." ".s:Escape(fname,0))
  if v:shell_error != 0
   redraw!
   echohl Error | echo "***error*** (zip#Write) sorry, unable to update ".zipfile." with ".fname | echohl None
"   call inputsave()|call input("Press <cr> to continue")|call inputrestore()

  elseif s:zipfile_{winnr()} =~ '^\a\+://'
   " support writing zipfiles across a network
   let netzipfile= s:zipfile_{winnr()}
"   call Decho("handle writing <".zipfile."> across network as <".netzipfile.">")
   1split|enew
   let binkeep= &binary
   let eikeep = &ei
   set binary ei=all
   exe "noswapfile e! ".fnameescape(zipfile)
   call netrw#NetWrite(netzipfile)
   let &ei     = eikeep
   let &binary = binkeep
   q!
   unlet s:zipfile_{winnr()}
  endif
  
  " cleanup and restore current directory
  cd ..
  call s:Rmdir("_ZIPVIM_")
  call s:ChgDir(curdir,s:WARNING,"(zip#Write) unable to return to ".curdir."!")
  call s:Rmdir(tmpdir)
  setlocal nomod

  let &report= repkeep
"  call Dret("zip#Write")
endfun

" ---------------------------------------------------------------------
" zip#Extract: extract a file from a zip archive {{{2
fun! zip#Extract()
"  call Dfunc("zip#Extract()")

  let repkeep= &report
  set report=10
  let fname= getline(".")
"  call Decho("fname<".fname.">")

  " sanity check
  if fname =~ '^"'
   let &report= repkeep
"   call Dret("zip#Extract")
   return
  endif
  if fname =~ '/$'
   redraw!
   echohl Error | echo "***error*** (zip#Extract) Please specify a file, not a directory" | echohl None
   let &report= repkeep
"   call Dret("zip#Extract")
   return
  endif

  " extract the file mentioned under the cursor
"  call Decho("system(".g:zip_extractcmd." ".shellescape(b:zipfile)." ".shellescape(shell).")")
  call system(g:zip_extractcmd." ".shellescape(b:zipfile)." ".shellescape(shell))
"  call Decho("zipfile<".b:zipfile.">")
  if v:shell_error != 0
   echohl Error | echo "***error*** ".g:zip_extractcmd." ".b:zipfile." ".fname.": failed!" | echohl NONE
  elseif !filereadable(fname)
   echohl Error | echo "***error*** attempted to extract ".fname." but it doesn't appear to be present!"
  else
   echo "***note*** successfully extracted ".fname
  endif

  " restore option
  let &report= repkeep

"  call Dret("zip#Extract")
endfun

" ---------------------------------------------------------------------
" s:Escape: {{{2
fun! s:Escape(fname,isfilt)
  if exists("*shellescape")
   if a:isfilt
    let qnameq= shellescape(a:fname,1)
   else
    let qnameq= shellescape(a:fname)
   endif
  else
   let qnameq= g:zip_shq.escape(a:fname,g:zip_shq).g:zip_shq
  endif
  if exists("+shellslash") && &shellslash && &shell =~ "cmd.exe"
   " renormalize directory separator on Windows
   let qnameq=substitute(qnameq, '/', '\\', 'g')
  endif
  return qnameq
endfun

" ---------------------------------------------------------------------
" ChgDir: {{{2
fun! s:ChgDir(newdir,errlvl,errmsg)
"  call Dfunc("ChgDir(newdir<".a:newdir."> errlvl=".a:errlvl."  errmsg<".a:errmsg.">)")

  try
   exe "cd ".fnameescape(a:newdir)
  catch /^Vim\%((\a\+)\)\=:E344/
   redraw!
   if a:errlvl == s:NOTE
    echo "***note*** ".a:errmsg
   elseif a:errlvl == s:WARNING
    echohl WarningMsg | echo "***warning*** ".a:errmsg | echohl NONE
   elseif a:errlvl == s:ERROR
    echohl Error | echo "***error*** ".a:errmsg | echohl NONE
   endif
"   call inputsave()|call input("Press <cr> to continue")|call inputrestore()
"   call Dret("ChgDir 1")
   return 1
  endtry

"  call Dret("ChgDir 0")
  return 0
endfun

" ---------------------------------------------------------------------
" s:Rmdir: {{{2
fun! s:Rmdir(fname)
"  call Dfunc("Rmdir(fname<".a:fname.">)")
  if (has("win32") || has("win95") || has("win64") || has("win16")) && &shell !~? 'sh$'
   call system("rmdir /S/Q ".s:Escape(a:fname,0))
  else
   call system("/bin/rm -rf ".s:Escape(a:fname,0))
  endif
"  call Dret("Rmdir")
endfun

" ------------------------------------------------------------------------
" Modelines And Restoration: {{{1
let &cpo= s:keepcpo
unlet s:keepcpo
" vim:ts=8 fdm=marker