From 165033dd11cb6d200b9b9233840dff96e6468e0d Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Wed, 10 Nov 2021 12:44:33 -0500 Subject: more refactoring, make commands blocking by default and handle ^C/^Z --- Cargo.lock | 25 +++++-- Cargo.toml | 1 + src/history.rs | 56 ++++++++++++--- src/main.rs | 3 +- src/nbsh.rs | 219 +++++++++++++++++++++++++++++---------------------------- 5 files changed, 180 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1797f47..7518950 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,9 +156,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blocking" @@ -452,6 +452,7 @@ dependencies = [ "async-std", "futures", "libc", + "nix 0.23.0", "pty-process", "textmode", "vt100", @@ -459,9 +460,21 @@ dependencies = [ [[package]] name = "nix" -version = "0.20.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" +checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", +] + +[[package]] +name = "nix" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188" dependencies = [ "bitflags", "cc", @@ -547,7 +560,7 @@ dependencies = [ "async-io", "async-process", "libc", - "nix", + "nix 0.20.0", "thiserror", ] @@ -625,7 +638,7 @@ dependencies = [ "blocking", "futures-lite", "itoa", - "nix", + "nix 0.20.0", "terminal_size", "thiserror", "vt100", diff --git a/Cargo.toml b/Cargo.toml index adb439d..9bed95f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ async-process = "1.2.0" async-std = "1.10.0" futures = "0.3.17" libc = "0.2.107" +nix = "0.23.0" pty-process = { version = "0.1.0", features = ["backend-async-std"] } textmode = { version = "0.1.0", features = ["async"] } vt100 = "0.12.0" diff --git a/src/history.rs b/src/history.rs index bf89ec1..00ad39a 100644 --- a/src/history.rs +++ b/src/history.rs @@ -25,7 +25,7 @@ impl History { .spawn_pty(Some(&pty_process::Size::new(24, 80))) .unwrap(); let entry = async_std::sync::Arc::new(async_std::sync::Mutex::new( - HistoryEntry::new(cmd), + HistoryEntry::new(cmd, child.id().try_into().unwrap()), )); let task_entry = async_std::sync::Arc::clone(&entry); let task_action = self.action.clone(); @@ -41,6 +41,12 @@ impl History { eprintln!("pty read failed: {:?}", e); } task_entry.lock_arc().await.running = false; + task_action + .send(crate::nbsh::Action::UpdateFocus( + crate::nbsh::InputSource::Repl, + )) + .await + .unwrap(); task_action .send(crate::nbsh::Action::Render) .await @@ -52,6 +58,12 @@ impl History { } }); self.entries.push(entry); + self.action + .send(crate::nbsh::Action::UpdateFocus( + crate::nbsh::InputSource::History(self.entries.len() - 1), + )) + .await + .unwrap(); self.action.send(crate::nbsh::Action::Render).await.unwrap(); Ok(self.entries.len() - 1) } @@ -61,10 +73,26 @@ impl History { key: textmode::Key, idx: usize, ) -> bool { - if let textmode::Key::Bytes(b) = key { - self.send_process_input(idx, &b).await.unwrap(); - } else { - unreachable!(); + match key { + textmode::Key::Ctrl(b'c') => { + let pid = self.entries[idx].lock_arc().await.pid; + nix::sys::signal::kill(pid, nix::sys::signal::Signal::SIGINT) + .unwrap(); + } + textmode::Key::Ctrl(b'z') => { + self.action + .send(crate::nbsh::Action::UpdateFocus( + crate::nbsh::InputSource::Repl, + )) + .await + .unwrap(); + } + textmode::Key::Ctrl(_) => {} + key => { + self.send_process_input(idx, &key.into_bytes()) + .await + .unwrap(); + } } false } @@ -75,6 +103,7 @@ impl History { repl_lines: usize, ) -> anyhow::Result<()> { let mut used_lines = repl_lines; + let mut pos = None; for entry in self.entries.iter().rev() { let entry = entry.lock_arc().await; let screen = entry.vt.screen(); @@ -95,23 +124,30 @@ impl History { } out.write_str(&entry.cmd); out.reset_attributes(); - out.write(b"\r\n"); if last_row > 5 { + out.write(b"\r\n"); out.set_bgcolor(textmode::color::RED); out.write(b"..."); out.reset_attributes(); - out.write(b"\r\n"); } + let mut end_pos = (0, 0); for row in screen .rows_formatted(0, 80) .take(last_row) .skip(last_row.saturating_sub(5)) { - out.write(&row); out.write(b"\r\n"); + out.write(&row); + end_pos = out.screen().cursor_position(); + } + if pos.is_none() { + pos = Some(end_pos); } out.reset_attributes(); } + if let Some(pos) = pos { + out.move_to(pos.0, pos.1); + } Ok(()) } @@ -126,15 +162,17 @@ impl History { struct HistoryEntry { cmd: String, + pid: nix::unistd::Pid, vt: vt100::Parser, running: bool, // option end time // start time } impl HistoryEntry { - fn new(cmd: &str) -> Self { + fn new(cmd: &str, pid: i32) -> Self { Self { cmd: cmd.into(), + pid: nix::unistd::Pid::from_raw(pid), vt: vt100::Parser::new(24, 80, 0), running: true, } diff --git a/src/main.rs b/src/main.rs index 0d1bb69..97624df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,7 @@ mod nbsh; mod repl; async fn async_main() -> anyhow::Result<()> { - let nbsh = nbsh::Nbsh::new(); - nbsh.run().await + nbsh::run().await } fn main() { diff --git a/src/nbsh.rs b/src/nbsh.rs index 8ba22b2..84d35e3 100644 --- a/src/nbsh.rs +++ b/src/nbsh.rs @@ -1,132 +1,137 @@ use textmode::Textmode as _; -pub struct Nbsh { - repl: crate::repl::Repl, - history: crate::history::History, +pub async fn run() -> anyhow::Result<()> { + let mut input = textmode::Input::new().await?; + let mut output = textmode::Output::new().await?; - action: async_std::channel::Receiver, -} - -impl Nbsh { - pub fn new() -> Self { - let (action_w, action_r) = async_std::channel::unbounded(); - Self { - repl: crate::repl::Repl::new(action_w.clone()), - history: crate::history::History::new(action_w), - action: action_r, - } - } - - pub async fn run(self) -> anyhow::Result<()> { - let mut input = textmode::Input::new().await?; - let mut output = textmode::Output::new().await?; - - // avoid the guards getting stuck in a task that doesn't run to - // completion - let _input_guard = input.take_raw_guard(); - let _output_guard = output.take_screen_guard(); + // avoid the guards getting stuck in a task that doesn't run to + // completion + let _input_guard = input.take_raw_guard(); + let _output_guard = output.take_screen_guard(); - let Self { - repl, - history, - action, - } = self; + let (action_w, action_r) = async_std::channel::unbounded(); - let repl = - async_std::sync::Arc::new(async_std::sync::Mutex::new(repl)); - let history = - async_std::sync::Arc::new(async_std::sync::Mutex::new(history)); - let input_source = async_std::sync::Arc::new( - async_std::sync::Mutex::new(InputSource::Repl), - ); + let repl = async_std::sync::Arc::new(async_std::sync::Mutex::new( + crate::repl::Repl::new(action_w.clone()), + )); + let history = async_std::sync::Arc::new(async_std::sync::Mutex::new( + crate::history::History::new(action_w), + )); + let input_source = async_std::sync::Arc::new( + async_std::sync::Mutex::new(InputSource::Repl), + ); - render( - &mut output, - &*repl.lock_arc().await, - &*history.lock_arc().await, - ) - .await - .unwrap(); + render( + &mut output, + &*repl.lock_arc().await, + &*history.lock_arc().await, + &*input_source.lock_arc().await, + ) + .await + .unwrap(); - let action_history = async_std::sync::Arc::clone(&history); - let action_repl = async_std::sync::Arc::clone(&repl); - let action_input_source = async_std::sync::Arc::clone(&input_source); + { + let repl = async_std::sync::Arc::clone(&repl); + let history = async_std::sync::Arc::clone(&history); + let input_source = async_std::sync::Arc::clone(&input_source); async_std::task::spawn(async move { - while let Ok(action) = action.recv().await { - match action { - Action::Render => { - render( - &mut output, - &*action_repl.lock_arc().await, - &*action_history.lock_arc().await, - ) - .await - .unwrap(); - } - Action::Run(cmd) => { - action_history - .lock_arc() - .await - .run(&cmd) - .await - .unwrap(); - } - Action::UpdateFocus(new_input_source) => { - *action_input_source.lock_arc().await = - new_input_source; - } - } + while let Ok(action) = action_r.recv().await { + handle_action( + action, + &mut output, + async_std::sync::Arc::clone(&repl), + async_std::sync::Arc::clone(&history), + async_std::sync::Arc::clone(&input_source), + ) + .await; } }); + } - loop { - let input_source = *input_source.lock_arc().await; - match input_source { - InputSource::Repl => { - input.parse_utf8(true); - input.parse_ctrl(true); - input.parse_meta(true); - input.parse_special_keys(true); - input.parse_single(false); - } - InputSource::History(_) => { - input.parse_utf8(false); - input.parse_ctrl(false); - input.parse_meta(false); - input.parse_special_keys(false); - input.parse_single(false); - } - } - let key = input.read_key().await.unwrap(); - if let Some(key) = key { - let quit = match input_source { - InputSource::Repl => { - repl.lock_arc().await.handle_key(key).await - } - InputSource::History(idx) => { - history.lock_arc().await.handle_key(key, idx).await - } - }; - if quit { - break; - } - } else { - break; - } + loop { + let quit = handle_input( + &mut input, + async_std::sync::Arc::clone(&repl), + async_std::sync::Arc::clone(&history), + async_std::sync::Arc::clone(&input_source), + ) + .await; + if quit { + break; + } + } + + Ok(()) +} + +async fn handle_action( + action: Action, + output: &mut textmode::Output, + repl: async_std::sync::Arc>, + history: async_std::sync::Arc< + async_std::sync::Mutex, + >, + input_source: async_std::sync::Arc>, +) { + match action { + Action::Render => { + render( + output, + &*repl.lock_arc().await, + &*history.lock_arc().await, + &*input_source.lock_arc().await, + ) + .await + .unwrap(); + } + Action::Run(ref cmd) => { + history.lock_arc().await.run(cmd).await.unwrap(); } + Action::UpdateFocus(new_input_source) => { + *input_source.lock_arc().await = new_input_source; + } + } +} - Ok(()) +async fn handle_input( + input: &mut textmode::Input, + repl: async_std::sync::Arc>, + history: async_std::sync::Arc< + async_std::sync::Mutex, + >, + input_source: async_std::sync::Arc>, +) -> bool { + let key = input.read_key().await.unwrap(); + if let Some(key) = key { + let input_source = *input_source.lock_arc().await; + let quit = match input_source { + InputSource::Repl => repl.lock_arc().await.handle_key(key).await, + InputSource::History(idx) => { + history.lock_arc().await.handle_key(key, idx).await + } + }; + if quit { + return true; + } + } else { + return true; } + false } async fn render( out: &mut textmode::Output, repl: &crate::repl::Repl, history: &crate::history::History, + input_source: &InputSource, ) -> anyhow::Result<()> { out.clear(); - history.render(out, repl.lines()).await?; - repl.render(out).await?; + if let InputSource::Repl = input_source { + history.render(out, repl.lines()).await?; + repl.render(out).await?; + } else { + history.render(out, 0).await?; + } out.refresh().await?; Ok(()) } -- cgit v1.2.3-54-g00ecf