diff --git a/nested/src/index/buffer.rs b/nested/src/index/buffer.rs index d48537b..f14beaf 100644 --- a/nested/src/index/buffer.rs +++ b/nested/src/index/buffer.rs @@ -77,5 +77,10 @@ where Key: Clone + Hash + Eq + Send + Sync + 'static, self.insert(key, item); } } + + pub fn remove(&mut self, key: Key) { + self.data.write().unwrap().remove(&key); + self.cast.notify(&key); + } } diff --git a/nested/src/string_editor.rs b/nested/src/string_editor.rs index c0be63f..4eae28e 100644 --- a/nested/src/string_editor.rs +++ b/nested/src/string_editor.rs @@ -43,10 +43,6 @@ impl TerminalEditor for CharEditor { } else { "".to_string() }) - .map_item( - |_idx, atom| - atom.add_style_back(TerminalStyle::fg_color((120, 200, 10))) - ) } fn handle_terminal_event(&mut self, event: &TerminalEvent) -> TerminalEditorResult { @@ -114,6 +110,10 @@ impl TerminalEditor for StringEditor { .decorate("\"", "\"", "", 1) .to_grid_horizontal() .flatten() + .map_item( + |_idx, atom| + atom.add_style_back(TerminalStyle::fg_color((120, 200, 10))) + ) } fn handle_terminal_event(&mut self, event: &TerminalEvent) -> TerminalEditorResult { diff --git a/shell/Cargo.toml b/shell/Cargo.toml index b3b735f..2e94e51 100644 --- a/shell/Cargo.toml +++ b/shell/Cargo.toml @@ -7,6 +7,9 @@ edition = "2018" nested = { path = "../nested" } cgmath = "*" termion = "*" +bincode = "*" +libc = "0.2.*" +tty = { git = "https://github.com/stemjail/tty-rs.git" } [dependencies.async-std] version = "1.9.0" diff --git a/shell/src/main.rs b/shell/src/main.rs index 24127ff..35526c7 100644 --- a/shell/src/main.rs +++ b/shell/src/main.rs @@ -1,5 +1,8 @@ +extern crate tty; + mod monstera; +mod process; use{ std::sync::{Arc, RwLock}, @@ -15,9 +18,9 @@ use{ context::{ReprTree, Object, MorphismType, MorphismMode, Context}, port::{UpdateTask}}, index::{IndexView}, - sequence::{SequenceView}, + sequence::{SequenceView, SequenceViewExt}, vec::{VecBuffer}, - integer::{RadixProjection, DigitEditor}, + integer::{RadixProjection, DigitEditor, PosIntEditor}, terminal::{ Terminal, TerminalStyle, @@ -27,60 +30,17 @@ use{ make_label, TerminalView, TerminalEditor}, - string_editor::{CharEditor}, + string_editor::{StringEditor}, tree_nav::{TreeNav, TreeNavResult, TreeCursor, TerminalTreeEditor}, list::{SExprView, ListCursorMode, ListEditor, ListEditorStyle} + }, + crate::{ + process::ProcessLauncher } }; -struct GridFill(T); -impl View for GridFill { - type Msg = Point2; -} - -impl IndexView> for GridFill { - type Item = T; - - fn area(&self) -> Option>> { - None - } - - fn get(&self, _: &Point2) -> Option { - Some(self.0.clone()) - } -} - #[async_std::main] async fn main() { - /* todo: - -open:: ->0: -( Path ) -( Sequence ( Sequence UnicodeChar ) ) -( Sequence UnicodeChar ) -<1: -( FileDescriptor ) -( MachineInt ) - -read:: ->0: -( FileDescriptor ) -( MachineInt ) -<1: -( Sequence MachineSyllab ) -( Vec MachineSyllab ) - -write:: ->0 -( FileDescriptor ) -( MachineInt ) ->1: -( Sequence MachineSyllab ) -( Vec MachineSyllab ) - - */ - let term_port = ViewPort::new(); let compositor = TerminalCompositor::new(term_port.inner()); @@ -107,83 +67,27 @@ write:: let cur_size_port = ViewPort::new(); let mut cur_size = nested::singleton::SingletonBuffer::new(Vector2::new(10, 10), cur_size_port.inner()); - // TypeEditor - let make_char_editor = || { - std::sync::Arc::new(std::sync::RwLock::new(CharEditor::new())) - }; - let make_subsub_editor = move || { - std::sync::Arc::new(std::sync::RwLock::new(ListEditor::new(make_char_editor.clone(), ListEditorStyle::String))) - }; - let make_sub_editor = move || { - std::sync::Arc::new(std::sync::RwLock::new(ListEditor::new(make_subsub_editor.clone(), ListEditorStyle::HorizontalSexpr))) - }; - - let mut te = ListEditor::new(make_sub_editor.clone(), ListEditorStyle::VerticalSexpr); - - te.goto( - TreeCursor { - leaf_mode: ListCursorMode::Insert, - tree_addr: vec![ 0 ] - } - ); - - let mut p = te.get_data_port().map(|sub_editor| sub_editor.read().unwrap().get_data_port()); - let status_chars_port = ViewPort::new(); let mut status_chars = VecBuffer::new(status_chars_port.inner()); - let help_port = ViewPort::>>::new(); - let mut help_buf = nested::index::buffer::IndexBuffer::, OuterViewPort>::new(help_port.inner()); - - let table_style = TerminalStyle::fg_color((120, 100, 80)); - let desc_style = TerminalStyle::italic(true); - help_buf.insert_iter(vec![ - (Point2::new(0, 0), make_label("CTRL+{c,d,g}").map_item(|_idx, atom| atom.add_style_back(TerminalStyle::bold(true)))), - (Point2::new(1, 0), make_label(" | ").map_item(move |_idx, atom| atom.add_style_back(table_style))), - (Point2::new(2, 0), make_label("quit").map_item(move |_idx, atom| atom.add_style_back(desc_style))), - - (Point2::new(0, 1), make_label("↞ ← ↑ ↓ → ↠").map_item(|_idx, atom| atom.add_style_back(TerminalStyle::bold(true)))), - (Point2::new(1, 1), make_label(" | ").map_item(move |_idx, atom| atom.add_style_back(table_style))), - (Point2::new(2, 1), make_label("move cursor").map_item(move |_idx, atom| atom.add_style_back(desc_style))), - - (Point2::new(0, 3), make_label(" (Select)").map_item(|_idx, atom| atom.add_style_back(TerminalStyle::bold(true)))), - (Point2::new(1, 3), make_label(" | ").map_item(move |_idx, atom| atom.add_style_back(table_style))), - (Point2::new(2, 3), make_label("delete item at cursor position").map_item(move |_idx, atom| atom.add_style_back(desc_style))), - - (Point2::new(0, 4), make_label(" (Insert)").map_item(|_idx, atom| atom.add_style_back(TerminalStyle::bold(true)))), - (Point2::new(1, 4), make_label(" | ").map_item(move |_idx, atom| atom.add_style_back(table_style))), - (Point2::new(2, 4), make_label("delete item right to cursor").map_item(move |_idx, atom| atom.add_style_back(desc_style))), - - (Point2::new(0, 5), make_label(" (Insert)").map_item(|_idx, atom| atom.add_style_back(TerminalStyle::bold(true)))), - (Point2::new(1, 5), make_label(" | ").map_item(move |_idx, atom| atom.add_style_back(table_style))), - (Point2::new(2, 5), make_label("delete item left to cursor").map_item(move |_idx, atom| atom.add_style_back(desc_style))), - - (Point2::new(0, 6), make_label("").map_item(|_idx, atom| atom.add_style_back(TerminalStyle::bold(true)))), - (Point2::new(1, 6), make_label(" | ").map_item(move |_idx, atom| atom.add_style_back(table_style))), - (Point2::new(2, 6), make_label("toggle cursor mode (insert / select)").map_item(move |_idx, atom| atom.add_style_back(desc_style))), - ]); - - let help_head = make_label("─────────────────────┬─────────────────────").map_item(move |_idx, atom| atom.add_style_back(table_style)); - table_buf.insert_iter(vec![ (Point2::new(0, 0), magic.clone()), - (Point2::new(0, 2), status_chars_port.outer().to_sequence().to_grid_horizontal()), - (Point2::new(0, 3), te.get_term_view()), - (Point2::new(0, 4), make_label(" ")), - (Point2::new(0, 5), help_head), - (Point2::new(0, 6), help_port.outer().flatten()), - (Point2::new(0, 7), magic.clone()), + (Point2::new(0, 1), status_chars_port.outer().to_sequence().to_grid_horizontal()), ]); compositor.write().unwrap().push(monstera::make_monstera()); compositor.write().unwrap().push(table_port.outer().flatten().offset(Vector2::new(40, 2))); -/* - te.get_data_port() - .map( - |item_editor| item_editor.read().unwrap().get_data_port() - ) -*/ + let mut y = 2; + + let mut process_launcher = ProcessLauncher::new(); + table_buf.insert(Point2::new(0, y), process_launcher.get_term_view()); + + process_launcher.goto(TreeCursor { + leaf_mode: ListCursorMode::Insert, + tree_addr: vec![ 0 ] + }); + loop { term_port.update(); match term.next_event().await { @@ -200,122 +104,60 @@ write:: TerminalEvent::Input(Event::Key(Key::Ctrl('d'))) => break, TerminalEvent::Input(Event::Key(Key::Left)) => { - if te.pxev() == TreeNavResult::Exit { - te.goto_home(); - } + process_launcher.pxev(); } TerminalEvent::Input(Event::Key(Key::Right)) => { - if te.nexd() == TreeNavResult::Exit { - te.goto_end(); - } + process_launcher.nexd(); } - TerminalEvent::Input(Event::Key(Key::Up)) => { te.up(); } + TerminalEvent::Input(Event::Key(Key::Up)) => { process_launcher.up(); } TerminalEvent::Input(Event::Key(Key::Down)) => { - if te.dn() == TreeNavResult::Continue { - te.goto_home(); + if process_launcher.dn() == TreeNavResult::Continue { + process_launcher.goto_home(); } } TerminalEvent::Input(Event::Key(Key::Home)) => { - if te.goto_home() == TreeNavResult::Exit { - te.goto_home(); - } + process_launcher.goto_home(); } TerminalEvent::Input(Event::Key(Key::End)) => { - if te.goto_end() == TreeNavResult::Exit { - te.goto_end(); - } + process_launcher.goto_end(); + } + TerminalEvent::Input(Event::Key(Key::Char('\n'))) => { + let output_view = process_launcher.launch(); + + y += 1; + table_buf.insert(Point2::new(0, y), output_view); + + process_launcher = ProcessLauncher::new(); + process_launcher.goto(TreeCursor { + leaf_mode: ListCursorMode::Insert, + tree_addr: vec![ 0 ] + }); + + y += 1; + table_buf.insert(Point2::new(0, y), process_launcher.get_term_view()); } - TerminalEvent::Input(Event::Key(Key::Char('\n'))) => { - /* - let mut strings = Vec::new(); - - let v = p.get_view().unwrap(); - for i in 0 .. v.len().unwrap_or(0) { - strings.push( - v - .get(&i).unwrap() - .get_view().unwrap() - .read().unwrap() - .iter().collect::() - ); - } - - if strings.len() == 0 { continue; } - - if let Ok(output) = - std::process::Command::new(strings[0].as_str()) - .args(&strings[1..]) - .output() - { - // take output and update terminal view - let mut line_port = ViewPort::new(); - let mut line = VecBuffer::new(line_port.inner()); - for byte in output.stdout { - match byte { - b'\n' => { - compositor.write().unwrap().push( - line_port.outer() - .to_sequence() - .map(|c| TerminalAtom::new(*c, TerminalStyle::fg_color((130,90,90)))) - .to_grid_horizontal() - .offset(Vector2::new(45, y)) - ); - y += 1; - line_port = ViewPort::new(); - line = VecBuffer::new(line_port.inner()); - } - byte => { - line.push(byte as char); - } - } - } - } else { - compositor.write().unwrap().push( - make_label("Command not found") - .map_item(|idx, a| a.add_style_back(TerminalStyle::fg_color((200,0,0)))) - .offset(Vector2::new(45, y)) - ); - y+=1; - } - - te.up(); - te.goto_home(); - te = ListEditor::new(make_sub_editor.clone()); - - compositor.write().unwrap().push(magic.offset(Vector2::new(40, y))); - y += 1; - compositor.write().unwrap().push( - te - .horizontal_sexpr_view() - .offset(Vector2::new(40, y)) - ); - y += 1; - - p = te.get_data_port().map(|string_editor| string_editor.read().unwrap().get_data_port()); -*/ - }, ev => { - if te.get_cursor().leaf_mode == ListCursorMode::Select { + if process_launcher.get_cursor().leaf_mode == ListCursorMode::Select { match ev { - TerminalEvent::Input(Event::Key(Key::Char('l'))) => { te.up(); }, - TerminalEvent::Input(Event::Key(Key::Char('a'))) => { te.dn(); }, - TerminalEvent::Input(Event::Key(Key::Char('i'))) => { te.pxev(); }, - TerminalEvent::Input(Event::Key(Key::Char('e'))) => { te.nexd(); }, - TerminalEvent::Input(Event::Key(Key::Char('u'))) => { te.goto_home(); }, - TerminalEvent::Input(Event::Key(Key::Char('o'))) => { te.goto_end(); }, + TerminalEvent::Input(Event::Key(Key::Char('l'))) => { process_launcher.up(); }, + TerminalEvent::Input(Event::Key(Key::Char('a'))) => { process_launcher.dn(); }, + TerminalEvent::Input(Event::Key(Key::Char('i'))) => { process_launcher.pxev(); }, + TerminalEvent::Input(Event::Key(Key::Char('e'))) => { process_launcher.nexd(); }, + TerminalEvent::Input(Event::Key(Key::Char('u'))) => { process_launcher.goto_home(); }, + TerminalEvent::Input(Event::Key(Key::Char('o'))) => { process_launcher.goto_end(); }, _ => { - te.handle_terminal_event(&ev); + process_launcher.handle_terminal_event(&ev); } } } else { - te.handle_terminal_event(&ev); + process_launcher.handle_terminal_event(&ev); } } } status_chars.clear(); - let cur = te.get_cursor(); + let cur = process_launcher.get_cursor(); if cur.tree_addr.len() > 0 { status_chars.push(TerminalAtom::new('@', TerminalStyle::fg_color((120, 80, 80)).add(TerminalStyle::bold(true)))); diff --git a/shell/src/process.rs b/shell/src/process.rs new file mode 100644 index 0000000..c0225f1 --- /dev/null +++ b/shell/src/process.rs @@ -0,0 +1,197 @@ +use { + std::{ + sync::Arc, + process::Command, + os::unix::io::{FromRawFd, AsRawFd}, + }, + std::sync::RwLock, + tty::{FileDesc, TtyServer}, + termion::event::{Key, Event}, + cgmath::Point2, + nested::{ + core::{ViewPort, OuterViewPort, Observer}, + singleton::{SingletonView, SingletonBuffer}, + sequence::{SequenceView, SequenceViewExt}, + index::buffer::IndexBuffer, + vec::VecBuffer, + terminal::{TerminalAtom, TerminalStyle, TerminalView, TerminalEvent, TerminalEditor, TerminalEditorResult, make_label}, + tree_nav::{TreeNav, TreeNavResult, TerminalTreeEditor, TreeCursor}, + list::{ListEditor, ListEditorStyle, sexpr::ListDecoration}, + string_editor::CharEditor, + } +}; + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> + +pub struct ProcessArg { + editor: ListEditor< CharEditor, + Box Arc> + Send + Sync + 'static> > +} + +impl ProcessArg { + pub fn get_data_port(&self) -> OuterViewPort> { + self.editor.get_data_port() + .map( + |char_editor| char_editor.read().unwrap().get_data_port().get_view().unwrap().get().unwrap() + ) + } +} + +impl TerminalEditor for ProcessArg { + fn get_term_view(&self) -> OuterViewPort { + self.editor + .get_seg_seq_view() + .to_grid_horizontal() + .flatten() + } + + fn handle_terminal_event(&mut self, event: &TerminalEvent) -> TerminalEditorResult { + match event { + TerminalEvent::Input(Event::Key(Key::Char(' '))) | + TerminalEvent::Input(Event::Key(Key::Char('\n'))) => { + self.editor.up(); + TerminalEditorResult::Exit + } + + event => self.editor.handle_terminal_event(event) + } + } +} + +impl TreeNav for ProcessArg { + fn get_cursor(&self) -> TreeCursor { self.editor.get_cursor() } + fn goto(&mut self, cur: TreeCursor) -> TreeNavResult { self.editor.goto(cur) } + fn goto_home(&mut self) -> TreeNavResult { self.editor.goto_home() } + fn goto_end(&mut self) -> TreeNavResult { self.editor.goto_end() } + fn pxev(&mut self) -> TreeNavResult { self.editor.pxev() } + fn nexd(&mut self) -> TreeNavResult { self.editor.nexd() } + fn up(&mut self) -> TreeNavResult { self.editor.up() } + fn dn(&mut self) -> TreeNavResult { self.editor.dn() } +} + +pub struct ProcessLauncher { + editor: ListEditor< ProcessArg, + Box Arc> + Send + Sync + 'static> > +} + +impl ProcessLauncher { + pub fn new() -> Self { + ProcessLauncher { + editor: ListEditor::new( + Box::new( + || { + Arc::new(RwLock::new(ProcessArg { + editor: ListEditor::new( + Box::new( + || { + Arc::new(RwLock::new(CharEditor::new())) + } + ), + ListEditorStyle::Plain) + })) + } + ), + ListEditorStyle::Plain + ) + } + } + + 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(); + for i in 0 .. v.len().unwrap_or(0) { + let arg_view = v.get(&i).unwrap().read().unwrap().get_data_port().get_view().unwrap(); + strings.push(arg_view.iter().collect::()); + } + + 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)); }, + }; + + let mut cmd = std::process::Command::new(strings[0].as_str()); + cmd.args(&strings[1..]).stdin(std::process::Stdio::null()); + + let process = match server.spawn(cmd) { + Ok(p) => p, + Err(e) => { return make_label(&format!("Failed to execute process: {}", e));}, + }; + + 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); + + async_std::task::spawn( + async move { + */ + + 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() + } else { + make_label("Failed to spawn ansi parser process") + .map_item(|idx, a| a.add_style_back(TerminalStyle::fg_color((200,0,0)))) + } + } else { + make_label("no command") + .map_item(|idx, a| a.add_style_back(TerminalStyle::fg_color((200,0,0)))) + } + } +} + +impl TerminalEditor for ProcessLauncher { + fn get_term_view(&self) -> OuterViewPort { + self.editor + .get_seg_seq_view() + .decorate("$(", ")", " ", 0) + .to_grid_horizontal() + .flatten() + } + + fn handle_terminal_event(&mut self, event: &TerminalEvent) -> TerminalEditorResult { + match event { + TerminalEvent::Input(Event::Key(Key::Char('\n'))) => { + // launch command + self.editor.up(); + self.editor.up(); + TerminalEditorResult::Exit + } + + event => self.editor.handle_terminal_event(event) + } + } +} + +impl TreeNav for ProcessLauncher { + fn get_cursor(&self) -> TreeCursor { self.editor.get_cursor() } + fn goto(&mut self, cur: TreeCursor) -> TreeNavResult { self.editor.goto(cur) } + fn goto_home(&mut self) -> TreeNavResult { self.editor.goto_home() } + fn goto_end(&mut self) -> TreeNavResult { self.editor.goto_end() } + fn pxev(&mut self) -> TreeNavResult { self.editor.pxev() } + fn nexd(&mut self) -> TreeNavResult { self.editor.nexd() } + fn up(&mut self) -> TreeNavResult { self.editor.up() } + fn dn(&mut self) -> TreeNavResult { self.editor.dn() } +} +