From 238e1540fee274c0c63a2df044b0c4354b8fb673 Mon Sep 17 00:00:00 2001 From: Kyler Clay Date: Sat, 31 May 2025 01:05:18 -0400 Subject: [PATCH] improved escape sequence parsing for Terminal --- src/prompt/readline/mod.rs | 8 ++- src/prompt/readline/term.rs | 111 +++++++++++++++++++++++++++++------- 2 files changed, 97 insertions(+), 22 deletions(-) diff --git a/src/prompt/readline/mod.rs b/src/prompt/readline/mod.rs index 3495c70..fce4ce7 100644 --- a/src/prompt/readline/mod.rs +++ b/src/prompt/readline/mod.rs @@ -21,6 +21,12 @@ pub mod history; const LOREM_IPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore\nmagna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; +/* + * Known issues: + * If the line buffer scrolls past the terminal height, shit gets fucked + * the cursor sometimes spazzes out during redraw, but ends up in the right place + */ + /// Unified interface for different line editing methods pub trait Readline { fn readline(&mut self) -> ShResult; @@ -38,7 +44,7 @@ pub struct FernVi { impl Readline for FernVi { fn readline(&mut self) -> ShResult { - /* + /* a monument to the insanity of debugging this shit self.term.writeln("This is a line!"); self.term.writeln("This is a line!"); self.term.writeln("This is a line!"); diff --git a/src/prompt/readline/term.rs b/src/prompt/readline/term.rs index 9e5f558..50c0bc2 100644 --- a/src/prompt/readline/term.rs +++ b/src/prompt/readline/term.rs @@ -303,28 +303,9 @@ impl Terminal { // ESC sequences if collected[0] == 0x1b && collected.len() == 1 { - // Peek next byte if any - let n = self.peek_byte(&mut buf[..1]); - if n == 0 { - return KeyEvent(KeyCode::Esc, ModKeys::empty()); + if let Some(code) = self.parse_esc_seq(&mut buf) { + return code } - collected.push(buf[0]); - - if buf[0] == b'[' { - // Read third byte - let _ = self.read_byte(&mut buf[..1]); - collected.push(buf[0]); - - return match buf[0] { - b'A' => KeyEvent(KeyCode::Up, ModKeys::empty()), - b'B' => KeyEvent(KeyCode::Down, ModKeys::empty()), - b'C' => KeyEvent(KeyCode::Right, ModKeys::empty()), - b'D' => KeyEvent(KeyCode::Left, ModKeys::empty()), - _ => KeyEvent(KeyCode::Esc, ModKeys::empty()), - }; - } - - return KeyEvent(KeyCode::Esc, ModKeys::empty()); } // Try parse valid UTF-8 from collected bytes @@ -342,6 +323,94 @@ impl Terminal { KeyEvent(KeyCode::Null, ModKeys::empty()) } + pub fn parse_esc_seq(&self, buf: &mut [u8]) -> Option { + let mut collected = vec![0x1b]; + + // Peek next byte + let _ = self.peek_byte(&mut buf[..1]); + let b1 = buf[0]; + collected.push(b1); + + match b1 { + b'[' => { + // Next byte(s) determine the sequence + let _ = self.peek_byte(&mut buf[..1]); + let b2 = buf[0]; + collected.push(b2); + + match b2 { + b'A' => Some(KeyEvent(KeyCode::Up, ModKeys::empty())), + b'B' => Some(KeyEvent(KeyCode::Down, ModKeys::empty())), + b'C' => Some(KeyEvent(KeyCode::Right, ModKeys::empty())), + b'D' => Some(KeyEvent(KeyCode::Left, ModKeys::empty())), + b'1'..=b'9' => { + // Might be Delete/Home/etc + let mut digits = vec![b2]; + + // Keep reading until we hit `~` or `;` (modifiers) + loop { + let _ = self.peek_byte(&mut buf[..1]); + let b = buf[0]; + collected.push(b); + + if b == b'~' { + break; + } else if b == b';' { + // modifier-aware sequence, like `ESC [ 1 ; 5 ~` + // You may want to parse the full thing + break; + } else if !b.is_ascii_digit() { + break; + } else { + digits.push(b); + } + } + + let key = match digits.as_slice() { + [b'1'] => KeyCode::Home, + [b'3'] => KeyCode::Delete, + [b'4'] => KeyCode::End, + [b'5'] => KeyCode::PageUp, + [b'6'] => KeyCode::PageDown, + [b'7'] => KeyCode::Home, // xterm alternate + [b'8'] => KeyCode::End, // xterm alternate + + // Function keys + [b'1',b'5'] => KeyCode::F(5), + [b'1',b'7'] => KeyCode::F(6), + [b'1',b'8'] => KeyCode::F(7), + [b'1',b'9'] => KeyCode::F(8), + [b'2',b'0'] => KeyCode::F(9), + [b'2',b'1'] => KeyCode::F(10), + [b'2',b'3'] => KeyCode::F(11), + [b'2',b'4'] => KeyCode::F(12), + _ => KeyCode::Esc, + }; + + Some(KeyEvent(key, ModKeys::empty())) + } + _ => Some(KeyEvent(KeyCode::Esc, ModKeys::empty())), + } + } + b'O' => { + let _ = self.peek_byte(&mut buf[..1]); + let b2 = buf[0]; + collected.push(b2); + + let key = match b2 { + b'P' => KeyCode::F(1), + b'Q' => KeyCode::F(2), + b'R' => KeyCode::F(3), + b'S' => KeyCode::F(4), + _ => KeyCode::Esc, + }; + + Some(KeyEvent(key, ModKeys::empty())) + } + _ => Some(KeyEvent(KeyCode::Esc, ModKeys::empty())), + } + } + pub fn cursor_pos(&mut self) -> (usize, usize) { self.write("\x1b[6n"); let mut buf = [0u8;32];