Skip to content

Commit 5455fc9

Browse files
fix(diff): propagate original filetype to proposed change buffer for syntax highlighting (#20)
Ensures the proposed diff window inherits the original file's filetype, restoring syntax highlighting. Adds best-effort filetype detection and unit test. Co-authored-by: ThomasK33 <[email protected]>
1 parent 72a4a41 commit 5455fc9

File tree

2 files changed

+93
-1
lines changed

2 files changed

+93
-1
lines changed

lua/claudecode/diff.lua

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,51 @@ end
231231
-- @param new_file_contents string Contents of the new file
232232
-- @param tab_name string Name for the diff tab/view
233233
-- @return table Result with provider, tab_name, and success status
234+
-- Detect filetype from a path or existing buffer (best-effort)
235+
local function _detect_filetype(path, buf)
236+
-- 1) Try Neovim's builtin matcher if available (>=0.10)
237+
if vim.filetype and type(vim.filetype.match) == "function" then
238+
local ok, ft = pcall(vim.filetype.match, { filename = path })
239+
if ok and ft and ft ~= "" then
240+
return ft
241+
end
242+
end
243+
244+
-- 2) Try reading from existing buffer
245+
if buf and vim.api.nvim_buf_is_valid(buf) then
246+
local ft = vim.api.nvim_buf_get_option(buf, "filetype")
247+
if ft and ft ~= "" then
248+
return ft
249+
end
250+
end
251+
252+
-- 3) Fallback to simple extension mapping
253+
local ext = path:match("%.([%w_%-]+)$") or ""
254+
local simple_map = {
255+
lua = "lua",
256+
ts = "typescript",
257+
js = "javascript",
258+
jsx = "javascriptreact",
259+
tsx = "typescriptreact",
260+
py = "python",
261+
go = "go",
262+
rs = "rust",
263+
c = "c",
264+
h = "c",
265+
cpp = "cpp",
266+
hpp = "cpp",
267+
md = "markdown",
268+
sh = "sh",
269+
zsh = "zsh",
270+
bash = "bash",
271+
json = "json",
272+
yaml = "yaml",
273+
yml = "yaml",
274+
toml = "toml",
275+
}
276+
return simple_map[ext]
277+
end
278+
234279
function M._open_native_diff(old_file_path, new_file_path, new_file_contents, tab_name)
235280
local new_filename = vim.fn.fnamemodify(new_file_path, ":t") .. ".new"
236281
local tmp_file, err = M._create_temp_file(new_file_contents, new_filename)
@@ -259,9 +304,16 @@ function M._open_native_diff(old_file_path, new_file_path, new_file_contents, ta
259304
vim.cmd("edit " .. vim.fn.fnameescape(tmp_file))
260305
vim.api.nvim_buf_set_name(0, new_file_path .. " (New)")
261306

307+
-- Propagate filetype to the proposed buffer for proper syntax highlighting (#20)
308+
local proposed_buf = vim.api.nvim_get_current_buf()
309+
local old_filetype = _detect_filetype(old_file_path)
310+
if old_filetype and old_filetype ~= "" then
311+
vim.api.nvim_set_option_value("filetype", old_filetype, { buf = proposed_buf })
312+
end
313+
262314
vim.cmd("wincmd =")
263315

264-
local new_buf = vim.api.nvim_get_current_buf()
316+
local new_buf = proposed_buf
265317
vim.api.nvim_set_option_value("buftype", "nofile", { buf = new_buf })
266318
vim.api.nvim_set_option_value("bufhidden", "wipe", { buf = new_buf })
267319
vim.api.nvim_set_option_value("swapfile", false, { buf = new_buf })
@@ -665,6 +717,12 @@ function M._create_diff_view_from_window(target_window, old_file_path, new_buffe
665717
vim.cmd("vsplit")
666718
local new_win = vim.api.nvim_get_current_win()
667719
vim.api.nvim_win_set_buf(new_win, new_buffer)
720+
721+
-- Ensure new buffer inherits filetype from original for syntax highlighting (#20)
722+
local original_ft = _detect_filetype(old_file_path, original_buffer)
723+
if original_ft and original_ft ~= "" then
724+
vim.api.nvim_set_option_value("filetype", original_ft, { buf = new_buffer })
725+
end
668726
vim.cmd("diffthis")
669727
logger.debug("diff", "Created split window", new_win, "with new buffer", new_buffer)
670728

tests/unit/diff_spec.lua

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,40 @@ describe("Diff Module", function()
215215
end)
216216
end)
217217

218+
describe("Filetype Propagation", function()
219+
it("should propagate original filetype to proposed buffer", function()
220+
diff.setup({})
221+
222+
-- Spy on nvim_set_option_value
223+
spy.on(_G.vim.api, "nvim_set_option_value")
224+
225+
local mock_file = {
226+
write = function() end,
227+
close = function() end,
228+
}
229+
local old_io_open = io.open
230+
rawset(io, "open", function()
231+
return mock_file
232+
end)
233+
234+
local result = diff._open_native_diff("/tmp/test.ts", "/tmp/test.ts", "-- new", "Propagate FT")
235+
expect(result.success).to_be_true()
236+
237+
-- Verify spy called with filetype typescript
238+
local calls = _G.vim.api.nvim_set_option_value.calls or {}
239+
local found = false
240+
for _, c in ipairs(calls) do
241+
if c.vals[1] == "filetype" and c.vals[2] == "typescript" then
242+
found = true
243+
break
244+
end
245+
end
246+
expect(found).to_be_true()
247+
248+
rawset(io, "open", old_io_open)
249+
end)
250+
end)
251+
218252
describe("Open Diff Function", function()
219253
it("should use native provider", function()
220254
diff.setup({})

0 commit comments

Comments
 (0)