From 9b0a22d6e9c858526704a79e61708fb96a016763 Mon Sep 17 00:00:00 2001 From: Dekara VanHoc Date: Thu, 26 Feb 2026 16:34:25 +0100 Subject: [PATCH] fix: harden render against stale window and node inputs Add compatibility fallbacks and defensive guards so mixed plugin states or transient windows do not crash scheduled renders, and keep tests runnable on Neovim builds that use --clean instead of --noplugin. --- justfile | 2 +- lua/render-markdown/lib/env.lua | 6 ++++++ lua/render-markdown/render/base.lua | 9 ++++++++ lua/render-markdown/request/view.lua | 12 +++++++++-- tests/unit/base_spec.lua | 32 ++++++++++++++++++++++++++++ tests/unit/view_spec.lua | 10 +++++++++ 6 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 tests/unit/base_spec.lua diff --git a/justfile b/justfile index a7e2e810..5f283be8 100644 --- a/justfile +++ b/justfile @@ -24,7 +24,7 @@ bench: [private] busted path: - nvim --headless --noplugin -u {{init}} -c "PlenaryBustedDirectory {{path}} {{settings}}" + nvim --headless --clean -u {{init}} -c "PlenaryBustedDirectory {{path}} {{settings}}" health: nvim -c "checkhealth render-markdown" -- - diff --git a/lua/render-markdown/lib/env.lua b/lua/render-markdown/lib/env.lua index a919bd6a..c6411f9d 100644 --- a/lua/render-markdown/lib/env.lua +++ b/lua/render-markdown/lib/env.lua @@ -207,6 +207,12 @@ function M.buf.wins(buf) return vim.fn.win_findbuf(buf) end +---@param buf integer +---@return integer[] +function M.buf.windows(buf) + return M.buf.wins(buf) +end + ---@class render.md.env.Win M.win = {} diff --git a/lua/render-markdown/render/base.lua b/lua/render-markdown/render/base.lua index 8f3445ec..ad09b1c4 100644 --- a/lua/render-markdown/render/base.lua +++ b/lua/render-markdown/render/base.lua @@ -13,6 +13,15 @@ Base.__index = Base ---@param node render.md.Node ---@return boolean function Base:execute(context, marks, node) + if type(node) ~= 'table' or type(node.height) ~= 'function' then + local Node = require('render-markdown.lib.node') + local ok, wrapped = pcall(Node.new, context.buf, node) + if not ok or not wrapped then + return false + end + node = wrapped + end + local instance = setmetatable({}, self) instance.context = context instance.marks = marks diff --git a/lua/render-markdown/request/view.lua b/lua/render-markdown/request/view.lua index 81ec79b6..cb2e46fb 100644 --- a/lua/render-markdown/request/view.lua +++ b/lua/render-markdown/request/view.lua @@ -14,9 +14,14 @@ View.__index = View function View.new(buf) local self = setmetatable({}, View) self.buf = buf + local wins = env.buf.wins and env.buf.wins(buf) + or (env.buf.windows and env.buf.windows(buf)) + or {} local ranges = {} ---@type render.md.Range[] - for _, win in ipairs(env.buf.wins(buf)) do - ranges[#ranges + 1] = env.range(buf, win, 10) + for _, win in ipairs(wins) do + if not env.valid or env.valid(buf, win) then + ranges[#ranges + 1] = env.range(buf, win, 10) + end end self.ranges = interval.coalesce(ranges) return self @@ -34,6 +39,9 @@ end ---@param win integer ---@return boolean function View:contains(win) + if env.valid and not env.valid(self.buf, win) then + return false + end local rows = env.range(self.buf, win, 0) for _, range in ipairs(self.ranges) do if interval.contains(range, rows) then diff --git a/tests/unit/base_spec.lua b/tests/unit/base_spec.lua new file mode 100644 index 00000000..d2bc4609 --- /dev/null +++ b/tests/unit/base_spec.lua @@ -0,0 +1,32 @@ +---@module 'luassert' + +local Base = require('render-markdown.render.base') +local mock = require('luassert.mock') + +describe('base', function() + it('normalizes raw nodes before setup', function() + local Node = mock(require('render-markdown.lib.node'), true) + local wrapped = { + height = function() + return 3 + end, + } + Node.new.on_call_with(1, 'raw-node').returns(wrapped) + + local ran = false + local Render = setmetatable({}, Base) + Render.__index = Render + + function Render:setup() + return type(self.node.height) == 'function' + end + + function Render:run() + ran = true + end + + local ok = Render:execute({ buf = 1 }, {}, 'raw-node') + assert.is_true(ok) + assert.is_true(ran) + end) +end) diff --git a/tests/unit/view_spec.lua b/tests/unit/view_spec.lua index e527a8be..1b7e2514 100644 --- a/tests/unit/view_spec.lua +++ b/tests/unit/view_spec.lua @@ -9,6 +9,7 @@ describe('view', function() local env = mock(require('render-markdown.lib.env'), true) env.buf.wins.on_call_with(0).returns(vim.tbl_keys(ranges)) for win, range in pairs(ranges) do + env.valid.on_call_with(0, win).returns(true) env.range.on_call_with(0, win, 10).returns(range) end end @@ -22,4 +23,13 @@ describe('view', function() }) assert.same('[0->15,20->30,35->45]', tostring(View.new(0))) end) + + it('contains invalid window', function() + local env = mock(require('render-markdown.lib.env'), true) + env.buf.wins.on_call_with(0).returns({ 1000 }) + env.valid.on_call_with(0, 1000).returns(true) + env.range.on_call_with(0, 1000, 10).returns({ 5, 10 }) + env.valid.on_call_with(0, 1001).returns(false) + assert.is_false(View.new(0):contains(1001)) + end) end)