improved escape sequence parsing for Terminal

This commit is contained in:
2025-05-31 01:05:18 -04:00
parent e7d8b98a73
commit 25ec8c72be
2 changed files with 97 additions and 22 deletions

View File

@@ -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."; 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 /// Unified interface for different line editing methods
pub trait Readline { pub trait Readline {
fn readline(&mut self) -> ShResult<String>; fn readline(&mut self) -> ShResult<String>;
@@ -38,7 +44,7 @@ pub struct FernVi {
impl Readline for FernVi { impl Readline for FernVi {
fn readline(&mut self) -> ShResult<String> { fn readline(&mut self) -> ShResult<String> {
/* /* 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!"); self.term.writeln("This is a line!");
self.term.writeln("This is a line!"); self.term.writeln("This is a line!");

View File

@@ -303,28 +303,9 @@ impl Terminal {
// ESC sequences // ESC sequences
if collected[0] == 0x1b && collected.len() == 1 { if collected[0] == 0x1b && collected.len() == 1 {
// Peek next byte if any if let Some(code) = self.parse_esc_seq(&mut buf) {
let n = self.peek_byte(&mut buf[..1]); return code
if n == 0 {
return KeyEvent(KeyCode::Esc, ModKeys::empty());
} }
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 // Try parse valid UTF-8 from collected bytes
@@ -342,6 +323,94 @@ impl Terminal {
KeyEvent(KeyCode::Null, ModKeys::empty()) KeyEvent(KeyCode::Null, ModKeys::empty())
} }
pub fn parse_esc_seq(&self, buf: &mut [u8]) -> Option<KeyEvent> {
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) { pub fn cursor_pos(&mut self) -> (usize, usize) {
self.write("\x1b[6n"); self.write("\x1b[6n");
let mut buf = [0u8;32]; let mut buf = [0u8;32];