aboutsummaryrefslogtreecommitdiff
path: root/src/raw_input.rs
diff options
context:
space:
mode:
authorkangal-bot <kangal@herdr.dev>2026-05-26 19:59:15 +0300
committerGitHub <noreply@github.com>2026-05-26 19:59:15 +0300
commit31e973502af205666cb4a48e3f9e10328f65cd4e (patch)
tree67ddda76c3d6ac551e9ae17ecaa4fce675f94262 /src/raw_input.rs
parent5210b08e7db000d619b365fbcb8ec08104b0fcaa (diff)
fix: buffer split host color replies (#310)
refs #306 Co-authored-by: kangal-bot <kangal-bot@users.noreply.github.com>
Diffstat (limited to 'src/raw_input.rs')
-rw-r--r--src/raw_input.rs93
1 files changed, 93 insertions, 0 deletions
diff --git a/src/raw_input.rs b/src/raw_input.rs
index fb9a1a3d..6c3878bf 100644
--- a/src/raw_input.rs
+++ b/src/raw_input.rs
@@ -216,6 +216,14 @@ pub(crate) fn flush_incomplete_input_bytes(buffer: &mut Vec<u8>) -> Option<Vec<u
return None;
}
+ if starts_with_incomplete_default_color_response(buffer) {
+ tracing::trace!(
+ len = buffer.len(),
+ "waiting for host color response terminator"
+ );
+ return None;
+ }
+
if buffer.as_slice() == [ESC] {
tracing::warn!(
bytes = ?buffer,
@@ -334,6 +342,11 @@ fn extract_one_event(buffer: &[u8]) -> Option<(RawInputEvent, usize)> {
Some((RawInputEvent::Key(key), consumed))
}
+fn starts_with_incomplete_default_color_response(buffer: &[u8]) -> bool {
+ find_osc_terminator(buffer).is_none()
+ && matches!(buffer.get(..5), Some(b"\x1b]10;" | b"\x1b]11;"))
+}
+
fn first_complete_utf8_char_len(buffer: &[u8]) -> Option<usize> {
let width = utf8_char_width(*buffer.first()?)?;
@@ -1285,4 +1298,84 @@ mod tests {
assert_eq!(ranges[0].len, input.len());
assert!(matches!(&ranges[0].event, RawInputEvent::Mouse(_)));
}
+
+ #[test]
+ fn parses_ghostty_default_background_response() {
+ let events = parse_raw_input_bytes_sync(b"\x1b]11;rgb:2828/2a2a/3636\x07");
+
+ assert_eq!(events.len(), 1);
+ assert!(matches!(
+ events[0],
+ RawInputEvent::HostDefaultColor {
+ kind: DefaultColorKind::Background,
+ color: RgbColor {
+ r: 0x28,
+ g: 0x2a,
+ b: 0x36
+ }
+ }
+ ));
+ }
+
+ #[test]
+ fn drain_complete_input_bytes_keeps_split_default_background_response_buffered() {
+ let mut buffer = b"\x1b]11;rgb:2828".to_vec();
+
+ let chunks = drain_complete_input_bytes(&mut buffer);
+
+ assert!(chunks.is_empty());
+ assert_eq!(buffer, b"\x1b]11;rgb:2828");
+ }
+
+ #[test]
+ fn flush_incomplete_input_bytes_keeps_split_default_background_response_buffered() {
+ let mut buffer = b"\x1b]11;rgb:2828".to_vec();
+
+ let flushed = flush_incomplete_input_bytes(&mut buffer);
+
+ assert!(flushed.is_none());
+ assert_eq!(buffer, b"\x1b]11;rgb:2828");
+ }
+
+ #[test]
+ fn flush_incomplete_input_bytes_keeps_default_background_response_split_after_command() {
+ let mut buffer = b"\x1b]11;".to_vec();
+
+ let flushed = flush_incomplete_input_bytes(&mut buffer);
+
+ assert!(flushed.is_none());
+ assert_eq!(buffer, b"\x1b]11;");
+ }
+
+ #[test]
+ fn flush_incomplete_input_bytes_keeps_default_background_response_split_inside_st() {
+ let mut buffer = b"\x1b]11;rgb:2828/2a2a/3636\x1b".to_vec();
+
+ let flushed = flush_incomplete_input_bytes(&mut buffer);
+
+ assert!(flushed.is_none());
+ assert_eq!(buffer, b"\x1b]11;rgb:2828/2a2a/3636\x1b");
+ }
+
+ #[test]
+ fn non_osc_default_color_text_remains_key_input() {
+ let events = parse_raw_input_bytes_sync(b"11;rgb:2828/2a2a/3636\x07");
+
+ assert_eq!(events.len(), 22);
+ assert_raw_key(
+ events.into_iter().next().unwrap(),
+ KeyCode::Char('1'),
+ KeyModifiers::empty(),
+ );
+ }
+
+ #[test]
+ fn flush_incomplete_input_bytes_does_not_hold_non_osc_default_color_text() {
+ let mut buffer = b"11;rgb:2828".to_vec();
+
+ let flushed = flush_incomplete_input_bytes(&mut buffer);
+
+ assert_eq!(flushed, None);
+ assert!(buffer.is_empty());
+ }
}