diff options
author | Jesse Luehrs <doy@tozt.net> | 2022-01-03 10:41:54 -0500 |
---|---|---|
committer | Jesse Luehrs <doy@tozt.net> | 2022-01-03 10:41:54 -0500 |
commit | 5b75e39514b75696fd812e711e59ce1b53b8b412 (patch) | |
tree | ff207933fa9a672457c686d52a95e3a42b455ff7 /src | |
parent | 39f1b8bec3891b3fcc7f60aea86e2ab2f8c3ea8a (diff) | |
download | nbsh-5b75e39514b75696fd812e711e59ce1b53b8b412.tar.gz nbsh-5b75e39514b75696fd812e711e59ce1b53b8b412.zip |
refocus the readline when the foreground process is suspended
Diffstat (limited to 'src')
-rw-r--r-- | src/env.rs | 10 | ||||
-rw-r--r-- | src/event.rs | 31 | ||||
-rw-r--r-- | src/pipeline/mod.rs | 21 | ||||
-rw-r--r-- | src/state/history/mod.rs | 82 | ||||
-rw-r--r-- | src/state/mod.rs | 5 |
5 files changed, 121 insertions, 28 deletions
@@ -9,6 +9,7 @@ pub enum Env { #[derive(serde::Serialize, serde::Deserialize)] pub struct V0 { pipeline: Option<String>, + idx: usize, #[serde( serialize_with = "serialize_status", deserialize_with = "deserialize_status" @@ -17,9 +18,10 @@ pub struct V0 { } impl Env { - pub fn new() -> Self { + pub fn new(idx: usize) -> Self { Self::V0(V0 { pipeline: None, + idx, latest_status: std::process::ExitStatus::from_raw(0), }) } @@ -32,6 +34,12 @@ impl Env { } } + pub fn idx(&self) -> usize { + match self { + Self::V0(env) => env.idx, + } + } + pub fn set_status(&mut self, status: async_std::process::ExitStatus) { match self { Self::V0(env) => { diff --git a/src/event.rs b/src/event.rs index 85a2127..2faeb2d 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,12 +1,33 @@ -#[derive(Debug)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] pub enum Event { + #[serde( + serialize_with = "serialize_key", + deserialize_with = "deserialize_key" + )] Key(textmode::Key), Resize((u16, u16)), PtyOutput, PtyClose, + ChildSuspend(usize), ClockTimer, } +#[allow(clippy::trivially_copy_pass_by_ref, clippy::needless_pass_by_value)] +fn serialize_key<S>(_key: &textmode::Key, _s: S) -> Result<S::Ok, S::Error> +where + S: serde::Serializer, +{ + todo!() +} + +#[allow(clippy::trivially_copy_pass_by_ref, clippy::needless_pass_by_value)] +fn deserialize_key<'de, D>(_d: D) -> Result<textmode::Key, D::Error> +where + D: serde::Deserializer<'de>, +{ + todo!() +} + pub struct Reader { pending: async_std::sync::Mutex<Pending>, cvar: async_std::sync::Condvar, @@ -55,6 +76,7 @@ struct Pending { size: Option<(u16, u16)>, pty_output: bool, pty_close: bool, + child_suspend: std::collections::VecDeque<usize>, clock_timer: bool, done: bool, } @@ -70,6 +92,7 @@ impl Pending { || self.size.is_some() || self.pty_output || self.pty_close + || !self.child_suspend.is_empty() || self.clock_timer } @@ -87,6 +110,9 @@ impl Pending { self.pty_close = false; return Some(Event::PtyClose); } + if let Some(idx) = self.child_suspend.pop_front() { + return Some(Event::ChildSuspend(idx)); + } if self.clock_timer { self.clock_timer = false; return Some(Event::ClockTimer); @@ -107,6 +133,9 @@ impl Pending { Some(Event::Resize(size)) => self.size = Some(size), Some(Event::PtyOutput) => self.pty_output = true, Some(Event::PtyClose) => self.pty_close = true, + Some(Event::ChildSuspend(idx)) => { + self.child_suspend.push_back(idx); + } Some(Event::ClockTimer) => self.clock_timer = true, None => self.done = true, } diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index 89381c6..f1caf12 100644 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -1,7 +1,7 @@ -use async_std::io::ReadExt as _; +use async_std::io::{ReadExt as _, WriteExt as _}; use async_std::stream::StreamExt as _; use futures_lite::future::FutureExt as _; -use std::os::unix::io::FromRawFd as _; +use std::os::unix::io::{FromRawFd as _, IntoRawFd as _}; use std::os::unix::process::ExitStatusExt as _; const PID0: nix::unistd::Pid = nix::unistd::Pid::from_raw(0); @@ -13,7 +13,7 @@ pub async fn run() -> anyhow::Result<i32> { let env = read_data().await?; let pipeline = crate::parse::Pipeline::parse(env.pipeline().unwrap())?; let (children, pg) = spawn_children(pipeline, &env)?; - let status = wait_children(children, pg).await; + let status = wait_children(children, pg, &env).await; if let Some(signal) = status.signal() { nix::sys::signal::raise(signal.try_into().unwrap())?; } @@ -31,6 +31,14 @@ async fn read_data() -> anyhow::Result<crate::env::Env> { Ok(env) } +async fn write_event(event: crate::event::Event) { + let mut fd4 = unsafe { async_std::fs::File::from_raw_fd(4) }; + fd4.write_all(&bincode::serialize(&event).unwrap()) + .await + .unwrap(); + let _ = fd4.into_raw_fd(); +} + fn spawn_children( pipeline: crate::parse::Pipeline, env: &crate::env::Env, @@ -69,6 +77,7 @@ fn spawn_children( async fn wait_children( children: Vec<Child<'_>>, pg: Option<nix::unistd::Pid>, + env: &crate::env::Env, ) -> std::process::ExitStatus { enum Res { Child(nix::Result<nix::sys::wait::WaitStatus>), @@ -150,8 +159,10 @@ async fn wait_children( } nix::sys::wait::WaitStatus::Stopped(pid, signal) => { if signal == nix::sys::signal::Signal::SIGTSTP { - // TODO: notify the main shell process and have it - // refocus the readline + write_event(crate::event::Event::ChildSuspend( + env.idx(), + )) + .await; if let Err(e) = nix::sys::signal::kill( pid, nix::sys::signal::Signal::SIGCONT, diff --git a/src/state/history/mod.rs b/src/state/history/mod.rs index 430eb28..bf0c11f 100644 --- a/src/state/history/mod.rs +++ b/src/state/history/mod.rs @@ -1,5 +1,6 @@ use async_std::io::WriteExt as _; -use std::os::unix::io::FromRawFd as _; +use futures_lite::future::FutureExt as _; +use std::os::unix::io::{FromRawFd as _, IntoRawFd as _}; use std::os::unix::process::ExitStatusExt as _; mod pty; @@ -104,7 +105,7 @@ impl History { run_commands( ast, async_std::sync::Arc::clone(&entry), - crate::env::Env::new(), + crate::env::Env::new(self.entries.len()), input_r, resize_r, event_w, @@ -525,7 +526,7 @@ fn run_commands( &entry, input_r, resize_r, - event_w, + event_w.clone(), ) { Ok(pty) => pty, Err(e) => { @@ -542,7 +543,8 @@ fn run_commands( for pipeline in ast.pipelines() { env.set_pipeline(pipeline.input_string().to_string()); - let (pipeline_status, done) = run_pipeline(&pty, &env).await; + let (pipeline_status, done) = + run_pipeline(&pty, &env, event_w.clone()).await; env.set_status(pipeline_status); if done { break; @@ -558,32 +560,70 @@ fn run_commands( async fn run_pipeline( pty: &pty::Pty, env: &crate::env::Env, + event_w: async_std::channel::Sender<crate::event::Event>, ) -> (async_std::process::ExitStatus, bool) { let mut cmd = pty_process::Command::new(std::env::current_exe().unwrap()); cmd.arg("--internal-cmd-runner"); - let (r, w) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC).unwrap(); + let (to_r, to_w) = + nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC).unwrap(); + let (from_r, from_w) = + nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC).unwrap(); unsafe { cmd.pre_exec(move || { - nix::unistd::dup2(r, 3)?; + nix::unistd::dup2(to_r, 3)?; + nix::unistd::dup2(from_w, 4)?; Ok(()) }); } let child = pty.spawn(cmd).unwrap(); - nix::unistd::close(r).unwrap(); - - let mut w = unsafe { async_std::fs::File::from_raw_fd(w) }; - w.write_all(&env.as_bytes()).await.unwrap(); - drop(w); - - let status = child.status_no_drop().await.unwrap(); - ( - status, - // i'm not sure what exactly the expected behavior here is - in zsh, - // SIGINT kills the whole command line while SIGTERM doesn't, but i - // don't know what the precise logic is or how other signals are - // handled - status.signal() == Some(signal_hook::consts::signal::SIGINT), - ) + nix::unistd::close(to_r).unwrap(); + nix::unistd::close(from_w).unwrap(); + + let mut to_w = unsafe { async_std::fs::File::from_raw_fd(to_w) }; + to_w.write_all(&env.as_bytes()).await.unwrap(); + drop(to_w); + + loop { + enum Res { + Read(bincode::Result<crate::event::Event>), + Exit(std::io::Result<std::process::ExitStatus>), + } + + let read = async move { + Res::Read( + blocking::unblock(move || { + let fh = unsafe { std::fs::File::from_raw_fd(from_r) }; + let env = bincode::deserialize_from(&fh); + let _ = fh.into_raw_fd(); + env + }) + .await, + ) + }; + let exit = async { Res::Exit(child.status_no_drop().await) }; + match read.or(exit).await { + Res::Read(Ok(event)) => event_w.send(event).await.unwrap(), + Res::Read(Err(e)) => { + eprintln!("nbsh: {}", e); + return (std::process::ExitStatus::from_raw(1 << 8), false); + } + Res::Exit(Ok(status)) => { + return ( + status, + // i'm not sure what exactly the expected behavior here is + // - in zsh, SIGINT kills the whole command line while + // SIGTERM doesn't, but i don't know what the precise + // logic is or how other signals are handled + status.signal() + == Some(signal_hook::consts::signal::SIGINT), + ); + } + Res::Exit(Err(e)) => { + eprintln!("nbsh: {}", e); + return (std::process::ExitStatus::from_raw(1 << 8), false); + } + } + } } fn set_bgcolor(out: &mut impl textmode::Textmode, idx: usize, focus: bool) { diff --git a/src/state/mod.rs b/src/state/mod.rs index 20261cf..656bd7a 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -194,6 +194,11 @@ impl State { } } } + crate::event::Event::ChildSuspend(idx) => { + if self.focus_idx() == Some(idx) { + self.set_focus(Focus::Readline, None).await; + } + } crate::event::Event::ClockTimer => {} }; Some(Action::Refresh) |