Skip to content

Commit

Permalink
feat: handle folds
Browse files Browse the repository at this point in the history
  • Loading branch information
3rd committed Jun 29, 2023
1 parent 9aa67f6 commit e42ea8c
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 67 deletions.
13 changes: 8 additions & 5 deletions lua/image/backends/kitty/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ backend.render = function(image, x, y, width, height)
image_id = image.internal_id,
quiet = 2,
})
image.is_rendered = false
backend.state.images[image.id] = image
helpers.restore_cursor()
return
end
Expand Down Expand Up @@ -113,7 +115,6 @@ backend.render = function(image, x, y, width, height)
action = codes.control.action.display,
quiet = 2,
image_id = image.internal_id,
placement_id = 1,
display_width = pixel_width,
display_height = pixel_height,
display_y = pixel_top,
Expand All @@ -124,7 +125,7 @@ backend.render = function(image, x, y, width, height)
helpers.restore_cursor()
end

backend.clear = function(image_id)
backend.clear = function(image_id, shallow)
if image_id then
local image = backend.state.images[image_id]
if not image then return end
Expand All @@ -134,16 +135,18 @@ backend.clear = function(image_id)
image_id = image.internal_id,
quiet = 2,
})
backend.state.images[image_id] = nil
image.is_rendered = false
if not shallow then backend.state.images[image_id] = nil end
return
end
helpers.write_graphics({
action = codes.control.action.delete,
display_delete = "a",
quiet = 2,
})
for id, _ in pairs(backend.state.images) do
backend.state.images[id] = nil
for id, image in pairs(backend.state.images) do
image.is_rendered = false
if not shallow then backend.state.images[id] = nil end
end
end

Expand Down
13 changes: 8 additions & 5 deletions lua/image/backends/ueberzug.lua
Original file line number Diff line number Diff line change
Expand Up @@ -76,23 +76,26 @@ backend.render = function(image, x, y, width, height)
})
backend.state.images[image.id] = image
end
backend.clear = function(image_id)
backend.clear = function(image_id, shallow)
if not child then return end
if image_id then
if not backend.state.images[image_id] then return end
local image = backend.state.images[image_id]
if not image then return end
child.write({
action = "remove",
identifier = image_id,
})
backend.state.images[image_id] = nil
image.is_rendered = false
if not shallow then backend.state.images[image_id] = nil end
return
end
for id, _ in pairs(backend.state.images) do
for id, image in pairs(backend.state.images) do
child.write({
action = "remove",
identifier = id,
})
backend.state.images[id] = nil
image.is_rendered = false
if not shallow then backend.state.images[id] = nil end
end
end

Expand Down
31 changes: 21 additions & 10 deletions lua/image/image.lua
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ local create_image = function(path, options, state)
height = nil,
},
with_virtual_padding = opts.with_virtual_padding or false,
is_rendered = false,
}

instance.get_dimensions = function()
Expand All @@ -52,7 +53,7 @@ local create_image = function(path, options, state)
local ok = renderer.render(instance, state)

-- virtual padding
if ok and instance.buffer and instance.with_virtual_padding then
if instance.buffer and instance.with_virtual_padding then
local row = instance.geometry.y
local width = instance.rendered_geometry.width or 1
local height = instance.rendered_geometry.height or 1
Expand Down Expand Up @@ -95,21 +96,31 @@ local create_image = function(path, options, state)
-- end

local previous_extmark = buf_extmark_map[instance.buffer .. ":" .. row]

if not ok and previous_extmark then
state.backend.clear(instance.id, true)
vim.api.nvim_buf_del_extmark(instance.buffer, state.extmarks_namespace, previous_extmark.id)
buf_extmark_map[instance.buffer .. ":" .. row] = nil
return
end

if previous_extmark then
if previous_extmark.height == height then return end
vim.api.nvim_buf_del_extmark(instance.buffer, state.extmarks_namespace, previous_extmark.id)
end

local text = string.rep(" ", width)
local filler = {}
for _ = 0, height - 1 do
filler[#filler + 1] = { { text, "" } }
if ok then
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 - 1, 0, {
id = numerical_id,
virt_lines = filler,
})
buf_extmark_map[instance.buffer .. ":" .. row] = { id = numerical_id, height = height }
end
vim.api.nvim_buf_set_extmark(instance.buffer, state.extmarks_namespace, row - 1, 0, {
id = numerical_id,
virt_lines = filler,
})
buf_extmark_map[instance.buffer .. ":" .. row] = { id = numerical_id, height = height }
end
end

