From 788bb4bbede8d7f93291ee568acf57e5a5e95710 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Tue, 12 Nov 2024 00:18:59 +0900 Subject: [PATCH 01/36] empty commit From f73d90aafdf7148e69f902b7583caf60cfc83c84 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Thu, 14 Nov 2024 00:22:22 +0900 Subject: [PATCH 02/36] wip --- src/file/toml.rs | 52 ++++- src/model/command.rs | 4 +- src/model/histories.rs | 409 +++++++++++++++++++++++++++++++++------ src/model/make.rs | 1 + src/model/runner_type.rs | 3 +- src/usecase/repeat.rs | 24 +-- src/usecase/tui/app.rs | 63 ++++-- 7 files changed, 452 insertions(+), 104 deletions(-) diff --git a/src/file/toml.rs b/src/file/toml.rs index 3441989..a7e4c53 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -6,13 +6,15 @@ use std::{ path::PathBuf, }; +use crate::model::command; + #[derive(Debug, Serialize, Deserialize)] struct Histories { history: Vec, } impl Histories { - fn from(histories: Vec<(PathBuf, Vec)>) -> Self { + fn from(histories: Vec<(PathBuf, Vec)>) -> Self { let mut result: Vec = vec![]; for h in histories { result.push(History { @@ -28,13 +30,13 @@ impl Histories { #[serde(rename_all = "kebab-case")] struct History { path: String, - executed_targets: Vec, + executed_targets: Vec, } -pub fn parse_history(content: String) -> Result)>> { +pub fn parse_history(content: String) -> Result)>> { let histories: Histories = toml::from_str(&content)?; - let mut result: Vec<(PathBuf, Vec)> = Vec::new(); + let mut result: Vec<(PathBuf, Vec)> = Vec::new(); for history in histories.history { result.push((PathBuf::from(history.path), history.executed_targets)); @@ -46,7 +48,7 @@ pub fn parse_history(content: String) -> Result)>> { pub fn store_history( history_directory_path: PathBuf, history_file_name: String, - histories_tuple: Vec<(PathBuf, Vec)>, + histories_tuple: Vec<(PathBuf, Vec)>, ) -> Result<()> { let histories = Histories::from(histories_tuple); @@ -63,6 +65,8 @@ pub fn store_history( #[cfg(test)] mod test { + use crate::model::runner_type; + use super::*; use anyhow::Result; @@ -71,7 +75,7 @@ mod test { struct Case { title: &'static str, content: String, - expect: Result)>>, + expect: Result)>>, } let cases = vec![ Case { @@ -90,14 +94,42 @@ executed-targets = ["run", "echo1"] ( PathBuf::from("/Users/user/code/fzf-make".to_string()), vec![ - "test".to_string(), - "check".to_string(), - "spell-check".to_string(), + command::Command::new( + runner_type::RunnerType::Make, + "target-a".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "check".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "spell-check".to_string(), + PathBuf::from("Makefile"), + 4, + ), ], ), ( PathBuf::from("/Users/user/code/golang/go-playground".to_string()), - vec!["run".to_string(), "echo1".to_string()], + vec![ + command::Command::new( + runner_type::RunnerType::Make, + "run".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "echo1".to_string(), + PathBuf::from("Makefile"), + 4, + ), + ], ), ]), }, diff --git a/src/model/command.rs b/src/model/command.rs index 99ed2c9..49a6e7f 100644 --- a/src/model/command.rs +++ b/src/model/command.rs @@ -1,8 +1,10 @@ +use serde::{Deserialize, Serialize}; use std::{fmt, path::PathBuf}; use super::runner_type; -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] pub struct Command { pub runner_type: runner_type::RunnerType, pub name: String, diff --git a/src/model/histories.rs b/src/model/histories.rs index ac1bfde..928fc76 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -1,13 +1,15 @@ use simple_home_dir::home_dir; use std::{env, path::PathBuf}; +use super::command; + #[derive(Clone, PartialEq, Debug)] pub struct Histories { histories: Vec, } impl Histories { - pub fn new(makefile_path: PathBuf, histories: Vec<(PathBuf, Vec)>) -> Self { + pub fn new(makefile_path: PathBuf, histories: Vec<(PathBuf, Vec)>) -> Self { match histories.len() { 0 => Self::default(makefile_path), _ => Self::from(makefile_path, histories), @@ -56,19 +58,19 @@ impl Histories { // result // } - // pub fn get_latest_target(&self, path: &PathBuf) -> Option<&String> { - // self.histories - // .iter() - // .find(|h| h.path == *path) - // .map(|h| h.executed_targets.first())? - // } + pub fn get_latest_target(&self, path: &PathBuf) -> Option<&command::Command> { + self.histories + .iter() + .find(|h| h.path == *path) + .map(|h| h.executed_targets.first())? + } - fn default(path: PathBuf) -> Self { + pub fn default(path: PathBuf) -> Self { let histories = vec![History::default(path)]; Self { histories } } - fn from(makefile_path: PathBuf, histories: Vec<(PathBuf, Vec)>) -> Self { + fn from(makefile_path: PathBuf, histories: Vec<(PathBuf, Vec)>) -> Self { let mut result = Histories { histories: Vec::new(), }; @@ -109,8 +111,8 @@ pub fn history_file_path() -> Option<(PathBuf, String)> { #[derive(Clone, PartialEq, Debug)] struct History { - path: PathBuf, // TODO: rename to working_directory - executed_targets: Vec, // TODO: make this to Vec + path: PathBuf, // TODO: rename to working_directory + executed_targets: Vec, // TODO: rename to executed_commands } impl History { @@ -121,7 +123,7 @@ impl History { } } - fn from(histories: (PathBuf, Vec)) -> Self { + fn from(histories: (PathBuf, Vec)) -> Self { Self { path: histories.0, executed_targets: histories.1, @@ -130,7 +132,7 @@ impl History { // TODO(#321): remove #[allow(dead_code)] - fn append(&self, executed_target: String) -> Self { + fn append(&self, executed_target: command::Command) -> Self { let mut executed_targets = self.executed_targets.clone(); executed_targets.retain(|t| *t != executed_target); executed_targets.insert(0, executed_target.clone()); @@ -149,6 +151,8 @@ impl History { #[cfg(test)] mod test { + use crate::model::runner_type; + use super::*; #[test] @@ -156,7 +160,7 @@ mod test { struct Case { title: &'static str, makefile_path: PathBuf, - histories: Vec<(PathBuf, Vec)>, + histories: Vec<(PathBuf, Vec)>, expect: Histories, } let cases = vec![ @@ -177,22 +181,74 @@ mod test { histories: vec![ ( PathBuf::from("/Users/user/code/fzf-make".to_string()), - vec!["target1".to_string(), "target2".to_string()], + vec![ + command::Command::new( + runner_type::RunnerType::Make, + "target1".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "target2".to_string(), + PathBuf::from("Makefile"), + 4, + ), + ], ), ( PathBuf::from("/Users/user/code/rustc".to_string()), - vec!["target-a".to_string(), "target-b".to_string()], + vec![ + command::Command::new( + runner_type::RunnerType::Make, + "target-a".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "target-b".to_string(), + PathBuf::from("Makefile"), + 4, + ), + ], ), ], expect: Histories { histories: vec![ History { path: PathBuf::from("/Users/user/code/fzf-make".to_string()), - executed_targets: vec!["target1".to_string(), "target2".to_string()], + executed_targets: vec![ + command::Command::new( + runner_type::RunnerType::Make, + "target1".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "target2".to_string(), + PathBuf::from("Makefile"), + 4, + ), + ], }, History { path: PathBuf::from("/Users/user/code/rustc".to_string()), - executed_targets: vec!["target-a".to_string(), "target-b".to_string()], + executed_targets: vec![ + command::Command::new( + runner_type::RunnerType::Make, + "target-a".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "target-b".to_string(), + PathBuf::from("Makefile"), + 4, + ), + ], }, ], }, @@ -203,22 +259,74 @@ mod test { histories: vec![ ( PathBuf::from("/Users/user/code/fzf-make".to_string()), - vec!["target1".to_string(), "target2".to_string()], + vec![ + command::Command::new( + runner_type::RunnerType::Make, + "target1".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "target2".to_string(), + PathBuf::from("Makefile"), + 4, + ), + ], ), ( PathBuf::from("/Users/user/code/rustc".to_string()), - vec!["target-a".to_string(), "target-b".to_string()], + vec![ + command::Command::new( + runner_type::RunnerType::Make, + "target-a".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "target-b".to_string(), + PathBuf::from("Makefile"), + 4, + ), + ], ), ], expect: Histories { histories: vec![ History { path: PathBuf::from("/Users/user/code/fzf-make".to_string()), - executed_targets: vec!["target1".to_string(), "target2".to_string()], + executed_targets: vec![ + command::Command::new( + runner_type::RunnerType::Make, + "target1".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "target2".to_string(), + PathBuf::from("Makefile"), + 4, + ), + ], }, History { path: PathBuf::from("/Users/user/code/rustc".to_string()), - executed_targets: vec!["target-a".to_string(), "target-b".to_string()], + executed_targets: vec![ + command::Command::new( + runner_type::RunnerType::Make, + "target-a".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "target-b".to_string(), + PathBuf::from("Makefile"), + 4, + ), + ], }, History { path: PathBuf::from("/Users/user/code/cargo".to_string()), @@ -324,7 +432,7 @@ mod test { fn history_append_test() { struct Case { title: &'static str, - appending_target: &'static str, + appending_target: command::Command, history: History, expect: History, } @@ -332,83 +440,266 @@ mod test { let cases = vec![ Case { title: "Append to head", - appending_target: "history2", + appending_target: command::Command::new( + runner_type::RunnerType::Make, + "history2".to_string(), + PathBuf::from("Makefile"), + 1, + ), history: History { path: path.clone(), - executed_targets: vec!["history0".to_string(), "history1".to_string()], + executed_targets: vec![ + command::Command::new( + runner_type::RunnerType::Make, + "history0".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history1".to_string(), + PathBuf::from("Makefile"), + 4, + ), + ], }, expect: History { path: path.clone(), executed_targets: vec![ - "history2".to_string(), - "history0".to_string(), - "history1".to_string(), + command::Command::new( + runner_type::RunnerType::Make, + "history2".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history0".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history1".to_string(), + PathBuf::from("Makefile"), + 4, + ), ], }, }, Case { title: "Append to head(Append to empty)", - appending_target: "history0", + appending_target: command::Command::new( + runner_type::RunnerType::Make, + "history0".to_string(), + PathBuf::from("Makefile"), + 4, + ), history: History { path: path.clone(), executed_targets: vec![], }, expect: History { path: path.clone(), - executed_targets: vec!["history0".to_string()], + executed_targets: vec![command::Command::new( + runner_type::RunnerType::Make, + "history0".to_string(), + PathBuf::from("Makefile"), + 4, + )], }, }, Case { title: "Append to head(Remove duplicated)", - appending_target: "history1", + appending_target: command::Command::new( + runner_type::RunnerType::Make, + "history1".to_string(), + PathBuf::from("Makefile"), + 4, + ), history: History { path: path.clone(), executed_targets: vec![ - "history0".to_string(), - "history1".to_string(), - "history2".to_string(), + command::Command::new( + runner_type::RunnerType::Make, + "history0".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history1".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history2".to_string(), + PathBuf::from("Makefile"), + 4, + ), ], }, expect: History { path: path.clone(), executed_targets: vec![ - "history1".to_string(), - "history0".to_string(), - "history2".to_string(), + command::Command::new( + runner_type::RunnerType::Make, + "history1".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history0".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history2".to_string(), + PathBuf::from("Makefile"), + 4, + ), ], }, }, Case { title: "Truncate when length exceeds 10", - appending_target: "history11", + appending_target: command::Command::new( + runner_type::RunnerType::Make, + "history11".to_string(), + PathBuf::from("Makefile"), + 1, + ), history: History { path: path.clone(), executed_targets: vec![ - "history0".to_string(), - "history1".to_string(), - "history2".to_string(), - "history3".to_string(), - "history4".to_string(), - "history5".to_string(), - "history6".to_string(), - "history7".to_string(), - "history8".to_string(), - "history9".to_string(), + command::Command::new( + runner_type::RunnerType::Make, + "history0".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history1".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history2".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history3".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history4".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history5".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history6".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history7".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history8".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history9".to_string(), + PathBuf::from("Makefile"), + 4, + ), ], }, expect: History { path: path.clone(), executed_targets: vec![ - "history11".to_string(), - "history0".to_string(), - "history1".to_string(), - "history2".to_string(), - "history3".to_string(), - "history4".to_string(), - "history5".to_string(), - "history6".to_string(), - "history7".to_string(), - "history8".to_string(), + command::Command::new( + runner_type::RunnerType::Make, + "history11".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history0".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history1".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history2".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history3".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history4".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history5".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history6".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history7".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history8".to_string(), + PathBuf::from("Makefile"), + 4, + ), ], }, }, @@ -417,7 +708,7 @@ mod test { for case in cases { assert_eq!( case.expect, - case.history.append(case.appending_target.to_string()), + case.history.append(case.appending_target), "\nFailed: 🚨{:?}🚨\n", case.title, ) diff --git a/src/model/make.rs b/src/model/make.rs index e9be47d..066a81c 100644 --- a/src/model/make.rs +++ b/src/model/make.rs @@ -69,6 +69,7 @@ impl Make { let file_name_string = file_name.to_str().unwrap(); if makefile_name_options.contains(&file_name_string) { let current_dir = match env::current_dir() { + // TODO: use model.current_dir Err(_) => return None, Ok(d) => d, }; diff --git a/src/model/runner_type.rs b/src/model/runner_type.rs index dd58864..1a7664d 100644 --- a/src/model/runner_type.rs +++ b/src/model/runner_type.rs @@ -1,8 +1,9 @@ +use serde::{Deserialize, Serialize}; use std::fmt; // TODO(#321): remove #[allow(dead_code)] -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] pub enum RunnerType { Make, Pnpm, diff --git a/src/usecase/repeat.rs b/src/usecase/repeat.rs index 85bb394..d074fde 100644 --- a/src/usecase/repeat.rs +++ b/src/usecase/repeat.rs @@ -23,23 +23,13 @@ impl Usecase for Repeat { match Model::new(config::Config::default()) { Err(e) => Err(e), Ok(model) => match model.app_state { - AppState::SelectTarget(model) => { - match model.histories.map(|_h| { - // TODO(#321): Decide the specification of this. - // 1. Find the latest history that starts with cwd and execute it (need to save information about which one is the latest) - // 2. When there are multiple candidates, display the choices and let the user choose? - match &model.runners.first() { - Some(_runner) => { - None:: // TODO(#321): Fix this when history function is implemented - // h - // .get_latest_target(&runner.path()) - // .map(execute_make_command), - } - None => None, - } - }) { - Some(Some(_)) => Ok(()), - _ => Err(anyhow!("No target found")), + AppState::SelectTarget(state) => { + match ( + state.runners.first(), + state.histories.get_latest_target(&state.current_dir), + ) { + (Some(r), Some(h)) => r.execute(h), + (_, _) => Err(anyhow!("fzf-make has not been executed in this path yet.")), } } _ => Err(anyhow!("Invalid state")), diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index ba63ec0..a09352e 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -9,7 +9,7 @@ use crate::{ }; use super::{config, ui::ui}; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture, KeyCode, KeyEvent}, execute, @@ -23,6 +23,7 @@ use ratatui::{ }; use std::{ collections::HashMap, + env, io::{self, Stderr}, panic, path::PathBuf, @@ -81,18 +82,22 @@ impl Model<'_> { } } - fn get_histories(makefile_path: PathBuf) -> Option { - history_file_path().map(|(history_file_dir, history_file_name)| { - let content = - match path_to_content::path_to_content(history_file_dir.join(history_file_name)) { + fn get_histories(makefile_path: PathBuf) -> Histories { + match history_file_path() { + Some((history_file_dir, history_file_name)) => { + let content = match path_to_content::path_to_content( + history_file_dir.join(history_file_name), + ) { Err(_) => return Histories::new(makefile_path, vec![]), // NOTE: Show error message on message pane https://github.com/kyu08/fzf-make/issues/152 Ok(c) => c, }; - // TODO: Show error message on message pane if parsing history file failed. https://github.com/kyu08/fzf-make/issues/152 - let histories = toml::parse_history(content.to_string()).unwrap_or_default(); + // TODO: Show error message on message pane if parsing history file failed. https://github.com/kyu08/fzf-make/issues/152 + let histories = toml::parse_history(content.to_string()).unwrap_or_default(); - Histories::new(makefile_path, histories) - }) + Histories::new(makefile_path, histories) + } + None => Histories::default(makefile_path), + } } fn transition_to_execute_target_state( @@ -262,11 +267,12 @@ fn update(model: &mut Model, message: Option) { #[derive(Debug)] pub struct SelectTargetState<'a> { + pub current_dir: PathBuf, pub current_pane: CurrentPane, pub runners: Vec, pub search_text_area: TextArea_<'a>, pub targets_list_state: ListState, - pub histories: Option, + pub histories: Histories, pub histories_list_state: ListState, } @@ -297,6 +303,10 @@ impl PartialEq for SelectTargetState<'_> { impl SelectTargetState<'_> { pub fn new(config: config::Config) -> Result { + let current_dir = match env::current_dir() { + Ok(d) => d, + Err(e) => bail!("Failed to get current directory: {}", e), + }; let makefile = match Make::create_makefile() { Err(e) => return Err(e), Ok(f) => f, @@ -311,6 +321,7 @@ impl SelectTargetState<'_> { let path = runner.path(); Ok(SelectTargetState { + current_dir, current_pane, runners: vec![runner], search_text_area: TextArea_(TextArea::default()), @@ -416,6 +427,8 @@ impl SelectTargetState<'_> { } pub fn get_history(&self) -> Vec { + // TODO(#321): この関数内で + // historyにあるcommandをself.runnersから取得するよう(行数やファイル名を最新状態からとってこないとちゃんとプレビュー表示できないため)(e.g. ファイル行番号が変わってる場合プレビューがずれる) vec![] // TODO(#321): implement when history function is implemented // UIに表示するためのhistory一覧を取得する関数。 @@ -544,27 +557,45 @@ impl SelectTargetState<'_> { } #[cfg(test)] - fn init_histories(history_targets: Vec) -> Option { + fn init_histories(history_targets: Vec) -> Histories { use std::{env, path::Path}; let makefile_path = env::current_dir().unwrap().join(Path::new("Test.mk")); - Some(Histories::new( + Histories::new( makefile_path.clone(), vec![(makefile_path, history_targets)], - )) + ) } #[cfg(test)] fn new_for_test() -> Self { + use crate::model::runner_type; + SelectTargetState { + current_dir: env::current_dir().unwrap(), current_pane: CurrentPane::Main, runners: vec![runner::Runner::MakeCommand(Make::new_for_test())], search_text_area: TextArea_(TextArea::default()), targets_list_state: ListState::with_selected(ListState::default(), Some(0)), histories: SelectTargetState::init_histories(vec![ - "history0".to_string(), - "history1".to_string(), - "history2".to_string(), + command::Command::new( + runner_type::RunnerType::Make, + "history0".to_string(), + PathBuf::from("Makefile"), + 1, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history1".to_string(), + PathBuf::from("Makefile"), + 4, + ), + command::Command::new( + runner_type::RunnerType::Make, + "history2".to_string(), + PathBuf::from("Makefile"), + 7, + ), ]), histories_list_state: ListState::with_selected(ListState::default(), Some(0)), } From 769f6d6f97c9dc4bf69b64d2ed44f5c57bc3c0db Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Fri, 15 Nov 2024 00:01:49 +0900 Subject: [PATCH 03/36] wip --- src/file/toml.rs | 30 ++- src/model/histories.rs | 573 +++++++++++++++++++++-------------------- src/usecase/tui/app.rs | 1 + 3 files changed, 313 insertions(+), 291 deletions(-) diff --git a/src/file/toml.rs b/src/file/toml.rs index a7e4c53..b5baa88 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -81,13 +81,31 @@ mod test { Case { title: "Success", content: r#" -[[history]] +[[tasks]] path = "/Users/user/code/fzf-make" -executed-targets = ["test", "check", "spell-check"] -[[history]] +[[tasks.commands]] +runner = "make" +command = "test" + +[[tasks.commands]] +runner = "make" +command = "check" + +[[tasks.commands]] +runner = "make" +command = "spell-check" + +[[tasks]]] path = "/Users/user/code/golang/go-playground" -executed-targets = ["run", "echo1"] + +[[tasks.commands]] +runner = "make" +command = "run" + +[[tasks.commands]] +runner = "make" +command = "echo1" "# .to_string(), expect: Ok(vec![ @@ -96,7 +114,9 @@ executed-targets = ["run", "echo1"] vec![ command::Command::new( runner_type::RunnerType::Make, - "target-a".to_string(), + // WARN: ここにコマンドファイル名って持つ必要ないんだっけ? + // ないならいらないフィールドをoptionにするか構造体を分けるかしたいなー + "test".to_string(), PathBuf::from("Makefile"), 1, ), diff --git a/src/model/histories.rs b/src/model/histories.rs index 928fc76..ecc8b7e 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -428,290 +428,291 @@ mod test { // ) // } // } - #[test] - fn history_append_test() { - struct Case { - title: &'static str, - appending_target: command::Command, - 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: command::Command::new( - runner_type::RunnerType::Make, - "history2".to_string(), - PathBuf::from("Makefile"), - 1, - ), - history: History { - path: path.clone(), - executed_targets: vec![ - command::Command::new( - runner_type::RunnerType::Make, - "history0".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history1".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - }, - expect: History { - path: path.clone(), - executed_targets: vec![ - command::Command::new( - runner_type::RunnerType::Make, - "history2".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history0".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history1".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - }, - }, - Case { - title: "Append to head(Append to empty)", - appending_target: command::Command::new( - runner_type::RunnerType::Make, - "history0".to_string(), - PathBuf::from("Makefile"), - 4, - ), - history: History { - path: path.clone(), - executed_targets: vec![], - }, - expect: History { - path: path.clone(), - executed_targets: vec![command::Command::new( - runner_type::RunnerType::Make, - "history0".to_string(), - PathBuf::from("Makefile"), - 4, - )], - }, - }, - Case { - title: "Append to head(Remove duplicated)", - appending_target: command::Command::new( - runner_type::RunnerType::Make, - "history1".to_string(), - PathBuf::from("Makefile"), - 4, - ), - history: History { - path: path.clone(), - executed_targets: vec![ - command::Command::new( - runner_type::RunnerType::Make, - "history0".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history1".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history2".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - }, - expect: History { - path: path.clone(), - executed_targets: vec![ - command::Command::new( - runner_type::RunnerType::Make, - "history1".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history0".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history2".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - }, - }, - Case { - title: "Truncate when length exceeds 10", - appending_target: command::Command::new( - runner_type::RunnerType::Make, - "history11".to_string(), - PathBuf::from("Makefile"), - 1, - ), - history: History { - path: path.clone(), - executed_targets: vec![ - command::Command::new( - runner_type::RunnerType::Make, - "history0".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history1".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history2".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history3".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history4".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history5".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history6".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history7".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history8".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history9".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - }, - expect: History { - path: path.clone(), - executed_targets: vec![ - command::Command::new( - runner_type::RunnerType::Make, - "history11".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history0".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history1".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history2".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history3".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history4".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history5".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history6".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history7".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history8".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - }, - }, - ]; - - for case in cases { - assert_eq!( - case.expect, - case.history.append(case.appending_target), - "\nFailed: 🚨{:?}🚨\n", - case.title, - ) - } - } + // TODO(#321): comment in this test + // #[test] + // fn history_append_test() { + // struct Case { + // title: &'static str, + // appending_target: command::Command, + // 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: command::Command::new( + // runner_type::RunnerType::Make, + // "history2".to_string(), + // PathBuf::from("Makefile"), + // 1, + // ), + // history: History { + // path: path.clone(), + // executed_targets: vec![ + // command::Command::new( + // runner_type::RunnerType::Make, + // "history0".to_string(), + // PathBuf::from("Makefile"), + // 1, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history1".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // ], + // }, + // expect: History { + // path: path.clone(), + // executed_targets: vec![ + // command::Command::new( + // runner_type::RunnerType::Make, + // "history2".to_string(), + // PathBuf::from("Makefile"), + // 1, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history0".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history1".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // ], + // }, + // }, + // Case { + // title: "Append to head(Append to empty)", + // appending_target: command::Command::new( + // runner_type::RunnerType::Make, + // "history0".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // history: History { + // path: path.clone(), + // executed_targets: vec![], + // }, + // expect: History { + // path: path.clone(), + // executed_targets: vec![command::Command::new( + // runner_type::RunnerType::Make, + // "history0".to_string(), + // PathBuf::from("Makefile"), + // 4, + // )], + // }, + // }, + // Case { + // title: "Append to head(Remove duplicated)", + // appending_target: command::Command::new( + // runner_type::RunnerType::Make, + // "history1".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // history: History { + // path: path.clone(), + // executed_targets: vec![ + // command::Command::new( + // runner_type::RunnerType::Make, + // "history0".to_string(), + // PathBuf::from("Makefile"), + // 1, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history1".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history2".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // ], + // }, + // expect: History { + // path: path.clone(), + // executed_targets: vec![ + // command::Command::new( + // runner_type::RunnerType::Make, + // "history1".to_string(), + // PathBuf::from("Makefile"), + // 1, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history0".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history2".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // ], + // }, + // }, + // Case { + // title: "Truncate when length exceeds 10", + // appending_target: command::Command::new( + // runner_type::RunnerType::Make, + // "history11".to_string(), + // PathBuf::from("Makefile"), + // 1, + // ), + // history: History { + // path: path.clone(), + // executed_targets: vec![ + // command::Command::new( + // runner_type::RunnerType::Make, + // "history0".to_string(), + // PathBuf::from("Makefile"), + // 1, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history1".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history2".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history3".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history4".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history5".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history6".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history7".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history8".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history9".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // ], + // }, + // expect: History { + // path: path.clone(), + // executed_targets: vec![ + // command::Command::new( + // runner_type::RunnerType::Make, + // "history11".to_string(), + // PathBuf::from("Makefile"), + // 1, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history0".to_string(), + // PathBuf::from("Makefile"), + // 1, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history1".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history2".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history3".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history4".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history5".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history6".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history7".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "history8".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // ], + // }, + // }, + // ]; + // + // for case in cases { + // assert_eq!( + // case.expect, + // case.history.append(case.appending_target), + // "\nFailed: 🚨{:?}🚨\n", + // case.title, + // ) + // } + // } } diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index a09352e..8d49e9f 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -82,6 +82,7 @@ impl Model<'_> { } } + // TODO: pass cwd instead of makefile_path fn get_histories(makefile_path: PathBuf) -> Histories { match history_file_path() { Some((history_file_dir, history_file_name)) => { From 46c2bdd5e07398da7757547018b81e6390621bd6 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Fri, 15 Nov 2024 00:26:44 +0900 Subject: [PATCH 04/36] wip --- src/file/toml.rs | 2 +- src/model/command.rs | 3 --- src/model/histories.rs | 18 +++++++++++++++--- src/usecase/tui/app.rs | 18 +++++++++++++----- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/file/toml.rs b/src/file/toml.rs index b5baa88..338a25b 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -30,7 +30,7 @@ impl Histories { #[serde(rename_all = "kebab-case")] struct History { path: String, - executed_targets: Vec, + executed_targets: Vec, } pub fn parse_history(content: String) -> Result)>> { diff --git a/src/model/command.rs b/src/model/command.rs index 49a6e7f..c73d8d9 100644 --- a/src/model/command.rs +++ b/src/model/command.rs @@ -1,10 +1,7 @@ -use serde::{Deserialize, Serialize}; use std::{fmt, path::PathBuf}; use super::runner_type; -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] pub struct Command { pub runner_type: runner_type::RunnerType, pub name: String, diff --git a/src/model/histories.rs b/src/model/histories.rs index ecc8b7e..c91baf3 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -1,7 +1,8 @@ +use serde::{Deserialize, Serialize}; use simple_home_dir::home_dir; use std::{env, path::PathBuf}; -use super::command; +use super::{command, runner_type}; #[derive(Clone, PartialEq, Debug)] pub struct Histories { @@ -66,6 +67,7 @@ impl Histories { } pub fn default(path: PathBuf) -> Self { + // TODO: should receive cwd instead of makefile_path let histories = vec![History::default(path)]; Self { histories } } @@ -111,8 +113,8 @@ pub fn history_file_path() -> Option<(PathBuf, String)> { #[derive(Clone, PartialEq, Debug)] struct History { - path: PathBuf, // TODO: rename to working_directory - executed_targets: Vec, // TODO: rename to executed_commands + path: PathBuf, // TODO: rename to working_directory + executed_targets: Vec, // TODO: rename to executed_commands } impl History { @@ -149,6 +151,16 @@ impl History { } } +/// In the history file, the command has only the name of the command and the runner type. +/// Because its file name where it's defined and file number is variable. +/// So we search them every time fzf-make is launched. +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +struct HistoryCommand { + runner_type: runner_type::RunnerType, + name: String, +} + #[cfg(test)] mod test { use crate::model::runner_type; diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index 8d49e9f..c0670f3 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -86,12 +86,15 @@ impl Model<'_> { fn get_histories(makefile_path: PathBuf) -> Histories { match history_file_path() { Some((history_file_dir, history_file_name)) => { - let content = match path_to_content::path_to_content( - history_file_dir.join(history_file_name), - ) { - Err(_) => return Histories::new(makefile_path, vec![]), // NOTE: Show error message on message pane https://github.com/kyu08/fzf-make/issues/152 - Ok(c) => c, + let content = { + let content = + path_to_content::path_to_content(history_file_dir.join(history_file_name)); + match content { + Err(_) => return Histories::new(makefile_path, vec![]), // NOTE: Show error message on message pane https://github.com/kyu08/fzf-make/issues/152 + Ok(c) => c, + } }; + // TODO: Show error message on message pane if parsing history file failed. https://github.com/kyu08/fzf-make/issues/152 let histories = toml::parse_history(content.to_string()).unwrap_or_default(); @@ -327,6 +330,8 @@ impl SelectTargetState<'_> { runners: vec![runner], search_text_area: TextArea_(TextArea::default()), targets_list_state: ListState::with_selected(ListState::default(), Some(0)), + // TODO: + // ここでHistoriesのうちすでに存在しないcommandをfilterする必要がありそう。その過程でcommand::Commandに変換するようにする。 histories: Model::get_histories(path), histories_list_state: ListState::with_selected(ListState::default(), Some(0)), }) @@ -428,6 +433,9 @@ impl SelectTargetState<'_> { } pub fn get_history(&self) -> Vec { + // MEMO: mainではhistoriesの中からmakefile_pathのhistoryを取得する関数。 + // cwdの履歴だけ取得するようにすればこの関数はいらなくなるかも。 + // // TODO(#321): この関数内で // historyにあるcommandをself.runnersから取得するよう(行数やファイル名を最新状態からとってこないとちゃんとプレビュー表示できないため)(e.g. ファイル行番号が変わってる場合プレビューがずれる) vec![] From b463c4cc9d85361d2559c79f67b6dbb2b34dcd8b Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Mon, 18 Nov 2024 00:53:39 +0900 Subject: [PATCH 05/36] wip --- src/file/toml.rs | 190 +++++++++++++++++++----------- src/model/command.rs | 1 + src/model/histories.rs | 258 +++-------------------------------------- src/usecase/repeat.rs | 4 +- src/usecase/tui/app.rs | 127 +++++++++++++------- 5 files changed, 231 insertions(+), 349 deletions(-) diff --git a/src/file/toml.rs b/src/file/toml.rs index 338a25b..4be05b7 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -6,51 +6,105 @@ use std::{ path::PathBuf, }; -use crate::model::command; +use crate::model::{histories, runner_type}; #[derive(Debug, Serialize, Deserialize)] struct Histories { - history: Vec, + histories: Vec, } impl Histories { - fn from(histories: Vec<(PathBuf, Vec)>) -> Self { + fn from(histories: histories::Histories) -> Self { let mut result: Vec = vec![]; - for h in histories { - result.push(History { - path: h.0.to_str().unwrap().to_string(), - executed_targets: h.1, - }); + for h in histories.histories { + result.push(History::from(h)); } - Histories { history: result } + Self { histories: result } + } + + fn into(self) -> histories::Histories { + let mut result: Vec = vec![]; + for h in self.histories { + result.push(History::into(h)); + } + histories::Histories { histories: result } + } +} + +impl std::default::Default for Histories { + fn default() -> Self { + Self { histories: vec![] } } } #[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] struct History { - path: String, - executed_targets: Vec, + path: PathBuf, + commands: Vec, +} + +impl History { + fn from(history: histories::History) -> Self { + let mut commands: Vec = vec![]; + for h in history.executed_commands { + commands.push(HistoryCommand::from(h)); + } + + History { + path: history.path, + commands, + } + } + + fn into(self) -> histories::History { + let mut commands: Vec = vec![]; + for h in self.commands { + commands.push(HistoryCommand::into(h)); + } + + histories::History { + path: self.path, + executed_commands: commands, + } + } } -pub fn parse_history(content: String) -> Result)>> { - let histories: Histories = toml::from_str(&content)?; +/// toml representation of histories::HistoryCommand. +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +struct HistoryCommand { + runner_type: runner_type::RunnerType, + name: String, +} - let mut result: Vec<(PathBuf, Vec)> = Vec::new(); +impl HistoryCommand { + fn from(command: histories::HistoryCommand) -> Self { + Self { + runner_type: command.runner_type, + name: command.name.clone(), + } + } - for history in histories.history { - result.push((PathBuf::from(history.path), history.executed_targets)); + fn into(self) -> histories::HistoryCommand { + histories::HistoryCommand { + runner_type: self.runner_type, + name: self.name, + } } - Ok(result) +} + +pub fn parse_history(content: String) -> Result { + let histories = toml::from_str(&content)?; + Ok(Histories::into(histories)) } #[allow(dead_code)] // TODO(#321): remove pub fn store_history( history_directory_path: PathBuf, history_file_name: String, - histories_tuple: Vec<(PathBuf, Vec)>, + histories: histories::Histories, ) -> Result<()> { - let histories = Histories::from(histories_tuple); + let histories = Histories::from(histories); if !history_directory_path.is_dir() { fs::create_dir_all(history_directory_path.clone())?; @@ -66,7 +120,6 @@ pub fn store_history( #[cfg(test)] mod test { use crate::model::runner_type; - use super::*; use anyhow::Result; @@ -75,7 +128,7 @@ mod test { struct Case { title: &'static str, content: String, - expect: Result)>>, + expect: Result, } let cases = vec![ Case { @@ -108,50 +161,55 @@ runner = "make" command = "echo1" "# .to_string(), - expect: Ok(vec![ - ( - PathBuf::from("/Users/user/code/fzf-make".to_string()), - vec![ - command::Command::new( - runner_type::RunnerType::Make, - // WARN: ここにコマンドファイル名って持つ必要ないんだっけ? - // ないならいらないフィールドをoptionにするか構造体を分けるかしたいなー - "test".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "check".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "spell-check".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - ), - ( - PathBuf::from("/Users/user/code/golang/go-playground".to_string()), - vec![ - command::Command::new( - runner_type::RunnerType::Make, - "run".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "echo1".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - ), - ]), + expect: Ok( + histories::Histories{ histories: vec![ + histories::History{ path: PathBuf::from("/Users/user/code/fzf-make".to_string()), executed_commands: vec![ + histories::HistoryCommand{ + runner_type: runner_type::RunnerType::Make, + name: "test".to_string() }, + ] }, + ] }, + // ( + // PathBuf::from("/Users/user/code/fzf-make".to_string()), + // vec![ + // command::Command::new( + // runner_type::RunnerType::Make, + // "test".to_string(), + // PathBuf::from("Makefile"), + // 1, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "check".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "spell-check".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // ], + // ), + // ( + // PathBuf::from("/Users/user/code/golang/go-playground".to_string()), + // vec![ + // command::Command::new( + // runner_type::RunnerType::Make, + // "run".to_string(), + // PathBuf::from("Makefile"), + // 1, + // ), + // command::Command::new( + // runner_type::RunnerType::Make, + // "echo1".to_string(), + // PathBuf::from("Makefile"), + // 4, + // ), + // ], + // ), + ), }, Case { title: "Error", diff --git a/src/model/command.rs b/src/model/command.rs index c73d8d9..d50d142 100644 --- a/src/model/command.rs +++ b/src/model/command.rs @@ -2,6 +2,7 @@ use std::{fmt, path::PathBuf}; use super::runner_type; +#[derive(PartialEq, Clone, Debug)] pub struct Command { pub runner_type: runner_type::RunnerType, pub name: String, diff --git a/src/model/histories.rs b/src/model/histories.rs index c91baf3..c5d699c 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -1,22 +1,17 @@ -use serde::{Deserialize, Serialize}; use simple_home_dir::home_dir; use std::{env, path::PathBuf}; use super::{command, runner_type}; +/// Histories is a all collection of History. This equals whole content of history.toml. +/// For now, we can define this as tuple like `pub struct Histories(Vec);` but we don't. +/// We respect that we can add some fields in the future easily. #[derive(Clone, PartialEq, Debug)] pub struct Histories { - histories: Vec, + pub histories: Vec, } impl Histories { - pub fn new(makefile_path: PathBuf, histories: Vec<(PathBuf, Vec)>) -> Self { - match histories.len() { - 0 => Self::default(makefile_path), - _ => Self::from(makefile_path, histories), - } - } - // TODO(#321): Make this fn returns Vec // pub fn get_histories(&self, paths: Vec) -> Vec { // let mut histories: Vec = Vec::new(); @@ -59,33 +54,11 @@ impl Histories { // result // } - pub fn get_latest_target(&self, path: &PathBuf) -> Option<&command::Command> { + pub fn get_latest_command(&self, path: &PathBuf) -> Option<&HistoryCommand> { self.histories .iter() .find(|h| h.path == *path) - .map(|h| h.executed_targets.first())? - } - - pub fn default(path: PathBuf) -> Self { - // TODO: should receive cwd instead of makefile_path - let histories = vec![History::default(path)]; - Self { histories } - } - - fn from(makefile_path: PathBuf, histories: Vec<(PathBuf, Vec)>) -> Self { - let mut result = Histories { - histories: Vec::new(), - }; - - for history in histories.clone() { - result.histories.push(History::from(history)); - } - - if !histories.iter().any(|h| h.0 == makefile_path) { - result.histories.push(History::default(makefile_path)); - } - - result + .map(|h| h.executed_commands.first())? } } @@ -112,30 +85,30 @@ pub fn history_file_path() -> Option<(PathBuf, String)> { } #[derive(Clone, PartialEq, Debug)] -struct History { - path: PathBuf, // TODO: rename to working_directory - executed_targets: Vec, // TODO: rename to executed_commands +pub struct History { + pub path: PathBuf, + pub executed_commands: Vec, // TODO: rename to executed_commands } impl History { fn default(path: PathBuf) -> Self { Self { path, - executed_targets: Vec::new(), + executed_commands: Vec::new(), } } fn from(histories: (PathBuf, Vec)) -> Self { Self { path: histories.0, - executed_targets: histories.1, + executed_commands: histories.1, } } // TODO(#321): remove #[allow(dead_code)] fn append(&self, executed_target: command::Command) -> Self { - let mut executed_targets = self.executed_targets.clone(); + let mut executed_targets = self.executed_commands.clone(); executed_targets.retain(|t| *t != executed_target); executed_targets.insert(0, executed_target.clone()); @@ -146,7 +119,7 @@ impl History { Self { path: self.path.clone(), - executed_targets, + executed_commands: executed_targets, } } } @@ -154,211 +127,14 @@ impl History { /// In the history file, the command has only the name of the command and the runner type. /// Because its file name where it's defined and file number is variable. /// So we search them every time fzf-make is launched. -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -struct HistoryCommand { - runner_type: runner_type::RunnerType, - name: String, +#[derive(PartialEq, Clone, Debug)] +pub struct HistoryCommand { + pub runner_type: runner_type::RunnerType, + pub name: String, } #[cfg(test)] mod test { - use crate::model::runner_type; - - use super::*; - - #[test] - fn histories_new_test() { - struct Case { - title: &'static str, - makefile_path: PathBuf, - histories: Vec<(PathBuf, Vec)>, - expect: Histories, - } - let cases = vec![ - Case { - title: "histories.len() == 0", - makefile_path: PathBuf::from("/Users/user/code/fzf-make".to_string()), - histories: vec![], - expect: Histories { - histories: vec![History { - path: PathBuf::from("/Users/user/code/fzf-make".to_string()), - executed_targets: vec![], - }], - }, - }, - Case { - title: "histories.len() != 0(Including makefile_path)", - makefile_path: PathBuf::from("/Users/user/code/fzf-make".to_string()), - histories: vec![ - ( - PathBuf::from("/Users/user/code/fzf-make".to_string()), - vec![ - command::Command::new( - runner_type::RunnerType::Make, - "target1".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "target2".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - ), - ( - PathBuf::from("/Users/user/code/rustc".to_string()), - vec![ - command::Command::new( - runner_type::RunnerType::Make, - "target-a".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "target-b".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - ), - ], - expect: Histories { - histories: vec![ - History { - path: PathBuf::from("/Users/user/code/fzf-make".to_string()), - executed_targets: vec![ - command::Command::new( - runner_type::RunnerType::Make, - "target1".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "target2".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - }, - History { - path: PathBuf::from("/Users/user/code/rustc".to_string()), - executed_targets: vec![ - command::Command::new( - runner_type::RunnerType::Make, - "target-a".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "target-b".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - }, - ], - }, - }, - Case { - title: "histories.len() != 0(Not including makefile_path)", - makefile_path: PathBuf::from("/Users/user/code/cargo".to_string()), - histories: vec![ - ( - PathBuf::from("/Users/user/code/fzf-make".to_string()), - vec![ - command::Command::new( - runner_type::RunnerType::Make, - "target1".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "target2".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - ), - ( - PathBuf::from("/Users/user/code/rustc".to_string()), - vec![ - command::Command::new( - runner_type::RunnerType::Make, - "target-a".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "target-b".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - ), - ], - expect: Histories { - histories: vec![ - History { - path: PathBuf::from("/Users/user/code/fzf-make".to_string()), - executed_targets: vec![ - command::Command::new( - runner_type::RunnerType::Make, - "target1".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "target2".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - }, - History { - path: PathBuf::from("/Users/user/code/rustc".to_string()), - executed_targets: vec![ - command::Command::new( - runner_type::RunnerType::Make, - "target-a".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "target-b".to_string(), - PathBuf::from("Makefile"), - 4, - ), - ], - }, - History { - path: PathBuf::from("/Users/user/code/cargo".to_string()), - executed_targets: vec![], - }, - ], - }, - }, - ]; - - for case in cases { - assert_eq!( - case.expect, - Histories::new(case.makefile_path, case.histories), - "\nFailed: 🚨{:?}🚨\n", - case.title, - ) - } - } - // TODO(#321): comment in this test // #[test] // fn histories_append_test() { diff --git a/src/usecase/repeat.rs b/src/usecase/repeat.rs index d074fde..89e95d8 100644 --- a/src/usecase/repeat.rs +++ b/src/usecase/repeat.rs @@ -25,8 +25,8 @@ impl Usecase for Repeat { Ok(model) => match model.app_state { AppState::SelectTarget(state) => { match ( - state.runners.first(), - state.histories.get_latest_target(&state.current_dir), + state.runners.first(), // TODO: firstではなく最後に実行されたcommandのrunnerを使うべき + state.histories.get_latest_command(&state.current_dir), ) { (Some(r), Some(h)) => r.execute(h), (_, _) => Err(anyhow!("fzf-make has not been executed in this path yet.")), diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index c0670f3..12e51c4 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -2,9 +2,9 @@ use crate::{ file::{path_to_content, toml}, model::{ command, - histories::{history_file_path, Histories}, + histories::{self, history_file_path}, make::Make, - runner, + runner, runner_type, }, }; @@ -31,6 +31,9 @@ use std::{ }; use tui_textarea::TextArea; +#[cfg(test)] +use crate::model::histories::Histories; + // AppState represents the state of the application. // "Making impossible states impossible" // The type of `AppState` is defined according to the concept of 'Making Impossible States Impossible'. @@ -82,26 +85,66 @@ impl Model<'_> { } } - // TODO: pass cwd instead of makefile_path - fn get_histories(makefile_path: PathBuf) -> Histories { + fn get_histories( + current_working_directory: PathBuf, + runners: Vec, + ) -> Vec { match history_file_path() { Some((history_file_dir, history_file_name)) => { let content = { let content = path_to_content::path_to_content(history_file_dir.join(history_file_name)); match content { - Err(_) => return Histories::new(makefile_path, vec![]), // NOTE: Show error message on message pane https://github.com/kyu08/fzf-make/issues/152 Ok(c) => c, + Err(_) => return vec![], } }; // TODO: Show error message on message pane if parsing history file failed. https://github.com/kyu08/fzf-make/issues/152 - let histories = toml::parse_history(content.to_string()).unwrap_or_default(); + let histories = match toml::parse_history(content.to_string()) { + Ok(h) => h, + Err(_) => return vec![], + }; - Histories::new(makefile_path, histories) + let mut result: Vec = Vec::new(); + for history in histories.histories { + if history.path != current_working_directory { + continue; + } + result = Self::get_commands_from_history(history.executed_commands, &runners); + break; + } + result } - None => Histories::default(makefile_path), + None => vec![], + } + } + + fn get_commands_from_history( + history_commands: Vec, + runners: &Vec, + ) -> Vec { + // TODO: Make this more readable and more performant. + let mut commands: Vec = Vec::new(); + for history_command in history_commands { + match history_command.runner_type { + runner_type::RunnerType::Make => { + for runner in runners { + if let runner::Runner::MakeCommand(make) = runner { + // PERF: This method is called every time. Memoize should be considered. + for c in make.to_commands() { + if c.name == history_command.name { + commands.push(c); + break; + } + } + } + } + } + runner_type::RunnerType::Pnpm => todo!(), + }; } + commands } fn transition_to_execute_target_state( @@ -276,7 +319,7 @@ pub struct SelectTargetState<'a> { pub runners: Vec, pub search_text_area: TextArea_<'a>, pub targets_list_state: ListState, - pub histories: Histories, + pub histories: Vec, pub histories_list_state: ListState, } @@ -324,15 +367,15 @@ impl SelectTargetState<'_> { let runner = { runner::Runner::MakeCommand(makefile) }; let path = runner.path(); + let runners = vec![runner]; Ok(SelectTargetState { current_dir, current_pane, - runners: vec![runner], + runners: runners.clone(), search_text_area: TextArea_(TextArea::default()), targets_list_state: ListState::with_selected(ListState::default(), Some(0)), - // TODO: - // ここでHistoriesのうちすでに存在しないcommandをfilterする必要がありそう。その過程でcommand::Commandに変換するようにする。 - histories: Model::get_histories(path), + // TODO: pass cwd instead of makefile_path + histories: Model::get_histories(path, runners), histories_list_state: ListState::with_selected(ListState::default(), Some(0)), }) } @@ -435,7 +478,9 @@ impl SelectTargetState<'_> { pub fn get_history(&self) -> Vec { // MEMO: mainではhistoriesの中からmakefile_pathのhistoryを取得する関数。 // cwdの履歴だけ取得するようにすればこの関数はいらなくなるかも。 - // + // TODO: + // そもそもapplication側ではcommand::Commandだけをhistoryとして持つべき。そうすれば実行時も楽だしpreviewも楽に出すことができる + // TODO(#321): この関数内で // historyにあるcommandをself.runnersから取得するよう(行数やファイル名を最新状態からとってこないとちゃんとプレビュー表示できないため)(e.g. ファイル行番号が変わってる場合プレビューがずれる) vec![] @@ -566,14 +611,22 @@ impl SelectTargetState<'_> { } #[cfg(test)] - fn init_histories(history_targets: Vec) -> Histories { - use std::{env, path::Path}; - - let makefile_path = env::current_dir().unwrap().join(Path::new("Test.mk")); - Histories::new( - makefile_path.clone(), - vec![(makefile_path, history_targets)], - ) + fn init_histories(history_commands: Vec) -> Histories { + let mut commands: Vec = Vec::new(); + + for h in history_commands { + commands.push(histories::HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: h.name, + }); + } + + Histories { + histories: vec![histories::History { + path: env::current_dir().unwrap().join(Path::new("Test.mk")), + executed_commands: commands, + }], + } } #[cfg(test)] @@ -587,24 +640,18 @@ impl SelectTargetState<'_> { search_text_area: TextArea_(TextArea::default()), targets_list_state: ListState::with_selected(ListState::default(), Some(0)), histories: SelectTargetState::init_histories(vec![ - command::Command::new( - runner_type::RunnerType::Make, - "history0".to_string(), - PathBuf::from("Makefile"), - 1, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history1".to_string(), - PathBuf::from("Makefile"), - 4, - ), - command::Command::new( - runner_type::RunnerType::Make, - "history2".to_string(), - PathBuf::from("Makefile"), - 7, - ), + histories::HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + }, + histories::HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history1".to_string(), + }, + histories::HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history2".to_string(), + }, ]), histories_list_state: ListState::with_selected(ListState::default(), Some(0)), } From 1ac8e26279a4b16a7e2455e10c32acdf10575899 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Tue, 19 Nov 2024 00:27:27 +0900 Subject: [PATCH 06/36] wip --- src/model/histories.rs | 7 ------ src/usecase/repeat.rs | 16 ++++++------ src/usecase/tui/app.rs | 55 +++++++++++++++++++++++++++++++----------- 3 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/model/histories.rs b/src/model/histories.rs index c5d699c..3b65b12 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -53,13 +53,6 @@ impl Histories { // } // result // } - - pub fn get_latest_command(&self, path: &PathBuf) -> Option<&HistoryCommand> { - self.histories - .iter() - .find(|h| h.path == *path) - .map(|h| h.executed_commands.first())? - } } // TODO(#321): should return Result not Option(returns when it fails to get the home dir) diff --git a/src/usecase/repeat.rs b/src/usecase/repeat.rs index 89e95d8..6ae7bc2 100644 --- a/src/usecase/repeat.rs +++ b/src/usecase/repeat.rs @@ -23,15 +23,13 @@ impl Usecase for Repeat { match Model::new(config::Config::default()) { Err(e) => Err(e), Ok(model) => match model.app_state { - AppState::SelectTarget(state) => { - match ( - state.runners.first(), // TODO: firstではなく最後に実行されたcommandのrunnerを使うべき - state.histories.get_latest_command(&state.current_dir), - ) { - (Some(r), Some(h)) => r.execute(h), - (_, _) => Err(anyhow!("fzf-make has not been executed in this path yet.")), - } - } + AppState::SelectTarget(state) => match state.get_latest_command() { + Some(c) => match state.get_runner(&c.runner_type) { + Some(runner) => runner.execute(c), + None => Err(anyhow!("runner not found.")), + }, + None => Err(anyhow!("fzf-make has not been executed in this path yet.")), + }, _ => Err(anyhow!("Invalid state")), }, } diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index 12e51c4..596ed2d 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -610,8 +610,29 @@ impl SelectTargetState<'_> { self.search_text_area.0.lines().join("") } + pub fn get_latest_command(&self) -> Option<&command::Command> { + Some(self.histories.first()?) + } + + pub fn get_runner(&self, runner_type: &runner_type::RunnerType) -> Option { + for runner in &self.runners { + match (runner_type, runner) { + (runner_type::RunnerType::Make, runner::Runner::MakeCommand(_)) => { + return Some(runner.clone()); + } + (runner_type::RunnerType::Pnpm, runner::Runner::PnpmCommand(_)) => { + return Some(runner.clone()); + } + _ => continue, + } + } + None + } + #[cfg(test)] fn init_histories(history_commands: Vec) -> Histories { + use std::path::Path; + let mut commands: Vec = Vec::new(); for h in history_commands { @@ -639,20 +660,26 @@ impl SelectTargetState<'_> { runners: vec![runner::Runner::MakeCommand(Make::new_for_test())], search_text_area: TextArea_(TextArea::default()), targets_list_state: ListState::with_selected(ListState::default(), Some(0)), - histories: SelectTargetState::init_histories(vec![ - histories::HistoryCommand { - runner_type: runner_type::RunnerType::Make, - name: "history0".to_string(), - }, - histories::HistoryCommand { - runner_type: runner_type::RunnerType::Make, - name: "history1".to_string(), - }, - histories::HistoryCommand { - runner_type: runner_type::RunnerType::Make, - name: "history2".to_string(), - }, - ]), + // histories: SelectTargetState::init_histories(vec![ + // histories::HistoryCommand { + // runner_type: runner_type::RunnerType::Make, + // name: "history0".to_string(), + // }, + // histories::HistoryCommand { + // runner_type: runner_type::RunnerType::Make, + // name: "history1".to_string(), + // }, + // histories::HistoryCommand { + // runner_type: runner_type::RunnerType::Make, + // name: "history2".to_string(), + // }, + // ]), + histories: vec![command::Command { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + file_name: todo!(), + line_number: todo!(), + }], histories_list_state: ListState::with_selected(ListState::default(), Some(0)), } } From 1eca854a59cce0b18d108618773c2c3b3dc1ec6e Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Tue, 19 Nov 2024 00:42:22 +0900 Subject: [PATCH 07/36] wip --- src/model/histories.rs | 14 ++++++++------ src/usecase/tui/app.rs | 38 ++++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/model/histories.rs b/src/model/histories.rs index 3b65b12..b007566 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -7,6 +7,7 @@ use super::{command, runner_type}; /// For now, we can define this as tuple like `pub struct Histories(Vec);` but we don't. /// We respect that we can add some fields in the future easily. #[derive(Clone, PartialEq, Debug)] +// TODO: 削除する? pub struct Histories { pub histories: Vec, } @@ -91,12 +92,13 @@ impl History { } } - fn from(histories: (PathBuf, Vec)) -> Self { - Self { - path: histories.0, - executed_commands: histories.1, - } - } + // TODO: 不要そうだったら消す + // fn from(histories: (PathBuf, Vec)) -> Self { + // Self { + // path: histories.0, + // executed_commands: histories.1, + // } + // } // TODO(#321): remove #[allow(dead_code)] diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index 596ed2d..69fa237 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -396,15 +396,17 @@ impl SelectTargetState<'_> { // TODO(#321): comment in this method // TODO: This method should return Result when it fails. - // pub fn append_history(&self, command: &str) -> Option { - // match &self.histories { - // Some(histories) => { - // histories.append(&self.runners[0].path(), command) - // // TODO(#321): For now, it is &self.runners[0] to pass the compilation, but it should be taken from runner::Command::path() - // } - // _ => None, - // } - // } + pub fn append_history(&self, command: &command::Command) -> Option { + TODO: そのpathのhistoryをgetする + TODO: そのpathのhistoryがない場合はここで初期化して追加する + match &self.histories { + Some(histories) => { + histories.append(&self.runners[0].path(), command) + // TODO(#321): For now, it is &self.runners[0] to pass the compilation, but it should be taken from runner::Command::path() + } + _ => None, + } + } fn selected_target(&self) -> Option { match self.targets_list_state.selected() { @@ -585,18 +587,18 @@ impl SelectTargetState<'_> { self.search_text_area.0.input(key_event); } - fn store_history(&mut self, _command: &command::Command) { - // TODO(#321): implement when history function is implemented + fn store_history(&mut self, command: &command::Command) { // NOTE: self.get_selected_target should be called before self.append_history. // Because self.histories_list_state.selected keeps the selected index of the history list // before update. - // if let Some(h) = self.append_history(command) { - // self.histories = Some(h) - // }; - // if let (Some((dir, file_name)), Some(h)) = (history_file_path(), &self.histories) { - // // TODO: handle error - // let _ = toml::store_history(dir, file_name, h.to_tuple()); - // }; + // TODO(#321): implement when history function is implemented + if let Some(h) = self.append_history(command) { + self.histories = Some(h) + }; + if let (Some((dir, file_name)), Some(h)) = (history_file_path(), &self.histories) { + // TODO: handle error + let _ = toml::store_history(dir, file_name, h.to_tuple()); + }; } fn reset_selection(&mut self) { From 0cdff02b06c5015a2753d10ac25da8443a47da7a Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sat, 23 Nov 2024 02:47:32 +0900 Subject: [PATCH 08/36] wip --- src/file/toml.rs | 11 +++++++++-- src/model/histories.rs | 12 +++++++++++- src/usecase/tui/app.rs | 43 +++++++++++++++++++++--------------------- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/file/toml.rs b/src/file/toml.rs index 4be05b7..c91101c 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -98,12 +98,19 @@ pub fn parse_history(content: String) -> Result { Ok(Histories::into(histories)) } -#[allow(dead_code)] // TODO(#321): remove pub fn store_history( history_directory_path: PathBuf, history_file_name: String, - histories: histories::Histories, + histories: histories::History, ) -> Result<()> { + + TODO: + TODO: + TODO: + TODO: + // 1. 改めて履歴ファイルからhistoriesを取得する + // 2. cwdの履歴を更新する + // 3. historiesを保存する let histories = Histories::from(histories); if !history_directory_path.is_dir() { diff --git a/src/model/histories.rs b/src/model/histories.rs index b007566..b07257d 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -7,7 +7,8 @@ use super::{command, runner_type}; /// For now, we can define this as tuple like `pub struct Histories(Vec);` but we don't. /// We respect that we can add some fields in the future easily. #[derive(Clone, PartialEq, Debug)] -// TODO: 削除する? +// TODO: 削除する?とはいえappendなどのメソッドはapp側ではなくここに実装したほうが凝集度が高くてよさそう。 +// pub struct Histories { pub histories: Vec, } @@ -128,6 +129,15 @@ pub struct HistoryCommand { pub name: String, } +impl HistoryCommand { + pub fn from(command: command::Command) -> Self { + Self { + runner_type: command.runner_type, + name: command.name, + } + } +} + #[cfg(test)] mod test { // TODO(#321): comment in this test diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index 69fa237..81cb8bc 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -319,6 +319,14 @@ pub struct SelectTargetState<'a> { pub runners: Vec, pub search_text_area: TextArea_<'a>, pub targets_list_state: ListState, + // このフィールドをVecにするかこのままにするか + // 前者 + // 実行時に変換が必要 + // 後者 + // 保存時に変換が必要 + // 存在しないcommandを非表示にできる + // 将来的にpreviewできる + // TODO: ↑をコメントとして記述する pub histories: Vec, pub histories_list_state: ListState, } @@ -394,20 +402,6 @@ impl SelectTargetState<'_> { } } - // TODO(#321): comment in this method - // TODO: This method should return Result when it fails. - pub fn append_history(&self, command: &command::Command) -> Option { - TODO: そのpathのhistoryをgetする - TODO: そのpathのhistoryがない場合はここで初期化して追加する - match &self.histories { - Some(histories) => { - histories.append(&self.runners[0].path(), command) - // TODO(#321): For now, it is &self.runners[0] to pass the compilation, but it should be taken from runner::Command::path() - } - _ => None, - } - } - fn selected_target(&self) -> Option { match self.targets_list_state.selected() { Some(i) => self.narrow_down_targets().get(i).cloned(), @@ -587,17 +581,24 @@ impl SelectTargetState<'_> { self.search_text_area.0.input(key_event); } - fn store_history(&mut self, command: &command::Command) { + fn store_history(&self, command: &command::Command) { // NOTE: self.get_selected_target should be called before self.append_history. // Because self.histories_list_state.selected keeps the selected index of the history list // before update. - // TODO(#321): implement when history function is implemented - if let Some(h) = self.append_history(command) { - self.histories = Some(h) - }; - if let (Some((dir, file_name)), Some(h)) = (history_file_path(), &self.histories) { + if let Some((dir, file_name)) = history_file_path() { + let new_history_commands: Vec = + [vec![command.clone()], self.histories.clone()] + .concat() + .iter() + .map(|c| histories::HistoryCommand::from(c.clone())) + .collect(); + let history = histories::History { + path: self.current_dir.clone(), + executed_commands: new_history_commands, + }; + // TODO: handle error - let _ = toml::store_history(dir, file_name, h.to_tuple()); + let _ = toml::store_history(dir, file_name, history); }; } From d4977727bba99b6afecbff400729476acc5240c4 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sat, 23 Nov 2024 23:44:39 +0900 Subject: [PATCH 09/36] read history --- src/file/toml.rs | 207 ++++++++++++++++++++------------------- src/model/histories.rs | 27 +++-- src/model/runner_type.rs | 1 + src/usecase/tui/app.rs | 134 +++++++++++-------------- 4 files changed, 177 insertions(+), 192 deletions(-) diff --git a/src/file/toml.rs b/src/file/toml.rs index c91101c..07ffea9 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -1,3 +1,8 @@ +use super::path_to_content; +use crate::model::{ + histories::{self, history_file_path}, + runner_type, +}; use anyhow::Result; use serde::{Deserialize, Serialize}; use std::{ @@ -6,23 +11,37 @@ use std::{ path::PathBuf, }; -use crate::model::{histories, runner_type}; - -#[derive(Debug, Serialize, Deserialize)] -struct Histories { +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] +pub struct Histories { histories: Vec, } impl Histories { - fn from(histories: histories::Histories) -> Self { - let mut result: Vec = vec![]; - for h in histories.histories { - result.push(History::from(h)); + pub fn get_history() -> Histories { + match history_file_path() { + Some((history_file_dir, history_file_name)) => { + match path_to_content::path_to_content(history_file_dir.join(history_file_name)) { + // TODO: Show error message on message pane if parsing history file failed. https://github.com/kyu08/fzf-make/issues/152 + Ok(c) => match parse_history(c.to_string()) { + Ok(h) => h, + Err(_) => Histories { histories: vec![] }, + }, + Err(_) => Histories { histories: vec![] }, + } + } + None => Histories { histories: vec![] }, } - Self { histories: result } } - fn into(self) -> histories::Histories { + // fn from(histories: histories::Histories) -> Self { + // let mut result: Vec = vec![]; + // for h in histories.histories { + // result.push(History::from(h)); + // } + // Self { histories: result } + // } + + pub fn into(self) -> histories::Histories { let mut result: Vec = vec![]; for h in self.histories { result.push(History::into(h)); @@ -31,13 +50,13 @@ impl Histories { } } -impl std::default::Default for Histories { - fn default() -> Self { - Self { histories: vec![] } - } -} +// impl std::default::Default for Histories { +// fn default() -> Self { +// Self { histories: vec![] } +// } +// } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] struct History { path: PathBuf, commands: Vec, @@ -46,7 +65,7 @@ struct History { impl History { fn from(history: histories::History) -> Self { let mut commands: Vec = vec![]; - for h in history.executed_commands { + for h in history.commands { commands.push(HistoryCommand::from(h)); } @@ -64,13 +83,13 @@ impl History { histories::History { path: self.path, - executed_commands: commands, + commands, } } } /// toml representation of histories::HistoryCommand. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] struct HistoryCommand { runner_type: runner_type::RunnerType, @@ -93,32 +112,33 @@ impl HistoryCommand { } } -pub fn parse_history(content: String) -> Result { +pub fn parse_history(content: String) -> Result { let histories = toml::from_str(&content)?; - Ok(Histories::into(histories)) + Ok(histories) } pub fn store_history( + current_working_directory: PathBuf, history_directory_path: PathBuf, history_file_name: String, - histories: histories::History, + new_history: histories::History, ) -> Result<()> { + let mut all_histories = Histories::get_history(); - TODO: - TODO: - TODO: - TODO: - // 1. 改めて履歴ファイルからhistoriesを取得する - // 2. cwdの履歴を更新する - // 3. historiesを保存する - let histories = Histories::from(histories); + for (i, history) in all_histories.clone().histories.iter().enumerate() { + if history.path == current_working_directory { + all_histories.histories[i] = History::from(new_history.clone()); + } + } + + // TODO: 2. cwdの履歴を更新する if !history_directory_path.is_dir() { fs::create_dir_all(history_directory_path.clone())?; } - + // TODO: 3. historiesを保存する let mut history_file = File::create(history_directory_path.join(history_file_name))?; - history_file.write_all(toml::to_string(&histories).unwrap().as_bytes())?; + history_file.write_all(toml::to_string(&all_histories).unwrap().as_bytes())?; history_file.flush()?; Ok(()) @@ -126,8 +146,8 @@ pub fn store_history( #[cfg(test)] mod test { - use crate::model::runner_type; use super::*; + use crate::model::runner_type; use anyhow::Result; #[test] @@ -135,95 +155,80 @@ mod test { struct Case { title: &'static str, content: String, - expect: Result, + expect: Result, } let cases = vec![ Case { title: "Success", content: r#" -[[tasks]] +[[histories]] path = "/Users/user/code/fzf-make" -[[tasks.commands]] -runner = "make" -command = "test" +[[histories.commands]] +runner-type = "make" +name = "test" -[[tasks.commands]] -runner = "make" -command = "check" +[[histories.commands]] +runner-type = "make" +name = "check" -[[tasks.commands]] -runner = "make" -command = "spell-check" +[[histories.commands]] +runner-type = "make" +name = "spell-check" -[[tasks]]] +[[histories]] path = "/Users/user/code/golang/go-playground" -[[tasks.commands]] -runner = "make" -command = "run" +[[histories.commands]] +runner-type = "make" +name = "run" -[[tasks.commands]] -runner = "make" -command = "echo1" +[[histories.commands]] +runner-type = "make" +name = "echo1" "# .to_string(), - expect: Ok( - histories::Histories{ histories: vec![ - histories::History{ path: PathBuf::from("/Users/user/code/fzf-make".to_string()), executed_commands: vec![ - histories::HistoryCommand{ - runner_type: runner_type::RunnerType::Make, - name: "test".to_string() }, - ] }, - ] }, - // ( - // PathBuf::from("/Users/user/code/fzf-make".to_string()), - // vec![ - // command::Command::new( - // runner_type::RunnerType::Make, - // "test".to_string(), - // PathBuf::from("Makefile"), - // 1, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "check".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "spell-check".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // ], - // ), - // ( - // PathBuf::from("/Users/user/code/golang/go-playground".to_string()), - // vec![ - // command::Command::new( - // runner_type::RunnerType::Make, - // "run".to_string(), - // PathBuf::from("Makefile"), - // 1, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "echo1".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // ], - // ), - ), + expect: Ok(Histories { + histories: vec![ + History { + path: PathBuf::from("/Users/user/code/fzf-make"), + commands: vec![ + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "test".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "check".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "spell-check".to_string(), + }, + ], + }, + History { + path: PathBuf::from("/Users/user/code/golang/go-playground"), + commands: vec![ + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "run".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "echo1".to_string(), + }, + ], + }, + ], + }), }, Case { title: "Error", content: r#" "# .to_string(), - expect: Err(anyhow::anyhow!("TOML parse error at line 1, column 1\n |\n1 | \n | ^\nmissing field `history`\n")), + expect: Err(anyhow::anyhow!("TOML parse error at line 1, column 1\n |\n1 | \n | ^\nmissing field `histories`\n")), }, ]; diff --git a/src/model/histories.rs b/src/model/histories.rs index b07257d..e742cd5 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -1,8 +1,7 @@ +use super::{command, runner_type}; use simple_home_dir::home_dir; use std::{env, path::PathBuf}; -use super::{command, runner_type}; - /// Histories is a all collection of History. This equals whole content of history.toml. /// For now, we can define this as tuple like `pub struct Histories(Vec);` but we don't. /// We respect that we can add some fields in the future easily. @@ -82,16 +81,16 @@ pub fn history_file_path() -> Option<(PathBuf, String)> { #[derive(Clone, PartialEq, Debug)] pub struct History { pub path: PathBuf, - pub executed_commands: Vec, // TODO: rename to executed_commands + pub commands: Vec, // TODO: rename to executed_commands } impl History { - fn default(path: PathBuf) -> Self { - Self { - path, - executed_commands: Vec::new(), - } - } + // fn default(path: PathBuf) -> Self { + // Self { + // path, + // commands: Vec::new(), + // } + // } // TODO: 不要そうだったら消す // fn from(histories: (PathBuf, Vec)) -> Self { @@ -103,10 +102,10 @@ impl History { // TODO(#321): remove #[allow(dead_code)] - fn append(&self, executed_target: command::Command) -> Self { - let mut executed_targets = self.executed_commands.clone(); - executed_targets.retain(|t| *t != executed_target); - executed_targets.insert(0, executed_target.clone()); + fn append(&self, _executed_target: command::Command) -> Self { + let mut executed_targets = self.commands.clone(); + // executed_targets.retain(|t| *t != executed_target); + // executed_targets.insert(0, executed_target.clone()); const MAX_LENGTH: usize = 10; if MAX_LENGTH < executed_targets.len() { @@ -115,7 +114,7 @@ impl History { Self { path: self.path.clone(), - executed_commands: executed_targets, + commands: executed_targets, } } } diff --git a/src/model/runner_type.rs b/src/model/runner_type.rs index 1a7664d..f0c9507 100644 --- a/src/model/runner_type.rs +++ b/src/model/runner_type.rs @@ -4,6 +4,7 @@ use std::fmt; // TODO(#321): remove #[allow(dead_code)] #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] pub enum RunnerType { Make, Pnpm, diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index 81cb8bc..0886c7b 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -1,5 +1,5 @@ use crate::{ - file::{path_to_content, toml}, + file::toml, model::{ command, histories::{self, history_file_path}, @@ -31,8 +31,8 @@ use std::{ }; use tui_textarea::TextArea; -#[cfg(test)] -use crate::model::histories::Histories; +// #[cfg(test)] +// use crate::model::histories::Histories; // AppState represents the state of the application. // "Making impossible states impossible" @@ -85,39 +85,22 @@ impl Model<'_> { } } + // returns available commands in cwd from history file fn get_histories( current_working_directory: PathBuf, runners: Vec, ) -> Vec { - match history_file_path() { - Some((history_file_dir, history_file_name)) => { - let content = { - let content = - path_to_content::path_to_content(history_file_dir.join(history_file_name)); - match content { - Ok(c) => c, - Err(_) => return vec![], - } - }; + let histories = toml::Histories::into(toml::Histories::get_history()); - // TODO: Show error message on message pane if parsing history file failed. https://github.com/kyu08/fzf-make/issues/152 - let histories = match toml::parse_history(content.to_string()) { - Ok(h) => h, - Err(_) => return vec![], - }; - - let mut result: Vec = Vec::new(); - for history in histories.histories { - if history.path != current_working_directory { - continue; - } - result = Self::get_commands_from_history(history.executed_commands, &runners); - break; - } - result + let mut result: Vec = Vec::new(); + for history in histories.histories { + if history.path != current_working_directory { + continue; } - None => vec![], + result = Self::get_commands_from_history(history.commands, &runners); + break; } + result } fn get_commands_from_history( @@ -373,17 +356,15 @@ impl SelectTargetState<'_> { CurrentPane::Main }; let runner = { runner::Runner::MakeCommand(makefile) }; - - let path = runner.path(); let runners = vec![runner]; + Ok(SelectTargetState { - current_dir, + current_dir: current_dir.clone(), current_pane, runners: runners.clone(), search_text_area: TextArea_(TextArea::default()), targets_list_state: ListState::with_selected(ListState::default(), Some(0)), - // TODO: pass cwd instead of makefile_path - histories: Model::get_histories(path, runners), + histories: Model::get_histories(current_dir, runners), histories_list_state: ListState::with_selected(ListState::default(), Some(0)), }) } @@ -594,11 +575,11 @@ impl SelectTargetState<'_> { .collect(); let history = histories::History { path: self.current_dir.clone(), - executed_commands: new_history_commands, + commands: new_history_commands, }; // TODO: handle error - let _ = toml::store_history(dir, file_name, history); + let _ = toml::store_history(self.current_dir.clone(), dir, file_name, history); }; } @@ -614,7 +595,7 @@ impl SelectTargetState<'_> { } pub fn get_latest_command(&self) -> Option<&command::Command> { - Some(self.histories.first()?) + self.histories.first() } pub fn get_runner(&self, runner_type: &runner_type::RunnerType) -> Option { @@ -633,26 +614,25 @@ impl SelectTargetState<'_> { } #[cfg(test)] - fn init_histories(history_commands: Vec) -> Histories { - use std::path::Path; - - let mut commands: Vec = Vec::new(); - - for h in history_commands { - commands.push(histories::HistoryCommand { - runner_type: runner_type::RunnerType::Make, - name: h.name, - }); - } - - Histories { - histories: vec![histories::History { - path: env::current_dir().unwrap().join(Path::new("Test.mk")), - executed_commands: commands, - }], - } - } - + // fn init_histories(history_commands: Vec) -> Histories { + // use std::path::Path; + // + // let mut commands: Vec = Vec::new(); + // + // for h in history_commands { + // commands.push(histories::HistoryCommand { + // runner_type: runner_type::RunnerType::Make, + // name: h.name, + // }); + // } + // + // Histories { + // histories: vec![histories::History { + // path: env::current_dir().unwrap().join(Path::new("Test.mk")), + // commands: commands, + // }], + // } + // } #[cfg(test)] fn new_for_test() -> Self { use crate::model::runner_type; @@ -663,26 +643,26 @@ impl SelectTargetState<'_> { runners: vec![runner::Runner::MakeCommand(Make::new_for_test())], search_text_area: TextArea_(TextArea::default()), targets_list_state: ListState::with_selected(ListState::default(), Some(0)), - // histories: SelectTargetState::init_histories(vec![ - // histories::HistoryCommand { - // runner_type: runner_type::RunnerType::Make, - // name: "history0".to_string(), - // }, - // histories::HistoryCommand { - // runner_type: runner_type::RunnerType::Make, - // name: "history1".to_string(), - // }, - // histories::HistoryCommand { - // runner_type: runner_type::RunnerType::Make, - // name: "history2".to_string(), - // }, - // ]), - histories: vec![command::Command { - runner_type: runner_type::RunnerType::Make, - name: "history0".to_string(), - file_name: todo!(), - line_number: todo!(), - }], + histories: vec![ + command::Command { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + file_name: PathBuf::from("Makefile"), + line_number: 1, + }, + command::Command { + runner_type: runner_type::RunnerType::Make, + name: "history1".to_string(), + file_name: PathBuf::from("Makefile"), + line_number: 4, + }, + command::Command { + runner_type: runner_type::RunnerType::Make, + name: "history2".to_string(), + file_name: PathBuf::from("Makefile"), + line_number: 7, + }, + ], histories_list_state: ListState::with_selected(ListState::default(), Some(0)), } } From 1528c7bb38d3124f2a9a3a29a277c9e78b5f9782 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 00:30:52 +0900 Subject: [PATCH 10/36] write history --- src/file/toml.rs | 34 ++++++++++---------------- src/model/histories.rs | 52 +++++++++++++++++++++------------------ src/usecase/tui/app.rs | 55 ++++++++++++++++-------------------------- 3 files changed, 63 insertions(+), 78 deletions(-) diff --git a/src/file/toml.rs b/src/file/toml.rs index 07ffea9..530ee04 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -33,13 +33,13 @@ impl Histories { } } - // fn from(histories: histories::Histories) -> Self { - // let mut result: Vec = vec![]; - // for h in histories.histories { - // result.push(History::from(h)); - // } - // Self { histories: result } - // } + fn from(histories: histories::Histories) -> Self { + let mut result: Vec = vec![]; + for h in histories.histories { + result.push(History::from(h)); + } + Self { histories: result } + } pub fn into(self) -> histories::Histories { let mut result: Vec = vec![]; @@ -118,27 +118,19 @@ pub fn parse_history(content: String) -> Result { } pub fn store_history( - current_working_directory: PathBuf, history_directory_path: PathBuf, history_file_name: String, - new_history: histories::History, + new_history: histories::Histories, ) -> Result<()> { - let mut all_histories = Histories::get_history(); - - for (i, history) in all_histories.clone().histories.iter().enumerate() { - if history.path == current_working_directory { - all_histories.histories[i] = History::from(new_history.clone()); - } - } - - // TODO: 2. cwdの履歴を更新する - if !history_directory_path.is_dir() { fs::create_dir_all(history_directory_path.clone())?; } - // TODO: 3. historiesを保存する let mut history_file = File::create(history_directory_path.join(history_file_name))?; - history_file.write_all(toml::to_string(&all_histories).unwrap().as_bytes())?; + history_file.write_all( + toml::to_string(&Histories::from(new_history)) + .unwrap() + .as_bytes(), + )?; history_file.flush()?; Ok(()) diff --git a/src/model/histories.rs b/src/model/histories.rs index e742cd5..22344b6 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -30,30 +30,36 @@ impl Histories { // histories // } - // 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, - // } - // }) - // } + pub fn append( + &self, + current_dir: PathBuf, + histories_before_update: Vec, + command: command::Command, + ) -> Self { + let new_history_commands: Vec = [vec![command], histories_before_update] + .concat() + .iter() + .map(|c| HistoryCommand::from(c.clone())) + .collect(); + let history = History { + path: current_dir.clone(), + commands: new_history_commands, + }; - // pub fn to_tuple(&self) -> Vec<(PathBuf, Vec)> { - // let mut result = Vec::new(); - // - // for history in &self.histories { - // result.push((history.path.clone(), history.executed_targets.clone())); - // } - // result - // } + let mut new_histories = self.histories.clone(); + match new_histories.iter().position(|h| h.path == history.path) { + Some(index) => { + new_histories[index] = history; + } + None => { + new_histories.insert(0, history); + } + } + + Histories { + histories: new_histories, + } + } } // TODO(#321): should return Result not Option(returns when it fails to get the home dir) diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index 0886c7b..b28da77 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -24,7 +24,8 @@ use ratatui::{ use std::{ collections::HashMap, env, - io::{self, Stderr}, + fs::File, + io::{self, Stderr, Write}, panic, path::PathBuf, process, @@ -100,6 +101,14 @@ impl Model<'_> { result = Self::get_commands_from_history(history.commands, &runners); break; } + + println!("{:?}", result); + let mut debug_file = File::create("debug.txt").unwrap(); + debug_file + .write_all(format!("{:?}", result).as_bytes()) + .unwrap(); + let _ = debug_file.flush(); + result } @@ -107,6 +116,7 @@ impl Model<'_> { history_commands: Vec, runners: &Vec, ) -> Vec { + // なぜかhistoryが表示されないのを修正する // TODO: Make this more readable and more performant. let mut commands: Vec = Vec::new(); for history_command in history_commands { @@ -278,7 +288,8 @@ fn update(model: &mut Model, message: Option) { Some(Message::ExecuteTarget) => { if let Some(command) = s.get_selected_target() { // TODO: make this a method of SelectTargetState - s.store_history(&command); + s.store_history(command.clone()); + // TODO: s.runners[0]ではなくcommand.runner_type を利用する let executor: runner::Runner = s.runners[0].clone(); model.transition_to_execute_target_state(executor, command); @@ -453,26 +464,7 @@ impl SelectTargetState<'_> { } pub fn get_history(&self) -> Vec { - // MEMO: mainではhistoriesの中からmakefile_pathのhistoryを取得する関数。 - // cwdの履歴だけ取得するようにすればこの関数はいらなくなるかも。 - // TODO: - // そもそもapplication側ではcommand::Commandだけをhistoryとして持つべき。そうすれば実行時も楽だしpreviewも楽に出すことができる - - // TODO(#321): この関数内で - // historyにあるcommandをself.runnersから取得するよう(行数やファイル名を最新状態からとってこないとちゃんとプレビュー表示できないため)(e.g. ファイル行番号が変わってる場合プレビューがずれる) - vec![] - // TODO(#321): implement when history function is implemented - // UIに表示するためのhistory一覧を取得する関数。 - // runnersを渡すと関連するhistory一覧を返すようにするのがよさそう。 - // let paths = self - // .runners - // .iter() - // .map(|r| r.path()) - // .collect::>(); - // - // self.histories - // .clone() - // .map_or(Vec::new(), |h| h.get_histories(paths)) + self.histories.clone() } fn next_target(&mut self) { @@ -562,24 +554,19 @@ impl SelectTargetState<'_> { self.search_text_area.0.input(key_event); } - fn store_history(&self, command: &command::Command) { + fn store_history(&self, command: command::Command) { // NOTE: self.get_selected_target should be called before self.append_history. // Because self.histories_list_state.selected keeps the selected index of the history list // before update. if let Some((dir, file_name)) = history_file_path() { - let new_history_commands: Vec = - [vec![command.clone()], self.histories.clone()] - .concat() - .iter() - .map(|c| histories::HistoryCommand::from(c.clone())) - .collect(); - let history = histories::History { - path: self.current_dir.clone(), - commands: new_history_commands, - }; + let all_histories = toml::Histories::get_history().into().append( + self.current_dir.clone(), + self.histories.clone(), + command, + ); // TODO: handle error - let _ = toml::store_history(self.current_dir.clone(), dir, file_name, history); + let _ = toml::store_history(dir, file_name, all_histories); }; } From be287651717a382c3e5b1299660d9dd2b8e80589 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 01:11:04 +0900 Subject: [PATCH 11/36] append_history --- src/file/toml.rs | 26 ++++++++++++- src/model/histories.rs | 86 +++++++++++++++--------------------------- src/usecase/tui/app.rs | 15 ++------ 3 files changed, 59 insertions(+), 68 deletions(-) diff --git a/src/file/toml.rs b/src/file/toml.rs index 530ee04..133ec69 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -1,11 +1,13 @@ use super::path_to_content; use crate::model::{ - histories::{self, history_file_path}, + histories::{self}, runner_type, }; use anyhow::Result; use serde::{Deserialize, Serialize}; +use simple_home_dir::home_dir; use std::{ + env, fs::{self, File}, io::Write, path::PathBuf, @@ -112,6 +114,28 @@ impl HistoryCommand { } } +// TODO(#321): should return Result not Option(returns when it fails to get the home dir) +pub fn history_file_path() -> Option<(PathBuf, String)> { + const HISTORY_FILE_NAME: &str = "history.toml"; + + match env::var("FZF_MAKE_IS_TESTING") { + Ok(_) => { + // When testing + let cwd = std::env::current_dir().unwrap(); + Some(( + cwd.join(PathBuf::from("test_dir")), + HISTORY_FILE_NAME.to_string(), + )) + } + _ => home_dir().map(|home_dir| { + ( + home_dir.join(PathBuf::from(".config/fzf-make")), + HISTORY_FILE_NAME.to_string(), + ) + }), + } +} + pub fn parse_history(content: String) -> Result { let histories = toml::from_str(&content)?; Ok(histories) diff --git a/src/model/histories.rs b/src/model/histories.rs index 22344b6..818d075 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -1,6 +1,5 @@ use super::{command, runner_type}; -use simple_home_dir::home_dir; -use std::{env, path::PathBuf}; +use std::path::PathBuf; /// Histories is a all collection of History. This equals whole content of history.toml. /// For now, we can define this as tuple like `pub struct Histories(Vec);` but we don't. @@ -30,29 +29,35 @@ impl Histories { // histories // } + // TODO: ut pub fn append( &self, current_dir: PathBuf, - histories_before_update: Vec, + history_of_cwd: Vec, command: command::Command, ) -> Self { - let new_history_commands: Vec = [vec![command], histories_before_update] - .concat() - .iter() - .map(|c| HistoryCommand::from(c.clone())) - .collect(); - let history = History { - path: current_dir.clone(), - commands: new_history_commands, + let new_history = { + let history_commands: Vec = history_of_cwd + .iter() + .map(|c| HistoryCommand::from(c.clone())) + .collect(); + let history = History { + path: current_dir.clone(), + commands: history_commands, + }; + history.append(command) }; let mut new_histories = self.histories.clone(); - match new_histories.iter().position(|h| h.path == history.path) { + match new_histories + .iter() + .position(|h| h.path == new_history.path) + { Some(index) => { - new_histories[index] = history; + new_histories[index] = new_history; } None => { - new_histories.insert(0, history); + new_histories.insert(0, new_history); } } @@ -62,65 +67,36 @@ impl Histories { } } -// TODO(#321): should return Result not Option(returns when it fails to get the home dir) -pub fn history_file_path() -> Option<(PathBuf, String)> { - const HISTORY_FILE_NAME: &str = "history.toml"; - - match env::var("FZF_MAKE_IS_TESTING") { - Ok(_) => { - // When testing - let cwd = std::env::current_dir().unwrap(); - Some(( - cwd.join(PathBuf::from("test_dir")), - HISTORY_FILE_NAME.to_string(), - )) - } - _ => home_dir().map(|home_dir| { - ( - home_dir.join(PathBuf::from(".config/fzf-make")), - HISTORY_FILE_NAME.to_string(), - ) - }), - } -} - #[derive(Clone, PartialEq, Debug)] pub struct History { pub path: PathBuf, - pub commands: Vec, // TODO: rename to executed_commands + pub commands: Vec, } impl History { - // fn default(path: PathBuf) -> Self { - // Self { - // path, - // commands: Vec::new(), - // } - // } - - // TODO: 不要そうだったら消す + // #[allow(dead_code)] // fn from(histories: (PathBuf, Vec)) -> Self { // Self { // path: histories.0, - // executed_commands: histories.1, + // commands: histories.1, // } // } - // TODO(#321): remove - #[allow(dead_code)] - fn append(&self, _executed_target: command::Command) -> Self { - let mut executed_targets = self.commands.clone(); - // executed_targets.retain(|t| *t != executed_target); - // executed_targets.insert(0, executed_target.clone()); + // TODO: ut + fn append(&self, executed_command: command::Command) -> Self { + let mut updated_commands = self.commands.clone(); + // removes the executed_command from the history + updated_commands.retain(|t| *t != HistoryCommand::from(executed_command.clone())); + updated_commands.insert(0, HistoryCommand::from(executed_command.clone())); const MAX_LENGTH: usize = 10; - if MAX_LENGTH < executed_targets.len() { - executed_targets.truncate(MAX_LENGTH); + if MAX_LENGTH < updated_commands.len() { + updated_commands.truncate(MAX_LENGTH); } Self { path: self.path.clone(), - commands: executed_targets, + commands: updated_commands, } } } diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index b28da77..68a1dc3 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -2,7 +2,7 @@ use crate::{ file::toml, model::{ command, - histories::{self, history_file_path}, + histories::{self}, make::Make, runner, runner_type, }, @@ -24,8 +24,7 @@ use ratatui::{ use std::{ collections::HashMap, env, - fs::File, - io::{self, Stderr, Write}, + io::{self, Stderr}, panic, path::PathBuf, process, @@ -102,13 +101,6 @@ impl Model<'_> { break; } - println!("{:?}", result); - let mut debug_file = File::create("debug.txt").unwrap(); - debug_file - .write_all(format!("{:?}", result).as_bytes()) - .unwrap(); - let _ = debug_file.flush(); - result } @@ -116,7 +108,6 @@ impl Model<'_> { history_commands: Vec, runners: &Vec, ) -> Vec { - // なぜかhistoryが表示されないのを修正する // TODO: Make this more readable and more performant. let mut commands: Vec = Vec::new(); for history_command in history_commands { @@ -558,7 +549,7 @@ impl SelectTargetState<'_> { // NOTE: self.get_selected_target should be called before self.append_history. // Because self.histories_list_state.selected keeps the selected index of the history list // before update. - if let Some((dir, file_name)) = history_file_path() { + if let Some((dir, file_name)) = toml::history_file_path() { let all_histories = toml::Histories::get_history().into().append( self.current_dir.clone(), self.histories.clone(), From 1b892af44bb0d6c9089a09d4c2b407f9241c67ae Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 01:15:40 +0900 Subject: [PATCH 12/36] delete unnecessary code --- src/model/histories.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/model/histories.rs b/src/model/histories.rs index 818d075..b8013e7 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -5,30 +5,11 @@ use std::path::PathBuf; /// For now, we can define this as tuple like `pub struct Histories(Vec);` but we don't. /// We respect that we can add some fields in the future easily. #[derive(Clone, PartialEq, Debug)] -// TODO: 削除する?とはいえappendなどのメソッドはapp側ではなくここに実装したほうが凝集度が高くてよさそう。 -// pub struct Histories { pub histories: Vec, } impl Histories { - // TODO(#321): Make this fn returns Vec - // pub fn get_histories(&self, paths: Vec) -> Vec { - // let mut histories: Vec = Vec::new(); - // - // for path in paths { - // let executed_targets = self - // .histories - // .iter() - // .find(|h| h.path == path) - // .map(|h| h.executed_targets.clone()) - // .unwrap_or(Vec::new()); - // histories = [histories, executed_targets].concat(); - // } - // - // histories - // } - // TODO: ut pub fn append( &self, From d2e452a696a7eb94f3f81aa047687b984df7f11a Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 01:22:38 +0900 Subject: [PATCH 13/36] specify_makefile_name: receive current_dir as an argument instead of using `env::current_dir` --- src/file/toml.rs | 6 ------ src/model/histories.rs | 8 -------- src/model/make.rs | 29 ++++++++++++++--------------- src/usecase/tui/app.rs | 2 +- 4 files changed, 15 insertions(+), 30 deletions(-) diff --git a/src/file/toml.rs b/src/file/toml.rs index 133ec69..72d9ef6 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -52,12 +52,6 @@ impl Histories { } } -// impl std::default::Default for Histories { -// fn default() -> Self { -// Self { histories: vec![] } -// } -// } - #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] struct History { path: PathBuf, diff --git a/src/model/histories.rs b/src/model/histories.rs index b8013e7..86c8938 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -55,14 +55,6 @@ pub struct History { } impl History { - // #[allow(dead_code)] - // fn from(histories: (PathBuf, Vec)) -> Self { - // Self { - // path: histories.0, - // commands: histories.1, - // } - // } - // TODO: ut fn append(&self, executed_command: command::Command) -> Self { let mut updated_commands = self.commands.clone(); diff --git a/src/model/make.rs b/src/model/make.rs index 066a81c..b1e8f1e 100644 --- a/src/model/make.rs +++ b/src/model/make.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Result}; use regex::Regex; use std::process; use std::{ - env, fs, + fs, path::{Path, PathBuf}, }; @@ -20,8 +20,8 @@ impl Make { format!("make {}", command.name) } - pub fn create_makefile() -> Result { - let Some(makefile_name) = Make::specify_makefile_name(".".to_string()) else { + pub fn create_makefile(current_dir: PathBuf) -> Result { + let Some(makefile_name) = Make::specify_makefile_name(current_dir, ".".to_string()) else { return Err(anyhow!("makefile not found.\n")); }; Make::new(Path::new(&makefile_name).to_path_buf()) @@ -56,7 +56,7 @@ impl Make { }) } - fn specify_makefile_name(target_path: String) -> Option { + fn specify_makefile_name(current_dir: PathBuf, target_path: String) -> Option { // By default, when make looks for the makefile, it tries the following names, in order: GNUmakefile, makefile and Makefile. // https://www.gnu.org/software/make/manual/make.html#Makefile-Names // It needs to enumerate `Makefile` too not only `makefile` to make it work on case insensitive file system @@ -68,12 +68,6 @@ impl Make { let file_name = e.unwrap().file_name(); let file_name_string = file_name.to_str().unwrap(); if makefile_name_options.contains(&file_name_string) { - let current_dir = match env::current_dir() { - // TODO: use model.current_dir - Err(_) => return None, - Ok(d) => d, - }; - temp_result.push(current_dir.join(file_name)); } } @@ -104,6 +98,7 @@ impl Make { #[cfg(test)] pub fn new_for_test() -> Make { use super::runner_type; + use std::env; Make { path: env::current_dir().unwrap().join(Path::new("Test.mk")), @@ -174,11 +169,12 @@ fn line_to_including_file_paths(line: String) -> Option> { #[cfg(test)] mod test { - use crate::model::runner_type; - use super::*; - - use std::fs::{self, File}; + use crate::model::runner_type; + use std::{ + env, + fs::{self, File}, + }; use uuid::Uuid; #[test] @@ -231,7 +227,10 @@ mod test { assert_eq!( expect, - Make::specify_makefile_name(tmp_dir.to_string_lossy().to_string()), + Make::specify_makefile_name( + env::current_dir().unwrap(), + tmp_dir.to_string_lossy().to_string() + ), "\nFailed: 🚨{:?}🚨\n", case.title, ); diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index 68a1dc3..a933d2f 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -347,7 +347,7 @@ impl SelectTargetState<'_> { Ok(d) => d, Err(e) => bail!("Failed to get current directory: {}", e), }; - let makefile = match Make::create_makefile() { + let makefile = match Make::create_makefile(current_dir.clone()) { Err(e) => return Err(e), Ok(f) => f, }; From 3e64452f7aa342dfb06b1c6993d67a65aa62bc4d Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 01:24:29 +0900 Subject: [PATCH 14/36] delete unnecessary code --- src/usecase/tui/app.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index a933d2f..aa9b018 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -1,3 +1,4 @@ +use super::{config, ui::ui}; use crate::{ file::toml, model::{ @@ -7,8 +8,6 @@ use crate::{ runner, runner_type, }, }; - -use super::{config, ui::ui}; use anyhow::{anyhow, bail, Result}; use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture, KeyCode, KeyEvent}, @@ -31,9 +30,6 @@ use std::{ }; use tui_textarea::TextArea; -// #[cfg(test)] -// use crate::model::histories::Histories; - // AppState represents the state of the application. // "Making impossible states impossible" // The type of `AppState` is defined according to the concept of 'Making Impossible States Impossible'. From 4e3fb731ca879a39ba94b25a79143c4534457dd0 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 01:29:05 +0900 Subject: [PATCH 15/36] fix comment --- src/file/toml.rs | 2 +- src/model/runner_type.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/file/toml.rs b/src/file/toml.rs index 72d9ef6..7c0acc8 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -108,7 +108,7 @@ impl HistoryCommand { } } -// TODO(#321): should return Result not Option(returns when it fails to get the home dir) +// TODO: should return Result not Option(returns when it fails to get the home dir) pub fn history_file_path() -> Option<(PathBuf, String)> { const HISTORY_FILE_NAME: &str = "history.toml"; diff --git a/src/model/runner_type.rs b/src/model/runner_type.rs index f0c9507..dbc5423 100644 --- a/src/model/runner_type.rs +++ b/src/model/runner_type.rs @@ -1,8 +1,6 @@ use serde::{Deserialize, Serialize}; use std::fmt; -// TODO(#321): remove -#[allow(dead_code)] #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum RunnerType { From 3a27495ae5fac96403b898a5f17399a6991e9184 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 01:42:25 +0900 Subject: [PATCH 16/36] use command.runner_type.to_runner instead of s.runners[0] --- src/model/runner_type.rs | 18 ++++++++++++++++++ src/usecase/tui/app.rs | 8 +++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/model/runner_type.rs b/src/model/runner_type.rs index dbc5423..8011c42 100644 --- a/src/model/runner_type.rs +++ b/src/model/runner_type.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; use std::fmt; +use super::runner; + #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum RunnerType { @@ -8,6 +10,22 @@ pub enum RunnerType { Pnpm, } +impl RunnerType { + pub fn to_runner(&self, runners: &Vec) -> Option { + match self { + RunnerType::Make => { + for r in runners { + if matches!(r, runner::Runner::MakeCommand(_)) { + return Some(r.clone()); + } + } + None + } + RunnerType::Pnpm => todo!(), + } + } +} + impl fmt::Display for RunnerType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let name = match self { diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index aa9b018..656e1de 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -274,12 +274,10 @@ fn update(model: &mut Model, message: Option) { Some(Message::SearchTextAreaKeyInput(key_event)) => s.handle_key_input(key_event), Some(Message::ExecuteTarget) => { if let Some(command) = s.get_selected_target() { - // TODO: make this a method of SelectTargetState s.store_history(command.clone()); - // TODO: s.runners[0]ではなくcommand.runner_type を利用する - let executor: runner::Runner = s.runners[0].clone(); - - model.transition_to_execute_target_state(executor, command); + if let Some(r) = command.runner_type.to_runner(&s.runners) { + model.transition_to_execute_target_state(r, command); + } }; } Some(Message::NextTarget) => s.next_target(), From c3901ace8ef46ec20c1ad71f6a69c9bbcfdb8b57 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 11:41:59 +0900 Subject: [PATCH 17/36] SelectTargetState.histories: Add comment --- src/usecase/tui/app.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index 656e1de..db1614a 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -298,14 +298,9 @@ pub struct SelectTargetState<'a> { pub runners: Vec, pub search_text_area: TextArea_<'a>, pub targets_list_state: ListState, - // このフィールドをVecにするかこのままにするか - // 前者 - // 実行時に変換が必要 - // 後者 - // 保存時に変換が必要 - // 存在しないcommandを非表示にできる - // 将来的にpreviewできる - // TODO: ↑をコメントとして記述する + /// This field could have been of type `Vec`, but it was intentionally made of type `Vec`. + /// This is because it allows for future features such as displaying the contents of history in the preview window + /// or hiding commands that existed at the time of execution but no longer exist. pub histories: Vec, pub histories_list_state: ListState, } From 73ce55eb4bda7c056ad3550ecbd71a95fae72c70 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 11:46:07 +0900 Subject: [PATCH 18/36] add pretty_assertions --- CREDITS | 27 +++++++++++++++++++++++++++ Cargo.lock | 23 +++++++++++++++++++++++ Cargo.toml | 1 + src/file/toml.rs | 1 + src/model/make.rs | 1 + src/model/target.rs | 1 + src/usecase/tui/app.rs | 4 ++-- 7 files changed, 56 insertions(+), 2 deletions(-) diff --git a/CREDITS b/CREDITS index 20799a8..506b6e3 100644 --- a/CREDITS +++ b/CREDITS @@ -1093,3 +1093,30 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +================================================================ + +rust-pretty-assertions +https://github.com/rust-pretty-assertions/rust-pretty-assertions/tree/main +---------------------------------------------------------------- + +The MIT License (MIT) + +Copyright (c) 2016 rust-derive-builder contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Cargo.lock b/Cargo.lock index 9906afe..08e1021 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,6 +129,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "downcast-rs" version = "1.2.0" @@ -186,6 +192,7 @@ dependencies = [ "crossterm", "fuzzy-matcher", "portable-pty", + "pretty_assertions", "ratatui", "regex", "serde", @@ -433,6 +440,16 @@ dependencies = [ "winreg", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro2" version = "1.0.76" @@ -1078,6 +1095,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zerocopy" version = "0.7.26" diff --git a/Cargo.toml b/Cargo.toml index 57c87ff..e7d645c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,4 @@ tui-textarea = "0.6.0" toml = "0.8.19" serde = {version = "1.0.204", features = ["derive"]} simple-home-dir = "0.4.0" +pretty_assertions = "1.4.1" diff --git a/src/file/toml.rs b/src/file/toml.rs index 7c0acc8..fd874f0 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -159,6 +159,7 @@ mod test { use super::*; use crate::model::runner_type; use anyhow::Result; + use pretty_assertions::assert_eq; #[test] fn parse_history_test() { diff --git a/src/model/make.rs b/src/model/make.rs index b1e8f1e..d9d5254 100644 --- a/src/model/make.rs +++ b/src/model/make.rs @@ -171,6 +171,7 @@ fn line_to_including_file_paths(line: String) -> Option> { mod test { use super::*; use crate::model::runner_type; + use pretty_assertions::assert_eq; use std::{ env, fs::{self, File}, diff --git a/src/model/target.rs b/src/model/target.rs index 7b1ed1d..ecd0222 100644 --- a/src/model/target.rs +++ b/src/model/target.rs @@ -88,6 +88,7 @@ fn line_to_target(line: String) -> Option { #[cfg(test)] mod test { use super::*; + use pretty_assertions::assert_eq; #[test] fn content_to_targets_test() { diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index db1614a..e56312b 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -677,9 +677,9 @@ impl<'a> PartialEq for TextArea_<'a> { #[cfg(test)] mod test { - use crate::model::runner_type; - use super::*; + use crate::model::runner_type; + use pretty_assertions::assert_eq; use std::env; #[test] From efaebabc08b49936a76764381fda06c95242cbf2 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 12:00:15 +0900 Subject: [PATCH 19/36] rename --- src/usecase/tui/app.rs | 119 +++++++++++++++++++++++++---------------- src/usecase/tui/ui.rs | 6 +-- 2 files changed, 76 insertions(+), 49 deletions(-) diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index e56312b..9d8c7e9 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -273,7 +273,7 @@ fn update(model: &mut Model, message: Option) { match message { Some(Message::SearchTextAreaKeyInput(key_event)) => s.handle_key_input(key_event), Some(Message::ExecuteTarget) => { - if let Some(command) = s.get_selected_target() { + if let Some(command) = s.get_selected_command() { s.store_history(command.clone()); if let Some(r) = command.runner_type.to_runner(&s.runners) { model.transition_to_execute_target_state(r, command); @@ -297,21 +297,21 @@ pub struct SelectTargetState<'a> { pub current_pane: CurrentPane, pub runners: Vec, pub search_text_area: TextArea_<'a>, - pub targets_list_state: ListState, + pub commands_list_state: ListState, /// This field could have been of type `Vec`, but it was intentionally made of type `Vec`. /// This is because it allows for future features such as displaying the contents of history in the preview window /// or hiding commands that existed at the time of execution but no longer exist. - pub histories: Vec, - pub histories_list_state: ListState, + pub history: Vec, + pub history_list_state: ListState, } impl PartialEq for SelectTargetState<'_> { fn eq(&self, other: &Self) -> bool { let without_runners = self.current_pane == other.current_pane && self.search_text_area == other.search_text_area - && self.targets_list_state == other.targets_list_state - && self.histories == other.histories - && self.histories_list_state == other.histories_list_state; + && self.commands_list_state == other.commands_list_state + && self.history == other.history + && self.history_list_state == other.history_list_state; if !without_runners { return false; // Early return for performance } @@ -354,13 +354,13 @@ impl SelectTargetState<'_> { current_pane, runners: runners.clone(), search_text_area: TextArea_(TextArea::default()), - targets_list_state: ListState::with_selected(ListState::default(), Some(0)), - histories: Model::get_histories(current_dir, runners), - histories_list_state: ListState::with_selected(ListState::default(), Some(0)), + commands_list_state: ListState::with_selected(ListState::default(), Some(0)), + history: Model::get_histories(current_dir, runners), + history_list_state: ListState::with_selected(ListState::default(), Some(0)), }) } - fn get_selected_target(&self) -> Option { + fn get_selected_command(&self) -> Option { match self.current_pane { CurrentPane::Main => self.selected_target(), CurrentPane::History => self.selected_history(), @@ -375,7 +375,7 @@ impl SelectTargetState<'_> { } fn selected_target(&self) -> Option { - match self.targets_list_state.selected() { + match self.commands_list_state.selected() { Some(i) => self.narrow_down_targets().get(i).cloned(), None => None, } @@ -387,7 +387,7 @@ impl SelectTargetState<'_> { return None; } - match self.histories_list_state.selected() { + match self.history_list_state.selected() { Some(i) => history.get(i).cloned(), None => None, } @@ -444,16 +444,16 @@ impl SelectTargetState<'_> { } pub fn get_history(&self) -> Vec { - self.histories.clone() + self.history.clone() } fn next_target(&mut self) { if self.narrow_down_targets().is_empty() { - self.targets_list_state.select(None); + self.commands_list_state.select(None); return; } - let i = match self.targets_list_state.selected() { + let i = match self.commands_list_state.selected() { Some(i) => { if self.narrow_down_targets().len() - 1 <= i { 0 @@ -463,16 +463,16 @@ impl SelectTargetState<'_> { } None => 0, }; - self.targets_list_state.select(Some(i)); + self.commands_list_state.select(Some(i)); } fn previous_target(&mut self) { if self.narrow_down_targets().is_empty() { - self.targets_list_state.select(None); + self.commands_list_state.select(None); return; } - let i = match self.targets_list_state.selected() { + let i = match self.commands_list_state.selected() { Some(i) => { if i == 0 { self.narrow_down_targets().len() - 1 @@ -482,17 +482,17 @@ impl SelectTargetState<'_> { } None => 0, }; - self.targets_list_state.select(Some(i)); + self.commands_list_state.select(Some(i)); } fn next_history(&mut self) { let history_list = self.get_history(); if history_list.is_empty() { - self.histories_list_state.select(None); + self.history_list_state.select(None); return; }; - let i = match self.histories_list_state.selected() { + let i = match self.history_list_state.selected() { Some(i) => { if history_list.len() - 1 <= i { 0 @@ -502,17 +502,17 @@ impl SelectTargetState<'_> { } None => 0, }; - self.histories_list_state.select(Some(i)); + self.history_list_state.select(Some(i)); } fn previous_history(&mut self) { let history_list_len = self.get_history().len(); match history_list_len { 0 => { - self.histories_list_state.select(None); + self.history_list_state.select(None); } _ => { - let i = match self.histories_list_state.selected() { + let i = match self.history_list_state.selected() { Some(i) => { if i == 0 { history_list_len - 1 @@ -522,7 +522,7 @@ impl SelectTargetState<'_> { } None => 0, }; - self.histories_list_state.select(Some(i)); + self.history_list_state.select(Some(i)); } }; } @@ -541,7 +541,7 @@ impl SelectTargetState<'_> { if let Some((dir, file_name)) = toml::history_file_path() { let all_histories = toml::Histories::get_history().into().append( self.current_dir.clone(), - self.histories.clone(), + self.history.clone(), command, ); @@ -552,9 +552,9 @@ impl SelectTargetState<'_> { fn reset_selection(&mut self) { if self.narrow_down_targets().is_empty() { - self.targets_list_state.select(None); + self.commands_list_state.select(None); } - self.targets_list_state.select(Some(0)); + self.commands_list_state.select(Some(0)); } pub fn get_search_area_text(&self) -> String { @@ -562,7 +562,7 @@ impl SelectTargetState<'_> { } pub fn get_latest_command(&self) -> Option<&command::Command> { - self.histories.first() + self.history.first() } pub fn get_runner(&self, runner_type: &runner_type::RunnerType) -> Option { @@ -609,8 +609,8 @@ impl SelectTargetState<'_> { current_pane: CurrentPane::Main, runners: vec![runner::Runner::MakeCommand(Make::new_for_test())], search_text_area: TextArea_(TextArea::default()), - targets_list_state: ListState::with_selected(ListState::default(), Some(0)), - histories: vec![ + commands_list_state: ListState::with_selected(ListState::default(), Some(0)), + history: vec![ command::Command { runner_type: runner_type::RunnerType::Make, name: "history0".to_string(), @@ -630,7 +630,7 @@ impl SelectTargetState<'_> { line_number: 7, }, ], - histories_list_state: ListState::with_selected(ListState::default(), Some(0)), + history_list_state: ListState::with_selected(ListState::default(), Some(0)), } } } @@ -766,7 +766,10 @@ mod test { message: Some(Message::NextTarget), expect_model: Model { app_state: AppState::SelectTarget(SelectTargetState { - targets_list_state: ListState::with_selected(ListState::default(), Some(1)), + commands_list_state: ListState::with_selected( + ListState::default(), + Some(1), + ), ..SelectTargetState::new_for_test() }), }, @@ -775,14 +778,20 @@ mod test { title: "Next(2 -> 0)", model: Model { app_state: AppState::SelectTarget(SelectTargetState { - targets_list_state: ListState::with_selected(ListState::default(), Some(2)), + commands_list_state: ListState::with_selected( + ListState::default(), + Some(2), + ), ..SelectTargetState::new_for_test() }), }, message: Some(Message::NextTarget), expect_model: Model { app_state: AppState::SelectTarget(SelectTargetState { - targets_list_state: ListState::with_selected(ListState::default(), Some(0)), + commands_list_state: ListState::with_selected( + ListState::default(), + Some(0), + ), ..SelectTargetState::new_for_test() }), }, @@ -791,14 +800,20 @@ mod test { title: "Previous(1 -> 0)", model: Model { app_state: AppState::SelectTarget(SelectTargetState { - targets_list_state: ListState::with_selected(ListState::default(), Some(1)), + commands_list_state: ListState::with_selected( + ListState::default(), + Some(1), + ), ..SelectTargetState::new_for_test() }), }, message: Some(Message::PreviousTarget), expect_model: Model { app_state: AppState::SelectTarget(SelectTargetState { - targets_list_state: ListState::with_selected(ListState::default(), Some(0)), + commands_list_state: ListState::with_selected( + ListState::default(), + Some(0), + ), ..SelectTargetState::new_for_test() }), }, @@ -807,14 +822,20 @@ mod test { title: "Previous(0 -> 2)", model: Model { app_state: AppState::SelectTarget(SelectTargetState { - targets_list_state: ListState::with_selected(ListState::default(), Some(0)), + commands_list_state: ListState::with_selected( + ListState::default(), + Some(0), + ), ..SelectTargetState::new_for_test() }), }, message: Some(Message::PreviousTarget), expect_model: Model { app_state: AppState::SelectTarget(SelectTargetState { - targets_list_state: ListState::with_selected(ListState::default(), Some(2)), + commands_list_state: ListState::with_selected( + ListState::default(), + Some(2), + ), ..SelectTargetState::new_for_test() }), }, @@ -870,7 +891,10 @@ mod test { was inputted when the target located not in top of the targets", model: Model { app_state: AppState::SelectTarget(SelectTargetState { - targets_list_state: ListState::with_selected(ListState::default(), Some(1)), + commands_list_state: ListState::with_selected( + ListState::default(), + Some(1), + ), ..SelectTargetState::new_for_test() }), }, @@ -879,7 +903,10 @@ mod test { ))), expect_model: Model { app_state: AppState::SelectTarget(SelectTargetState { - targets_list_state: ListState::with_selected(ListState::default(), Some(0)), + commands_list_state: ListState::with_selected( + ListState::default(), + Some(0), + ), search_text_area: { let mut text_area = TextArea::default(); text_area.input(KeyEvent::from(KeyCode::Char('a'))); @@ -894,7 +921,7 @@ mod test { model: { let mut m = Model { app_state: AppState::SelectTarget(SelectTargetState { - targets_list_state: ListState::with_selected( + commands_list_state: ListState::with_selected( ListState::default(), None, ), @@ -913,7 +940,7 @@ mod test { message: Some(Message::NextTarget), expect_model: Model { app_state: AppState::SelectTarget(SelectTargetState { - targets_list_state: ListState::with_selected(ListState::default(), None), + commands_list_state: ListState::with_selected(ListState::default(), None), search_text_area: { let mut text_area = TextArea::default(); text_area.input(KeyEvent::from(KeyCode::Char('w'))); @@ -928,7 +955,7 @@ mod test { model: { let mut m = Model { app_state: AppState::SelectTarget(SelectTargetState { - targets_list_state: ListState::with_selected( + commands_list_state: ListState::with_selected( ListState::default(), None, ), @@ -947,7 +974,7 @@ mod test { message: Some(Message::PreviousTarget), expect_model: Model { app_state: AppState::SelectTarget(SelectTargetState { - targets_list_state: ListState::with_selected(ListState::default(), None), + commands_list_state: ListState::with_selected(ListState::default(), None), search_text_area: { let mut text_area = TextArea::default(); text_area.input(KeyEvent::from(KeyCode::Char('w'))); diff --git a/src/usecase/tui/ui.rs b/src/usecase/tui/ui.rs index 4863a33..2cceb4d 100644 --- a/src/usecase/tui/ui.rs +++ b/src/usecase/tui/ui.rs @@ -68,7 +68,7 @@ fn render_preview_block(model: &SelectTargetState, f: &mut Frame, chunk: ratatui let narrow_down_targets = model.narrow_down_targets(); let selecting_command = - narrow_down_targets.get(model.targets_list_state.selected().unwrap_or(0)); + narrow_down_targets.get(model.commands_list_state.selected().unwrap_or(0)); let (fg_color_, border_style) = color_and_border_style_for_selectable(model.current_pane.is_main()); @@ -172,7 +172,7 @@ fn render_targets_block( ), chunk, // NOTE: It is against TEA's way to update the model value on the UI side, but it is unavoidable so it is allowed. - &mut model.targets_list_state, + &mut model.commands_list_state, ); } @@ -211,7 +211,7 @@ fn render_history_block( ), chunk, // NOTE: It is against TEA's way to update the model value on the UI side, but it is unavoidable so it is allowed. - &mut model.histories_list_state, + &mut model.history_list_state, ); } From 4152334ca6df43124864e3d0820f91678a1633e6 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 12:26:30 +0900 Subject: [PATCH 20/36] histories.append --- src/model/histories.rs | 224 +++++++++++++++++++++++------------------ src/usecase/tui/app.rs | 8 +- 2 files changed, 130 insertions(+), 102 deletions(-) diff --git a/src/model/histories.rs b/src/model/histories.rs index 86c8938..81d0807 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -10,25 +10,19 @@ pub struct Histories { } impl Histories { - // TODO: ut - pub fn append( - &self, - current_dir: PathBuf, - history_of_cwd: Vec, - command: command::Command, - ) -> Self { + pub fn append(&self, current_dir: PathBuf, command: command::Command) -> Self { + // Update the command history for the current directory. let new_history = { - let history_commands: Vec = history_of_cwd - .iter() - .map(|c| HistoryCommand::from(c.clone())) - .collect(); - let history = History { - path: current_dir.clone(), - commands: history_commands, - }; - history.append(command) + match self.histories.iter().find(|h| h.path == current_dir) { + Some(history) => history.append(command.clone()), + None => History { + path: current_dir, + commands: vec![HistoryCommand::from(command)], + }, + } }; + // Update the whole histories. let mut new_histories = self.histories.clone(); match new_histories .iter() @@ -94,87 +88,123 @@ impl HistoryCommand { #[cfg(test)] mod test { - // TODO(#321): comment in this 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, - // ) - // } - // } + use super::*; + use pretty_assertions::assert_eq; + use std::path::PathBuf; + + #[test] + fn histories_append_test() { + struct Case { + title: &'static str, + histories: Histories, + command_to_append: command::Command, + expect: Histories, + } + + let path_to_append = PathBuf::from("/Users/user/code/fzf-make".to_string()); + let cases = vec![ + Case { + // Use raw string literal as workaround for + // https://github.com/rust-lang/rustfmt/issues/4800. + title: r#"The command executed is appended to the existing history if there is history for cwd."#, + histories: Histories { + histories: vec![ + History { + path: PathBuf::from("/Users/user/code/rustc".to_string()), + commands: vec![HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + }], + }, + History { + path: path_to_append.clone(), + commands: vec![HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + }], + }, + ], + }, + command_to_append: command::Command { + runner_type: runner_type::RunnerType::Make, + name: "append".to_string(), + file_name: PathBuf::from("Makefile"), + line_number: 1, + }, + expect: Histories { + histories: vec![ + History { + path: PathBuf::from("/Users/user/code/rustc".to_string()), + commands: vec![HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + }], + }, + History { + path: path_to_append.clone(), + commands: vec![ + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "append".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + }, + ], + }, + ], + }, + }, + Case { + title: r#"A new history is appended if there is no history for cwd."#, + histories: Histories { + histories: vec![History { + path: PathBuf::from("/Users/user/code/rustc".to_string()), + commands: vec![HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + }], + }], + }, + command_to_append: command::Command { + runner_type: runner_type::RunnerType::Make, + name: "append".to_string(), + file_name: PathBuf::from("Makefile"), + line_number: 1, + }, + expect: Histories { + histories: vec![ + History { + path: path_to_append.clone(), + commands: vec![HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "append".to_string(), + }], + }, + History { + path: PathBuf::from("/Users/user/code/rustc".to_string()), + commands: vec![HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + }], + }, + ], + }, + }, + ]; + + for case in cases { + assert_eq!( + case.expect, + case.histories + .append(path_to_append.clone(), case.command_to_append), + "\nFailed: 🚨{:?}🚨\n", + case.title, + ) + } + } + // TODO(#321): comment in this test // #[test] // fn history_append_test() { diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index 9d8c7e9..17d0316 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -539,11 +539,9 @@ impl SelectTargetState<'_> { // Because self.histories_list_state.selected keeps the selected index of the history list // before update. if let Some((dir, file_name)) = toml::history_file_path() { - let all_histories = toml::Histories::get_history().into().append( - self.current_dir.clone(), - self.history.clone(), - command, - ); + let all_histories = toml::Histories::get_history() + .into() + .append(self.current_dir.clone(), command); // TODO: handle error let _ = toml::store_history(dir, file_name, all_histories); From bc61ca9d985a12f10b46f1b54b0d6e12ac032b94 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 12:55:09 +0900 Subject: [PATCH 21/36] history.append --- src/model/histories.rs | 526 ++++++++++++++++++----------------------- 1 file changed, 230 insertions(+), 296 deletions(-) diff --git a/src/model/histories.rs b/src/model/histories.rs index 81d0807..e9791c1 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -49,7 +49,6 @@ pub struct History { } impl History { - // TODO: ut fn append(&self, executed_command: command::Command) -> Self { let mut updated_commands = self.commands.clone(); // removes the executed_command from the history @@ -96,9 +95,9 @@ mod test { fn histories_append_test() { struct Case { title: &'static str, - histories: Histories, + before: Histories, command_to_append: command::Command, - expect: Histories, + after: Histories, } let path_to_append = PathBuf::from("/Users/user/code/fzf-make".to_string()); @@ -107,7 +106,7 @@ mod test { // Use raw string literal as workaround for // https://github.com/rust-lang/rustfmt/issues/4800. title: r#"The command executed is appended to the existing history if there is history for cwd."#, - histories: Histories { + before: Histories { histories: vec![ History { path: PathBuf::from("/Users/user/code/rustc".to_string()), @@ -131,7 +130,7 @@ mod test { file_name: PathBuf::from("Makefile"), line_number: 1, }, - expect: Histories { + after: Histories { histories: vec![ History { path: PathBuf::from("/Users/user/code/rustc".to_string()), @@ -158,7 +157,7 @@ mod test { }, Case { title: r#"A new history is appended if there is no history for cwd."#, - histories: Histories { + before: Histories { histories: vec![History { path: PathBuf::from("/Users/user/code/rustc".to_string()), commands: vec![HistoryCommand { @@ -173,7 +172,7 @@ mod test { file_name: PathBuf::from("Makefile"), line_number: 1, }, - expect: Histories { + after: Histories { histories: vec![ History { path: path_to_append.clone(), @@ -196,8 +195,8 @@ mod test { for case in cases { assert_eq!( - case.expect, - case.histories + case.after, + case.before .append(path_to_append.clone(), case.command_to_append), "\nFailed: 🚨{:?}🚨\n", case.title, @@ -205,291 +204,226 @@ mod test { } } - // TODO(#321): comment in this test - // #[test] - // fn history_append_test() { - // struct Case { - // title: &'static str, - // appending_target: command::Command, - // 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: command::Command::new( - // runner_type::RunnerType::Make, - // "history2".to_string(), - // PathBuf::from("Makefile"), - // 1, - // ), - // history: History { - // path: path.clone(), - // executed_targets: vec![ - // command::Command::new( - // runner_type::RunnerType::Make, - // "history0".to_string(), - // PathBuf::from("Makefile"), - // 1, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history1".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // ], - // }, - // expect: History { - // path: path.clone(), - // executed_targets: vec![ - // command::Command::new( - // runner_type::RunnerType::Make, - // "history2".to_string(), - // PathBuf::from("Makefile"), - // 1, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history0".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history1".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // ], - // }, - // }, - // Case { - // title: "Append to head(Append to empty)", - // appending_target: command::Command::new( - // runner_type::RunnerType::Make, - // "history0".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // history: History { - // path: path.clone(), - // executed_targets: vec![], - // }, - // expect: History { - // path: path.clone(), - // executed_targets: vec![command::Command::new( - // runner_type::RunnerType::Make, - // "history0".to_string(), - // PathBuf::from("Makefile"), - // 4, - // )], - // }, - // }, - // Case { - // title: "Append to head(Remove duplicated)", - // appending_target: command::Command::new( - // runner_type::RunnerType::Make, - // "history1".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // history: History { - // path: path.clone(), - // executed_targets: vec![ - // command::Command::new( - // runner_type::RunnerType::Make, - // "history0".to_string(), - // PathBuf::from("Makefile"), - // 1, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history1".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history2".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // ], - // }, - // expect: History { - // path: path.clone(), - // executed_targets: vec![ - // command::Command::new( - // runner_type::RunnerType::Make, - // "history1".to_string(), - // PathBuf::from("Makefile"), - // 1, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history0".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history2".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // ], - // }, - // }, - // Case { - // title: "Truncate when length exceeds 10", - // appending_target: command::Command::new( - // runner_type::RunnerType::Make, - // "history11".to_string(), - // PathBuf::from("Makefile"), - // 1, - // ), - // history: History { - // path: path.clone(), - // executed_targets: vec![ - // command::Command::new( - // runner_type::RunnerType::Make, - // "history0".to_string(), - // PathBuf::from("Makefile"), - // 1, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history1".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history2".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history3".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history4".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history5".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history6".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history7".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history8".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history9".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // ], - // }, - // expect: History { - // path: path.clone(), - // executed_targets: vec![ - // command::Command::new( - // runner_type::RunnerType::Make, - // "history11".to_string(), - // PathBuf::from("Makefile"), - // 1, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history0".to_string(), - // PathBuf::from("Makefile"), - // 1, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history1".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history2".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history3".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history4".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history5".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history6".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history7".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history8".to_string(), - // PathBuf::from("Makefile"), - // 4, - // ), - // ], - // }, - // }, - // ]; - // - // for case in cases { - // assert_eq!( - // case.expect, - // case.history.append(case.appending_target), - // "\nFailed: 🚨{:?}🚨\n", - // case.title, - // ) - // } - // } + #[test] + fn history_append_test() { + struct Case { + title: &'static str, + before: History, + command_to_append: command::Command, + after: History, + } + let path = PathBuf::from("/Users/user/code/fzf-make".to_string()); + let cases = vec![ + Case { + title: "Append to head", + before: History { + path: path.clone(), + commands: vec![ + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history1".to_string(), + }, + ], + }, + command_to_append: command::Command::new( + runner_type::RunnerType::Make, + "history2".to_string(), + PathBuf::from("Makefile"), + 1, + ), + after: History { + path: path.clone(), + commands: vec![ + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history2".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history1".to_string(), + }, + ], + }, + }, + Case { + title: "Append to head(Append to empty)", + before: History { + path: path.clone(), + commands: vec![], + }, + command_to_append: command::Command::new( + runner_type::RunnerType::Make, + "history0".to_string(), + PathBuf::from("Makefile"), + 4, + ), + after: History { + path: path.clone(), + commands: vec![HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + }], + }, + }, + Case { + title: "Append to head(Remove duplicated command)", + before: History { + path: path.clone(), + commands: vec![ + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history1".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history2".to_string(), + }, + ], + }, + command_to_append: command::Command::new( + runner_type::RunnerType::Make, + "history2".to_string(), + PathBuf::from("Makefile"), + 1, + ), + after: History { + path: path.clone(), + commands: vec![ + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history2".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history1".to_string(), + }, + ], + }, + }, + Case { + title: "Truncate when length exceeds 10", + before: History { + path: path.clone(), + commands: vec![ + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history1".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history2".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history3".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history4".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history5".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history6".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history7".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history8".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history9".to_string(), + }, + ], + }, + command_to_append: command::Command::new( + runner_type::RunnerType::Make, + "history10".to_string(), + PathBuf::from("Makefile"), + 1, + ), + after: History { + path: path.clone(), + commands: vec![ + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history10".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history0".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history1".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history2".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history3".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history4".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history5".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history6".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history7".to_string(), + }, + HistoryCommand { + runner_type: runner_type::RunnerType::Make, + name: "history8".to_string(), + }, + ], + }, + }, + ]; + + for case in cases { + assert_eq!( + case.after, + case.before.append(case.command_to_append), + "\nFailed: 🚨{:?}🚨\n", + case.title, + ) + } + } } From 43bf4da893049c1e14f82037b8ed0ec5cee70d8c Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 13:05:14 +0900 Subject: [PATCH 22/36] comment in tests temporary disabled --- src/usecase/tui/app.rs | 232 +++++++++++++++++++---------------------- 1 file changed, 106 insertions(+), 126 deletions(-) diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index 17d0316..c8d4f0f 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -858,32 +858,28 @@ mod test { )), }, }, - // TODO(#321): comment in this test - // Case { - // title: "ExecuteTarget(History)", - // model: Model { - // app_state: AppState::SelectTarget(SelectTargetState { - // current_pane: CurrentPane::History, - // histories_list_state: ListState::with_selected( - // ListState::default(), - // Some(1), - // ), - // ..SelectTargetState::new_for_test() - // }), - // }, - // message: Some(Message::ExecuteTarget), - // expect_model: Model { - // app_state: AppState::ExecuteTarget(ExecuteTargetState::new( - // runner::Runner::MakeCommand(Make::new_for_test()), - // command::Command::new( - // runner_type::RunnerType::Make, - // "history1".to_string(), - // PathBuf::new(), - // 4, - // ) - // )), - // }, - // }, + Case { + title: "ExecuteTarget(History)", + model: Model { + app_state: AppState::SelectTarget(SelectTargetState { + current_pane: CurrentPane::History, + history_list_state: ListState::with_selected(ListState::default(), Some(1)), + ..SelectTargetState::new_for_test() + }), + }, + message: Some(Message::ExecuteTarget), + expect_model: Model { + app_state: AppState::ExecuteTarget(ExecuteTargetState::new( + runner::Runner::MakeCommand(Make::new_for_test()), + command::Command::new( + runner_type::RunnerType::Make, + "history1".to_string(), + PathBuf::from("Makefile"), + 4, + ), + )), + }, + }, Case { title: "Selecting position should be reset if some kind of char was inputted when the target located not in top of the targets", @@ -982,106 +978,90 @@ mod test { }), }, }, - // TODO(#321): comment in this test - // Case { - // title: "NextHistory", - // model: Model { - // app_state: AppState::SelectTarget(SelectTargetState { - // current_pane: CurrentPane::History, - // histories_list_state: ListState::with_selected( - // ListState::default(), - // Some(0), - // ), - // ..SelectTargetState::new_for_test() - // }), - // }, - // message: Some(Message::NextHistory), - // expect_model: Model { - // app_state: AppState::SelectTarget(SelectTargetState { - // current_pane: CurrentPane::History, - // histories_list_state: ListState::with_selected( - // ListState::default(), - // Some(1), - // ), - // ..SelectTargetState::new_for_test() - // }), - // }, - // }, - // TODO(#321): comment in this test - // Case { - // title: "PreviousHistory", - // model: Model { - // app_state: AppState::SelectTarget(SelectTargetState { - // current_pane: CurrentPane::History, - // histories_list_state: ListState::with_selected( - // ListState::default(), - // Some(0), - // ), - // ..SelectTargetState::new_for_test() - // }), - // }, - // message: Some(Message::NextHistory), - // expect_model: Model { - // app_state: AppState::SelectTarget(SelectTargetState { - // current_pane: CurrentPane::History, - // histories_list_state: ListState::with_selected( - // ListState::default(), - // Some(1), - // ), - // ..SelectTargetState::new_for_test() - // }), - // }, - // }, - // TODO(#321): comment in this test - // Case { - // title: "When the last history is selected and NextHistory is received, it returns to the beginning.", - // model: Model { - // app_state: AppState::SelectTarget(SelectTargetState { - // current_pane: CurrentPane::History, - // histories_list_state: ListState::with_selected( - // ListState::default(), - // Some(2), - // ), - // ..SelectTargetState::new_for_test() - // }), - // }, - // message: Some(Message::NextHistory), - // expect_model: Model { - // app_state: AppState::SelectTarget(SelectTargetState { - // current_pane: CurrentPane::History, - // histories_list_state: ListState::with_selected( - // ListState::default(), - // Some(0), - // ), - // ..SelectTargetState::new_for_test() - // }), - // }, - // }, - // TODO(#321): comment in this test - // Case { - // title: "When the first history is selected and PreviousHistory is received, it moves to the last history.", - // model: Model { - // app_state: AppState::SelectTarget(SelectTargetState { - // current_pane: CurrentPane::History, - // histories_list_state: ListState::with_selected( - // ListState::default(), - // Some(0), - // ), - // ..SelectTargetState::new_for_test() - // }), - // }, - // message: Some(Message::PreviousHistory), - // expect_model: Model { - // app_state: AppState::SelectTarget(SelectTargetState { - // current_pane: CurrentPane::History, - // histories_list_state: ListState::with_selected( - // ListState::default(), - // Some(2), - // ), - // ..SelectTargetState::new_for_test() - // }), - // }, - // }, + Case { + title: "NextHistory", + model: Model { + app_state: AppState::SelectTarget(SelectTargetState { + current_pane: CurrentPane::History, + history_list_state: ListState::with_selected(ListState::default(), Some(0)), + ..SelectTargetState::new_for_test() + }), + }, + message: Some(Message::NextHistory), + expect_model: Model { + app_state: AppState::SelectTarget(SelectTargetState { + current_pane: CurrentPane::History, + history_list_state: ListState::with_selected(ListState::default(), Some(1)), + ..SelectTargetState::new_for_test() + }), + }, + }, + Case { + title: "PreviousHistory", + model: Model { + app_state: AppState::SelectTarget(SelectTargetState { + current_pane: CurrentPane::History, + history_list_state: ListState::with_selected(ListState::default(), Some(0)), + ..SelectTargetState::new_for_test() + }), + }, + message: Some(Message::NextHistory), + expect_model: Model { + app_state: AppState::SelectTarget(SelectTargetState { + current_pane: CurrentPane::History, + history_list_state: ListState::with_selected(ListState::default(), Some(1)), + ..SelectTargetState::new_for_test() + }), + }, + }, + Case { + title: "When the last history is selected and NextHistory is received, it returns to the beginning.", + model: Model { + app_state: AppState::SelectTarget(SelectTargetState { + current_pane: CurrentPane::History, + history_list_state: ListState::with_selected( + ListState::default(), + Some(2), + ), + ..SelectTargetState::new_for_test() + }), + }, + message: Some(Message::NextHistory), + expect_model: Model { + app_state: AppState::SelectTarget(SelectTargetState { + current_pane: CurrentPane::History, + history_list_state: ListState::with_selected( + ListState::default(), + Some(0), + ), + ..SelectTargetState::new_for_test() + }), + }, + }, + Case { + title: "When the first history is selected and PreviousHistory is received, it moves to the last history.", + model: Model { + app_state: AppState::SelectTarget(SelectTargetState { + current_pane: CurrentPane::History, + history_list_state: ListState::with_selected( + ListState::default(), + Some(0), + ), + ..SelectTargetState::new_for_test() + }), + }, + message: Some(Message::PreviousHistory), + expect_model: Model { + app_state: AppState::SelectTarget(SelectTargetState { + current_pane: CurrentPane::History, + history_list_state: ListState::with_selected( + ListState::default(), + Some(2), + ), + ..SelectTargetState::new_for_test() + }), + }, + }, ]; // NOTE: When running tests, you need to set FZF_MAKE_IS_TESTING=true. Otherwise, the developer's history file will be overwritten. From d9a71bd30a06d8ac9ae0d106a6c866f77d078f6b Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 13:08:12 +0900 Subject: [PATCH 23/36] rename: target -> command --- src/usecase/help.rs | 2 +- src/usecase/repeat.rs | 2 +- src/usecase/tui/app.rs | 256 ++++++++++++++++++++--------------------- src/usecase/tui/ui.rs | 56 ++++----- 4 files changed, 158 insertions(+), 158 deletions(-) diff --git a/src/usecase/help.rs b/src/usecase/help.rs index 2958021..6f13a28 100644 --- a/src/usecase/help.rs +++ b/src/usecase/help.rs @@ -30,7 +30,7 @@ USAGE: SUBCOMMANDS: repeat, --repeat, -r - Execute the last executed make target. + Execute the last executed command. history, --history, -h Launch fzf-make with the history pane focused. help, --help, -h diff --git a/src/usecase/repeat.rs b/src/usecase/repeat.rs index 6ae7bc2..241d1d1 100644 --- a/src/usecase/repeat.rs +++ b/src/usecase/repeat.rs @@ -23,7 +23,7 @@ impl Usecase for Repeat { match Model::new(config::Config::default()) { Err(e) => Err(e), Ok(model) => match model.app_state { - AppState::SelectTarget(state) => match state.get_latest_command() { + AppState::SelectCommand(state) => match state.get_latest_command() { Some(c) => match state.get_runner(&c.runner_type) { Some(runner) => runner.execute(c), None => Err(anyhow!("runner not found.")), diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index c8d4f0f..db56b08 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -36,8 +36,8 @@ use tui_textarea::TextArea; // See: https://www.youtube.com/watch?v=IcgmSRJHu_8 #[derive(PartialEq, Debug)] pub enum AppState<'a> { - SelectTarget(SelectTargetState<'a>), - ExecuteTarget(ExecuteTargetState), + SelectCommand(SelectCommandState<'a>), + ExecuteCommand(ExecuteCommandState), ShouldQuit, } @@ -48,9 +48,9 @@ pub struct Model<'a> { impl Model<'_> { pub fn new(config: config::Config) -> Result { - match SelectTargetState::new(config) { + match SelectCommandState::new(config) { Ok(s) => Ok(Model { - app_state: AppState::SelectTarget(s), + app_state: AppState::SelectCommand(s), }), Err(e) => Err(e), } @@ -58,21 +58,21 @@ impl Model<'_> { fn handle_key_input(&self, key: KeyEvent) -> Option { match &self.app_state { - AppState::SelectTarget(s) => match key.code { + AppState::SelectCommand(s) => match key.code { KeyCode::Tab => Some(Message::MoveToNextPane), KeyCode::Esc => Some(Message::Quit), _ => match s.current_pane { CurrentPane::Main => match key.code { - KeyCode::Down => Some(Message::NextTarget), - KeyCode::Up => Some(Message::PreviousTarget), - KeyCode::Enter => Some(Message::ExecuteTarget), + KeyCode::Down => Some(Message::NextCommand), + KeyCode::Up => Some(Message::PreviousCommand), + KeyCode::Enter => Some(Message::ExecuteCommand), _ => Some(Message::SearchTextAreaKeyInput(key)), }, CurrentPane::History => match key.code { KeyCode::Char('q') => Some(Message::Quit), KeyCode::Down => Some(Message::NextHistory), KeyCode::Up => Some(Message::PreviousHistory), - KeyCode::Enter | KeyCode::Char(' ') => Some(Message::ExecuteTarget), + KeyCode::Enter | KeyCode::Char(' ') => Some(Message::ExecuteCommand), _ => None, }, }, @@ -127,12 +127,12 @@ impl Model<'_> { commands } - fn transition_to_execute_target_state( + fn transition_to_execute_command_state( &mut self, runner: runner::Runner, command: command::Command, ) { - self.app_state = AppState::ExecuteTarget(ExecuteTargetState::new(runner, command)); + self.app_state = AppState::ExecuteCommand(ExecuteCommandState::new(runner, command)); } fn transition_to_should_quit_state(&mut self) { @@ -144,13 +144,13 @@ impl Model<'_> { self.app_state == AppState::ShouldQuit } - pub fn is_target_selected(&self) -> bool { - matches!(self.app_state, AppState::ExecuteTarget(_)) + pub fn is_command_selected(&self) -> bool { + matches!(self.app_state, AppState::ExecuteCommand(_)) } pub fn command_to_execute(&self) -> Option<(runner::Runner, command::Command)> { match &self.app_state { - AppState::ExecuteTarget(command) => { + AppState::ExecuteCommand(command) => { let command = command.clone(); Some((command.executor, command.command)) } @@ -174,7 +174,7 @@ pub fn main(config: config::Config) -> Result<()> { } let mut model = model.unwrap(); - let target = match run(&mut terminal, &mut model) { + let command = match run(&mut terminal, &mut model) { Ok(t) => t, Err(e) => { shutdown_terminal(&mut terminal)?; @@ -184,7 +184,7 @@ pub fn main(config: config::Config) -> Result<()> { shutdown_terminal(&mut terminal)?; - match target { + match command { Some((runner, command)) => { runner.show_command(&command); let _ = runner.execute(&command); // TODO: handle error @@ -216,7 +216,7 @@ fn run<'a, B: Backend>( match handle_event(model) { Ok(message) => { update(model, message); - if model.should_quit() || model.is_target_selected() { + if model.should_quit() || model.is_command_selected() { break; } } @@ -246,9 +246,9 @@ fn shutdown_terminal(terminal: &mut Terminal>) -> Resul enum Message { SearchTextAreaKeyInput(KeyEvent), - ExecuteTarget, - NextTarget, - PreviousTarget, + ExecuteCommand, + NextCommand, + PreviousCommand, MoveToNextPane, NextHistory, PreviousHistory, @@ -269,19 +269,19 @@ fn handle_event(model: &Model) -> io::Result> { // TODO: make this method Model's method // TODO: Make this function returns `Result` or have a field like Model.error to hold errors fn update(model: &mut Model, message: Option) { - if let AppState::SelectTarget(ref mut s) = model.app_state { + if let AppState::SelectCommand(ref mut s) = model.app_state { match message { Some(Message::SearchTextAreaKeyInput(key_event)) => s.handle_key_input(key_event), - Some(Message::ExecuteTarget) => { + Some(Message::ExecuteCommand) => { if let Some(command) = s.get_selected_command() { s.store_history(command.clone()); if let Some(r) = command.runner_type.to_runner(&s.runners) { - model.transition_to_execute_target_state(r, command); + model.transition_to_execute_command_state(r, command); } }; } - Some(Message::NextTarget) => s.next_target(), - Some(Message::PreviousTarget) => s.previous_target(), + Some(Message::NextCommand) => s.next_command(), + Some(Message::PreviousCommand) => s.previous_command(), Some(Message::MoveToNextPane) => s.move_to_next_pane(), Some(Message::NextHistory) => s.next_history(), Some(Message::PreviousHistory) => s.previous_history(), @@ -292,7 +292,7 @@ fn update(model: &mut Model, message: Option) { } #[derive(Debug)] -pub struct SelectTargetState<'a> { +pub struct SelectCommandState<'a> { pub current_dir: PathBuf, pub current_pane: CurrentPane, pub runners: Vec, @@ -305,7 +305,7 @@ pub struct SelectTargetState<'a> { pub history_list_state: ListState, } -impl PartialEq for SelectTargetState<'_> { +impl PartialEq for SelectCommandState<'_> { fn eq(&self, other: &Self) -> bool { let without_runners = self.current_pane == other.current_pane && self.search_text_area == other.search_text_area @@ -330,7 +330,7 @@ impl PartialEq for SelectTargetState<'_> { } } -impl SelectTargetState<'_> { +impl SelectCommandState<'_> { pub fn new(config: config::Config) -> Result { let current_dir = match env::current_dir() { Ok(d) => d, @@ -349,7 +349,7 @@ impl SelectTargetState<'_> { let runner = { runner::Runner::MakeCommand(makefile) }; let runners = vec![runner]; - Ok(SelectTargetState { + Ok(SelectCommandState { current_dir: current_dir.clone(), current_pane, runners: runners.clone(), @@ -362,7 +362,7 @@ impl SelectTargetState<'_> { fn get_selected_command(&self) -> Option { match self.current_pane { - CurrentPane::Main => self.selected_target(), + CurrentPane::Main => self.selected_command(), CurrentPane::History => self.selected_history(), } } @@ -374,9 +374,9 @@ impl SelectTargetState<'_> { } } - fn selected_target(&self) -> Option { + fn selected_command(&self) -> Option { match self.commands_list_state.selected() { - Some(i) => self.narrow_down_targets().get(i).cloned(), + Some(i) => self.narrow_down_commands().get(i).cloned(), None => None, } } @@ -393,7 +393,7 @@ impl SelectTargetState<'_> { } } - pub fn narrow_down_targets(&self) -> Vec { + pub fn narrow_down_commands(&self) -> Vec { let commands = { let mut commands: Vec = Vec::new(); for runner in &self.runners { @@ -417,19 +417,19 @@ impl SelectTargetState<'_> { let matcher = SkimMatcherV2::default(); let mut list: Vec<(i64, String)> = commands .into_iter() - .filter_map(|target| { + .filter_map(|command| { let mut key_input = self.search_text_area.0.lines().join(""); key_input.retain(|c| !c.is_whitespace()); matcher - .fuzzy_indices(&target.to_string(), key_input.as_str()) - .map(|(score, _)| (score, target.to_string())) + .fuzzy_indices(&command.to_string(), key_input.as_str()) + .map(|(score, _)| (score, command.to_string())) }) .collect(); list.sort_by(|(score1, _), (score2, _)| score1.cmp(score2)); list.reverse(); - list.into_iter().map(|(_, target)| target).collect() + list.into_iter().map(|(_, command)| command).collect() }; let mut result: Vec = Vec::new(); @@ -447,15 +447,15 @@ impl SelectTargetState<'_> { self.history.clone() } - fn next_target(&mut self) { - if self.narrow_down_targets().is_empty() { + fn next_command(&mut self) { + if self.narrow_down_commands().is_empty() { self.commands_list_state.select(None); return; } let i = match self.commands_list_state.selected() { Some(i) => { - if self.narrow_down_targets().len() - 1 <= i { + if self.narrow_down_commands().len() - 1 <= i { 0 } else { i + 1 @@ -466,8 +466,8 @@ impl SelectTargetState<'_> { self.commands_list_state.select(Some(i)); } - fn previous_target(&mut self) { - if self.narrow_down_targets().is_empty() { + fn previous_command(&mut self) { + if self.narrow_down_commands().is_empty() { self.commands_list_state.select(None); return; } @@ -475,7 +475,7 @@ impl SelectTargetState<'_> { let i = match self.commands_list_state.selected() { Some(i) => { if i == 0 { - self.narrow_down_targets().len() - 1 + self.narrow_down_commands().len() - 1 } else { i - 1 } @@ -535,7 +535,7 @@ impl SelectTargetState<'_> { } fn store_history(&self, command: command::Command) { - // NOTE: self.get_selected_target should be called before self.append_history. + // NOTE: self.get_selected_command should be called before self.append_history. // Because self.histories_list_state.selected keeps the selected index of the history list // before update. if let Some((dir, file_name)) = toml::history_file_path() { @@ -549,7 +549,7 @@ impl SelectTargetState<'_> { } fn reset_selection(&mut self) { - if self.narrow_down_targets().is_empty() { + if self.narrow_down_commands().is_empty() { self.commands_list_state.select(None); } self.commands_list_state.select(Some(0)); @@ -602,7 +602,7 @@ impl SelectTargetState<'_> { fn new_for_test() -> Self { use crate::model::runner_type; - SelectTargetState { + SelectCommandState { current_dir: env::current_dir().unwrap(), current_pane: CurrentPane::Main, runners: vec![runner::Runner::MakeCommand(Make::new_for_test())], @@ -634,16 +634,16 @@ impl SelectTargetState<'_> { } #[derive(Clone, Debug, PartialEq)] -pub struct ExecuteTargetState { +pub struct ExecuteCommandState { /// It is possible to have one concrete type like Command struct here. /// But from the perspective of simpleness of code base, this field has trait object. executor: runner::Runner, command: command::Command, } -impl ExecuteTargetState { +impl ExecuteCommandState { fn new(executor: runner::Runner, command: command::Command) -> Self { - ExecuteTargetState { executor, command } + ExecuteCommandState { executor, command } } } @@ -692,40 +692,40 @@ mod test { Case { title: "MoveToNextPane(Main -> History)", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { current_pane: CurrentPane::Main, - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, message: Some(Message::MoveToNextPane), expect_model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { current_pane: CurrentPane::History, - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, }, Case { title: "MoveToNextPane(History -> Main)", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { current_pane: CurrentPane::History, - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, message: Some(Message::MoveToNextPane), expect_model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { current_pane: CurrentPane::Main, - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, }, Case { title: "Quit", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { - ..SelectTargetState::new_for_test() + app_state: AppState::SelectCommand(SelectCommandState { + ..SelectCommandState::new_for_test() }), }, message: Some(Message::Quit), @@ -736,118 +736,118 @@ mod test { Case { title: "SearchTextAreaKeyInput(a)", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { - ..SelectTargetState::new_for_test() + app_state: AppState::SelectCommand(SelectCommandState { + ..SelectCommandState::new_for_test() }), }, message: Some(Message::SearchTextAreaKeyInput(KeyEvent::from( KeyCode::Char('a'), ))), expect_model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { search_text_area: { let mut text_area = TextArea::default(); text_area.input(KeyEvent::from(KeyCode::Char('a'))); TextArea_(text_area) }, - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, }, Case { title: "Next(0 -> 1)", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { - ..SelectTargetState::new_for_test() + app_state: AppState::SelectCommand(SelectCommandState { + ..SelectCommandState::new_for_test() }), }, - message: Some(Message::NextTarget), + message: Some(Message::NextCommand), expect_model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { commands_list_state: ListState::with_selected( ListState::default(), Some(1), ), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, }, Case { title: "Next(2 -> 0)", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { commands_list_state: ListState::with_selected( ListState::default(), Some(2), ), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, - message: Some(Message::NextTarget), + message: Some(Message::NextCommand), expect_model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { commands_list_state: ListState::with_selected( ListState::default(), Some(0), ), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, }, Case { title: "Previous(1 -> 0)", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { commands_list_state: ListState::with_selected( ListState::default(), Some(1), ), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, - message: Some(Message::PreviousTarget), + message: Some(Message::PreviousCommand), expect_model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { commands_list_state: ListState::with_selected( ListState::default(), Some(0), ), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, }, Case { title: "Previous(0 -> 2)", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { commands_list_state: ListState::with_selected( ListState::default(), Some(0), ), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, - message: Some(Message::PreviousTarget), + message: Some(Message::PreviousCommand), expect_model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { commands_list_state: ListState::with_selected( ListState::default(), Some(2), ), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, }, Case { - title: "ExecuteTarget(Main)", + title: "ExecuteCommand(Main)", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { - ..SelectTargetState::new_for_test() + app_state: AppState::SelectCommand(SelectCommandState { + ..SelectCommandState::new_for_test() }), }, - message: Some(Message::ExecuteTarget), + message: Some(Message::ExecuteCommand), expect_model: Model { - app_state: AppState::ExecuteTarget(ExecuteTargetState::new( + app_state: AppState::ExecuteCommand(ExecuteCommandState::new( runner::Runner::MakeCommand(Make::new_for_test()), command::Command::new( runner_type::RunnerType::Make, @@ -859,17 +859,17 @@ mod test { }, }, Case { - title: "ExecuteTarget(History)", + title: "ExecuteCommand(History)", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { current_pane: CurrentPane::History, history_list_state: ListState::with_selected(ListState::default(), Some(1)), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, - message: Some(Message::ExecuteTarget), + message: Some(Message::ExecuteCommand), expect_model: Model { - app_state: AppState::ExecuteTarget(ExecuteTargetState::new( + app_state: AppState::ExecuteCommand(ExecuteCommandState::new( runner::Runner::MakeCommand(Make::new_for_test()), command::Command::new( runner_type::RunnerType::Make, @@ -882,21 +882,21 @@ mod test { }, Case { title: "Selecting position should be reset if some kind of char - was inputted when the target located not in top of the targets", + was inputted when the command located not in top of the commands", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { commands_list_state: ListState::with_selected( ListState::default(), Some(1), ), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, message: Some(Message::SearchTextAreaKeyInput(KeyEvent::from( KeyCode::Char('a'), ))), expect_model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { commands_list_state: ListState::with_selected( ListState::default(), Some(0), @@ -906,24 +906,24 @@ mod test { text_area.input(KeyEvent::from(KeyCode::Char('a'))); TextArea_(text_area) }, - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, }, Case { - title: "NextTarget when there is no targets to select, panic should not occur", + title: "NextCommand when there is no commands to select, panic should not occur", model: { let mut m = Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { commands_list_state: ListState::with_selected( ListState::default(), None, ), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }; update( - // There should not be targets because init_model has ["target0", "target1", "target2"] as target. + // There should not be commands because init_model has ["target0", "target1", "target2"] as command. &mut m, Some(Message::SearchTextAreaKeyInput(KeyEvent::from( KeyCode::Char('w'), @@ -931,33 +931,33 @@ mod test { ); m }, - message: Some(Message::NextTarget), + message: Some(Message::NextCommand), expect_model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { commands_list_state: ListState::with_selected(ListState::default(), None), search_text_area: { let mut text_area = TextArea::default(); text_area.input(KeyEvent::from(KeyCode::Char('w'))); TextArea_(text_area) }, - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, }, Case { - title: "PreviousTarget when there is no targets to select, panic should not occur", + title: "PreviousCommand when there is no commands to select, panic should not occur", model: { let mut m = Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { commands_list_state: ListState::with_selected( ListState::default(), None, ), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }; update( - // There should not be targets because init_model has ["target0", "target1", "target2"] as target. + // There should not be commands because init_model has ["target0", "target1", "target2"] as command. &mut m, Some(Message::SearchTextAreaKeyInput(KeyEvent::from( KeyCode::Char('w'), @@ -965,100 +965,100 @@ mod test { ); m }, - message: Some(Message::PreviousTarget), + message: Some(Message::PreviousCommand), expect_model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { commands_list_state: ListState::with_selected(ListState::default(), None), search_text_area: { let mut text_area = TextArea::default(); text_area.input(KeyEvent::from(KeyCode::Char('w'))); TextArea_(text_area) }, - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, }, Case { title: "NextHistory", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { current_pane: CurrentPane::History, history_list_state: ListState::with_selected(ListState::default(), Some(0)), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, message: Some(Message::NextHistory), expect_model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { current_pane: CurrentPane::History, history_list_state: ListState::with_selected(ListState::default(), Some(1)), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, }, Case { title: "PreviousHistory", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { current_pane: CurrentPane::History, history_list_state: ListState::with_selected(ListState::default(), Some(0)), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, message: Some(Message::NextHistory), expect_model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { current_pane: CurrentPane::History, history_list_state: ListState::with_selected(ListState::default(), Some(1)), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, }, Case { title: "When the last history is selected and NextHistory is received, it returns to the beginning.", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { current_pane: CurrentPane::History, history_list_state: ListState::with_selected( ListState::default(), Some(2), ), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, message: Some(Message::NextHistory), expect_model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { current_pane: CurrentPane::History, history_list_state: ListState::with_selected( ListState::default(), Some(0), ), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, }, Case { title: "When the first history is selected and PreviousHistory is received, it moves to the last history.", model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { current_pane: CurrentPane::History, history_list_state: ListState::with_selected( ListState::default(), Some(0), ), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, message: Some(Message::PreviousHistory), expect_model: Model { - app_state: AppState::SelectTarget(SelectTargetState { + app_state: AppState::SelectCommand(SelectCommandState { current_pane: CurrentPane::History, history_list_state: ListState::with_selected( ListState::default(), Some(2), ), - ..SelectTargetState::new_for_test() + ..SelectCommandState::new_for_test() }), }, }, diff --git a/src/usecase/tui/ui.rs b/src/usecase/tui/ui.rs index 2cceb4d..c471e6c 100644 --- a/src/usecase/tui/ui.rs +++ b/src/usecase/tui/ui.rs @@ -5,7 +5,7 @@ use std::{ use crate::model::command; -use super::app::{AppState, CurrentPane, Model, SelectTargetState}; +use super::app::{AppState, CurrentPane, Model, SelectCommandState}; use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem}; use ratatui::{ layout::{Constraint, Direction, Layout}, @@ -17,7 +17,7 @@ use ratatui::{ use tui_term::widget::PseudoTerminal; pub fn ui(f: &mut Frame, model: &mut Model) { - if let AppState::SelectTarget(model) = &mut model.app_state { + if let AppState::SelectCommand(model) = &mut model.app_state { let main_and_key_bindings = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Min(3), Constraint::Length(1)]) @@ -30,18 +30,18 @@ pub fn ui(f: &mut Frame, model: &mut Model) { .split(main_and_key_bindings[0]); render_input_block(model, f, main[1]); - let preview_and_targets = Layout::default() + let preview_and_commands = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Percentage(70), Constraint::Percentage(30)]) .split(main[0]); - render_preview_block(model, f, preview_and_targets[0]); + render_preview_block(model, f, preview_and_commands[0]); - let targets = Layout::default() + let commands = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(70), Constraint::Percentage(30)]) - .split(preview_and_targets[1]); - render_targets_block(model, f, targets[0]); - render_history_block(model, f, targets[1]); + .split(preview_and_commands[1]); + render_commands_block(model, f, commands[0]); + render_history_block(model, f, commands[1]); } } @@ -64,11 +64,11 @@ fn color_and_border_style_for_selectable( } // Because the setup process of the terminal and render_widget function need to be done in the same scope, the call of the render_widget function is included. -fn render_preview_block(model: &SelectTargetState, f: &mut Frame, chunk: ratatui::layout::Rect) { - let narrow_down_targets = model.narrow_down_targets(); +fn render_preview_block(model: &SelectCommandState, f: &mut Frame, chunk: ratatui::layout::Rect) { + let narrow_down_commands = model.narrow_down_commands(); let selecting_command = - narrow_down_targets.get(model.commands_list_state.selected().unwrap_or(0)); + narrow_down_commands.get(model.commands_list_state.selected().unwrap_or(0)); let (fg_color_, border_style) = color_and_border_style_for_selectable(model.current_pane.is_main()); @@ -81,7 +81,7 @@ fn render_preview_block(model: &SelectTargetState, f: &mut Frame, chunk: ratatui .title(title) .title_style(TITLE_STYLE); - if !model.get_search_area_text().is_empty() && narrow_down_targets.is_empty() { + if !model.get_search_area_text().is_empty() && narrow_down_commands.is_empty() { f.render_widget(block, chunk); return; } @@ -159,15 +159,15 @@ fn preview_command(file_path: PathBuf, line_number: u32) -> CommandBuilder { cmd } -fn render_targets_block( - model: &mut SelectTargetState, +fn render_commands_block( + model: &mut SelectCommandState, f: &mut Frame, chunk: ratatui::layout::Rect, ) { f.render_stateful_widget( - targets_block( - " 📢 Targets ", - model.narrow_down_targets(), + commands_block( + " 📢 Commands ", + model.narrow_down_commands(), model.current_pane.is_main(), ), chunk, @@ -176,7 +176,7 @@ fn render_targets_block( ); } -fn render_input_block(model: &mut SelectTargetState, f: &mut Frame, chunk: ratatui::layout::Rect) { +fn render_input_block(model: &mut SelectCommandState, f: &mut Frame, chunk: ratatui::layout::Rect) { let (fg_color, border_style) = color_and_border_style_for_selectable(model.current_pane.is_main()); @@ -193,18 +193,18 @@ fn render_input_block(model: &mut SelectTargetState, f: &mut Frame, chunk: ratat model .search_text_area .0 - .set_placeholder_text("Type text to search target"); + .set_placeholder_text("Type text to search command"); f.render_widget(&model.search_text_area.0, chunk); } fn render_history_block( - model: &mut SelectTargetState, + model: &mut SelectCommandState, f: &mut Frame, chunk: ratatui::layout::Rect, ) { f.render_stateful_widget( - targets_block( + commands_block( " 📚 History ", model.get_history(), model.current_pane.is_history(), @@ -215,13 +215,13 @@ fn render_history_block( ); } -fn render_hint_block(model: &mut SelectTargetState, f: &mut Frame, chunk: ratatui::layout::Rect) { +fn render_hint_block(model: &mut SelectCommandState, f: &mut Frame, chunk: ratatui::layout::Rect) { let hint_text = match model.current_pane { CurrentPane::Main => { - "Execute the selected target: | Select target: ↑/↓ | Narrow down target: (type any character) | Move to next tab: | Quit: " + "Execute the selected command: | Select command: ↑/↓ | Narrow down command: (type any character) | Move to next tab: | Quit: " } CurrentPane::History => { - "Execute the selected target: | Select target: ↑/↓ | Move to next tab: | Quit: q/" + "Execute the selected command: | Select command: ↑/↓ | Move to next tab: | Quit: q/" } }; let hint = Span::styled(hint_text, Style::default().fg(FG_COLOR_SELECTED)); @@ -232,16 +232,16 @@ fn render_hint_block(model: &mut SelectTargetState, f: &mut Frame, chunk: ratatu f.render_widget(key_notes_footer, chunk); } -fn targets_block( +fn commands_block( title: &str, - narrowed_down_targets: Vec, + narrowed_down_commands: Vec, is_current: bool, ) -> List<'_> { let (fg_color, border_style) = color_and_border_style_for_selectable(is_current); - let list: Vec = narrowed_down_targets + let list: Vec = narrowed_down_commands .into_iter() - .map(|target| ListItem::new(target.to_string()).style(Style::default())) + .map(|command| ListItem::new(command.to_string()).style(Style::default())) .collect(); List::new(list) From 7af110fd3a1b00e6e8809e46daeb1e8df28cb69f Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 13:13:24 +0900 Subject: [PATCH 24/36] Add a comment tells that the the commands are sorted by executed time descending in the history file. --- src/model/histories.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/model/histories.rs b/src/model/histories.rs index e9791c1..8047160 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -45,6 +45,8 @@ impl Histories { #[derive(Clone, PartialEq, Debug)] pub struct History { pub path: PathBuf, + /// The commands are sorted in descending order of execution time. + /// This means that the first element is the most recently executed command. pub commands: Vec, } From 067a1af59916398cc22c9ca9ef1a33dad3972937 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 13:23:54 +0900 Subject: [PATCH 25/36] format import statements --- src/model/command.rs | 3 +-- src/model/file_util.rs | 1 - src/model/runner_type.rs | 3 +-- src/model/target.rs | 6 ++---- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/model/command.rs b/src/model/command.rs index d50d142..825c317 100644 --- a/src/model/command.rs +++ b/src/model/command.rs @@ -1,6 +1,5 @@ -use std::{fmt, path::PathBuf}; - use super::runner_type; +use std::{fmt, path::PathBuf}; #[derive(PartialEq, Clone, Debug)] pub struct Command { diff --git a/src/model/file_util.rs b/src/model/file_util.rs index e6d1f2b..a1d5f42 100644 --- a/src/model/file_util.rs +++ b/src/model/file_util.rs @@ -1,5 +1,4 @@ use anyhow::{anyhow, Result}; - use std::{fs::read_to_string, path::PathBuf}; pub fn path_to_content(path: PathBuf) -> Result { diff --git a/src/model/runner_type.rs b/src/model/runner_type.rs index 8011c42..cdf21d1 100644 --- a/src/model/runner_type.rs +++ b/src/model/runner_type.rs @@ -1,8 +1,7 @@ +use super::runner; use serde::{Deserialize, Serialize}; use std::fmt; -use super::runner; - #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum RunnerType { diff --git a/src/model/target.rs b/src/model/target.rs index ecd0222..76ed998 100644 --- a/src/model/target.rs +++ b/src/model/target.rs @@ -1,8 +1,6 @@ -use std::path::PathBuf; - -use regex::Regex; - use super::{command, runner_type}; +use regex::Regex; +use std::path::PathBuf; #[derive(Debug, Clone, PartialEq)] pub struct Targets(pub Vec); From a5acdc6bbcff5106f85dad80a73036c55c79e146 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 13:25:28 +0900 Subject: [PATCH 26/36] remove unnecessary comment --- src/model/runner.rs | 2 -- src/usecase/tui/app.rs | 1 - 2 files changed, 3 deletions(-) diff --git a/src/model/runner.rs b/src/model/runner.rs index 6cbfc33..9a8028a 100644 --- a/src/model/runner.rs +++ b/src/model/runner.rs @@ -6,8 +6,6 @@ use std::path::PathBuf; #[allow(dead_code)] #[derive(Debug, Clone, PartialEq)] pub enum Runner { - // TODO(#321): Use associated constants if possible. - // ref: https://doc.rust-lang.org/reference/items/associated-items.html#associated-constants MakeCommand(make::Make), PnpmCommand(pnpm::Pnpm), } diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index db56b08..000fe3c 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -136,7 +136,6 @@ impl Model<'_> { } fn transition_to_should_quit_state(&mut self) { - // TODO: remove mut self.app_state = AppState::ShouldQuit; } From 4687b7ec1c9ddecfbb752dcc2c7f1f158fa96642 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:24:58 +0900 Subject: [PATCH 27/36] make `content_to_include_file_paths` private --- src/model/make.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/make.rs b/src/model/make.rs index d9d5254..9aeea64 100644 --- a/src/model/make.rs +++ b/src/model/make.rs @@ -130,7 +130,7 @@ impl Make { /// The path should be relative path from current directory where make command is executed. /// So the path can be treated as it is. /// NOTE: path include `..` is not supported for now like `include ../c.mk`. -pub fn content_to_include_file_paths(file_content: String) -> Vec { +fn content_to_include_file_paths(file_content: String) -> Vec { let mut result: Vec = Vec::new(); for line in file_content.lines() { let Some(include_files) = line_to_including_file_paths(line.to_string()) else { From 9f46faefafacea94b0ad9dbc09499f61d059af15 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:25:12 +0900 Subject: [PATCH 28/36] delete unnecessary path specification --- src/file/toml.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/file/toml.rs b/src/file/toml.rs index fd874f0..ee51d51 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -115,7 +115,7 @@ pub fn history_file_path() -> Option<(PathBuf, String)> { match env::var("FZF_MAKE_IS_TESTING") { Ok(_) => { // When testing - let cwd = std::env::current_dir().unwrap(); + let cwd = env::current_dir().unwrap(); Some(( cwd.join(PathBuf::from("test_dir")), HISTORY_FILE_NAME.to_string(), From b19cddc92a70da18bf32e6e4c2a1f7d3778d3c7e Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:27:59 +0900 Subject: [PATCH 29/36] make.rs: rename create_makefile to new, new to new_internal --- src/model/make.rs | 28 ++++++++++++++-------------- src/usecase/tui/app.rs | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/model/make.rs b/src/model/make.rs index 9aeea64..9cd7ef7 100644 --- a/src/model/make.rs +++ b/src/model/make.rs @@ -20,32 +20,22 @@ impl Make { format!("make {}", command.name) } - pub fn create_makefile(current_dir: PathBuf) -> Result { + pub fn new(current_dir: PathBuf) -> Result { let Some(makefile_name) = Make::specify_makefile_name(current_dir, ".".to_string()) else { return Err(anyhow!("makefile not found.\n")); }; - Make::new(Path::new(&makefile_name).to_path_buf()) - } - - pub fn to_commands(&self) -> Vec { - let mut result: Vec = vec![]; - result.append(&mut self.targets.0.to_vec()); - for include_file in &self.include_files { - Vec::append(&mut result, &mut include_file.to_commands()); - } - - result + Make::new_internal(Path::new(&makefile_name).to_path_buf()) } // I gave up writing tests using temp_dir because it was too difficult (it was necessary to change the implementation to some extent). // It is not difficult to ensure that it works with manual tests, so I will not do it for now. - fn new(path: PathBuf) -> Result { + fn new_internal(path: PathBuf) -> Result { // If the file path does not exist, the make command cannot be executed in the first place, // so it is not handled here. let file_content = file_util::path_to_content(path.clone())?; let include_files = content_to_include_file_paths(file_content.clone()) .iter() - .map(|included_file_path| Make::new(included_file_path.clone())) + .map(|included_file_path| Make::new_internal(included_file_path.clone())) .filter_map(Result::ok) .collect(); @@ -56,6 +46,16 @@ impl Make { }) } + pub fn to_commands(&self) -> Vec { + let mut result: Vec = vec![]; + result.append(&mut self.targets.0.to_vec()); + for include_file in &self.include_files { + Vec::append(&mut result, &mut include_file.to_commands()); + } + + result + } + fn specify_makefile_name(current_dir: PathBuf, target_path: String) -> Option { // By default, when make looks for the makefile, it tries the following names, in order: GNUmakefile, makefile and Makefile. // https://www.gnu.org/software/make/manual/make.html#Makefile-Names diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index 000fe3c..521165d 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -335,7 +335,7 @@ impl SelectCommandState<'_> { Ok(d) => d, Err(e) => bail!("Failed to get current directory: {}", e), }; - let makefile = match Make::create_makefile(current_dir.clone()) { + let makefile = match Make::new(current_dir.clone()) { Err(e) => return Err(e), Ok(f) => f, }; From 8ed1f59a436f38744265650a5677de87e78d660c Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:30:56 +0900 Subject: [PATCH 30/36] change format to display command: (runner_type) name -> [runner_type] name] --- src/model/command.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/model/command.rs b/src/model/command.rs index 825c317..e405b77 100644 --- a/src/model/command.rs +++ b/src/model/command.rs @@ -12,13 +12,13 @@ pub struct Command { impl Command { pub fn new( runner_type: runner_type::RunnerType, - command_name: String, + name: String, file_name: PathBuf, line_number: u32, ) -> Self { Self { runner_type, - name: command_name, + name, file_name, line_number, } @@ -27,6 +27,6 @@ impl Command { impl fmt::Display for Command { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "({}) {}", self.runner_type, self.name) + write!(f, "[{}] {}", self.runner_type, self.name) } } From b056ea25d423d3c0550b82d47dbd64fd019bbb5d Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:34:02 +0900 Subject: [PATCH 31/36] model/histories.rs: fix comment --- src/model/histories.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/model/histories.rs b/src/model/histories.rs index 8047160..7b5d822 100644 --- a/src/model/histories.rs +++ b/src/model/histories.rs @@ -69,9 +69,10 @@ impl History { } } -/// In the history file, the command has only the name of the command and the runner type. -/// Because its file name where it's defined and file number is variable. -/// So we search them every time fzf-make is launched. +/// In the history file, the command has only the name of the command and the runner type though +/// command::Command has `file_name`, `line_number` as well. +/// Because its file name where it's defined and line number is variable. +/// So we search them every time fzf-make is launched instead of storing them in the history file. #[derive(PartialEq, Clone, Debug)] pub struct HistoryCommand { pub runner_type: runner_type::RunnerType, From 50a85c5e4d3c8cd11602bfdb24dc5133af8cff8f Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:38:53 +0900 Subject: [PATCH 32/36] make some methods private --- src/usecase/tui/app.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index 521165d..6b7234d 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -139,15 +139,15 @@ impl Model<'_> { self.app_state = AppState::ShouldQuit; } - pub fn should_quit(&self) -> bool { + fn should_quit(&self) -> bool { self.app_state == AppState::ShouldQuit } - pub fn is_command_selected(&self) -> bool { + fn is_command_selected(&self) -> bool { matches!(self.app_state, AppState::ExecuteCommand(_)) } - pub fn command_to_execute(&self) -> Option<(runner::Runner, command::Command)> { + fn command_to_execute(&self) -> Option<(runner::Runner, command::Command)> { match &self.app_state { AppState::ExecuteCommand(command) => { let command = command.clone(); From 0e206e96a71208ad49d549c5a765c0a3b3f40432 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:39:02 +0900 Subject: [PATCH 33/36] rename var name --- src/usecase/tui/app.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index 6b7234d..57ab763 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -306,12 +306,12 @@ pub struct SelectCommandState<'a> { impl PartialEq for SelectCommandState<'_> { fn eq(&self, other: &Self) -> bool { - let without_runners = self.current_pane == other.current_pane + let other_than_runners = self.current_pane == other.current_pane && self.search_text_area == other.search_text_area && self.commands_list_state == other.commands_list_state && self.history == other.history && self.history_list_state == other.history_list_state; - if !without_runners { + if !other_than_runners { return false; // Early return for performance } From c655f912606ab00b0836ef9509688862b3e4d1e6 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:42:53 +0900 Subject: [PATCH 34/36] rename: store_history -> create_or_update_history_file --- src/file/toml.rs | 2 +- src/usecase/tui/app.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/file/toml.rs b/src/file/toml.rs index ee51d51..f7659ff 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -135,7 +135,7 @@ pub fn parse_history(content: String) -> Result { Ok(histories) } -pub fn store_history( +pub fn create_or_update_history_file( history_directory_path: PathBuf, history_file_name: String, new_history: histories::Histories, diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index 57ab763..c8ca352 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -543,7 +543,7 @@ impl SelectCommandState<'_> { .append(self.current_dir.clone(), command); // TODO: handle error - let _ = toml::store_history(dir, file_name, all_histories); + let _ = toml::create_or_update_history_file(dir, file_name, all_histories); }; } From 27cfea523c3451be205a04990d316433d54a2a8e Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:49:18 +0900 Subject: [PATCH 35/36] delete unnecessary code --- src/model/runner_type.rs | 2 +- src/usecase/tui/app.rs | 20 -------------------- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/src/model/runner_type.rs b/src/model/runner_type.rs index cdf21d1..967883b 100644 --- a/src/model/runner_type.rs +++ b/src/model/runner_type.rs @@ -20,7 +20,7 @@ impl RunnerType { } None } - RunnerType::Pnpm => todo!(), + RunnerType::Pnpm => todo!("implement and write test"), } } } diff --git a/src/usecase/tui/app.rs b/src/usecase/tui/app.rs index c8ca352..6a2ceb2 100644 --- a/src/usecase/tui/app.rs +++ b/src/usecase/tui/app.rs @@ -577,26 +577,6 @@ impl SelectCommandState<'_> { None } - #[cfg(test)] - // fn init_histories(history_commands: Vec) -> Histories { - // use std::path::Path; - // - // let mut commands: Vec = Vec::new(); - // - // for h in history_commands { - // commands.push(histories::HistoryCommand { - // runner_type: runner_type::RunnerType::Make, - // name: h.name, - // }); - // } - // - // Histories { - // histories: vec![histories::History { - // path: env::current_dir().unwrap().join(Path::new("Test.mk")), - // commands: commands, - // }], - // } - // } #[cfg(test)] fn new_for_test() -> Self { use crate::model::runner_type; From 424bfa8bd2dcb333109f5a48f9a858d08c74fca6 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:50:36 +0900 Subject: [PATCH 36/36] reoder import block --- src/controller/controller_main.rs | 5 ++--- src/main.rs | 4 +--- src/usecase/repeat.rs | 5 ++--- src/usecase/tui/ui.rs | 12 +++++------- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/controller/controller_main.rs b/src/controller/controller_main.rs index 3fafdab..cea82e7 100644 --- a/src/controller/controller_main.rs +++ b/src/controller/controller_main.rs @@ -1,10 +1,9 @@ +use crate::usecase::fzf_make::FzfMake; +use crate::usecase::{fzf_make, help, history, invalid_arg, repeat, usecase_main, version}; use colored::Colorize; use std::sync::Arc; use std::{collections::HashMap, env}; -use crate::usecase::fzf_make::FzfMake; -use crate::usecase::{fzf_make, help, history, invalid_arg, repeat, usecase_main, version}; - pub fn run() { let command_line_args = env::args().collect(); let usecase = args_to_usecase(command_line_args); diff --git a/src/main.rs b/src/main.rs index 58e31e1..b080db2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,13 +2,11 @@ mod controller; mod file; mod model; mod usecase; - -use crate::controller::controller_main; use std::panic; fn main() { let result = panic::catch_unwind(|| { - controller_main::run(); + controller::controller_main::run(); }); match result { Ok(_) => {} diff --git a/src/usecase/repeat.rs b/src/usecase/repeat.rs index 241d1d1..de77ce1 100644 --- a/src/usecase/repeat.rs +++ b/src/usecase/repeat.rs @@ -1,10 +1,9 @@ -use crate::usecase::usecase_main::Usecase; -use anyhow::{anyhow, Result}; - use super::tui::{ app::{AppState, Model}, config, }; +use crate::usecase::usecase_main::Usecase; +use anyhow::{anyhow, Result}; pub struct Repeat; diff --git a/src/usecase/tui/ui.rs b/src/usecase/tui/ui.rs index c471e6c..1d0059f 100644 --- a/src/usecase/tui/ui.rs +++ b/src/usecase/tui/ui.rs @@ -1,11 +1,5 @@ -use std::{ - path::PathBuf, - sync::{Arc, RwLock}, -}; - -use crate::model::command; - use super::app::{AppState, CurrentPane, Model, SelectCommandState}; +use crate::model::command; use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem}; use ratatui::{ layout::{Constraint, Direction, Layout}, @@ -14,6 +8,10 @@ use ratatui::{ widgets::{Block, Borders, List, ListItem, Paragraph, Wrap}, Frame, }; +use std::{ + path::PathBuf, + sync::{Arc, RwLock}, +}; use tui_term::widget::PseudoTerminal; pub fn ui(f: &mut Frame, model: &mut Model) {