T59: the newest line typed is the one of the highest number, not the lowest on the screen, so a prompt drawn at the top over older lines takes its clicks; a press goes no lower than that line's last row or the cursor's

This commit is contained in:
ZacharyZhang-NY committed 2026-10-19 16:22:50 -04:00
1 parent 34be6e3521
commit 28d761572d
4 files changed
+30 -14

No files matched your search

+10 -8
View File
@@ -92,7 +92,7 @@ pub fn arrows<T>(term: &Term<T>, click: Point) -> Option<i32> {
})
}
/// The left press, SGR, a shell that takes clicks hears for `click`, its row the screen's or counted from its prompt's first, as kitty and Ghostty send it; none on a prompt or before the input.
/// The left press, SGR, a shell that takes clicks hears for `click`, its row the screen's or counted from its prompt's first, as kitty and Ghostty send it; none on a prompt, before the input or below it.
pub fn press<T>(term: &Term<T>, click: Point, relative: bool) -> Option<Vec<u8>> {
if term.mode().contains(TermMode::ALT_SCREEN) {
return None;
@@ -107,16 +107,18 @@ pub fn press<T>(term: &Term<T>, click: Point, relative: bool) -> Option<Vec<u8>>
let end = (back.clone())
.filter(|&at| at < cursor)
.find(|&at| prompted(cell(at)));
// The newest line typed, its first cell; a shell drawing its prompts again keeps its number.
let mut typed = back.filter_map(|at| match part(cell(at)) {
// The newest line typed, by its number, which only grows, its first and last cells; a shell drawing its prompts again keeps it.
let typed = back.filter_map(|at| match part(cell(at)) {
Some(Part::Input(line)) => Some((at, line)),
_ => None,
});
let input = typed.next().map(|(last, line)| {
let first =
(typed.take_while(|&(_, other)| other == line).last()).map_or(last, |(first, _)| first);
(first, line)
let newest = typed.fold(None, |newest, (at, line)| match newest {
Some((_, _, kept)) if kept > line => newest,
Some((_, last, kept)) if kept == line => Some((at, last, line)),
_ => Some((at, at, line)),
});
let input = newest.map(|(first, _, line)| (first, line));
let bottom = newest.map_or(cursor.0, |(_, last, _)| last.0.max(cursor.0));
// An older line typed between a prompt and the input leaves that prompt behind.
let older = |end: (i32, usize), (input, line): ((i32, usize), u32)| {
(end.0..=input.0)
@@ -139,7 +141,7 @@ pub fn press<T>(term: &Term<T>, click: Point, relative: bool) -> Option<Vec<u8>>
(None, Some((input, _))) => (at < input, input.0),
(None, None) => return None,
};
if before || prompted(cell(at)) {
if before || at.0 > bottom || prompted(cell(at)) {
return None;
}
let row = if relative { at.0 - first } else { at.0 } + 1;
+18 -4
View File
@@ -107,11 +107,10 @@ fn a_shell_that_takes_clicks_hears_the_press_where_it_landed() {
sent(&typed, click(1, 7), true).as_deref(),
Some("\x1b[<0;8;1M")
);
let below = sent(&typed, click(2, 3), true);
assert_eq!(
below.as_deref(),
Some("\x1b[<0;4;2M"),
"a row below goes too"
sent(&typed, click(2, 3), true),
None,
"a row below the input"
);
assert_eq!(sent(&typed, click(0, 1), false), None, "before the prompt");
assert_eq!(sent(&typed, click(1, 0), false), None, "on the prompt");
@@ -253,3 +252,18 @@ fn a_prompt_drawn_again_after_the_typed_text_keeps_its_line() {
"from the input's own row"
);
}
#[test]
fn the_newest_line_typed_is_known_by_its_number_not_its_place() {
let marked = "\x1b]133;A;click_events=1\x07$ \x1b]133;B\x07";
let ran = "\x1b]133;C\x07\x1b]133;D;0\x07";
let home = format!("\r\n\r\n\r\n{marked}oldlower{ran}\x1b[H{marked}abcdef");
let above = term(20, &home);
let at = sent(&above, click(0, 5), false);
assert_eq!(
at.as_deref(),
Some("\x1b[<0;6;1M"),
"the prompt at the top, old input below"
);
assert_eq!(sent(&above, click(3, 4), false), None, "the old line below");
}