process editor: write keyboard input to pty

This commit is contained in:
Michael Sippel 2021-11-11 22:29:37 +01:00
parent b9e285acb4
commit c9c6958a62
Signed by: senvas
GPG key ID: F96CF119C34B64A6
5 changed files with 263 additions and 117 deletions

View file

@ -14,15 +14,15 @@ use {
} }
}; };
pub struct SingletonBufferView<T: Clone + Eq + Send + Sync + 'static>(Arc<RwLock<T>>); pub struct SingletonBufferView<T: Clone + Send + Sync + 'static>(Arc<RwLock<T>>);
impl<T> View for SingletonBufferView<T> impl<T> View for SingletonBufferView<T>
where T: Clone + Eq + Send + Sync + 'static { where T: Clone + Send + Sync + 'static {
type Msg = (); type Msg = ();
} }
impl<T> SingletonView for SingletonBufferView<T> impl<T> SingletonView for SingletonBufferView<T>
where T: Clone + Eq + Send + Sync + 'static { where T: Clone + Send + Sync + 'static {
type Item = T; type Item = T;
fn get(&self) -> Self::Item { fn get(&self) -> Self::Item {
@ -32,13 +32,13 @@ where T: Clone + Eq + Send + Sync + 'static {
#[derive(Clone)] #[derive(Clone)]
pub struct SingletonBuffer<T> pub struct SingletonBuffer<T>
where T: Clone + Eq + Send + Sync + 'static { where T: Clone + Send + Sync + 'static {
value: Arc<RwLock<T>>, value: Arc<RwLock<T>>,
cast: Arc<RwLock<ObserverBroadcast<dyn SingletonView<Item = T>>>> cast: Arc<RwLock<ObserverBroadcast<dyn SingletonView<Item = T>>>>
} }
impl<T> SingletonBuffer<T> impl<T> SingletonBuffer<T>
where T: Clone + Eq + Send + Sync + 'static { where T: Clone + Send + Sync + 'static {
pub fn new( pub fn new(
value: T, value: T,
port: InnerViewPort<dyn SingletonView<Item = T>> port: InnerViewPort<dyn SingletonView<Item = T>>
@ -56,6 +56,16 @@ where T: Clone + Eq + Send + Sync + 'static {
self.value.read().unwrap().clone() 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<T> SingletonBuffer<T>
where T: Clone + Eq + Send + Sync + 'static {
pub fn set(&mut self, new_value: T) { pub fn set(&mut self, new_value: T) {
let mut v = self.value.write().unwrap(); let mut v = self.value.write().unwrap();
if *v != new_value { if *v != new_value {
@ -65,6 +75,6 @@ where T: Clone + Eq + Send + Sync + 'static {
} }
} }
} }
*/
// TODO: impl Deref & DerefMut // TODO: impl Deref & DerefMut

View file

@ -27,7 +27,7 @@ pub fn read_ansi_from<R: Read + Unpin>(ansi_reader: &mut R, port: InnerViewPort<
cursor: Point2::new(0, 0), cursor: Point2::new(0, 0),
style: TerminalStyle::default(), style: TerminalStyle::default(),
invert: false, invert: false,
term_width: 80, term_width: 120,
cursor_save: Point2::new(0, 0), cursor_save: Point2::new(0, 0),
@ -159,7 +159,7 @@ impl Perform for PerfAtom {
b'\t' => self.horizontal_tab(), b'\t' => self.horizontal_tab(),
0x8 => self.backspace(), 0x8 => self.backspace(),
_ => { _ => {
eprintln!("unhandled execute byte {:02x}", byte); //eprintln!("unhandled execute byte {:02x}", byte);
} }
} }
} }

View file

@ -34,7 +34,9 @@ use{
TerminalEvent, TerminalEvent,
make_label, make_label,
TerminalView, TerminalView,
TerminalEditor}, TerminalEditor,
TerminalEditorResult
},
string_editor::{StringEditor}, string_editor::{StringEditor},
tree_nav::{TreeNav, TreeNavResult, TreeCursor, TerminalTreeEditor}, tree_nav::{TreeNav, TreeNavResult, TreeCursor, TerminalTreeEditor},
list::{SExprView, ListCursorMode, ListEditor, ListEditorStyle}, list::{SExprView, ListCursorMode, ListEditor, ListEditorStyle},
@ -82,7 +84,11 @@ impl IndexView<Point2<i16>> for Plot {
if let Some(cur_val) = self.data.get(&(pt.x as usize)) { if let Some(cur_val) = self.data.get(&(pt.x as usize)) {
if cur_val <= self.limit { if cur_val <= self.limit {
if pt.y == (self.limit - cur_val) as i16 { 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 { if pt.x > 0 {
@ -98,7 +104,7 @@ impl IndexView<Point2<i16>> for Plot {
pt.y > (self.limit - cur_val) as i16 pt.y > (self.limit - cur_val) as i16
) )
{ {
return Some(TerminalAtom::from('|')); return Some(TerminalAtom::from('.'));
} }
} }
} }
@ -274,64 +280,6 @@ async fn main() {
); );
loop { 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(); status_chars.clear();
let cur = process_list_editor.get_cursor(); 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)))); 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); drop(term);

