优化编辑工具调用里多hunk场景的行号解析
This commit is contained in:
@@ -535,9 +535,11 @@ fn collect_hunk_lines<'a>(lines: &'a [&'a str], start: usize) -> Vec<&'a str> {
|
|||||||
/// Find where a hunk's context lines match in the file, returning (start_line, old_count, new_count).
|
/// Find where a hunk's context lines match in the file, returning (start_line, old_count, new_count).
|
||||||
/// `start_line` is 1-based.
|
/// `start_line` is 1-based.
|
||||||
///
|
///
|
||||||
/// The file on disk may be in either pre-patch or post-patch state, so we try
|
/// The file on disk may be in either pre-patch or post-patch state, and may
|
||||||
/// matching with "new file" lines (context + added) first, then fall back to
|
/// have been further modified. We try three strategies in order:
|
||||||
/// "old file" lines (context + deleted).
|
/// 1. Contiguous match of context+added lines (post-patch file, no further edits)
|
||||||
|
/// 2. Contiguous match of context+deleted lines (pre-patch file)
|
||||||
|
/// 3. Subsequence match of context-only lines (file has been further modified)
|
||||||
fn find_hunk_position(
|
fn find_hunk_position(
|
||||||
file_lines: &[String],
|
file_lines: &[String],
|
||||||
hunk_lines: &[&str],
|
hunk_lines: &[&str],
|
||||||
@@ -555,44 +557,41 @@ fn find_hunk_position(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build "new file" view: context + added lines (what the file looks like after patch)
|
// Strategy 1: contiguous match of context+added (post-patch)
|
||||||
let new_view: Vec<&str> = hunk_lines
|
let new_view: Vec<&str> = hunk_lines
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|l| l.starts_with(' ') || l.starts_with('+'))
|
.filter(|l| l.starts_with(' ') || l.starts_with('+'))
|
||||||
.map(|l| &l[1..])
|
.map(|l| &l[1..])
|
||||||
.collect();
|
.collect();
|
||||||
|
if let Some(pos) = find_contiguous(file_lines, &new_view) {
|
||||||
|
return Some((pos + 1, new_count, new_count));
|
||||||
|
}
|
||||||
|
|
||||||
// Build "old file" view: context + deleted lines (what the file looked like before patch)
|
// Strategy 2: contiguous match of context+deleted (pre-patch)
|
||||||
let old_view: Vec<&str> = hunk_lines
|
let old_view: Vec<&str> = hunk_lines
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|l| l.starts_with(' ') || l.starts_with('-'))
|
.filter(|l| l.starts_with(' ') || l.starts_with('-'))
|
||||||
.map(|l| &l[1..])
|
.map(|l| &l[1..])
|
||||||
.collect();
|
.collect();
|
||||||
|
if let Some(pos) = find_contiguous(file_lines, &old_view) {
|
||||||
|
return Some((pos + 1, old_count, new_count));
|
||||||
|
}
|
||||||
|
|
||||||
// Try matching the new view first (file is post-patch), then old view (file is pre-patch)
|
// Strategy 3: subsequence match of context-only lines (file further modified)
|
||||||
for (view, is_new) in [(&new_view, true), (&old_view, false)] {
|
let ctx_only: Vec<&str> = hunk_lines
|
||||||
if view.is_empty() {
|
.iter()
|
||||||
continue;
|
.filter(|l| l.starts_with(' '))
|
||||||
}
|
.map(|l| &l[1..])
|
||||||
if let Some(pos) = find_view_in_file(file_lines, view) {
|
.collect();
|
||||||
// `pos` is 0-based file index where the view starts.
|
if let Some(pos) = find_subsequence(file_lines, &ctx_only) {
|
||||||
// For the hunk header we want the OLD file line number.
|
return Some((pos + 1, old_count, new_count));
|
||||||
// If matched on new view: adjust by the lines added before this hunk
|
|
||||||
// (old_start = pos - added_lines_before ... but we don't know that)
|
|
||||||
// Simpler: old_start = pos for new view match (close enough,
|
|
||||||
// and matches what the user sees in the file).
|
|
||||||
// If matched on old view: pos is already the old start.
|
|
||||||
let start_line = pos + 1; // 1-based
|
|
||||||
let reported_count = if is_new { new_count } else { old_count };
|
|
||||||
return Some((start_line, reported_count, reported_count));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the position of `view` lines in `file_lines`. Returns 0-based start index.
|
/// Find contiguous `view` lines in `file_lines`. Returns 0-based start index.
|
||||||
fn find_view_in_file(file_lines: &[String], view: &[&str]) -> Option<usize> {
|
fn find_contiguous(file_lines: &[String], view: &[&str]) -> Option<usize> {
|
||||||
if view.is_empty() || view.len() > file_lines.len() {
|
if view.is_empty() || view.len() > file_lines.len() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -601,17 +600,47 @@ fn find_view_in_file(file_lines: &[String], view: &[&str]) -> Option<usize> {
|
|||||||
if file_lines[i].as_str() != first {
|
if file_lines[i].as_str() != first {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let all_match = view
|
if view.iter().enumerate().all(|(j, v)| file_lines[i + j].as_str() == *v) {
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.all(|(j, v)| file_lines[i + j].as_str() == *v);
|
|
||||||
if all_match {
|
|
||||||
return Some(i);
|
return Some(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find `needles` as an ordered subsequence in `file_lines` within a small window.
|
||||||
|
/// Returns 0-based index of the first needle's position.
|
||||||
|
fn find_subsequence(file_lines: &[String], needles: &[&str]) -> Option<usize> {
|
||||||
|
if needles.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let first = needles[0];
|
||||||
|
for start in 0..file_lines.len() {
|
||||||
|
if file_lines[start].as_str() != first {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut cursor = start + 1;
|
||||||
|
let mut all_found = true;
|
||||||
|
for &needle in &needles[1..] {
|
||||||
|
// Allow up to 10 lines gap between consecutive context lines
|
||||||
|
let limit = std::cmp::min(cursor + 10, file_lines.len());
|
||||||
|
match file_lines[cursor..limit]
|
||||||
|
.iter()
|
||||||
|
.position(|fl| fl.as_str() == needle)
|
||||||
|
{
|
||||||
|
Some(offset) => cursor = cursor + offset + 1,
|
||||||
|
None => {
|
||||||
|
all_found = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if all_found {
|
||||||
|
return Some(start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract the last path component as the folder name.
|
/// Extract the last path component as the folder name.
|
||||||
pub fn folder_name_from_path(path: &str) -> String {
|
pub fn folder_name_from_path(path: &str) -> String {
|
||||||
path.rsplit(['/', '\\']).next().unwrap_or(path).to_string()
|
path.rsplit(['/', '\\']).next().unwrap_or(path).to_string()
|
||||||
|
|||||||
Reference in New Issue
Block a user