use std::ops::Not; use std::os::fd::BorrowedFd; mod cells; use cells::*; use ctlfun::Parameter::*; use ctlfun::TerminalInput::*; use ctlfun::{ControlFunction, TerminalInputParser}; use dsb::cell::Style; pub struct Terminal { pub style: Style, pub cursor: (u16, u16), pub saved_cursor: (u16, u16), pub view_o: Option, pub cells: Cells, pub p: TerminalInputParser, pub mode: Mode, pub alternate: Option>, } use std::default::Default::default; impl Terminal { pub fn new(sz: (u16, u16), alt: bool) -> Self { Self { view_o: Some(0), style: DSTYLE, saved_cursor: (1, 1), cursor: (1, 1), cells: Cells::new(sz, alt), p: default(), mode: Mode::Normal, alternate: alt .not() .then(|| Terminal::new(sz, true)) .map(Box::new), } } } pub enum Mode { Normal, Raw, } impl Terminal { pub fn scroll(&mut self, rows: f32) { let Some(vo) = self.view_o.as_mut() else { return; }; if rows < 0.0 { let rows = rows.ceil().abs() as usize; *vo = (*vo + rows).min(self.cells.row); } else { let rows = rows.floor() as usize; *vo = vo.saturating_sub(rows); } } fn decsc(&mut self) { self.saved_cursor = self.cursor; } pub fn resize(&mut self, (c, r): (u16, u16)) { self.cells.resize((c, r)); self.alternate.as_mut().map(|x| x.resize((c, r))); self.cursor = (self.cursor.0.min(c - 2), self.cursor.1.min(r - 2)); self.view_o.as_mut().map(|x| *x = self.cells.row); println!("successful resize {c} {r} {:?}", self.cursor) } fn decrc(&mut self) { self.cursor = self.saved_cursor; } fn clear(&mut self) { self.cells.cells().fill(DCELL); } #[implicit_fn::implicit_fn] pub fn rx(&mut self, x: u8, pty: BorrowedFd<'_>) { match self.p.parse_byte(x) { Continue => {} Char(x) => { if self.cursor.0 >= self.cells.c() { println!("overflow"); self.cursor.0 = 1; self.cursor.1 += 1; } while self.cursor.1 >= self.cells.r() { // println!("newline"); self.cursor.1 -= 1; self.cells.grow(1); if let Some(vo) = self.view_o.as_mut() && *vo + 1 == self.cells.row as usize { *vo += 1; } } let w = self.cells.c(); let c = self.cells.get_at(self.cursor); c.letter = Some(x); c.style = self.style; // eprintln!( // "@ {:?} (mx {w}) c={}", // self.cursor, // c.letter.unwrap() // ); self.cursor.0 += 1; } Control(ControlFunction { start: b'', params: [], .. }) => { self.cursor.0 -= 1; } Control(ControlFunction { start: b'[', params: [], end: b'm', .. }) => { self.style = DSTYLE; } Control(ControlFunction { start: b'[', params, end: b'm', .. }) => { let input: Vec = params.iter().map(_.value_or(0)).collect(); for &action in parse_sgr() .parse(&input) .into_result() .iter() .flatten() { use StyleAction::*; match action { Reset => self.style = DSTYLE, SetFg(c) => self.style.fg = c, SetBg(c) => self.style.bg = c, ModeSet(1) => self.style.flags |= BOLD, ModeSet(2) => self.style.flags |= DIM, ModeSet(3) => self.style.flags |= ITALIC, ModeSet(4) => self.style.flags |= UNDERLINE, ModeSet(7) => self.style.flags |= INVERT, ModeSet(9) => self.style.flags |= STRIKETHROUGH, ModeSet(22) => self.style.flags &= !(BOLD | DIM), _ => {} } } } Control(ControlFunction { start: b'[', params: [p], end: b'A', .. }) => { self.cursor.1 -= p.value_or(1); } Control(ControlFunction { start: b'[', params: [p], end: b'B', .. }) => { self.cursor.1 += p.value_or(1); } Control(ControlFunction { start: b'[', params: [p], end: b'C', .. }) => { self.cursor.0 += p.value_or(1); } Control(ControlFunction { start: b'[', params: [p], end: b'D', .. }) => { self.cursor.0 = (self.cursor.0.saturating_sub(p.value_or(1))).max(1); } Control(ControlFunction { start: b'[', params: [Value(y), Value(x)], end: b'H', .. }) => { self.cursor = (*x, *y); } Control(ControlFunction { start: b'[', params: [Default], end: b'H', .. }) => { self.cursor = (1, 1); } Control(ControlFunction { start: b'[', params: [Value(y)], end: b'd', .. }) => { self.cursor.1 = *y; } Control(ControlFunction { start: b'[', params: [Value(x)], end: b'G', .. }) => { self.cursor.0 = *x; } Control(ControlFunction { start: b'[', params: [x], end: b'J', .. }) => { for row in match x.value_or(0) { 0 => self.cursor.1 + 1..self.cells.r(), 1 => 1..self.cursor.1, 2 => 1..self.cells.r(), 3 => 0..0, _ => unreachable!(), } { self.cells.row(row).fill(DCELL); } } Control(ControlFunction { start: b'[', params: [Default], end: b'K', .. }) => { self.cells.past(self.cursor).fill(DCELL); } Control(ControlFunction { start: b'[', params: [x], end: b'M', .. }) => { let x = x.value_or(1); self.cells.grow(x as _); } Control(ControlFunction { start: b'[', params: [x], end: b'L', .. }) => { let x = x.value_or(1); self.cells.insert_lines(x as _, self.cursor.1); } Control(ControlFunction { start: b'[', params: [Value(6)], end: b'n', .. }) => { super::write( pty, format!("\x1b[{};{}R", self.cursor.1, self.cursor.0) .as_bytes(), ) .unwrap(); } Control(ControlFunction { start: b'[', params: [p], end: b'@', .. }) => { let count = p.value_or(1); self.cells.insert_chars(count, self.cursor); } Control(ControlFunction { start: b'[', params: [p], end: b'P', .. }) => { let count = p.value_or(1); self.cells.delete_chars(count, self.cursor); } //decstbm Control(ControlFunction { start: b'[', params: v, end: b'r', .. }) => { self.cells.margin = match v { [t, b] => (t.value_or(1), b.value_or(self.cells.r())), _ => (1, self.cells.r()), }; self.cursor = (1, 1); assert!(self.cells.margin.0 < self.cells.margin.1); } Control(ControlFunction { start: b'[', params: [x], end: b'X', .. }) => { let x = x.value_or(1); for cell in self.cells.row(self.cursor.1).iter_mut().take(x as _) { *cell = DCELL; } } Control(ControlFunction { start: b'\r', params: [], .. }) => { self.cursor.0 = 1; } Control(ControlFunction { start: b'\n', params: [], .. }) => { self.cursor.1 += 1; } Control( ControlFunction { start: b'\x1b', params: [], end: b'7', .. } | ControlFunction { start: b'[', params: [], end: b's', .. }, ) => { self.decsc(); } Control( ControlFunction { start: b'\x1b', params: [], end: b'8', .. } | ControlFunction { start: b'[', params: [], end: b'u', .. }, ) => { self.decrc(); } Control(ControlFunction { start: b'[', params: [y, x], end: b'f', .. }) => { self.cursor = (x.value_or(1) as _, y.value_or(1)); } Control(ControlFunction { start: b'[', params: [Value(n)], end: b'h' | b'l', .. }) => match n { 2004 => {} 1047 if let Some(mut alt) = self.alternate.take() => { alt.clear(); std::mem::swap(self, &mut *alt); self.alternate = Some(alt); } 1048 => self.decsc(), 1049 if let Some(mut alt) = self.alternate.take() => { alt.clear(); self.decsc(); std::mem::swap(self, &mut *alt); self.decrc(); self.alternate = Some(alt); } _ => {} }, Control(ControlFunction { start: b'\x1b', params: [], bytes: [b'('], end: b'B', .. }) => {} Control(x) => if x.start == b'[' { println!( "CSI {:?} {}", x.params .iter() .map(|x| match x { ctlfun::Parameter::Default => "default".to_string(), ctlfun::Parameter::Value(x) => x.to_string(), }) .collect::>(), x.end as char, ) } else { println!( "{} {:?} {} {}", x.start as char, x.params .iter() .map(|x| match x { ctlfun::Parameter::Default => "default".to_string(), ctlfun::Parameter::Value(x) => x.to_string(), }) .collect::>(), String::from_utf8_lossy(&x.bytes), x.end as char, ); }, _ => unreachable!(), } } } #[derive(Clone, Copy, Debug)] enum StyleAction { Reset, ModeSet(u16), SetFg([u8; 3]), SetBg([u8; 3]), } pub const BOLD: u8 = 1; pub const DIM: u8 = 1 << 1; pub const ITALIC: u8 = 1 << 2; pub const INVERT: u8 = 1 << 3; pub const UNDERLINE: u8 = 1 << 4; pub const STRIKETHROUGH: u8 = 1 << 5; fn value<'a>( r: impl std::ops::RangeBounds, ) -> impl Parser<'a, &'a [u16], u16> { any().filter(move |x| r.contains(x)) } use chumsky::prelude::*; use crate::colors; #[implicit_fn::implicit_fn] fn parse_sgr<'a>() -> impl Parser<'a, &'a [u16], Vec> { use StyleAction::*; let color = choice(( // 5;n just(5) .ignore_then(any()) .map(|x: u16| colors::EIGHT[x.min(0xff) as usize]), just(2) .ignore_then(any().repeated().collect_exactly::<[u16; 3]>()) .map(_.map(|x| x.min(0xff) as u8)), )); choice(( just(0u16).to(Reset), value(1..10).map(ModeSet), value(30..=37).map(_ - 30).map(colors::four).map(SetFg), just(38).ignore_then(color.map(SetFg)), just(39).to(SetFg(colors::FOREGROUND)), value(40..=47).map(_ - 40).map(colors::four).map(SetBg), just(48).ignore_then(color.map(SetBg)), just(49).to(SetBg(colors::BACKGROUND)), value(90..=97).map(_ - 82).map(colors::four).map(SetFg), value(100..=107).map(_ - 92).map(colors::four).map(SetBg), )) .repeated() .collect() .labelled("style") }