From c9c6958a62970e8c416d6c3a3aa4c12c3fac5880 Mon Sep 17 00:00:00 2001 From: Michael Sippel Date: Thu, 11 Nov 2021 22:29:37 +0100 Subject: [PATCH] process editor: write keyboard input to pty --- nested/src/singleton/buffer.rs | 22 +++-- nested/src/terminal/ansi_parser.rs | 4 +- shell/src/main.rs | 145 +++++++++++++++++------------ shell/src/process.rs | 128 +++++++++++++++++++------ shell/src/pty.rs | 81 ++++++++++++---- 5 files changed, 263 insertions(+), 117 deletions(-) diff --git a/nested/src/singleton/buffer.rs b/nested/src/singleton/buffer.rs index 1ff3490..090bf19 100644 --- a/nested/src/singleton/buffer.rs +++ b/nested/src/singleton/buffer.rs @@ -14,15 +14,15 @@ use { } }; -pub struct SingletonBufferView(Arc>); +pub struct SingletonBufferView(Arc>); impl View for SingletonBufferView -where T: Clone + Eq + Send + Sync + 'static { +where T: Clone + Send + Sync + 'static { type Msg = (); } impl SingletonView for SingletonBufferView -where T: Clone + Eq + Send + Sync + 'static { +where T: Clone + Send + Sync + 'static { type Item = T; fn get(&self) -> Self::Item { @@ -32,13 +32,13 @@ where T: Clone + Eq + Send + Sync + 'static { #[derive(Clone)] pub struct SingletonBuffer -where T: Clone + Eq + Send + Sync + 'static { +where T: Clone + Send + Sync + 'static { value: Arc>, cast: Arc>>> } impl SingletonBuffer -where T: Clone + Eq + Send + Sync + 'static { +where T: Clone + Send + Sync + 'static { pub fn new( value: T, port: InnerViewPort> @@ -56,6 +56,16 @@ where T: Clone + Eq + Send + Sync + 'static { self.value.read().unwrap().clone() } + pub fn set(&mut self, new_value: T) { + let mut v = self.value.write().unwrap(); + *v = new_value; + drop(v); + self.cast.notify(&()); + } +} +/* +impl SingletonBuffer +where T: Clone + Eq + Send + Sync + 'static { pub fn set(&mut self, new_value: T) { let mut v = self.value.write().unwrap(); if *v != new_value { @@ -65,6 +75,6 @@ where T: Clone + Eq + Send + Sync + 'static { } } } - +*/ // TODO: impl Deref & DerefMut diff --git a/nested/src/terminal/ansi_parser.rs b/nested/src/terminal/ansi_parser.rs index 7ad4f43..60d1c48 100644 --- a/nested/src/terminal/ansi_parser.rs +++ b/nested/src/terminal/ansi_parser.rs @@ -27,7 +27,7 @@ pub fn read_ansi_from(ansi_reader: &mut R, port: InnerViewPort< cursor: Point2::new(0, 0), style: TerminalStyle::default(), invert: false, - term_width: 80, + term_width: 120, cursor_save: Point2::new(0, 0), @@ -159,7 +159,7 @@ impl Perform for PerfAtom { b'\t' => self.horizontal_tab(), 0x8 => self.backspace(), _ => { - eprintln!("unhandled execute byte {:02x}", byte); + //eprintln!("unhandled execute byte {:02x}", byte); } } } diff --git a/shell/src/main.rs b/shell/src/main.rs index 6b84dcb..93e0a5d 100644 --- a/shell/src/main.rs +++ b/shell/src/main.rs @@ -34,7 +34,9 @@ use{ TerminalEvent, make_label, TerminalView, - TerminalEditor}, + TerminalEditor, + TerminalEditorResult + }, string_editor::{StringEditor}, tree_nav::{TreeNav, TreeNavResult, TreeCursor, TerminalTreeEditor}, list::{SExprView, ListCursorMode, ListEditor, ListEditorStyle}, @@ -82,7 +84,11 @@ impl IndexView> for Plot { if let Some(cur_val) = self.data.get(&(pt.x as usize)) { if cur_val <= self.limit { if pt.y == (self.limit - cur_val) as i16 { - return Some(TerminalAtom::from('*')); + return Some(TerminalAtom::from( + if cur_val < 4 { 'o' } + else if cur_val < 8 { 'O' } + else { '*' } + )); } } if pt.x > 0 { @@ -98,7 +104,7 @@ impl IndexView> for Plot { pt.y > (self.limit - cur_val) as i16 ) { - return Some(TerminalAtom::from('|')); + return Some(TerminalAtom::from('.')); } } } @@ -272,66 +278,8 @@ async fn main() { } } ); - + loop { - let ev = term.next_event().await; - match ev { - TerminalEvent::Resize(new_size) => { - cur_size.set(new_size); - term_port.inner().get_broadcast().notify(&IndexArea::Full); - } - TerminalEvent::Input(Event::Key(Key::Ctrl('c'))) | - TerminalEvent::Input(Event::Key(Key::Ctrl('g'))) | - TerminalEvent::Input(Event::Key(Key::Ctrl('d'))) => break, - - TerminalEvent::Input(Event::Key(Key::Left)) => { - process_list_editor.pxev(); - } - TerminalEvent::Input(Event::Key(Key::Right)) => { - process_list_editor.nexd(); - } - TerminalEvent::Input(Event::Key(Key::Up)) => { - if process_list_editor.up() == TreeNavResult::Exit { - process_list_editor.dn(); - process_list_editor.goto_home(); - } - } - TerminalEvent::Input(Event::Key(Key::Down)) => { - if process_list_editor.dn() == TreeNavResult::Continue { - process_list_editor.goto_home(); - } - } - TerminalEvent::Input(Event::Key(Key::Home)) => { - process_list_editor.goto_home(); - } - TerminalEvent::Input(Event::Key(Key::End)) => { - process_list_editor.goto_end(); - } - TerminalEvent::Input(Event::Key(Key::Char('\n'))) => { - if let Some(launcher) = process_list_editor.get_item() { - launcher.write().unwrap().launch_pty2(); - } - } - - ev => { - if process_list_editor.get_cursor().leaf_mode == ListCursorMode::Select { - match ev { - TerminalEvent::Input(Event::Key(Key::Char('l'))) => { process_list_editor.up(); }, - TerminalEvent::Input(Event::Key(Key::Char('a'))) => { process_list_editor.dn(); }, - TerminalEvent::Input(Event::Key(Key::Char('i'))) => { process_list_editor.pxev(); }, - TerminalEvent::Input(Event::Key(Key::Char('e'))) => { process_list_editor.nexd(); }, - TerminalEvent::Input(Event::Key(Key::Char('u'))) => { process_list_editor.goto_home(); }, - TerminalEvent::Input(Event::Key(Key::Char('o'))) => { process_list_editor.goto_end(); }, - _ => { - process_list_editor.handle_terminal_event(&ev); - } - } - } else { - process_list_editor.handle_terminal_event(&ev); - } - } - } - status_chars.clear(); let cur = process_list_editor.get_cursor(); @@ -360,6 +308,79 @@ async fn main() { status_chars.push(TerminalAtom::new(c, TerminalStyle::fg_color((200, 200, 20)))); } } + + let ev = term.next_event().await; + + if let TerminalEvent::Resize(new_size) = ev { + cur_size.set(new_size); + term_port.inner().get_broadcast().notify(&IndexArea::Full); + continue; + } + + if let Some(process_editor) = process_list_editor.get_item() { + let mut pe = process_editor.write().unwrap(); + if pe.is_captured() { + if let TerminalEditorResult::Exit = pe.handle_terminal_event(&ev) { + drop(pe); + process_list_editor.up(); + process_list_editor.nexd(); + } + continue; + } + } + + match ev { + TerminalEvent::Input(Event::Key(Key::Ctrl('d'))) => break, + TerminalEvent::Input(Event::Key(Key::Ctrl('l'))) => { + process_list_editor.goto(TreeCursor { + leaf_mode: ListCursorMode::Insert, + tree_addr: vec![ 0 ] + }); + process_list_editor.data.clear(); + }, + TerminalEvent::Input(Event::Key(Key::Left)) => { + process_list_editor.pxev(); + } + TerminalEvent::Input(Event::Key(Key::Right)) => { + process_list_editor.nexd(); + } + TerminalEvent::Input(Event::Key(Key::Up)) => { + if process_list_editor.up() == TreeNavResult::Exit { + process_list_editor.dn(); + process_list_editor.goto_home(); + } + } + TerminalEvent::Input(Event::Key(Key::Down)) => { + if process_list_editor.dn() == TreeNavResult::Continue { + process_list_editor.goto_home(); + } + } + TerminalEvent::Input(Event::Key(Key::Home)) => { + process_list_editor.goto_home(); + } + TerminalEvent::Input(Event::Key(Key::End)) => { + process_list_editor.goto_end(); + } + ev => { + if process_list_editor.get_cursor().leaf_mode == ListCursorMode::Select { + match ev { + TerminalEvent::Input(Event::Key(Key::Char('l'))) => { process_list_editor.up(); }, + TerminalEvent::Input(Event::Key(Key::Char('a'))) => { process_list_editor.dn(); }, + TerminalEvent::Input(Event::Key(Key::Char('i'))) => { process_list_editor.pxev(); }, + TerminalEvent::Input(Event::Key(Key::Char('e'))) => { process_list_editor.nexd(); }, + TerminalEvent::Input(Event::Key(Key::Char('u'))) => { process_list_editor.goto_home(); }, + TerminalEvent::Input(Event::Key(Key::Char('o'))) => { process_list_editor.goto_end(); }, + _ => { + process_list_editor.handle_terminal_event(&ev); + } + } + } else { + if let TerminalEditorResult::Exit = process_list_editor.handle_terminal_event(&ev) { + process_list_editor.nexd(); + } + } + } + } } drop(term); diff --git a/shell/src/process.rs b/shell/src/process.rs index 70b3d6c..13e0219 100644 --- a/shell/src/process.rs +++ b/shell/src/process.rs @@ -15,7 +15,7 @@ use { vec::VecBuffer, terminal::{TerminalAtom, TerminalStyle, TerminalView, TerminalEvent, TerminalEditor, TerminalEditorResult, make_label}, tree_nav::{TreeNav, TreeNavResult, TerminalTreeEditor, TreeCursor}, - list::{ListEditor, ListEditorStyle, sexpr::ListDecoration}, + list::{ListCursorMode, ListEditor, ListEditorStyle, sexpr::ListDecoration}, string_editor::CharEditor, } }; @@ -74,9 +74,11 @@ pub struct ProcessLauncher { >, pty: Option, ptybox: Arc>, + suspended: bool, pty_port: ViewPort, - + status_port: ViewPort>>, + comp_port: ViewPort, compositor: Arc> } @@ -84,6 +86,7 @@ pub struct ProcessLauncher { impl ProcessLauncher { pub fn new() -> Self { let pty_port = ViewPort::new(); + let status_port = ViewPort::new(); let comp_port = ViewPort::new(); let box_port = ViewPort::::new(); let compositor = nested::terminal::TerminalCompositor::new(comp_port.inner()); @@ -125,21 +128,16 @@ impl ProcessLauncher { pty_port.outer() .map_item(|_,a:&TerminalAtom| a.add_style_back(TerminalStyle::fg_color((230, 230, 230)))), box_port.inner() - ), + ), + suspended: false, pty_port, + status_port, comp_port, compositor } } - pub fn launch_pty2(&mut self) { - self.launch_pty(self.pty_port.inner()); - } - - pub fn launch_pty(&mut self, port: InnerViewPort) -> Option { - self.up(); - self.up(); - + pub fn launch_pty(&mut self) { let mut strings = Vec::new(); let v = self.cmd_editor.get_data_port().get_view().unwrap(); @@ -153,11 +151,18 @@ impl ProcessLauncher { let mut cmd = crate::pty::CommandBuilder::new(strings[0].as_str()); cmd.args(&strings[1..]); - crate::pty::PTY::new(cmd, port) - } else { - None + self.cmd_editor.goto(TreeCursor { + leaf_mode: ListCursorMode::Insert, + tree_addr: vec![] + }); + + self.pty = crate::pty::PTY::new(cmd, self.pty_port.inner(), self.status_port.inner()); } } + + pub fn is_captured(&self) -> bool { + self.pty.is_some() && !self.suspended + } } impl TerminalEditor for ProcessLauncher { @@ -166,27 +171,94 @@ impl TerminalEditor for ProcessLauncher { } fn handle_terminal_event(&mut self, event: &TerminalEvent) -> TerminalEditorResult { + + // todo: move to observer of status view + if let Some(status) = self.status_port.outer().get_view().get() { + self.pty = None; + self.suspended = false; + } + match event { - TerminalEvent::Input(Event::Key(Key::Char('\n'))) => { - // launch command - self.cmd_editor.up(); - self.cmd_editor.up(); + TerminalEvent::Input(Event::Key(Key::Ctrl('c'))) => { + self.pty = None; + self.suspended = false; + self.cmd_editor.goto(TreeCursor { + leaf_mode: ListCursorMode::Insert, + tree_addr: vec![] + }); + TerminalEditorResult::Exit + }, + TerminalEvent::Input(Event::Key(Key::Ctrl('z'))) => { + self.suspended = true; + self.cmd_editor.goto(TreeCursor { + leaf_mode: ListCursorMode::Insert, + tree_addr: vec![] + }); TerminalEditorResult::Exit } - - event => self.cmd_editor.handle_terminal_event(event) + event => { + if let Some(pty) = self.pty.as_mut() { + pty.handle_terminal_event(event); + TerminalEditorResult::Continue + } else { + match event { + TerminalEvent::Input(Event::Key(Key::Char('\n'))) => { + // launch command + self.launch_pty(); + TerminalEditorResult::Continue + } + event => self.cmd_editor.handle_terminal_event(event) + } + } + } } } } impl TreeNav for ProcessLauncher { - fn get_cursor(&self) -> TreeCursor { self.cmd_editor.get_cursor() } - fn goto(&mut self, cur: TreeCursor) -> TreeNavResult { self.cmd_editor.goto(cur) } - fn goto_home(&mut self) -> TreeNavResult { self.cmd_editor.goto_home() } - fn goto_end(&mut self) -> TreeNavResult { self.cmd_editor.goto_end() } - fn pxev(&mut self) -> TreeNavResult { self.cmd_editor.pxev() } - fn nexd(&mut self) -> TreeNavResult { self.cmd_editor.nexd() } - fn up(&mut self) -> TreeNavResult { self.cmd_editor.up() } - fn dn(&mut self) -> TreeNavResult { self.cmd_editor.dn() } + fn get_cursor(&self) -> TreeCursor { + self.cmd_editor.get_cursor() + } + + fn goto(&mut self, cur: TreeCursor) -> TreeNavResult { + self.suspended = false; + if let Some(status) = self.status_port.outer().get_view().get() { + self.pty = None; + } + + if self.pty.is_none() { + self.cmd_editor.goto(cur) + } else { + self.cmd_editor.goto(TreeCursor { + leaf_mode: ListCursorMode::Select, + tree_addr: vec![] + }); + TreeNavResult::Continue + } + } + + fn goto_home(&mut self) -> TreeNavResult { + self.cmd_editor.goto_home() + } + + fn goto_end(&mut self) -> TreeNavResult { + self.cmd_editor.goto_end() + } + + fn pxev(&mut self) -> TreeNavResult { + self.cmd_editor.pxev() + } + + fn nexd(&mut self) -> TreeNavResult { + self.cmd_editor.nexd() + } + + fn up(&mut self) -> TreeNavResult { + self.cmd_editor.up() + } + + fn dn(&mut self) -> TreeNavResult { + self.cmd_editor.dn() + } } diff --git a/shell/src/pty.rs b/shell/src/pty.rs index 4a11c7b..b0b5f88 100644 --- a/shell/src/pty.rs +++ b/shell/src/pty.rs @@ -4,7 +4,8 @@ use { std::sync::Mutex, nested::{ core::{InnerViewPort}, - terminal::{TerminalView, TerminalEvent} + singleton::{SingletonView, SingletonBuffer}, + terminal::{TerminalView, TerminalEvent, TerminalEditorResult} } }; @@ -13,20 +14,20 @@ pub use portable_pty::CommandBuilder; //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> pub struct PTY { - master: Mutex>, - child: Box + master: Mutex> } impl PTY { pub fn new( cmd: portable_pty::CommandBuilder, - port: InnerViewPort + term_port: InnerViewPort, + status_port: InnerViewPort>> ) -> Option { // Create a new pty let mut pair = portable_pty::native_pty_system().openpty(portable_pty::PtySize { rows: 25, - cols: 80, + cols: 120, // Not all systems support pixel_width, pixel_height, // but it is good practice to set it to something @@ -37,37 +38,79 @@ impl PTY { pixel_height: 0, }).unwrap(); - if let Ok(child) = pair.slave.spawn_command(cmd) { + if let Ok(mut child) = pair.slave.spawn_command(cmd) { let mut reader = pair.master.try_clone_reader().unwrap(); async_std::task::spawn_blocking( move || { - nested::terminal::ansi_parser::read_ansi_from(&mut reader, port); + nested::terminal::ansi_parser::read_ansi_from(&mut reader, term_port); }); - + + async_std::task::spawn_blocking( + move || { + let mut status_buf = SingletonBuffer::new(None, status_port); + if let Ok(status) = child.wait() { + status_buf.set(Some(status)); + } + } + ); + Some(PTY { - master: Mutex::new(pair.master), - child + master: Mutex::new(pair.master) }) } else { None } } - pub fn get_status(&mut self) -> bool { - if let Ok(Some(status)) = self.child.try_wait() { - true - } else { - false - } - } - - pub fn handle_terminal_event(&mut self, event: &TerminalEvent) { + pub fn handle_terminal_event(&mut self, event: &TerminalEvent) -> TerminalEditorResult { match event { + TerminalEvent::Input(Event::Key(Key::Char('\n'))) => { + self.master.lock().unwrap().write(&[13]).unwrap(); + TerminalEditorResult::Continue + }, TerminalEvent::Input(Event::Key(Key::Char(c))) => { write!(self.master.lock().unwrap(), "{}", c); + TerminalEditorResult::Continue + }, + TerminalEvent::Input(Event::Key(Key::Esc)) => { + self.master.lock().unwrap().write(&[0x1b]).unwrap(); + TerminalEditorResult::Continue + } + TerminalEvent::Input(Event::Key(Key::Backspace)) => { + self.master.lock().unwrap().write(&[0x8]).unwrap(); + TerminalEditorResult::Continue + } + TerminalEvent::Input(Event::Key(Key::F(n))) => { + self.master.lock().unwrap().write(&[ + 0x1b, + 0x0a, + match n { + 11 => 133, + 12 => 134, + n => 58 + n + } + ]).unwrap(); + TerminalEditorResult::Continue + } + TerminalEvent::Input(Event::Key(Key::Up)) => { + self.master.lock().unwrap().write(&[0, b'\x1B', b'[', b'A']).unwrap(); + TerminalEditorResult::Continue + } + TerminalEvent::Input(Event::Key(Key::Down)) => { + self.master.lock().unwrap().write(&[0, b'\x1B', b'[', b'B']).unwrap(); + TerminalEditorResult::Continue + } + TerminalEvent::Input(Event::Key(Key::Right)) => { + self.master.lock().unwrap().write(&[0, b'\x1B', b'[', b'C']).unwrap(); + TerminalEditorResult::Continue + } + TerminalEvent::Input(Event::Key(Key::Left)) => { + self.master.lock().unwrap().write(&[0, b'\x1B', b'[', b'D']).unwrap(); + TerminalEditorResult::Continue } _ => { + TerminalEditorResult::Exit } } }