mirror of
https://github.com/folke/snacks.nvim
synced 2025-08-04 10:49:08 +00:00
feat(image): use kitty's unicode placeholder images
This commit is contained in:
parent
7fda8c12a3
commit
7d655fe09d
2 changed files with 81 additions and 62 deletions
|
@ -17,12 +17,18 @@ M.meta = {
|
|||
---@field file? string
|
||||
local defaults = {}
|
||||
local uv = vim.uv or vim.loop
|
||||
local ns = vim.api.nvim_create_namespace("snacks.image")
|
||||
|
||||
---@alias snacks.image.Dim {col: number, row: number, width: number, height: number}
|
||||
|
||||
local images = {} ---@type table<number, snacks.Image>
|
||||
local id = 0
|
||||
local id = 30
|
||||
local ids = {} ---@type table<string, number>
|
||||
local exts = { "png", "jpg", "jpeg", "gif", "bmp", "webp" }
|
||||
local diacritics = vim.split(
|
||||
"0305,030D,030E,0310,0312,033D,033E,033F,0346,034A,034B,034C,0350,0351,0352,0357,035B,0363,0364,0365,0366,0367,0368,0369,036A,036B,036C,036D,036E,036F,0483,0484,0485,0486,0487,0592,0593,0594,0595,0597,0598,0599,059C,059D,059E,059F,05A0,05A1,05A8,05A9,05AB,05AC,05AF,05C4,0610,0611,0612,0613,0614,0615,0616,0617,0657,0658,0659,065A,065B,065D,065E,06D6,06D7,06D8,06D9,06DA,06DB,06DC,06DF,06E0,06E1,06E2,06E4,06E7,06E8,06EB,06EC,0730,0732,0733,0735,0736,073A,073D,073F,0740,0741,0743,0745,0747,0749,074A,07EB,07EC,07ED,07EE,07EF,07F0,07F1,07F3,0816,0817,0818,0819,081B,081C,081D,081E,081F,0820,0821,0822,0823,0825,0826,0827,0829,082A,082B,082C,082D,0951,0953,0954,0F82,0F83,0F86,0F87,135D,135E,135F,17DD,193A,1A17,1A75,1A76,1A77,1A78,1A79,1A7A,1A7B,1A7C,1B6B,1B6D,1B6E,1B6F,1B70,1B71,1B72,1B73,1CD0,1CD1,1CD2,1CDA,1CDB,1CE0,1DC0,1DC1,1DC3,1DC4,1DC5,1DC6,1DC7,1DC8,1DC9,1DCB,1DCC,1DD1,1DD2,1DD3,1DD4,1DD5,1DD6,1DD7,1DD8,1DD9,1DDA,1DDB,1DDC,1DDD,1DDE,1DDF,1DE0,1DE1,1DE2,1DE3,1DE4,1DE5,1DE6,1DFE,20D0,20D1,20D4,20D5,20D6,20D7,20DB,20DC,20E1,20E7,20E9,20F0,2CEF,2CF0,2CF1,2DE0,2DE1,2DE2,2DE3,2DE4,2DE5,2DE6,2DE7,2DE8,2DE9,2DEA,2DEB,2DEC,2DED,2DEE,2DEF,2DF0,2DF1,2DF2,2DF3,2DF4,2DF5,2DF6,2DF7,2DF8,2DF9,2DFA,2DFB,2DFC,2DFD,2DFE,2DFF,A66F,A67C,A67D,A6F0,A6F1,A8E0,A8E1,A8E2,A8E3,A8E4,A8E5,A8E6,A8E7,A8E8,A8E9,A8EA,A8EB,A8EC,A8ED,A8EE,A8EF,A8F0,A8F1,AAB0,AAB2,AAB3,AAB7,AAB8,AABE,AABF,AAC1,FE20,FE21,FE22,FE23,FE24,FE25,FE26,10A0F,10A38,1D185,1D186,1D187,1D188,1D189,1D1AA,1D1AB,1D1AC,1D1AD,1D242,1D243,1D244",
|
||||
","
|
||||
)
|
||||
|
||||
---@param buf number
|
||||
---@param opts? snacks.image.Config
|
||||
|
@ -37,15 +43,23 @@ function M.new(buf, opts)
|
|||
|
||||
local self = setmetatable({}, M)
|
||||
images[buf] = self
|
||||
id = id + 1
|
||||
self.id = id
|
||||
self.file = file
|
||||
|
||||
-- convert to PNG if needed
|
||||
self.file = self:convert(file)
|
||||
|
||||
-- re-use image ids for the same file
|
||||
if not ids[self.file] then
|
||||
id = id + 1
|
||||
ids[self.file] = id
|
||||
end
|
||||
self.id = ids[self.file]
|
||||
|
||||
self.opts = Snacks.config.get("image", defaults, opts or {})
|
||||
|
||||
Snacks.util.bo(buf, {
|
||||
filetype = "image",
|
||||
buftype = "nofile",
|
||||
-- modifiable = false,
|
||||
modifiable = false,
|
||||
modified = false,
|
||||
swapfile = false,
|
||||
})
|
||||
|
@ -54,7 +68,7 @@ function M.new(buf, opts)
|
|||
|
||||
local group = vim.api.nvim_create_augroup("snacks.image." .. self.id, { clear = true })
|
||||
vim.api.nvim_create_autocmd(
|
||||
{ "VimResized", "BufWinEnter", "WinClosed", "BufWinLeave", "WinNew", "BufEnter", "BufLeave" },
|
||||
{ "VimResized", "BufWinEnter", "WinClosed", "BufWinLeave", "WinNew", "BufEnter", "BufLeave", "WinResized" },
|
||||
{
|
||||
group = group,
|
||||
buffer = self.buf,
|
||||
|
@ -78,7 +92,6 @@ function M.new(buf, opts)
|
|||
})
|
||||
|
||||
local update = self.update
|
||||
self:convert()
|
||||
if self:ready() then
|
||||
vim.schedule(function()
|
||||
self:create()
|
||||
|
@ -92,60 +105,64 @@ function M.new(buf, opts)
|
|||
return self
|
||||
end
|
||||
|
||||
---@param win number
|
||||
---@return snacks.image.Dim
|
||||
function M:dim(win)
|
||||
local border = setmetatable({ opts = vim.api.nvim_win_get_config(win) }, { __index = Snacks.win }):border_size()
|
||||
local pos = vim.api.nvim_win_get_position(win)
|
||||
return {
|
||||
row = pos[1] + border.top,
|
||||
col = pos[2] + border.left,
|
||||
width = vim.api.nvim_win_get_width(win),
|
||||
height = vim.api.nvim_win_get_height(win),
|
||||
}
|
||||
function M:grid_size()
|
||||
local width, height = vim.o.columns, vim.o.lines
|
||||
for _, win in pairs(vim.api.nvim_list_wins()) do
|
||||
if vim.api.nvim_win_get_buf(win) == self.buf then
|
||||
width = math.min(width, vim.api.nvim_win_get_width(win))
|
||||
height = math.min(height, vim.api.nvim_win_get_height(win))
|
||||
end
|
||||
end
|
||||
return width, height
|
||||
end
|
||||
|
||||
---@param win? number
|
||||
function M:hide(win)
|
||||
self:request({ a = "d", i = self.id, p = win })
|
||||
--- Renders the unicode placeholder grid in the buffer
|
||||
---@param width number
|
||||
---@param height number
|
||||
function M:render(width, height)
|
||||
local hl = "SnacksImage" .. self.id
|
||||
-- image id is coded in the foreground color
|
||||
vim.api.nvim_set_hl(0, hl, { fg = self.id })
|
||||
local lines = {} ---@type string[]
|
||||
for r = 1, height do
|
||||
local line = {} ---@type string[]
|
||||
for c = 1, width do
|
||||
-- cell positions are encoded as diacritics for the placeholder unicode character
|
||||
line[#line + 1] = vim.fn.nr2char(0x10EEEE)
|
||||
line[#line + 1] = vim.fn.nr2char(tonumber(diacritics[r], 16))
|
||||
line[#line + 1] = vim.fn.nr2char(tonumber(diacritics[c], 16))
|
||||
end
|
||||
lines[#lines + 1] = table.concat(line)
|
||||
end
|
||||
vim.bo[self.buf].modifiable = true
|
||||
vim.api.nvim_buf_set_lines(self.buf, 0, -1, false, lines)
|
||||
vim.bo[self.buf].modifiable = false
|
||||
for r = 1, height do
|
||||
vim.api.nvim_buf_set_extmark(self.buf, ns, r - 1, 0, {
|
||||
end_col = #lines[r],
|
||||
hl_group = hl,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function M:hide()
|
||||
self:request({ a = "d", i = self.id })
|
||||
end
|
||||
|
||||
function M:update()
|
||||
if not self:ready() then
|
||||
return
|
||||
end
|
||||
-- hide images that are no longer visible
|
||||
for win in pairs(self.wins) do
|
||||
local buf = vim.api.nvim_win_is_valid(win) and vim.api.nvim_win_get_buf(win)
|
||||
if buf ~= self.buf then
|
||||
self:hide(win)
|
||||
self.wins[win] = nil
|
||||
end
|
||||
end
|
||||
|
||||
for _, win in pairs(vim.api.nvim_list_wins()) do
|
||||
if vim.api.nvim_win_get_buf(win) == self.buf then
|
||||
self:render(win)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param win number
|
||||
function M:render(win)
|
||||
if not vim.api.nvim_win_is_valid(win or win) then
|
||||
return
|
||||
end
|
||||
local dim = self:dim(win)
|
||||
self.wins[win] = dim
|
||||
vim.api.nvim_win_call(win, function()
|
||||
io.write("\27[" .. (dim.row + 1) .. ";" .. (dim.col + 1) .. "H")
|
||||
self:request({
|
||||
a = "p",
|
||||
i = self.id,
|
||||
p = win,
|
||||
c = dim.width,
|
||||
r = dim.height,
|
||||
})
|
||||
local width, height = self:grid_size()
|
||||
self:request({
|
||||
a = "p",
|
||||
U = 1,
|
||||
i = self.id,
|
||||
c = width,
|
||||
r = height,
|
||||
})
|
||||
vim.schedule(function()
|
||||
self:render(width, height)
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -164,22 +181,23 @@ function M:create()
|
|||
})
|
||||
end
|
||||
|
||||
function M:convert()
|
||||
local ext = vim.fn.fnamemodify(self.file, ":e")
|
||||
---@param file string
|
||||
function M:convert(file)
|
||||
local ext = vim.fn.fnamemodify(file, ":e")
|
||||
if ext == "png" then
|
||||
return
|
||||
return file
|
||||
end
|
||||
local fin = ext == "gif" and self.file .. "[0]" or self.file
|
||||
local fin = ext == "gif" and file .. "[0]" or file
|
||||
local root = vim.fn.stdpath("cache") .. "/snacks/image"
|
||||
vim.fn.mkdir(root, "p")
|
||||
self.file = root .. "/" .. Snacks.util.file_encode(fin) .. ".png"
|
||||
if vim.fn.filereadable(self.file) == 1 then
|
||||
return
|
||||
file = root .. "/" .. Snacks.util.file_encode(fin) .. ".png"
|
||||
if vim.fn.filereadable(file) == 1 then
|
||||
return file
|
||||
end
|
||||
self._convert = uv.spawn("magick", {
|
||||
args = {
|
||||
fin,
|
||||
self.file,
|
||||
file,
|
||||
},
|
||||
}, function()
|
||||
self._convert:close()
|
||||
|
@ -188,6 +206,7 @@ function M:convert()
|
|||
self:update()
|
||||
end)
|
||||
end)
|
||||
return file
|
||||
end
|
||||
|
||||
---@param opts table<string, string|number>|{data?: string}
|
||||
|
|
|
@ -169,7 +169,7 @@ function M.setup(opts)
|
|||
|
||||
if M.config.image.enabled then
|
||||
vim.api.nvim_create_autocmd("BufReadCmd", {
|
||||
pattern = "*.png,*.jpg,*.jpeg,*.gif,*.bmp",
|
||||
pattern = "*.png,*.jpg,*.jpeg,*.gif,*.bmp,*.webp",
|
||||
group = group,
|
||||
callback = function(e)
|
||||
require("snacks.image").new(e.buf)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue