summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2022-01-03 23:12:08 -0500
committerJesse Luehrs <doy@tozt.net>2022-01-03 23:12:08 -0500
commit1a281c87758ca559b4353fbd166dcd8e92a97f3a (patch)
tree32a01ce3487c98cafb325ab49fceff566a1a3d30
parent10717b2b1f29a1b35742ae0cb98e775ac55e248c (diff)
downloadnbsh-1a281c87758ca559b4353fbd166dcd8e92a97f3a.tar.gz
nbsh-1a281c87758ca559b4353fbd166dcd8e92a97f3a.zip
propagate current directory changes back to the main process
-rw-r--r--src/env.rs38
-rw-r--r--src/event.rs22
-rw-r--r--src/format.rs10
-rw-r--r--src/info.rs10
-rw-r--r--src/pipeline/mod.rs38
-rw-r--r--src/state/history/mod.rs35
-rw-r--r--src/state/history/pty.rs14
-rw-r--r--src/state/mod.rs70
-rw-r--r--src/state/readline.rs12
9 files changed, 159 insertions, 90 deletions
diff --git a/src/env.rs b/src/env.rs
index 7b1d79c..1708c06 100644
--- a/src/env.rs
+++ b/src/env.rs
@@ -1,12 +1,12 @@
use serde::Deserialize as _;
use std::os::unix::process::ExitStatusExt as _;
-#[derive(serde::Serialize, serde::Deserialize)]
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum Env {
V0(V0),
}
-#[derive(serde::Serialize, serde::Deserialize)]
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct V0 {
pipeline: Option<String>,
idx: usize,
@@ -15,14 +15,16 @@ pub struct V0 {
deserialize_with = "deserialize_status"
)]
latest_status: async_std::process::ExitStatus,
+ pwd: std::path::PathBuf,
}
impl Env {
- pub fn new(idx: usize) -> Self {
+ pub fn new() -> Self {
Self::V0(V0 {
pipeline: None,
- idx,
+ idx: 0,
latest_status: std::process::ExitStatus::from_raw(0),
+ pwd: std::env::current_dir().unwrap(),
})
}
@@ -40,6 +42,12 @@ impl Env {
}
}
+ pub fn set_idx(&mut self, idx: usize) {
+ match self {
+ Self::V0(env) => env.idx = idx,
+ }
+ }
+
pub fn set_status(&mut self, status: async_std::process::ExitStatus) {
match self {
Self::V0(env) => {
@@ -48,6 +56,20 @@ impl Env {
}
}
+ pub fn set_current_dir(&mut self, pwd: std::path::PathBuf) {
+ match self {
+ Self::V0(env) => {
+ env.pwd = pwd;
+ }
+ }
+ }
+
+ pub fn current_dir(&self) -> &std::path::Path {
+ match self {
+ Self::V0(env) => &env.pwd,
+ }
+ }
+
pub fn pipeline(&self) -> Option<&str> {
match self {
Self::V0(env) => env.pipeline.as_deref(),
@@ -60,6 +82,14 @@ impl Env {
}
}
+ pub fn apply(&self, cmd: &mut pty_process::Command) {
+ match self {
+ Self::V0(env) => {
+ cmd.current_dir(&env.pwd);
+ }
+ }
+ }
+
pub fn as_bytes(&self) -> Vec<u8> {
bincode::serialize(self).unwrap()
}
diff --git a/src/event.rs b/src/event.rs
index 2faeb2d..f983f10 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -7,8 +7,9 @@ pub enum Event {
Key(textmode::Key),
Resize((u16, u16)),
PtyOutput,
- PtyClose,
+ PtyClose(crate::env::Env),
ChildSuspend(usize),
+ PipelineExit(crate::env::Env),
ClockTimer,
}
@@ -75,8 +76,9 @@ struct Pending {
key: std::collections::VecDeque<textmode::Key>,
size: Option<(u16, u16)>,
pty_output: bool,
- pty_close: bool,
+ pty_close: std::collections::VecDeque<crate::env::Env>,
child_suspend: std::collections::VecDeque<usize>,
+ pipeline_exit: std::collections::VecDeque<crate::env::Env>,
clock_timer: bool,
done: bool,
}
@@ -91,8 +93,9 @@ impl Pending {
|| !self.key.is_empty()
|| self.size.is_some()
|| self.pty_output
- || self.pty_close
+ || !self.pty_close.is_empty()
|| !self.child_suspend.is_empty()
+ || !self.pipeline_exit.is_empty()
|| self.clock_timer
}
@@ -106,13 +109,15 @@ impl Pending {
if let Some(size) = self.size.take() {
return Some(Event::Resize(size));
}
- if self.pty_close {
- self.pty_close = false;
- return Some(Event::PtyClose);
+ if let Some(env) = self.pty_close.pop_front() {
+ return Some(Event::PtyClose(env));
}
if let Some(idx) = self.child_suspend.pop_front() {
return Some(Event::ChildSuspend(idx));
}
+ if let Some(env) = self.pipeline_exit.pop_front() {
+ return Some(Event::PipelineExit(env));
+ }
if self.clock_timer {
self.clock_timer = false;
return Some(Event::ClockTimer);
@@ -132,10 +137,13 @@ impl Pending {
Some(Event::Key(key)) => self.key.push_back(key),
Some(Event::Resize(size)) => self.size = Some(size),
Some(Event::PtyOutput) => self.pty_output = true,
- Some(Event::PtyClose) => self.pty_close = true,
+ Some(Event::PtyClose(env)) => self.pty_close.push_back(env),
Some(Event::ChildSuspend(idx)) => {
self.child_suspend.push_back(idx);
}
+ Some(Event::PipelineExit(env)) => {
+ self.pipeline_exit.push_back(env);
+ }
Some(Event::ClockTimer) => self.clock_timer = true,
None => self.done = true,
}
diff --git a/src/format.rs b/src/format.rs
index 1560439..f2414cc 100644
--- a/src/format.rs
+++ b/src/format.rs
@@ -1,5 +1,15 @@
use std::os::unix::process::ExitStatusExt as _;
+pub fn path(path: &std::path::Path) -> String {
+ let mut path = path.display().to_string();
+ if let Ok(home) = std::env::var("HOME") {
+ if path.starts_with(&home) {
+ path.replace_range(..home.len(), "~");
+ }
+ }
+ path
+}
+
pub fn exit_status(status: std::process::ExitStatus) -> String {
status.signal().map_or_else(
|| format!("{:03} ", status.code().unwrap()),
diff --git a/src/info.rs b/src/info.rs
index 3ff2576..5653326 100644
--- a/src/info.rs
+++ b/src/info.rs
@@ -1,13 +1,3 @@
-pub fn pwd() -> anyhow::Result<String> {
- let mut pwd = std::env::current_dir()?.display().to_string();
- if let Ok(home) = std::env::var("HOME") {
- if pwd.starts_with(&home) {
- pwd.replace_range(..home.len(), "~");
- }
- }
- Ok(pwd)
-}
-
pub fn user() -> anyhow::Result<String> {
Ok(users::get_current_username()
.ok_or_else(|| anyhow::anyhow!("couldn't get username"))?
diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs
index 6be63f6..fd7d569 100644
--- a/src/pipeline/mod.rs
+++ b/src/pipeline/mod.rs
@@ -10,16 +10,26 @@ mod command;
pub use command::{Child, Command};
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, &env).await;
+ let mut env = read_data().await?;
+ run_with_env(&mut env).await?;
+ let status = *env.latest_status();
+ let pwd = std::env::current_dir()?;
+ env.set_current_dir(pwd);
+ write_event(crate::event::Event::PipelineExit(env)).await?;
if let Some(signal) = status.signal() {
nix::sys::signal::raise(signal.try_into().unwrap())?;
}
Ok(status.code().unwrap())
}
+async fn run_with_env(env: &mut crate::env::Env) -> anyhow::Result<()> {
+ let pipeline = crate::parse::Pipeline::parse(env.pipeline().unwrap())?;
+ let (children, pg) = spawn_children(pipeline, env)?;
+ let status = wait_children(children, pg, env).await;
+ env.set_status(status);
+ Ok(())
+}
+
async fn read_data() -> anyhow::Result<crate::env::Env> {
// Safety: this code is only called by crate::history::run_pipeline, which
// passes data through on fd 3, and which will not spawn this process
@@ -31,13 +41,12 @@ async fn read_data() -> anyhow::Result<crate::env::Env> {
Ok(env)
}
-async fn write_event(event: crate::event::Event) {
+async fn write_event(event: crate::event::Event) -> anyhow::Result<()> {
let mut fd4 = unsafe { async_std::fs::File::from_raw_fd(4) };
- fd4.write_all(&bincode::serialize(&event).unwrap())
- .await
- .unwrap();
- fd4.flush().await.unwrap();
+ fd4.write_all(&bincode::serialize(&event)?).await?;
+ fd4.flush().await?;
let _ = fd4.into_raw_fd();
+ Ok(())
}
fn spawn_children(
@@ -160,10 +169,13 @@ async fn wait_children(
}
nix::sys::wait::WaitStatus::Stopped(pid, signal) => {
if signal == nix::sys::signal::Signal::SIGTSTP {
- write_event(crate::event::Event::ChildSuspend(
- env.idx(),
- ))
- .await;
+ if let Err(e) = write_event(
+ crate::event::Event::ChildSuspend(env.idx()),
+ )
+ .await
+ {
+ bail!(e);
+ }
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 5ed86b3..3bddbde 100644
--- a/src/state/history/mod.rs
+++ b/src/state/history/mod.rs
@@ -89,6 +89,7 @@ impl History {
pub async fn run(
&mut self,
ast: crate::parse::Commands,
+ env: crate::env::Env,
event_w: async_std::channel::Sender<crate::event::Event>,
) -> anyhow::Result<usize> {
let (input_w, input_r) = async_std::channel::unbounded();
@@ -97,6 +98,7 @@ impl History {
let entry = async_std::sync::Arc::new(async_std::sync::Mutex::new(
Entry::new(
ast.input_string().to_string(),
+ env.clone(),
self.size,
input_w,
resize_w,
@@ -105,7 +107,7 @@ impl History {
run_commands(
ast,
async_std::sync::Arc::clone(&entry),
- crate::env::Env::new(self.entries.len()),
+ env.clone(),
input_r,
resize_r,
event_w,
@@ -118,8 +120,9 @@ impl History {
pub async fn parse_error(
&mut self,
e: crate::parse::Error,
+ env: crate::env::Env,
event_w: async_std::channel::Sender<crate::event::Event>,
- ) {
+ ) -> anyhow::Result<usize> {
// XXX would be great to not have to do this
let (input_w, input_r) = async_std::channel::unbounded();
let (resize_w, resize_r) = async_std::channel::unbounded();
@@ -129,15 +132,24 @@ impl History {
resize_r.close();
let err_str = format!("{}", e);
- let mut entry =
- Entry::new(e.into_input(), self.size, input_w, resize_w);
+ let mut entry = Entry::new(
+ e.into_input(),
+ env.clone(),
+ self.size,
+ input_w,
+ resize_w,
+ );
entry.vt.process(err_str.replace('\n', "\r\n").as_bytes());
let status = async_std::process::ExitStatus::from_raw(1 << 8);
entry.exit_info = Some(ExitInfo::new(status));
self.entries.push(async_std::sync::Arc::new(
async_std::sync::Mutex::new(entry),
));
- event_w.send(crate::event::Event::PtyClose).await.unwrap();
+ event_w
+ .send(crate::event::Event::PtyClose(env.clone()))
+ .await
+ .unwrap();
+ Ok(self.entries.len() - 1)
}
pub async fn entry(
@@ -257,6 +269,7 @@ impl std::iter::DoubleEndedIterator for VisibleEntries {
pub struct Entry {
cmdline: String,
+ env: crate::env::Env,
vt: vt100::Parser,
audible_bell_state: usize,
visual_bell_state: usize,
@@ -271,12 +284,14 @@ pub struct Entry {
impl Entry {
fn new(
cmdline: String,
+ env: crate::env::Env,
size: (u16, u16),
input: async_std::channel::Sender<Vec<u8>>,
resize: async_std::channel::Sender<(u16, u16)>,
) -> Self {
Self {
cmdline,
+ env,
vt: vt100::Parser::new(size.0, size.1, 0),
audible_bell_state: 0,
visual_bell_state: 0,
@@ -544,7 +559,7 @@ fn run_commands(
for pipeline in ast.pipelines() {
env.set_pipeline(pipeline.input_string().to_string());
let (pipeline_status, done) =
- run_pipeline(&pty, &env, event_w.clone()).await;
+ run_pipeline(&pty, &mut env, event_w.clone()).await;
env.set_status(pipeline_status);
if done {
break;
@@ -553,17 +568,18 @@ fn run_commands(
entry.lock_arc().await.exit_info =
Some(ExitInfo::new(*env.latest_status()));
- pty.close().await;
+ pty.close(env).await;
});
}
async fn run_pipeline(
pty: &pty::Pty,
- env: &crate::env::Env,
+ env: &mut 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");
+ env.apply(&mut cmd);
let (to_r, to_w) =
nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC).unwrap();
let (from_r, from_w) =
@@ -602,6 +618,9 @@ async fn run_pipeline(
};
let exit = async { Res::Exit(child.status_no_drop().await) };
match read.or(exit).await {
+ Res::Read(Ok(crate::event::Event::PipelineExit(new_env))) => {
+ *env = new_env;
+ }
Res::Read(Ok(event)) => event_w.send(event).await.unwrap(),
Res::Read(Err(e)) => {
if let bincode::ErrorKind::Io(e) = &*e {
diff --git a/src/state/history/pty.rs b/src/state/history/pty.rs
index 4df5358..4ac0f7d 100644
--- a/src/state/history/pty.rs
+++ b/src/state/history/pty.rs
@@ -3,7 +3,7 @@ use futures_lite::future::FutureExt as _;
pub struct Pty {
pty: async_std::sync::Arc<pty_process::Pty>,
- close_w: async_std::channel::Sender<()>,
+ close_w: async_std::channel::Sender<crate::env::Env>,
}
impl Pty {
@@ -39,8 +39,8 @@ impl Pty {
Ok(cmd.spawn(&self.pty)?)
}
- pub async fn close(&self) {
- self.close_w.send(()).await.unwrap();
+ pub async fn close(&self, env: crate::env::Env) {
+ self.close_w.send(env).await.unwrap();
}
}
@@ -49,7 +49,7 @@ async fn pty_task(
entry: async_std::sync::Arc<async_std::sync::Mutex<super::Entry>>,
input_r: async_std::channel::Receiver<Vec<u8>>,
resize_r: async_std::channel::Receiver<(u16, u16)>,
- close_r: async_std::channel::Receiver<()>,
+ close_r: async_std::channel::Receiver<crate::env::Env>,
event_w: async_std::channel::Sender<crate::event::Event>,
) {
loop {
@@ -57,7 +57,7 @@ async fn pty_task(
Read(Result<usize, std::io::Error>),
Write(Result<Vec<u8>, async_std::channel::RecvError>),
Resize(Result<(u16, u16), async_std::channel::RecvError>),
- Close(Result<(), async_std::channel::RecvError>),
+ Close(Result<crate::env::Env, async_std::channel::RecvError>),
}
let mut buf = [0_u8; 4096];
let read = async { Res::Read((&*pty).read(&mut buf).await) };
@@ -98,9 +98,9 @@ async fn pty_task(
}
},
Res::Close(res) => match res {
- Ok(()) => {
+ Ok(env) => {
event_w
- .send(crate::event::Event::PtyClose)
+ .send(crate::event::Event::PtyClose(env))
.await
.unwrap();
return;
diff --git a/src/state/mod.rs b/src/state/mod.rs
index 656bd7a..83d75c5 100644
--- a/src/state/mod.rs
+++ b/src/state/mod.rs
@@ -24,6 +24,7 @@ pub enum Action {
pub struct State {
readline: readline::Readline,
history: history::History,
+ env: crate::env::Env,
focus: Focus,
scene: Scene,
escape: bool,
@@ -36,6 +37,7 @@ impl State {
Self {
readline: readline::Readline::new(),
history: history::History::new(),
+ env: crate::env::Env::new(),
focus: Focus::Readline,
scene: Scene::Readline,
escape: false,
@@ -65,12 +67,7 @@ impl State {
)
.await?;
self.readline
- .render(
- out,
- self.history.entry_count(),
- true,
- self.offset,
- )
+ .render(out, &self.env, true, self.offset)
.await?;
}
Focus::History(idx) => {
@@ -90,12 +87,7 @@ impl State {
.await?;
let pos = out.screen().cursor_position();
self.readline
- .render(
- out,
- self.history.entry_count(),
- false,
- self.offset,
- )
+ .render(out, &self.env, false, self.offset)
.await?;
out.move_to(pos.0, pos.1);
}
@@ -111,12 +103,7 @@ impl State {
)
.await?;
self.readline
- .render(
- out,
- self.history.entry_count(),
- idx.is_none(),
- self.offset,
- )
+ .render(out, &self.env, idx.is_none(), self.offset)
.await?;
out.hide_cursor(true);
}
@@ -178,10 +165,15 @@ impl State {
.await;
self.scene = self.default_scene(self.focus, None).await;
}
- crate::event::Event::PtyClose => {
+ crate::event::Event::PtyClose(env) => {
if let Some(idx) = self.focus_idx() {
let entry = self.history.entry(idx).await;
if !entry.running() {
+ if self.hide_readline {
+ let idx = self.env.idx();
+ self.env = env;
+ self.env.set_idx(idx);
+ }
self.set_focus(
if self.hide_readline {
Focus::Readline
@@ -199,6 +191,10 @@ impl State {
self.set_focus(Focus::Readline, None).await;
}
}
+ crate::event::Event::PipelineExit(_) => {
+ // this should be handled by the pipeline runner directly
+ unreachable!();
+ }
crate::event::Event::ClockTimer => {}
};
Some(Action::Refresh)
@@ -225,23 +221,25 @@ impl State {
self.readline.clear_input();
let entry = self.history.entry(idx).await;
let input = entry.cmd();
- match self.parse(input) {
+ let idx = match self.parse(input) {
Ok(ast) => {
let idx = self
.history
- .run(ast, event_w.clone())
+ .run(ast, self.env.clone(), event_w.clone())
.await
.unwrap();
self.set_focus(Focus::History(idx), Some(entry))
.await;
self.hide_readline = true;
+ idx
}
- Err(e) => {
- self.history
- .parse_error(e, event_w.clone())
- .await;
- }
- }
+ Err(e) => self
+ .history
+ .parse_error(e, self.env.clone(), event_w.clone())
+ .await
+ .unwrap(),
+ };
+ self.env.set_idx(idx + 1);
} else {
self.set_focus(Focus::Readline, None).await;
}
@@ -341,22 +339,24 @@ impl State {
textmode::Key::Ctrl(b'm') => {
let input = self.readline.input();
if !input.is_empty() {
- match self.parse(input) {
+ let idx = match self.parse(input) {
Ok(ast) => {
let idx = self
.history
- .run(ast, event_w.clone())
+ .run(ast, self.env.clone(), event_w.clone())
.await
.unwrap();
self.set_focus(Focus::History(idx), None).await;
self.hide_readline = true;
+ idx
}
- Err(e) => {
- self.history
- .parse_error(e, event_w.clone())
- .await;
- }
- }
+ Err(e) => self
+ .history
+ .parse_error(e, self.env.clone(), event_w.clone())
+ .await
+ .unwrap(),
+ };
+ self.env.set_idx(idx + 1);
self.readline.clear_input();
}
}
diff --git a/src/state/readline.rs b/src/state/readline.rs
index db19bd7..33ee1b4 100644
--- a/src/state/readline.rs
+++ b/src/state/readline.rs
@@ -20,11 +20,11 @@ impl Readline {
pub async fn render(
&self,
out: &mut impl textmode::Textmode,
- entry_count: usize,
+ env: &crate::env::Env,
focus: bool,
offset: time::UtcOffset,
) -> anyhow::Result<()> {
- let pwd = crate::info::pwd()?;
+ let pwd = env.current_dir();
let user = crate::info::user()?;
let hostname = crate::info::hostname()?;
let time = crate::info::time(offset)?;
@@ -37,24 +37,24 @@ impl Readline {
out.move_to(self.size.0 - 2, 0);
if focus {
out.set_bgcolor(textmode::Color::Rgb(0x56, 0x1b, 0x8b));
- } else if entry_count % 2 == 0 {
+ } else if env.idx() % 2 == 0 {
out.set_bgcolor(textmode::Color::Rgb(0x24, 0x21, 0x00));
} else {
out.set_bgcolor(textmode::Color::Rgb(0x20, 0x20, 0x20));
}
out.write(b"\x1b[K");
out.set_fgcolor(textmode::color::YELLOW);
- out.write_str(&format!("{}", entry_count + 1));
+ out.write_str(&format!("{}", env.idx() + 1));
out.reset_attributes();
if focus {
out.set_bgcolor(textmode::Color::Rgb(0x56, 0x1b, 0x8b));
- } else if entry_count % 2 == 0 {
+ } else if env.idx() % 2 == 0 {
out.set_bgcolor(textmode::Color::Rgb(0x24, 0x21, 0x00));
} else {
out.set_bgcolor(textmode::Color::Rgb(0x20, 0x20, 0x20));
}
out.write_str(" (");
- out.write_str(&pwd);
+ out.write_str(&crate::format::path(pwd));
out.write_str(")");
out.move_to(self.size.0 - 2, self.size.1 - 4 - idlen - timelen);
out.write_str(&id);