Preserve ambiguous terminal input
ci / test (push) Canceled after 0s
ci / fuzz-smoke (push) Canceled after 0s
ci / macos-client (macos-aarch64, macos-14) (push) Canceled after 0s
ci / macos-client (macos-x86_64, macos-13) (push) Canceled after 0s
ci / windows-client (push) Canceled after 0s
ci / package-release (linux-x86_64, ubuntu-latest, , , ) (push) Canceled after 0s
ci / package-release (macos-aarch64, macos-14, , , ) (push) Canceled after 0s
ci / package-release (macos-x86_64, macos-13, , , ) (push) Canceled after 0s
ci / package-release (windows-aarch64, windows-latest, aarch64, windows, aarch64-pc-windows-msvc) (push) Canceled after 0s
ci / package-release (windows-x86_64, windows-latest, , , ) (push) Canceled after 0s
ci / remote-bench (push) Canceled after 0s
ci / publish-gitea-release (push) Canceled after 0s

This commit is contained in:
DuProcess
2026-07-17 18:31:30 -04:00
parent 367af4de82
commit 25c3358842
+76 -15
View File
@@ -8436,7 +8436,7 @@ fn strip_unowned_terminal_reports(bytes: Vec<u8>, mouse_tracking: bool) -> (Vec<
return (bytes, false);
}
let before = bytes.len();
let stripped = strip_stale_mouse_reports(&bytes);
let stripped = strip_complete_terminal_mouse_reports(&bytes);
let changed = stripped.len() != before;
(stripped, changed)
}
@@ -8572,6 +8572,42 @@ fn strip_stale_mouse_reports(bytes: &[u8]) -> Vec<u8> {
out
}
fn strip_complete_terminal_mouse_reports(bytes: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(bytes.len());
let mut offset = 0;
while offset < bytes.len() {
if let Some(len) = prefixed_mouse_report_len(bytes, offset) {
offset += len;
} else {
out.push(bytes[offset]);
offset += 1;
}
}
out
}
fn prefixed_mouse_report_len(bytes: &[u8], offset: usize) -> Option<usize> {
match bytes.get(offset).copied()? {
0x1b if bytes.get(offset + 1) == Some(&b'[') => match bytes.get(offset + 2).copied() {
Some(b'M') if offset + 6 <= bytes.len() => Some(6),
Some(b'<') => mouse_report_params_len(bytes, offset + 3, 2).map(|len| len + 3),
Some(byte) if byte.is_ascii_digit() => {
mouse_report_params_len(bytes, offset + 2, 2).map(|len| len + 2)
}
_ => None,
},
0x9b => match bytes.get(offset + 1).copied() {
Some(b'M') if offset + 5 <= bytes.len() => Some(5),
Some(b'<') => mouse_report_params_len(bytes, offset + 2, 2).map(|len| len + 2),
Some(byte) if byte.is_ascii_digit() => {
mouse_report_params_len(bytes, offset + 1, 2).map(|len| len + 1)
}
_ => None,
},
_ => None,
}
}
fn stale_mouse_report_len(bytes: &[u8], offset: usize) -> Option<usize> {
match bytes.get(offset).copied()? {
0x1b if bytes.get(offset + 1) == Some(&b'[') => match bytes.get(offset + 2).copied() {
@@ -8643,25 +8679,27 @@ fn stale_mouse_report_maybe_incomplete(bytes: &[u8], offset: usize) -> bool {
return false;
};
match rest {
[0x1b] | [0x1b, b'['] | [0x1b, b'[', b'<'] => true,
[0x1b, b'[', b'<'] => true,
[0x1b, b'[', b'M', ..] if rest.len() < 6 => true,
[0x1b, b'[', b'<', params @ ..] | [0x1b, b'[', params @ ..]
if params_are_mouse_prefix(params, 2) =>
{
true
}
[0x9b] | [0x9b, b'<'] => true,
[0x1b, b'[', b'<', params @ ..] if params_are_terminal_report_prefix(params) => true,
[0x1b, b'[', params @ ..] if params_are_mouse_prefix(params, 2) => true,
[0x9b, b'<'] => true,
[0x9b, b'M', ..] if rest.len() < 5 => true,
[0x9b, b'<', params @ ..] | [0x9b, params @ ..] if params_are_mouse_prefix(params, 2) => {
true
}
params if params_are_mouse_prefix(params, 2) => true,
[0x9b, b'<', params @ ..] if params_are_terminal_report_prefix(params) => true,
[0x9b, params @ ..] if params_are_mouse_prefix(params, 2) => true,
_ => false,
}
}
fn params_are_terminal_report_prefix(params: &[u8]) -> bool {
!params.is_empty()
&& params
.iter()
.all(|byte| byte.is_ascii_digit() || *byte == b';')
}
fn params_are_mouse_prefix(params: &[u8], min_semicolons: usize) -> bool {
let mut semicolons = 0usize;
let mut semicolons = 0;
let mut saw_digit = false;
for byte in params {
match byte {
@@ -13969,6 +14007,22 @@ mod tests {
assert_eq!(preserved, b"\x1b[<35;10;1M");
}
#[test]
fn live_unowned_filter_preserves_ambiguous_user_input() {
for input in [
b"\x1b".as_slice(),
b"\x1b[".as_slice(),
b"\x1b[A".as_slice(),
b"\x1b[200~paste\x1b[201~".as_slice(),
b"35;152;1M".as_slice(),
b"printf 35;152;1M\r".as_slice(),
] {
let (preserved, changed) = strip_unowned_terminal_reports(input.to_vec(), false);
assert!(!changed, "live filter changed {input:?}");
assert_eq!(preserved, input);
}
}
#[test]
fn transient_udp_send_errors_are_not_terminal_fatal() {
#[cfg(unix)]
@@ -14059,12 +14113,19 @@ mod tests {
}
#[test]
fn incomplete_three_field_mouse_prefix_is_suppressed() {
assert_eq!(strip_stale_mouse_reports(b"35;152;"), b"");
fn only_explicit_incomplete_mouse_prefixes_are_suppressed() {
assert_eq!(strip_stale_mouse_reports(b"35;152;"), b"35;152;");
assert_eq!(strip_stale_mouse_reports(b"\x1b[<35;152;"), b"");
assert_eq!(strip_stale_mouse_reports(b"\x9b35;152;"), b"");
}
#[test]
fn lone_escape_survives_stale_reconnect_filter() {
assert_eq!(strip_stale_mouse_reports(b"\x1b"), b"\x1b");
assert_eq!(strip_stale_mouse_reports(b"\x1b["), b"\x1b[");
assert_eq!(strip_stale_mouse_reports(b"\x1b[A"), b"\x1b[A");
}
#[test]
fn stale_focus_reports_are_stripped_from_pending_input() {
assert_eq!(strip_stale_mouse_reports(b"\x1b[Ihello\x1b[O"), b"hello");