summaryrefslogtreecommitdiffstats
path: root/src/io/input_history.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/io/input_history.rs')
-rw-r--r--src/io/input_history.rs229
1 files changed, 229 insertions, 0 deletions
diff --git a/src/io/input_history.rs b/src/io/input_history.rs
new file mode 100644
index 0000000..52b9e5a
--- /dev/null
+++ b/src/io/input_history.rs
@@ -0,0 +1,229 @@
+use std::fmt::Display;
+use std::io::Write;
+
+use clap::Parser;
+use strum_macros::Display;
+
+use anyhow::{anyhow, Context, Result};
+
+use crate::{
+ common::read_lines,
+ io::Args,
+ modes::{Edit, InputCompleted, InputSimple},
+};
+
+pub struct InputHistory {
+ file_path: std::path::PathBuf,
+ content: Vec<HistoryElement>,
+ filtered: Vec<HistoryElement>,
+ index: usize,
+ log_are_enabled: bool,
+}
+
+impl InputHistory {
+ pub fn load(path: &str) -> Result<Self> {
+ let file_path = std::path::PathBuf::from(shellexpand::tilde(path).to_string());
+ Ok(Self {
+ content: Self::load_content(&file_path)?,
+ file_path,
+ filtered: vec![],
+ index: 0,
+ log_are_enabled: Args::parse().log,
+ })
+ }
+
+ fn load_content(path: &std::path::Path) -> Result<Vec<HistoryElement>> {
+ if !std::path::Path::new(&path).exists() {
+ std::fs::File::create(path)?;
+ }
+ Ok(read_lines(path)?
+ .map(HistoryElement::from_str)
+ .filter_map(|line| line.ok())
+ .collect())
+ }
+
+ pub fn write_elem(&self, elem: &HistoryElement) -> Result<()> {
+ let mut hist_file = std::fs::OpenOptions::new()
+ .append(true)
+ .open(&self.file_path)?;
+ hist_file.write_all(elem.to_string().as_bytes())?;
+ Ok(())
+ }
+
+ pub fn filter_by_mode(&mut self, edit_mode: Edit) {
+ let Some(kind) = HistoryKind::from_mode(edit_mode) else {
+ return;
+ };
+ self.index = 0;
+ self.filtered = self
+ .content
+ .iter()
+ .filter(|elem| elem.kind == kind)
+ .rev()
+ .map(|elem| elem.to_owned())
+ .collect()
+ }
+
+ pub fn next(&mut self) {
+ if !self.filtered.is_empty() {
+ self.index = (self.index + 1) % self.filtered.len();
+ }
+ }
+
+ pub fn prev(&mut self) {
+ if self.index > 0 {
+ self.index -= 1
+ } else if !self.filtered.is_empty() {
+ self.index = self.filtered.len() - 1
+ }
+ }
+
+ pub fn current(&self) -> Option<&str> {
+ let Some(elem) = self.filtered.get(self.index) else {
+ return None;
+ };
+ Some(&elem.content)
+ }
+
+ /// If logs are disabled, nothing is saved on disk, only during current session
+ pub fn update(&mut self, mode: Edit, input_string: &str) -> Result<()> {
+ let Some(elem) = HistoryElement::from_mode_input_string(mode, input_string) else {
+ return Ok(());
+ };
+ if self.log_are_enabled {
+ self.write_elem(&elem)?;
+ }
+ self.content.push(elem);
+ Ok(())
+ }
+}
+
+#[derive(Display, PartialEq, Eq, Clone)]
+pub enum HistoryKind {
+ Cd,
+ Search,
+ Exec,
+ Action,
+ Rename,
+ Chmod,
+ Newfile,
+ Newdir,
+ RegexMatch,
+ Sort,
+ Filter,
+ SetNvimAddr,
+ Shell,
+ Remote,
+}
+
+impl HistoryKind {
+ fn from_string(kind: &String) -> Result<Self> {
+ Ok(match kind.as_ref() {
+ "Cd" => Self::Cd,
+ "Search" => Self::Search,
+ "Exec" => Self::Exec,
+ "Action" => Self::Action,
+ "Rename" => Self::Rename,
+ "Chmod" => Self::Chmod,
+ "Newfile" => Self::Newfile,
+ "Newdir" => Self::Newdir,
+ "RegexMatch" => Self::RegexMatch,
+ "Sort" => Self::Sort,
+ "Filter" => Self::Filter,
+ "SetNvimAddr" => Self::SetNvimAddr,
+ "Shell" => Self::Shell,
+ "Remote" => Self::Remote,
+ _ => return Err(anyhow!("{kind} isn't a valid HistoryKind")),
+ })
+ }
+
+ fn from_input_simple(input_simple: InputSimple) -> Option<Self> {
+ match input_simple {
+ InputSimple::Rename => Some(Self::Rename),
+ InputSimple::Chmod => Some(Self::Chmod),
+ InputSimple::Newfile => Some(Self::Newfile),
+ InputSimple::Newdir => Some(Self::Newdir),
+ InputSimple::RegexMatch => Some(Self::RegexMatch),
+ InputSimple::Sort => Some(Self::Sort),
+ InputSimple::Filter => Some(Self::Filter),
+ InputSimple::SetNvimAddr => Some(Self::SetNvimAddr),
+ InputSimple::Shell => Some(Self::Shell),
+ InputSimple::Remote => Some(Self::Remote),
+ _ => None,
+ }
+ }
+
+ fn from_input_completed(input_completed: InputCompleted) -> Self {
+ match input_completed {
+ InputCompleted::Cd => Self::Cd,
+ InputCompleted::Search => Self::Search,
+ InputCompleted::Exec => Self::Exec,
+ InputCompleted::Action => Self::Action,
+ }
+ }
+
+ fn from_mode(edit_mode: Edit) -> Option<Self> {
+ match edit_mode {
+ Edit::InputSimple(input_simple) => Self::from_input_simple(input_simple),
+ Edit::InputCompleted(input_completed) => {
+ Some(Self::from_input_completed(input_completed))
+ }
+ _ => None,
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct HistoryElement {
+ kind: HistoryKind,
+ content: String,
+}
+
+impl Display for HistoryElement {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ writeln!(
+ f,
+ "{kind} - {content}",
+ kind = self.kind,
+ content = self.content
+ )
+ }
+}
+
+impl HistoryElement {
+ fn split_kind_content(line: Result<String, std::io::Error>) -> Result<(String, String)> {
+ let line = line?.to_owned();
+ let (mut kind, mut content) = line
+ .split_once('-')
+ .context("no delimiter '-' found in line")?;
+ kind = kind.trim();
+ content = content.trim();
+ Ok((kind.to_owned(), content.to_owned()))
+ }
+
+ pub fn from_mode_input_string(mode: Edit, input_string: &str) -> Option<Self> {
+ let Some(kind) = HistoryKind::from_mode(mode) else {
+ return None;
+ };
+ Some(Self {
+ kind,
+ content: input_string.to_owned(),
+ })
+ }
+
+ fn from_str(line: Result<String, std::io::Error>) -> Result<Self> {
+ let (kind, content) = Self::split_kind_content(line)?;
+ if content.is_empty() {
+ Err(anyhow!("empty line"))
+ } else {
+ Ok(Self {
+ kind: HistoryKind::from_string(&kind)?,
+ content: content.to_owned(),
+ })
+ }
+ }
+
+ pub fn for_display(&self) -> &str {
+ &self.content
+ }
+}