diff --git a/doc/render-latex.txt b/doc/render-latex.txt index dbcf5ad..e539a24 100644 --- a/doc/render-latex.txt +++ b/doc/render-latex.txt @@ -37,11 +37,10 @@ and: Inline math is intentionally not image-rendered. It uses highlights and conceal for smooth editing and predictable cursor behavior. -Images are hidden while popups or floating windows are open. Plain command-line -typing does not hide images by default; opt in with -`render.hide_on_cmdline = true`. When command-line hiding is disabled, cmdline -UI floats are ignored but completion popups still hide images until -command-line mode exits. +Images are hidden while completion popups are open. Plain command-line typing +does not hide images by default; opt in with `render.hide_on_cmdline = true`. +Floating windows from other plugins do not hide images, but active float details +are reported by `:RenderLatex doctor` for troubleshooting. =============================================================================== REQUIREMENTS *render-latex-requirements* diff --git a/lua/render_latex/annotations.lua b/lua/render_latex/annotations.lua index fe1acc6..00c9c90 100644 --- a/lua/render_latex/annotations.lua +++ b/lua/render_latex/annotations.lua @@ -1,6 +1,7 @@ local Config = require("render_latex.config") local Compat = require("render_latex.compat") local Detect = require("render_latex.detect") +local Util = require("render_latex.util") local M = {} @@ -326,19 +327,13 @@ function M.render_inline_fallback(bufnr, ns, state, ranges) end local active = {} - for _, winid in ipairs(vim.fn.win_findbuf(bufnr)) do - if vim.api.nvim_win_is_valid(winid) then - local cursor = vim.api.nvim_win_get_cursor(winid) - local cursor_row = cursor[1] - 1 - local cursor_col = cursor[2] - for index, item in ipairs(items) do - if - cursor_row == item.row - and cursor_col >= item.start_col - and cursor_col < item.end_col - then - active[index] = true - end + for _, winid in ipairs(Util.current_tab_wins_for_buf(bufnr)) do + local cursor = vim.api.nvim_win_get_cursor(winid) + local cursor_row = cursor[1] - 1 + local cursor_col = cursor[2] + for index, item in ipairs(items) do + if cursor_row == item.row and cursor_col >= item.start_col and cursor_col < item.end_col then + active[index] = true end end end diff --git a/lua/render_latex/init.lua b/lua/render_latex/init.lua index e64ca83..e83f687 100644 --- a/lua/render_latex/init.lua +++ b/lua/render_latex/init.lua @@ -32,15 +32,13 @@ local function register_autocmds() local function queue_visible_buffers() local seen = {} - for _, winid in ipairs(vim.api.nvim_list_wins()) do - local cfg = vim.api.nvim_win_get_config(winid) - if cfg.relative == nil or cfg.relative == "" then - local bufnr = vim.api.nvim_win_get_buf(winid) - if not seen[bufnr] and should_attach(bufnr) then - seen[bufnr] = true - Renderer.attach(bufnr) - Renderer.queue(bufnr) - end + local Util = require("render_latex.util") + for _, winid in ipairs(Util.current_tab_normal_wins()) do + local bufnr = vim.api.nvim_win_get_buf(winid) + if not seen[bufnr] and should_attach(bufnr) then + seen[bufnr] = true + Renderer.attach(bufnr) + Renderer.queue(bufnr) end end end @@ -58,8 +56,6 @@ local function register_autocmds() end vim.api.nvim_create_autocmd({ - "BufEnter", - "BufWinEnter", "InsertEnter", "InsertLeave", "CursorMoved", @@ -83,6 +79,17 @@ local function register_autocmds() end, }) + vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter", "TabEnter", "WinEnter" }, { + group = Config.augroup, + callback = function(args) + local bufnr = args.buf or vim.api.nvim_get_current_buf() + if not should_attach(bufnr) then + Renderer.detach(bufnr) + end + update_transient_ui_suppression(true) + end, + }) + vim.api.nvim_create_autocmd("ColorScheme", { group = Config.augroup, callback = function() @@ -92,6 +99,13 @@ local function register_autocmds() end, }) + vim.api.nvim_create_autocmd({ "BufLeave", "TabLeave", "WinLeave" }, { + group = Config.augroup, + callback = function() + Renderer.hide_visible() + end, + }) + vim.api.nvim_create_autocmd("WinScrolled", { group = Config.augroup, callback = function(args) @@ -129,12 +143,13 @@ local function register_autocmds() vim.api.nvim_create_autocmd("BufWinLeave", { group = Config.augroup, callback = function(args) - Renderer.detach_window(args.buf, vim.api.nvim_get_current_win()) + Renderer.hide_visible() + Renderer.detach_inactive_windows(args.buf) update_transient_ui_suppression(false) end, }) - vim.api.nvim_create_autocmd({ "CmdlineEnter", "CmdlineChanged", "CmdlineLeave", "WinEnter" }, { + vim.api.nvim_create_autocmd({ "CmdlineEnter", "CmdlineChanged", "CmdlineLeave" }, { group = Config.augroup, callback = function() update_transient_ui_suppression(true) @@ -144,6 +159,7 @@ local function register_autocmds() vim.api.nvim_create_autocmd("WinClosed", { group = Config.augroup, callback = function(args) + Renderer.hide_visible() local winid = tonumber(args.match) if winid ~= nil then Renderer.detach_winid(winid) @@ -261,6 +277,7 @@ function M.doctor_lines() local Install = require("render_latex.install") local Integrations = require("render_latex.integrations") local Renderer = require("render_latex.renderer") + local Ui = require("render_latex.ui") local Worker = require("render_latex.worker") local bufnr = vim.api.nvim_get_current_buf() @@ -272,6 +289,7 @@ function M.doctor_lines() local render = Renderer.resolved_options() local source = require("render_latex.sources").status(bufnr) local suppression = Renderer.suppression_status() + local floats = Ui.floating_windows() local filetype = vim.bo[bufnr].filetype local lines = { @@ -313,7 +331,24 @@ function M.doctor_lines() "conceallevel: " .. tostring(vim.wo.conceallevel), "concealcursor: " .. tostring(vim.wo.concealcursor), "suppressed by cmdline: " .. tostring(suppression.cmdline), - "image placement suppressed by popups/floating windows: " .. tostring(suppression.floating), + "image placement suppressed by popup/cmdline UI: " .. tostring(suppression.floating), + "active floating windows: " .. tostring(#floats), + }) + + for _, float in ipairs(floats) do + lines[#lines + 1] = table.concat({ + "- winid: " .. tostring(float.winid), + "relative: " .. tostring(float.relative), + "focusable: " .. tostring(float.focusable), + "zindex: " .. tostring(float.zindex), + "row: " .. tostring(float.row), + "col: " .. tostring(float.col), + "width: " .. tostring(float.width), + "height: " .. tostring(float.height), + }, ", ") + end + + vim.list_extend(lines, { "", "## Worker", "", diff --git a/lua/render_latex/renderer.lua b/lua/render_latex/renderer.lua index d208597..a897baa 100644 --- a/lua/render_latex/renderer.lua +++ b/lua/render_latex/renderer.lua @@ -444,15 +444,13 @@ end local function visible_inline_ranges(bufnr) local ranges = {} local line_count = vim.api.nvim_buf_line_count(bufnr) - for _, winid in ipairs(vim.fn.win_findbuf(bufnr)) do - if Util.win_is_valid(winid) then - local top = math.max(0, vim.fn.line("w0", winid) - 1) - local bottom = math.min(line_count - 1, vim.fn.line("w$", winid) - 1) - ranges[#ranges + 1] = { start_row = top, end_row = bottom } - end + for _, winid in ipairs(Util.current_tab_wins_for_buf(bufnr)) do + local top = math.max(0, vim.fn.line("w0", winid) - 1) + local bottom = math.min(line_count - 1, vim.fn.line("w$", winid) - 1) + ranges[#ranges + 1] = { start_row = top, end_row = bottom } end if #ranges == 0 then - return nil + return {} end return Sources.inline_ranges(bufnr, ranges) end @@ -614,7 +612,7 @@ function M.hide_all() end end -local function hide_visible() +function M.hide_visible() local backend = ImageBackend.get() if backend ~= nil then pcall(backend.del, math.huge) @@ -766,19 +764,17 @@ local function collect_window_snapshot(bufnr, viewport_state, prefetch) windows = {}, } - for _, winid in ipairs(vim.fn.win_findbuf(bufnr)) do - if Util.win_is_valid(winid) then - local top, bottom = Viewport.viewport_range(viewport_state, winid, prefetch) - local text_top, text_bottom = Viewport.visible_text_bounds(winid) - snapshot.winids[#snapshot.winids + 1] = winid - snapshot.ranges[#snapshot.ranges + 1] = { top = top, bottom = bottom } - snapshot.windows[winid] = { - width = vim.api.nvim_win_get_width(winid), - text_top = text_top, - text_bottom = text_bottom, - cursor_row = vim.api.nvim_win_get_cursor(winid)[1] - 1, - } - end + for _, winid in ipairs(Util.current_tab_wins_for_buf(bufnr)) do + local top, bottom = Viewport.viewport_range(viewport_state, winid, prefetch) + local text_top, text_bottom = Viewport.visible_text_bounds(winid) + snapshot.winids[#snapshot.winids + 1] = winid + snapshot.ranges[#snapshot.ranges + 1] = { top = top, bottom = bottom } + snapshot.windows[winid] = { + width = vim.api.nvim_win_get_width(winid), + text_top = text_top, + text_bottom = text_bottom, + cursor_row = vim.api.nvim_win_get_cursor(winid)[1] - 1, + } end return snapshot @@ -1395,6 +1391,31 @@ function M.detach_window(bufnr, winid) clear_window_images(bufnr, state, winid) end +---@param bufnr integer +function M.detach_inactive_windows(bufnr) + local state = buffers[bufnr] + if state == nil then + return + end + + local winids = {} + for winid, _ in pairs(state.images) do + winids[winid] = true + end + for winid, _ in pairs(state.focused_keys) do + winids[winid] = true + end + for winid, _ in pairs(state.viewports) do + winids[winid] = true + end + + for winid, _ in pairs(winids) do + if not Util.win_is_valid(winid) or vim.api.nvim_win_get_buf(winid) ~= bufnr then + clear_window_images(bufnr, state, winid) + end + end +end + ---@param winid integer function M.detach_winid(winid) for bufnr, state in pairs(buffers) do @@ -1499,7 +1520,7 @@ function M.set_suppressed(reason, value) if suppression.cmdline then M.hide_all() elseif suppression.floating then - hide_visible() + M.hide_visible() end end diff --git a/lua/render_latex/ui.lua b/lua/render_latex/ui.lua index 3f8db96..d82b934 100644 --- a/lua/render_latex/ui.lua +++ b/lua/render_latex/ui.lua @@ -1,17 +1,33 @@ local M = {} -function M.has_popup_or_floating_windows() - if vim.fn.pumvisible() == 1 then - return true - end +local Config = require("render_latex.config") +function M.floating_windows() + local floats = {} for _, winid in ipairs(vim.api.nvim_list_wins()) do local cfg = vim.api.nvim_win_get_config(winid) if cfg.relative ~= nil and cfg.relative ~= "" then - return true + floats[#floats + 1] = { + winid = winid, + relative = cfg.relative, + focusable = cfg.focusable, + zindex = cfg.zindex, + row = cfg.row, + col = cfg.col, + width = cfg.width, + height = cfg.height, + } end end - return false + return floats +end + +function M.has_popup_or_floating_windows() + if vim.fn.pumvisible() == 1 then + return true + end + + return Config.render.hide_on_cmdline and vim.fn.getcmdtype() ~= "" end return M diff --git a/lua/render_latex/util.lua b/lua/render_latex/util.lua index fa8d396..025e0b9 100644 --- a/lua/render_latex/util.lua +++ b/lua/render_latex/util.lua @@ -43,6 +43,38 @@ function M.win_is_valid(winid) return winid ~= nil and vim.api.nvim_win_is_valid(winid) end +---@param winid integer +function M.win_is_normal(winid) + if not M.win_is_valid(winid) then + return false + end + local cfg = vim.api.nvim_win_get_config(winid) + return cfg.relative == nil or cfg.relative == "" +end + +---@return integer[] +function M.current_tab_normal_wins() + local wins = {} + for _, winid in ipairs(vim.api.nvim_tabpage_list_wins(0)) do + if M.win_is_normal(winid) then + wins[#wins + 1] = winid + end + end + return wins +end + +---@param bufnr integer +---@return integer[] +function M.current_tab_wins_for_buf(bufnr) + local wins = {} + for _, winid in ipairs(M.current_tab_normal_wins()) do + if vim.api.nvim_win_get_buf(winid) == bufnr then + wins[#wins + 1] = winid + end + end + return wins +end + ---@param value string ---@return string function M.escape_pattern(value) diff --git a/lua/render_latex/viewport.lua b/lua/render_latex/viewport.lua index 73c7bca..9ceff4e 100644 --- a/lua/render_latex/viewport.lua +++ b/lua/render_latex/viewport.lua @@ -1,4 +1,5 @@ local Config = require("render_latex.config") +local Util = require("render_latex.util") local M = {} @@ -77,11 +78,9 @@ end ---@return { top: integer, bottom: integer }[] function M.viewport_ranges(bufnr, viewport_state, prefetch) local ranges = {} - for _, winid in ipairs(vim.fn.win_findbuf(bufnr)) do - if vim.api.nvim_win_is_valid(winid) then - local top, bottom = M.viewport_range(viewport_state, winid, prefetch) - ranges[#ranges + 1] = { top = top, bottom = bottom } - end + for _, winid in ipairs(Util.current_tab_wins_for_buf(bufnr)) do + local top, bottom = M.viewport_range(viewport_state, winid, prefetch) + ranges[#ranges + 1] = { top = top, bottom = bottom } end return ranges end diff --git a/tests/render_latex_spec.lua b/tests/render_latex_spec.lua index f7d8d0c..885f702 100644 --- a/tests/render_latex_spec.lua +++ b/tests/render_latex_spec.lua @@ -1573,7 +1573,8 @@ describe("render_latex.setup", function() end) it("requeues all visible markdown buffers on ColorScheme", function() - local previous_list_wins = vim.api.nvim_list_wins + local previous_tabpage_list_wins = vim.api.nvim_tabpage_list_wins + local previous_win_is_valid = vim.api.nvim_win_is_valid local previous_win_get_config = vim.api.nvim_win_get_config local previous_win_get_buf = vim.api.nvim_win_get_buf local previous_attach = renderer.attach @@ -1606,9 +1607,12 @@ describe("render_latex.setup", function() local right = vim.api.nvim_create_buf(false, true) vim.bo[left].filetype = "markdown" vim.bo[right].filetype = "markdown" - vim.api.nvim_list_wins = function() + vim.api.nvim_tabpage_list_wins = function() return { 101, 202 } end + vim.api.nvim_win_is_valid = function(winid) + return winid == 101 or winid == 202 + end vim.api.nvim_win_get_config = function() return { relative = "" } end @@ -1622,7 +1626,8 @@ describe("render_latex.setup", function() vim.api.nvim_exec_autocmds("ColorScheme", {}) - vim.api.nvim_list_wins = previous_list_wins + vim.api.nvim_tabpage_list_wins = previous_tabpage_list_wins + vim.api.nvim_win_is_valid = previous_win_is_valid vim.api.nvim_win_get_config = previous_win_get_config vim.api.nvim_win_get_buf = previous_win_get_buf renderer.attach = previous_attach @@ -1638,6 +1643,213 @@ describe("render_latex.setup", function() assert.are.same({ left, right }, queued) end) + it("hides terminal images on context leave events", function() + local previous_hide_visible = renderer.hide_visible + local hide_count = 0 + + renderer.hide_visible = function() + hide_count = hide_count + 1 + end + + render_latex.setup({ install = { auto = false } }) + for _, event in ipairs({ "BufLeave", "TabLeave", "WinLeave" }) do + vim.api.nvim_exec_autocmds(event, { modeline = false }) + end + + renderer.hide_visible = previous_hide_visible + + assert.are.equal(3, hide_count) + end) + + it("hides terminal images on BufWinLeave", function() + local previous_hide_visible = renderer.hide_visible + local previous_detach_inactive_windows = renderer.detach_inactive_windows + local hide_count = 0 + local detached_bufnr + + renderer.hide_visible = function() + hide_count = hide_count + 1 + end + renderer.detach_inactive_windows = function(bufnr) + detached_bufnr = bufnr + end + + local buf = vim.api.nvim_create_buf(false, true) + render_latex.setup({ install = { auto = false } }) + vim.api.nvim_exec_autocmds("BufWinLeave", { buffer = buf, modeline = false }) + + renderer.hide_visible = previous_hide_visible + renderer.detach_inactive_windows = previous_detach_inactive_windows + + assert.are.equal(1, hide_count) + assert.are.equal(buf, detached_bufnr) + end) + + it("hides terminal images on WinClosed", function() + local previous_hide_visible = renderer.hide_visible + local previous_detach_winid = renderer.detach_winid + local hide_count = 0 + local detached_winid + + renderer.hide_visible = function() + hide_count = hide_count + 1 + end + renderer.detach_winid = function(winid) + detached_winid = winid + end + + render_latex.setup({ install = { auto = false } }) + vim.api.nvim_exec_autocmds("WinClosed", { pattern = "42", modeline = false }) + + renderer.hide_visible = previous_hide_visible + renderer.detach_winid = previous_detach_winid + + assert.are.equal(1, hide_count) + assert.are.equal(42, detached_winid) + end) + + it("requeues visible markdown buffers on context enter events", function() + local previous_tabpage_list_wins = vim.api.nvim_tabpage_list_wins + local previous_win_is_valid = vim.api.nvim_win_is_valid + local previous_win_get_config = vim.api.nvim_win_get_config + local previous_win_get_buf = vim.api.nvim_win_get_buf + local previous_attach = renderer.attach + local previous_queue = renderer.queue + local previous_suppression = renderer.suppression_status + local previous_set_suppressed = renderer.set_suppressed + local previous_has_popup = ui.has_popup_or_floating_windows + local previous_getcmdtype = vim.fn.getcmdtype + local attached = {} + local queued = {} + + renderer.attach = function(bufnr) + attached[#attached + 1] = bufnr + end + renderer.queue = function(bufnr) + queued[#queued + 1] = bufnr + end + renderer.suppression_status = function() + return { cmdline = false, floating = false } + end + renderer.set_suppressed = function() end + ui.has_popup_or_floating_windows = function() + return false + end + vim.fn.getcmdtype = function() + return "" + end + + local markdown = vim.api.nvim_create_buf(false, true) + local text = vim.api.nvim_create_buf(false, true) + vim.bo[markdown].filetype = "markdown" + vim.bo[text].filetype = "text" + vim.api.nvim_set_current_buf(markdown) + vim.api.nvim_tabpage_list_wins = function() + return { 101, 202 } + end + vim.api.nvim_win_is_valid = function(winid) + return winid == 101 or winid == 202 + end + vim.api.nvim_win_get_config = function() + return { relative = "" } + end + vim.api.nvim_win_get_buf = function(winid) + return winid == 101 and markdown or text + end + + render_latex.setup({ install = { auto = false } }) + attached = {} + queued = {} + + for _, event in ipairs({ "BufEnter", "BufWinEnter", "TabEnter", "WinEnter" }) do + vim.api.nvim_exec_autocmds(event, { buffer = markdown, modeline = false }) + end + + vim.api.nvim_tabpage_list_wins = previous_tabpage_list_wins + vim.api.nvim_win_is_valid = previous_win_is_valid + vim.api.nvim_win_get_config = previous_win_get_config + vim.api.nvim_win_get_buf = previous_win_get_buf + renderer.attach = previous_attach + renderer.queue = previous_queue + renderer.suppression_status = previous_suppression + renderer.set_suppressed = previous_set_suppressed + ui.has_popup_or_floating_windows = previous_has_popup + vim.fn.getcmdtype = previous_getcmdtype + + assert.are.same({ markdown, markdown, markdown, markdown }, attached) + assert.are.same({ markdown, markdown, markdown, markdown }, queued) + end) + + it("does not queue markdown buffers from inactive tabs", function() + local previous_list_wins = vim.api.nvim_list_wins + local previous_tabpage_list_wins = vim.api.nvim_tabpage_list_wins + local previous_win_is_valid = vim.api.nvim_win_is_valid + local previous_win_get_config = vim.api.nvim_win_get_config + local previous_win_get_buf = vim.api.nvim_win_get_buf + local previous_attach = renderer.attach + local previous_queue = renderer.queue + local previous_suppression = renderer.suppression_status + local previous_set_suppressed = renderer.set_suppressed + local previous_has_popup = ui.has_popup_or_floating_windows + local previous_getcmdtype = vim.fn.getcmdtype + local queued = {} + + renderer.attach = function() end + renderer.queue = function(bufnr) + queued[#queued + 1] = bufnr + end + renderer.suppression_status = function() + return { cmdline = false, floating = false } + end + renderer.set_suppressed = function() end + ui.has_popup_or_floating_windows = function() + return false + end + vim.fn.getcmdtype = function() + return "" + end + + local current = vim.api.nvim_create_buf(false, true) + local inactive = vim.api.nvim_create_buf(false, true) + vim.bo[current].filetype = "text" + vim.bo[inactive].filetype = "markdown" + vim.api.nvim_set_current_buf(current) + vim.api.nvim_list_wins = function() + return { 101, 202 } + end + vim.api.nvim_tabpage_list_wins = function() + return { 101 } + end + vim.api.nvim_win_is_valid = function(winid) + return winid == 101 or winid == 202 + end + vim.api.nvim_win_get_config = function() + return { relative = "" } + end + vim.api.nvim_win_get_buf = function(winid) + return winid == 101 and current or inactive + end + + render_latex.setup({ install = { auto = false } }) + queued = {} + + vim.api.nvim_exec_autocmds("TabEnter", { buffer = current, modeline = false }) + + vim.api.nvim_list_wins = previous_list_wins + vim.api.nvim_tabpage_list_wins = previous_tabpage_list_wins + vim.api.nvim_win_is_valid = previous_win_is_valid + vim.api.nvim_win_get_config = previous_win_get_config + vim.api.nvim_win_get_buf = previous_win_get_buf + renderer.attach = previous_attach + renderer.queue = previous_queue + renderer.suppression_status = previous_suppression + renderer.set_suppressed = previous_set_suppressed + ui.has_popup_or_floating_windows = previous_has_popup + vim.fn.getcmdtype = previous_getcmdtype + + assert.are.same({}, queued) + end) + it("uses the lightweight refresh path on WinScrolled", function() local previous_scroll = renderer.scroll local previous_queue = renderer.queue @@ -1668,8 +1880,10 @@ describe("render_latex.setup", function() end local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(buf) vim.bo[buf].filetype = "markdown" render_latex.setup({ install = { auto = false } }) + queued = {} vim.api.nvim_exec_autocmds("WinScrolled", { buffer = buf, modeline = false }) @@ -1710,8 +1924,10 @@ describe("render_latex.setup", function() end local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(buf) vim.bo[buf].filetype = "markdown" render_latex.setup({ install = { auto = false } }) + queued = {} vim.api.nvim_exec_autocmds("BufEnter", { buffer = buf, modeline = false }) @@ -1727,7 +1943,66 @@ describe("render_latex.setup", function() end) describe("render_latex.ui", function() - it("detects floating windows independently of cmdline settings", function() + it("keeps completion popups suppressed", function() + local previous_pumvisible = vim.fn.pumvisible + vim.fn.pumvisible = function() + return 1 + end + + local ok, detected = pcall(ui.has_popup_or_floating_windows) + vim.fn.pumvisible = previous_pumvisible + + assert.is_true(ok) + assert.is_true(detected) + end) + + it("reports floating window details for diagnostics", function() + local buf = vim.api.nvim_create_buf(false, true) + local win = vim.api.nvim_open_win(buf, false, { + relative = "editor", + row = 1, + col = 1, + width = 10, + height = 1, + style = "minimal", + }) + + local ok, floats = pcall(ui.floating_windows) + vim.api.nvim_win_close(win, true) + + assert.is_true(ok) + assert.are.equal(1, #floats) + assert.are.equal(win, floats[1].winid) + assert.are.equal("editor", floats[1].relative) + assert.are.equal(10, floats[1].width) + assert.are.equal(1, floats[1].height) + end) + + it("does not suppress image placement for floating windows", function() + local buf = vim.api.nvim_create_buf(false, true) + local win = vim.api.nvim_open_win(buf, false, { + relative = "editor", + row = 1, + col = 1, + width = 10, + height = 1, + style = "minimal", + focusable = false, + }) + + local ok, detected = pcall(ui.has_popup_or_floating_windows) + vim.api.nvim_win_close(win, true) + + assert.is_true(ok) + assert.is_false(detected) + end) + + it("does not suppress command-line UI when command-line hiding is disabled", function() + config.setup({ render = { hide_on_cmdline = false } }) + local previous_getcmdtype = vim.fn.getcmdtype + vim.fn.getcmdtype = function() + return ":" + end local buf = vim.api.nvim_create_buf(false, true) local win = vim.api.nvim_open_win(buf, false, { relative = "editor", @@ -1740,6 +2015,23 @@ describe("render_latex.ui", function() local ok, detected = pcall(ui.has_popup_or_floating_windows) vim.api.nvim_win_close(win, true) + vim.fn.getcmdtype = previous_getcmdtype + config.setup() + + assert.is_true(ok) + assert.is_false(detected) + end) + + it("suppresses command-line UI when command-line hiding is enabled", function() + config.setup({ render = { hide_on_cmdline = true } }) + local previous_getcmdtype = vim.fn.getcmdtype + vim.fn.getcmdtype = function() + return ":" + end + + local ok, detected = pcall(ui.has_popup_or_floating_windows) + vim.fn.getcmdtype = previous_getcmdtype + config.setup() assert.is_true(ok) assert.is_true(detected) @@ -1763,6 +2055,7 @@ describe("render_latex.doctor", function() assert.matches("# render%-latex doctor", text) assert.matches("image backend status", text) assert.matches("hide on cmdline", text) + assert.matches("active floating windows", text) assert.matches("kitty probing", text) assert.matches("tmux passthrough", text) assert.matches("## render%-markdown.nvim", text) @@ -1771,6 +2064,32 @@ describe("render_latex.doctor", function() assert.matches("The render loop does not inspect other plugins", text) end) + it("includes active floating window details", function() + local buf = vim.api.nvim_create_buf(false, true) + local win = vim.api.nvim_open_win(buf, false, { + relative = "editor", + row = 2, + col = 3, + width = 12, + height = 4, + style = "minimal", + focusable = false, + zindex = 80, + }) + + local lines = render_latex.doctor_lines() + vim.api.nvim_win_close(win, true) + local text = table.concat(lines, "\n") + + assert.matches("active floating windows: 1", text) + assert.matches("winid: " .. tostring(win), text) + assert.matches("relative: editor", text) + assert.matches("focusable: false", text) + assert.matches("zindex: 80", text) + assert.matches("width: 12", text) + assert.matches("height: 4", text) + end) + it("includes backend availability in tmux diagnostics", function() local previous_open_scratch = util.open_scratch local opened @@ -2497,6 +2816,37 @@ describe("render_latex.worker", function() end) describe("render_latex.renderer", function() + it("does not run whole-buffer inline fallback for inactive-tab buffers", function() + config.setup({ render_modes = { vim.api.nvim_get_mode().mode } }) + local previous_inline = detect.inline + local previous_inline_ranges = detect.inline_ranges + local inline_count = 0 + local inline_ranges_count = 0 + + detect.inline = function() + inline_count = inline_count + 1 + return {} + end + detect.inline_ranges = function(_, ranges) + inline_ranges_count = inline_ranges_count + 1 + assert.are.same({}, ranges) + return {} + end + + local buf = vim.api.nvim_create_buf(true, true) + vim.bo[buf].filetype = "markdown" + vim.api.nvim_buf_set_lines(buf, 0, -1, false, { "inline $x$", "after" }) + + renderer.render(buf) + + detect.inline = previous_inline + detect.inline_ranges = previous_inline_ranges + config.setup() + + assert.are.equal(0, inline_count) + assert.are.equal(1, inline_ranges_count) + end) + local function with_jupynvim_render(opts, assertions) opts = opts or {} local previous_notebook = package.loaded["jupynvim.notebook"] @@ -3860,19 +4210,24 @@ end) describe("render_latex.viewport", function() it("computes each window range once when filtering visible equations", function() - local previous_win_findbuf = vim.fn.win_findbuf + local previous_tabpage_list_wins = vim.api.nvim_tabpage_list_wins local previous_win_is_valid = vim.api.nvim_win_is_valid + local previous_win_get_config = vim.api.nvim_win_get_config + local previous_win_get_buf = vim.api.nvim_win_get_buf local buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_lines(buf, 0, -1, false, { "one", "two", "three" }) - vim.fn.win_findbuf = function(target) - if target == buf then - return { 101 } - end - return {} + vim.api.nvim_tabpage_list_wins = function() + return { 101 } end vim.api.nvim_win_is_valid = function(winid) return winid == 101 end + vim.api.nvim_win_get_config = function() + return { relative = "" } + end + vim.api.nvim_win_get_buf = function() + return buf + end local previous_viewport_range = viewport.viewport_range local calls = 0 @@ -3883,8 +4238,10 @@ describe("render_latex.viewport", function() local ok, ranges = pcall(viewport.viewport_ranges, buf, {}, 0) viewport.viewport_range = previous_viewport_range - vim.fn.win_findbuf = previous_win_findbuf + vim.api.nvim_tabpage_list_wins = previous_tabpage_list_wins vim.api.nvim_win_is_valid = previous_win_is_valid + vim.api.nvim_win_get_config = previous_win_get_config + vim.api.nvim_win_get_buf = previous_win_get_buf assert.is_true(ok) assert.are.equal(1, calls) @@ -3893,21 +4250,26 @@ describe("render_latex.viewport", function() end) it("hides equations inside closed folds from visible equation filtering", function() - local previous_win_findbuf = vim.fn.win_findbuf + local previous_tabpage_list_wins = vim.api.nvim_tabpage_list_wins local previous_win_is_valid = vim.api.nvim_win_is_valid + local previous_win_get_config = vim.api.nvim_win_get_config + local previous_win_get_buf = vim.api.nvim_win_get_buf local previous_viewport_range = viewport.viewport_range local previous_fold_closed = viewport.fold_closed local buf = vim.api.nvim_create_buf(false, true) - vim.fn.win_findbuf = function(target) - if target == buf then - return { 101 } - end - return {} + vim.api.nvim_tabpage_list_wins = function() + return { 101 } end vim.api.nvim_win_is_valid = function(winid) return winid == 101 end + vim.api.nvim_win_get_config = function() + return { relative = "" } + end + vim.api.nvim_win_get_buf = function() + return buf + end viewport.viewport_range = function() return 0, 10 end @@ -3920,8 +4282,10 @@ describe("render_latex.viewport", function() { start_row = 5, end_row = 6, key = "visible" }, }, {}, 0) - vim.fn.win_findbuf = previous_win_findbuf + vim.api.nvim_tabpage_list_wins = previous_tabpage_list_wins vim.api.nvim_win_is_valid = previous_win_is_valid + vim.api.nvim_win_get_config = previous_win_get_config + vim.api.nvim_win_get_buf = previous_win_get_buf viewport.viewport_range = previous_viewport_range viewport.fold_closed = previous_fold_closed