From 47770cd4b83321092c94e352d1a379e0cde69916 Mon Sep 17 00:00:00 2001 From: Cannon07 Date: Thu, 11 Jun 2026 21:11:41 +0530 Subject: [PATCH] fix: validate diff layout, fall back to tab on unknown value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An unknown diff layout — a typo like "vspllit", or a layout removed in a future version — silently fell through to the "tabnew" branch, making the config look ignored. Validate the resolved layout (both diff.layout and the per-backend diff.layouts overrides) in the layout_for_backend chokepoint; warn once (log.warn surfaces via vim.notify, so guard against per-diff spam) and fall back to "tab". Surfaced during review of #82 (per-backend layouts) as a pre-existing gap that the new layouts map widened. Co-Authored-By: Claude Opus 4.8 --- lua/code-preview/diff.lua | 24 +++++++++++++++-- tests/plugin/diff_lifecycle_spec.lua | 40 ++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/lua/code-preview/diff.lua b/lua/code-preview/diff.lua index 8d19964..59aac87 100644 --- a/lua/code-preview/diff.lua +++ b/lua/code-preview/diff.lua @@ -127,15 +127,35 @@ local function active_count() return n end +local VALID_LAYOUTS = { tab = true, vsplit = true, inline = true } + +-- Values we've already warned about, so a misconfigured layout warns once +-- rather than on every diff (log.warn surfaces via vim.notify). +local warned_layouts = {} + +-- Validate a resolved layout. An unknown value (a typo like "vspllit", or a +-- layout removed in a future version) otherwise falls through to the "tabnew" +-- branch silently, making the config look ignored. Warn once and fall back to +-- the supported default. +local function valid_layout(layout) + if VALID_LAYOUTS[layout] then return layout end + if layout and not warned_layouts[layout] then + warned_layouts[layout] = true + log.warn(log.fmt( + 'unknown diff layout %q; falling back to "tab" (valid: tab, vsplit, inline)', layout)) + end + return "tab" +end + local function layout_for_backend(cfg, backend) local diff_cfg = (cfg and cfg.diff) or {} local layouts = diff_cfg.layouts or {} if backend and layouts[backend] then - return layouts[backend] + return valid_layout(layouts[backend]) end - return diff_cfg.layout or "tab" + return valid_layout(diff_cfg.layout or "tab") end function M.is_open(file_path) diff --git a/tests/plugin/diff_lifecycle_spec.lua b/tests/plugin/diff_lifecycle_spec.lua index de805a0..e1c674f 100644 --- a/tests/plugin/diff_lifecycle_spec.lua +++ b/tests/plugin/diff_lifecycle_spec.lua @@ -333,4 +333,44 @@ describe("diff layouts", function() os.remove(orig) os.remove(prop) end) + + it("an unknown layout value falls back to the default tab layout", function() + local orig = tmp_file("bad_layout_orig.txt", "alpha\nbeta") + local prop = tmp_file("bad_layout_prop.txt", "alpha\ngamma") + + local tabs_before = #vim.api.nvim_list_tabpages() + + -- A typo'd layout must not silently behave like vsplit/inline; it warns + -- (once) and falls back to a new tab. + with_layout("vspllit", function() + diff.show_diff(orig, prop, "layout_bad_value.txt") + end) + + assert.is_true(diff.is_open()) + assert.equals(tabs_before + 1, #vim.api.nvim_list_tabpages()) + local diff_tabpage = vim.api.nvim_get_current_tabpage() + assert.equals(2, #vim.api.nvim_tabpage_list_wins(diff_tabpage)) + + diff.close_diff() + os.remove(orig) + os.remove(prop) + end) + + it("an unknown per-backend layout override falls back to tab", function() + local orig = tmp_file("bad_backend_orig.txt", "alpha\nbeta") + local prop = tmp_file("bad_backend_prop.txt", "alpha\ngamma") + + local tabs_before = #vim.api.nvim_list_tabpages() + + with_layout("tab", function() + diff.show_diff(orig, prop, "layout_bad_backend.txt", nil, nil, "codex") + end, { codex = "vspllit" }) + + assert.is_true(diff.is_open()) + assert.equals(tabs_before + 1, #vim.api.nvim_list_tabpages()) + + diff.close_diff() + os.remove(orig) + os.remove(prop) + end) end)