diff --git a/CHANGELOG.md b/CHANGELOG.md index ba522f4f..f11631a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * submodules support ([#1087](https://github.com/extrawurst/gitui/issues/1087)) * remember tab between app starts ([#1338](https://github.com/extrawurst/gitui/issues/1338)) +* commit msg history ([#1345](https://github.com/extrawurst/gitui/issues/1345)) * customizable `cmdbar_bg` theme color & screen spanning selected line bg [[@gigitsu](https://github.com/gigitsu)] ([#1299](https://github.com/extrawurst/gitui/pull/1299)) * word motions to text input [[@Rodrigodd](https://github.com/Rodrigodd)] ([#1256](https://github.com/extrawurst/gitui/issues/1256)) * file blame at right revision from commit-details [[@heiskane](https://github.com/heiskane)] ([#1122](https://github.com/extrawurst/gitui/issues/1122)) diff --git a/src/app.rs b/src/app.rs index b443a580..894373bc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -133,6 +133,7 @@ impl App { queue.clone(), theme.clone(), key_config.clone(), + options.clone(), ), blame_file_popup: BlameFileComponent::new( &repo, diff --git a/src/components/commit.rs b/src/components/commit.rs index 5128d99c..8f5c193d 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -5,6 +5,7 @@ use super::{ }; use crate::{ keys::{key_match, SharedKeyConfig}, + options::SharedOptions, queue::{InternalEvent, NeedsUpdate, Queue}, strings, try_or_popup, ui::style::SharedTheme, @@ -51,6 +52,8 @@ pub struct CommitComponent { git_branch_name: cached::BranchName, commit_template: Option, theme: SharedTheme, + commit_msg_history_idx: usize, + options: SharedOptions, } const FIRST_LINE_LIMIT: usize = 50; @@ -62,6 +65,7 @@ impl CommitComponent { queue: Queue, theme: SharedTheme, key_config: SharedKeyConfig, + options: SharedOptions, ) -> Self { Self { queue, @@ -78,6 +82,8 @@ impl CommitComponent { commit_template: None, theme, repo, + commit_msg_history_idx: 0, + options, } } @@ -186,6 +192,11 @@ impl CommitComponent { self.commit_with_msg(msg)?, CommitResult::ComitDone ) { + self.options + .borrow_mut() + .add_commit_msg(self.input.get_text()); + self.commit_msg_history_idx = 0; + self.hide(); self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); self.input.clear(); @@ -328,6 +339,14 @@ impl Component for CommitComponent { true, true, )); + + out.push(CommandInfo::new( + strings::commands::commit_next_msg_from_history( + &self.key_config, + ), + true, + true, + )); } visibility_blocking(self) @@ -362,6 +381,18 @@ impl Component for CommitComponent { InternalEvent::OpenExternalEditor(None), ); self.hide(); + } else if key_match( + e, + self.key_config.keys.commit_history_next, + ) { + if let Some(msg) = self + .options + .borrow() + .commit_msg(self.commit_msg_history_idx) + { + self.input.set_text(msg); + self.commit_msg_history_idx += 1; + } } else { } // stop key event propagation @@ -424,6 +455,7 @@ impl Component for CommitComponent { } }; + self.commit_msg_history_idx = 0; self.input.show()?; Ok(()) diff --git a/src/keys/key_list.rs b/src/keys/key_list.rs index b54ad80d..24c66e95 100644 --- a/src/keys/key_list.rs +++ b/src/keys/key_list.rs @@ -111,6 +111,7 @@ pub struct KeysList { pub view_submodules: GituiKeyEvent, pub view_submodule_parent: GituiKeyEvent, pub update_submodule: GituiKeyEvent, + pub commit_history_next: GituiKeyEvent, } #[rustfmt::skip] @@ -192,6 +193,7 @@ impl Default for KeysList { view_submodules: GituiKeyEvent::new(KeyCode::Char('S'), KeyModifiers::SHIFT), view_submodule_parent: GituiKeyEvent::new(KeyCode::Char('p'), KeyModifiers::empty()), update_submodule: GituiKeyEvent::new(KeyCode::Char('u'), KeyModifiers::empty()), + commit_history_next: GituiKeyEvent::new(KeyCode::Char('n'), KeyModifiers::CONTROL), } } } diff --git a/src/keys/key_list_file.rs b/src/keys/key_list_file.rs index 4d1a1618..9ac19e4f 100644 --- a/src/keys/key_list_file.rs +++ b/src/keys/key_list_file.rs @@ -82,6 +82,7 @@ pub struct KeysListFile { pub view_submodules: Option, pub view_submodule_parent: Option, pub update_dubmodule: Option, + pub commit_history_next: Option, } impl KeysListFile { @@ -172,6 +173,7 @@ impl KeysListFile { view_submodules: self.view_submodules.unwrap_or(default.view_submodules), view_submodule_parent: self.view_submodule_parent.unwrap_or(default.view_submodule_parent), update_submodule: self.update_dubmodule.unwrap_or(default.update_submodule), + commit_history_next: self.commit_history_next.unwrap_or(default.commit_history_next), } } } diff --git a/src/options.rs b/src/options.rs index 2aba12d5..f51e4501 100644 --- a/src/options.rs +++ b/src/options.rs @@ -16,13 +16,16 @@ use std::{ rc::Rc, }; -#[derive(Default, Copy, Clone, Serialize, Deserialize)] +#[derive(Default, Clone, Serialize, Deserialize)] struct OptionsData { pub tab: usize, pub diff: DiffOptions, pub status_show_untracked: Option, + pub commit_msgs: Vec, } +const COMMIT_MSG_HISTRY_LENGTH: usize = 20; + #[derive(Clone)] pub struct Options { repo: RepoPathRef, @@ -93,6 +96,26 @@ impl Options { self.save(); } + pub fn add_commit_msg(&mut self, msg: &str) { + self.data.commit_msgs.push(msg.to_owned()); + while self.data.commit_msgs.len() > COMMIT_MSG_HISTRY_LENGTH { + self.data.commit_msgs.remove(0); + } + self.save(); + } + + pub fn commit_msg(&self, idx: usize) -> Option { + if self.data.commit_msgs.is_empty() { + None + } else { + Some( + self.data.commit_msgs + [idx % self.data.commit_msgs.len()] + .to_string(), + ) + } + } + fn save(&self) { if let Err(e) = self.save_failable() { log::error!("options save error: {}", e); diff --git a/src/strings.rs b/src/strings.rs index 61470799..bb76aca2 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -837,6 +837,19 @@ pub mod commands { CMD_GROUP_COMMIT, ) } + pub fn commit_next_msg_from_history( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Previous Msg [{}]", + key_config + .get_hint(key_config.keys.commit_history_next), + ), + "use previous commit message from history", + CMD_GROUP_COMMIT, + ) + } pub fn commit_enter(key_config: &SharedKeyConfig) -> CommandText { CommandText::new( format!(