diff --git a/Makefile b/Makefile index 071b76b..3222f5b 100644 --- a/Makefile +++ b/Makefile @@ -3,12 +3,19 @@ # Default target all: format check test +# Detect if we are already inside a Nix shell +ifeq (,$(IN_NIX_SHELL)) +NIX_PREFIX := nix develop .#ci -c +else +NIX_PREFIX := +endif + # Check for syntax errors check: @echo "Checking Lua files for syntax errors..." - nix develop .#ci -c find lua -name "*.lua" -type f -exec lua -e "assert(loadfile('{}'))" \; + $(NIX_PREFIX) find lua -name "*.lua" -type f -exec lua -e "assert(loadfile('{}'))" \; @echo "Running luacheck..." - nix develop .#ci -c luacheck lua/ tests/ --no-unused-args --no-max-line-length + $(NIX_PREFIX) luacheck lua/ tests/ --no-unused-args --no-max-line-length # Format all files format: diff --git a/lua/claudecode/diff.lua b/lua/claudecode/diff.lua index ef44503..ac7317c 100644 --- a/lua/claudecode/diff.lua +++ b/lua/claudecode/diff.lua @@ -225,12 +225,57 @@ function M._cleanup_diff_layout(tab_name, target_win, new_win) logger.debug("diff", "[CLEANUP] Layout cleanup completed for:", tab_name) end +-- Detect filetype from a path or existing buffer (best-effort) +local function _detect_filetype(path, buf) + -- 1) Try Neovim's builtin matcher if available (>=0.10) + if vim.filetype and type(vim.filetype.match) == "function" then + local ok, ft = pcall(vim.filetype.match, { filename = path }) + if ok and ft and ft ~= "" then + return ft + end + end + + -- 2) Try reading from existing buffer + if buf and vim.api.nvim_buf_is_valid(buf) then + local ft = vim.api.nvim_buf_get_option(buf, "filetype") + if ft and ft ~= "" then + return ft + end + end + + -- 3) Fallback to simple extension mapping + local ext = path:match("%.([%w_%-]+)$") or "" + local simple_map = { + lua = "lua", + ts = "typescript", + js = "javascript", + jsx = "javascriptreact", + tsx = "typescriptreact", + py = "python", + go = "go", + rs = "rust", + c = "c", + h = "c", + cpp = "cpp", + hpp = "cpp", + md = "markdown", + sh = "sh", + zsh = "zsh", + bash = "bash", + json = "json", + yaml = "yaml", + yml = "yaml", + toml = "toml", + } + return simple_map[ext] +end --- Open diff using native Neovim functionality -- @param old_file_path string Path to the original file -- @param new_file_path string Path to the new file (used for naming) -- @param new_file_contents string Contents of the new file -- @param tab_name string Name for the diff tab/view -- @return table Result with provider, tab_name, and success status + function M._open_native_diff(old_file_path, new_file_path, new_file_contents, tab_name) local new_filename = vim.fn.fnamemodify(new_file_path, ":t") .. ".new" 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 vim.cmd("edit " .. vim.fn.fnameescape(tmp_file)) vim.api.nvim_buf_set_name(0, new_file_path .. " (New)") + -- Propagate filetype to the proposed buffer for proper syntax highlighting (#20) + local proposed_buf = vim.api.nvim_get_current_buf() + local old_filetype = _detect_filetype(old_file_path) + if old_filetype and old_filetype ~= "" then + vim.api.nvim_set_option_value("filetype", old_filetype, { buf = proposed_buf }) + end + vim.cmd("wincmd =") - local new_buf = vim.api.nvim_get_current_buf() + local new_buf = proposed_buf vim.api.nvim_set_option_value("buftype", "nofile", { buf = new_buf }) vim.api.nvim_set_option_value("bufhidden", "wipe", { buf = new_buf }) 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 vim.cmd("vsplit") local new_win = vim.api.nvim_get_current_win() vim.api.nvim_win_set_buf(new_win, new_buffer) + + -- Ensure new buffer inherits filetype from original for syntax highlighting (#20) + local original_ft = _detect_filetype(old_file_path, original_buffer) + if original_ft and original_ft ~= "" then + vim.api.nvim_set_option_value("filetype", original_ft, { buf = new_buffer }) + end vim.cmd("diffthis") logger.debug("diff", "Created split window", new_win, "with new buffer", new_buffer) diff --git a/tests/unit/diff_spec.lua b/tests/unit/diff_spec.lua index 3d6faa6..e91e408 100644 --- a/tests/unit/diff_spec.lua +++ b/tests/unit/diff_spec.lua @@ -215,6 +215,40 @@ describe("Diff Module", function() end) end) + describe("Filetype Propagation", function() + it("should propagate original filetype to proposed buffer", function() + diff.setup({}) + + -- Spy on nvim_set_option_value + spy.on(_G.vim.api, "nvim_set_option_value") + + local mock_file = { + write = function() end, + close = function() end, + } + local old_io_open = io.open + rawset(io, "open", function() + return mock_file + end) + + local result = diff._open_native_diff("/tmp/test.ts", "/tmp/test.ts", "-- new", "Propagate FT") + expect(result.success).to_be_true() + + -- Verify spy called with filetype typescript + local calls = _G.vim.api.nvim_set_option_value.calls or {} + local found = false + for _, c in ipairs(calls) do + if c.vals[1] == "filetype" and c.vals[2] == "typescript" then + found = true + break + end + end + expect(found).to_be_true() + + rawset(io, "open", old_io_open) + end) + end) + describe("Open Diff Function", function() it("should use native provider", function() diff.setup({})