summaryrefslogtreecommitdiffstats
path: root/src/shell/history/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/shell/history/mod.rs')
-rw-r--r--src/shell/history/mod.rs302
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);
- }
- }
-}