small software-rendered rust tty
-rw-r--r--src/main.rs22
-rw-r--r--src/render.rs12
-rw-r--r--src/term.rs107
-rw-r--r--src/term/cells.rs86
4 files changed, 132 insertions, 95 deletions
diff --git a/src/main.rs b/src/main.rs
index 22f6c6f..944318e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -23,6 +23,8 @@ use ctlfun::TerminalInputParser;
use fimg::Image;
use minifb::{InputCallback, Key, WindowOptions};
use nix::pty::{ForkptyResult, forkpty};
+use nix::sys::wait::waitpid;
+use nix::unistd::Pid;
use render::FONT;
use term::*;
mod render;
@@ -30,7 +32,7 @@ mod term;
use libc::{
F_GETFL, F_SETFL, O_NONBLOCK, TIOCSWINSZ, fcntl, ioctl, winsize,
};
-fn spawn(shell: &str) -> Result<OwnedFd> {
+fn spawn(shell: &str) -> Result<(OwnedFd, Pid)> {
let x = unsafe { forkpty(None, None)? };
match x {
ForkptyResult::Child => {
@@ -49,7 +51,7 @@ fn spawn(shell: &str) -> Result<OwnedFd> {
0
)
};
- Ok(master)
+ Ok((master, child))
}
}
}
@@ -95,7 +97,7 @@ fn main() -> Result<()> {
w.set_input_callback(Box::new(KeyPress(ktx)));
w.update();
- let pty = spawn("bash")?;
+ let (pty, pid) = spawn("bash")?;
let pty1 = pty.try_clone()?;
let pty2 = pty.try_clone()?;
@@ -184,13 +186,21 @@ fn main() -> Result<()> {
sleep(Duration::from_millis(10))
}
});
+ std::thread::spawn(move || {
+ loop {
+ match waitpid(pid, None).unwrap() {
+ nix::sys::wait::WaitStatus::Exited(..) => exit(0),
+ _ => (),
+ }
+ }
+ });
sleep(Duration::from_millis(100));
w.update();
let ppem = 20.0;
let (fw, fh) = render::dims(&FONT, ppem);
- let cols = (w.get_size().0 as f32 / fw).floor() as u16 + 1;
- let rows = (w.get_size().1 as f32 / fh).floor() as u16;
+ let cols = (w.get_size().0 as f32 / fw).floor() as u16 - 1;
+ let rows = (w.get_size().1 as f32 / fh).floor() as u16 - 1;
dbg!(rows, cols);
let mut t = Terminal::new((cols, rows), false);
unsafe {
@@ -216,7 +226,7 @@ fn main() -> Result<()> {
t.rx(char);
}
}
- let i = render::render(&t, w.get_size(), ppem);
+ let i = render::render(&mut t, w.get_size(), ppem);
let x = Image::<Box<[u32]>, 1>::from(i.as_ref());
w.update_with_buffer(x.buffer(), w.get_size().0, w.get_size().1)?;
}
diff --git a/src/render.rs b/src/render.rs
index 6ab9dc5..845f2d2 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -10,19 +10,15 @@ use crate::colors;
#[implicit_fn::implicit_fn]
pub fn render(
- x: &super::Terminal,
+ x: &mut super::Terminal,
(w, h): (usize, usize),
ppem: f32,
) -> Image<Box<[u8]>, 3> {
let m = FONT.metrics(&[]);
let sz = ppem * (m.max_width / m.units_per_em as f32);
let mut i = Image::build(w as _, h as _).fill(colors::BACKGROUND);
- for (col, k) in x.cells[x.size.0 as usize * x.row..]
- .chunks_exact(x.size.0 as _)
- .zip(0..)
- .skip(1)
- {
- for (&(mut cell), j) in col.iter().skip(2).zip(0..) {
+ for (col, k) in x.cells.rows().zip(1..) {
+ for (&(mut cell), j) in col.iter().zip(1..) {
if cell.style.flags & crate::term::INVERT != 0 {
std::mem::swap(&mut cell.style.bg, &mut cell.style.color);
}
@@ -123,7 +119,7 @@ pub fn render(
unsafe {
i.as_mut().overlay_at(
&cell,
- 4 + ((x.cursor.0 - 1) as f32 * sz) as u32,
+ 4 + ((x.cursor.0 + 1) as f32 * sz) as u32,
(x.cursor.1 as f32 * (ppem * 1.25)) as u32 - 20,
)
};
diff --git a/src/term.rs b/src/term.rs
index 3eaa3e3..aaf1cec 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -1,6 +1,7 @@
use std::iter::repeat_n;
use std::ops::Not;
-
+mod cells;
+use cells::*;
use ctlfun::Parameter::*;
use ctlfun::TerminalInput::*;
use ctlfun::{ControlFunction, TerminalInputParser};
@@ -9,10 +10,8 @@ pub struct Terminal {
pub style: Style,
pub cursor: (u16, u16),
pub saved_cursor: (u16, u16),
- pub size: (u16, u16),
- pub cells: Vec<Cell>,
- pub row: usize,
+ pub cells: Cells,
pub p: TerminalInputParser,
pub mode: Mode,
@@ -21,17 +20,12 @@ pub struct Terminal {
use std::default::Default::default;
impl Terminal {
- pub fn new(sz @ (c, r): (u16, u16), alt: bool) -> Self {
+ pub fn new(sz: (u16, u16), alt: bool) -> Self {
Self {
style: default(),
saved_cursor: (1, 1),
cursor: (1, 1),
- size: (c + 1, r + 1),
- row: 0,
- cells: vec![
- Cell::default();
- (c as usize + 1) * (r as usize + 1)
- ],
+ cells: Cells::new(sz),
p: default(),
mode: Mode::Normal,
@@ -47,29 +41,6 @@ pub enum Mode {
Raw,
}
-#[derive(Clone, Copy)]
-pub struct Style {
- pub bg: [u8; 3],
- pub color: [u8; 3],
- pub flags: u8,
-}
-use crate::colors;
-impl std::default::Default for Style {
- fn default() -> Self {
- Self {
- bg: colors::BACKGROUND,
- flags: 0,
- color: colors::FOREGROUND,
- }
- }
-}
-
-#[derive(Clone, Copy, Default)]
-pub struct Cell {
- pub style: Style,
- pub letter: Option<char>,
-}
-
impl Terminal {
fn decsc(&mut self) {
self.saved_cursor = self.cursor;
@@ -78,15 +49,7 @@ impl Terminal {
self.cursor = self.saved_cursor;
}
fn clear(&mut self) {
- self.cells[self.row * self.size.0 as usize..]
- .fill(Cell::default());
- }
- fn grow(&mut self, by: usize) {
- self.row += by;
- self.cells.extend(repeat_n(
- Cell::default(),
- (by * (self.size.0) as usize) as usize,
- ));
+ self.cells.cells().fill(Cell::default());
}
#[implicit_fn::implicit_fn]
pub fn rx(&mut self, x: u8) {
@@ -94,34 +57,25 @@ impl Terminal {
Continue => {}
Char(x) => {
self.cursor.0 += 1;
- if self.cursor.0 == self.size.0 {
+ if self.cursor.0 >= self.cells.c() {
println!("overflow");
self.cursor.0 = 1;
self.cursor.1 += 1;
}
- while self.cursor.1 >= self.size.1 {
+ while self.cursor.1 >= self.cells.r() {
println!("newline");
self.cursor.1 -= 1;
- self.grow(1);
+ self.cells.grow(1);
}
- assert!(
- self.cells.len()
- == self.row * (self.size.0 as usize)
- + (self.size.0 as usize)
- * (self.size.1 as usize)
- );
- // assert!(
- // self.cells[self.row * self.size.0 as usize..].len()
- // == self.size.0 as usize * self.size.1 as usize
- // );
- // dbg!(self.cursor);
- let c = &mut self.cells[self.row * self.size.0 as usize..]
- // y*w+x
- [(self.cursor.1 * self.size.0 + self.cursor.0)
- as usize];
+ let w = self.cells.c();
+ let c = self.cells.get_at(self.cursor);
c.letter = Some(x);
c.style = self.style;
- dbg!(x);
+ eprintln!(
+ "@ {} (mx {w}) c={}",
+ self.cursor.0,
+ c.letter.unwrap()
+ );
}
Control(ControlFunction {
start: b'',
@@ -239,18 +193,13 @@ impl Terminal {
..
}) => {
for row in match x.value_or(0) {
- 0 => self.cursor.1 + 1..self.size.1,
- 1 => 0..self.cursor.1,
- 2 => 0..self.size.1,
+ 0 => self.cursor.1 + 1..self.cells.r(),
+ 1 => 1..self.cursor.1,
+ 2 => 1..self.cells.r(),
3 => 0..0,
_ => unreachable!(),
} {
- for cell in self.cells
- [self.row * self.size.0 as usize..]
- [row as usize * self.size.0 as usize..]
- .iter_mut()
- .take(self.size.0 as _)
- {
+ for cell in self.cells.row(row) {
*cell = Cell::default();
}
}
@@ -261,12 +210,7 @@ impl Terminal {
end: b'K',
..
}) => {
- for cell in &mut self.cells
- [self.row * self.size.0 as usize..]
- [(self.cursor.1 * self.size.0 + self.cursor.0) as usize
- ..(self.cursor.1 * self.size.0 + self.size.0)
- as usize]
- {
+ for cell in self.cells.past(self.cursor) {
*cell = Cell::default();
}
}
@@ -277,7 +221,7 @@ impl Terminal {
..
}) => {
let x = x.value_or(1);
- self.grow(x as _);
+ self.cells.grow(x as _);
}
Control(ControlFunction {
start: b'[',
@@ -286,12 +230,11 @@ impl Terminal {
..
}) => {
let x = x.value_or(1);
- for cell in &mut self.cells
- [self.row * self.size.0 as usize..][..x as usize]
+ for cell in
+ self.cells.row(self.cursor.1).iter_mut().take(x as _)
{
*cell = Cell::default();
}
- self.grow(x as _);
}
Control(ControlFunction {
@@ -426,6 +369,8 @@ fn value<'a>(
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<StyleAction>> {
use StyleAction::*;
diff --git a/src/term/cells.rs b/src/term/cells.rs
new file mode 100644
index 0000000..06f0415
--- /dev/null
+++ b/src/term/cells.rs
@@ -0,0 +1,86 @@
+// one-indexed cell array
+pub struct Cells {
+ pub size: (u16, u16),
+ pub cells: Vec<Cell>,
+ pub row: u16,
+}
+#[derive(Clone, Copy)]
+pub struct Style {
+ pub bg: [u8; 3],
+ pub color: [u8; 3],
+ pub flags: u8,
+}
+use std::iter::repeat_n;
+
+use crate::colors;
+impl std::default::Default for Style {
+ fn default() -> Self {
+ Self {
+ bg: colors::BACKGROUND,
+ flags: 0,
+ color: colors::FOREGROUND,
+ }
+ }
+}
+
+#[derive(Clone, Copy, Default)]
+pub struct Cell {
+ pub style: Style,
+ pub letter: Option<char>,
+}
+
+impl Cells {
+ pub fn new((c, r): (u16, u16)) -> Self {
+ Self {
+ size: (c + 1, r + 1),
+ cells: vec![
+ Cell::default();
+ (c as usize + 1) * (r as usize + 1)
+ ],
+ row: 0,
+ }
+ }
+ pub fn cells(&mut self) -> &mut [Cell] {
+ assert!(
+ self.cells.len()
+ == self.row as usize * (self.size.0 as usize)
+ + (self.size.0 as usize) * (self.size.1 as usize)
+ );
+ assert!(
+ self.cells[self.row as usize * self.size.0 as usize..].len()
+ == self.size.0 as usize * self.size.1 as usize
+ );
+ &mut self.cells[self.row as usize * self.size.0 as usize..]
+ }
+ pub fn r(&mut self) -> u16 {
+ self.size.1
+ }
+ pub fn c(&mut self) -> u16 {
+ self.size.0
+ }
+ #[track_caller]
+ pub fn get_at(&mut self, (x, y): (u16, u16)) -> &mut Cell {
+ let w = self.c();
+ assert!(x < self.c() && y < self.r(), "out of bounds");
+ &mut self.cells()[((y - 1) * w + x - 1) as usize]
+ }
+ pub fn rows(&mut self) -> impl Iterator<Item = &mut [Cell]> {
+ let w = self.c();
+ self.cells().chunks_exact_mut(w as _)
+ }
+ pub fn row(&mut self, row: u16) -> &mut [Cell] {
+ let w = self.c();
+ &mut self.cells()
+ [(row as usize - 1) * w as usize..row as usize * w as usize]
+ }
+ pub fn past(&mut self, (x, row): (u16, u16)) -> &mut [Cell] {
+ &mut self.rows().nth(row as usize - 1).unwrap()[x as usize - 1..]
+ }
+ pub fn grow(&mut self, by: u16) {
+ self.row += by;
+ self.cells.extend(repeat_n(
+ Cell::default(),
+ by as usize * (self.size.0) as usize,
+ ));
+ }
+}