mirror of
https://github.com/neovim/neovim.git
synced 2026-11-06 05:16:52 +00:00
Problem:
- `vim.fs.abspath('fugitive:///a')` returns "<CWD>/fugitive:/a", so
filetype functions get a mangled path for URI buffers.
- `vim.fs.root()` doesn't accept "file://" URIs.
- No URI parser, so modules use ad-hoc scheme patterns, e.g.
`vim.ui.open('~/a:b')` treats the path as a URL.
Solution:
- Introduce `vim.uri.parse()`, and use it everywhere.
- DWIM: a one-letter "scheme" is a drive letter.
- `vim.fs.abspath()` returns URLs as-is.
- Expose `vim.uri` a module. Deprecate the old `vim.uri_xx()` names.
238 lines
7.7 KiB
Lua
238 lines
7.7 KiB
Lua
local t = require('test.testutil')
|
|
local n = require('test.functional.testnvim')()
|
|
|
|
local describe, it, before_each = t.describe, t.it, t.before_each
|
|
local eq = t.eq
|
|
local ok = t.ok
|
|
local exec_lua = n.exec_lua
|
|
local clear = n.clear
|
|
local feed = n.feed
|
|
local eval = n.eval
|
|
local is_ci = t.is_ci
|
|
local is_os = t.is_os
|
|
local poke_eventloop = n.poke_eventloop
|
|
|
|
describe('vim.ui', function()
|
|
before_each(function()
|
|
clear({ args_rm = { '-u' }, args = { '--clean' } })
|
|
end)
|
|
|
|
describe('input()', function()
|
|
it('can input text', function()
|
|
local result = exec_lua [[
|
|
local opts = {
|
|
prompt = 'Input: ',
|
|
}
|
|
local input
|
|
local cb = function(item)
|
|
input = item
|
|
end
|
|
-- input would require input and block the test;
|
|
local prompt
|
|
vim.fn.input = function(opts)
|
|
prompt = opts.prompt
|
|
return "Inputted text"
|
|
end
|
|
vim.ui.input(opts, cb)
|
|
vim.wait(100, function() return input ~= nil end)
|
|
return {input, prompt}
|
|
]]
|
|
eq('Inputted text', result[1])
|
|
eq('Input: ', result[2])
|
|
end)
|
|
|
|
it('can input text on nil opt', function()
|
|
feed(':lua vim.ui.input(nil, function(input) result = input end)<cr>')
|
|
eq('', eval('v:errmsg'))
|
|
feed('Inputted text<cr>')
|
|
eq('Inputted text', exec_lua('return result'))
|
|
end)
|
|
|
|
it('can input text on {} opt', function()
|
|
feed(':lua vim.ui.input({}, function(input) result = input end)<cr>')
|
|
eq('', eval('v:errmsg'))
|
|
feed('abcdefg<cr>')
|
|
eq('abcdefg', exec_lua('return result'))
|
|
end)
|
|
|
|
it('can input empty text #18144', function()
|
|
feed(':lua vim.ui.input({}, function(input) result = input end)<cr>')
|
|
feed('<cr>')
|
|
eq('', exec_lua('return result'))
|
|
end)
|
|
|
|
it('can input empty text with cancelreturn opt #18144', function()
|
|
feed(':lua vim.ui.input({ cancelreturn = "CANCEL" }, function(input) result = input end)<cr>')
|
|
feed('<cr>')
|
|
eq('', exec_lua('return result'))
|
|
end)
|
|
|
|
it('can return nil when aborted with ESC #18144', function()
|
|
feed(':lua result = "on_confirm not called"<cr>')
|
|
feed(':lua vim.ui.input({}, function(input) result = input end)<cr>')
|
|
feed('Inputted Text<esc>')
|
|
-- Note: When `result == nil`, exec_lua('returns result') returns vim.NIL
|
|
eq(true, exec_lua('return (nil == result)'))
|
|
end)
|
|
|
|
it('can return opts.cancelreturn when aborted with ESC with cancelreturn opt #18144', function()
|
|
feed(':lua result = "on_confirm not called"<cr>')
|
|
feed(':lua vim.ui.input({ cancelreturn = "CANCEL" }, function(input) result = input end)<cr>')
|
|
feed('Inputted Text<esc>')
|
|
eq('CANCEL', exec_lua('return result'))
|
|
end)
|
|
|
|
it('can return nil when interrupted with Ctrl-C #18144', function()
|
|
feed(':lua result = "on_confirm not called"<cr>')
|
|
feed(':lua vim.ui.input({}, function(input) result = input end)<cr>')
|
|
poke_eventloop() -- This is needed because Ctrl-C flushes input
|
|
feed('Inputted Text<c-c>')
|
|
eq(true, exec_lua('return (nil == result)'))
|
|
end)
|
|
|
|
it(
|
|
'can return the identical object when an arbitrary opts.cancelreturn object is given',
|
|
function()
|
|
feed(':lua fn = function() return 42 end<CR>')
|
|
eq(42, exec_lua('return fn()'))
|
|
feed(':lua vim.ui.input({ cancelreturn = fn }, function(input) result = input end)<cr>')
|
|
feed('cancel<esc>')
|
|
eq(true, exec_lua('return (result == fn)'))
|
|
eq(42, exec_lua('return result()'))
|
|
end
|
|
)
|
|
end)
|
|
|
|
describe('open()', function()
|
|
it('validation', function()
|
|
if is_os('win') or not is_ci('github') then
|
|
exec_lua [[vim.system = function() return { wait=function() return { code=3 } end } end]]
|
|
end
|
|
if not is_os('bsd') then
|
|
local rv = exec_lua([[
|
|
local cmd, err = vim.ui.open('non-existent-file')
|
|
if err and err:find('no handler found') then
|
|
return -1
|
|
end
|
|
return cmd:wait(100).code
|
|
]])
|
|
ok(type(rv) == 'number' and rv ~= 0, 'nonzero exit code', rv)
|
|
end
|
|
|
|
exec_lua [[
|
|
vim.fn.has = function() return 0 end
|
|
vim.fn.executable = function() return 0 end
|
|
]]
|
|
eq(
|
|
'vim.ui.open: no handler found (tried: wslview, explorer.exe, xdg-open, lemonade)',
|
|
exec_lua [[local _, err = vim.ui.open('foo') ; return err]]
|
|
)
|
|
end)
|
|
|
|
it('escapes cmd.exe metacharacters #41337', function()
|
|
eq(
|
|
{
|
|
{ 'cmd.exe', '/c', 'start', '', 'https://example.com/?q=^&^|^<^>^^^%^!' },
|
|
{ 'cmd.exe', '/c', 'start', '', 'C:/a^&b.pdf' },
|
|
-- Not escaped: libuv quotes it.
|
|
{ 'cmd.exe', '/c', 'start', '', 'C:/a b&c.pdf' },
|
|
},
|
|
exec_lua(function()
|
|
vim.fn.has = function(feat)
|
|
return feat == 'win32' and 1 or 0
|
|
end
|
|
local captured --- @type string[]
|
|
vim.system = function(cmd)
|
|
captured = cmd
|
|
return {
|
|
wait = function()
|
|
return { code = 0 }
|
|
end,
|
|
}
|
|
end
|
|
local cmds = {} --- @type string[][]
|
|
for _, path in ipairs({ 'https://example.com/?q=&|<>^%!', 'C:/a&b.pdf', 'C:/a b&c.pdf' }) do
|
|
vim.ui.open(path)
|
|
cmds[#cmds + 1] = captured
|
|
end
|
|
return cmds
|
|
end)
|
|
)
|
|
end)
|
|
|
|
it('opt.cmd #29490', function()
|
|
t.matches(
|
|
'ENOENT: no such file or directory',
|
|
t.pcall_err(exec_lua, function()
|
|
vim.ui.open('foo', { cmd = { 'non-existent-tool' } })
|
|
end)
|
|
)
|
|
|
|
eq(
|
|
{
|
|
code = 0,
|
|
signal = 0,
|
|
stderr = '',
|
|
stdout = 'arg1=arg1;arg2=https://example.com;',
|
|
},
|
|
exec_lua(function(cmd_)
|
|
local cmd, err = vim.ui.open('https://example.com', { cmd = cmd_ })
|
|
assert(cmd and not err)
|
|
return cmd:wait()
|
|
end, { n.testprg('printargs-test'), 'arg1' })
|
|
)
|
|
|
|
-- Expands a path containing ":".
|
|
eq(
|
|
('arg1=%s/a:b;'):format(vim.fs.normalize('~')),
|
|
exec_lua(function(opener)
|
|
return vim.ui.open('~/a:b', { cmd = { opener } }):wait().stdout
|
|
end, n.testprg('printargs-test'))
|
|
)
|
|
end)
|
|
|
|
it('can reuse opt.cmd without accumulating paths', function()
|
|
eq(
|
|
'arg1=arg1;arg2=https://example.com/second;',
|
|
exec_lua(function(opener)
|
|
local opts = { cmd = { opener, 'arg1' } }
|
|
vim.ui.open('https://example.com/first', opts):wait()
|
|
return vim.ui.open('https://example.com/second', opts):wait().stdout
|
|
end, n.testprg('printargs-test'))
|
|
)
|
|
end)
|
|
|
|
it('gx on a help tag opens URL', function()
|
|
n.command('helptags $VIMRUNTIME/doc')
|
|
n.command('help nvim.txt')
|
|
|
|
local link_ns = n.api.nvim_create_namespace('nvim.help.urls')
|
|
local tag = n.api.nvim_buf_get_extmarks(0, link_ns, 0, -1, {
|
|
limit = 1,
|
|
details = true,
|
|
})[1]
|
|
|
|
local url = tag[4].url
|
|
assert(url)
|
|
|
|
--- points to the neovim.io site
|
|
eq(true, vim.startswith(url, 'https://neovim.io/doc'))
|
|
|
|
-- tag is URI encoded
|
|
local param = url:match('%?tag=(.*)')
|
|
local tagname =
|
|
n.api.nvim_buf_get_text(0, tag[2], tag[3], tag[4].end_row, tag[4].end_col, {})[1]
|
|
eq(vim.uri.encode(tagname), param)
|
|
|
|
-- non-nvim tags are ignored
|
|
local buf = n.api.nvim_create_buf(false, false)
|
|
n.api.nvim_buf_set_lines(buf, 0, 0, false, {
|
|
'|nonexisting|',
|
|
})
|
|
n.api.nvim_set_option_value('filetype', 'help', { buf = buf, scope = 'local' })
|
|
local tags = n.api.nvim_buf_get_extmarks(buf, link_ns, 0, -1, {})
|
|
eq(0, #tags)
|
|
end)
|
|
end)
|
|
end)
|