Compare commits

...

1 Commits

Author SHA1 Message Date
Justin M. Keyes
4f12cc58c8 feat(vim.ui): vim.ui.open() DWIM mode
Problem:
The `gx` mapping has too much logic in it, so the logic is not in user
configs or plugins, and it lacks test coverage.

Solution:
Move the logic into `vim.ui.open()`.
2024-09-06 13:27:05 +02:00
2 changed files with 67 additions and 52 deletions

View File

@ -97,37 +97,13 @@ do
--- Map |gx| to call |vim.ui.open| on the <cfile> at cursor.
do
local function do_open(uri)
local cmd, err = vim.ui.open(uri)
local rv = cmd and cmd:wait(1000) or nil
if cmd and rv and rv.code ~= 0 then
err = ('vim.ui.open: command %s (%d): %s'):format(
(rv.code == 124 and 'timeout' or 'failed'),
rv.code,
vim.inspect(cmd.cmd)
)
end
return err
end
local gx_desc =
'Opens filepath or URI under cursor with the system handler (file explorer, web browser, …)'
vim.keymap.set({ 'n' }, 'gx', function()
for _, url in ipairs(require('vim.ui')._get_urls()) do
local err = do_open(url)
if err then
vim.notify(err, vim.log.levels.ERROR)
end
end
vim.ui.open()
end, { desc = gx_desc })
vim.keymap.set({ 'x' }, 'gx', function()
local lines =
vim.fn.getregion(vim.fn.getpos('.'), vim.fn.getpos('v'), { type = vim.fn.mode() })
-- Trim whitespace on each line and concatenate.
local err = do_open(table.concat(vim.iter(lines):map(vim.trim):totable()))
if err then
vim.notify(err, vim.log.levels.ERROR)
end
vim.ui.open()
end, { desc = gx_desc })
end

View File

@ -124,7 +124,7 @@ end
--- end
--- ```
---
---@param path string Path or URL to open
---@param path? string Path or URL to open, or `nil` to get path or URL at cursor.
---
---@return vim.SystemObj|nil # Command object, or nil if not found.
---@return nil|string # Error message on failure, or nil on success.
@ -132,32 +132,29 @@ end
---@see |vim.system()|
function M.open(path)
vim.validate({
path = { path, 'string' },
path = { path, 'string', true },
})
local is_uri = path:match('%w+:')
if not is_uri then
path = vim.fs.normalize(path)
end
local function do_open(uri)
local cmd --- @type string[]
local opts --- @type vim.SystemOpts
opts = { text = true, detach = true }
if vim.fn.has('mac') == 1 then
cmd = { 'open', path }
cmd = { 'open', uri }
elseif vim.fn.has('win32') == 1 then
if vim.fn.executable('rundll32') == 1 then
cmd = { 'rundll32', 'url.dll,FileProtocolHandler', path }
cmd = { 'rundll32', 'url.dll,FileProtocolHandler', uri }
else
return nil, 'vim.ui.open: rundll32 not found'
end
elseif vim.fn.executable('wslview') == 1 then
cmd = { 'wslview', path }
cmd = { 'wslview', uri }
elseif vim.fn.executable('explorer.exe') == 1 then
cmd = { 'explorer.exe', path }
cmd = { 'explorer.exe', uri }
elseif vim.fn.executable('xdg-open') == 1 then
cmd = { 'xdg-open', path }
cmd = { 'xdg-open', uri }
opts.stdout = false
opts.stderr = false
else
@ -167,6 +164,48 @@ function M.open(path)
return vim.system(cmd, opts), nil
end
local function do_open2(uri)
local cmd, err = do_open(uri)
-- wait() terminates the process if necessary (avoids stale processes), and allows us to show an
-- error message if needed.
local rv = cmd and cmd:wait(1000) or nil
if cmd and rv and rv.code ~= 0 then
err = ('vim.ui.open: command %s (%d): %s'):format(
(rv.code == 124 and 'timeout' or 'failed'),
rv.code,
vim.inspect(cmd.cmd)
)
end
return err
end
if path then
local is_uri = path:match('%w+:')
if not is_uri then
path = vim.fs.normalize(path)
end
return do_open(path)
else -- DWIM mode: get the URL or path from the cursor position, and show error.
local visual = not not vim.fn.mode():match('[vV\22]')
if visual then
local lines =
vim.fn.getregion(vim.fn.getpos('.'), vim.fn.getpos('v'), { type = vim.fn.mode() })
-- Trim whitespace on each line and concatenate.
local err = do_open2(table.concat(vim.iter(lines):map(vim.trim):totable()))
if err then
vim.notify(err, vim.log.levels.ERROR)
end
else
for _, url in ipairs(M._get_urls()) do
local err = do_open2(url)
if err then
vim.notify(err, vim.log.levels.ERROR)
end
end
end
end
end
--- Returns all URLs at cursor, if any.
--- @return string[]
function M._get_urls()