From 3a082edd58ecdf4db31702c6c8487c42227049a3 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Tue, 8 Mar 2022 19:58:53 -0500 Subject: parse a history file if available doesn't do anything with it yet --- src/shell/old_history.rs | 185 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 src/shell/old_history.rs (limited to 'src/shell/old_history.rs') diff --git a/src/shell/old_history.rs b/src/shell/old_history.rs new file mode 100644 index 0000000..49fd1c2 --- /dev/null +++ b/src/shell/old_history.rs @@ -0,0 +1,185 @@ +use crate::shell::prelude::*; + +use tokio::io::AsyncBufReadExt as _; + +use pest::Parser as _; + +#[derive(pest_derive::Parser)] +#[grammar = "history.pest"] +struct HistoryLine; + +pub struct History { + entries: std::sync::Arc>>, +} + +impl History { + pub fn new() -> Self { + let entries = std::sync::Arc::new(std::sync::Mutex::new(vec![])); + tokio::spawn(Self::task(std::sync::Arc::clone(&entries))); + Self { entries } + } + + pub fn entry_count(&self) -> usize { + self.entries.lock().unwrap().len() + } + + async fn task(entries: std::sync::Arc>>) { + // TODO: we should actually read this in reverse order, because we + // want to populate the most recent entries first + let mut stream = tokio_stream::wrappers::LinesStream::new( + tokio::io::BufReader::new( + tokio::fs::File::open(crate::dirs::history_file()) + .await + .unwrap(), + ) + .lines(), + ); + while let Some(line) = stream.next().await { + let line = if let Ok(line) = line { + line + } else { + continue; + }; + let entry = if let Ok(entry) = line.parse() { + entry + } else { + continue; + }; + entries.lock().unwrap().push(entry); + } + } +} + +pub struct Entry { + cmdline: String, + start_time: Option, + duration: Option, +} + +impl Entry { + pub fn render( + &self, + out: &mut impl textmode::Textmode, + offset: time::UtcOffset, + ) { + let size = out.screen().size(); + let mut time = "".to_string(); + if let Some(duration) = self.duration { + time.push_str(&crate::format::duration(duration)); + } + if let Some(start_time) = self.start_time { + time.push_str(&crate::format::time(start_time.to_offset(offset))); + } + + out.write_str(" $ "); + let start = usize::from(out.screen().cursor_position().1); + let end = usize::from(size.1) - time.len() - 2; + let max_len = end - start; + let cmd = if self.cmdline.len() > max_len { + &self.cmdline[..(max_len - 4)] + } else { + &self.cmdline + }; + out.write_str(cmd); + if self.cmdline.len() > max_len { + out.write_str(" "); + out.set_fgcolor(textmode::color::BLUE); + out.write_str("..."); + } + out.reset_attributes(); + + out.set_bgcolor(textmode::Color::Rgb(0x20, 0x20, 0x20)); + let cur_pos = out.screen().cursor_position(); + out.write_str(&" ".repeat( + usize::from(size.1) - time.len() - 1 - usize::from(cur_pos.1), + )); + out.write_str(&time); + out.write_str(" "); + out.reset_attributes(); + } + + pub fn cmd(&self) -> &str { + &self.cmdline + } +} + +impl std::str::FromStr for Entry { + type Err = anyhow::Error; + + fn from_str(line: &str) -> std::result::Result { + let mut parsed = + HistoryLine::parse(Rule::line, line).map_err(|e| anyhow!(e))?; + let line = parsed.next().unwrap(); + assert!(matches!(line.as_rule(), Rule::line)); + + let mut start_time = None; + let mut duration = None; + let mut cmdline = None; + for part in line.into_inner() { + match part.as_rule() { + Rule::time => { + start_time = + Some(time::OffsetDateTime::from_unix_timestamp( + part.as_str().parse()?, + )?); + } + Rule::duration => { + if part.as_str() == "0" { + continue; + } + let mut dur_parts = part.as_str().split('.'); + let secs: u64 = dur_parts.next().unwrap().parse()?; + let nsec_str = dur_parts.next().unwrap_or("0"); + let nsec_str = &nsec_str[..9.min(nsec_str.len())]; + let nsecs: u64 = nsec_str.parse()?; + duration = Some(std::time::Duration::from_nanos( + secs * 1_000_000_000 + + nsecs + * (10u64.pow( + (9 - nsec_str.len()).try_into().unwrap(), + )), + )); + } + Rule::command => { + cmdline = Some(part.as_str().to_string()); + } + Rule::line => unreachable!(), + Rule::EOI => break, + } + } + + Ok(Self { + cmdline: cmdline.unwrap(), + start_time, + duration, + }) + } +} + +#[test] +fn test_parse() { + let entry: Entry = + ": 1646779848:1234.56;vim ~/.zsh_history".parse().unwrap(); + assert_eq!(entry.cmdline, "vim ~/.zsh_history"); + assert_eq!( + entry.duration, + Some(std::time::Duration::from_nanos(1_234_560_000_000)) + ); + assert_eq!( + entry.start_time, + Some(time::macros::datetime!(2022-03-08 22:50:48).assume_utc()) + ); + + let entry: Entry = ": 1646779848:1;vim ~/.zsh_history".parse().unwrap(); + assert_eq!(entry.cmdline, "vim ~/.zsh_history"); + assert_eq!(entry.duration, Some(std::time::Duration::from_secs(1))); + assert_eq!( + entry.start_time, + Some(time::macros::datetime!(2022-03-08 22:50:48).assume_utc()) + ); + + let entry: Entry = "vim ~/.zsh_history".parse().unwrap(); + assert_eq!(entry.cmdline, "vim ~/.zsh_history"); + assert_eq!(entry.duration, None); + assert_eq!(entry.start_time, None); +} -- cgit v1.2.3-54-g00ecf