summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2022-01-03 10:41:54 -0500
committerJesse Luehrs <doy@tozt.net>2022-01-03 10:41:54 -0500
commit5b75e39514b75696fd812e711e59ce1b53b8b412 (patch)
treeff207933fa9a672457c686d52a95e3a42b455ff7 /src
parent39f1b8bec3891b3fcc7f60aea86e2ab2f8c3ea8a (diff)
downloadnbsh-5b75e39514b75696fd812e711e59ce1b53b8b412.tar.gz
nbsh-5b75e39514b75696fd812e711e59ce1b53b8b412.zip
refocus the readline when the foreground process is suspended
Diffstat (limited to 'src')
-rw-r--r--src/env.rs10
-rw-r--r--src/event.rs31
-rw-r--r--src/pipeline/mod.rs21
-rw-r--r--src/state/history/mod.rs82
-rw-r--r--src/state/mod.rs5
5 files changed, 121 insertions, 28 deletions
diff --git a/src/env.rs b/src/env.rs
index f78b790..7b1d79c 100644
--- a/src/env.rs
+++ b/src/env.rs
@@ -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)