Skip to content

Commit 6d6e390

Browse files
authored
refactor(diff): improve line matching and replacement logic (#1329)
Refactors diff application logic to use a new utils.split_lines function for consistent line splitting. Applies diffs from bottom to top to preserve line numbering and ensures correct replacement of lines in buffers. This improves accuracy when applying multiple diffs to the same file and enhances code readability.
1 parent 76cc416 commit 6d6e390

File tree

2 files changed

+43
-24
lines changed

2 files changed

+43
-24
lines changed

lua/CopilotChat/config/mappings.lua

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ local function get_diff(block)
5151

5252
-- If we found a valid buffer, get the reference content
5353
if bufnr and utils.buf_valid(bufnr) then
54-
reference = table.concat(vim.api.nvim_buf_get_lines(bufnr, start_line - 1, end_line, false), '\n')
54+
local lines = vim.api.nvim_buf_get_lines(bufnr, start_line - 1, end_line, false)
55+
reference = table.concat(lines, '\n')
5556
filetype = vim.bo[bufnr].filetype
5657
end
5758
end
@@ -212,7 +213,7 @@ return {
212213
return
213214
end
214215

215-
local lines = vim.split(message.content, '\n')
216+
local lines = utils.split_lines(message.content)
216217
local new_lines = {}
217218
local changed = false
218219

@@ -241,9 +242,9 @@ return {
241242
return
242243
end
243244

244-
local lines = vim.split(diff.change, '\n', { trimempty = false })
245+
local lines = utils.split_lines(diff.change)
245246
vim.api.nvim_buf_set_lines(diff.bufnr, diff.start_line - 1, diff.end_line, false, lines)
246-
copilot.set_selection(diff.bufnr, diff.start_line, diff.start_line + #lines - 1)
247+
copilot.set_selection(diff.bufnr, diff.start_line, diff.end_line)
247248
end,
248249
},
249250

@@ -352,10 +353,9 @@ return {
352353
}
353354

354355
if copilot.config.mappings.show_diff.full_diff then
355-
local modified = utils.buf_valid(diff.bufnr) and vim.api.nvim_buf_get_lines(diff.bufnr, 0, -1, false) or {}
356+
local original = utils.buf_valid(diff.bufnr) and vim.api.nvim_buf_get_lines(diff.bufnr, 0, -1, false) or {}
356357

357-
-- Apply all diffs from same file
358-
if #modified > 0 then
358+
if #original > 0 then
359359
-- Find all diffs from the same file in this section
360360
local message = copilot.chat:get_message(constants.ROLE.ASSISTANT, true)
361361
local section = message and message.section
@@ -367,30 +367,38 @@ return {
367367
table.insert(same_file_diffs, block_diff)
368368
end
369369
end
370+
end
370371

371-
-- Sort diffs bottom to top to preserve line numbering
372-
table.sort(same_file_diffs, function(a, b)
373-
return a.start_line > b.start_line
374-
end)
372+
-- Ensure we at least apply the current diff
373+
if #same_file_diffs == 0 then
374+
table.insert(same_file_diffs, diff)
375375
end
376376

377-
for _, file_diff in ipairs(same_file_diffs) do
378-
local start_idx = file_diff.start_line
379-
local end_idx = file_diff.end_line
380-
for _ = start_idx, end_idx do
381-
table.remove(modified, start_idx)
377+
-- Sort diffs by start_line in descending order (apply from bottom to top)
378+
table.sort(same_file_diffs, function(a, b)
379+
return a.start_line > b.start_line
380+
end)
381+
382+
local result = vim.deepcopy(original)
383+
384+
-- Apply diffs from bottom to top so line numbers remain valid
385+
for _, d in ipairs(same_file_diffs) do
386+
local change_lines = utils.split_lines(d.change)
387+
388+
-- Remove original lines (from end to start to avoid index shifting)
389+
for i = d.end_line, d.start_line, -1 do
390+
if result[i] then
391+
table.remove(result, i)
392+
end
382393
end
383-
local change_lines = vim.split(file_diff.change, '\n')
384-
for i, line in ipairs(change_lines) do
385-
table.insert(modified, start_idx + i, line)
394+
395+
-- Insert replacement lines at start_line
396+
for i = #change_lines, 1, -1 do
397+
table.insert(result, d.start_line, change_lines[i])
386398
end
387399
end
388400

389-
modified = vim.tbl_filter(function(line)
390-
return line ~= nil
391-
end, modified)
392-
393-
opts.text = table.concat(modified, '\n')
401+
opts.text = table.concat(result, '\n')
394402
else
395403
opts.text = diff.change
396404
end

lua/CopilotChat/utils.lua

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,17 @@ function M.empty(v)
772772
return false
773773
end
774774

775+
--- Split text into lines
776+
---@param text string The text to split
777+
---@return string[] A table of lines
778+
function M.split_lines(text)
779+
if not text or text == '' then
780+
return {}
781+
end
782+
783+
return vim.split(text, '\r?\n', { trimempty = false })
784+
end
785+
775786
--- Convert glob pattern to regex pattern
776787
--- https://github.com/davidm/lua-glob-pattern/blob/master/lua/globtopattern.lua
777788
---@param g string The glob pattern

0 commit comments

Comments
 (0)