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
13 changes: 7 additions & 6 deletions lua/code-preview/diff.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local M = {}

local log = require("code-preview.log")
local resolve_hl = require("code-preview.hl").resolve

-- Active diffs keyed by absolute file path.
-- Each entry: { tab, bufs, augroup, inline_win }
Expand All @@ -20,10 +21,10 @@ local function apply_highlights(config)
local cur = config.highlights.current
local pro = config.highlights.proposed
for name, hl in pairs(cur) do
vim.api.nvim_set_hl(current_ns, name, hl)
vim.api.nvim_set_hl(current_ns, name, resolve_hl(hl))
end
for name, hl in pairs(pro) do
vim.api.nvim_set_hl(proposed_ns, name, hl)
vim.api.nvim_set_hl(proposed_ns, name, resolve_hl(hl))
end
end

Expand Down Expand Up @@ -203,10 +204,10 @@ end

local function apply_inline_highlights(config)
local hl = config.highlights.inline or {}
vim.api.nvim_set_hl(0, "ClaudeDiffInlineAdded", hl.added or { bg = "#2e4c2e" })
vim.api.nvim_set_hl(0, "ClaudeDiffInlineRemoved", hl.removed or { bg = "#4c2e2e" })
vim.api.nvim_set_hl(0, "ClaudeDiffInlineAddedText", hl.added_text or { bg = "#3a6e3a" })
vim.api.nvim_set_hl(0, "ClaudeDiffInlineRemovedText", hl.removed_text or { bg = "#6e3a3a" })
vim.api.nvim_set_hl(0, "ClaudeDiffInlineAdded", resolve_hl(hl.added) or { bg = "#2e4c2e" })
vim.api.nvim_set_hl(0, "ClaudeDiffInlineRemoved", resolve_hl(hl.removed) or { bg = "#4c2e2e" })
vim.api.nvim_set_hl(0, "ClaudeDiffInlineAddedText", resolve_hl(hl.added_text) or { bg = "#3a6e3a" })
vim.api.nvim_set_hl(0, "ClaudeDiffInlineRemovedText", resolve_hl(hl.removed_text) or { bg = "#6e3a3a" })
vim.api.nvim_set_hl(0, "ClaudeDiffInlineAddedSign", { fg = "#73e896", bold = true })
vim.api.nvim_set_hl(0, "ClaudeDiffInlineRemovedSign", { fg = "#f47070", bold = true })
end
Expand Down
29 changes: 29 additions & 0 deletions lua/code-preview/hl.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-- hl.lua β€” normalizes a highlights-config value into an nvim_set_hl-compatible
-- table. A bare highlight-group-name string links to that group; a table
-- passes through (optionally merged with an override table). Extracted from
-- near-duplicate resolve_hl (diff.lua) / define_hl (neo_tree.lua) helpers.
--
-- IMPORTANT: never set `default = true` here. nvim_set_hl's `default` flag is
-- sticky for the life of the process (once a group has ANY definition in a
-- namespace, a later `default = true` call is silently ignored) β€” that
-- staleness is exactly the bug this module fixes. Do not reintroduce it.
local M = {}

--- @param hl table|string|nil a highlights-config value
--- @param opts table|nil optional overrides merged on top (force)
--- @return table|nil nvim_set_hl-compatible table, or nil if hl is
--- neither a string nor a table (caller decides
--- the fallback, e.g. `resolve(hl) or {...}`)
function M.resolve(hl, opts)
if type(hl) == "string" then
hl = { link = hl }
elseif type(hl) ~= "table" then
return nil
end
if opts then
return vim.tbl_extend("force", hl, opts)
end
return hl
end

return M
8 changes: 4 additions & 4 deletions lua/code-preview/neo_tree.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local M = {}

local changes = require("code-preview.changes")
local log = require("code-preview.log")
local resolve_hl = require("code-preview.hl").resolve

-- Guard: all neo-tree interaction goes through pcall
local has_neo_tree = false
Expand All @@ -16,10 +17,9 @@ local setup_done = false

-- Define a highlight group from config (supports string link or table spec)
local function define_hl(name, hl_config, opts)
if type(hl_config) == "string" then
vim.api.nvim_set_hl(0, name, vim.tbl_extend("force", { link = hl_config, default = true }, opts or {}))
elseif type(hl_config) == "table" then
vim.api.nvim_set_hl(0, name, vim.tbl_extend("force", hl_config, opts or {}))
local resolved = resolve_hl(hl_config, opts)
if resolved then
vim.api.nvim_set_hl(0, name, resolved)
end
end

Expand Down
127 changes: 127 additions & 0 deletions tests/plugin/diff_lifecycle_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,133 @@ local function tmp_file(name, content)
return path
end

