mirror of
https://github.com/neovim/neovim.git
synced 2026-08-31 03:13:48 +00:00
feat(health): vim.ui.img healthcheck #39449
This commit is contained in:
@@ -91,4 +91,52 @@ function M.query(caps, cb)
|
||||
end)
|
||||
end
|
||||
|
||||
--- Send an APC sequence to the terminal and call {cb} for each APC response received.
|
||||
--- Cleans up after {timeout} milliseconds if no response is received.
|
||||
---
|
||||
--- {cb} receives the full APC sequence including the `\027_` prefix.
|
||||
--- Return `true` from {cb} to stop listening.
|
||||
---
|
||||
---@param payload string APC sequence to send (full escape sequence including prefix/suffix)
|
||||
---@param opts {timeout?:integer} Options table (timeout in milliseconds, default 1000)
|
||||
---@param cb fun(resp:string):boolean? Callback invoked for each APC TermResponse
|
||||
---@overload fun(payload:string, cb:fun(resp:string):boolean?)
|
||||
function M.query_apc(payload, opts, cb)
|
||||
if type(opts) == 'function' then
|
||||
cb = opts
|
||||
opts = {}
|
||||
end
|
||||
|
||||
vim.validate('payload', payload, 'string')
|
||||
vim.validate('opts', opts, 'table')
|
||||
vim.validate('cb', cb, 'function')
|
||||
|
||||
local timeout = opts and opts.timeout or 1000
|
||||
local timer = assert(vim.uv.new_timer())
|
||||
|
||||
local id = vim.api.nvim_create_autocmd('TermResponse', {
|
||||
nested = true,
|
||||
callback = function(ev)
|
||||
local resp = ev.data.sequence ---@type string
|
||||
if resp:match('^\027_') then
|
||||
if not timer:is_closing() then
|
||||
timer:close()
|
||||
end
|
||||
return cb(resp)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_ui_send(payload)
|
||||
|
||||
timer:start(timeout, 0, function()
|
||||
vim.schedule(function()
|
||||
pcall(vim.api.nvim_del_autocmd, id)
|
||||
end)
|
||||
if not timer:is_closing() then
|
||||
timer:close()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -113,6 +113,17 @@ function M.del(id)
|
||||
return true
|
||||
end
|
||||
|
||||
---@private
|
||||
--- Query whether the host terminal supports displaying images.
|
||||
--- Blocks until the terminal responds or times out.
|
||||
---
|
||||
---@param opts? {timeout?: integer} timeout in milliseconds (default: 1000)
|
||||
---@return boolean supported true if the terminal supports image display
|
||||
---@return string? msg error detail if the terminal responded but not with OK
|
||||
function M._supported(opts)
|
||||
return require('vim.ui.img._kitty').supported(opts)
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd('VimLeavePre', {
|
||||
callback = function()
|
||||
---@type integer[]
|
||||
|
||||
@@ -147,4 +147,50 @@ function M.delete(img_id)
|
||||
}))
|
||||
end
|
||||
|
||||
--- Query whether this terminal supports the kitty graphics protocol.
|
||||
--- Blocks until the terminal responds or times out.
|
||||
---
|
||||
---@param opts? {timeout?: integer} timeout in milliseconds (default: 1000)
|
||||
---@return boolean supported
|
||||
---@return string? msg error detail if terminal responded but not with OK
|
||||
function M.supported(opts)
|
||||
local timeout = opts and opts.timeout or 1000
|
||||
|
||||
-- Do not use APC on terminals that echo unknown sequences
|
||||
if vim.env.TERM_PROGRAM == 'Apple_Terminal' then
|
||||
return false
|
||||
end
|
||||
|
||||
local query_id = generate_id()
|
||||
|
||||
---@type boolean?
|
||||
local result
|
||||
---@type string?
|
||||
local msg
|
||||
|
||||
require('vim.tty').query_apc(
|
||||
seq({ a = 'q', i = query_id, s = 1, v = 1 }),
|
||||
{ timeout = timeout },
|
||||
function(resp)
|
||||
-- kitty APC response: \027_G[<fields>,]i=<id>[,<fields>];<status>
|
||||
-- status is "OK" or an error code+message like "ENODATA:Missing image data"
|
||||
local id = resp:match('^\027_G[^;]*i=(%d+)')
|
||||
local status = resp:match(';(.-)%s*$')
|
||||
if id and tonumber(id) == query_id and status then
|
||||
result = true
|
||||
msg = status ~= 'OK' and status or nil
|
||||
return true
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
-- Wait in a blocking fashion for the response, checking
|
||||
-- at least every 200ms, or faster if the timeout is small
|
||||
vim.wait(timeout + 100, function()
|
||||
return result ~= nil
|
||||
end, math.max(math.min(math.ceil(timeout / 10), 200), 1))
|
||||
|
||||
return result == true, msg
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,51 +1,23 @@
|
||||
local M = {}
|
||||
local health = vim.health
|
||||
|
||||
local function system(cmd)
|
||||
local result = vim.system(cmd, { text = true }):wait()
|
||||
if not result then -- Workaround https://github.com/neovim/neovim/issues/37922
|
||||
return false, 'command failed'
|
||||
end
|
||||
return result.code == 0, vim.trim(('%s\n%s'):format(result.stdout, result.stderr))
|
||||
end
|
||||
|
||||
local function get_tmux_option(option)
|
||||
local cmd = { 'tmux', 'show-option', '-qvg', option } -- try global scope
|
||||
local ok, out = system(cmd)
|
||||
local val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
|
||||
if not ok then
|
||||
health.error(('command failed: %s\n%s'):format(vim.inspect(cmd), out))
|
||||
return 'error'
|
||||
elseif val == '' then
|
||||
cmd = { 'tmux', 'show-option', '-qvgs', option } -- try session scope
|
||||
ok, out = system(cmd)
|
||||
val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
|
||||
if not ok then
|
||||
health.error(('command failed: %s\n%s'):format(vim.inspect(cmd), out))
|
||||
return 'error'
|
||||
end
|
||||
end
|
||||
return val
|
||||
end
|
||||
|
||||
function M.check()
|
||||
health.start('vim.ui.img')
|
||||
|
||||
if not vim.env.TMUX or vim.fn.executable('tmux') == 0 then
|
||||
health.ok('no terminal multiplexer detected')
|
||||
return
|
||||
local supported, msg = require('vim.ui.img')._supported()
|
||||
|
||||
if supported then
|
||||
if msg then
|
||||
health.ok(('Graphics protocol: supported (%s)'):format(msg))
|
||||
else
|
||||
health.ok('Graphics protocol: supported')
|
||||
end
|
||||
else
|
||||
health.error('Graphics protocol: not supported by this terminal.')
|
||||
end
|
||||
|
||||
local passthrough = get_tmux_option('allow-passthrough')
|
||||
if passthrough ~= 'error' then
|
||||
if passthrough == 'on' or passthrough == 'all' then
|
||||
health.ok('allow-passthrough: ' .. passthrough)
|
||||
else
|
||||
health.error(
|
||||
'`allow-passthrough` is not enabled. Images will not be displayed.',
|
||||
{ 'Add to ~/.tmux.conf:\nset-option -g allow-passthrough on' }
|
||||
)
|
||||
end
|
||||
if vim.env.TMUX then
|
||||
health.warn('tmux is detected. Images may not display correctly.')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user