aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonas Maier <jonas@x77.dev>2026-05-03 16:22:16 +0200
committerJonas Maier <jonas@x77.dev>2026-05-03 16:22:16 +0200
commitf18f2612611b56160f22cd348dad4c1ed60b2fe6 (patch)
tree17d78d9993ec087d9d2e34406c5160123ccf8c36
parent4f1dfb90ba2a859875c2cee9990e9c94fb789ef3 (diff)
downloadpish-f18f2612611b56160f22cd348dad4c1ed60b2fe6.tar.gz
no more constant memory leak due to escape parsing
-rw-r--r--src/ansi/mod.rs124
1 files changed, 13 insertions, 111 deletions
diff --git a/src/ansi/mod.rs b/src/ansi/mod.rs
index cbdcbe6..020713a 100644
--- a/src/ansi/mod.rs
+++ b/src/ansi/mod.rs
@@ -1,25 +1,4 @@
-use std::{collections::BTreeMap, io::Read, os::unix::ffi::OsStrExt, sync::RwLock};
-
-use crate::cursor::Direction;
-
-pub enum KeyboardInput {
- Eof,
- Key(u8),
- CtrlA,
- CtrlB,
- CtrlC,
- CtrlE,
- CtrlD,
- CtrlL,
- CtrlR,
- Arrow(Direction),
- CtrlArrow(Direction),
- DeleteLeft,
- DeleteRight,
- CtrlDeleteRight,
- Home,
- End,
-}
+use std::{collections::BTreeMap, io::Read, sync::RwLock};
fn read1() -> Option<u8> {
let mut buf = [0];
@@ -29,95 +8,8 @@ fn read1() -> Option<u8> {
}
}
-fn byte_to_dir(b: u8) -> Option<Direction> {
- use Direction::*;
- match b {
- b'A' => Some(Up),
- b'B' => Some(Down),
- b'C' => Some(Right),
- b'D' => Some(Left),
- _ => None,
- }
-}
-
-fn read_escape(debug: bool) -> KeyboardInput {
- use Direction::*;
- use KeyboardInput::*;
-
- let mut seq = vec![match read1() {
- Some(x) => x,
- None => return Eof,
- }];
-
- if seq[0] == b'[' {
- // still more
- while {
- let last = seq[seq.len() - 1];
- !(0x40..=0x7E).contains(&last) || seq.len() == 1
- } {
- seq.push(match read1() {
- Some(x) => x,
- None => return Eof,
- });
- }
-
- if debug {
- println!("escape: {}", seq.escape_ascii());
- }
-
- match seq[1] {
- b'3' => {
- if seq.len() > 2 && seq[2] == b'~' {
- DeleteRight
- } else {
- todo!("unhandled: {}", seq.escape_ascii());
- }
- }
- b'H' => Home,
- b'F' => End,
- b'd' => CtrlDeleteRight,
-
- // Ctrl Arrow
- b'1' => {
- if seq[1..].starts_with(b"1;5") {
- if seq.len() == 4 {
- todo!("idk what this is.");
- }
- match seq[4] {
- b'A' => CtrlArrow(Up),
- b'B' => CtrlArrow(Down),
- b'C' => CtrlArrow(Right),
- b'D' => CtrlArrow(Left),
- _ => todo!("unhandled {}", seq.escape_ascii()),
- }
- } else {
- todo!("unhandled {}", seq[1..].escape_ascii())
- }
- }
-
- x => {
- if let Some(dir) = byte_to_dir(x) {
- Arrow(dir)
- } else {
- todo!("escape characters {}", seq[1..].escape_ascii())
- }
- }
- }
- } else {
- if debug {
- println!("escape: {}", seq.escape_ascii());
- }
- match seq[0] {
- b'd' => CtrlDeleteRight,
- x => todo!("unhandled escape code: ESC {x}"),
- }
- }
-}
-
pub fn read(debug: bool) -> Option<KbInput<'static>> {
- let trie = EscapeTrie::from(ti());
- let trie = Box::leak(Box::new(trie)); // TODO don't leak memory, this is only temporary
- let mut reader = EscapingStdinReader::new(trie);
+ let mut reader = EscapingStdinReader::new(et());
loop {
let Some(b) = read1() else {
break None;
@@ -216,6 +108,7 @@ pub struct Escape<'a> {
use terminfo_lean::parse::Terminfo;
static TERMINFO: RwLock<Option<&'static Terminfo<'static>>> = RwLock::new(None);
+static ESCAPE_TRIE: RwLock<Option<&'static EscapeTrie>> = RwLock::new(None);
fn parse_terminfo() -> Result<Terminfo<'static>, ()> {
let term = std::env::var_os("TERM").unwrap_or_else(|| "xterm".into());
@@ -244,15 +137,24 @@ pub fn setup() {
println!("using backup terminfo (might not be correct for this terminal)");
parse_terminfo_backup()
});
- let ti = Box::leak(Box::new(ti));
+ let ti: &'static _ = Box::leak(Box::new(ti));
+
TERMINFO.clear_poison();
*TERMINFO.write().unwrap() = Some(ti);
+
+ let et = Box::leak(Box::new(EscapeTrie::from(ti)));
+ ESCAPE_TRIE.clear_poison();
+ *ESCAPE_TRIE.write().unwrap() = Some(et);
}
pub fn ti() -> &'static Terminfo<'static> {
TERMINFO.read().unwrap().unwrap()
}
+fn et() -> &'static EscapeTrie {
+ ESCAPE_TRIE.read().unwrap().unwrap()
+}
+
fn is_parametrized(x: &[u8]) -> bool {
let mut pct = false;
for &b in x {