#![allow(clippy::match_same_arms)]
#![allow(clippy::unused_self)]
#![allow(clippy::unnecessary_wraps)]
#![allow(clippy::needless_pass_by_ref_mut)]
use crate::input::event::{Event, PasteEvent, ResizeEvent};
use crate::input::keyboard::{KeyCode, KeyEvent, KeyModifiers};
use crate::terminal::{MouseButton, MouseEvent, MouseEventKind};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ParseError {
Empty,
Incomplete,
UnrecognizedSequence(Vec<u8>),
InvalidUtf8,
PasteBufferOverflow,
InvalidResizeFormat,
SequenceTooLong,
}
pub type ParseResult = Result<(Event, usize), ParseError>;
const MAX_PASTE_BUFFER_SIZE: usize = 10 * 1024 * 1024;
pub const MAX_CSI_LENGTH: usize = 256;
pub const MAX_DCS_LENGTH: usize = 64 * 1024;
#[derive(Clone, Debug, Default)]
pub struct InputParser {
in_paste: bool,
paste_buffer: Vec<u8>,
}
impl InputParser {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn parse(&mut self, input: &[u8]) -> ParseResult {
if input.is_empty() {
return Err(ParseError::Empty);
}
if self.in_paste {
return self.parse_paste(input);
}
let first = input[0];
match first {
0x1b => self.parse_escape(input),
0x00 => Ok((KeyEvent::new(KeyCode::Null, KeyModifiers::CTRL).into(), 1)),
0x09 => Ok((KeyEvent::key(KeyCode::Tab).into(), 1)),
0x0A => Ok((KeyEvent::key(KeyCode::Enter).into(), 1)), 0x0D => Ok((KeyEvent::key(KeyCode::Enter).into(), 1)), 0x01..=0x08 | 0x0B..=0x0C | 0x0E..=0x1a => {
let c = (first - 1 + b'a') as char;
Ok((
KeyEvent::new(KeyCode::Char(c), KeyModifiers::CTRL).into(),
1,
))
}
0x7f => Ok((KeyEvent::key(KeyCode::Backspace).into(), 1)),
0x20..=0x7e => Ok((KeyEvent::char(first as char).into(), 1)),
0x80..=0xff => self.parse_utf8(input),
_ => Ok((KeyEvent::char(first as char).into(), 1)),
}
}
fn parse_escape(&mut self, input: &[u8]) -> ParseResult {
if input.len() == 1 {
return Err(ParseError::Incomplete);
}
match input[1] {
b'[' => self.parse_csi(input),
b'O' => self.parse_ss3(input),
b'P' => self.parse_dcs(input),
0x20..=0x7e => {
let c = input[1] as char;
Ok((KeyEvent::new(KeyCode::Char(c), KeyModifiers::ALT).into(), 2))
}
0x1b => Ok((KeyEvent::key(KeyCode::Esc).into(), 1)),
_ => Ok((KeyEvent::key(KeyCode::Esc).into(), 1)),
}
}
fn parse_csi(&mut self, input: &[u8]) -> ParseResult {
if input.len() < 3 {
return Err(ParseError::Incomplete);
}
let mut end = 2;
while end < input.len() {
if end - 2 >= MAX_CSI_LENGTH {
return Err(ParseError::SequenceTooLong);
}
let b = input[end];
if (0x40..=0x7e).contains(&b) {
break;
}
end += 1;
}
if end >= input.len() {
if end - 2 >= MAX_CSI_LENGTH {
return Err(ParseError::SequenceTooLong);
}
return Err(ParseError::Incomplete);
}
let final_byte = input[end];
let params = &input[2..end];
match final_byte {
b'A' => self.parse_modified_key(params, KeyCode::Up, end + 1),
b'B' => self.parse_modified_key(params, KeyCode::Down, end + 1),
b'C' => self.parse_modified_key(params, KeyCode::Right, end + 1),
b'D' => self.parse_modified_key(params, KeyCode::Left, end + 1),
b'H' => self.parse_modified_key(params, KeyCode::Home, end + 1),
b'F' => self.parse_modified_key(params, KeyCode::End, end + 1),
b'E' => self.parse_modified_key(params, KeyCode::KeypadBegin, end + 1),
b'~' => self.parse_tilde_key(params, end + 1),
b'M' => {
if params.first() == Some(&b'<') {
self.parse_sgr_mouse(input)
} else {
self.parse_x11_mouse(input, end + 1)
}
}
b'm' => self.parse_sgr_mouse(input),
b'I' => Ok((Event::FocusGained, end + 1)),
b'O' => Ok((Event::FocusLost, end + 1)),
b't' => self.parse_resize(params, end + 1),
_ => Err(ParseError::UnrecognizedSequence(input[..=end].to_vec())),
}
}
fn parse_dcs(&self, input: &[u8]) -> ParseResult {
let mut i = 2; while i < input.len() {
if i - 2 >= MAX_DCS_LENGTH {
return Err(ParseError::SequenceTooLong);
}
match input[i] {
0x1b => {
if i + 1 < input.len() {
if input[i + 1] == b'\\' {
return Err(ParseError::UnrecognizedSequence(input[..=i + 1].to_vec()));
}
} else {
return Err(ParseError::Incomplete);
}
}
0x9c => {
return Err(ParseError::UnrecognizedSequence(input[..=i].to_vec()));
}
_ => {}
}
i += 1;
}
if i - 2 >= MAX_DCS_LENGTH {
return Err(ParseError::SequenceTooLong);
}
Err(ParseError::Incomplete)
}
fn parse_modified_key(&self, params: &[u8], base_key: KeyCode, consumed: usize) -> ParseResult {
let modifiers = if params.is_empty() {
KeyModifiers::empty()
} else {
self.parse_modifiers(params)?
};
Ok((KeyEvent::new(base_key, modifiers).into(), consumed))
}
fn parse_modifiers(&self, params: &[u8]) -> Result<KeyModifiers, ParseError> {
let s = std::str::from_utf8(params).map_err(|_| ParseError::InvalidUtf8)?;
let parts: Vec<&str> = s.split(';').collect();
if parts.len() >= 2 {
if let Ok(n) = parts[1].parse::<u8>() {
let n = n.saturating_sub(1);
let mut mods = KeyModifiers::empty();
if n & 1 != 0 {
mods |= KeyModifiers::SHIFT;
}
if n & 2 != 0 {
mods |= KeyModifiers::ALT;
}
if n & 4 != 0 {
mods |= KeyModifiers::CTRL;
}
return Ok(mods);
}
}
Ok(KeyModifiers::empty())
}
fn parse_tilde_key(&mut self, params: &[u8], consumed: usize) -> ParseResult {
let s = std::str::from_utf8(params).map_err(|_| ParseError::InvalidUtf8)?;
let parts: Vec<&str> = s.split(';').collect();
let num: u8 = parts.first().and_then(|p| p.parse().ok()).unwrap_or(0);
let modifiers = if parts.len() >= 2 {
self.parse_modifiers(params)?
} else {
KeyModifiers::empty()
};
let code = match num {
1 => KeyCode::Home,
2 => KeyCode::Insert,
3 => KeyCode::Delete,
4 => KeyCode::End,
5 => KeyCode::PageUp,
6 => KeyCode::PageDown,
7 => KeyCode::Home,
8 => KeyCode::End,
11 => KeyCode::F(1),
12 => KeyCode::F(2),
13 => KeyCode::F(3),
14 => KeyCode::F(4),
15 => KeyCode::F(5),
17 => KeyCode::F(6),
18 => KeyCode::F(7),
19 => KeyCode::F(8),
20 => KeyCode::F(9),
21 => KeyCode::F(10),
23 => KeyCode::F(11),
24 => KeyCode::F(12),
25 => KeyCode::F(13),
26 => KeyCode::F(14),
28 => KeyCode::F(15),
29 => KeyCode::F(16),
31 => KeyCode::F(17),
32 => KeyCode::F(18),
33 => KeyCode::F(19),
34 => KeyCode::F(20),
200 => {
self.in_paste = true;
return Err(ParseError::Incomplete);
}
201 => {
return Err(ParseError::UnrecognizedSequence(params.to_vec()));
}
_ => return Err(ParseError::UnrecognizedSequence(params.to_vec())),
};
Ok((KeyEvent::new(code, modifiers).into(), consumed))
}
fn parse_ss3(&mut self, input: &[u8]) -> ParseResult {
if input.len() < 3 {
return Err(ParseError::Incomplete);
}
let code = match input[2] {
b'P' => KeyCode::F(1),
b'Q' => KeyCode::F(2),
b'R' => KeyCode::F(3),
b'S' => KeyCode::F(4),
b'A' => KeyCode::Up,
b'B' => KeyCode::Down,
b'C' => KeyCode::Right,
b'D' => KeyCode::Left,
b'H' => KeyCode::Home,
b'F' => KeyCode::End,
b'M' => KeyCode::Enter,
_ => return Err(ParseError::UnrecognizedSequence(input[..3].to_vec())),
};
Ok((KeyEvent::key(code).into(), 3))
}
fn parse_x11_mouse(&self, input: &[u8], start: usize) -> ParseResult {
if input.len() < start + 3 {
return Err(ParseError::Incomplete);
}
let cb = input[start];
let cx = input[start + 1].saturating_sub(33);
let cy = input[start + 2].saturating_sub(33);
let (button, kind) = decode_x11_button(cb);
let (shift, alt, ctrl) = decode_x11_modifiers(cb);
let event = MouseEvent::new(u32::from(cx), u32::from(cy), button, kind)
.with_modifiers(shift, ctrl, alt);
Ok((Event::Mouse(event), start + 3))
}
fn parse_sgr_mouse(&self, input: &[u8]) -> ParseResult {
let term_pos = input.iter().position(|&b| b == b'M' || b == b'm');
let Some(term_pos) = term_pos else {
return Err(ParseError::Incomplete);
};
let is_release = input[term_pos] == b'm';
let params_start = if input.len() > 2 && input[2] == b'<' {
3
} else {
2
};
let params = &input[params_start..term_pos];
let s = std::str::from_utf8(params).map_err(|_| ParseError::InvalidUtf8)?;
let parts: Vec<&str> = s.split(';').collect();
if parts.len() < 3 {
return Err(ParseError::UnrecognizedSequence(
input[..=term_pos].to_vec(),
));
}
let cb: u8 = parts[0].parse().unwrap_or(0);
let cx: u32 = parts[1].parse::<u32>().unwrap_or(1).saturating_sub(1);
let cy: u32 = parts[2].parse::<u32>().unwrap_or(1).saturating_sub(1);
let (button, mut kind) = decode_sgr_button(cb);
if is_release {
kind = MouseEventKind::Release;
}
let (shift, alt, ctrl) = decode_sgr_modifiers(cb);
let event = MouseEvent::new(cx, cy, button, kind).with_modifiers(shift, ctrl, alt);
Ok((Event::Mouse(event), term_pos + 1))
}
fn parse_resize(&self, params: &[u8], consumed: usize) -> ParseResult {
let s = std::str::from_utf8(params).map_err(|_| ParseError::InvalidUtf8)?;
let parts: Vec<&str> = s.split(';').collect();
if parts.len() >= 3 && parts[0] == "8" {
let height: u16 = parts[1]
.parse()
.map_err(|_| ParseError::InvalidResizeFormat)?;
let width: u16 = parts[2]
.parse()
.map_err(|_| ParseError::InvalidResizeFormat)?;
Ok((Event::Resize(ResizeEvent::new(width, height)), consumed))
} else {
Err(ParseError::UnrecognizedSequence(params.to_vec()))
}
}
fn parse_paste(&mut self, input: &[u8]) -> ParseResult {
const START_SEQ: &[u8] = b"\x1b[200~";
const END_SEQ: &[u8] = b"\x1b[201~";
let content_start = if input.starts_with(START_SEQ) {
START_SEQ.len()
} else {
0
};
let effective_input = &input[content_start..];
if let Some(pos) = find_subsequence(effective_input, END_SEQ) {
let available = MAX_PASTE_BUFFER_SIZE.saturating_sub(self.paste_buffer.len());
if pos > available {
self.in_paste = false;
self.paste_buffer.clear();
return Err(ParseError::PasteBufferOverflow);
}
self.paste_buffer.extend_from_slice(&effective_input[..pos]);
self.in_paste = false;
let content = String::from_utf8_lossy(&self.paste_buffer).into_owned();
self.paste_buffer.clear();
Ok((
Event::Paste(PasteEvent::new(content)),
content_start + pos + END_SEQ.len(),
))
} else {
let available = MAX_PASTE_BUFFER_SIZE.saturating_sub(self.paste_buffer.len());
if effective_input.len() > available {
self.in_paste = false;
self.paste_buffer.clear();
return Err(ParseError::PasteBufferOverflow);
}
self.paste_buffer.extend_from_slice(effective_input);
Err(ParseError::Incomplete)
}
}
fn parse_utf8(&self, input: &[u8]) -> ParseResult {
let first = input[0];
let expected_len = if first & 0b1110_0000 == 0b1100_0000 {
2
} else if first & 0b1111_0000 == 0b1110_0000 {
3
} else if first & 0b1111_1000 == 0b1111_0000 {
4
} else {
return Err(ParseError::InvalidUtf8);
};
if input.len() < expected_len {
return Err(ParseError::Incomplete);
}
let s = std::str::from_utf8(&input[..expected_len]).map_err(|_| ParseError::InvalidUtf8)?;
let c = s.chars().next().ok_or(ParseError::InvalidUtf8)?;
Ok((KeyEvent::char(c).into(), expected_len))
}
pub fn clear(&mut self) {
self.in_paste = false;
self.paste_buffer.clear();
}
}
fn decode_x11_button(cb: u8) -> (MouseButton, MouseEventKind) {
let low = cb & 0b0000_0011;
let motion = cb & 0b0010_0000 != 0;
let scroll = cb & 0b0100_0000 != 0;
if scroll {
let kind = match low {
0 => MouseEventKind::ScrollUp,
1 => MouseEventKind::ScrollDown,
2 => MouseEventKind::ScrollLeft,
3 => MouseEventKind::ScrollRight,
_ => MouseEventKind::ScrollUp,
};
(MouseButton::None, kind)
} else if motion {
let button = match low {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
_ => MouseButton::None,
};
(button, MouseEventKind::Move)
} else {
let button = match low {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
3 => return (MouseButton::None, MouseEventKind::Release),
_ => MouseButton::None,
};
(button, MouseEventKind::Press)
}
}
fn decode_x11_modifiers(cb: u8) -> (bool, bool, bool) {
let shift = cb & 0b0000_0100 != 0;
let alt = cb & 0b0000_1000 != 0;
let ctrl = cb & 0b0001_0000 != 0;
(shift, alt, ctrl)
}
fn decode_sgr_button(cb: u8) -> (MouseButton, MouseEventKind) {
let low = cb & 0b0000_0011;
let motion = cb & 0b0010_0000 != 0;
let scroll = cb & 0b0100_0000 != 0;
if scroll {
let kind = match low {
0 => MouseEventKind::ScrollUp,
1 => MouseEventKind::ScrollDown,
2 => MouseEventKind::ScrollLeft,
3 => MouseEventKind::ScrollRight,
_ => MouseEventKind::ScrollUp,
};
(MouseButton::None, kind)
} else if motion {
let button = match low {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
_ => MouseButton::None,
};
(button, MouseEventKind::Move)
} else {
let button = match low {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
_ => MouseButton::None,
};
(button, MouseEventKind::Press)
}
}
fn decode_sgr_modifiers(cb: u8) -> (bool, bool, bool) {
let shift = cb & 0b0000_0100 != 0;
let alt = cb & 0b0000_1000 != 0;
let ctrl = cb & 0b0001_0000 != 0;
(shift, alt, ctrl)
}
fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack
.windows(needle.len())
.position(|window| window == needle)
}
#[cfg(test)]
#[allow(clippy::uninlined_format_args)]
mod tests {
use super::*;
#[test]
fn test_parse_char() {
let mut parser = InputParser::new();
let (event, consumed) = parser.parse(b"a").unwrap();
assert_eq!(consumed, 1);
assert_eq!(event, Event::Key(KeyEvent::char('a')));
}
#[test]
fn test_parse_ctrl_c() {
let mut parser = InputParser::new();
let (event, consumed) = parser.parse(&[0x03]).unwrap();
assert_eq!(consumed, 1);
let key = event.key().unwrap();
assert!(key.is_ctrl_c());
}
#[test]
fn test_parse_escape() {
let mut parser = InputParser::new();
let result = parser.parse(b"\x1b");
assert_eq!(result, Err(ParseError::Incomplete));
}
#[test]
fn test_parse_arrow_up() {
let mut parser = InputParser::new();
let (event, consumed) = parser.parse(b"\x1b[A").unwrap();
assert_eq!(consumed, 3);
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Up);
}
#[test]
fn test_parse_arrow_with_modifiers() {
let mut parser = InputParser::new();
let (event, _) = parser.parse(b"\x1b[1;2A").unwrap();
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Up);
assert!(key.shift());
}
#[test]
fn test_parse_f1() {
let mut parser = InputParser::new();
let (event, _) = parser.parse(b"\x1bOP").unwrap();
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::F(1));
}
#[test]
fn test_parse_delete() {
let mut parser = InputParser::new();
let (event, _) = parser.parse(b"\x1b[3~").unwrap();
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Delete);
}
#[test]
fn test_parse_alt_key() {
let mut parser = InputParser::new();
let (event, consumed) = parser.parse(b"\x1bx").unwrap();
assert_eq!(consumed, 2);
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Char('x'));
assert!(key.alt());
}
#[test]
fn test_parse_sgr_mouse() {
let mut parser = InputParser::new();
let (event, _) = parser.parse(b"\x1b[<0;11;6M").unwrap();
let mouse = event.mouse().unwrap();
assert_eq!(mouse.x, 10);
assert_eq!(mouse.y, 5);
assert_eq!(mouse.button, MouseButton::Left);
assert_eq!(mouse.kind, MouseEventKind::Press);
}
#[test]
fn test_parse_sgr_mouse_release() {
let mut parser = InputParser::new();
let (event, _) = parser.parse(b"\x1b[<0;11;6m").unwrap();
let mouse = event.mouse().unwrap();
assert_eq!(mouse.kind, MouseEventKind::Release);
}
#[test]
fn test_parse_utf8() {
let mut parser = InputParser::new();
let (event, consumed) = parser.parse("æ—¥".as_bytes()).unwrap();
assert_eq!(consumed, 3);
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Char('æ—¥'));
}
#[test]
fn test_parse_focus() {
let mut parser = InputParser::new();
let (event, _) = parser.parse(b"\x1b[I").unwrap();
assert_eq!(event, Event::FocusGained);
let (event, _) = parser.parse(b"\x1b[O").unwrap();
assert_eq!(event, Event::FocusLost);
}
#[test]
fn test_parse_backspace() {
let mut parser = InputParser::new();
let (event, _) = parser.parse(&[0x7f]).unwrap();
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Backspace);
}
#[test]
fn test_bracketed_paste_simple() {
eprintln!("[TEST] test_bracketed_paste_simple: Testing basic paste flow");
let mut parser = InputParser::new();
eprintln!("[TEST] Sending paste start sequence ESC[200~");
let result = parser.parse(b"\x1b[200~");
eprintln!("[TEST] Result after paste start: {result:?}");
assert_eq!(
result,
Err(ParseError::Incomplete),
"Paste start should return Incomplete"
);
assert!(parser.in_paste, "Parser should enter paste mode");
eprintln!("[TEST] Sending paste content 'hello' with end sequence");
let (event, consumed) = parser.parse(b"hello\x1b[201~").unwrap();
eprintln!("[TEST] Consumed {consumed} bytes, event: {event:?}");
assert_eq!(consumed, 11); let paste = event.paste().expect("Should be a paste event");
assert_eq!(paste.content, "hello");
assert!(!parser.in_paste, "Parser should exit paste mode");
eprintln!("[TEST] SUCCESS: Basic paste flow works correctly");
}
#[test]
fn test_bracketed_paste_multiline() {
eprintln!("[TEST] test_bracketed_paste_multiline: Testing multiline paste");
let mut parser = InputParser::new();
let _ = parser.parse(b"\x1b[200~");
assert!(parser.in_paste);
let content = b"line1\nline2\nline3\x1b[201~";
eprintln!(
"[TEST] Sending multiline content: {:?}",
String::from_utf8_lossy(&content[..15])
);
let (event, _) = parser.parse(content).unwrap();
let paste = event.paste().expect("Should be a paste event");
eprintln!("[TEST] Received paste content: {:?}", paste.content);
assert_eq!(paste.content, "line1\nline2\nline3");
eprintln!("[TEST] SUCCESS: Multiline paste works correctly");
}
#[test]
fn test_bracketed_paste_with_escapes() {
eprintln!("[TEST] test_bracketed_paste_with_escapes: Testing paste with embedded escapes");
let mut parser = InputParser::new();
let _ = parser.parse(b"\x1b[200~");
assert!(parser.in_paste);
let content = b"text with \x1b escape\x1b[201~";
eprintln!("[TEST] Sending content with embedded escape byte");
let (event, _) = parser.parse(content).unwrap();
let paste = event.paste().expect("Should be a paste event");
eprintln!("[TEST] Received paste content: {:?}", paste.content);
assert!(paste.content.contains('\x1b'));
assert_eq!(paste.content, "text with \x1b escape");
eprintln!("[TEST] SUCCESS: Paste with embedded escapes works correctly");
}
#[test]
fn test_bracketed_paste_chunked() {
eprintln!("[TEST] test_bracketed_paste_chunked: Testing chunked paste delivery");
let mut parser = InputParser::new();
eprintln!("[TEST] Sending paste start");
let _ = parser.parse(b"\x1b[200~");
assert!(parser.in_paste);
eprintln!("[TEST] Sending first chunk 'hello '");
let result = parser.parse(b"hello ");
eprintln!("[TEST] First chunk result: {result:?}");
assert_eq!(result, Err(ParseError::Incomplete));
assert!(parser.in_paste, "Should still be in paste mode");
eprintln!("[TEST] Sending second chunk 'world' with end sequence");
let (event, _) = parser.parse(b"world\x1b[201~").unwrap();
let paste = event.paste().expect("Should be a paste event");
eprintln!("[TEST] Final paste content: {:?}", paste.content);
assert_eq!(paste.content, "hello world");
eprintln!("[TEST] SUCCESS: Chunked paste works correctly");
}
#[test]
fn test_bracketed_paste_empty() {
eprintln!("[TEST] test_bracketed_paste_empty: Testing empty paste");
let mut parser = InputParser::new();
let _ = parser.parse(b"\x1b[200~");
assert!(parser.in_paste);
eprintln!("[TEST] Sending immediate end sequence (empty paste)");
let (event, _) = parser.parse(b"\x1b[201~").unwrap();
let paste = event.paste().expect("Should be a paste event");
eprintln!("[TEST] Paste content: {:?}", paste.content);
assert_eq!(paste.content, "");
assert!(!parser.in_paste);
eprintln!("[TEST] SUCCESS: Empty paste works correctly");
}
#[test]
fn test_bracketed_paste_with_unicode() {
eprintln!("[TEST] test_bracketed_paste_with_unicode: Testing unicode paste");
let mut parser = InputParser::new();
let _ = parser.parse(b"\x1b[200~");
assert!(parser.in_paste);
let content = "日本語テスト🎉\x1b[201~".as_bytes();
eprintln!("[TEST] Sending unicode content");
let (event, _) = parser.parse(content).unwrap();
let paste = event.paste().expect("Should be a paste event");
eprintln!("[TEST] Paste content: {:?}", paste.content);
assert_eq!(paste.content, "日本語テスト🎉");
eprintln!("[TEST] SUCCESS: Unicode paste works correctly");
}
#[test]
fn test_bracketed_paste_clear_resets_state() {
eprintln!(
"[TEST] test_bracketed_paste_clear_resets_state: Testing clear() resets paste state"
);
let mut parser = InputParser::new();
let _ = parser.parse(b"\x1b[200~");
let _ = parser.parse(b"partial content");
assert!(parser.in_paste);
eprintln!("[TEST] Calling clear()");
parser.clear();
assert!(!parser.in_paste, "clear() should exit paste mode");
eprintln!("[TEST] SUCCESS: clear() properly resets paste state");
}
#[test]
fn test_paste_end_without_start() {
eprintln!("[TEST] test_paste_end_without_start: Testing paste end without start");
let mut parser = InputParser::new();
eprintln!("[TEST] Sending paste end without being in paste mode");
let result = parser.parse(b"\x1b[201~");
eprintln!("[TEST] Result: {result:?}");
assert!(matches!(result, Err(ParseError::UnrecognizedSequence(_))));
eprintln!("[TEST] SUCCESS: Paste end without start handled correctly");
}
#[test]
fn test_bracketed_paste_full_sequence_at_once() {
eprintln!(
"[TEST] test_bracketed_paste_full_sequence_at_once: Testing full paste sequence in single call"
);
let mut parser = InputParser::new();
let full_input = b"\x1b[200~hello world\x1b[201~";
eprintln!("[TEST] Sending full paste sequence: {:?}", full_input);
let result = parser.parse(full_input);
eprintln!("[TEST] First parse result: {:?}", result);
assert_eq!(result, Err(ParseError::Incomplete));
assert!(parser.in_paste, "Parser should be in paste mode");
let result = parser.parse(full_input);
eprintln!("[TEST] Second parse result: {:?}", result);
let (event, consumed) = result.expect("Should parse paste event");
let paste = event.paste().expect("Should be a paste event");
eprintln!("[TEST] Paste content: {:?}", paste.content);
eprintln!("[TEST] Consumed: {} bytes", consumed);
assert_eq!(
paste.content, "hello world",
"Start sequence should be stripped"
);
assert!(
!paste.content.contains("\x1b[200~"),
"Content should not contain start sequence"
);
assert_eq!(consumed, full_input.len(), "Should consume entire input");
eprintln!("[TEST] SUCCESS: Full sequence at once correctly strips start sequence");
}
#[test]
fn test_parse_sgr_mouse_middle_click() {
eprintln!("[TEST] test_parse_sgr_mouse_middle_click: Testing middle mouse button");
let mut parser = InputParser::new();
let input = b"\x1b[<1;20;10M";
eprintln!("[TEST] Input: {:?}", input);
eprintln!("[TEST] Button byte: 1 (middle)");
let (event, consumed) = parser.parse(input).unwrap();
eprintln!("[TEST] Consumed: {} bytes", consumed);
let mouse = event.mouse().expect("Should be a mouse event");
eprintln!(
"[TEST] Mouse: button={:?} kind={:?} at ({}, {})",
mouse.button, mouse.kind, mouse.x, mouse.y
);
assert_eq!(mouse.button, MouseButton::Middle);
assert_eq!(mouse.kind, MouseEventKind::Press);
assert_eq!(mouse.x, 19); assert_eq!(mouse.y, 9); eprintln!("[TEST] PASS: Middle click detected correctly");
}
#[test]
fn test_parse_sgr_mouse_right_click() {
eprintln!("[TEST] test_parse_sgr_mouse_right_click: Testing right mouse button");
let mut parser = InputParser::new();
let input = b"\x1b[<2;30;15M";
eprintln!("[TEST] Input: {:?}", input);
eprintln!("[TEST] Button byte: 2 (right)");
let (event, _) = parser.parse(input).unwrap();
let mouse = event.mouse().expect("Should be a mouse event");
eprintln!(
"[TEST] Mouse: button={:?} at ({}, {})",
mouse.button, mouse.x, mouse.y
);
assert_eq!(mouse.button, MouseButton::Right);
assert_eq!(mouse.kind, MouseEventKind::Press);
assert_eq!(mouse.x, 29);
assert_eq!(mouse.y, 14);
eprintln!("[TEST] PASS: Right click detected correctly");
}
#[test]
fn test_parse_sgr_mouse_with_shift() {
eprintln!("[TEST] test_parse_sgr_mouse_with_shift: Testing Shift modifier");
let mut parser = InputParser::new();
let input = b"\x1b[<4;10;5M";
eprintln!("[TEST] Input: {:?}", input);
eprintln!("[TEST] Button byte: 4 = 0(left) + 4(shift)");
let (event, _) = parser.parse(input).unwrap();
let mouse = event.mouse().expect("Should be a mouse event");
eprintln!(
"[TEST] Modifiers: shift={} ctrl={} alt={}",
mouse.shift, mouse.ctrl, mouse.alt
);
assert_eq!(mouse.button, MouseButton::Left);
assert!(mouse.shift, "Shift modifier should be set");
assert!(!mouse.ctrl, "Ctrl should not be set");
assert!(!mouse.alt, "Alt should not be set");
eprintln!("[TEST] PASS: Shift modifier detected");
}
#[test]
fn test_parse_sgr_mouse_with_ctrl() {
eprintln!("[TEST] test_parse_sgr_mouse_with_ctrl: Testing Ctrl modifier");
let mut parser = InputParser::new();
let input = b"\x1b[<16;30;15M";
eprintln!("[TEST] Input: {:?}", input);
eprintln!("[TEST] Button byte: 16 = 0(left) + 16(ctrl)");
let (event, _) = parser.parse(input).unwrap();
let mouse = event.mouse().expect("Should be a mouse event");
eprintln!(
"[TEST] Modifiers: shift={} ctrl={} alt={}",
mouse.shift, mouse.ctrl, mouse.alt
);
assert_eq!(mouse.button, MouseButton::Left);
assert!(mouse.ctrl, "Ctrl modifier should be set");
assert!(!mouse.shift, "Shift should not be set");
assert!(!mouse.alt, "Alt should not be set");
eprintln!("[TEST] PASS: Ctrl modifier detected");
}
#[test]
fn test_parse_sgr_mouse_with_alt() {
eprintln!("[TEST] test_parse_sgr_mouse_with_alt: Testing Alt modifier");
let mut parser = InputParser::new();
let input = b"\x1b[<8;20;10M";
eprintln!("[TEST] Input: {:?}", input);
eprintln!("[TEST] Button byte: 8 = 0(left) + 8(alt)");
let (event, _) = parser.parse(input).unwrap();
let mouse = event.mouse().expect("Should be a mouse event");
eprintln!(
"[TEST] Modifiers: shift={} ctrl={} alt={}",
mouse.shift, mouse.ctrl, mouse.alt
);
assert!(mouse.alt, "Alt modifier should be set");
assert!(!mouse.shift, "Shift should not be set");
assert!(!mouse.ctrl, "Ctrl should not be set");
eprintln!("[TEST] PASS: Alt modifier detected");
}
#[test]
fn test_parse_sgr_mouse_with_multiple_modifiers() {
eprintln!(
"[TEST] test_parse_sgr_mouse_with_multiple_modifiers: Testing combined modifiers"
);
let mut parser = InputParser::new();
let input = b"\x1b[<20;15;8M";
eprintln!("[TEST] Input: {:?}", input);
eprintln!("[TEST] Button byte: 20 = 0(left) + 4(shift) + 16(ctrl)");
let (event, _) = parser.parse(input).unwrap();
let mouse = event.mouse().expect("Should be a mouse event");
eprintln!(
"[TEST] Modifiers: shift={} ctrl={} alt={}",
mouse.shift, mouse.ctrl, mouse.alt
);
assert!(mouse.shift, "Shift should be set");
assert!(mouse.ctrl, "Ctrl should be set");
assert!(!mouse.alt, "Alt should not be set");
eprintln!("[TEST] PASS: Multiple modifiers detected");
}
#[test]
fn test_parse_sgr_mouse_scroll_up() {
eprintln!("[TEST] test_parse_sgr_mouse_scroll_up: Testing scroll wheel up");
let mut parser = InputParser::new();
let input = b"\x1b[<64;10;5M";
eprintln!("[TEST] Input: {:?}", input);
eprintln!("[TEST] Button byte: 64 (scroll up)");
let (event, _) = parser.parse(input).unwrap();
let mouse = event.mouse().expect("Should be a mouse event");
eprintln!("[TEST] Event kind: {:?}", mouse.kind);
assert_eq!(mouse.kind, MouseEventKind::ScrollUp);
assert!(mouse.is_scroll());
eprintln!("[TEST] PASS: Scroll up detected");
}
#[test]
fn test_parse_sgr_mouse_scroll_down() {
eprintln!("[TEST] test_parse_sgr_mouse_scroll_down: Testing scroll wheel down");
let mut parser = InputParser::new();
let input = b"\x1b[<65;10;5M";
eprintln!("[TEST] Input: {:?}", input);
eprintln!("[TEST] Button byte: 65 (scroll down)");
let (event, _) = parser.parse(input).unwrap();
let mouse = event.mouse().expect("Should be a mouse event");
eprintln!("[TEST] Event kind: {:?}", mouse.kind);
assert_eq!(mouse.kind, MouseEventKind::ScrollDown);
eprintln!("[TEST] PASS: Scroll down detected");
}
#[test]
fn test_parse_sgr_mouse_scroll_left() {
eprintln!("[TEST] test_parse_sgr_mouse_scroll_left: Testing horizontal scroll left");
let mut parser = InputParser::new();
let input = b"\x1b[<66;10;5M";
eprintln!("[TEST] Input: {:?}", input);
eprintln!("[TEST] Button byte: 66 (scroll left)");
let (event, _) = parser.parse(input).unwrap();
let mouse = event.mouse().expect("Should be a mouse event");
eprintln!("[TEST] Event kind: {:?}", mouse.kind);
assert_eq!(mouse.kind, MouseEventKind::ScrollLeft);
eprintln!("[TEST] PASS: Scroll left detected");
}
#[test]
fn test_parse_sgr_mouse_scroll_right() {
eprintln!("[TEST] test_parse_sgr_mouse_scroll_right: Testing horizontal scroll right");
let mut parser = InputParser::new();
let input = b"\x1b[<67;10;5M";
eprintln!("[TEST] Input: {:?}", input);
eprintln!("[TEST] Button byte: 67 (scroll right)");
let (event, _) = parser.parse(input).unwrap();
let mouse = event.mouse().expect("Should be a mouse event");
eprintln!("[TEST] Event kind: {:?}", mouse.kind);
assert_eq!(mouse.kind, MouseEventKind::ScrollRight);
eprintln!("[TEST] PASS: Scroll right detected");
}
#[test]
fn test_parse_sgr_mouse_motion() {
eprintln!("[TEST] test_parse_sgr_mouse_motion: Testing mouse motion (drag)");
let mut parser = InputParser::new();
let input = b"\x1b[<32;50;25M";
eprintln!("[TEST] Input: {:?}", input);
eprintln!("[TEST] Button byte: 32 (motion with left button)");
let (event, _) = parser.parse(input).unwrap();
let mouse = event.mouse().expect("Should be a mouse event");
eprintln!(
"[TEST] Kind: {:?}, Button: {:?}, Position: ({}, {})",
mouse.kind, mouse.button, mouse.x, mouse.y
);
assert_eq!(mouse.kind, MouseEventKind::Move);
assert_eq!(mouse.x, 49);
assert_eq!(mouse.y, 24);
eprintln!("[TEST] PASS: Motion event detected");
}
#[test]
fn test_parse_sgr_mouse_large_coordinates() {
eprintln!(
"[TEST] test_parse_sgr_mouse_large_coordinates: Testing large terminal coordinates"
);
let mut parser = InputParser::new();
let input = b"\x1b[<0;999;500M";
eprintln!("[TEST] Input: {:?}", input);
eprintln!("[TEST] Testing coordinates (999, 500)");
let (event, _) = parser.parse(input).unwrap();
let mouse = event.mouse().expect("Should be a mouse event");
eprintln!("[TEST] Parsed coordinates: ({}, {})", mouse.x, mouse.y);
assert_eq!(mouse.x, 998, "X should be 999-1=998 (0-indexed)");
assert_eq!(mouse.y, 499, "Y should be 500-1=499 (0-indexed)");
eprintln!("[TEST] PASS: Large coordinates handled correctly");
}
#[test]
fn test_parse_sgr_mouse_all_buttons_release() {
eprintln!("[TEST] test_parse_sgr_mouse_all_buttons_release: Testing button release");
let mut parser = InputParser::new();
let (event, _) = parser.parse(b"\x1b[<0;10;5m").unwrap();
let mouse = event.mouse().unwrap();
eprintln!("[TEST] Left release: kind={:?}", mouse.kind);
assert_eq!(mouse.kind, MouseEventKind::Release);
assert_eq!(mouse.button, MouseButton::Left);
let (event, _) = parser.parse(b"\x1b[<1;10;5m").unwrap();
let mouse = event.mouse().unwrap();
eprintln!("[TEST] Middle release: kind={:?}", mouse.kind);
assert_eq!(mouse.kind, MouseEventKind::Release);
assert_eq!(mouse.button, MouseButton::Middle);
let (event, _) = parser.parse(b"\x1b[<2;10;5m").unwrap();
let mouse = event.mouse().unwrap();
eprintln!("[TEST] Right release: kind={:?}", mouse.kind);
assert_eq!(mouse.kind, MouseEventKind::Release);
assert_eq!(mouse.button, MouseButton::Right);
eprintln!("[TEST] PASS: All button releases detected");
}
#[test]
fn test_parse_x11_mouse_basic() {
eprintln!("[TEST] test_parse_x11_mouse_basic: Testing legacy X11 mouse encoding");
let mut parser = InputParser::new();
let input = b"\x1b[M +&";
eprintln!("[TEST] Input bytes: {:02x?}", input);
eprintln!("[TEST] X11 encoding: button=' '(32), x='+'(43-33=10), y='&'(38-33=5)");
let (event, consumed) = parser.parse(input).unwrap();
eprintln!("[TEST] Consumed: {} bytes", consumed);
let mouse = event.mouse().expect("Should be a mouse event");
eprintln!(
"[TEST] Parsed: button={:?} at ({}, {})",
mouse.button, mouse.x, mouse.y
);
assert_eq!(mouse.button, MouseButton::Left);
assert_eq!(mouse.x, 10);
assert_eq!(mouse.y, 5);
eprintln!("[TEST] PASS: X11 mouse encoding parsed correctly");
}
#[test]
fn test_parse_sgr_mouse_coordinate_boundary() {
eprintln!("[TEST] test_parse_sgr_mouse_coordinate_boundary: Testing coordinate edge cases");
let mut parser = InputParser::new();
let (event, _) = parser.parse(b"\x1b[<0;1;1M").unwrap();
let mouse = event.mouse().unwrap();
eprintln!("[TEST] Min coords (1,1) -> ({}, {})", mouse.x, mouse.y);
assert_eq!(mouse.x, 0);
assert_eq!(mouse.y, 0);
let (event, _) = parser.parse(b"\x1b[<0;9999;9999M").unwrap();
let mouse = event.mouse().unwrap();
eprintln!(
"[TEST] Large coords (9999,9999) -> ({}, {})",
mouse.x, mouse.y
);
assert_eq!(mouse.x, 9998);
assert_eq!(mouse.y, 9998);
eprintln!("[TEST] PASS: Coordinate boundaries handled correctly");
}
#[test]
fn test_parse_ctrl_sequences_all() {
let mut parser = InputParser::new();
for (i, expected_char) in ('a'..='z').enumerate() {
let ctrl_byte = (i + 1) as u8;
let (event, consumed) = parser.parse(&[ctrl_byte]).unwrap();
assert_eq!(consumed, 1);
let key = event.key().expect("Should be key event");
match ctrl_byte {
0x09 => assert_eq!(key.code, KeyCode::Tab, "0x09 should be Tab"),
0x0A => assert_eq!(key.code, KeyCode::Enter, "0x0A should be Enter"),
0x0D => assert_eq!(key.code, KeyCode::Enter, "0x0D should be Enter"),
_ => {
assert_eq!(key.code, KeyCode::Char(expected_char));
assert!(
key.ctrl(),
"Ctrl modifier should be set for byte 0x{:02x}",
ctrl_byte
);
}
}
}
}
#[test]
fn test_parse_function_keys_f1_f12() {
let mut parser = InputParser::new();
let ss3_keys = [(b"P", 1), (b"Q", 2), (b"R", 3), (b"S", 4)];
for (suffix, num) in ss3_keys {
let mut input = vec![0x1b, b'O'];
input.push(suffix[0]);
let (event, _) = parser.parse(&input).unwrap();
let key = event.key().unwrap();
assert_eq!(
key.code,
KeyCode::F(num),
"F{} should be parsed from SS3",
num
);
}
let csi_keys = [
(15, 5),
(17, 6),
(18, 7),
(19, 8),
(20, 9),
(21, 10),
(23, 11),
(24, 12),
];
for (num_code, f_num) in csi_keys {
let input = format!("\x1b[{}~", num_code);
let (event, _) = parser.parse(input.as_bytes()).unwrap();
let key = event.key().unwrap();
assert_eq!(
key.code,
KeyCode::F(f_num),
"F{} should be parsed from CSI {}~",
f_num,
num_code
);
}
}
#[test]
fn test_parse_special_keys_navigation() {
let mut parser = InputParser::new();
let (event, _) = parser.parse(b"\x1b[H").unwrap();
assert_eq!(event.key().unwrap().code, KeyCode::Home);
let (event, _) = parser.parse(b"\x1b[F").unwrap();
assert_eq!(event.key().unwrap().code, KeyCode::End);
let tilde_keys = [
(2, KeyCode::Insert),
(3, KeyCode::Delete),
(5, KeyCode::PageUp),
(6, KeyCode::PageDown),
];
for (num, expected_code) in tilde_keys {
let input = format!("\x1b[{}~", num);
let (event, _) = parser.parse(input.as_bytes()).unwrap();
assert_eq!(event.key().unwrap().code, expected_code);
}
}
#[test]
fn test_parse_all_arrow_keys() {
let mut parser = InputParser::new();
let arrows = [
(b'A', KeyCode::Up),
(b'B', KeyCode::Down),
(b'C', KeyCode::Right),
(b'D', KeyCode::Left),
];
for (char_code, expected_key) in arrows {
let input = [0x1b, b'[', char_code];
let (event, consumed) = parser.parse(&input).unwrap();
assert_eq!(consumed, 3);
assert_eq!(event.key().unwrap().code, expected_key);
}
}
#[test]
fn test_parse_malformed_sequence() {
let mut parser = InputParser::new();
let result = parser.parse(b"\x1b[999Z");
assert!(matches!(result, Err(ParseError::UnrecognizedSequence(_))));
let result = parser.parse(b"\x1b[999~");
assert!(matches!(result, Err(ParseError::UnrecognizedSequence(_))));
}
#[test]
fn test_parse_empty_input() {
let mut parser = InputParser::new();
let result = parser.parse(&[]);
assert_eq!(result, Err(ParseError::Empty));
}
#[test]
fn test_parse_incomplete_csi() {
let mut parser = InputParser::new();
let result = parser.parse(b"\x1b[");
assert_eq!(result, Err(ParseError::Incomplete));
let result = parser.parse(b"\x1b[1;2");
assert_eq!(result, Err(ParseError::Incomplete));
}
#[test]
fn test_csi_sequence_too_long() {
let mut parser = InputParser::new();
let mut long_csi = vec![0x1b, b'['];
long_csi.extend(std::iter::repeat_n(b'0', 300));
let result = parser.parse(&long_csi);
assert_eq!(
result,
Err(ParseError::SequenceTooLong),
"CSI sequence exceeding MAX_CSI_LENGTH should return SequenceTooLong"
);
}
#[test]
fn test_csi_sequence_at_limit() {
let mut parser = InputParser::new();
let mut valid_csi = vec![0x1b, b'['];
valid_csi.extend(std::iter::repeat_n(b'9', 250));
valid_csi.push(b'~');
let result = parser.parse(&valid_csi);
assert!(
!matches!(result, Err(ParseError::SequenceTooLong)),
"CSI sequence under MAX_CSI_LENGTH should not return SequenceTooLong"
);
}
#[test]
fn test_dcs_sequence_too_long() {
let mut parser = InputParser::new();
let mut long_dcs = vec![0x1b, b'P'];
long_dcs.extend(std::iter::repeat_n(b'X', 70 * 1024));
let result = parser.parse(&long_dcs);
assert_eq!(
result,
Err(ParseError::SequenceTooLong),
"DCS sequence exceeding MAX_DCS_LENGTH should return SequenceTooLong"
);
}
#[test]
fn test_dcs_sequence_at_limit() {
let mut parser = InputParser::new();
let mut valid_dcs = vec![0x1b, b'P'];
valid_dcs.extend(std::iter::repeat_n(b'X', 60 * 1024));
valid_dcs.extend_from_slice(b"\x1b\\");
let result = parser.parse(&valid_dcs);
assert!(
!matches!(result, Err(ParseError::SequenceTooLong)),
"DCS sequence under MAX_DCS_LENGTH should not return SequenceTooLong"
);
}
#[test]
fn test_parse_invalid_utf8() {
let mut parser = InputParser::new();
let result = parser.parse(&[0x80]);
assert!(matches!(result, Err(ParseError::InvalidUtf8)));
let result = parser.parse(&[0xc3]); assert_eq!(result, Err(ParseError::Incomplete));
}
#[test]
fn test_parse_invalid_utf8_in_csi_params() {
let mut parser = InputParser::new();
let result = parser.parse(&[0x1b, b'[', 0x80, b'A']);
assert_eq!(
result,
Err(ParseError::InvalidUtf8),
"Invalid UTF-8 in CSI arrow key params should return InvalidUtf8"
);
let result = parser.parse(&[0x1b, b'[', 0x80, b'~']);
assert_eq!(
result,
Err(ParseError::InvalidUtf8),
"Invalid UTF-8 in tilde key params should return InvalidUtf8"
);
let result = parser.parse(&[0x1b, b'[', b'8', b';', 0x80, b';', b'8', b'0', b't']);
assert_eq!(
result,
Err(ParseError::InvalidUtf8),
"Invalid UTF-8 in resize params should return InvalidUtf8"
);
}
#[test]
fn test_parse_null_character() {
let mut parser = InputParser::new();
let (event, consumed) = parser.parse(&[0x00]).unwrap();
assert_eq!(consumed, 1);
assert_eq!(event.key().unwrap().code, KeyCode::Null);
}
#[test]
fn test_parse_double_escape() {
let mut parser = InputParser::new();
let (event, consumed) = parser.parse(b"\x1b\x1b").unwrap();
assert_eq!(consumed, 1);
assert_eq!(event.key().unwrap().code, KeyCode::Esc);
}
#[test]
fn test_parse_resize_event() {
let mut parser = InputParser::new();
let (event, _) = parser.parse(b"\x1b[8;50;120t").unwrap();
match event {
Event::Resize(resize) => {
assert_eq!(resize.width, 120);
assert_eq!(resize.height, 50);
}
other => {
assert!(matches!(other, Event::Resize(_)), "Expected Resize event");
}
}
}
#[test]
fn test_parse_resize_invalid_height() {
let mut parser = InputParser::new();
let result = parser.parse(b"\x1b[8;<10;120t");
assert_eq!(
result,
Err(ParseError::InvalidResizeFormat),
"Non-numeric height should return error"
);
}
#[test]
fn test_parse_resize_invalid_width() {
let mut parser = InputParser::new();
let result = parser.parse(b"\x1b[8;50;>80t");
assert_eq!(
result,
Err(ParseError::InvalidResizeFormat),
"Non-numeric width should return error"
);
}
#[test]
fn test_parse_resize_overflow_dimensions() {
let mut parser = InputParser::new();
let result = parser.parse(b"\x1b[8;99999;120t");
assert_eq!(
result,
Err(ParseError::InvalidResizeFormat),
"Overflow height should return error"
);
}
#[test]
fn test_parse_resize_empty_values() {
let mut parser = InputParser::new();
let result = parser.parse(b"\x1b[8;;t");
assert_eq!(
result,
Err(ParseError::InvalidResizeFormat),
"Empty height/width should return error"
);
}
#[test]
fn test_parse_resize_zero_dimensions() {
let mut parser = InputParser::new();
let (event, _) = parser.parse(b"\x1b[8;0;0t").unwrap();
match event {
Event::Resize(resize) => {
assert_eq!(resize.width, 0);
assert_eq!(resize.height, 0);
}
other => {
assert!(matches!(other, Event::Resize(_)), "Expected Resize event");
}
}
}
#[test]
fn test_parse_resize_large_dimensions() {
let mut parser = InputParser::new();
let (event, _) = parser.parse(b"\x1b[8;1000;2000t").unwrap();
match event {
Event::Resize(resize) => {
assert_eq!(resize.width, 2000);
assert_eq!(resize.height, 1000);
}
other => {
assert!(matches!(other, Event::Resize(_)), "Expected Resize event");
}
}
}
#[test]
fn test_parse_keyboard_with_all_modifiers() {
let mut parser = InputParser::new();
let (event, _) = parser.parse(b"\x1b[1;8A").unwrap();
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Up);
assert!(key.shift(), "Shift should be set");
assert!(key.alt(), "Alt should be set");
assert!(key.ctrl(), "Ctrl should be set");
}
#[test]
fn test_x11_mouse_with_modifiers() {
let mut parser = InputParser::new();
let input = b"\x1b[M$+&";
let (event, _) = parser.parse(input).unwrap();
let mouse = event.mouse().unwrap();
assert!(
mouse.shift,
"Shift modifier should be detected in X11 encoding"
);
}
#[test]
fn test_paste_buffer_overflow_single_chunk() {
eprintln!(
"[TEST] test_paste_buffer_overflow_single_chunk: Testing overflow in single chunk"
);
let mut parser = InputParser::new();
let result = parser.parse(b"\x1b[200~");
assert_eq!(result, Err(ParseError::Incomplete));
assert!(parser.in_paste, "Should be in paste mode");
let oversized_content: Vec<u8> = vec![b'X'; MAX_PASTE_BUFFER_SIZE + 1];
let result = parser.parse(&oversized_content);
eprintln!("[TEST] Result: {:?}", result);
assert_eq!(
result,
Err(ParseError::PasteBufferOverflow),
"Should return PasteBufferOverflow error"
);
assert!(
!parser.in_paste,
"Parser should exit paste mode after overflow"
);
assert!(
parser.paste_buffer.is_empty(),
"Paste buffer should be cleared after overflow"
);
eprintln!("[TEST] PASS: Single chunk overflow handled correctly");
}
#[test]
fn test_paste_buffer_overflow_incremental() {
eprintln!(
"[TEST] test_paste_buffer_overflow_incremental: Testing overflow across multiple chunks"
);
let mut parser = InputParser::new();
let _ = parser.parse(b"\x1b[200~");
assert!(parser.in_paste);
let almost_full: Vec<u8> = vec![b'A'; MAX_PASTE_BUFFER_SIZE - 10];
let result = parser.parse(&almost_full);
assert_eq!(
result,
Err(ParseError::Incomplete),
"Should still be accumulating"
);
assert!(parser.in_paste, "Should still be in paste mode");
let overflow_chunk: Vec<u8> = vec![b'B'; 20];
let result = parser.parse(&overflow_chunk);
eprintln!("[TEST] Result after overflow chunk: {:?}", result);
assert_eq!(
result,
Err(ParseError::PasteBufferOverflow),
"Should return PasteBufferOverflow"
);
assert!(!parser.in_paste, "Parser should exit paste mode");
assert!(
parser.paste_buffer.is_empty(),
"Buffer should be cleared after overflow"
);
eprintln!("[TEST] PASS: Incremental overflow handled correctly");
}
#[test]
fn test_paste_buffer_overflow_with_end_sequence() {
eprintln!(
"[TEST] test_paste_buffer_overflow_with_end_sequence: Testing overflow when end sequence present"
);
let mut parser = InputParser::new();
let _ = parser.parse(b"\x1b[200~");
assert!(parser.in_paste);
let almost_full: Vec<u8> = vec![b'X'; MAX_PASTE_BUFFER_SIZE - 5];
let _ = parser.parse(&almost_full);
let mut final_chunk = vec![b'Y'; 20]; final_chunk.extend_from_slice(b"\x1b[201~"); let result = parser.parse(&final_chunk);
eprintln!("[TEST] Result: {:?}", result);
assert_eq!(
result,
Err(ParseError::PasteBufferOverflow),
"Should return overflow error even with end sequence"
);
assert!(!parser.in_paste, "Should exit paste mode");
assert!(parser.paste_buffer.is_empty(), "Buffer should be cleared");
eprintln!("[TEST] PASS: Overflow with end sequence handled correctly");
}
#[test]
fn test_paste_buffer_exactly_at_limit() {
eprintln!("[TEST] test_paste_buffer_exactly_at_limit: Testing paste exactly at size limit");
let mut parser = InputParser::new();
let _ = parser.parse(b"\x1b[200~");
assert!(parser.in_paste);
let exact_limit: Vec<u8> = vec![b'E'; MAX_PASTE_BUFFER_SIZE];
let result = parser.parse(&exact_limit);
assert_eq!(
result,
Err(ParseError::Incomplete),
"Should accept exactly at limit"
);
assert!(parser.in_paste, "Should still be in paste mode");
let (event, _) = parser.parse(b"\x1b[201~").expect("Should complete paste");
let paste = event.paste().expect("Should be paste event");
assert_eq!(
paste.content.len(),
MAX_PASTE_BUFFER_SIZE,
"Content should be exactly at limit"
);
eprintln!("[TEST] PASS: Exactly at limit works correctly");
}
#[test]
fn test_paste_buffer_overflow_resets_for_next_paste() {
eprintln!(
"[TEST] test_paste_buffer_overflow_resets_for_next_paste: Testing recovery after overflow"
);
let mut parser = InputParser::new();
let _ = parser.parse(b"\x1b[200~");
let oversized: Vec<u8> = vec![b'X'; MAX_PASTE_BUFFER_SIZE + 100];
let result = parser.parse(&oversized);
assert_eq!(result, Err(ParseError::PasteBufferOverflow));
assert!(!parser.in_paste, "Should exit paste mode after overflow");
let _ = parser.parse(b"\x1b[200~");
assert!(parser.in_paste, "Should enter paste mode again");
let (event, _) = parser
.parse(b"normal paste content\x1b[201~")
.expect("Normal paste should work after overflow");
let paste = event.paste().expect("Should be paste event");
assert_eq!(
paste.content, "normal paste content",
"Normal paste should work after previous overflow"
);
eprintln!("[TEST] PASS: Recovery after overflow works correctly");
}
#[test]
fn test_edge_partial_csi_sequence() {
let mut parser = InputParser::new();
let result = parser.parse(b"\x1b[");
assert_eq!(
result,
Err(ParseError::Incomplete),
"ESC [ should be Incomplete"
);
let result = parser.parse(b"\x1b[1");
assert_eq!(
result,
Err(ParseError::Incomplete),
"ESC [1 should be Incomplete"
);
let result = parser.parse(b"\x1b[1;2");
assert_eq!(
result,
Err(ParseError::Incomplete),
"ESC [1;2 should be Incomplete"
);
}
#[test]
fn test_edge_partial_mouse_coordinates() {
let mut parser = InputParser::new();
let result = parser.parse(b"\x1b[<0;10");
assert_eq!(
result,
Err(ParseError::Incomplete),
"Truncated SGR mouse should be Incomplete"
);
let result = parser.parse(b"\x1b[<0;10;5");
assert_eq!(
result,
Err(ParseError::Incomplete),
"SGR mouse without M/m should be Incomplete"
);
}
#[test]
fn test_edge_invalid_csi_parameters() {
let mut parser = InputParser::new();
let result = parser.parse(b"\x1b[abc~");
assert!(
matches!(result, Err(ParseError::UnrecognizedSequence(_))),
"CSI with non-numeric params should be unrecognized"
);
let result = parser.parse(b"\x1b[;A");
assert!(result.is_ok() || matches!(result, Err(ParseError::Incomplete)));
}
#[test]
fn test_edge_invalid_utf8_single_byte() {
let mut parser = InputParser::new();
let result = parser.parse(&[0x80]);
assert_eq!(
result,
Err(ParseError::InvalidUtf8),
"Lone continuation byte should be InvalidUtf8"
);
let result = parser.parse(&[0xFF]);
assert_eq!(
result,
Err(ParseError::InvalidUtf8),
"0xFF should be InvalidUtf8"
);
let result = parser.parse(&[0xFE]);
assert_eq!(
result,
Err(ParseError::InvalidUtf8),
"0xFE should be InvalidUtf8"
);
}
#[test]
fn test_edge_truncated_utf8_sequence() {
let mut parser = InputParser::new();
let result = parser.parse(&[0xC2]);
assert_eq!(
result,
Err(ParseError::Incomplete),
"Truncated 2-byte UTF-8 should be Incomplete"
);
let result = parser.parse(&[0xE6]);
assert_eq!(
result,
Err(ParseError::Incomplete),
"Truncated 3-byte UTF-8 should be Incomplete"
);
let result = parser.parse(&[0xE6, 0x97]);
assert_eq!(
result,
Err(ParseError::Incomplete),
"Partial 3-byte UTF-8 should be Incomplete"
);
let result = parser.parse(&[0xF0]);
assert_eq!(
result,
Err(ParseError::Incomplete),
"Truncated 4-byte UTF-8 should be Incomplete"
);
}
#[test]
fn test_edge_overlong_utf8_encoding() {
let mut parser = InputParser::new();
let result = parser.parse(&[0xC0, 0x41]);
assert_eq!(
result,
Err(ParseError::InvalidUtf8),
"Overlong C0 encoding should be InvalidUtf8"
);
let result = parser.parse(&[0xC1, 0x80]);
assert_eq!(
result,
Err(ParseError::InvalidUtf8),
"Overlong C1 encoding should be InvalidUtf8"
);
}
#[test]
fn test_edge_garbage_after_valid_sequence() {
let mut parser = InputParser::new();
let (event, consumed) = parser.parse(b"\x1b[Agarbage").unwrap();
assert_eq!(consumed, 3, "Should only consume the arrow key sequence");
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Up);
let (event, consumed) = parser.parse(b"garbage").unwrap();
assert_eq!(consumed, 1, "Should parse one char at a time");
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Char('g'));
}
#[test]
fn test_edge_rapid_escape_sequences() {
let mut parser = InputParser::new();
let input = b"\x1b[A\x1b[B\x1b[C\x1b[D";
let (event, consumed) = parser.parse(input).unwrap();
assert_eq!(consumed, 3);
assert_eq!(event.key().unwrap().code, KeyCode::Up);
let (event, consumed) = parser.parse(&input[3..]).unwrap();
assert_eq!(consumed, 3);
assert_eq!(event.key().unwrap().code, KeyCode::Down);
let (event, consumed) = parser.parse(&input[6..]).unwrap();
assert_eq!(consumed, 3);
assert_eq!(event.key().unwrap().code, KeyCode::Right);
let (event, consumed) = parser.parse(&input[9..]).unwrap();
assert_eq!(consumed, 3);
assert_eq!(event.key().unwrap().code, KeyCode::Left);
}
#[test]
fn test_edge_sgr_mouse_zero_coordinates() {
let mut parser = InputParser::new();
let result = parser.parse(b"\x1b[<0;0;0M");
match result {
Ok((event, _)) => {
let mouse = event.mouse().unwrap();
assert!(
mouse.x <= 1 && mouse.y <= 1,
"Zero/one coord should saturate near 0, got ({}, {})",
mouse.x,
mouse.y
);
}
Err(ParseError::UnrecognizedSequence(_)) => {
}
Err(e) => {
assert!(
matches!(e, ParseError::UnrecognizedSequence(_)),
"Unexpected error: {e:?}"
);
}
}
}
#[test]
fn test_edge_empty_input_after_escape() {
let mut parser = InputParser::new();
let result = parser.parse(b"");
assert_eq!(result, Err(ParseError::Empty));
let result = parser.parse(b"\x1b");
assert_eq!(result, Err(ParseError::Incomplete));
}
#[test]
fn test_edge_mixed_valid_invalid_sequence() {
let mut parser = InputParser::new();
let (event, consumed) = parser.parse(b"a\x1b[A").unwrap();
assert_eq!(consumed, 1, "Should only consume 'a'");
assert_eq!(event.key().unwrap().code, KeyCode::Char('a'));
let (event, consumed) = parser.parse(b"\x1b[A").unwrap();
assert_eq!(consumed, 3);
assert_eq!(event.key().unwrap().code, KeyCode::Up);
}
#[test]
fn test_edge_function_key_range() {
let mut parser = InputParser::new();
for (i, key_char) in ['P', 'Q', 'R', 'S'].iter().enumerate() {
let input = format!("\x1bO{}", key_char);
let (event, _) = parser.parse(input.as_bytes()).unwrap();
let key = event.key().unwrap();
assert_eq!(
key.code,
KeyCode::F((i + 1) as u8),
"F{} key mismatch",
i + 1
);
}
let f_keys = [
(b"\x1b[15~".as_slice(), 5),
(b"\x1b[17~".as_slice(), 6),
(b"\x1b[18~".as_slice(), 7),
(b"\x1b[19~".as_slice(), 8),
(b"\x1b[20~".as_slice(), 9),
(b"\x1b[21~".as_slice(), 10),
(b"\x1b[23~".as_slice(), 11),
(b"\x1b[24~".as_slice(), 12),
];
for (input, expected_n) in f_keys {
let (event, _) = parser.parse(input).unwrap();
let key = event.key().unwrap();
assert_eq!(
key.code,
KeyCode::F(expected_n),
"F{} key mismatch",
expected_n
);
}
}
#[test]
fn test_edge_special_keys() {
let mut parser = InputParser::new();
let special_keys = [
(b"\x1b[2~".as_slice(), KeyCode::Insert),
(b"\x1b[3~".as_slice(), KeyCode::Delete),
(b"\x1b[5~".as_slice(), KeyCode::PageUp),
(b"\x1b[6~".as_slice(), KeyCode::PageDown),
(b"\x1b[H".as_slice(), KeyCode::Home),
(b"\x1b[F".as_slice(), KeyCode::End),
];
for (input, expected_code) in special_keys {
let (event, _) = parser.parse(input).unwrap();
let key = event.key().unwrap();
assert_eq!(
key.code, expected_code,
"Special key {:?} mismatch",
expected_code
);
}
}
#[test]
fn test_edge_tab_and_enter() {
let mut parser = InputParser::new();
let (event, consumed) = parser.parse(&[0x09]).unwrap();
assert_eq!(consumed, 1);
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Tab, "0x09 should be Tab");
let (event, consumed) = parser.parse(&[0x0D]).unwrap();
assert_eq!(consumed, 1);
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Enter, "0x0D should be Enter");
let (event, consumed) = parser.parse(&[0x0A]).unwrap();
assert_eq!(consumed, 1);
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Enter, "0x0A should be Enter");
}
#[test]
fn test_edge_null_byte() {
let mut parser = InputParser::new();
let (event, consumed) = parser.parse(&[0x00]).unwrap();
assert_eq!(consumed, 1);
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Null, "0x00 should return KeyCode::Null");
}
#[test]
fn test_edge_esc_followed_by_printable() {
let mut parser = InputParser::new();
let (event, consumed) = parser.parse(b"\x1ba").unwrap();
assert_eq!(consumed, 2);
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Char('a'));
assert!(key.alt(), "Should have Alt modifier");
}
#[test]
fn test_edge_x11_mouse_boundary_coordinates() {
let mut parser = InputParser::new();
let input = [0x1b, b'[', b'M', 32, 223, 223];
let (event, _) = parser.parse(&input).unwrap();
let mouse = event.mouse().unwrap();
assert_eq!(mouse.x, 190, "X11 max X should be 190");
assert_eq!(mouse.y, 190, "X11 max Y should be 190");
}
#[test]
fn test_edge_sgr_mouse_button_4_and_5() {
let mut parser = InputParser::new();
let result = parser.parse(b"\x1b[<128;10;10M");
match result {
Ok((event, _)) => {
let mouse = event.mouse().unwrap();
assert!(matches!(
mouse.button,
MouseButton::Left
| MouseButton::Middle
| MouseButton::Right
| MouseButton::None
));
}
Err(ParseError::UnrecognizedSequence(_)) => {
}
Err(e) => {
assert!(
matches!(e, ParseError::UnrecognizedSequence(_)),
"Unexpected error for button 4: {e:?}"
);
}
}
}
#[test]
fn test_edge_multiple_modifiers_keyboard() {
let mut parser = InputParser::new();
let (event, _) = parser.parse(b"\x1b[1;6A").unwrap();
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Up);
assert!(key.ctrl(), "Ctrl should be set");
assert!(key.shift(), "Shift should be set");
assert!(!key.alt(), "Alt should not be set");
let (event, _) = parser.parse(b"\x1b[1;7B").unwrap();
let key = event.key().unwrap();
assert_eq!(key.code, KeyCode::Down);
assert!(key.ctrl(), "Ctrl should be set");
assert!(!key.shift(), "Shift should not be set");
assert!(key.alt(), "Alt should be set");
}
}