fix(tui): attach every line range when one file is mentioned twice (#7259)

* fix(tui): attach every line range when one file is mentioned twice

Mentions are deduplicated before they are attached, and a resolved file
is keyed by its path alone. `compare @notes.txt:1-1 with @notes.txt:4-4`
therefore attached lines 1-1 and dropped 4-4, while the composer showed a
"resolved on send" chip for each and the model got no hint that the
second range was missing.

Key a ranged mention by path and range. Repeating the same range, or the
whole file, still deduplicates as before.

Signed-off-by: Lstarsky0 <[email protected]>

* fix(tui): keep a range key from matching a literal file name

The range was joined to the path with `:`, which a file name can contain.
With files `notes.txt` and `notes.txt:1-1`, `@notes.txt:01-01
@notes.txt:1-1` gave both the key `notes.txt:1-1`, and the literal file
was dropped. Join them with a NUL, which no path contains.

Signed-off-by: Lstarsky0 <[email protected]>

---------

Signed-off-by: Lstarsky0 <[email protected]>
This commit is contained in:
WissssleyL authored and GitHub committed 2026-11-10 19:57:59 +08:00
1 parent 0f00f50a62
commit 3c8eb5540c
1 file changed
+30 -1
+30 -1
View File
@@ -1119,7 +1119,10 @@ fn local_context_from_file_mentions(
// Missing mentions dedup on the typed token: there is no resolved
// path to key on, and the workspace-root guess must not become one.
let dedup_key = if exists {
display_path.clone()
match range {
Some(FileRange { start, end }) => format!("{display_path}\0{start}-{end}"),
None => display_path.clone(),
}
} else {
format!("missing:{mention}")
};
@@ -1737,6 +1740,32 @@ mod tests {
assert!(!content.contains("<missing-file"), "got: {content}");
}
#[test]
fn two_ranges_of_one_file_are_both_attached() {
let tmp = TempDir::new().expect("tempdir");
std::fs::write(tmp.path().join("notes.txt"), "one\ntwo\nthree\nfour\n").expect("write");
let content = user_request_with_file_mentions(
"compare @notes.txt:1-1 with @notes.txt:4-4",
tmp.path(),
None,
);
assert!(content.contains("lines=\"1-1\">\none\n"), "got: {content}");
assert!(content.contains("lines=\"4-4\">\nfour\n"), "got: {content}");
if cfg!(unix) {
std::fs::write(tmp.path().join("notes.txt:1-1"), "literal\n").expect("write");
let content = user_request_with_file_mentions(
"@notes.txt:01-01 @notes.txt:1-1",
tmp.path(),
None,
);
assert!(content.contains("lines=\"1-1\">\none\n"), "got: {content}");
assert!(content.contains("literal\n"), "got: {content}");
}
}
/// Negative test: a truly missing path still produces `<missing-file>`
/// so the user gets an explicit signal instead of silent failure.
#[test]