Skip to content

runtime(zip): Customizable zip/unzip commands - #20832

Open
curbe454 wants to merge 21 commits into
vim:masterfrom
curbe454:zip-feat
Open

runtime(zip): Customizable zip/unzip commands#20832
curbe454 wants to merge 21 commits into
vim:masterfrom
curbe454:zip-feat

Conversation

@curbe454

Copy link
Copy Markdown
Contributor

I'd like to change another program other than zip/unzip to open a zip file such as 7z.
According to the doc, I changed the global variable g:zip_unzipcmd in my .vimrc:

let g:zip_unzipcmd = "7z"

It didn't work. This is because this plugin is primarily only for these two zip and unzip binary executable files, with some CLI options hard encoded. For example, this below codes in runtime/autoload/zip.vim:

let gnu_cmd = "keepj sil r! " . g:zip_unzipcmd . " -Z1 -- " . s:Escape(a:zipfile, 1)

In this pr, these hard coded options are eliminated, so that users can choose their program to open .zip files.

@chrisbra

Copy link
Copy Markdown
Member

this also requires an update to runtime/doc/pi_zip.txt. Also please be very careful, to not introduce command injection or arbitrary file writes. We had some security issues around this.

@curbe454

curbe454 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Ok. This pr makes no difference to filename logic. I added more custom commands, and every default value of them should be checked by dist#vim#IsSafeExecutable prevent injection by environment, as before.

fun! s:SafeExecutable(exe)
if !executable(g:zip_update[0]) && &shell !~ 'pwsh'
call s:Mess('Error', "***error*** (zip) '".a:exe."' not available on your system")
finish
endif
if !dist#vim#IsSafeExecutable('zip', a:exe) && &shell !~ 'pwsh'
call s:Mess('Error', "Warning: NOT executing " .. a:exe .. " from current directory!")
return v:false
endif
return v:true
endfun
" garantee default command is exist and not be injected by environment
" every default command should be checked
if !s:SafeExecutable(g:zip_delete[0]) | finish | endif
if !s:SafeExecutable(g:zip_update[0]) | finish | endif
if !s:SafeExecutable(g:zip_browse[0]) | finish | endif
if !s:SafeExecutable(g:zip_read[0]) | finish | endif
if !s:SafeExecutable(g:zip_extract[0]) | finish | endif

@curbe454

curbe454 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

The default commands use zip and unzip, and I tried using 7z (note that the -ba option of 7z is not stable API):

let g:zip_browse= ["printf", "'';",
  \ "7zlbaf() { 7z l -ba \"$@\" | awk '{print ($3 ~ /D[^[:space:]]{4}/ ? $6 \"/\" : $6)}'; }",
  \ " && ", "7zlbaf"]    " or save command as a file with `#!/bin/sh`
let g:zip_read= ["7z", "x", "-so"]
let g:zip_extract= ["7z", "x"]
let g:zip_delete= ["7z", "d"]
let g:zip_update= ["7z", "u"]

I found that 7z behaviors different on file names with special characters, such as a[a].txt or a*.txt. For example:

$ ls zipglob
'a[a].txt'  'a*.txt'  'a?.txt'  'a\.txt'  'a\\.txt'
$ wc -l 'zipglob/a*.txt'
1 zipglob/a*.txt
$ unzip -p exp.zip 'zipglob/a\*.txt' | wc -l
1
$ 7z x -so exp.zip 'zipglob/a\*.txt' | wc -l
2

This means the behavior of visiting(read/write) files that contains special character in a zip file without zip/unzip is unpredictable.

@chrisbra

Copy link
Copy Markdown
Member

This means the behavior of visiting(read/write) files that contains special character in a zip file without zip/unzip is unpredictable.

Not only that, the zip/unzip command line parser is already insane. There is no sane way to handle strange characters robustly.

That would mean we would need a custom escape function like what is done for powershell using PSEscape().

@curbe454

Copy link
Copy Markdown
Contributor Author

I found there's no support for Windows PowerShell 5, only PowerShell Core. So the CI test fails. I'll fix it.

@curbe454

Copy link
Copy Markdown
Contributor Author

Not only that, the zip/unzip command line parser is already insane. There is no sane way to handle strange characters robustly.

That would mean we would need a custom escape function like what is done for powershell using PSEscape().

When the commands to open/edit zip files are customizable, the resposibility to guarantee the correctness are transmit to the users. Based on this opinion, us developers should ensure the default behaviors are correct.

@curbe454

curbe454 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

As for my above example:

let g:zip_browse= ["printf", "'';",
  \ "7zlbaf() { 7z l -ba \"$@\" | awk '{print ($3 ~ /D[^[:space:]]{4}/ ? $6 \"/\" : $6)}'; }",
  \ " && ", "7zlbaf"]

This hack is not good since one more meanless command is executed. There's another example (on Windows):

