const MAX_CONTENT_BYTES: usize = 256 * 1024;
const MAX_BLOCK_LINES: usize = 64;
const SNIPPET_CHARS: usize = 120;
pub(super) fn near_miss_hint(content: &str, block: &str) -> Option<String> {
if block.is_empty() || content.len() > MAX_CONTENT_BYTES {
return None;
}
if block.contains("\\\\") {
let dedoubled = block.replace("\\\\", "\\");
if block_found(content, &dedoubled) {
let sample = block
.lines()
.find(|l| l.contains("\\\\"))
.map(|l| truncate(l.trim()))
.unwrap_or_default();
return Some(format!(
"Your block over-escapes backslashes: de-doubling `\\\\` to \
`\\` makes it match the file. The file has SINGLE \
backslashes where your block doubles them (e.g. your \
`{sample}`). Re-send the same edit with single backslashes."
));
}
}
let content_lines: Vec<&str> = content.lines().collect();
let block_trim: Vec<&str> = block.lines().map(str::trim).collect();
let m = block_trim.len();
if m == 0 || m > MAX_BLOCK_LINES || content_lines.len() < m {
return None;
}
let mut best_score = 0usize;
let mut best_start = 0usize;
for i in 0..=(content_lines.len() - m) {
let score = (0..m)
.filter(|&j| content_lines[i + j].trim() == block_trim[j])
.count();
if score > best_score {
best_score = score;
best_start = i;
}
}
if best_score == 0 || best_score * 2 < m {
return None;
}
let first_line = best_start + 1; let last_line = best_start + m;
let diff = (0..m).find(|&j| content_lines[best_start + j].trim() != block_trim[j]);
match diff {
Some(j) => Some(format!(
"Closest match: lines {first_line}-{last_line} ({best_score}/{m} \
lines already match). First difference at line {}: file has \
`{}` but your block has `{}`.",
best_start + j + 1,
truncate(content_lines[best_start + j].trim()),
truncate(block_trim[j]),
)),
None => Some(format!(
"Lines {first_line}-{last_line} match your block except for \
whitespace/indentation — re-read those lines and copy them \
exactly."
)),
}
}
fn block_found(content: &str, block: &str) -> bool {
if content.contains(block) {
return true;
}
let content_lines: Vec<&str> = content.lines().collect();
let block_trim: Vec<&str> = block.lines().map(str::trim).collect();
let m = block_trim.len();
if m == 0 || content_lines.len() < m {
return false;
}
(0..=(content_lines.len() - m))
.any(|i| (0..m).all(|j| content_lines[i + j].trim() == block_trim[j]))
}
fn truncate(s: &str) -> String {
if s.chars().count() <= SNIPPET_CHARS {
return s.to_string();
}
let cut: String = s.chars().take(SNIPPET_CHARS).collect();
format!("{cut}…")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn over_escaped_backslashes_detected() {
let content = "fn pat() {\n let re = r\"^\\s*fn\\s+\\w+\";\n re\n}\n";
let block = " let re = r\"^\\\\s*fn\\\\s+\\\\w+\";\n re\n";
let hint = near_miss_hint(content, block).expect("must diagnose over-escaping");
assert!(hint.contains("over-escapes backslashes"), "got: {hint}");
assert!(hint.contains("\\\\s"), "sample line should appear: {hint}");
}
#[test]
fn dedouble_that_still_does_not_match_is_not_reported() {
let content = "alpha\nbeta\ngamma\n";
let block = "let re = r\"^\\\\d+\";\n";
let hint = near_miss_hint(content, block);
assert!(
hint.is_none() || !hint.as_ref().unwrap().contains("over-escapes"),
"got: {hint:?}"
);
}
#[test]
fn closest_window_reports_first_difference() {
let content = "fn main() {\n let a = 1;\n let b = 2;\n let c = 3;\n}\n";
let block = "fn main() {\n let a = 1;\n let b = 99;\n let c = 3;\n";
let hint = near_miss_hint(content, block).expect("must find the near window");
assert!(hint.contains("Closest match: lines 1-4"), "got: {hint}");
assert!(hint.contains("3/4"), "got: {hint}");
assert!(hint.contains("First difference at line 3"), "got: {hint}");
assert!(hint.contains("let b = 2;"), "file side missing: {hint}");
assert!(hint.contains("let b = 99;"), "block side missing: {hint}");
}
#[test]
fn whitespace_only_mismatch_reported_as_such() {
let content = "if x {\n do_it();\n}\n";
let block = "if x {\n do_it();\n}";
let hint = near_miss_hint(content, block).expect("must diagnose whitespace");
assert!(
hint.contains("except for whitespace/indentation"),
"got: {hint}"
);
}
#[test]
fn unrelated_block_yields_no_hint() {
let content = "alpha\nbeta\ngamma\n";
let block = "fn totally() {\n different();\n}\n";
assert_eq!(near_miss_hint(content, block), None);
}
#[test]
fn below_half_match_yields_no_hint() {
let content = "one\ntwo\nthree\nfour\nfive\n";
let block = "one\nX\nY\nZ\n";
assert_eq!(near_miss_hint(content, block), None);
}
#[test]
fn oversized_content_is_skipped() {
let content = "x\n".repeat(200_000); let block = "x\ny\n";
assert_eq!(near_miss_hint(&content, block), None);
}
#[test]
fn empty_block_yields_no_hint() {
assert_eq!(near_miss_hint("anything\n", ""), None);
}
#[test]
fn long_lines_are_truncated_in_the_hint() {
let long_a = format!("let value = \"{}A\";", "a".repeat(200));
let long_b = format!("let value = \"{}B\";", "a".repeat(200));
let content = format!("start\n{long_a}\nend\n");
let block = format!("start\n{long_b}\nend\n");
let hint = near_miss_hint(&content, &block).expect("must diagnose");
assert!(hint.contains('…'), "snippets must be truncated: {hint}");
}
}