summaryrefslogtreecommitdiffstats
path: root/src/shell/history/entry.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/shell/history/entry.rs')
-rw-r--r--src/shell/history/entry.rs344
1 files changed, 190 insertions, 154 deletions
diff --git a/src/shell/history/entry.rs b/src/shell/history/entry.rs
index a45d99d..0491bf7 100644
--- a/src/shell/history/entry.rs
+++ b/src/shell/history/entry.rs
@@ -1,25 +1,13 @@
use crate::shell::prelude::*;
-enum State {
- Running((usize, usize)),
- Exited(ExitInfo),
-}
-
pub struct Entry {
cmdline: String,
env: Env,
- state: State,
- vt: vt100::Parser,
- audible_bell_state: usize,
- visual_bell_state: usize,
- audible_bell: bool,
- visual_bell: bool,
- real_bell_pending: bool,
+ pty: super::pty::Pty,
fullscreen: Option<bool>,
- input: async_std::channel::Sender<Vec<u8>>,
- resize: async_std::channel::Sender<(u16, u16)>,
- start_time: time::OffsetDateTime,
start_instant: std::time::Instant,
+ start_time: time::OffsetDateTime,
+ state: State,
}
impl Entry {
@@ -27,39 +15,37 @@ impl Entry {
cmdline: String,
env: Env,
size: (u16, u16),
- input: async_std::channel::Sender<Vec<u8>>,
- resize: async_std::channel::Sender<(u16, u16)>,
- ) -> Self {
- let span = (0, cmdline.len());
- Self {
+ event_w: crate::shell::event::Writer,
+ ) -> Result<Self> {
+ let start_instant = std::time::Instant::now();
+ let start_time = time::OffsetDateTime::now_utc();
+
+ let (pty, pts) = super::pty::Pty::new(size, event_w.clone()).unwrap();
+ let (child, fh) = Self::spawn_command(&cmdline, &env, &pts)?;
+ tokio::spawn(Self::task(child, fh, env.idx(), event_w));
+ Ok(Self {
cmdline,
env,
- state: State::Running(span),
- vt: vt100::Parser::new(size.0, size.1, 0),
- audible_bell_state: 0,
- visual_bell_state: 0,
- audible_bell: false,
- visual_bell: false,
- real_bell_pending: false,
- input,
- resize,
+ pty,
fullscreen: None,
- start_time: time::OffsetDateTime::now_utc(),
- start_instant: std::time::Instant::now(),
- }
+ start_instant,
+ start_time,
+ state: State::Running((0, 0)),
+ })
}
pub fn render(
- &mut self,
+ &self,
out: &mut impl textmode::Textmode,
- idx: usize,
entry_count: usize,
- size: (u16, u16),
+ vt: &mut super::pty::Vt,
focused: bool,
scrolling: bool,
offset: time::UtcOffset,
) {
- let time = self.exit_info().map_or_else(
+ let idx = self.env.idx();
+ let size = out.screen().size();
+ let time = self.state.exit_info().map_or_else(
|| {
format!(
"[{}]",
@@ -77,13 +63,11 @@ impl Entry {
},
);
- self.bell(out);
- if focused {
- self.audible_bell = false;
- self.visual_bell = false;
+ if vt.bell(focused) {
+ out.write(b"\x07");
}
- set_bgcolor(out, idx, focused);
+ Self::set_bgcolor(out, idx, focused);
out.set_fgcolor(textmode::color::YELLOW);
let entry_count_width = format!("{}", entry_count + 1).len();
let idx_str = format!("{}", idx + 1);
@@ -92,8 +76,8 @@ impl Entry {
out.write_str(" ");
out.reset_attributes();
- set_bgcolor(out, idx, focused);
- if let Some(info) = self.exit_info() {
+ Self::set_bgcolor(out, idx, focused);
+ if let Some(info) = self.state.exit_info() {
if info.status.signal().is_some() {
out.set_fgcolor(textmode::color::MAGENTA);
} else if info.status.success() {
@@ -107,13 +91,13 @@ impl Entry {
}
out.reset_attributes();
- if self.audible_bell || self.visual_bell {
+ if vt.is_bell() {
out.set_bgcolor(textmode::Color::Rgb(64, 16, 16));
} else {
- set_bgcolor(out, idx, focused);
+ Self::set_bgcolor(out, idx, focused);
}
out.write_str("$ ");
- set_bgcolor(out, idx, focused);
+ Self::set_bgcolor(out, idx, focused);
let start = usize::from(out.screen().cursor_position().1);
let end = usize::from(size.1) - time.len() - 2;
let max_len = end - start;
@@ -130,7 +114,7 @@ impl Entry {
if !cmd[span.0..span.1].is_empty() {
out.set_bgcolor(textmode::Color::Rgb(16, 64, 16));
out.write_str(&cmd[span.0..span.1]);
- set_bgcolor(out, idx, focused);
+ Self::set_bgcolor(out, idx, focused);
}
if !cmd[span.1..].is_empty() {
out.write_str(&cmd[span.1..]);
@@ -155,7 +139,7 @@ impl Entry {
}
out.reset_attributes();
- set_bgcolor(out, idx, focused);
+ Self::set_bgcolor(out, idx, focused);
let cur_pos = out.screen().cursor_position();
out.write_str(&" ".repeat(
usize::from(size.1) - time.len() - 1 - usize::from(cur_pos.1),
@@ -164,7 +148,7 @@ impl Entry {
out.write_str(" ");
out.reset_attributes();
- if self.binary() {
+ if vt.binary() {
let msg = "This appears to be binary data. Fullscreen this entry to view anyway.";
let len: u16 = msg.len().try_into().unwrap();
out.move_to(
@@ -175,7 +159,8 @@ impl Entry {
out.write_str(msg);
out.hide_cursor(true);
} else {
- let last_row = self.output_lines(focused && !scrolling);
+ let last_row =
+ vt.output_lines(focused && !scrolling, self.state.running());
let mut max_lines = self.max_lines(entry_count);
if last_row > max_lines {
out.write(b"\r\n");
@@ -185,7 +170,7 @@ impl Entry {
max_lines -= 1;
}
let mut out_row = out.screen().cursor_position().0 + 1;
- let screen = self.vt.screen();
+ let screen = vt.screen();
let pos = screen.cursor_position();
let mut wrapped = false;
let mut cursor_found = None;
@@ -216,66 +201,41 @@ impl Entry {
}
}
}
- out.reset_attributes();
- }
- pub fn render_fullscreen(&mut self, out: &mut impl textmode::Textmode) {
- out.write(&self.vt.screen().state_formatted());
- self.bell(out);
- self.audible_bell = false;
- self.visual_bell = false;
out.reset_attributes();
}
- pub async fn send_input(&self, bytes: Vec<u8>) {
- if self.running() {
- self.input.send(bytes).await.unwrap();
- }
- }
-
- pub async fn resize(&mut self, size: (u16, u16)) {
- if self.running() {
- self.resize.send(size).await.unwrap();
- self.vt.set_size(size.0, size.1);
- }
+ pub fn render_fullscreen(&self, out: &mut impl textmode::Textmode) {
+ self.pty.with_vt_mut(|vt| {
+ out.write(&vt.screen().state_formatted());
+ if vt.bell(true) {
+ out.write(b"\x07");
+ }
+ out.reset_attributes();
+ });
}
- pub fn size(&self) -> (u16, u16) {
- self.vt.screen().size()
+ pub fn input(&self, bytes: Vec<u8>) {
+ self.pty.input(bytes);
}
- pub fn process(&mut self, input: &[u8]) {
- self.vt.process(input);
- let screen = self.vt.screen();
-
- let new_audible_bell_state = screen.audible_bell_count();
- if new_audible_bell_state != self.audible_bell_state {
- self.audible_bell = true;
- self.real_bell_pending = true;
- self.audible_bell_state = new_audible_bell_state;
- }
-
- let new_visual_bell_state = screen.visual_bell_count();
- if new_visual_bell_state != self.visual_bell_state {
- self.visual_bell = true;
- self.real_bell_pending = true;
- self.visual_bell_state = new_visual_bell_state;
- }
+ pub fn resize(&self, size: (u16, u16)) {
+ self.pty.resize(size);
}
pub fn cmd(&self) -> &str {
&self.cmdline
}
- pub fn env(&self) -> &Env {
- &self.env
+ pub fn start_time(&self) -> time::OffsetDateTime {
+ self.start_time
}
pub fn toggle_fullscreen(&mut self) {
if let Some(fullscreen) = self.fullscreen {
self.fullscreen = Some(!fullscreen);
} else {
- self.fullscreen = Some(!self.vt.screen().alternate_screen());
+ self.fullscreen = Some(!self.pty.fullscreen());
}
}
@@ -284,110 +244,186 @@ impl Entry {
}
pub fn running(&self) -> bool {
- matches!(self.state, State::Running(_))
+ self.state.running()
}
- pub fn binary(&self) -> bool {
- self.vt.screen().errors() > 5
+ pub fn exited(&mut self, exit_info: ExitInfo) {
+ self.state = State::Exited(exit_info);
}
pub fn lines(&self, entry_count: usize, focused: bool) -> usize {
+ let running = self.running();
1 + std::cmp::min(
- self.output_lines(focused),
+ self.pty.with_vt(|vt| vt.output_lines(focused, running)),
self.max_lines(entry_count),
)
}
+ pub fn should_fullscreen(&self) -> bool {
+ self.fullscreen.unwrap_or_else(|| self.pty.fullscreen())
+ }
+
+ pub fn lock_vt(&self) -> std::sync::MutexGuard<super::pty::Vt> {
+ self.pty.lock_vt()
+ }
+
+ pub fn set_span(&mut self, new_span: (usize, usize)) {
+ if let State::Running(ref mut span) = self.state {
+ *span = new_span;
+ }
+ }
+
fn max_lines(&self, entry_count: usize) -> usize {
if self.env.idx() == entry_count - 1 {
- usize::from(self.size().0) * 2 / 3
+ 15
} else {
5
}
}
- pub fn output_lines(&self, focused: bool) -> usize {
- if self.binary() {
- return 1;
+ fn set_bgcolor(
+ out: &mut impl textmode::Textmode,
+ idx: usize,
+ focus: bool,
+ ) {
+ if focus {
+ out.set_bgcolor(textmode::Color::Rgb(0x56, 0x1b, 0x8b));
+ } else if idx % 2 == 0 {
+ out.set_bgcolor(textmode::Color::Rgb(0x24, 0x21, 0x00));
+ } else {
+ out.set_bgcolor(textmode::Color::Rgb(0x20, 0x20, 0x20));
}
+ }
- let screen = self.vt.screen();
- let mut last_row = 0;
- for (idx, row) in screen.rows(0, self.size().1).enumerate() {
- if !row.is_empty() {
- last_row = idx + 1;
- }
+ fn spawn_command(
+ cmdline: &str,
+ env: &Env,
+ pts: &pty_process::Pts,
+ ) -> Result<(tokio::process::Child, std::fs::File)> {
+ let mut cmd = pty_process::Command::new(crate::info::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: from_r was just opened above and is not used anywhere else
+ let fh = unsafe { std::fs::File::from_raw_fd(from_r) };
+ // Safety: dup2 is an async-signal-safe function
+ unsafe {
+ cmd.pre_exec(move || {
+ nix::unistd::dup2(from_w, 3)?;
+ Ok(())
+ });
}
- if focused && self.running() {
- last_row = std::cmp::max(
- last_row,
- usize::from(screen.cursor_position().0) + 1,
- );
- }
- last_row
+ let child = cmd.spawn(pts)?;
+ nix::unistd::close(from_w)?;
+ Ok((child, fh))
}
- pub fn should_fullscreen(&self) -> bool {
- self.fullscreen
- .unwrap_or_else(|| self.vt.screen().alternate_screen())
- }
+ async fn task(
+ mut child: tokio::process::Child,
+ fh: std::fs::File,
+ idx: usize,
+ event_w: crate::shell::event::Writer,
+ ) {
+ enum Res {
+ Read(crate::runner::Event),
+ Exit(std::io::Result<std::process::ExitStatus>),
+ }
- pub fn set_span(&mut self, span: (usize, usize)) {
- if matches!(self.state, State::Running(_)) {
- self.state = State::Running(span);
+ let (read_w, read_r) = tokio::sync::mpsc::unbounded_channel();
+ tokio::task::spawn_blocking(move || loop {
+ let event = bincode::deserialize_from(&fh);
+ match event {
+ Ok(event) => {
+ read_w.send(event).unwrap();
+ }
+ Err(e) => {
+ match &*e {
+ bincode::ErrorKind::Io(io_e) => {
+ assert!(
+ io_e.kind()
+ == std::io::ErrorKind::UnexpectedEof
+ );
+ }
+ e => {
+ panic!("{}", e);
+ }
+ }
+ break;
+ }
+ }
+ });
+
+ let mut stream: futures_util::stream::SelectAll<_> = [
+ tokio_stream::wrappers::UnboundedReceiverStream::new(read_r)
+ .map(Res::Read)
+ .boxed(),
+ futures_util::stream::once(child.wait())
+ .map(Res::Exit)
+ .boxed(),
+ ]
+ .into_iter()
+ .collect();
+ let mut exit_status = None;
+ let mut new_env = None;
+ while let Some(res) = stream.next().await {
+ match res {
+ Res::Read(event) => match event {
+ crate::runner::Event::RunPipeline(new_span) => {
+ // we could just update the span in place here, but we
+ // do this as an event so that we can also trigger a
+ // refresh
+ event_w.send(Event::ChildRunPipeline(idx, new_span));
+ }
+ crate::runner::Event::Suspend => {
+ event_w.send(Event::ChildSuspend(idx));
+ }
+ crate::runner::Event::Exit(env) => {
+ new_env = Some(env);
+ }
+ },
+ Res::Exit(status) => {
+ exit_status = Some(status.unwrap());
+ }
+ }
}
+ event_w.send(Event::ChildExit(
+ idx,
+ ExitInfo::new(exit_status.unwrap()),
+ new_env,
+ ));
}
+}
- pub async fn finish(
- &mut self,
- env: Env,
- event_w: async_std::channel::Sender<Event>,
- ) {
- self.state = State::Exited(ExitInfo::new(env.latest_status()));
- self.env = env;
- event_w.send(Event::PtyClose).await.unwrap();
- }
+enum State {
+ Running((usize, usize)),
+ Exited(ExitInfo),
+}
+impl State {
fn exit_info(&self) -> Option<&ExitInfo> {
- match &self.state {
- State::Running(..) => None,
- State::Exited(exit_info) => Some(exit_info),
+ match self {
+ Self::Running(_) => None,
+ Self::Exited(exit_info) => Some(exit_info),
}
}
- fn bell(&mut self, out: &mut impl textmode::Textmode) {
- if self.real_bell_pending {
- if self.audible_bell {
- out.write(b"\x07");
- }
- if self.visual_bell {
- out.write(b"\x1bg");
- }
- self.real_bell_pending = false;
- }
+ fn running(&self) -> bool {
+ self.exit_info().is_none()
}
}
-struct ExitInfo {
- status: async_std::process::ExitStatus,
+#[derive(Debug)]
+pub struct ExitInfo {
+ status: std::process::ExitStatus,
instant: std::time::Instant,
}
impl ExitInfo {
- fn new(status: async_std::process::ExitStatus) -> Self {
+ fn new(status: std::process::ExitStatus) -> Self {
Self {
status,
instant: std::time::Instant::now(),
}
}
}
-
-fn set_bgcolor(out: &mut impl textmode::Textmode, idx: usize, focus: bool) {
- if focus {
- out.set_bgcolor(textmode::Color::Rgb(0x56, 0x1b, 0x8b));
- } else if idx % 2 == 0 {
- out.set_bgcolor(textmode::Color::Rgb(0x24, 0x21, 0x00));
- } else {
- out.set_bgcolor(textmode::Color::Rgb(0x20, 0x20, 0x20));
- }
-}