Skip to content

Commit

Permalink
feat: rewrite most things, restructure API, extmarks, multi-window su…
Browse files Browse the repository at this point in the history
…pport, auto-clear, ueberzug++
  • Loading branch information
3rd committed Jun 28, 2023
1 parent 1bf4cbf commit cd58741
Show file tree
Hide file tree
Showing 14 changed files with 581 additions and 320 deletions.
49 changes: 5 additions & 44 deletions lua/image/backends/kitty/helpers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,49 +14,14 @@ local get_chunked = function(str)
return chunks
end

-- https://github.com/edluffy/hologram.nvim/blob/main/lua/hologram/state.lua#L15
local get_term_size = function()
local ffi = require("ffi")
ffi.cdef([[
typedef struct {
unsigned short row;
unsigned short col;
unsigned short xpixel;
unsigned short ypixel;
} winsize;
int ioctl(int, int, ...);
]])

local TIOCGWINSZ = nil
if vim.fn.has("linux") == 1 then
TIOCGWINSZ = 0x5413
elseif vim.fn.has("mac") == 1 then
TIOCGWINSZ = 0x40087468
elseif vim.fn.has("bsd") == 1 then
TIOCGWINSZ = 0x40087468
end

local sz = ffi.new("winsize")
assert(ffi.C.ioctl(1, TIOCGWINSZ, sz) == 0, "Hologram failed to get screen size: detected OS is not supported.")

return {
screen_x = sz.xpixel,
screen_y = sz.ypixel,
screen_cols = sz.col,
screen_rows = sz.row,
cell_width = sz.xpixel / sz.col,
cell_height = sz.ypixel / sz.row,
}
end

local encode = function(data)
if is_tmux then return "\x1bPtmux;" .. data:gsub("\x1b", "\x1b\x1b") .. "\x1b\\" end
return data
end

local write = vim.schedule_wrap(function(data)
if data == "" then return end
utils.debug("write:", vim.inspect(data))
-- utils.debug("write:", vim.inspect(data))
stdout:write(data)
-- vim.fn.chansend(vim.v.stderr, data)
end)
Expand Down Expand Up @@ -96,30 +61,26 @@ local write_graphics = function(config, data)
end
end
else
utils.debug("control:", control_payload)
-- utils.debug("control:", control_payload)
write(encode("\x1b_G" .. control_payload .. "\x1b\\"))
end
end

-- local rshift = function(x, by)
-- return math.floor(x / 2 ^ by)
-- end
local write_placeholder = function(image_id, x, y, rows, columns)
local write_placeholder = function(image_id, x, y, width, height)
local foreground = "\x1b[38;5;" .. image_id .. "m"
local restore = "\x1b[39m"

write(foreground)
for i = 0, rows - 1 do
for i = 0, height - 1 do
move_cursor(x, y + i + 1)
for j = 0, columns - 1 do
for j = 0, width - 1 do
write(codes.placeholder .. codes.diacritics[i + 1] .. codes.diacritics[j + 1])
end
end
write(restore)
end

return {
get_term_size = get_term_size,
move_cursor = move_cursor,
restore_cursor = restore_cursor,
write = write,
Expand Down
58 changes: 18 additions & 40 deletions lua/image/backends/kitty/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ local utils = require("image/utils")
local codes = require("image/backends/kitty/codes")
local helpers = require("image/backends/kitty/helpers")

local term_size = helpers.get_term_size()

local images = {}
local last_kitty_id = 0

Expand All @@ -26,42 +24,18 @@ backend.setup = function(options)
utils.throw("tmux does not have allow-passthrough enabled")
return
end

vim.defer_fn(function()
-- log(get_term_size())
end, 1000)
end

-- extend from empty line strategy to use extmarks
backend.render = function(image_id, url, x, y, max_cols, max_rows)
if not images[image_id] then
backend.render = function(image, x, y, width, height)
if not images[image.id] then
last_kitty_id = last_kitty_id + 1
images[image_id] = last_kitty_id
images[image.id] = last_kitty_id
end
local kitty_id = images[image_id]

local image_width, image_height = utils.png.get_dimensions(url)
local rows = math.floor(image_height / term_size.cell_height)
local columns = math.floor(image_width / term_size.cell_width)
-- local rows = max_rows
-- local pixel_height = math.floor(max_rows * term_size.cell_height)
-- local pixel_width = math.floor(image_width * pixel_height / image_height)
-- local columns = math.floor(pixel_width / term_size.cell_width)
-- log({
-- image_width = image_width,
-- image_height = image_height,
-- columns = columns,
-- rows = rows,
-- })

-- if true then
-- helpers.move_cursor(10, 10)
-- return
-- end

helpers.move_cursor(x, y, true)
local kitty_id = images[image.id]

-- transmit image
helpers.move_cursor(x, y, true)
helpers.write_graphics({
action = codes.control.action.transmit,
image_id = kitty_id,
Expand All @@ -70,36 +44,37 @@ backend.render = function(image_id, url, x, y, max_cols, max_rows)
display_cursor_policy = codes.control.display_cursor_policy.do_not_move,
display_virtual_placeholder = is_tmux and 1 or 0,
quiet = 2,
}, url)
}, image.path)

-- unicode placeholders
if is_tmux then
-- create virtual image placement
helpers.write_graphics({
action = codes.control.action.display,
quiet = 2,
image_id = kitty_id,
display_rows = rows,
display_columns = columns,
display_rows = height,
display_columns = width,
display_cursor_policy = codes.control.display_cursor_policy.do_not_move,
display_virtual_placeholder = 1,
})

