mirror of
https://github.com/neovim/neovim.git
synced 2026-08-17 18:14:46 -05:00
Merge #41093 from barrettruth/feat/zip-finish
This commit is contained in:
+1
-1
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
+31
-19
@@ -10,6 +10,15 @@ local function unzip()
|
||||
if command == '' then
|
||||
return nil, 'unzip executable not found'
|
||||
end
|
||||
-- Windows searches the current directory before $PATH, so an archive could be opened with an
|
||||
-- `unzip` shipped next to it.
|
||||
if vim.fn.has('win32') == 1 then
|
||||
local dir = uv.fs_realpath(vim.fs.dirname(vim.fs.normalize(command)))
|
||||
local cwd = uv.fs_realpath(vim.fn.getcwd())
|
||||
if dir and cwd and dir == cwd then
|
||||
return nil, 'refusing to run unzip from the current directory'
|
||||
end
|
||||
end
|
||||
return command
|
||||
end
|
||||
|
||||
@@ -302,33 +311,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.
|
||||
@@ -383,13 +395,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)
|
||||
@@ -475,7 +487,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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,11 +325,24 @@ 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)
|
||||
|
||||
it('runs the backend when the cwd is its directory', function()
|
||||
t.skip(t.is_os('win'), 'N/A: Windows searches the cwd before $PATH')
|
||||
local archive = stage(fixtures, 'browser.zip')
|
||||
local bin = vim.fs.joinpath(assert(vim.uv.fs_realpath(root)), 'bin')
|
||||
t.mkdir(bin)
|
||||
assert(vim.uv.fs_symlink(vim.fn.exepath('unzip'), vim.fs.joinpath(bin, 'unzip')))
|
||||
n.clear({ args = { '--clean' }, env = { PATH = bin .. ':' .. os.getenv('PATH') } })
|
||||
|
||||
api.nvim_set_current_dir(bin)
|
||||
edit(archive)
|
||||
eq('folder/', lines()[1])
|
||||
end)
|
||||
|
||||
it('keeps a leading dash in a path from becoming a backend option', function()
|
||||
local archive = stage(old_samples, 'poc.zip')
|
||||
clear_zip()
|
||||
@@ -335,7 +350,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 +369,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 +412,11 @@ describe('nvim.zip', function()
|
||||
api.nvim_win_set_cursor(0, { line_of('star*.txt'), 0 })
|
||||
feed('<CR>')
|
||||
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 +433,7 @@ describe('nvim.zip', function()
|
||||
exec_lua(function(uri)
|
||||
vim.api.nvim_input('hunter2<CR>')
|
||||
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 +447,7 @@ describe('nvim.zip', function()
|
||||
exec_lua(function(uri)
|
||||
vim.api.nvim_input('no1<CR>no2<CR>no3<CR>')
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user