let g:zip_browse  = ["7zlbaf"]
let g:zip_read    = ["7z", "x", "-so"]
let g:zip_extract = ["7z", "x"]
let g:zip_delete  = ["7z", "d"]
let g:zip_update  = ["7z", "u"]

while the 7zlbaf.bat is a custom script file which is added to %PATH%(or $Env:Path), with awk installed:

@7z l -ba %* | awk.exe "{ gsub(/\\/, \""/\"", $6); print ($3 ~ /D[^[:space:]]{4}/ ? $6 \""/\"" : $6)}"

They are still not elegant. Maybe we should provide an API to let users to format the outputs of commands, which may solve special character problem.

@chrisbra
chrisbra requested a review from Copilot July 28, 2026 19:47
@chrisbra

Copy link
Copy Markdown
Member

so you think this is worthwhile? or shall we rather drop it?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors Vim’s built-in zip.vim plugin to make the external zip/unzip tooling configurable (moving from single-string command variables to List-based command+args variables), and extends/clarifies the PowerShell fallback behavior on Windows. It also updates the zip plugin documentation and adjusts the zip plugin test coverage accordingly.

Changes:

  • Replace hard-coded zip/unzip option usage with new configurable List variables (g:zip_browse, g:zip_read, g:zip_extract, g:zip_delete, g:zip_update).
  • Add a new g:zip_pwsh knob and internal selection logic to support both Windows PowerShell and pwsh.
  • Update pi_zip.txt docs and test_plugin_zip.vim to match the new configuration model and fallback behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
src/testdir/test_plugin_zip.vim Updates tests for new List-based command variables and adds Windows/PowerShell-related coverage.
runtime/doc/pi_zip.txt Documents the new configuration variables and updated PowerShell fallback behavior.
runtime/autoload/zip.vim Implements new configurable command lists, adjusts GNU/PowerShell execution flow, and adds g:zip_pwsh selection logic.
Comments suppressed due to low confidence (6)

runtime/autoload/zip.vim:130

  • s:SafeExecutable() checks executable(g:zip_update[0]) instead of the passed-in executable (a:exe). This makes zip.vim abort on systems without "zip" even when only "unzip" is needed (e.g. browsing/reading), and defeats the per-command checks for g:zip_browse/read/extract.
  if !executable(g:zip_update[0]) && !s:isPS()

runtime/autoload/zip.vim:110

  • echohl will error for unknown highlight groups; "Note" is not a standard group. Use an existing group (e.g. MoreMsg) to avoid E411 when &shell is cmd.exe.
  call s:Mess('Note', "***note*** (zip) you can set shell option or `g:zip_pwsh` to enable powershell fallback")

runtime/autoload/zip.vim:423

  • The zip#Write() missing-executable error message incorrectly labels itself as (zip#Read), which is confusing when troubleshooting.
    call s:Mess('Error', "***error*** (zip#Read) sorry, your system doesn't appear to have the ".join(g:zip_update)." program")

runtime/autoload/zip.vim:506

  • This redraw is only needed when PowerShell is used (see the comment about PowerShell ProgressAction output). The current condition runs it when PowerShell is NOT used.
  if !s:isPS()

src/testdir/test_plugin_zip.vim:25

  • This test mutates the global 'shell' option but never restores it, which can leak state into subsequent tests. Prefer configuring the new g:zip_pwsh knob for the PowerShell fallback, or save/restore &shell.
  ### Windows OS: PowerShell fallback should be manually set up
  if &shell =~? 'cmd'
    set shell=powershell
  endif

runtime/doc/pi_zip.txt:84

  • Minor punctuation/wording issues in the updated PowerShell fallback description (extra space before comma, and missing double-space after the sentence) reduce readability and don't match typical help style.
   using PowerShell , the plugin will fall back to a PowerShell cmdlet.
   The PowerShell cmdlets are limited: they cannot write or extract files
   within subdirectories of a zip archive.  The advantage, however, is that
   no separate unzip binary needs to be installed. The 'shell' option will
   be checked whether to choose "pwsh" or "powershell", or use 'g:zip_pwsh'

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread runtime/autoload/zip.vim
Comment thread runtime/autoload/zip.vim
Comment on lines +22 to +25
### Windows OS: PowerShell fallback should be manually set up
if &shell =~? 'cmd'
set shell=powershell
endif

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread runtime/doc/pi_zip.txt Outdated
@curbe454

Copy link
Copy Markdown
Contributor Author

so you think this is worthwhile? or shall we rather drop it?

I thnk at least (runtime(zip): Compatability for both powershell 5 and pwsh 7) is meaningful.

@curbe454

Copy link
Copy Markdown
Contributor Author
@7z l -ba %* | awk.exe "{ gsub(/\\/, \""/\"", $6); print ($3 ~ /D[^[:space:]]{4}/ ? $6 \""/\"" : $6)}"

They are still not elegant. Maybe we should provide an API to let users to format the outputs of commands, which may solve special character problem.

