From 1817b22c2010786821fc1f76cc4abee50e60f1fc Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sat, 20 Jan 2024 23:00:11 +0900 Subject: [PATCH] wip Store to history file --- src/model/histories.rs | 187 ++++++++++++++++++++++++++++++++++++ src/usecase/fzf_make/app.rs | 68 +++++++------ 2 files changed, 225 insertions(+), 30 deletions(-) diff --git a/src/model/histories.rs b/src/model/histories.rs index cc3e9dc3..7401961c 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -1,3 +1,4 @@ +use simple_home_dir::home_dir; use std::path::PathBuf; #[derive(Clone, PartialEq, Debug)] @@ -22,6 +23,22 @@ impl Histories { .map(|h| h.executed_targets.clone()) } + pub fn append(&self, path: &PathBuf, executed_target: &str) -> Option { + let mut new_histories = self.histories.clone(); + + new_histories + .iter() + .position(|h| h.path == *path) + .map(|index| { + let new_history = new_histories[index].append(executed_target.to_string()); + new_histories[index] = new_history; + + Self { + histories: new_histories, + } + }) + } + fn default(path: PathBuf) -> Self { let histories = vec![History::default(path)]; Self { histories } @@ -39,6 +56,14 @@ impl Histories { } } +pub fn history_file_path() -> Option { + home_dir().map(|mut h| { + let base_path: &'static str = ".config/fzf-make/history.toml"; + h.push(PathBuf::from(base_path)); + h.clone() + }) +} + #[derive(Clone, PartialEq, Debug)] struct History { path: PathBuf, @@ -59,6 +84,17 @@ impl History { executed_targets: histories.1, } } + + fn append(&self, executed_target: String) -> Self { + let mut executed_targets = self.executed_targets.clone(); + executed_targets.retain(|t| *t != executed_target); + executed_targets.insert(0, executed_target.clone()); + + Self { + path: self.path.clone(), + executed_targets, + } + } } #[cfg(test)] @@ -122,4 +158,155 @@ mod test { ) } } + + #[test] + fn histories_append_test() { + struct Case { + title: &'static str, + path: PathBuf, + appending_target: &'static str, + histories: Histories, + expect: Option, + } + let cases = vec![ + Case { + title: "Success", + path: PathBuf::from("/Users/user/code/fzf-make".to_string()), + appending_target: "history1", + histories: Histories { + histories: vec![ + History { + path: PathBuf::from("/Users/user/code/rustc".to_string()), + executed_targets: vec!["history0".to_string(), "history1".to_string()], + }, + History { + path: PathBuf::from("/Users/user/code/fzf-make".to_string()), + executed_targets: vec![ + "history0".to_string(), + "history1".to_string(), + "history2".to_string(), + ], + }, + ], + }, + expect: Some(Histories { + histories: vec![ + History { + path: PathBuf::from("/Users/user/code/rustc".to_string()), + executed_targets: vec!["history0".to_string(), "history1".to_string()], + }, + History { + path: PathBuf::from("/Users/user/code/fzf-make".to_string()), + executed_targets: vec![ + "history1".to_string(), + "history0".to_string(), + "history2".to_string(), + ], + }, + ], + }), + }, + Case { + title: "Returns None when path is not found", + path: PathBuf::from("/Users/user/code/non-existent-dir".to_string()), + appending_target: "history1", + histories: Histories { + histories: vec![ + History { + path: PathBuf::from("/Users/user/code/rustc".to_string()), + executed_targets: vec!["history0".to_string(), "history1".to_string()], + }, + History { + path: PathBuf::from("/Users/user/code/fzf-make".to_string()), + executed_targets: vec![ + "history0".to_string(), + "history1".to_string(), + "history2".to_string(), + ], + }, + ], + }, + expect: None, + }, + ]; + + for case in cases { + assert_eq!( + case.expect, + case.histories.append(&case.path, case.appending_target), + "\nFailed: ๐Ÿšจ{:?}๐Ÿšจ\n", + case.title, + ) + } + } + + #[test] + fn history_append_test() { + struct Case { + title: &'static str, + appending_target: &'static str, + history: History, + expect: History, + } + let path = PathBuf::from("/Users/user/code/fzf-make".to_string()); + let cases = vec![ + Case { + title: "Append to head", + appending_target: "history2", + history: History { + path: path.clone(), + executed_targets: vec!["history0".to_string(), "history1".to_string()], + }, + expect: History { + path: path.clone(), + executed_targets: vec![ + "history2".to_string(), + "history0".to_string(), + "history1".to_string(), + ], + }, + }, + Case { + title: "Append to head(Append to empty)", + appending_target: "history0", + history: History { + path: path.clone(), + executed_targets: vec![], + }, + expect: History { + path: path.clone(), + executed_targets: vec!["history0".to_string()], + }, + }, + Case { + title: "Append to head(Remove duplicated)", + appending_target: "history1", + history: History { + path: path.clone(), + executed_targets: vec![ + "history0".to_string(), + "history1".to_string(), + "history2".to_string(), + ], + }, + expect: History { + path: path.clone(), + executed_targets: vec![ + "history1".to_string(), + "history0".to_string(), + "history2".to_string(), + ], + }, + }, + ]; + + for case in cases { + assert_eq!( + case.expect, + case.history.append(case.appending_target.to_string()), + "\nFailed: ๐Ÿšจ{:?}๐Ÿšจ\n", + case.title, + ) + } + } } diff --git a/src/usecase/fzf_make/app.rs b/src/usecase/fzf_make/app.rs index 71753ad3..faa335c3 100644 --- a/src/usecase/fzf_make/app.rs +++ b/src/usecase/fzf_make/app.rs @@ -1,6 +1,9 @@ use crate::{ file::{path_to_content, toml}, - model::{histories::Histories, makefile::Makefile}, + model::{ + histories::{history_file_path, Histories}, + makefile::Makefile, + }, }; use super::ui::ui; @@ -18,12 +21,9 @@ use ratatui::{ widgets::ListState, Terminal, }; -use simple_home_dir::home_dir; use std::{ io::{self, Stderr}, - panic, - path::PathBuf, - process, + panic, process, }; use tui_textarea::TextArea; @@ -100,6 +100,15 @@ impl Model<'_> { }) } + pub fn append_history(&self) -> Option { + match (&self.histories.clone(), &self.app_state.clone()) { + (Some(histories), AppState::ExecuteTarget(Some(target))) => { + histories.append(&self.makefile.path, target) + } + _ => None, + } + } + pub fn narrow_down_targets(&self) -> Vec { if self.search_text_area.0.is_empty() { return self.makefile.to_targets_string(); @@ -131,25 +140,18 @@ impl Model<'_> { } fn get_histories() -> Option { - let path = match home_dir() { - None => return None, - Some(mut h) => { - let base_path: &'static str = ".config/fzf-make/history.toml"; - h.push(PathBuf::from(base_path)); - h.clone() - } - }; - - let content = match path_to_content::path_to_content(path.clone()) { - Err(_) => return Some(Histories::new(path, vec![])), // NOTE: Show error message on message pane https://github.com/kyu08/fzf-make/issues/152 - Ok(c) => c, - }; - let histories = match toml::parse_history(content.to_string()) { - Err(_) => vec![], // NOTE: Show error message on message pane https://github.com/kyu08/fzf-make/issues/152 - Ok(h) => h, - }; + history_file_path().map(|path| { + let content = match path_to_content::path_to_content(path.clone()) { + Err(_) => return Histories::new(path, vec![]), // NOTE: Show error message on message pane https://github.com/kyu08/fzf-make/issues/152 + Ok(c) => c, + }; + let histories = match toml::parse_history(content.to_string()) { + Err(_) => vec![], // NOTE: Show error message on message pane https://github.com/kyu08/fzf-make/issues/152 + Ok(h) => h, + }; - Some(Histories::new(path, histories)) + Histories::new(path, histories) + }) } fn next_target(&mut self) { @@ -415,14 +417,20 @@ fn update(model: &mut Model, message: Option) { Some(Message::PreviousTarget) => model.previous_target(), Some(Message::NextHistory) => model.next_history(), Some(Message::PreviousHistory) => model.previous_history(), - Some(Message::ExecuteTarget) => match model.current_pane { - CurrentPane::Main => { - model.app_state = AppState::ExecuteTarget(model.selected_target()); - } - CurrentPane::History => { - model.app_state = AppState::ExecuteTarget(model.selected_history()); + Some(Message::ExecuteTarget) => { + let target = match model.current_pane { + CurrentPane::Main => model.selected_target(), + CurrentPane::History => model.selected_history(), + }; + model.app_state = AppState::ExecuteTarget(target); + if let Some(h) = model.append_history() { + model.histories = Some(h) } - }, + + // TODO: store to history file + // toml.rsใจใฎ้–ขใ‚ใ‚Šๆ–นใŒๅˆๆœŸๅŒ–ๆ™‚ใจๅŒๆง˜ใ‹ใƒใ‚งใƒƒใ‚ฏใ™ใ‚‹ + } + Some(Message::SearchTextAreaKeyInput(key_event)) => { if let KeyCode::Char(_) = key_event.code { model.reset_selection();