commit 8c63d84be157a0a944ff9a3eb7d62c2ea2d50f94
parent 76c77bd114ee5c3be0e448018c4f3a17cf8bcf42
Author: skewb1k <skewb1kunix@gmail.com>
Date: Tue, 27 Jan 2026 16:51:00 +0300
feat(health): check `vim.ui.open()` tool #37569
Problem:
`:checkhealth` does not report when no `vim.ui.open()` handler is
available.
Solution:
Factor command resolution into `_get_open_cmd()` and reuse it from
`:checkhealth` to detect missing handlers.
Diffstat:
3 files changed, 38 insertions(+), 11 deletions(-)
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
@@ -363,6 +363,8 @@ PLUGINS
unrelated Python virtual environments are activated.
|provider-python|
+• |:checkhealth| now checks for an available |vim.ui.open()| handler.
+
STARTUP
• todo
diff --git a/runtime/lua/vim/health/health.lua b/runtime/lua/vim/health/health.lua
@@ -418,6 +418,14 @@ local function check_external_tools()
health.warn('ripgrep not available')
end
+ local open_cmd, err = vim.ui._get_open_cmd()
+ if open_cmd then
+ health.ok(('vim.ui.open: handler found (%s)'):format(open_cmd[1]))
+ else
+ --- @cast err string
+ health.warn(err)
+ end
+
-- `vim.pack` prefers git 2.36 but tries to work with 2.x.
if vim.fn.executable('git') == 1 then
local git = vim.fn.exepath('git')
diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua
@@ -166,25 +166,42 @@ function M.open(path, opt)
if opt.cmd then
cmd = vim.list_extend(opt.cmd --[[@as string[] ]], { path })
- elseif vim.fn.has('mac') == 1 then
- cmd = { 'open', path }
+ else
+ local open_cmd, err = M._get_open_cmd()
+ if err then
+ return nil, err
+ end
+ ---@cast open_cmd string[]
+ if open_cmd[1] == 'xdg-open' then
+ job_opt.stdout = false
+ job_opt.stderr = false
+ end
+ cmd = vim.list_extend(open_cmd, { path })
+ end
+
+ return vim.system(cmd, job_opt), nil
+end
+
+--- Get an available command used to open the path or URL.
+---
+--- @return string[]|nil # Command, or nil if not found.
+--- @return nil|string # Error message on failure, or nil on success.
+function M._get_open_cmd()
+ if vim.fn.has('mac') == 1 then
+ return { 'open' }, nil
elseif vim.fn.has('win32') == 1 then
- cmd = { 'cmd.exe', '/c', 'start', '', path }
+ return { 'cmd.exe', '/c', 'start', '' }, nil
elseif vim.fn.executable('xdg-open') == 1 then
- cmd = { 'xdg-open', path }
- job_opt.stdout = false
- job_opt.stderr = false
+ return { 'xdg-open' }, nil
elseif vim.fn.executable('wslview') == 1 then
- cmd = { 'wslview', path }
+ return { 'wslview' }, nil
elseif vim.fn.executable('explorer.exe') == 1 then
- cmd = { 'explorer.exe', path }
+ return { 'explorer.exe' }, nil
elseif vim.fn.executable('lemonade') == 1 then
- cmd = { 'lemonade', 'open', path }
+ return { 'lemonade', 'open' }, nil
else
return nil, 'vim.ui.open: no handler found (tried: wslview, explorer.exe, xdg-open, lemonade)'
end
-
- return vim.system(cmd, job_opt), nil
end
--- Returns all URLs at cursor, if any.