I tested providng a new API on my device (which is not committed):

if !exists("g:zip_browse_format")
  let g:zip_browse_tailcmd= [""]
endif

" ...

  let gnu_cmd = 'keepj sil r! ' . join(g:zip_browse) . ' ' . s:Escape(a:zipfile, 1) . ' ' . join(g:zip_browse_tailcmd)

And the configuration becomes to(on Windows):

let g:zip_browse  = ["7z l -ba"]
let g:zip_browse_format  = ["|", "awk", "'" . '{ gsub(/\\/, \\\"/\\\", $6); print ($3 ~ /D[^[:space:]]{4}/ ? $6 \\\"/\\\" : $6) }' . "'"]

It's kind of painful to debug to right codes because of escape characters. Or provide a customizable function g:zip_browse_format to format outputs, but this means manipulting strings with vimscript; and cannot make use of stream mechanism of pipe, which is more efficient.

So do not provide this API is better. Users can do some hack on the new global variables (g:zip_browse, ...). Examples see my comments above.

@chrisbra

chrisbra commented Jul 30, 2026

Copy link
Copy Markdown
Member

Okay. I merged in untime(zip): Compatability for both powershell 5 and pwsh 7 and fixed a few bugs in that commit. I also included the change for the SafeExecutable().

The thing I do not like about the new variables is, that you are changing the default types to a list. That will bite each user who has been customizing those variables, so I left that out.

Have a look at it and see if this works for you. Thanks!

chrisbra pushed a commit that referenced this pull request Jul 30, 2026
related: #20832

Co-Authored-By: curbe454 <asu454@outlook.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
zeertzjq added a commit to neovim/neovim that referenced this pull request Jul 31, 2026
… 5 and pwsh 7

related: vim/vim#20832

vim/vim@b0e0b22

Co-authored-by: curbe454 <asu454@outlook.com>
Co-Authored-By: curbe454 <asu454@outlook.com>
runtime(zip): Improve Compatibility for powershell 5 and pwsh 7

related: vim#20832

Co-Authored-By: curbe454 <asu454@outlook.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
@curbe454 curbe454 changed the title runtime(zip)!: Customizable zip/unzip commands runtime(zip): Customizable zip/unzip commands Jul 31, 2026
@curbe454

Copy link
Copy Markdown
Contributor Author

The thing I do not like about the new variables is, that you are changing the default types to a list. That will bite each user who has been customizing those variables, so I left that out.

I'm glad to know your attitude to destructive changes. But my original purpose is to introduce customizable zip/unzip commands. I found a hard encoding issue, then found this API is not reasonable.

let gnu_cmd = "keepj sil r! " . g:zip_unzipcmd . " -Z1 -- " . s:Escape(a:zipfile, 1)

If anyone configure to let g:zip_cmd = '7z' the gnu_cmd would be keepj sil r! 7z -Z1 -- FPATH, which always fails. This means the original API exposure is unavailable, or useless. So I introduced the destructive changes.

If destructive changes are not expected, we can cosider another way to implement the functionality. For example, expose more variables.

if !exists("g:zip_zipcmd_deleteopt")
let g:zip_zipcmd_deleteopt= "-d"
endif
if !exists("g:zip_zipcmd_updateopt")
let g:zip_zipcmd_updateopt= "-u"
endif
if !exists("g:zip_unzipcmd")
let g:zip_unzipcmd= "unzip"
endif
if !exists("g:zip_unzipcmd_browseopt")
let g:zip_unzipcmd_browseopt= "-Z1 --"
endif
if !exists("g:zip_unzipcmd_readopt")
let g:zip_unzipcmd_readopt= "-p --"
endif
if !exists("g:zip_extractcmd")
let g:zip_extractcmd= g:zip_unzipcmd
endif
if !exists("g:zip_extractcmd_extractopt")
let g:zip_extractcmd_extractopt= "-o"
endif

@chrisbra

Copy link
Copy Markdown
Member

Well, the other alternative would be to use vim variarbles, that are set by the existing variables if those exists, something like this:

if !exists('g:zip_zipcmd_list')
  if exists('g:zip_zipcmd')
    let g:zip_zipcmd_list = [g:zip_zipcmd, '-d']
  else
    let g:zip_zipcmd_list = ['zip', '-d']
  endif
endif

MariaSolOs pushed a commit to MariaSolOs/neovim that referenced this pull request Aug 3, 2026
… 5 and pwsh 7

related: vim/vim#20832

vim/vim@b0e0b22

Co-authored-by: curbe454 <asu454@outlook.com>
Co-Authored-By: curbe454 <asu454@outlook.com>
erdivartanovich pushed a commit to erdivartanovich/neovim that referenced this pull request Aug 4, 2026
… 5 and pwsh 7

related: vim/vim#20832

vim/vim@b0e0b22

Co-authored-by: curbe454 <asu454@outlook.com>
Co-Authored-By: curbe454 <asu454@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants