From 53193c426d19d529382e75e8af4ab9266b992557 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Wed, 5 Jan 2022 06:22:35 -0500 Subject: split out more --- src/state/history/entry.rs | 305 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 src/state/history/entry.rs (limited to 'src/state/history/entry.rs') diff --git a/src/state/history/entry.rs b/src/state/history/entry.rs new file mode 100644 index 0000000..932ec80 --- /dev/null +++ b/src/state/history/entry.rs @@ -0,0 +1,305 @@ +use std::os::unix::process::ExitStatusExt as _; + +pub struct Entry { + cmdline: String, + env: crate::env::Env, + vt: vt100::Parser, + audible_bell_state: usize, + visual_bell_state: usize, + fullscreen: Option, + input: async_std::channel::Sender>, + resize: async_std::channel::Sender<(u16, u16)>, + start_time: time::OffsetDateTime, + start_instant: std::time::Instant, + exit_info: Option, +} + +impl Entry { + pub 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, + input, + resize, + fullscreen: None, + start_time: time::OffsetDateTime::now_utc(), + start_instant: std::time::Instant::now(), + exit_info: None, + } + } + + pub fn render( + &self, + out: &mut impl textmode::Textmode, + idx: usize, + entry_count: usize, + width: u16, + focused: bool, + scrolling: bool, + offset: time::UtcOffset, + ) { + let time = self.exit_info.as_ref().map_or_else( + || { + format!( + "[{}]", + crate::format::time(self.start_time.to_offset(offset)) + ) + }, + |info| { + format!( + "({}) [{}]", + crate::format::duration( + info.instant - self.start_instant + ), + crate::format::time(self.start_time.to_offset(offset)), + ) + }, + ); + + 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); + out.write_str(&" ".repeat(entry_count_width - idx_str.len())); + out.write_str(&idx_str); + out.write_str(" "); + out.reset_attributes(); + + set_bgcolor(out, idx, focused); + 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(); + + set_bgcolor(out, idx, focused); + out.write_str("$ "); + if self.running() { + out.set_bgcolor(textmode::Color::Rgb(16, 64, 16)); + } + let cmd = self.cmd(); + let start = usize::from(out.screen().cursor_position().1); + let end = usize::from(width) - time.len() - 2; + let max_len = end - start; + if cmd.len() > max_len { + out.write_str(&cmd[..(max_len - 4)]); + out.set_fgcolor(textmode::color::BLUE); + out.write_str(" ..."); + } else { + out.write_str(cmd); + } + out.reset_attributes(); + + set_bgcolor(out, idx, focused); + let cur_pos = out.screen().cursor_position(); + out.write_str(&" ".repeat( + usize::from(width) - time.len() - 1 - usize::from(cur_pos.1), + )); + out.write_str(&time); + out.write_str(" "); + out.reset_attributes(); + + if self.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( + out.screen().cursor_position().0 + 1, + (width - len) / 2, + ); + out.set_fgcolor(textmode::color::RED); + out.write_str(msg); + out.hide_cursor(true); + } else { + let last_row = self.output_lines(width, focused && !scrolling); + if last_row > 5 { + out.write(b"\r\n"); + out.set_fgcolor(textmode::color::BLUE); + out.write_str("..."); + 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.reset_attributes(); + if !wrapped { + out.move_to(out_row, 0); + } + out.write(&row); + wrapped = screen.row_wrapped(idx); + if pos.0 == idx { + cursor_found = Some(out_row); + } + out_row += 1; + } + if focused && !scrolling { + if let Some(row) = cursor_found { + out.hide_cursor(screen.hide_cursor()); + out.move_to(row, pos.1); + } else { + out.hide_cursor(true); + } + } + } + out.reset_attributes(); + } + + pub fn render_fullscreen(&mut self, out: &mut impl textmode::Textmode) { + 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(); + } + + pub async fn send_input(&self, bytes: Vec) { + 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 size(&self) -> (u16, u16) { + self.vt.screen().size() + } + + pub fn process(&mut self, input: &[u8]) { + self.vt.process(input); + } + + pub fn cmd(&self) -> &str { + &self.cmdline + } + + pub fn env(&self) -> &crate::env::Env { + &self.env + } + + 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()); + } + } + + pub fn set_fullscreen(&mut self, fullscreen: bool) { + self.fullscreen = Some(fullscreen); + } + + pub fn running(&self) -> bool { + self.exit_info.is_none() + } + + pub fn binary(&self) -> bool { + self.vt.screen().errors() > 5 + } + + pub fn lines(&self, width: u16, focused: bool) -> usize { + let lines = self.output_lines(width, focused); + 1 + std::cmp::min(6, lines) + } + + pub fn output_lines(&self, width: u16, focused: bool) -> usize { + if self.binary() { + return 1; + } + + 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, + usize::from(screen.cursor_position().0) + 1, + ); + } + last_row + } + + pub fn should_fullscreen(&self) -> bool { + self.fullscreen + .unwrap_or_else(|| self.vt.screen().alternate_screen()) + } + + pub async fn finish( + &mut self, + env: crate::env::Env, + event_w: async_std::channel::Sender, + ) { + self.exit_info = Some(ExitInfo::new(*env.latest_status())); + self.env = env; + event_w.send(crate::event::Event::PtyClose).await.unwrap(); + } +} + +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 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)); + } +} -- cgit v1.2.3-54-g00ecf