From c6113da5a9992881eddf98c985e6da888db76bc6 Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Sun, 12 Oct 2025 20:25:14 +0200 Subject: [PATCH] fix(difftool): fully resolve symlinks when comparing paths #36147 Fixes issue on mac where it was constantly reloading buffers as paths were not being normalized and resolved correctly (in relation to buffer name). Quickfix entry: /var/folders/pt/2s7dzyw12v36tsslrghfgpkr0000gn/T/git-difftool.m95lj8/right/app.vue Buffer name: /private/var/folders/pt/2s7dzyw12v36tsslrghfgpkr0000gn/T/git-difftool.m95lj8/right/app.vue /var was synlinked to /private/var and this was not being properly handled. Also added lazy redraw to avoid too many redraws when this happens in future and added test for symlink handling. Signed-off-by: Tomas Slusny --- runtime/lua/vim/_core/util.lua | 14 ++++++++--- .../dist/opt/nvim.difftool/lua/difftool.lua | 23 +++++++++++------ test/functional/plugin/difftool_spec.lua | 25 +++++++++++++++++++ 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/runtime/lua/vim/_core/util.lua b/runtime/lua/vim/_core/util.lua index 561118369e..9444c70085 100644 --- a/runtime/lua/vim/_core/util.lua +++ b/runtime/lua/vim/_core/util.lua @@ -5,12 +5,20 @@ local M = {} --- @param file string --- @return number buffer number of the edited buffer M.edit_in = function(winnr, file) + local function resolved_path(path) + if not path or path == '' then + return '' + end + return vim.fn.resolve(vim.fs.abspath(path)) + end + return vim.api.nvim_win_call(winnr, function() - local current = vim.fs.abspath(vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(winnr))) + local current_buf = vim.api.nvim_win_get_buf(winnr) + local current = resolved_path(vim.api.nvim_buf_get_name(current_buf)) -- Check if the current buffer is already the target file - if current == (file and vim.fs.abspath(file) or '') then - return vim.api.nvim_get_current_buf() + if current == resolved_path(file) then + return current_buf end -- Read the file into the buffer diff --git a/runtime/pack/dist/opt/nvim.difftool/lua/difftool.lua b/runtime/pack/dist/opt/nvim.difftool/lua/difftool.lua index 1b4d974f82..41c7fde3b5 100644 --- a/runtime/pack/dist/opt/nvim.difftool/lua/difftool.lua +++ b/runtime/pack/dist/opt/nvim.difftool/lua/difftool.lua @@ -140,14 +140,16 @@ local function diff_dirs_diffr(left_dir, right_dir, opt) elseif right_exists then status = 'A' end + local left = vim.fn.resolve(vim.fs.abspath(modified_left)) + local right = vim.fn.resolve(vim.fs.abspath(modified_right)) table.insert(qf_entries, { - filename = modified_right, + filename = right, text = status, user_data = { diff = true, rel = vim.fs.relpath(left_dir, modified_left), - left = vim.fs.abspath(modified_left), - right = vim.fs.abspath(modified_right), + left = left, + right = right, }, }) end @@ -426,7 +428,7 @@ function M.open(left, right, opt) layout.group = vim.api.nvim_create_augroup('nvim.difftool.events', { clear = true }) local hl_id = vim.api.nvim_create_namespace('nvim.difftool.hl') - local function get_diff_entry() + local function get_diff_entry(bufnr) --- @type {idx: number, items: table[], size: number} local qf_info = vim.fn.getqflist({ idx = 0, items = 1, size = 1 }) if qf_info.size == 0 then @@ -434,7 +436,12 @@ function M.open(left, right, opt) end local entry = qf_info.items[qf_info.idx] - if not entry or not entry.user_data or not entry.user_data.diff then + if + not entry + or not entry.user_data + or not entry.user_data.diff + or (bufnr and entry.bufnr ~= bufnr) + then return nil end @@ -466,14 +473,16 @@ function M.open(left, right, opt) vim.api.nvim_create_autocmd('BufWinEnter', { group = layout.group, pattern = '*', - callback = function() - local entry = get_diff_entry() + callback = function(args) + local entry = get_diff_entry(args.buf) if not entry then return end + vim.w.lazyredraw = true vim.schedule(function() diff_files(entry.user_data.left, entry.user_data.right, true) + vim.w.lazyredraw = false end) end, }) diff --git a/test/functional/plugin/difftool_spec.lua b/test/functional/plugin/difftool_spec.lua index 7cff9b4251..87024c9abb 100644 --- a/test/functional/plugin/difftool_spec.lua +++ b/test/functional/plugin/difftool_spec.lua @@ -61,6 +61,31 @@ describe('nvim.difftool', function() ) end) + it('handles symlinks', function() + -- Create a symlink in right dir pointing to file2.txt in left dir + local symlink_path = vim.fs.joinpath(testdir_right, 'file2.txt') + local target_path = vim.fs.joinpath('..', testdir_left, 'file2.txt') + assert(vim.uv.fs_symlink(target_path, symlink_path) == true) + finally(function() + os.remove(symlink_path) + end) + assert(fn.getftype(symlink_path) == 'link') + + -- Run difftool + command(('DiffTool %s %s'):format(testdir_left, testdir_right)) + local qflist = fn.getqflist() + local entries = {} + for _, item in ipairs(qflist) do + table.insert(entries, { text = item.text, rel = item.user_data and item.user_data.rel }) + end + + -- file2.txt should not be reported as added or deleted anymore + eq({ + { text = 'M', rel = 'file1.txt' }, + { text = 'A', rel = 'file3.txt' }, + }, entries) + end) + it('has autocmds when diff window is opened', function() command(('DiffTool %s %s'):format(testdir_left, testdir_right)) local autocmds = fn.nvim_get_autocmds({ group = 'nvim.difftool.events' })