diff --git a/runtime/doc/zip.txt b/runtime/doc/zip.txt index 03dc389f8b..fbaf77c405 100644 --- a/runtime/doc/zip.txt +++ b/runtime/doc/zip.txt @@ -8,7 +8,7 @@ Builtin plugin: zip *zip* Nvim opens a read-only listing when |:edit| is used with a zip archive. The listing is a |dir| buffer with 'filetype' set to "zip"; entries open as -read-only `zipfile://{archive}::{path}` buffers. Requires the `unzip` +read-only `zip://{archive}/{path}` buffers. Requires the `unzip` executable. Recognized extensions include zip, jar, apk, epub, Office and OpenDocument diff --git a/runtime/ftplugin/java.vim b/runtime/ftplugin/java.vim index 00d0532dd6..509f5d146b 100644 --- a/runtime/ftplugin/java.vim +++ b/runtime/ftplugin/java.vim @@ -80,8 +80,11 @@ if exists("g:ftplugin_java_source_path") && function! JavaFileTypeZipFile() abort let l:member = substitute(v:fname, '\.', '/', 'g') . '.java' - return 'zipfile://' . get(s:zip_files, bufnr('%'), s:zip_files[0]) . - \ '::' . l:member + let l:archive = get(s:zip_files, bufnr('%'), s:zip_files[0]) + " The builtin plugin joins the paths; zipPlugin.vim separates them with "::". + return exists('#nvim.zip') + \ ? 'zip://' . l:archive . '/' . l:member + \ : 'zipfile://' . l:archive . '::' . l:member endfunction " E120 for "inex=s:JavaFileTypeZipFile()" before v8.2.3900. @@ -391,7 +394,11 @@ if exists("s:zip_func_upgradable") def! s:JavaFileTypeZipFile(): string const member: string = substitute(v:fname, '\.', '/', 'g') .. '.java' - return 'zipfile://' .. get(zip_files, bufnr('%'), zip_files[0]) .. '::' .. member + const archive: string = get(zip_files, bufnr('%'), zip_files[0]) + # The builtin plugin joins the paths; zipPlugin.vim separates them with "::". + return exists('#nvim.zip') + ? 'zip://' .. archive .. '/' .. member + : 'zipfile://' .. archive .. '::' .. member enddef setlocal includeexpr=s:JavaFileTypeZipFile() diff --git a/runtime/lua/nvim/zip.lua b/runtime/lua/nvim/zip.lua index 47f7da80ec..32eb97476c 100644 --- a/runtime/lua/nvim/zip.lua +++ b/runtime/lua/nvim/zip.lua @@ -307,33 +307,36 @@ local function read_tempfile(buf, temp) set_readonly(buf) end ---- Parse a `zipfile://` buffer name, as used by quickfix and direct `:edit`. ----@param name string `zipfile://{archive}::{path}` +--- Resolve a `zip://` buffer name, as used by quickfix and direct `:edit`. +--- +--- The archive path and the entry path are simply joined, so the split is found by walking +--- components: the first one that is a regular file is the archive, because a regular file +--- cannot have children on disk. Entry paths may therefore contain any character. +---@param name string `zip://{archive}/{path}` ---@return string?, string? archive and entry path -local function parse_uri(name) - if not vim.startswith(name, 'zipfile://') then +local function resolve_uri(name) + if not vim.startswith(name, 'zip://') then return end - local value = name:sub(11) - local separator ---@type integer? + local value = name:sub(7) local offset = 1 while true do - local next_separator = value:find('::', offset, true) - if not next_separator then - break + local separator = value:find('/', offset + 1, true) + if not separator then + return end - separator = next_separator - offset = next_separator + 2 + local archive = value:sub(1, separator - 1) + local stat = uv.fs_stat(archive) + if stat and stat.type == 'file' then + return archive, value:sub(separator + 1) + end + offset = separator end - if not separator then - return - end - return value:sub(1, separator - 1), value:sub(separator + 2) end ---@class (private) nvim.zip.State ---@field source string Path to the archive. ----@field path? string Archive path shown by a `zipfile://` buffer. +---@field path? string Archive path shown by a `zip://` buffer. ---@field paths? string[] Entry paths carried over from the initial listing. ---@field prefix? string Archive directory currently listed. ---@field pending_prefix? string Prefix to commit once the backend succeeds. @@ -388,13 +391,13 @@ end --- Read one archive entry into a read-only buffer. ---@param buf integer Target entry buffer. ----@param name string `zipfile://` buffer name. +---@param name string `zip://` buffer name. function M.read(buf, name) buf = vim._resolve_bufnr(buf) local state = get_state(buf) local source, path = state and state.source, state and state.path if not source or not path then - source, path = parse_uri(name) + source, path = resolve_uri(name) end if not source or not path then set_readonly(buf) @@ -480,7 +483,7 @@ function M.open(buf, name, entry) require('nvim.dir').open(buf, name, M) return end - local uri = ('zipfile://%s::%s'):format(state.source, path) + local uri = ('zip://%s/%s'):format(state.source, path) local entry_buf = vim.fn.bufadd(uri) set_state(entry_buf, { source = state.source, path = path }) api.nvim_cmd({ diff --git a/runtime/plugin/zip.lua b/runtime/plugin/zip.lua index 8ba5aaa6c6..dc3f09c9c9 100644 --- a/runtime/plugin/zip.lua +++ b/runtime/plugin/zip.lua @@ -86,7 +86,7 @@ end api.nvim_create_autocmd('BufReadCmd', { group = group, - pattern = 'zipfile://*', + pattern = 'zip://*', desc = 'Read zip archive entry', callback = function(ev) if legacy_loaded() then diff --git a/test/functional/plugin/zip_spec.lua b/test/functional/plugin/zip_spec.lua index 41f87370fb..e8ce394e8f 100644 --- a/test/functional/plugin/zip_spec.lua +++ b/test/functional/plugin/zip_spec.lua @@ -196,7 +196,7 @@ describe('nvim.zip', function() it('opens entries at quickfix locations', function() local archive = stage(fixtures, 'browser.zip') clear_zip() - local uri = ('zipfile://%s::crlf.txt'):format(archive) + local uri = ('zip://%s/crlf.txt'):format(archive) fn.setqflist({}, 'r', { items = { { filename = uri, lnum = 2, col = 1 } } }) api.nvim_cmd({ cmd = 'cfirst' }, {}) @@ -220,12 +220,12 @@ describe('nvim.zip', function() local archive = stage(fixtures, 'browser.zip') clear_zip() - edit(('zipfile://%s::crlf.txt'):format(archive)) + edit(('zip://%s/crlf.txt'):format(archive)) eq({ 'one', 'two' }, lines()) eq('dos', api.nvim_get_option_value('fileformat', { buf = 0 })) eq(true, api.nvim_get_option_value('endofline', { buf = 0 })) - edit(('zipfile://%s::noeol.txt'):format(archive)) + edit(('zip://%s/noeol.txt'):format(archive)) eq({ 'no final newline' }, lines()) eq(false, api.nvim_get_option_value('endofline', { buf = 0 })) end) @@ -300,7 +300,9 @@ describe('nvim.zip', function() feed('gf') poke_eventloop() - eq(('zipfile://%s::folder/root.java'):format(archive), api.nvim_buf_get_name(0)) + local uri = legacy and ('zipfile://%s::folder/root.java'):format(archive) + or ('zip://%s/folder/root.java'):format(archive) + eq(uri, api.nvim_buf_get_name(0)) eq({ 'class root {}' }, lines()) eq('java', api.nvim_get_option_value('filetype', { buf = 0 })) end @@ -323,7 +325,7 @@ describe('nvim.zip', function() { [[zipglob/a\\.txt]], [[a test file with a double \]] }, } for _, case in ipairs(cases) do - edit(('zipfile://%s::%s'):format(archive, case[1])) + edit(('zip://%s/%s'):format(archive, case[1])) eq({ case[2] }, lines()) end end) @@ -335,7 +337,7 @@ describe('nvim.zip', function() edit(archive) eq({ '-d/', 'pwned' }, lines()) - edit(('zipfile://%s::-d/tmp'):format(archive)) + edit(('zip://%s/-d/tmp'):format(archive)) eq({ '' }, lines()) end) @@ -354,7 +356,7 @@ describe('nvim.zip', function() poke_eventloop() eq({ 'nested payload' }, lines()) - local uri = ('zipfile://%s::crlf.txt'):format(archive) + local uri = ('zip://%s/crlf.txt'):format(archive) edit(uri) eq(uri, api.nvim_buf_get_name(0)) eq({ 'one', 'two' }, lines()) @@ -397,11 +399,11 @@ describe('nvim.zip', function() api.nvim_win_set_cursor(0, { line_of('star*.txt'), 0 }) feed('') poke_eventloop() - eq(('zipfile://%s::names/star*.txt'):format(archive), api.nvim_buf_get_name(0)) + eq(('zip://%s/names/star*.txt'):format(archive), api.nvim_buf_get_name(0)) eq({ 'content of star*.txt' }, lines()) for _, name in ipairs(names) do - edit(('zipfile://%s::names/%s'):format(archive, name)) + edit(('zip://%s/names/%s'):format(archive, name)) eq({ 'content of ' .. name }, lines()) end end) @@ -418,7 +420,7 @@ describe('nvim.zip', function() exec_lua(function(uri) vim.api.nvim_input('hunter2') vim.api.nvim_cmd({ cmd = 'edit', args = { uri }, magic = { file = false, bar = false } }, {}) - end, ('zipfile://%s::secret.txt'):format(archive)) + end, ('zip://%s/secret.txt'):format(archive)) poke_eventloop() eq({ 'secret content' }, lines()) @@ -432,7 +434,7 @@ describe('nvim.zip', function() exec_lua(function(uri) vim.api.nvim_input('no1no2no3') vim.api.nvim_cmd({ cmd = 'edit', args = { uri }, magic = { file = false, bar = false } }, {}) - end, ('zipfile://%s::secret.txt'):format(archive)) + end, ('zip://%s/secret.txt'):format(archive)) poke_eventloop() eq(true, exec_capture('messages'):find('incorrect password', 1, true) ~= nil)