@@ -40,55 +40,81 @@ local function parse_hunks(diff_text)
4040 return hunks
4141end
4242
43- --- Apply a single hunk to content, with fallback/context logic
43+ --- Try to match old_snippet in lines starting at approximate start_line
44+ --- @param lines table
45+ --- @param old_snippet table
46+ --- @param approx_start number
47+ --- @param search_range number
48+ --- @return number ? matched_start
49+ local function find_best_match (lines , old_snippet , approx_start , search_range )
50+ local best_idx , best_score = nil , - 1
51+ local old_len = # old_snippet
52+
53+ if old_len == 0 then
54+ return approx_start
55+ end
56+
57+ local min_start = math.max (1 , approx_start - search_range )
58+ local max_start = math.min (# lines - old_len + 1 , approx_start + search_range )
59+
60+ for start_idx = min_start , max_start do
61+ local score = 0
62+ for i = 1 , old_len do
63+ if vim .trim (lines [start_idx + i - 1 ] or ' ' ) == vim .trim (old_snippet [i ] or ' ' ) then
64+ score = score + 1
65+ end
66+ end
67+
68+ if score > best_score then
69+ best_score = score
70+ best_idx = start_idx
71+ end
72+
73+ if score == old_len then
74+ return best_idx
75+ end
76+ end
77+
78+ if best_score >= math.ceil (old_len * 0.8 ) then
79+ return best_idx
80+ end
81+
82+ return nil
83+ end
84+
85+ --- Apply a single hunk to content
4486--- @param hunk table
4587--- @param content string
4688--- @return string patched_content , boolean applied_cleanly
4789local function apply_hunk (hunk , content )
48- local dmp = require (' CopilotChat.vendor.diff_match_patch' )
49- local patch = dmp .patch_make (table.concat (hunk .old_snippet , ' \n ' ), table.concat (hunk .new_snippet , ' \n ' ))
50-
51- -- First try: direct application
52- local patched , results = dmp .patch_apply (patch , content )
53- if not vim .tbl_contains (results , false ) then
54- return patched , true
55- end
56-
57- -- Fallback: direct replacement
5890 local lines = vim .split (content , ' \n ' )
59- local insert_idx = hunk .start_old or 1
60- if not hunk .start_old then
61- -- No starting point, try to find best match
62- local match_idx , best_score = nil , - 1
63- local context_lines = vim .tbl_filter (function (line )
64- return line and line ~= ' '
65- end , hunk .old_snippet )
66- local context_len = # context_lines
67- if context_len > 0 then
68- for i = 1 , # lines - context_len + 1 do
69- local score = 0
70- for j = 1 , context_len do
71- if vim .trim (lines [i + j - 1 ] or ' ' ) == vim .trim (context_lines [j ] or ' ' ) then
72- score = score + 1
73- end
74- end
75- if score > best_score then
76- best_score = score
77- match_idx = i
78- end
79- end
91+ local start_idx = hunk .start_old
92+
93+ -- If we have a start line hint, try to find best match within +/- 2 lines
94+ if start_idx and start_idx > 0 and start_idx <= # lines then
95+ local match_idx = find_best_match (lines , hunk .old_snippet , start_idx , 2 )
96+ if match_idx then
97+ start_idx = match_idx
8098 end
81- if best_score > 0 and match_idx then
82- insert_idx = match_idx
99+ else
100+ -- No valid start line, search for best match in whole content
101+ local match_idx = find_best_match (lines , hunk .old_snippet , 1 , # lines )
102+ if match_idx then
103+ start_idx = match_idx
104+ else
105+ start_idx = 1
83106 end
84107 end
85108
86- local start_idx = insert_idx
87- local end_idx = insert_idx + # hunk .old_snippet
109+ -- Replace old lines with new lines
110+ local end_idx = start_idx + # hunk .old_snippet - 1
88111 local new_lines = vim .list_slice (lines , 1 , start_idx - 1 )
89112 vim .list_extend (new_lines , hunk .new_snippet )
90113 vim .list_extend (new_lines , lines , end_idx + 1 , # lines )
91- return table.concat (new_lines , ' \n ' ), false
114+
115+ -- Check if we matched exactly at the hinted position
116+ local applied_cleanly = find_best_match (lines , hunk .old_snippet , hunk .start_old or start_idx , 0 ) == start_idx
117+ return table.concat (new_lines , ' \n ' ), applied_cleanly
92118end
93119
94120--- Apply unified diff to a table of lines and return new lines
@@ -129,7 +155,7 @@ function M.get_diff(block, lines)
129155 return block .content , content
130156 end
131157
132- local patched_lines = vim .split (block .content , ' \n ' )
158+ local patched_lines = vim .split (block .content , ' \n ' , { trimempty = true } )
133159 local start_idx = block .header .start_line
134160 local end_idx = block .header .end_line
135161 local original_lines = lines
0 commit comments