-- write placeholder
helpers.write_placeholder(kitty_id, x, y, rows, columns)
helpers.write_placeholder(kitty_id, x, y, width, height)
helpers.restore_cursor()
return
end

-- default display
local term_size = utils.term.get_size()
local pixel_width = math.ceil(width * term_size.cell_width)
local pixel_height = math.ceil(height * term_size.cell_height)

helpers.move_cursor(x + 1, y + 1)
helpers.write_graphics({
action = codes.control.action.display,
quiet = 2,
image_id = kitty_id,
placement_id = 1,
display_rows = rows,
display_columns = columns,
display_width = pixel_width,
display_height = pixel_height,
display_zindex = -1,
display_cursor_policy = codes.control.display_cursor_policy.do_not_move,
})
Expand All @@ -108,13 +83,16 @@ end

backend.clear = function(image_id)
if image_id then
utils.log("kitty: clear", image_id)
helpers.write_graphics({
action = codes.control.action.delete,
display_delete = "i",
image_id = 1,
quiet = 2,
})
return
end
utils.log("kitty: clear all")
helpers.write_graphics({
action = codes.control.action.delete,
display_delete = "a",
Expand Down
56 changes: 32 additions & 24 deletions lua/image/backends/ueberzug.lua
Original file line number Diff line number Diff line change
Expand Up @@ -58,35 +58,43 @@ end

---@type Backend
local backend = {
setup = function()
if not child then spawn() end
end,
render = function(image_id, url, x, y, max_cols, max_rows)
if not child then return end
---@diagnostic disable-next-line: assign-type-mismatch
state = nil,
}
backend.setup = function(state)
backend.state = state
if not child then spawn() end
end
backend.render = function(image, x, y, width, height)
if not child then return end
child.write({
action = "add",
identifier = image.id,
path = image.path,
x = x,
y = y,
width = width,
height = height,
})
backend.state.images[image.id] = image
end
backend.clear = function(image_id)
if not child then return end
if image_id then
child.write({
action = "add",
action = "remove",
identifier = image_id,
path = url,
x = x,
y = y,
max_cols = max_cols,
max_rows = max_rows,
})
end,
clear = function(image_id)
if not child then return end
if image_id then
child.write({
action = "remove",
identifier = image_id,
})
return
end
backend.state.images[image_id] = nil
return
end
for id, _ in pairs(backend.state.images) do
child.write({
action = "remove",
identifier = "all",
identifier = id,
})
end,
}
backend.state.images[id] = nil
end
end

return backend
98 changes: 98 additions & 0 deletions lua/image/image.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
local utils = require("image/utils")
local renderer = require("image/renderer")

local next_numerical_id = 1

---@param path string
---@param options? ImageOptions
---@param state State
local create_image = function(path, options, state)
if options and options.id then
local existing_image = state.images[options.id] ---@type Image
if existing_image then return existing_image end
end

local opts = options or {}
local numerical_id = next_numerical_id
next_numerical_id = next_numerical_id + 1

---@type Image
local instance = {
id = opts.id or utils.random.id(),
path = path,
window = opts.window or nil,
buffer = opts.buffer or nil,
geometry = {
x = opts.x or nil,
y = opts.y or nil,
width = opts.width or nil,
height = opts.height or nil,
},
rendered_geometry = {
x = nil,
y = nil,
width = nil,
height = nil,
},
with_virtual_padding = opts.with_virtual_padding or false,
}

instance.get_dimensions = function()
return utils.png.get_dimensions(instance.path)
end

---@param geometry? ImageGeometry
instance.render = function(geometry)
if geometry then instance.geometry = vim.tbl_deep_extend("force", instance.geometry, geometry) end

local ok = renderer.render(instance, state)

-- virtual padding
if ok and instance.buffer and instance.with_virtual_padding then
local row = instance.geometry.y - 1
local width = instance.rendered_geometry.width or 1
local height = instance.rendered_geometry.height or 1

-- remove same-row extmarks
-- local extmarks =
-- vim.api.nvim_buf_get_extmarks(instance.buffer, state.extmarks_namespace, 0, -1, { details = true })
-- for _, extmark in ipairs(extmarks) do
-- local mark_id, mark_row, mark_col, mark_opts = unpack(extmark)
-- local virt_height = #(mark_opts.virt_lines or {})
-- if mark_row == row then
-- if virt_height == height then return end
-- vim.api.nvim_buf_del_extmark(instance.buffer, state.extmarks_namespace, mark_id)
-- end
-- end

local text = string.rep(" ", width)
local filler = {}
for _ = 0, height - 1 do
filler[#filler + 1] = { { text, "" } }
end
vim.api.nvim_buf_set_extmark(instance.buffer, state.extmarks_namespace, row, 0, {
id = numerical_id,
virt_lines = filler,
})
end
end

instance.clear = function()
state.backend.clear(instance.id)
utils.debug("extmark del", { id = numerical_id, buf = instance.buffer })
vim.api.nvim_buf_del_extmark(instance.buffer, state.extmarks_namespace, numerical_id)
end

return instance
end

---@param path string
---@param options? ImageOptions
---@param state State
local from_file = function(path, options, state)
return create_image(path, options, state)
end

return {
from_file = from_file,
}
Loading

0 comments on commit cd58741

Please sign in to comment.