describe("diff highlights configured as strings", function()
-- Temporarily override highlights config for one test, restoring it afterwards.
local function with_highlights(highlights, fn)
local cfg = require("code-preview").config
local saved = vim.deepcopy(cfg.highlights)
cfg.highlights = vim.tbl_deep_extend("force", vim.deepcopy(saved), highlights)
local ok, err = pcall(fn)
cfg.highlights = saved
if not ok then error(err, 2) end
end

before_each(function()
changes.clear_all()
diff.close_diff()
vim.api.nvim_set_hl(0, "TestLinkTarget", { fg = "#ff00ff" })
vim.api.nvim_set_hl(0, "TestLinkTargetTwo", { fg = "#00ff00" })
end)

it("accepts a highlight-group-name string for highlights.inline.*", function()
local orig = tmp_file("hl_inline_orig.txt", "line one\nline two")
local prop = tmp_file("hl_inline_prop.txt", "line one\nline TWO")

local ok, err = pcall(function()
with_highlights({ inline = { added = "TestLinkTarget" } }, function()
local diff_cfg = require("code-preview").config.diff
local saved_layout = diff_cfg.layout
diff_cfg.layout = "inline"
diff.show_diff(orig, prop, "hl_inline.txt")
diff_cfg.layout = saved_layout
end)
end)
assert.is_true(ok, tostring(err))
assert.is_true(diff.is_open())

local hl = vim.api.nvim_get_hl(0, { name = "ClaudeDiffInlineAdded" })
assert.equals("TestLinkTarget", hl.link)

diff.close_diff()
os.remove(orig)
os.remove(prop)
end)

it("accepts a highlight-group-name string for highlights.current/proposed", function()
local orig = tmp_file("hl_tab_orig.txt", "line1\nline2")
local prop = tmp_file("hl_tab_prop.txt", "line1\nchanged")

local ok, err = pcall(function()
with_highlights({ current = { DiffAdd = "TestLinkTarget" } }, function()
local diff_cfg = require("code-preview").config.diff
local saved_layout = diff_cfg.layout
diff_cfg.layout = "tab"
diff.show_diff(orig, prop, "hl_tab.txt")
diff_cfg.layout = saved_layout
end)
end)
assert.is_true(ok, tostring(err))
assert.is_true(diff.is_open())

local current_ns = vim.api.nvim_get_namespaces()["claude_diff_current_hl"]
local hl = vim.api.nvim_get_hl(current_ns, { name = "DiffAdd" })
assert.equals("TestLinkTarget", hl.link)

diff.close_diff()
os.remove(orig)
os.remove(prop)
end)

-- Regression test: a re-applied string-linked highlight must pick up a
-- changed target (e.g. after the user edits their config and re-sources
-- it). Previously nvim_set_hl's `default = true` made the first link
-- sticky for the rest of the process, so the second show_diff below would
-- have kept pointing at TestLinkTarget instead of TestLinkTargetTwo.
it("updates the link when a highlights.inline.* string value changes", function()
local orig = tmp_file("hl_inline_relink_orig.txt", "line one\nline two")
local prop = tmp_file("hl_inline_relink_prop.txt", "line one\nline TWO")

local function show_with(target)
with_highlights({ inline = { added = target } }, function()
local diff_cfg = require("code-preview").config.diff
local saved_layout = diff_cfg.layout
diff_cfg.layout = "inline"
diff.show_diff(orig, prop, "hl_inline_relink.txt")
diff_cfg.layout = saved_layout
end)
end

show_with("TestLinkTarget")
assert.equals("TestLinkTarget", vim.api.nvim_get_hl(0, { name = "ClaudeDiffInlineAdded" }).link)
diff.close_diff()

show_with("TestLinkTargetTwo")
assert.equals("TestLinkTargetTwo", vim.api.nvim_get_hl(0, { name = "ClaudeDiffInlineAdded" }).link)

diff.close_diff()
os.remove(orig)
os.remove(prop)
end)

it("updates the link when a highlights.current/proposed string value changes", function()
local orig = tmp_file("hl_tab_relink_orig.txt", "line1\nline2")
local prop = tmp_file("hl_tab_relink_prop.txt", "line1\nchanged")

local function show_with(target)
with_highlights({ current = { DiffAdd = target } }, function()
local diff_cfg = require("code-preview").config.diff
local saved_layout = diff_cfg.layout
diff_cfg.layout = "tab"
diff.show_diff(orig, prop, "hl_tab_relink.txt")
diff_cfg.layout = saved_layout
end)
end

local current_ns = vim.api.nvim_get_namespaces()["claude_diff_current_hl"]

show_with("TestLinkTarget")
assert.equals("TestLinkTarget", vim.api.nvim_get_hl(current_ns, { name = "DiffAdd" }).link)
diff.close_diff()

show_with("TestLinkTargetTwo")
assert.equals("TestLinkTargetTwo", vim.api.nvim_get_hl(current_ns, { name = "DiffAdd" }).link)

diff.close_diff()
os.remove(orig)
os.remove(prop)
end)
end)

describe("diff lifecycle", function()
before_each(function()
changes.clear_all()
Expand Down