View file

@ -15,7 +15,7 @@ use {
vec::VecBuffer, vec::VecBuffer,
terminal::{TerminalAtom, TerminalStyle, TerminalView, TerminalEvent, TerminalEditor, TerminalEditorResult, make_label}, terminal::{TerminalAtom, TerminalStyle, TerminalView, TerminalEvent, TerminalEditor, TerminalEditorResult, make_label},
tree_nav::{TreeNav, TreeNavResult, TerminalTreeEditor, TreeCursor}, tree_nav::{TreeNav, TreeNavResult, TerminalTreeEditor, TreeCursor},
list::{ListEditor, ListEditorStyle, sexpr::ListDecoration}, list::{ListCursorMode, ListEditor, ListEditorStyle, sexpr::ListDecoration},
string_editor::CharEditor, string_editor::CharEditor,
} }
}; };
@ -74,8 +74,10 @@ pub struct ProcessLauncher {
>, >,
pty: Option<crate::pty::PTY>, pty: Option<crate::pty::PTY>,
ptybox: Arc<RwLock<crate::ascii_box::AsciiBox>>, ptybox: Arc<RwLock<crate::ascii_box::AsciiBox>>,
suspended: bool,
pty_port: ViewPort<dyn TerminalView>, pty_port: ViewPort<dyn TerminalView>,
status_port: ViewPort<dyn SingletonView<Item = Option<portable_pty::ExitStatus>>>,
comp_port: ViewPort<dyn TerminalView>, comp_port: ViewPort<dyn TerminalView>,
compositor: Arc<RwLock<nested::terminal::TerminalCompositor>> compositor: Arc<RwLock<nested::terminal::TerminalCompositor>>
@ -84,6 +86,7 @@ pub struct ProcessLauncher {
impl ProcessLauncher { impl ProcessLauncher {
pub fn new() -> Self { pub fn new() -> Self {
let pty_port = ViewPort::new(); let pty_port = ViewPort::new();
let status_port = ViewPort::new();
let comp_port = ViewPort::new(); let comp_port = ViewPort::new();
let box_port = ViewPort::<dyn TerminalView>::new(); let box_port = ViewPort::<dyn TerminalView>::new();
let compositor = nested::terminal::TerminalCompositor::new(comp_port.inner()); let compositor = nested::terminal::TerminalCompositor::new(comp_port.inner());
@ -125,21 +128,16 @@ impl ProcessLauncher {
pty_port.outer() pty_port.outer()
.map_item(|_,a:&TerminalAtom| a.add_style_back(TerminalStyle::fg_color((230, 230, 230)))), .map_item(|_,a:&TerminalAtom| a.add_style_back(TerminalStyle::fg_color((230, 230, 230)))),
box_port.inner() box_port.inner()
), ),
suspended: false,
pty_port, pty_port,
status_port,
comp_port, comp_port,
compositor compositor
} }
} }
pub fn launch_pty2(&mut self) { pub fn launch_pty(&mut self) {
self.launch_pty(self.pty_port.inner());
}
pub fn launch_pty(&mut self, port: InnerViewPort<dyn TerminalView>) -> Option<crate::pty::PTY> {
self.up();
self.up();
let mut strings = Vec::new(); let mut strings = Vec::new();
let v = self.cmd_editor.get_data_port().get_view().unwrap(); 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()); let mut cmd = crate::pty::CommandBuilder::new(strings[0].as_str());
cmd.args(&strings[1..]); cmd.args(&strings[1..]);
crate::pty::PTY::new(cmd, port) self.cmd_editor.goto(TreeCursor {
} else { leaf_mode: ListCursorMode::Insert,
None 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 { impl TerminalEditor for ProcessLauncher {
@ -166,27 +171,94 @@ impl TerminalEditor for ProcessLauncher {
} }
fn handle_terminal_event(&mut self, event: &TerminalEvent) -> TerminalEditorResult { 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 { match event {
TerminalEvent::Input(Event::Key(Key::Char('\n'))) => { TerminalEvent::Input(Event::Key(Key::Ctrl('c'))) => {
// launch command self.pty = None;
self.cmd_editor.up(); self.suspended = false;
self.cmd_editor.up(); 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 TerminalEditorResult::Exit
} }
event => {
event => self.cmd_editor.handle_terminal_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 { impl TreeNav for ProcessLauncher {
fn get_cursor(&self) -> TreeCursor { self.cmd_editor.get_cursor() } fn get_cursor(&self) -> TreeCursor {
fn goto(&mut self, cur: TreeCursor) -> TreeNavResult { self.cmd_editor.goto(cur) } self.cmd_editor.get_cursor()
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 goto(&mut self, cur: TreeCursor) -> TreeNavResult {
fn nexd(&mut self) -> TreeNavResult { self.cmd_editor.nexd() } self.suspended = false;
fn up(&mut self) -> TreeNavResult { self.cmd_editor.up() } if let Some(status) = self.status_port.outer().get_view().get() {
fn dn(&mut self) -> TreeNavResult { self.cmd_editor.dn() } 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()
}
} }

View file

@ -4,7 +4,8 @@ use {
std::sync::Mutex, std::sync::Mutex,
nested::{ nested::{
core::{InnerViewPort}, core::{InnerViewPort},
terminal::{TerminalView, TerminalEvent} singleton::{SingletonView, SingletonBuffer},
terminal::{TerminalView, TerminalEvent, TerminalEditorResult}
} }
}; };
@ -13,20 +14,20 @@ pub use portable_pty::CommandBuilder;
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
pub struct PTY { pub struct PTY {
master: Mutex<Box<dyn portable_pty::MasterPty + Send>>, master: Mutex<Box<dyn portable_pty::MasterPty + Send>>
child: Box<dyn portable_pty::Child + Send + Sync>
} }
impl PTY { impl PTY {
pub fn new( pub fn new(
cmd: portable_pty::CommandBuilder, cmd: portable_pty::CommandBuilder,
port: InnerViewPort<dyn TerminalView> term_port: InnerViewPort<dyn TerminalView>,
status_port: InnerViewPort<dyn SingletonView<Item = Option<portable_pty::ExitStatus>>>
) -> Option<Self> { ) -> Option<Self> {
// Create a new pty // Create a new pty
let mut pair = portable_pty::native_pty_system().openpty(portable_pty::PtySize { let mut pair = portable_pty::native_pty_system().openpty(portable_pty::PtySize {
rows: 25, rows: 25,
cols: 80, cols: 120,
// Not all systems support pixel_width, pixel_height, // Not all systems support pixel_width, pixel_height,
// but it is good practice to set it to something // but it is good practice to set it to something
@ -37,37 +38,79 @@ impl PTY {
pixel_height: 0, pixel_height: 0,
}).unwrap(); }).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(); let mut reader = pair.master.try_clone_reader().unwrap();
async_std::task::spawn_blocking( async_std::task::spawn_blocking(
move || { 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 { Some(PTY {
master: Mutex::new(pair.master), master: Mutex::new(pair.master)
child
}) })
} else { } else {
None None
} }
} }
pub fn get_status(&mut self) -> bool { pub fn handle_terminal_event(&mut self, event: &TerminalEvent) -> TerminalEditorResult {
if let Ok(Some(status)) = self.child.try_wait() {
true
} else {
false
}
}
pub fn handle_terminal_event(&mut self, event: &TerminalEvent) {
match event { 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))) => { TerminalEvent::Input(Event::Key(Key::Char(c))) => {
write!(self.master.lock().unwrap(), "{}", 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
} }
} }
} }