Skip to content

Commit

Permalink
feat: compute window masks and toggle overlapped window images
Browse files Browse the repository at this point in the history
  • Loading branch information
3rd committed Jul 10, 2023
1 parent 5979f47 commit e524050
Show file tree
Hide file tree
Showing 7 changed files with 306 additions and 167 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ require("image").setup({
max_height_window_percentage = 50,
kitty_method = "normal",
kitty_tmux_write_delay = 10, -- makes rendering more reliable with Kitty+Tmux
window_overlap_clear_enabled = false, -- toggles images when windows are overlapped
window_overlap_clear_ft_ignore = { "cmp_menu", "cmp_docs", "" },
})
```

Expand Down
52 changes: 46 additions & 6 deletions lua/image/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ local default_options = {
max_height_window_percentage = 50,
kitty_method = "normal",
kitty_tmux_write_delay = 10,
window_overlap_clear_enabled = false,
window_overlap_clear_ft_ignore = { "cmp_menu", "cmp_docs", "" },
}

---@type State
Expand Down Expand Up @@ -54,7 +56,7 @@ api.setup = function(options)
for integration_name, integration_options in pairs(opts.integrations) do
if integration_options.enabled then
local integration = require("image/integrations/" .. integration_name)
if type(integration.setup) == "function" then integration.setup(api, integration_options) end
if type(integration.setup) == "function" then integration.setup(api, integration_options, state) end
end
end

Expand All @@ -68,9 +70,48 @@ api.setup = function(options)
---@type table<number, { topline: number, botline: number, bufnr: number, height: number; folded_lines: 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 })
on_win = vim.schedule_wrap(function(_, winid, bufnr, topline, botline)
-- get current window
local window = nil
local windows = {}
if state.options.window_overlap_clear_enabled then
windows = utils.window.get_windows({
normal = true,
floating = true,
with_masks = state.options.window_overlap_clear_enabled,
ignore_masking_filetypes = state.options.window_overlap_clear_ft_ignore,
})
for _, w in ipairs(windows) do
if w.id == winid then
window = w
break
end
end
else
window = utils.window.get_window(winid)
end
if not window then return end

-- toggle images in overlapped windows
if state.options.window_overlap_clear_enabled then
for _, current_window in ipairs(windows) do
local images = api.get_images({ window = current_window.id, buffer = bufnr })
if #current_window.masks > 0 then
for _, current_image in ipairs(images) do
current_image:clear(true)
end
else
for _, current_image in ipairs(images) do
current_image:render()
end
end
end
end

-- all handling below is only for non-floating windows
if window.is_floating then return end

-- get history entry or init
local prev = window_history[winid]
if not prev then
window_history[winid] = { topline = topline, botline = botline, bufnr = bufnr }
Expand Down Expand Up @@ -106,8 +147,7 @@ api.setup = function(options)
{ topline = topline, botline = botline, bufnr = bufnr, height = height, folded_lines = folded_lines }

-- execute deferred clear / rerender
utils.debug("needs_clear", needs_clear, "needs_rerender", needs_rerender)
if needs_rerender then utils.debug("window", winid, "needs rerender") end
-- utils.debug("needs_clear", needs_clear, "needs_rerender", needs_rerender)
local images = (needs_clear and api.get_images({ window = winid, buffer = prev.bufnr }))
or (needs_rerender and api.get_images({ window = winid, buffer = bufnr }))
or {}
Expand All @@ -122,7 +162,7 @@ api.setup = function(options)
end
end
end, 0)
end,
end),
})

-- setup autocommands
Expand Down
141 changes: 75 additions & 66 deletions lua/image/integrations/markdown.lua
Original file line number Diff line number Diff line change
Expand Up @@ -43,80 +43,88 @@ local query_buffer_images = function(buffer)
return images
end

---@type fun(ctx: IntegrationContext)
local render = vim.schedule_wrap(function(ctx)
local windows = utils.window.get_visible_windows()

for _, window in ipairs(windows) do
if vim.bo[window.buffer].filetype == "markdown" then
local matches = query_buffer_images(window.buffer)

local previous_images = ctx.api.get_images({
window = window.id,
buffer = window.buffer,
})
local new_image_ids = {}

local file_path = vim.api.nvim_buf_get_name(window.buffer)

for _, match in ipairs(matches) do
local id = string.format("%d:%d:%d:%s", window.id, window.buffer, match.range.start_row, match.url)
local height = nil

---@param image Image
local render_image = function(image)
if ctx.options.sizing_strategy == "height-from-empty-lines" then
local empty_line_count = -1
local lines = vim.api.nvim_buf_get_lines(window.buffer, 0, -1, false)
for i = match.range.end_row + 2, #lines do
if lines[i] == "" then
empty_line_count = empty_line_count + 1
else
break
local render = vim.schedule_wrap(
---@param ctx IntegrationContext
function(ctx)
local windows = utils.window.get_windows({
normal = true,
with_masks = ctx.state.options.window_overlap_clear_enabled,
ignore_masking_filetypes = ctx.state.options.window_overlap_clear_ft_ignore,
})
-- utils.debug("[markdown] render", { windows = windows })

for _, window in ipairs(windows) do
if window.buffer_filetype == "markdown" then
local matches = query_buffer_images(window.buffer)

local previous_images = ctx.api.get_images({
window = window.id,
buffer = window.buffer,
})
local new_image_ids = {}

local file_path = vim.api.nvim_buf_get_name(window.buffer)

for _, match in ipairs(matches) do
local id = string.format("%d:%d:%d:%s", window.id, window.buffer, match.range.start_row, match.url)
local height = nil

---@param image Image
local render_image = function(image)
-- utils.debug("[markdown] rendering image", id, window)
if ctx.options.sizing_strategy == "height-from-empty-lines" then
local empty_line_count = -1
local lines = vim.api.nvim_buf_get_lines(window.buffer, 0, -1, false)
for i = match.range.end_row + 2, #lines do
if lines[i] == "" then
empty_line_count = empty_line_count + 1
else
break
end
end
height = math.max(1, empty_line_count)
end
height = math.max(1, empty_line_count)
image:render({
height = height,
x = match.range.start_col,
y = match.range.start_row + 1,
})
table.insert(new_image_ids, id)
end
image:render({
height = height,
x = match.range.start_col,
y = match.range.start_row + 1,
})
table.insert(new_image_ids, id)
end

-- remote
if is_remote_url(match.url) then
if not ctx.options.download_remote_images then return end
-- remote
if is_remote_url(match.url) then
if not ctx.options.download_remote_images then return end

ctx.api.from_url(
match.url,
{ id = id, window = window.id, buffer = window.buffer, with_virtual_padding = true },
function(image)
if not image then return end
render_image(image)
end
)
else
-- local
local path = resolve_absolute_path(file_path, match.url)
local ok, image = pcall(ctx.api.from_file, path, {
id = id,
window = window.id,
buffer = window.buffer,
with_virtual_padding = true,
})
if ok then render_image(image) end
ctx.api.from_url(
match.url,
{ id = id, window = window.id, buffer = window.buffer, with_virtual_padding = true },
function(image)
if not image then return end
render_image(image)
end
)
else
-- local
local path = resolve_absolute_path(file_path, match.url)
local ok, image = pcall(ctx.api.from_file, path, {
id = id,
window = window.id,
buffer = window.buffer,
with_virtual_padding = true,
})
if ok then render_image(image) end
end
end
end

-- clear previous images
for _, image in ipairs(previous_images) do
if not vim.tbl_contains(new_image_ids, image.id) then image:clear() end
-- clear previous images
for _, image in ipairs(previous_images) do
if not vim.tbl_contains(new_image_ids, image.id) then image:clear() end
end
end
end
end
end)
)

---@type fun(ctx: IntegrationContext)
local setup_autocommands = function(ctx)
Expand Down Expand Up @@ -173,12 +181,13 @@ local setup_autocommands = function(ctx)
end
end

---@type fun(api: API, options: MarkdownIntegrationOptions)
local setup = function(api, options)
---@type fun(api: API, options: MarkdownIntegrationOptions, state: State)
local setup = function(api, options, state)
local opts = options or {} --[[@as MarkdownIntegrationOptions]]
local context = {
api = api,
options = opts,
state = state,
}

vim.defer_fn(function()
Expand Down
Loading

0 comments on commit e524050

Please sign in to comment.