Expand Down
45 changes: 45 additions & 0 deletions lua/image/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,39 @@ api.setup = function(options)
-- setup namespaces
state.extmarks_namespace = vim.api.nvim_create_namespace("image.nvim")

---@type table<number, { topline: number, botline: number, bufnr: number, height: number }>
local window_history = {}
vim.api.nvim_set_decoration_provider(state.extmarks_namespace, {
on_win = function(_, winid, bufnr, topline, botline)
utils.debug("on_win", { winid = winid })

local prev = window_history[winid]
if not prev then
window_history[winid] = { topline = topline, botline = botline, bufnr = bufnr }
return
end

local height = vim.api.nvim_win_get_height(winid)
local needs_clear = prev.bufnr ~= bufnr
local needs_rerender = prev.topline ~= topline or prev.botline ~= botline or prev.height ~= height
window_history[winid] = { topline = topline, botline = botline, bufnr = bufnr, height = height }

if needs_clear or needs_rerender then
vim.defer_fn(function()
if needs_clear then
for _, curr in ipairs(api.get_images({ buffer = prev.bufnr })) do
curr.clear()
end
elseif needs_rerender then
for _, curr in ipairs(api.get_images({ window = winid })) do
curr.render()
end
end
end, 0)
end
end,
})

-- setup autocommands
local group = vim.api.nvim_create_augroup("image.nvim", { clear = true })

Expand Down Expand Up @@ -105,6 +138,18 @@ api.setup = function(options)
end
end,
})

-- rerender on scroll/fold
-- vim.api.nvim_create_autocmd("WinScrolled", {
-- group = group,
-- callback = function(au)
-- utils.debug("WinScrolled", au)
-- local images = api.get_images({ window = tonumber(au.file) })
-- for _, current_image in ipairs(images) do
-- current_image.render()
-- end
-- end,
-- })
end

---@param path string
Expand Down
2 changes: 0 additions & 2 deletions lua/image/integrations/markdown.lua
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ local setup_autocommands = function(ctx)
vim.api.nvim_create_autocmd({
"WinNew",
"BufWinEnter",
"WinScrolled",
"WinResized",
}, {
group = group,
Expand All @@ -102,7 +101,6 @@ local setup_autocommands = function(ctx)
render(ctx)
end,
})

