Apply hunks to file content. Hunks are applied bottom-to-top to preserve line numbers.
(content: &str, hunks: &[Hunk])
| 122 | |
| 123 | /// Apply hunks to file content. Hunks are applied bottom-to-top to preserve line numbers. |
| 124 | fn apply_hunks(content: &str, hunks: &[Hunk]) -> Result<String, String> { |
| 125 | let mut file_lines: Vec<String> = content.lines().map(|s| s.to_string()).collect(); |
| 126 | |
| 127 | // Sort hunks by old_start descending (apply bottom-to-top) |
| 128 | let mut sorted_hunks: Vec<&Hunk> = hunks.iter().collect(); |
| 129 | sorted_hunks.sort_by_key(|h| std::cmp::Reverse(h.old_start)); |
| 130 | |
| 131 | for hunk in sorted_hunks { |
| 132 | let start_idx = hunk.old_start.saturating_sub(1); // Convert 1-indexed to 0-indexed |
| 133 | |
| 134 | // Verify context lines match (basic verification) |
| 135 | for (offset, ctx_line) in &hunk.context { |
| 136 | let line_idx = start_idx + offset; |
| 137 | if line_idx < file_lines.len() && file_lines[line_idx].trim() != ctx_line.trim() { |
| 138 | tracing::warn!( |
| 139 | "Context mismatch at line {}: expected {:?}, found {:?}", |
| 140 | line_idx + 1, |
| 141 | ctx_line, |
| 142 | file_lines[line_idx] |
| 143 | ); |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | // Find and remove the old lines, then insert new lines |
| 148 | let mut lines_to_remove: Vec<usize> = Vec::new(); |
| 149 | let mut search_idx = start_idx; |
| 150 | |
| 151 | for removal in &hunk.removals { |
| 152 | // Find the line to remove starting from search_idx |
| 153 | let found_idx = file_lines[search_idx..] |
| 154 | .iter() |
| 155 | .position(|line| line.trim() == removal.trim()) |
| 156 | .map(|pos| search_idx + pos); |
| 157 | match found_idx { |
| 158 | Some(idx) => { |
| 159 | lines_to_remove.push(idx); |
| 160 | search_idx = idx + 1; |
| 161 | } |
| 162 | None => { |
| 163 | return Err(format!( |
| 164 | "Could not find line to remove: {:?} near line {}", |
| 165 | removal, |
| 166 | start_idx + 1 |
| 167 | )); |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | // Remove lines bottom-to-top |
| 173 | lines_to_remove.sort_unstable(); |
| 174 | lines_to_remove.reverse(); |
| 175 | for idx in &lines_to_remove { |
| 176 | file_lines.remove(*idx); |
| 177 | } |
| 178 | |
| 179 | // Insert additions at the position of the first removal (or at start_idx) |
| 180 | let insert_at = lines_to_remove |
| 181 | .last() |