diff options
Diffstat (limited to 'src/history.rs')
-rw-r--r-- | src/history.rs | 407 |
1 files changed, 0 insertions, 407 deletions
diff --git a/src/history.rs b/src/history.rs deleted file mode 100644 index d6f198a..0000000 --- a/src/history.rs +++ /dev/null @@ -1,407 +0,0 @@ -use async_std::io::{ReadExt as _, WriteExt as _}; -use futures_lite::future::FutureExt as _; -use pty_process::Command as _; -use std::os::unix::process::ExitStatusExt as _; -use textmode::Textmode as _; - -pub struct History { - size: (u16, u16), - entries: Vec<crate::util::Mutex<HistoryEntry>>, -} - -impl History { - pub fn new() -> Self { - Self { - size: (24, 80), - entries: vec![], - } - } - - pub async fn handle_key(&self, key: textmode::Key, idx: usize) { - let entry = self.entries[idx].lock_arc().await; - if entry.running() { - entry.input.send(key.into_bytes()).await.unwrap(); - } - } - - pub async fn render( - &self, - out: &mut textmode::Output, - repl_lines: usize, - focus: Option<usize>, - ) -> anyhow::Result<()> { - if let Some(idx) = focus { - let mut entry = self.entries[idx].lock_arc().await; - if entry.should_fullscreen() { - entry.render_fullscreen(out); - return Ok(()); - } - } - - let mut used_lines = repl_lines; - let mut pos = None; - for (idx, entry) in self.entries.iter().enumerate().rev() { - let mut entry = entry.lock_arc().await; - let focused = focus.map_or(false, |focus| idx == focus); - let last_row = entry.lines(self.size.1, focused); - used_lines += 1 + std::cmp::min(6, last_row); - if used_lines > self.size.0 as usize { - break; - } - if focused && used_lines == 1 && entry.running() { - used_lines = 2; - } - out.move_to( - (self.size.0 as usize - used_lines).try_into().unwrap(), - 0, - ); - entry.render(out, self.size.1, focused); - if focused { - pos = Some(out.screen().cursor_position()); - } - } - if let Some(pos) = pos { - out.move_to(pos.0, pos.1); - } - Ok(()) - } - - pub async fn resize(&mut self, size: (u16, u16)) { - self.size = size; - for entry in &self.entries { - let entry = entry.lock_arc().await; - if entry.running() { - entry.resize.send(size).await.unwrap(); - } - } - } - - pub async fn run( - &mut self, - cmd: &str, - action_w: async_std::channel::Sender<crate::action::Action>, - ) -> anyhow::Result<usize> { - let (exe, args) = crate::parse::cmd(cmd); - let (input_w, input_r) = async_std::channel::unbounded(); - let (resize_w, resize_r) = async_std::channel::unbounded(); - let entry = crate::util::mutex(HistoryEntry::new( - cmd, self.size, input_w, resize_w, - )); - if crate::builtins::is(&exe) { - let code: i32 = crate::builtins::run(&exe, &args).into(); - entry.lock_arc().await.exit_info = Some(ExitInfo::new( - async_std::process::ExitStatus::from_raw(code << 8), - )); - action_w - .send(crate::action::Action::UpdateFocus( - crate::state::Focus::Readline, - )) - .await - .unwrap(); - } else { - let mut process = async_std::process::Command::new(&exe); - process.args(&args); - let child = process - .spawn_pty(Some(&pty_process::Size::new( - self.size.0, - self.size.1, - ))) - .unwrap(); - run_process( - child, - async_std::sync::Arc::clone(&entry), - input_r, - resize_r, - action_w, - ); - } - self.entries.push(entry); - Ok(self.entries.len() - 1) - } - - pub async fn toggle_fullscreen(&mut self, idx: usize) { - self.entries[idx].lock_arc().await.toggle_fullscreen(); - } - - pub async fn is_fullscreen(&self, idx: usize) -> bool { - self.entries[idx].lock_arc().await.should_fullscreen() - } - - pub fn entry_count(&self) -> usize { - self.entries.len() - } -} - -struct HistoryEntry { - cmd: String, - vt: vt100::Parser, - audible_bell_state: usize, - visual_bell_state: usize, - fullscreen: Option<bool>, - input: async_std::channel::Sender<Vec<u8>>, - resize: async_std::channel::Sender<(u16, u16)>, - start_time: chrono::DateTime<chrono::Local>, - start_instant: std::time::Instant, - exit_info: Option<ExitInfo>, -} - -impl HistoryEntry { - fn new( - cmd: &str, - size: (u16, u16), - input: async_std::channel::Sender<Vec<u8>>, - resize: async_std::channel::Sender<(u16, u16)>, - ) -> Self { - Self { - cmd: cmd.into(), - vt: vt100::Parser::new(size.0, size.1, 0), - audible_bell_state: 0, - visual_bell_state: 0, - input, - resize, - fullscreen: None, - start_time: chrono::Local::now(), - start_instant: std::time::Instant::now(), - exit_info: None, - } - } - - fn render( - &mut self, - out: &mut textmode::Output, - width: u16, - focused: bool, - ) { - out.set_bgcolor(textmode::Color::Rgb(32, 32, 32)); - if let Some(info) = self.exit_info { - if info.status.signal().is_some() { - out.set_fgcolor(textmode::color::MAGENTA); - } else if info.status.success() { - out.set_fgcolor(textmode::color::DARKGREY); - } else { - out.set_fgcolor(textmode::color::RED); - } - out.write_str(&crate::format::exit_status(info.status)); - } else { - out.write_str(" "); - } - out.reset_attributes(); - if focused { - out.set_fgcolor(textmode::color::BLACK); - out.set_bgcolor(textmode::color::CYAN); - } else { - out.set_bgcolor(textmode::Color::Rgb(32, 32, 32)); - } - out.write_str("$ "); - out.reset_attributes(); - out.set_bgcolor(textmode::Color::Rgb(32, 32, 32)); - if self.running() { - out.set_bgcolor(textmode::Color::Rgb(16, 64, 16)); - } - out.write_str(&self.cmd); - out.reset_attributes(); - out.set_bgcolor(textmode::Color::Rgb(32, 32, 32)); - let time = if let Some(info) = self.exit_info { - format!( - "[{} ({:6})]", - self.start_time.time().format("%H:%M:%S"), - crate::format::duration(info.instant - self.start_instant) - ) - } else { - format!("[{}]", self.start_time.time().format("%H:%M:%S")) - }; - let cur_pos = out.screen().cursor_position(); - out.write_str( - &" ".repeat(width as usize - time.len() - 1 - cur_pos.1 as usize), - ); - out.write_str(&time); - out.write_str(" "); - out.reset_attributes(); - let last_row = self.lines(width, focused); - if last_row > 5 { - out.write(b"\r\n"); - out.set_fgcolor(textmode::color::BLUE); - out.write(b"..."); - out.reset_attributes(); - } - let mut out_row = out.screen().cursor_position().0 + 1; - let screen = self.vt.screen(); - let pos = screen.cursor_position(); - let mut wrapped = false; - let mut cursor_found = None; - for (idx, row) in screen - .rows_formatted(0, width) - .enumerate() - .take(last_row) - .skip(last_row.saturating_sub(5)) - { - let idx: u16 = idx.try_into().unwrap(); - out.write(b"\x1b[m"); - if !wrapped { - out.write(format!("\x1b[{}H", out_row + 1).as_bytes()); - } - out.write(&row); - wrapped = screen.row_wrapped(idx); - if pos.0 == idx { - cursor_found = Some(out_row); - } - out_row += 1; - } - if focused { - if let Some(row) = cursor_found { - if screen.hide_cursor() { - out.write(b"\x1b[?25l"); - } else { - out.write(b"\x1b[?25h"); - out.move_to(row, pos.1); - } - } else { - out.write(b"\x1b[?25l"); - } - } - out.reset_attributes(); - } - - fn render_fullscreen(&mut self, out: &mut textmode::Output) { - let screen = self.vt.screen(); - let new_audible_bell_state = screen.audible_bell_count(); - let new_visual_bell_state = screen.visual_bell_count(); - - out.write(&screen.state_formatted()); - - if self.audible_bell_state != new_audible_bell_state { - out.write(b"\x07"); - self.audible_bell_state = new_audible_bell_state; - } - - if self.visual_bell_state != new_visual_bell_state { - out.write(b"\x1bg"); - self.visual_bell_state = new_visual_bell_state; - } - - out.reset_attributes(); - } - - fn toggle_fullscreen(&mut self) { - if let Some(fullscreen) = self.fullscreen { - self.fullscreen = Some(!fullscreen); - } else { - self.fullscreen = Some(!self.vt.screen().alternate_screen()); - } - } - - fn running(&self) -> bool { - self.exit_info.is_none() - } - - fn lines(&self, width: u16, focused: bool) -> usize { - let screen = self.vt.screen(); - let mut last_row = 0; - for (idx, row) in screen.rows(0, width).enumerate() { - if !row.is_empty() { - last_row = idx + 1; - } - } - if focused && self.running() { - last_row = std::cmp::max( - last_row, - screen.cursor_position().0 as usize + 1, - ); - } - last_row - } - - fn should_fullscreen(&self) -> bool { - self.fullscreen - .unwrap_or_else(|| self.vt.screen().alternate_screen()) - } -} - -#[derive(Copy, Clone)] -struct ExitInfo { - status: async_std::process::ExitStatus, - instant: std::time::Instant, -} - -impl ExitInfo { - fn new(status: async_std::process::ExitStatus) -> Self { - Self { - status, - instant: std::time::Instant::now(), - } - } -} - -fn run_process( - mut child: pty_process::async_std::Child, - entry: crate::util::Mutex<HistoryEntry>, - input_r: async_std::channel::Receiver<Vec<u8>>, - resize_r: async_std::channel::Receiver<(u16, u16)>, - action_w: async_std::channel::Sender<crate::action::Action>, -) { - async_std::task::spawn(async move { - loop { - enum Res { - Read(Result<usize, std::io::Error>), - Write(Result<Vec<u8>, async_std::channel::RecvError>), - Resize(Result<(u16, u16), async_std::channel::RecvError>), - } - let mut buf = [0_u8; 4096]; - let mut pty = child.pty(); - let read = async { Res::Read(pty.read(&mut buf).await) }; - let write = async { Res::Write(input_r.recv().await) }; - let resize = async { Res::Resize(resize_r.recv().await) }; - match read.race(write).race(resize).await { - Res::Read(res) => { - match res { - Ok(bytes) => { - entry.lock_arc().await.vt.process(&buf[..bytes]); - } - Err(e) => { - if e.raw_os_error() != Some(libc::EIO) { - eprintln!("pty read failed: {:?}", e); - } - // XXX not sure if this is safe - are we sure - // the child exited? - entry.lock_arc().await.exit_info = Some( - ExitInfo::new(child.status().await.unwrap()), - ); - action_w - .send(crate::action::Action::UpdateFocus( - crate::state::Focus::Readline, - )) - .await - .unwrap(); - break; - } - } - action_w - .send(crate::action::Action::Render) - .await - .unwrap(); - } - Res::Write(res) => match res { - Ok(bytes) => { - pty.write(&bytes).await.unwrap(); - } - Err(e) => { - panic!("failed to read from input channel: {}", e); - } - }, - Res::Resize(res) => match res { - Ok(size) => { - child - .resize_pty(&pty_process::Size::new( - size.0, size.1, - )) - .unwrap(); - entry.lock_arc().await.vt.set_size(size.0, size.1); - } - Err(e) => { - panic!("failed to read from resize channel: {}", e); - } - }, - } - } - }); -} |