diff options
Diffstat (limited to 'src/shell/history/mod.rs')
-rw-r--r-- | src/shell/history/mod.rs | 302 |
1 files changed, 64 insertions, 238 deletions
diff --git a/src/shell/history/mod.rs b/src/shell/history/mod.rs index 1bc4e62..91149c1 100644 --- a/src/shell/history/mod.rs +++ b/src/shell/history/mod.rs @@ -1,12 +1,12 @@ use crate::shell::prelude::*; mod entry; -pub use entry::Entry; +pub use entry::{Entry, ExitInfo}; mod pty; pub struct History { size: (u16, u16), - entries: Vec<crate::mutex::Mutex<Entry>>, + entries: Vec<Entry>, scroll_pos: usize, } @@ -19,31 +19,27 @@ impl History { } } - pub async fn render( + pub fn render( &self, out: &mut impl textmode::Textmode, repl_lines: usize, focus: Option<usize>, scrolling: bool, offset: time::UtcOffset, - ) -> anyhow::Result<()> { - let mut used_lines = repl_lines; + ) { let mut cursor = None; - for (idx, mut entry) in - self.visible(repl_lines, focus, scrolling).await.rev() + for (idx, used_lines, mut vt) in + self.visible(repl_lines, focus, scrolling).rev() { let focused = focus.map_or(false, |focus| idx == focus); - used_lines += - entry.lines(self.entry_count(), focused && !scrolling); out.move_to( (usize::from(self.size.0) - used_lines).try_into().unwrap(), 0, ); - entry.render( + self.entries[idx].render( out, - idx, self.entry_count(), - self.size, + &mut *vt, focused, scrolling, offset, @@ -59,67 +55,38 @@ impl History { out.move_to(pos.0, pos.1); out.hide_cursor(hide); } - Ok(()) } - pub async fn render_fullscreen( - &self, - out: &mut impl textmode::Textmode, - idx: usize, - ) { - let mut entry = self.entries[idx].lock_arc().await; - entry.render_fullscreen(out); + pub fn entry(&self, idx: usize) -> &Entry { + &self.entries[idx] } - pub async fn send_input(&mut self, idx: usize, input: Vec<u8>) { - self.entry(idx).await.send_input(input).await; + pub fn entry_mut(&mut self, idx: usize) -> &mut Entry { + &mut self.entries[idx] } - pub async fn resize(&mut self, size: (u16, u16)) { + pub fn resize(&mut self, size: (u16, u16)) { self.size = size; for entry in &self.entries { - entry.lock_arc().await.resize(size).await; + entry.resize(size); } } - pub async fn run( + pub fn run( &mut self, - cmdline: &str, - env: &Env, - event_w: async_std::channel::Sender<Event>, - ) -> anyhow::Result<usize> { - let (input_w, input_r) = async_std::channel::unbounded(); - let (resize_w, resize_r) = async_std::channel::unbounded(); - - let entry = crate::mutex::new(Entry::new( - cmdline.to_string(), - env.clone(), - self.size, - input_w, - resize_w, - )); - run_commands( - cmdline.to_string(), - crate::mutex::clone(&entry), - env.clone(), - input_r, - resize_r, - event_w, - ); - - self.entries.push(entry); - Ok(self.entries.len() - 1) - } - - pub async fn entry(&self, idx: usize) -> crate::mutex::Guard<Entry> { - self.entries[idx].lock_arc().await + cmdline: String, + env: Env, + event_w: crate::shell::event::Writer, + ) { + self.entries + .push(Entry::new(cmdline, env, self.size, event_w).unwrap()); } pub fn entry_count(&self) -> usize { self.entries.len() } - pub async fn make_focus_visible( + pub fn make_focus_visible( &mut self, repl_lines: usize, focus: Option<usize>, @@ -134,8 +101,7 @@ impl History { while focus < self .visible(repl_lines, Some(focus), scrolling) - .await - .map(|(idx, _)| idx) + .map(|(idx, ..)| idx) .next() .unwrap() { @@ -149,8 +115,7 @@ impl History { while focus > self .visible(repl_lines, Some(focus), scrolling) - .await - .map(|(idx, _)| idx) + .map(|(idx, ..)| idx) .last() .unwrap() { @@ -158,225 +123,86 @@ impl History { } } - async fn visible( + pub async fn save(&self) { + // TODO: we'll probably want some amount of flock or something here + let mut fh = tokio::fs::OpenOptions::new() + .append(true) + .open(crate::dirs::history_file()) + .await + .unwrap(); + for entry in &self.entries { + fh.write_all( + format!( + ": {}:0;{}\n", + entry.start_time().unix_timestamp(), + entry.cmd() + ) + .as_bytes(), + ) + .await + .unwrap(); + } + } + + fn visible( &self, repl_lines: usize, focus: Option<usize>, scrolling: bool, ) -> VisibleEntries { let mut iter = VisibleEntries::new(); - if self.entries.is_empty() { - return iter; - } - let mut used_lines = repl_lines; for (idx, entry) in self.entries.iter().enumerate().rev().skip(self.scroll_pos) { - let entry = entry.lock_arc().await; let focused = focus.map_or(false, |focus| idx == focus); used_lines += entry.lines(self.entry_count(), focused && !scrolling); if used_lines > usize::from(self.size.0) { break; } - iter.add(idx, entry); + iter.add(idx, used_lines, entry.lock_vt()); } iter } } -struct VisibleEntries { - entries: std::collections::VecDeque<(usize, crate::mutex::Guard<Entry>)>, +struct VisibleEntries<'a> { + entries: std::collections::VecDeque<( + usize, + usize, + std::sync::MutexGuard<'a, pty::Vt>, + )>, } -impl VisibleEntries { +impl<'a> VisibleEntries<'a> { fn new() -> Self { Self { entries: std::collections::VecDeque::new(), } } - fn add(&mut self, idx: usize, entry: crate::mutex::Guard<Entry>) { + fn add( + &mut self, + idx: usize, + offset: usize, + vt: std::sync::MutexGuard<'a, pty::Vt>, + ) { // push_front because we are adding them in reverse order - self.entries.push_front((idx, entry)); + self.entries.push_front((idx, offset, vt)); } } -impl std::iter::Iterator for VisibleEntries { - type Item = (usize, crate::mutex::Guard<Entry>); +impl<'a> std::iter::Iterator for VisibleEntries<'a> { + type Item = (usize, usize, std::sync::MutexGuard<'a, pty::Vt>); fn next(&mut self) -> Option<Self::Item> { self.entries.pop_front() } } -impl std::iter::DoubleEndedIterator for VisibleEntries { +impl<'a> std::iter::DoubleEndedIterator for VisibleEntries<'a> { fn next_back(&mut self) -> Option<Self::Item> { self.entries.pop_back() } } - -fn run_commands( - cmdline: String, - entry: crate::mutex::Mutex<Entry>, - mut env: Env, - input_r: async_std::channel::Receiver<Vec<u8>>, - resize_r: async_std::channel::Receiver<(u16, u16)>, - event_w: async_std::channel::Sender<Event>, -) { - async_std::task::spawn(async move { - let pty = match pty::Pty::new( - entry.lock_arc().await.size(), - &entry, - input_r, - resize_r, - event_w.clone(), - ) { - Ok(pty) => pty, - Err(e) => { - let mut entry = entry.lock_arc().await; - entry.process( - format!("nbsh: failed to allocate pty: {}\r\n", e) - .as_bytes(), - ); - env.set_status(async_std::process::ExitStatus::from_raw( - 1 << 8, - )); - entry.finish(env, event_w).await; - return; - } - }; - - let status = - match spawn_commands(&cmdline, &pty, &mut env, event_w.clone()) - .await - { - Ok(status) => status, - Err(e) => { - let mut entry = entry.lock_arc().await; - entry.process( - format!( - "nbsh: failed to spawn {}: {}\r\n", - cmdline, e - ) - .as_bytes(), - ); - env.set_status(async_std::process::ExitStatus::from_raw( - 1 << 8, - )); - entry.finish(env, event_w).await; - return; - } - }; - env.set_status(status); - - entry.lock_arc().await.finish(env, event_w).await; - pty.close().await; - }); -} - -async fn spawn_commands( - cmdline: &str, - pty: &pty::Pty, - env: &mut Env, - event_w: async_std::channel::Sender<Event>, -) -> anyhow::Result<async_std::process::ExitStatus> { - let mut cmd = pty_process::Command::new(std::env::current_exe()?); - cmd.args(&["-c", cmdline, "--status-fd", "3"]); - env.apply(&mut cmd); - let (from_r, from_w) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?; - // Safety: dup2 is an async-signal-safe function - unsafe { - cmd.pre_exec(move || { - nix::unistd::dup2(from_w, 3)?; - Ok(()) - }); - } - let child = pty.spawn(cmd)?; - nix::unistd::close(from_w)?; - - let (read_w, read_r) = async_std::channel::unbounded(); - let new_read = move || { - let read_w = read_w.clone(); - async_std::task::spawn(async move { - let event = blocking::unblock(move || { - // Safety: from_r was just opened above and is only - // referenced in this closure, which takes ownership of it - // at the start and returns ownership of it at the end - let fh = unsafe { std::fs::File::from_raw_fd(from_r) }; - let event = bincode::deserialize_from(&fh); - let _ = fh.into_raw_fd(); - event - }) - .await; - if read_w.is_closed() { - // we should never drop read_r while there are still valid - // things to read - assert!(event.is_err()); - } else { - read_w.send(event).await.unwrap(); - } - }); - }; - - new_read(); - let mut read_done = false; - let mut exit_done = None; - loop { - enum Res { - Read(bincode::Result<crate::runner::Event>), - Exit(std::io::Result<std::process::ExitStatus>), - } - - let read_r = read_r.clone(); - let read = async move { Res::Read(read_r.recv().await.unwrap()) }; - let exit = async { - Res::Exit(if exit_done.is_none() { - child.status_no_drop().await - } else { - std::future::pending().await - }) - }; - match read.or(exit).await { - Res::Read(Ok(event)) => match event { - crate::runner::Event::RunPipeline(idx, span) => { - event_w - .send(Event::ChildRunPipeline(idx, span)) - .await - .unwrap(); - new_read(); - } - crate::runner::Event::Suspend(idx) => { - event_w.send(Event::ChildSuspend(idx)).await.unwrap(); - new_read(); - } - crate::runner::Event::Exit(new_env) => { - *env = new_env; - read_done = true; - } - }, - Res::Read(Err(e)) => { - if let bincode::ErrorKind::Io(io_e) = &*e { - if io_e.kind() == std::io::ErrorKind::UnexpectedEof { - read_done = true; - } else { - anyhow::bail!(e); - } - } else { - anyhow::bail!(e); - } - } - Res::Exit(Ok(status)) => { - exit_done = Some(status); - } - Res::Exit(Err(e)) => { - anyhow::bail!(e); - } - } - if let (true, Some(status)) = (read_done, exit_done) { - nix::unistd::close(from_r)?; - return Ok(status); - } - } -} |