Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lua/image/image.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function Image:render(geometry)
if not self.global_state.enabled then return end

if geometry then self.geometry = vim.tbl_deep_extend("force", self.geometry, geometry) end
if self.global_state.rendering_paused then return end

-- don't render if we are in the conmmand-line-window, in this case previously rendered images can
-- be left in place
Expand Down
19 changes: 14 additions & 5 deletions lua/image/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ local state = {
remote_cache = {},
tmp_dir = vim.fn.tempname(),
disable_decorator_handling = false,
rendering_paused = false,
hijacked_win_buf_images = {},
enabled = true,
}
Expand Down Expand Up @@ -384,22 +385,27 @@ api.setup = function(options)
or (state.options.tmux_show_only_in_active_window and utils.tmux.is_tmux)
then
local images_to_restore_on_focus = {}
local focus_event_generation = 0
local initial_tmux_window_id = utils.tmux.get_window_id()
local initial_tmux_session = utils.tmux.get_current_session()

vim.api.nvim_create_autocmd("FocusLost", {
group = group,
callback = function() -- auto-clear images when windows and buffers change
-- bail if not enabled
focus_event_generation = focus_event_generation + 1
local generation = focus_event_generation
if state.options.editor_only_render_when_focused then state.rendering_paused = true end
if not state.enabled then return end

vim.schedule(function()
if generation ~= focus_event_generation then return end
log.debug("FocusLost")
if
state.options.editor_only_render_when_focused
or (utils.tmux.is_tmux and utils.tmux.get_window_id() ~= initial_tmux_window_id)
or (utils.tmux.is_tmux and utils.tmux.get_current_session() ~= initial_tmux_session)
then
state.rendering_paused = true
state.disable_decorator_handling = true

local images = api.get_images()
Expand All @@ -417,26 +423,29 @@ api.setup = function(options)
vim.api.nvim_create_autocmd("FocusGained", {
group = group,
callback = function() -- auto-clear images when windows and buffers change
-- bail if not enabled
focus_event_generation = focus_event_generation + 1
local was_rendering_paused = state.rendering_paused
state.rendering_paused = false
state.disable_decorator_handling = false
if not state.enabled then return end

log.debug("FocusGained")

state.disable_decorator_handling = false

vim.schedule_wrap(function()
-- force rerender
local images = api.get_images()
for _, current_image in ipairs(images) do
if current_image.is_rendered then
current_image:clear()
current_image:render()
elseif was_rendering_paused then
current_image:render()
end
end

-- render images cleared on focus loss
for _, current_image in ipairs(images_to_restore_on_focus) do
current_image:render()
if not current_image.is_rendered then current_image:render() end
end
images_to_restore_on_focus = {}
end)()
Expand Down
1 change: 1 addition & 0 deletions lua/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
---@field remote_cache { [string]: string }
---@field tmp_dir string
---@field disable_decorator_handling boolean
---@field rendering_paused boolean
---@field hijacked_win_buf_images { [string]: Image }
---@field processor ImageProcessor

Expand Down
108 changes: 108 additions & 0 deletions tests/core/focus_rendering_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
local unload_image_modules = function()
for module_name in pairs(package.loaded) do
if module_name == "image" or module_name:match("^image/") then package.loaded[module_name] = nil end
end
end

local disabled_integrations = {
markdown = { enabled = false },
asciidoc = { enabled = false },
typst = { enabled = false },
neorg = { enabled = false },
rst = { enabled = false },
syslang = { enabled = false },
html = { enabled = false },
css = { enabled = false },
org = { enabled = false },
}

describe("focus-aware rendering", function()
local originals
local render_calls
local backend_state

before_each(function()
unload_image_modules()
originals = {
create_autocmd = vim.api.nvim_create_autocmd,
schedule = vim.schedule,
schedule_wrap = vim.schedule_wrap,
uv = vim.uv,
}
render_calls = {}
backend_state = nil
if not vim.uv then vim.uv = vim.loop end

package.loaded["image/renderer"] = {
render = function(image)
render_calls[#render_calls + 1] = image.id
image.global_state.images[image.id] = image
image.is_rendered = true
return true
end,
clear_cache_for_path = function() end,
}
package.loaded["image/backends/kitty"] = {
features = { crop = true },
setup = function(state)
backend_state = state
end,
clear = function(id, shallow)
local image = backend_state.images[id]
if not image then return end
image.is_rendered = false
if not shallow then backend_state.images[id] = nil end
end,
render = function(image)
image.is_rendered = true
backend_state.images[image.id] = image
end,
}
end)

after_each(function()
vim.api.nvim_create_autocmd = originals.create_autocmd
vim.schedule = originals.schedule
vim.schedule_wrap = originals.schedule_wrap
vim.uv = originals.uv
pcall(vim.api.nvim_del_augroup_by_name, "image.nvim")
local namespace = vim.api.nvim_get_namespaces()["image.nvim"]
if namespace then pcall(vim.api.nvim_set_decoration_provider, namespace, {}) end
unload_image_modules()
end)

it("keeps images hidden while unfocused and restores them on focus gain", function()
local focus_lost
local focus_gained
vim.schedule = function(callback)
callback()
end
vim.schedule_wrap = function(callback)
return callback
end
vim.api.nvim_create_autocmd = function(events, options)
if events == "FocusLost" then focus_lost = options.callback end
if events == "FocusGained" then focus_gained = options.callback end
return originals.create_autocmd(events, options)
end

local image = require("image")
image.setup({
editor_only_render_when_focused = true,
integrations = disabled_integrations,
hijack_file_patterns = {},
})
vim.api.nvim_create_autocmd = originals.create_autocmd

local current_image = image.from_file("tests/test_data/100x100.png")
current_image:render()
assert.are.same(1, #render_calls)

focus_lost()
current_image:render()
assert.are.same(1, #render_calls)

focus_gained()
assert.are.same(2, #render_calls)
end)
end)