use super::*;
fn parse_chunks(chunks: &[&[u8]]) -> Vec<Segment> {
let mut parser = Segmenter::default();
let mut events = Vec::new();
for chunk in chunks {
events.extend(parser.push(chunk));
}
events
}
fn b(s: &[u8]) -> Segment {
Segment::Bytes(s.to_vec())
}
fn paste(s: &[u8]) -> Segment {
Segment::Paste(s.to_vec())
}
#[test]
fn passes_through_plain_text_chunks() {
assert_eq!(
parse_chunks(&[b"hello", b" ", b"world"]),
vec![b(b"hello"), b(b" "), b(b"world")]
);
}
#[test]
fn keeps_plain_text_and_control_sequences_separate() {
assert_eq!(
parse_chunks(&[b"a\x1b[Ab"]),
vec![b(b"a"), b(b"\x1b[A"), b(b"b")]
);
}
#[test]
fn parses_multiple_standard_csi_keys_in_one_chunk() {
assert_eq!(
parse_chunks(&[b"\x1b[A\x1b[B\x1b[C\x1b[D"]),
vec![b(b"\x1b[A"), b(b"\x1b[B"), b(b"\x1b[C"), b(b"\x1b[D")]
);
}
#[test]
fn parses_csi_sequences_with_parameters() {
assert_eq!(
parse_chunks(&[b"\x1b[1;5A\x1b[5~\x1b[6~"]),
vec![b(b"\x1b[1;5A"), b(b"\x1b[5~"), b(b"\x1b[6~")]
);
}
#[test]
fn parses_kitty_protocol_sequence_as_one_key_event() {
assert_eq!(parse_chunks(&[b"\x1b[97;5u"]), vec![b(b"\x1b[97;5u")]);
}
#[test]
fn parses_ss3_sequences_as_one_key_event() {
assert_eq!(
parse_chunks(&[b"\x1bOA\x1bOB\x1bOC\x1bOD"]),
vec![b(b"\x1bOA"), b(b"\x1bOB"), b(b"\x1bOC"), b(b"\x1bOD")]
);
}
#[test]
fn does_not_consume_a_following_escape_as_ss3_final_byte() {
assert_eq!(
parse_chunks(&[b"\x1bO\x1b[A"]),
vec![b(b"\x1bO"), b(b"\x1b[A")]
);
}
#[test]
fn parses_meta_csi_sequence_with_double_escape() {
assert_eq!(parse_chunks(&[b"\x1b\x1b[A"]), vec![b(b"\x1b\x1b[A")]);
}
#[test]
fn parses_escaped_printable_code_points() {
assert_eq!(
parse_chunks(&[b"\x1bx\x1b1"]),
vec![b(b"\x1bx"), b(b"\x1b1")]
);
}
#[test]
fn parses_escaped_supplementary_code_points() {
assert_eq!(
parse_chunks(&["\u{1b}😀".as_bytes()]),
vec![b("\u{1b}😀".as_bytes())]
);
}
#[test]
fn preserves_legacy_esc_bracket_sequences_in_a_mixed_chunk() {
assert_eq!(
parse_chunks(&[b"\x1b[[A\x1b[[5~"]),
vec![b(b"\x1b[[A"), b(b"\x1b[[5~")]
);
}
#[test]
fn preserves_legacy_esc_bracket_sequences_across_chunks() {
assert_eq!(
parse_chunks(&[b"\x1b[[", b"A\x1b[[5~"]),
vec![b(b"\x1b[[A"), b(b"\x1b[[5~")]
);
}
#[test]
fn parses_legacy_and_standard_csi_sequences_mixed_together() {
assert_eq!(
parse_chunks(&[b"\x1b[[A\x1b[B\x1b[[6~\x1b[1;5D"]),
vec![b(b"\x1b[[A"), b(b"\x1b[B"), b(b"\x1b[[6~"), b(b"\x1b[1;5D")]
);
}
#[test]
fn holds_incomplete_csi_sequence_until_final_byte_arrives() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b["), vec![]);
assert!(parser.has_pending_escape());
assert_eq!(parser.push(b"1;5"), vec![]);
assert_eq!(parser.push(b"A"), vec![b(b"\x1b[1;5A")]);
}
#[test]
fn holds_incomplete_legacy_esc_bracket_sequence_until_final_byte_arrives() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b[["), vec![]);
assert_eq!(parser.push(b"5"), vec![]);
assert_eq!(parser.push(b"~"), vec![b(b"\x1b[[5~")]);
}
#[test]
fn holds_incomplete_ss3_sequence_until_final_byte_arrives() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1bO"), vec![]);
assert_eq!(parser.push(b"A"), vec![b(b"\x1bOA")]);
}
#[test]
fn holds_incomplete_double_escape_csi_sequence_until_final_byte_arrives() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b\x1b["), vec![]);
assert_eq!(parser.push(b"A"), vec![b(b"\x1b\x1b[A")]);
}
#[test]
fn keeps_pending_plain_escape_and_can_flush_it() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b"), vec![]);
assert!(parser.has_pending_escape());
assert_eq!(parser.flush_pending_escape(), Some(b"\x1b".to_vec()));
assert!(!parser.has_pending_escape());
}
#[test]
fn flushes_pending_csi_prefix_as_literal_input() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b["), vec![]);
assert!(parser.has_pending_escape());
assert_eq!(parser.flush_pending_escape(), Some(b"\x1b[".to_vec()));
assert!(!parser.has_pending_escape());
assert_eq!(parser.push(b"A"), vec![b(b"A")]);
}
#[test]
fn reset_clears_pending_input_state() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b["), vec![]);
parser.reset();
assert_eq!(parser.push(b"A"), vec![b(b"A")]);
}
#[test]
fn treats_invalid_csi_continuation_as_escaped_code_point_plus_plain_text() {
assert_eq!(parse_chunks(&[b"\x1b[\n"]), vec![b(b"\x1b["), b(b"\n")]);
}
#[test]
fn parses_mixed_text_and_many_key_events_in_one_read() {
assert_eq!(
parse_chunks(&[b"start\x1b[A mid \x1bOH end\x1b[[5~"]),
vec![
b(b"start"),
b(b"\x1b[A"),
b(b" mid "),
b(b"\x1bOH"),
b(b" end"),
b(b"\x1b[[5~"),
]
);
}
#[test]
fn flushes_pending_ss3_prefix_as_literal_input() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1bO"), vec![]);
assert!(parser.has_pending_escape());
assert_eq!(parser.flush_pending_escape(), Some(b"\x1bO".to_vec()));
assert_eq!(parser.push(b"x"), vec![b(b"x")]);
}
#[test]
fn flushes_pending_legacy_csi_prefix_as_literal_input() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b[["), vec![]);
assert!(parser.has_pending_escape());
assert_eq!(parser.flush_pending_escape(), Some(b"\x1b[[".to_vec()));
assert_eq!(parser.push(b"x"), vec![b(b"x")]);
}
#[test]
fn parses_meta_ss3_sequence_with_double_escape() {
assert_eq!(parse_chunks(&[b"\x1b\x1bOA"]), vec![b(b"\x1b\x1bOA")]);
}
#[test]
fn holds_incomplete_double_escape_ss3_sequence_until_final_byte_arrives() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b\x1bO"), vec![]);
assert!(parser.has_pending_escape());
assert_eq!(parser.push(b"A"), vec![b(b"\x1b\x1bOA")]);
}
#[test]
fn emits_double_escape_as_single_event_for_non_control_character() {
assert_eq!(parse_chunks(&[b"\x1b\x1bx"]), vec![b(b"\x1b\x1b"), b(b"x")]);
}
#[test]
fn empty_chunk_produces_no_events() {
assert_eq!(parse_chunks(&[b""]), vec![]);
}
#[test]
fn empty_chunk_does_not_disturb_pending_state() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b["), vec![]);
assert_eq!(parser.push(b""), vec![]);
assert!(parser.has_pending_escape());
assert_eq!(parser.push(b"A"), vec![b(b"\x1b[A")]);
}
#[test]
fn plain_text_followed_by_incomplete_escape_holds_escape_as_pending() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"hello\x1b"), vec![b(b"hello")]);
assert!(parser.has_pending_escape());
assert_eq!(parser.flush_pending_escape(), Some(b"\x1b".to_vec()));
}
#[test]
fn splits_batched_0x7f_backspace_characters_into_individual_events() {
assert_eq!(
parse_chunks(&[b"\x7f\x7f\x7f"]),
vec![b(b"\x7f"), b(b"\x7f"), b(b"\x7f")]
);
}
#[test]
fn splits_batched_backspace_characters_into_individual_events() {
assert_eq!(
parse_chunks(&[b"\x08\x08\x08"]),
vec![b(b"\x08"), b(b"\x08"), b(b"\x08")]
);
}
#[test]
fn splits_mixed_0x7f_and_0x08_backspace_characters() {
assert_eq!(
parse_chunks(&[b"\x7f\x08\x7f"]),
vec![b(b"\x7f"), b(b"\x08"), b(b"\x7f")]
);
}
#[test]
fn splits_mixed_printable_text_and_0x7f_backspace_characters() {
assert_eq!(
parse_chunks(&[b"abc\x7f\x7f\x7f"]),
vec![b(b"abc"), b(b"\x7f"), b(b"\x7f"), b(b"\x7f")]
);
}
#[test]
fn single_0x7f_backspace_character_is_preserved_as_individual_event() {
assert_eq!(parse_chunks(&[b"\x7f"]), vec![b(b"\x7f")]);
}
#[test]
fn single_backspace_character_is_preserved_as_individual_event() {
assert_eq!(parse_chunks(&[b"\x08"]), vec![b(b"\x08")]);
}
#[test]
fn splits_trailing_0x7f_backspace_from_text() {
assert_eq!(parse_chunks(&[b"abc\x7f"]), vec![b(b"abc"), b(b"\x7f")]);
}
#[test]
fn splits_0x7f_backspace_characters_before_escape_sequences() {
assert_eq!(
parse_chunks(&[b"\x7f\x7f\x1b[A"]),
vec![b(b"\x7f"), b(b"\x7f"), b(b"\x1b[A")]
);
}
#[test]
fn splits_0x7f_backspace_characters_after_escape_sequences() {
assert_eq!(
parse_chunks(&[b"\x1b[A\x7f\x7f"]),
vec![b(b"\x1b[A"), b(b"\x7f"), b(b"\x7f")]
);
}
#[test]
fn splits_0x7f_backspace_characters_between_escape_sequences() {
assert_eq!(
parse_chunks(&[b"\x1b[A\x7f\x1b[B"]),
vec![b(b"\x1b[A"), b(b"\x7f"), b(b"\x1b[B")]
);
}
#[test]
fn splits_backspace_characters_around_escape_sequences() {
assert_eq!(
parse_chunks(&[b"\x08\x1b[A\x08"]),
vec![b(b"\x08"), b(b"\x1b[A"), b(b"\x08")]
);
}
#[test]
fn splits_interleaved_text_and_0x7f_backspace_characters() {
assert_eq!(
parse_chunks(&[b"ab\x7fcd"]),
vec![b(b"ab"), b(b"\x7f"), b(b"cd")]
);
}
#[test]
fn does_not_split_pasted_carriage_return_from_text() {
assert_eq!(parse_chunks(&[b"\rtest"]), vec![b(b"\rtest")]);
}
#[test]
fn does_not_split_pasted_tab_from_text() {
assert_eq!(parse_chunks(&[b"\ttest"]), vec![b(b"\ttest")]);
}
#[test]
fn assembles_csi_sequence_from_single_byte_chunks() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b"), vec![]);
assert_eq!(parser.push(b"["), vec![]);
assert_eq!(parser.push(b"1"), vec![]);
assert_eq!(parser.push(b";"), vec![]);
assert_eq!(parser.push(b"5"), vec![]);
assert_eq!(parser.push(b"A"), vec![b(b"\x1b[1;5A")]);
}
#[test]
fn emits_paste_event_for_bracketed_paste_sequence() {
assert_eq!(
parse_chunks(&[b"\x1b[200~hello world\x1b[201~"]),
vec![paste(b"hello world")]
);
}
#[test]
fn emits_paste_event_for_multiline_bracketed_paste() {
assert_eq!(
parse_chunks(&[b"\x1b[200~line1\nline2\x1b[201~"]),
vec![paste(b"line1\nline2")]
);
}
#[test]
fn paste_content_with_escape_sequences_is_delivered_verbatim() {
assert_eq!(
parse_chunks(&[b"\x1b[200~hello\x1b[Aworld\x1b[201~"]),
vec![paste(b"hello\x1b[Aworld")]
);
}
#[test]
fn emits_normal_events_before_and_after_bracketed_paste() {
assert_eq!(
parse_chunks(&[b"before\x1b[200~pasted\x1b[201~after"]),
vec![b(b"before"), paste(b"pasted"), b(b"after")]
);
}
#[test]
fn emits_multiple_paste_events_in_one_chunk() {
assert_eq!(
parse_chunks(&[b"\x1b[200~first\x1b[201~mid\x1b[200~second\x1b[201~"]),
vec![paste(b"first"), b(b"mid"), paste(b"second")]
);
}
#[test]
fn holds_incomplete_bracketed_paste_as_pending() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b[200~hello"), vec![]);
assert!(!parser.has_pending_escape());
assert_eq!(parser.push(b" world\x1b[201~"), vec![paste(b"hello world")]);
}
#[test]
fn assembles_bracketed_paste_from_chunk_by_chunk_delivery() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b[200~"), vec![]);
assert_eq!(parser.push(b"hello"), vec![]);
assert_eq!(parser.push(b"\x1b[201~"), vec![paste(b"hello")]);
}
#[test]
fn emits_empty_paste_for_adjacent_paste_markers() {
assert_eq!(parse_chunks(&[b"\x1b[200~\x1b[201~"]), vec![paste(b"")]);
}
#[test]
fn handles_paste_start_split_before_the_tilde() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b[200"), vec![]);
assert!(!parser.has_pending_escape());
assert_eq!(parser.push(b"~hello\x1b[201~"), vec![paste(b"hello")]);
}
#[test]
fn has_pending_escape_returns_true_for_length_3_paste_start_prefix() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b[2"), vec![]);
assert!(parser.has_pending_escape());
}
#[test]
fn has_pending_escape_returns_true_for_length_4_paste_start_prefix() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b[20"), vec![]);
assert!(parser.has_pending_escape());
}
#[test]
fn paste_event_delivers_backspace_chars_verbatim_without_splitting() {
assert_eq!(
parse_chunks(&[b"\x1b[200~\x7f\x08\x7f\x1b[201~"]),
vec![paste(b"\x7f\x08\x7f")]
);
}
#[test]
fn holds_esc_plus_split_utf8_codepoint_until_continuation_arrives() {
let mut parser = Segmenter::default();
assert_eq!(parser.push(b"\x1b\xf0\x9f"), vec![]);
assert!(parser.has_pending_escape());
assert_eq!(parser.push(b"\x98\x80"), vec![b("\u{1b}😀".as_bytes())]);
}