mirror of
https://github.com/folke/snacks.nvim
synced 2025-12-23 08:47:57 +00:00
fix(image): detect kitty image protocol through terminal capability request. Closes #1695
This commit is contained in:
parent
f27b8b313e
commit
43261baf87
4 changed files with 147 additions and 37 deletions
|
|
@ -3,7 +3,7 @@ local M = {}
|
|||
|
||||
---@param buf number
|
||||
---@param opts? snacks.image.Opts|{src?: string}
|
||||
function M.attach(buf, opts)
|
||||
function M._attach(buf, opts)
|
||||
opts = opts or {}
|
||||
local file = opts.src or vim.api.nvim_buf_get_name(buf)
|
||||
if not Snacks.image.supports(file) then
|
||||
|
|
@ -35,4 +35,13 @@ function M.attach(buf, opts)
|
|||
end
|
||||
end
|
||||
|
||||
---@param buf number
|
||||
---@param opts? snacks.image.Opts|{src?: string}
|
||||
function M.attach(buf, opts)
|
||||
local Terminal = require("snacks.image.terminal")
|
||||
Terminal.detect(function()
|
||||
M._attach(buf, opts)
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -431,7 +431,7 @@ function M.hover()
|
|||
end
|
||||
|
||||
---@param buf number
|
||||
function M.attach(buf)
|
||||
function M._attach(buf)
|
||||
if vim.b[buf].snacks_image_attached then
|
||||
return
|
||||
end
|
||||
|
|
@ -456,4 +456,12 @@ function M.attach(buf)
|
|||
end
|
||||
end
|
||||
|
||||
---@param buf number
|
||||
function M.attach(buf)
|
||||
local Terminal = require("snacks.image.terminal")
|
||||
Terminal.detect(function()
|
||||
M._attach(buf)
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ M.meta = {
|
|||
---@class snacks.image.Env
|
||||
---@field name string
|
||||
---@field env table<string, string|true>
|
||||
---@field terminal? string
|
||||
---@field supported? boolean default: false
|
||||
---@field placeholders? boolean default: false
|
||||
---@field setup? fun(): boolean?
|
||||
|
|
@ -230,7 +231,6 @@ function M.langs()
|
|||
return q:match("queries/(.-)/images%.scm")
|
||||
end, queries)
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param ev? vim.api.keyset.create_autocmd.callback_args
|
||||
function M.setup(ev)
|
||||
|
|
@ -238,6 +238,7 @@ function M.setup(ev)
|
|||
return
|
||||
end
|
||||
did_setup = true
|
||||
|
||||
local group = vim.api.nvim_create_augroup("snacks.image", { clear = true })
|
||||
|
||||
vim.api.nvim_create_autocmd({ "BufWipeout", "BufDelete" }, {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
---@class snacks.image.terminal
|
||||
---@field transform? fun(data: string): string
|
||||
local M = {}
|
||||
|
||||
local size ---@type snacks.image.terminal.Dim?
|
||||
|
|
@ -6,25 +7,19 @@ local size ---@type snacks.image.terminal.Dim?
|
|||
local environments = {
|
||||
{
|
||||
name = "kitty",
|
||||
env = { TERM = "kitty", KITTY_PID = true },
|
||||
terminal = "kitty",
|
||||
supported = true,
|
||||
placeholders = true,
|
||||
},
|
||||
{
|
||||
name = "ghostty",
|
||||
env = { TERM = "ghostty", GHOSTTY_BIN_DIR = true },
|
||||
terminal = "ghostty",
|
||||
supported = true,
|
||||
placeholders = true,
|
||||
},
|
||||
{
|
||||
name = "wezterm",
|
||||
env = {
|
||||
TERM = "wezterm",
|
||||
WEZTERM_PANE = true,
|
||||
WEZTERM_EXECUTABLE = true,
|
||||
WEZTERM_CONFIG_FILE = true,
|
||||
SNACKS_WEZTERM = true,
|
||||
},
|
||||
terminal = "wezterm",
|
||||
supported = true,
|
||||
placeholders = false,
|
||||
},
|
||||
|
|
@ -44,6 +39,8 @@ local environments = {
|
|||
|
||||
M._env = nil ---@type snacks.image.Env?
|
||||
|
||||
M._terminal = nil ---@type snacks.image.Terminal?
|
||||
|
||||
vim.api.nvim_create_autocmd("VimResized", {
|
||||
group = vim.api.nvim_create_augroup("snacks.image.terminal", { clear = true }),
|
||||
callback = function()
|
||||
|
|
@ -51,19 +48,6 @@ vim.api.nvim_create_autocmd("VimResized", {
|
|||
end,
|
||||
})
|
||||
|
||||
-- HACK: ghostty doesn't like it when sending images too fast,
|
||||
-- after Neovim startup, so we delay the first image
|
||||
local queue = {} ---@type string[]?
|
||||
vim.defer_fn(
|
||||
vim.schedule_wrap(function()
|
||||
for _, data in ipairs(queue or {}) do
|
||||
io.stdout:write(data)
|
||||
end
|
||||
queue = nil
|
||||
end),
|
||||
100
|
||||
)
|
||||
|
||||
function M.size()
|
||||
if size then
|
||||
return size
|
||||
|
|
@ -136,11 +120,16 @@ function M.env()
|
|||
if override then
|
||||
e.detected = override ~= "0" and override ~= "false"
|
||||
else
|
||||
for k, v in pairs(e.env) do
|
||||
local val = os.getenv(k)
|
||||
if val and (v == true or val:find(v)) then
|
||||
e.detected = true
|
||||
break
|
||||
if e.terminal and M._terminal and M._terminal.terminal then
|
||||
e.detected = M._terminal.terminal:lower():find(e.terminal:lower()) ~= nil
|
||||
end
|
||||
if not e.detected then
|
||||
for k, v in pairs(e.env or {}) do
|
||||
local val = os.getenv(k)
|
||||
if val and (v == true or val:find(v)) then
|
||||
e.detected = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -165,7 +154,7 @@ end
|
|||
|
||||
---@param opts table<string, string|number>|{data?: string}
|
||||
function M.request(opts)
|
||||
opts.q = opts.q or 2 -- silence all
|
||||
opts.q = opts.q ~= false and (opts.q or 2) or nil -- silence all
|
||||
local msg = {} ---@type string[]
|
||||
for k, v in pairs(opts) do
|
||||
if k ~= "data" then
|
||||
|
|
@ -178,10 +167,6 @@ function M.request(opts)
|
|||
msg[#msg + 1] = tostring(opts.data)
|
||||
end
|
||||
local data = "\27_G" .. table.concat(msg) .. "\27\\"
|
||||
local env = M.env()
|
||||
if env.transform then
|
||||
data = env.transform(data)
|
||||
end
|
||||
if Snacks.image.config.debug.request and opts.m ~= 1 then
|
||||
Snacks.debug.inspect(opts)
|
||||
end
|
||||
|
|
@ -194,11 +179,118 @@ function M.set_cursor(pos)
|
|||
end
|
||||
|
||||
function M.write(data)
|
||||
if queue then
|
||||
table.insert(queue, data)
|
||||
data = M.transform and M.transform(data) or data
|
||||
if vim.api.nvim_ui_send then
|
||||
vim.api.nvim_ui_send(data)
|
||||
else
|
||||
io.stdout:write(data)
|
||||
end
|
||||
end
|
||||
|
||||
---@param cb fun(term: snacks.image.Terminal)
|
||||
function M.detect(cb)
|
||||
if M._terminal then
|
||||
if M._terminal.pending then
|
||||
table.insert(M._terminal.pending, cb)
|
||||
return
|
||||
end
|
||||
return cb(M._terminal)
|
||||
end
|
||||
|
||||
---@class snacks.image.Terminal
|
||||
---@field terminal? string
|
||||
---@field version? string
|
||||
---@field supported? boolean
|
||||
---@field placeholders? boolean
|
||||
local ret = {
|
||||
terminal = "unknown",
|
||||
version = "unknown",
|
||||
pending = { cb }, ---@type fun(term: snacks.image.Terminal)[]
|
||||
}
|
||||
M._terminal = ret
|
||||
|
||||
local function on_done()
|
||||
for _, c in ipairs(ret.pending or {}) do
|
||||
c(ret)
|
||||
end
|
||||
ret.pending = nil
|
||||
end
|
||||
|
||||
if vim.env.TMUX then
|
||||
pcall(vim.fn.system, { "tmux", "set", "-p", "allow-passthrough", "all" })
|
||||
M.transform = function(data)
|
||||
return ("\027Ptmux;" .. data:gsub("\027", "\027\027")) .. "\027\\"
|
||||
end
|
||||
end
|
||||
|
||||
local timer = assert(vim.uv.new_timer())
|
||||
local done = 0
|
||||
|
||||
local id = vim.api.nvim_create_autocmd("TermResponse", {
|
||||
group = vim.api.nvim_create_augroup("image.terminal.detect", { clear = true }),
|
||||
callback = function(ev)
|
||||
local data = ev.data.sequence
|
||||
if data:find("^\27P>|") then
|
||||
local term, version = data:match("P>|(%S+)%s*(.*)")
|
||||
if term and version then
|
||||
ret.terminal = term
|
||||
ret.version = version
|
||||
done = done + 1
|
||||
end
|
||||
elseif data:find("^\27_G") then
|
||||
local args, ok = data:match("\27_G(.*);(.*)")
|
||||
if args then
|
||||
local params = {}
|
||||
for _, a in ipairs(vim.split(args, ",")) do
|
||||
local k, v = a:match("(%S+)=(%S+)")
|
||||
if k and v then
|
||||
params[k] = v
|
||||
end
|
||||
end
|
||||
if params["i"] == "31" and ok then
|
||||
ret.supported = ok == "OK"
|
||||
done = done + 1
|
||||
-- elseif params["i"] == "32" and ok then
|
||||
-- ret.placeholders = ok == "OK"
|
||||
-- done = done + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
if done < 2 then
|
||||
return
|
||||
end
|
||||
if timer and not timer:is_closing() then
|
||||
timer:stop()
|
||||
timer:close()
|
||||
end
|
||||
vim.schedule(on_done)
|
||||
return true -- delete autocmd
|
||||
end,
|
||||
})
|
||||
|
||||
timer:start(1000, 0, function()
|
||||
timer:stop()
|
||||
timer:close()
|
||||
vim.schedule(function()
|
||||
vim.api.nvim_del_autocmd(id)
|
||||
on_done()
|
||||
end)
|
||||
end)
|
||||
|
||||
M.request({
|
||||
i = 31,
|
||||
s = 1,
|
||||
v = 1,
|
||||
a = "q",
|
||||
t = "d",
|
||||
f = 24,
|
||||
q = false,
|
||||
data = "AAAA",
|
||||
})
|
||||
|
||||
M.write("\27[>q")
|
||||
end
|
||||
|
||||
function M.setup() end
|
||||
|
||||
return M
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue