From 372ef9cb776f7bdac6f4873f3e2c3ed348cec909 Mon Sep 17 00:00:00 2001 From: Michael Sippel Date: Wed, 15 Sep 2021 15:24:39 +0200 Subject: [PATCH] shell: use portable-pty crate instead of pty-rs --- nested/Cargo.toml | 2 + nested/src/list/editor.rs | 2 - nested/src/terminal/ansi_parser.rs | 292 +++++++++++++++++++++++++++++ nested/src/terminal/mod.rs | 1 + shell/Cargo.toml | 2 +- shell/src/expr.rs | 28 +++ shell/src/main.rs | 7 +- shell/src/process.rs | 71 +++---- 8 files changed, 358 insertions(+), 47 deletions(-) create mode 100644 nested/src/terminal/ansi_parser.rs create mode 100644 shell/src/expr.rs diff --git a/nested/Cargo.toml b/nested/Cargo.toml index 4db1541..0be90e1 100644 --- a/nested/Cargo.toml +++ b/nested/Cargo.toml @@ -8,6 +8,8 @@ version = "0.1.0" no_deadlocks = "*" cgmath = { version = "0.18.0", features = ["serde"] } termion = "1.5.5" +vte = "0.10.1" +ansi_colours = "1.0" signal-hook = "0.3.1" signal-hook-async-std = "0.2.0" serde = { version = "1.0", features = ["derive"] } diff --git a/nested/src/list/editor.rs b/nested/src/list/editor.rs index 736d27d..6113454 100644 --- a/nested/src/list/editor.rs +++ b/nested/src/list/editor.rs @@ -327,8 +327,6 @@ where ItemEditor: TerminalTreeEditor + ?Sized + Send + Sync + 'static, } ); } - } else { - self.goto_home(); } TreeNavResult::Continue } diff --git a/nested/src/terminal/ansi_parser.rs b/nested/src/terminal/ansi_parser.rs new file mode 100644 index 0000000..aadfb44 --- /dev/null +++ b/nested/src/terminal/ansi_parser.rs @@ -0,0 +1,292 @@ +use { + std::{ + sync::{Arc, RwLock}, + pin::Pin, + fs::File, + os::unix::io::FromRawFd + }, + std::io::Read, + //async_std::{io::{Read, ReadExt}}, + crate::{ + core::{InnerViewPort, OuterViewPort}, + terminal::{ + TerminalAtom, + TerminalStyle, + TerminalView + }, + index::buffer::IndexBuffer + }, + cgmath::Point2, + vte::{Params, Parser, Perform} +}; + +pub fn read_ansi_from(ansi_reader: &mut R, port: InnerViewPort) { + let mut statemachine = Parser::new(); + + let mut performer = PerfAtom { + cursor: Point2::new(0, 0), + style: TerminalStyle::default(), + term_width: 200, + + buf: IndexBuffer::new(port), + + colors: ColorPalett { + black: (1, 1, 1), + red: (222, 56, 43), + green: (0, 64, 0), + yellow: (255, 199, 6), + blue: (0, 111, 184), + magenta: (118, 38, 113), + cyan: (44, 181, 233), + white: (204, 204, 204) + } + }; + + let mut buf = [0; 2048]; + + loop { + match ansi_reader.read(&mut buf) { + Ok(0) => break, + Ok(n) => { + for byte in &buf[..n] { + statemachine.advance(&mut performer, *byte); + } + }, + Err(err) => { + println!("err: {}", err); + break; + }, + } + } +} + + +struct ColorPalett { + black: (u8, u8, u8), + red: (u8, u8, u8), + green: (u8, u8, u8), + yellow: (u8, u8, u8), + blue: (u8, u8, u8), + magenta: (u8, u8, u8), + cyan: (u8, u8, u8), + white: (u8, u8, u8) +} + +struct PerfAtom { + colors: ColorPalett, + term_width: i16, + + cursor: Point2, + style: TerminalStyle, + + buf: IndexBuffer, TerminalAtom>, +} + +impl PerfAtom { + fn write_atom(&mut self, pos: Point2, atom: Option) { + if let Some(a) = atom { + self.buf.insert(pos, a); + } else { + self.buf.remove(pos); + } + } +} + +impl Perform for PerfAtom { + fn print(&mut self, c: char) { + //eprintln!("[print] {:?}", c); + self.write_atom( + self.cursor, + Some(TerminalAtom::new(c, self.style)) + ); + + self.cursor.x += 1; + if self.cursor.x > self.term_width { + self.cursor.x = 0; + self.cursor.y += 1; + } + } + + fn execute(&mut self, byte: u8) { + //eprintln!("[execute] {:02x}", byte); + match byte { + b'\n' => { + self.cursor.x = 0; + self.cursor.y += 1; + }, + _ => {} + } + } + + fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { + eprintln!( + "[hook] params={:?}, intermediates={:?}, ignore={:?}, char={:?}", + params, intermediates, ignore, c + ); + } + + fn put(&mut self, byte: u8) { + eprintln!("[put] {:02x}", byte); + } + + fn unhook(&mut self) { + eprintln!("[unhook]"); + } + + fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { + eprintln!("[osc_dispatch] params={:?} bell_terminated={}", params, bell_terminated); + } + + fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { + eprintln!( + "[csi_dispatch] params={:#?}, intermediates={:?}, ignore={:?}, char={:?}", + params, intermediates, ignore, c + ); + + let mut piter = params.into_iter(); + + match c { + // Set SGR + 'm' => while let Some(n) = piter.next() { + match n[0] { + 0 => self.style = TerminalStyle::default(), + 1 => self.style = self.style.add(TerminalStyle::bold(true)), + 3 => self.style = self.style.add(TerminalStyle::italic(true)), + 4 => self.style = self.style.add(TerminalStyle::underline(true)), + + 30 => self.style = self.style.add(TerminalStyle::fg_color(self.colors.black)), + 40 => self.style = self.style.add(TerminalStyle::bg_color(self.colors.black)), + 31 => self.style = self.style.add(TerminalStyle::fg_color(self.colors.red)), + 41 => self.style = self.style.add(TerminalStyle::bg_color(self.colors.red)), + 32 => self.style = self.style.add(TerminalStyle::fg_color(self.colors.green)), + 42 => self.style = self.style.add(TerminalStyle::bg_color(self.colors.green)), + 33 => self.style = self.style.add(TerminalStyle::fg_color(self.colors.yellow)), + 43 => self.style = self.style.add(TerminalStyle::bg_color(self.colors.yellow)), + 34 => self.style = self.style.add(TerminalStyle::fg_color(self.colors.blue)), + 44 => self.style = self.style.add(TerminalStyle::bg_color(self.colors.blue)), + 35 => self.style = self.style.add(TerminalStyle::fg_color(self.colors.magenta)), + 45 => self.style = self.style.add(TerminalStyle::bg_color(self.colors.magenta)), + 36 => self.style = self.style.add(TerminalStyle::fg_color(self.colors.cyan)), + 46 => self.style = self.style.add(TerminalStyle::bg_color(self.colors.cyan)), + 37 => self.style = self.style.add(TerminalStyle::fg_color(self.colors.white)), + 47 => self.style = self.style.add(TerminalStyle::bg_color(self.colors.white)), + + 38 => { + let x = piter.next().unwrap(); + match x[0] { + 2 => { + let r = piter.next().unwrap(); + let g = piter.next().unwrap(); + let b = piter.next().unwrap(); + self.style = self.style.add(TerminalStyle::fg_color((r[0] as u8, g[0] as u8, b[30] as u8))) + }, + 5 => { + let v = piter.next().unwrap(); + self.style = self.style.add(TerminalStyle::fg_color(ansi_colours::rgb_from_ansi256(v[0] as u8))) + }, + _ => {} + } + }, + + 48 => { + let x = piter.next().unwrap(); + match x[0] { + 2 => { + let r = piter.next().unwrap(); + let g = piter.next().unwrap(); + let b = piter.next().unwrap(); + self.style = self.style.add(TerminalStyle::bg_color((r[0] as u8, g[0] as u8, b[30] as u8))) + }, + 5 => { + let v = piter.next().unwrap(); + self.style = self.style.add(TerminalStyle::bg_color(ansi_colours::rgb_from_ansi256(v[0] as u8))) + }, + _ => {} + } + }, + + _ => {} + } + }, + + 'H' => { + if let Some(y) = piter.next() { self.cursor.y = y[0] as i16 - 1 }; + if let Some(x) = piter.next() { self.cursor.x = x[0] as i16 - 1 }; + + eprintln!("cursor at {:?}", self.cursor); + }, + + 'A' => { self.cursor.y -= piter.next().unwrap()[0] as i16; } + 'B' => { self.cursor.y += piter.next().unwrap()[0] as i16; } + 'C' => { self.cursor.x += piter.next().unwrap()[0] as i16; } + 'D' => { self.cursor.x -= piter.next().unwrap()[0] as i16; } + 'E' => { + self.cursor.x = 0; + self.cursor.y += piter.next().unwrap()[0] as i16; + } + + 'J' => { + let x = piter.next().unwrap_or(&[0 as u16; 1]); + match x[0] { + 0 => { + + }, + 1 => { + + } + 2 => { + for y in 0 .. 100 { + for x in 0 .. self.term_width { + self.write_atom(Point2::new(x, y), None); + } + } + } + + // invalid + _ => {} + } + } + + 'K' => { + let x = piter.next().unwrap(); + match x[0] { + + // clear cursor until end + 0 => { + for x in self.cursor.x .. self.term_width { + self.write_atom(Point2::new(x, self.cursor.y), None); + } + }, + + // clear start until cursor + 1 => { + for x in 0 .. self.cursor.x { + self.write_atom(Point2::new(x, self.cursor.y), None); + } + }, + + // clear entire line + 2 => { + for x in 0 .. self.term_width { + self.write_atom(Point2::new(x, self.cursor.y), None); + } + }, + + // invalid + _ => {} + } + } + + _ => {} + } + } + + fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { + eprintln!( + "[esc_dispatch] intermediates={:?}, ignore={:?}, byte={:02x}", + intermediates, ignore, byte + ); + } +} + + diff --git a/nested/src/terminal/mod.rs b/nested/src/terminal/mod.rs index 7585677..45e4422 100644 --- a/nested/src/terminal/mod.rs +++ b/nested/src/terminal/mod.rs @@ -2,6 +2,7 @@ pub mod style; pub mod atom; pub mod terminal; pub mod compositor; +pub mod ansi_parser; pub use { style::{TerminalStyle}, diff --git a/shell/Cargo.toml b/shell/Cargo.toml index 2e94e51..d086995 100644 --- a/shell/Cargo.toml +++ b/shell/Cargo.toml @@ -9,7 +9,7 @@ cgmath = "*" termion = "*" bincode = "*" libc = "0.2.*" -tty = { git = "https://github.com/stemjail/tty-rs.git" } +portable-pty = "0.4.0" [dependencies.async-std] version = "1.9.0" diff --git a/shell/src/expr.rs b/shell/src/expr.rs new file mode 100644 index 0000000..c154a87 --- /dev/null +++ b/shell/src/expr.rs @@ -0,0 +1,28 @@ +use { + std::{ + sync::{Arc, RwLock} + } + nested::{ + core::TypeTerm, + } +}; + +struct ExprEditor { + editor: Arc>, + type_tag: TypeTerm +} + +impl TreeNav for ExprEditor { + +} + +impl TerminalEditor for ExprEditor { + fn get_term_view(&self) -> OuterViewPort { + + } + + fn handle_terminal_event(&mut self, event: &TerminalEvent) -> TerminalEditorResult { + + } +} + diff --git a/shell/src/main.rs b/shell/src/main.rs index e55e876..79a433e 100644 --- a/shell/src/main.rs +++ b/shell/src/main.rs @@ -1,5 +1,5 @@ -extern crate tty; +extern crate portable_pty; mod monstera; mod process; @@ -170,6 +170,7 @@ async fn main() { } TerminalEvent::Input(Event::Key(Key::Up)) => { process_launcher.up(); } TerminalEvent::Input(Event::Key(Key::Down)) => { + //process_launcher.dn(); if process_launcher.dn() == TreeNavResult::Continue { process_launcher.goto_home(); } @@ -183,12 +184,10 @@ async fn main() { TerminalEvent::Input(Event::Key(Key::Char('\n'))) => { let output_view = process_launcher.launch(); - let range = output_view.get_view().unwrap().range(); - let box_port = ViewPort::new(); let test_box = Arc::new(RwLock::new(AsciiBox { content: Some(output_view.map_item(|_,a| a.add_style_back(TerminalStyle::fg_color((230, 230, 230)))).get_view().unwrap()), - extent: range.end - range.start + Vector2::new(1,1) + extent: Vector2::new(120,30) })); box_port.inner().set_view(Some(test_box.clone() as Arc)); diff --git a/shell/src/process.rs b/shell/src/process.rs index c0225f1..25e4996 100644 --- a/shell/src/process.rs +++ b/shell/src/process.rs @@ -5,7 +5,6 @@ use { os::unix::io::{FromRawFd, AsRawFd}, }, std::sync::RwLock, - tty::{FileDesc, TtyServer}, termion::event::{Key, Event}, cgmath::Point2, nested::{ @@ -20,6 +19,7 @@ use { string_editor::CharEditor, } }; +use portable_pty::{CommandBuilder, PtySize, native_pty_system, PtySystem}; //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> @@ -96,10 +96,10 @@ impl ProcessLauncher { } } - pub fn launch(&mut self) -> OuterViewPort { + pub fn launch(&mut self) -> (OuterViewPort) { self.up(); self.up(); - + let mut strings = Vec::new(); let v = self.editor.get_data_port().get_view().unwrap(); @@ -109,49 +109,40 @@ impl ProcessLauncher { } if strings.len() > 0 { - let stdin = FileDesc::new(libc::STDIN_FILENO, false); - let mut server = match TtyServer::new(Some(&stdin)) { - Ok(s) => s, - Err(e) => { return make_label(&format!("Error TTY server: {}", e)); }, - }; + // Create a new pty + let mut pair = native_pty_system().openpty(PtySize { + rows: 30, + cols: 120, + // Not all systems support pixel_width, pixel_height, + // but it is good practice to set it to something + // that matches the size of the selected font. That + // is more complex than can be shown here in this + // brief example though! + pixel_width: 0, + pixel_height: 0, + }).unwrap(); - let mut cmd = std::process::Command::new(strings[0].as_str()); - cmd.args(&strings[1..]).stdin(std::process::Stdio::null()); + // Spawn a shell into the pty + let mut cmd = CommandBuilder::new(strings[0].as_str()); + cmd.args(&strings[1..]); - let process = match server.spawn(cmd) { - Ok(p) => p, - Err(e) => { return make_label(&format!("Failed to execute process: {}", e));}, - }; + if let Ok(child) = pair.slave.spawn_command(cmd) { + // Read and parse output from the pty with reader + let mut reader = pair.master.try_clone_reader().unwrap(); - if let Ok(mut term_view_proc) = std::process::Command::new("./target/release/ansi_parser") - .stdin(unsafe{ std::process::Stdio::from_raw_fd(server.get_master().as_raw_fd()) }) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::null()) - .spawn() - { - let mut term_view_bin = term_view_proc.stdout.unwrap(); - /* - //let mut term_view_bin = async_std::io::BufReader::new(unsafe { async_std::fs::File::from_raw_fd( term_view_proc.stdout.unwrap().as_raw_fd() ) } ); - //let mut tv_stream = async_bincode::AsyncBincodeReader::<_, (Point2, Option)>::from(term_view_bin); + // Send data to the pty by writing to the master + //writeln!(pair.master, "ls -l\r\n"); - async_std::task::spawn( - async move { - */ + let port = ViewPort::new(); + let p = port.inner(); + async_std::task::spawn_blocking( + move || { + nested::terminal::ansi_parser::read_ansi_from(&mut reader, p); + }); - let output_view_port = ViewPort::new(); - let mut output_buf = IndexBuffer::new(output_view_port.inner()); - - while let Ok((pos, atom)) = bincode::deserialize_from(&mut term_view_bin) { - if let Some(a) = atom { - output_buf.insert(pos, a); - } else { - output_buf.remove(pos); - } - } -// }); - output_view_port.outer() + port.into_outer() } else { - make_label("Failed to spawn ansi parser process") + make_label("invalid command") .map_item(|idx, a| a.add_style_back(TerminalStyle::fg_color((200,0,0)))) } } else {