vim.api.nvim_create_autocmd({
"TextChanged",
"TextChangedI",
Expand Down
128 changes: 84 additions & 44 deletions lua/image/renderer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ local render = function(image, state)
bottom = term_size.screen_rows,
left = 0,
}
local topfill = 0

-- infer missing w/h component
if width == 0 and height ~= 0 then width = math.ceil(height * image_dimensions.width / image_dimensions.height) end
Expand All @@ -69,19 +70,32 @@ local render = function(image, state)
width = math.min(width, term_size.screen_cols)
height = math.min(width, term_size.screen_rows)

-- utils.debug(("(1) x: %d, y: %d, width: %d, height: %d y_offset: %d"):format(x, y, width, height, y_offset))
utils.debug(("(1) x: %d, y: %d, width: %d, height: %d y_offset: %d"):format(x, y, width, height, y_offset))

if image.window ~= nil then
-- window is valid
-- bail if the window is invalid
local window = utils.window.get_window(image.window)
if window == nil then return false end

-- window is visibile
-- bail if the window is not visible
if not window.is_visible then return false end

-- if the image is tied to a buffer the window must be displaying that buffer
if image.buffer ~= nil and window.buffer ~= image.buffer then return false end

-- get topfill and check fold status
local current_win = vim.api.nvim_get_current_win()
vim.api.nvim_command("noautocmd call nvim_set_current_win(" .. image.window .. ")")
topfill = vim.fn.winsaveview().topfill
local is_folded = vim.fn.foldclosed(image.geometry.y) ~= -1
vim.api.nvim_command("noautocmd call nvim_set_current_win(" .. current_win .. ")")

-- bail if the image is inside a fold
if image.buffer and is_folded then
utils.debug("inside fold", image.id)
return false
end

-- global offsets
local global_offsets = get_global_offsets()
x_offset = global_offsets.x - window.scroll_x
Expand Down Expand Up @@ -132,55 +146,80 @@ local render = function(image, state)

-- extmark offsets
if image.with_virtual_padding and image.window and image.buffer then

if image.window and image.buffer then
local win_info = vim.fn.getwininfo(image.window)[1]
local topline = win_info.topline
local botline = win_info.botline

local current_win = vim.api.nvim_get_current_win()
vim.api.nvim_command("noautocmd call nvim_set_current_win(" .. image.window .. ")")
local topfill = vim.fn.winsaveview().topfill
vim.api.nvim_command("noautocmd call nvim_set_current_win(" .. current_win .. ")")

-- bail if the image is above the top of the window and there's no topfill
if topfill == 0 and image.geometry.y < topline then prevent_rendering = true end

-- bail if the image + its height is above the top of the window + topfill
if image.geometry.y + height + 1 < topline + topfill then prevent_rendering = true end

-- bail if the image is below the bottom of the window
if image.geometry.y > botline then prevent_rendering = true end

-- offset by topfill if the image started above the top of the window
if not prevent_rendering then
if topfill > 0 and image.geometry.y < topline then
--
absolute_y = absolute_y - (height - topfill)
else
-- offset by any pre-y virtual lines
local extmarks = vim.tbl_map(
function(mark)
---@diagnostic disable-next-line: deprecated
local mark_id, mark_row, mark_col, mark_opts = unpack(mark)
local virt_height = #(mark_opts.virt_lines or {})
return { id = mark_id, row = mark_row + 1, col = mark_col, height = virt_height }
end,
vim.api.nvim_buf_get_extmarks(
image.buffer,
-1,
{ topline - 1, 0 },
{ image.geometry.y, 0 },
{ details = true }
-- bail if out of bounds
if image.geometry.y + 1 < topline or image.geometry.y > botline then prevent_rendering = true end

-- extmark offsets
if image.with_virtual_padding then
-- bail if the image is above the top of the window and there's no topfill
if topfill == 0 and image.geometry.y < topline then prevent_rendering = true end

-- bail if the image + its height is above the top of the window + topfill
if image.geometry.y + height + 1 < topline + topfill then prevent_rendering = true end

-- bail if the image is below the bottom of the window
if image.geometry.y > botline then prevent_rendering = true end

-- offset by topfill if the image started above the top of the window
if not prevent_rendering then
if topfill > 0 and image.geometry.y < topline then
--
absolute_y = absolute_y - (height - topfill)
else
-- offset by any pre-y virtual lines
local extmarks = vim.tbl_map(
function(mark)
---@diagnostic disable-next-line: deprecated
local mark_id, mark_row, mark_col, mark_opts = unpack(mark)
local virt_height = #(mark_opts.virt_lines or {})
return { id = mark_id, row = mark_row + 1, col = mark_col, height = virt_height }
end,
vim.api.nvim_buf_get_extmarks(
image.buffer,
-1,
{ topline - 1, 0 },
{ image.geometry.y, 0 },
{ details = true }
)
)
)

local offset = topfill
for _, mark in ipairs(extmarks) do
if mark.row ~= image.geometry.y then offset = offset + mark.height end
local offset = topfill
for _, mark in ipairs(extmarks) do
if mark.row ~= image.geometry.y then offset = offset + mark.height end
end

absolute_y = absolute_y + offset
end
end
end

absolute_y = absolute_y + offset
-- folds
local offset = 0
local current_win = vim.api.nvim_get_current_win()
vim.api.nvim_command("noautocmd call nvim_set_current_win(" .. image.window .. ")")

if vim.wo.foldenable then
local i = topline
while i <= image.geometry.y do
local fold_start, fold_end = vim.fn.foldclosed(i), vim.fn.foldclosedend(i)
if fold_start ~= -1 and fold_end ~= -1 then
utils.debug(("i: %d fold start: %d, fold end: %d"):format(i, fold_start, fold_end))
offset = offset + (fold_end - fold_start)
i = fold_end + 1
else
i = i + 1
end
end
end
vim.api.nvim_command("noautocmd call nvim_set_current_win(" .. current_win .. ")")
utils.debug(("fold offset: %d"):format(offset))
absolute_y = absolute_y - offset
end

if prevent_rendering then absolute_y = -999999 end
Expand All @@ -190,12 +229,13 @@ local render = function(image, state)

-- prevent useless rerendering
if
image.rendered_geometry.x == rendered_geometry.x
image.is_rendered
and image.rendered_geometry.x == rendered_geometry.x
and image.rendered_geometry.y == rendered_geometry.y
and image.rendered_geometry.width == rendered_geometry.width
and image.rendered_geometry.height == rendered_geometry.height
then
return false
return true
end

state.backend.render(image, absolute_x, absolute_y, width, height)
Expand Down
Loading

0 comments on commit e42ea8c

Please sign in to comment.