From 1a281c87758ca559b4353fbd166dcd8e92a97f3a Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Mon, 3 Jan 2022 23:12:08 -0500 Subject: propagate current directory changes back to the main process --- src/env.rs | 38 +++++++++++++++++++++++--- src/event.rs | 22 ++++++++++----- src/format.rs | 10 +++++++ src/info.rs | 10 ------- src/pipeline/mod.rs | 38 +++++++++++++++++--------- src/state/history/mod.rs | 35 ++++++++++++++++++------ src/state/history/pty.rs | 14 +++++----- src/state/mod.rs | 70 ++++++++++++++++++++++++------------------------ src/state/readline.rs | 12 ++++----- 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, 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 { 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, size: Option<(u16, u16)>, pty_output: bool, - pty_close: bool, + pty_close: std::collections::VecDeque, child_suspend: std::collections::VecDeque, + pipeline_exit: std::collections::VecDeque, 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 { - 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 { 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 { - 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 { // 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 { 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, ) -> anyhow::Result { 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, - ) { + ) -> anyhow::Result { // 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>, 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, ) -> (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, - close_w: async_std::channel::Sender<()>, + close_w: async_std::channel::Sender, } 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>, input_r: async_std::channel::Receiver>, resize_r: async_std::channel::Receiver<(u16, u16)>, - close_r: async_std::channel::Receiver<()>, + close_r: async_std::channel::Receiver, event_w: async_std::channel::Sender, ) { loop { @@ -57,7 +57,7 @@ async fn pty_task( Read(Result), Write(Result, async_std::channel::RecvError>), Resize(Result<(u16, u16), async_std::channel::RecvError>), - Close(Result<(), async_std::channel::RecvError>), + Close(Result), } 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); -- cgit v1.2.3-54-g00ecf