From 2ced3f9acc9c15611f468c85aade46d8a2e6f1ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20Plagborg=20Bak=20S=C3=B8rensen?= <57013304+kpbaks@users.noreply.github.com> Date: Tue, 28 Oct 2025 22:41:36 +0100 Subject: [PATCH] fix: disable blame and history popup for untracked files (#2489) * fix: disable blame and history popup for untracked files An untracked file does not have any history data. Right now when you press `B` for the blame popup or the `H` for the history popup you get an empty popup where the title spins endlessly trying to find the file in the commit history, and show relevant information. This commit disables the two actions in the `StatusTreeComponent`, when the selected item is a file which is not tracked by git. --------- Co-authored-by: extrawurst --- CHANGELOG.md | 1 + src/components/status_tree.rs | 87 ++++++++++++++++++++++++----------- 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce1bacbf..b8ded444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * yanking commit ranges no longer generates incorrect dotted range notations, but lists each individual commit [[@naseschwarz](https://github.com/naseschwarz)] (https://github.com/gitui-org/gitui/issues/2576) * print slightly nicer errors when failing to create a directory [[@linkmauve](https://github.com/linkmauve)] (https://github.com/gitui-org/gitui/pull/2728) * When the terminal is insufficient to display all the commands, the cmdbar_bg configuration color does not fully take effect. ([#2347](https://github.com/extrawurst/gitui/issues/2347)) +* disable blame and history popup keybinds for untracked files [[@kpbaks](https://github.com/kpbaks)] ([#2489](https://github.com/gitui-org/gitui/pull/2489)) ## [0.27.0] - 2024-01-14 diff --git a/src/components/status_tree.rs b/src/components/status_tree.rs index 0091cb7c..f04224ec 100644 --- a/src/components/status_tree.rs +++ b/src/components/status_tree.rs @@ -308,6 +308,25 @@ impl StatusTreeComponent { } } } + + fn open_history(&mut self) { + match self.selection_file() { + Some(status_item) + if !matches!( + status_item.status, + StatusItemType::New + ) => + { + self.hide(); + self.queue.push(InternalEvent::OpenPopup( + StackablePopupOpen::FileRevlog(FileRevOpen::new( + status_item.path, + )), + )); + } + _ => {} + } + } } /// Used for drawing the `FileTreeComponent` @@ -395,11 +414,18 @@ impl Component for StatusTreeComponent { out: &mut Vec, force_all: bool, ) -> CommandBlocking { + let available = self.focused || force_all; + let selection = self.selection_file(); + let selected_is_file = selection.is_some(); + let tracked = selection.is_some_and(|s| { + !matches!(s.status, StatusItemType::New) + }); + out.push( CommandInfo::new( strings::commands::navigate_tree(&self.key_config), !self.is_empty(), - self.focused || force_all, + available, ) .order(order::NAV), ); @@ -407,8 +433,8 @@ impl Component for StatusTreeComponent { out.push( CommandInfo::new( strings::commands::blame_file(&self.key_config), - self.selection_file().is_some(), - self.focused || force_all, + selected_is_file && tracked, + available, ) .order(order::RARE_ACTION), ); @@ -418,8 +444,8 @@ impl Component for StatusTreeComponent { strings::commands::open_file_history( &self.key_config, ), - self.selection_file().is_some(), - self.focused || force_all, + selected_is_file && tracked, + available, ) .order(order::RARE_ACTION), ); @@ -427,8 +453,8 @@ impl Component for StatusTreeComponent { out.push( CommandInfo::new( strings::commands::edit_item(&self.key_config), - self.selection_file().is_some(), - self.focused || force_all, + selected_is_file, + available, ) .order(order::RARE_ACTION), ); @@ -436,8 +462,8 @@ impl Component for StatusTreeComponent { out.push( CommandInfo::new( strings::commands::copy_path(&self.key_config), - self.selection_file().is_some(), - self.focused || force_all, + selected_is_file, + available, ) .order(order::RARE_ACTION), ); @@ -445,35 +471,40 @@ impl Component for StatusTreeComponent { CommandBlocking::PassingOn } + #[expect(clippy::cognitive_complexity)] fn event(&mut self, ev: &Event) -> Result { if self.focused { if let Event::Key(e) = ev { return if key_match(e, self.key_config.keys.blame) { - if let Some(status_item) = self.selection_file() { - self.hide(); - self.queue.push(InternalEvent::OpenPopup( - StackablePopupOpen::BlameFile( - BlameFileOpen { - file_path: status_item.path, - commit_id: self.revision, - selection: None, - }, - ), - )); + match self.selection_file() { + Some(status_item) + if !matches!( + status_item.status, + StatusItemType::New + ) => + { + self.hide(); + self.queue.push( + InternalEvent::OpenPopup( + StackablePopupOpen::BlameFile( + BlameFileOpen { + file_path: status_item + .path, + commit_id: self.revision, + selection: None, + }, + ), + ), + ); + } + _ => {} } Ok(EventState::Consumed) } else if key_match( e, self.key_config.keys.file_history, ) { - if let Some(status_item) = self.selection_file() { - self.hide(); - self.queue.push(InternalEvent::OpenPopup( - StackablePopupOpen::FileRevlog( - FileRevOpen::new(status_item.path), - ), - )); - } + self.open_history(); Ok(EventState::Consumed) } else if key_match(e, self.key_config.keys.edit_file) {