From 28d9459cf09019bda716d26a50e1d8962549b060 Mon Sep 17 00:00:00 2001 From: ctbaum Date: Mon, 20 Jul 2026 23:35:13 +0200 Subject: [PATCH 1/2] fix(image): stop progress spinner timer when the placement is closed Placement:progress() starts a repeating 80ms timer whose stop condition requires self:ready(). A closed placement can never become ready, so closing an image buffer while its conversion is still running leaks the timer. Since the buffer stays valid after :bd and is reused when the same file is reopened, the leaked timer keeps clearing the buffer's image extmarks every 80ms and redrawing the loading spinner, which permanently prevents the image from being displayed again. Stop the timer when the placement is closed, and delete the spinner extmark when the timer stops instead of leaving the last drawn spinner in the buffer. --- lua/snacks/image/placement.lua | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lua/snacks/image/placement.lua b/lua/snacks/image/placement.lua index 059585e72..2ba3f06e5 100644 --- a/lua/snacks/image/placement.lua +++ b/lua/snacks/image/placement.lua @@ -149,19 +149,28 @@ function M:progress() vim.api.nvim_buf_set_lines(self.buf, 0, -1, false, {}) vim.bo[self.buf].modifiable = false local timer = assert(uv.new_timer()) + local eid ---@type number? timer:start( 0, 80, vim.schedule_wrap(function() - if self:ready() or self.img:failed() or not vim.api.nvim_buf_is_valid(self.buf) then + local valid = vim.api.nvim_buf_is_valid(self.buf) + -- also stop when the placement is closed: `self:ready()` can never + -- become true for a closed placement, and the buffer may stay valid + -- (and be reused for the same file) long after the placement is gone + if self.closed or self:ready() or self.img:failed() or not valid then timer:stop() if not timer:is_closing() then timer:close() end + -- remove the spinner instead of leaving it behind + if eid and valid then + vim.api.nvim_buf_del_extmark(self.buf, ns, eid) + end return end vim.api.nvim_buf_clear_namespace(self.buf, ns, 0, -1) - vim.api.nvim_buf_set_extmark(self.buf, ns, 0, 0, { + eid = vim.api.nvim_buf_set_extmark(self.buf, ns, 0, 0, { virt_text = { { Snacks.util.spinner(), "SnacksImageSpinner" }, { " " }, From 4ec47ad3d260fc1823e9a9dc2af59a7832f46493 Mon Sep 17 00:00:00 2001 From: ctbaum Date: Mon, 20 Jul 2026 23:57:58 +0200 Subject: [PATCH 2/2] fix(image): un-hide placements when their buffer becomes visible again Placement:update() calls hide() when the buffer is shown in no window, but nothing ever calls show() for buffer placements (only snacks.image.inline manages hide/show explicitly, for inline doc images). Once hidden, update() keeps rendering the placement with self.hidden still set, so all its virtual lines are blanked and the image never reappears after switching back to the buffer. Un-hide non-inline placements in update() when their buffer is visible in a window again. --- lua/snacks/image/placement.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lua/snacks/image/placement.lua b/lua/snacks/image/placement.lua index 2ba3f06e5..9c630f2d4 100644 --- a/lua/snacks/image/placement.lua +++ b/lua/snacks/image/placement.lua @@ -547,6 +547,12 @@ function M:update() self:hide() return end + -- the buffer is visible again, so un-hide. Inline placements are + -- hidden/shown explicitly by `snacks.image.inline`, so leave those alone. + if self.hidden and not self.opts.inline then + self.hidden = false + state.hidden = false + end self.img:place(self) self:debug("update")