From 5f29d2f8a4c44c0376b9fa8c56e01567efe2d77e Mon Sep 17 00:00:00 2001 From: Andrea Berlingieri Date: Sun, 9 Jun 2024 11:21:12 +0200 Subject: [PATCH 01/10] Add Go to line feature for the blame view Add new stackable popup to get a line number to go to from the user Add a new internal event to change the selection in the most recent blame view as a result of using the go to line popup Add new keybinding for the go to line functionality (Shift-L) Add boolean to BlameFilePopup to check whether the view is currently shadowed by the go to line popup and, if so, let the key events bubble up to the go to line popup Add new command definition for the go to line functionality in BlameFilePopup Add clamping of the selected row in set_open_selection --- src/app.rs | 47 +++++++++++++---- src/keys/key_list.rs | 2 + src/popups/blame_file.rs | 28 +++++++++- src/popups/goto_line.rs | 111 +++++++++++++++++++++++++++++++++++++++ src/popups/mod.rs | 2 + src/queue.rs | 4 ++ src/strings.rs | 12 +++++ 7 files changed, 194 insertions(+), 12 deletions(-) create mode 100644 src/popups/goto_line.rs diff --git a/src/app.rs b/src/app.rs index a7f7996235..5ec7d2abab 100644 --- a/src/app.rs +++ b/src/app.rs @@ -10,14 +10,15 @@ use crate::{ options::{Options, SharedOptions}, popup_stack::PopupStack, popups::{ - AppOption, BlameFilePopup, BranchListPopup, CommitPopup, - CompareCommitsPopup, ConfirmPopup, CreateBranchPopup, - ExternalEditorPopup, FetchPopup, FileRevlogPopup, - FuzzyFindPopup, HelpPopup, InspectCommitPopup, - LogSearchPopupPopup, MsgPopup, OptionsPopup, PullPopup, - PushPopup, PushTagsPopup, RenameBranchPopup, ResetPopup, - RevisionFilesPopup, StashMsgPopup, SubmodulesListPopup, - TagCommitPopup, TagListPopup, + AppOption, BlameFileOpen, BlameFilePopup, BranchListPopup, + CommitPopup, CompareCommitsPopup, ConfirmPopup, + CreateBranchPopup, ExternalEditorPopup, FetchPopup, + FileRevlogPopup, FuzzyFindPopup, GotoLinePopup, HelpPopup, + InspectCommitPopup, LogSearchPopupPopup, MsgPopup, + OptionsPopup, PullPopup, PushPopup, PushTagsPopup, + RenameBranchPopup, ResetPopup, RevisionFilesPopup, + StashMsgPopup, SubmodulesListPopup, TagCommitPopup, + TagListPopup, }, queue::{ Action, AppTabs, InternalEvent, NeedsUpdate, Queue, @@ -106,6 +107,7 @@ pub struct App { popup_stack: PopupStack, options: SharedOptions, repo_path_text: String, + goto_line_popup: GotoLinePopup, // "Flags" requires_redraw: Cell, @@ -208,6 +210,7 @@ impl App { stashing_tab: Stashing::new(&env), stashlist_tab: StashList::new(&env), files_tab: FilesTab::new(&env), + goto_line_popup: GotoLinePopup::new(&env), tab: 0, queue: env.queue, theme: env.theme, @@ -495,7 +498,8 @@ impl App { status_tab, files_tab, stashing_tab, - stashlist_tab + stashlist_tab, + goto_line_popup ] ); @@ -526,7 +530,8 @@ impl App { fetch_popup, options_popup, confirm_popup, - msg_popup + msg_popup, + goto_line_popup ] ); @@ -670,6 +675,9 @@ impl App { StackablePopupOpen::CompareCommits(param) => { self.compare_commits_popup.open(param)?; } + StackablePopupOpen::GotoLine => { + self.goto_line_popup.open()?; + } } Ok(()) @@ -872,6 +880,25 @@ impl App { InternalEvent::CommitSearch(options) => { self.revlog.search(options); } + InternalEvent::GotoLine(line) => { + if let Some(popup) = self.popup_stack.pop() { + if let StackablePopupOpen::BlameFile(params) = + popup + { + self.popup_stack.push( + StackablePopupOpen::BlameFile( + BlameFileOpen { + selection: Some(line), + ..params + }, + ), + ) + } + flags.insert( + NeedsUpdate::ALL | NeedsUpdate::COMMANDS, + ); + } + } }; Ok(flags) diff --git a/src/keys/key_list.rs b/src/keys/key_list.rs index a542ef938a..436193ae01 100644 --- a/src/keys/key_list.rs +++ b/src/keys/key_list.rs @@ -123,6 +123,7 @@ pub struct KeysList { pub commit_history_next: GituiKeyEvent, pub commit: GituiKeyEvent, pub newline: GituiKeyEvent, + pub goto_line: GituiKeyEvent, } #[rustfmt::skip] @@ -215,6 +216,7 @@ impl Default for KeysList { commit_history_next: GituiKeyEvent::new(KeyCode::Char('n'), KeyModifiers::CONTROL), commit: GituiKeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL), newline: GituiKeyEvent::new(KeyCode::Enter, KeyModifiers::empty()), + goto_line: GituiKeyEvent::new(KeyCode::Char('L'), KeyModifiers::SHIFT), } } } diff --git a/src/popups/blame_file.rs b/src/popups/blame_file.rs index 34143481fc..fbf73c59fd 100644 --- a/src/popups/blame_file.rs +++ b/src/popups/blame_file.rs @@ -97,6 +97,7 @@ pub struct BlameFilePopup { app_sender: Sender, git_sender: Sender, repo: RepoPathRef, + goto_line_popup_is_open: bool, } impl DrawableComponent for BlameFilePopup { @@ -234,6 +235,16 @@ impl Component for BlameFilePopup { ) .order(1), ); + out.push( + CommandInfo::new( + strings::commands::open_line_number_popup( + &self.key_config, + ), + true, + has_result, + ) + .order(1), + ); } visibility_blocking(self) @@ -243,7 +254,7 @@ impl Component for BlameFilePopup { &mut self, event: &crossterm::event::Event, ) -> Result { - if self.is_visible() { + if self.is_visible() && !self.goto_line_popup_is_open { if let Event::Key(key) = event { if key_match(key, self.key_config.keys.exit_popup) { self.hide_stacked(false); @@ -307,6 +318,16 @@ impl Component for BlameFilePopup { ), )); } + } else if key_match( + key, + self.key_config.keys.goto_line, + ) { + self.goto_line_popup_is_open = true; + self.hide_stacked(true); + self.visible = true; + self.queue.push(InternalEvent::OpenPopup( + StackablePopupOpen::GotoLine, + )); } return Ok(EventState::Consumed); @@ -344,6 +365,7 @@ impl BlameFilePopup { git_sender: env.sender_git.clone(), blame: None, repo: env.repo.clone(), + goto_line_popup_is_open: false, } } @@ -378,6 +400,7 @@ impl BlameFilePopup { ))); self.table_state.get_mut().select(Some(0)); self.visible = true; + self.goto_line_popup_is_open = false; self.update()?; Ok(()) @@ -721,7 +744,8 @@ impl BlameFilePopup { self.open_request.as_ref().and_then(|req| req.selection) { let mut table_state = self.table_state.take(); - table_state.select(Some(selection)); + let max_line_number = self.get_max_line_number(); + table_state.select(Some(selection.min(max_line_number))); self.table_state.set(table_state); } } diff --git a/src/popups/goto_line.rs b/src/popups/goto_line.rs new file mode 100644 index 0000000000..5f53b16138 --- /dev/null +++ b/src/popups/goto_line.rs @@ -0,0 +1,111 @@ +use crate::{ + app::Environment, + components::{ + visibility_blocking, CommandBlocking, CommandInfo, Component, + DrawableComponent, EventState, + }, + keys::{key_match, SharedKeyConfig}, + queue::{InternalEvent, Queue}, + ui::{self, style::SharedTheme}, +}; + +use ratatui::{ + layout::Rect, + widgets::{Block, Clear, Paragraph}, + Frame, +}; + +use anyhow::Result; + +use crossterm::event::{Event, KeyCode}; + +pub struct GotoLinePopup { + visible: bool, + line: String, + key_config: SharedKeyConfig, + queue: Queue, + theme: SharedTheme, +} + +impl GotoLinePopup { + pub fn new(env: &Environment) -> Self { + Self { + visible: false, + line: String::new(), + key_config: env.key_config.clone(), + queue: env.queue.clone(), + theme: env.theme.clone(), + } + } + + pub fn open(&mut self) -> Result<()> { + self.visible = true; + Ok(()) + } +} + +impl Component for GotoLinePopup { + /// + fn commands( + &self, + _out: &mut Vec, + _force_all: bool, + ) -> CommandBlocking { + visibility_blocking(self) + } + + fn is_visible(&self) -> bool { + self.visible + } + + /// + fn event(&mut self, event: &Event) -> Result { + if self.is_visible() { + if let Event::Key(key) = event { + if key_match(key, self.key_config.keys.exit_popup) { + self.visible = false; + self.line.clear(); + self.queue.push(InternalEvent::PopupStackPop) + } else if let KeyCode::Char(c) = key.code { + if c.is_digit(10) { + // I'd assume it's unusual for people to blame + // files with milions of lines + if self.line.len() < 6 { + self.line.push(c) + } + } + } else if let KeyCode::Backspace = key.code { + self.line.pop(); + } else if key_match(key, self.key_config.keys.enter) { + self.visible = false; + if self.line.len() > 0 { + self.queue.push(InternalEvent::GotoLine( + self.line.parse::().unwrap(), + )); + } + self.queue.push(InternalEvent::PopupStackPop); + self.line.clear(); + } + return Ok(EventState::Consumed); + } + } + + Ok(EventState::NotConsumed) + } +} + +impl DrawableComponent for GotoLinePopup { + fn draw(&self, f: &mut Frame, area: Rect) -> Result<()> { + if self.is_visible() { + let input = Paragraph::new(self.line.as_str()) + .style(self.theme.text(true, false)) + .block(Block::bordered().title("Go to Line")); + + let input_area = ui::centered_rect_absolute(15, 3, area); + f.render_widget(Clear, input_area); + f.render_widget(input, input_area); + } + + Ok(()) + } +} diff --git a/src/popups/mod.rs b/src/popups/mod.rs index 2216461ad7..65b8c196d0 100644 --- a/src/popups/mod.rs +++ b/src/popups/mod.rs @@ -8,6 +8,7 @@ mod externaleditor; mod fetch; mod file_revlog; mod fuzzy_find; +mod goto_line; mod help; mod inspect_commit; mod log_search; @@ -34,6 +35,7 @@ pub use externaleditor::ExternalEditorPopup; pub use fetch::FetchPopup; pub use file_revlog::{FileRevOpen, FileRevlogPopup}; pub use fuzzy_find::FuzzyFindPopup; +pub use goto_line::GotoLinePopup; pub use help::HelpPopup; pub use inspect_commit::{InspectCommitOpen, InspectCommitPopup}; pub use log_search::LogSearchPopupPopup; diff --git a/src/queue.rs b/src/queue.rs index 9ee7830bdd..e0da43e11a 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -68,6 +68,8 @@ pub enum StackablePopupOpen { InspectCommit(InspectCommitOpen), /// CompareCommits(InspectCommitOpen), + /// + GotoLine, } pub enum AppTabs { @@ -146,6 +148,8 @@ pub enum InternalEvent { RewordCommit(CommitId), /// CommitSearch(LogFilterSearchOptions), + /// + GotoLine(usize), } /// single threaded simple queue for components to communicate with each other diff --git a/src/strings.rs b/src/strings.rs index 70ca9e3e8f..3d8b1f70d3 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -1300,6 +1300,18 @@ pub mod commands { CMD_GROUP_LOG, ) } + pub fn open_line_number_popup( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Go to Line [{}]", + key_config.get_hint(key_config.keys.goto_line), + ), + "go to a given line number in the blame view", + CMD_GROUP_GENERAL, + ) + } pub fn log_tag_commit( key_config: &SharedKeyConfig, ) -> CommandText { From 9c526de1a96f62ce0cc79e8e0ddb8d760ef86902 Mon Sep 17 00:00:00 2001 From: Andrea Berlingieri Date: Sun, 9 Jun 2024 11:45:12 +0200 Subject: [PATCH 02/10] Fix make check errors --- src/app.rs | 4 ++-- src/popups/goto_line.rs | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/app.rs b/src/app.rs index 5ec7d2abab..26812d634b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -676,7 +676,7 @@ impl App { self.compare_commits_popup.open(param)?; } StackablePopupOpen::GotoLine => { - self.goto_line_popup.open()?; + self.goto_line_popup.open(); } } @@ -892,7 +892,7 @@ impl App { ..params }, ), - ) + ); } flags.insert( NeedsUpdate::ALL | NeedsUpdate::COMMANDS, diff --git a/src/popups/goto_line.rs b/src/popups/goto_line.rs index 5f53b16138..c6b2e8a832 100644 --- a/src/popups/goto_line.rs +++ b/src/popups/goto_line.rs @@ -38,9 +38,8 @@ impl GotoLinePopup { } } - pub fn open(&mut self) -> Result<()> { + pub fn open(&mut self) { self.visible = true; - Ok(()) } } @@ -65,22 +64,22 @@ impl Component for GotoLinePopup { if key_match(key, self.key_config.keys.exit_popup) { self.visible = false; self.line.clear(); - self.queue.push(InternalEvent::PopupStackPop) + self.queue.push(InternalEvent::PopupStackPop); } else if let KeyCode::Char(c) = key.code { - if c.is_digit(10) { + if c.is_ascii_digit() { // I'd assume it's unusual for people to blame // files with milions of lines if self.line.len() < 6 { - self.line.push(c) + self.line.push(c); } } - } else if let KeyCode::Backspace = key.code { + } else if key.code == KeyCode::Backspace { self.line.pop(); } else if key_match(key, self.key_config.keys.enter) { self.visible = false; - if self.line.len() > 0 { + if !self.line.is_empty() { self.queue.push(InternalEvent::GotoLine( - self.line.parse::().unwrap(), + self.line.parse::().expect("This shouldn't happen since the input is constrained to ascii digits only"), )); } self.queue.push(InternalEvent::PopupStackPop); From 55da5d97ea62ed2aefa19f9d47146f209a230048 Mon Sep 17 00:00:00 2001 From: Andrea Berlingieri Date: Sun, 9 Jun 2024 12:07:02 +0200 Subject: [PATCH 03/10] Move goto_line_popup up the components list for proper event handling Remove unnecessary goto_line_popup_is_open boolean --- src/app.rs | 4 ++-- src/popups/blame_file.rs | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/app.rs b/src/app.rs index 26812d634b..bd2de7b642 100644 --- a/src/app.rs +++ b/src/app.rs @@ -474,6 +474,7 @@ impl App { msg_popup, confirm_popup, commit_popup, + goto_line_popup, blame_file_popup, file_revlog_popup, stashmsg_popup, @@ -498,8 +499,7 @@ impl App { status_tab, files_tab, stashing_tab, - stashlist_tab, - goto_line_popup + stashlist_tab ] ); diff --git a/src/popups/blame_file.rs b/src/popups/blame_file.rs index fbf73c59fd..9f4987b6cc 100644 --- a/src/popups/blame_file.rs +++ b/src/popups/blame_file.rs @@ -97,7 +97,6 @@ pub struct BlameFilePopup { app_sender: Sender, git_sender: Sender, repo: RepoPathRef, - goto_line_popup_is_open: bool, } impl DrawableComponent for BlameFilePopup { @@ -254,7 +253,7 @@ impl Component for BlameFilePopup { &mut self, event: &crossterm::event::Event, ) -> Result { - if self.is_visible() && !self.goto_line_popup_is_open { + if self.is_visible() { if let Event::Key(key) = event { if key_match(key, self.key_config.keys.exit_popup) { self.hide_stacked(false); @@ -322,7 +321,6 @@ impl Component for BlameFilePopup { key, self.key_config.keys.goto_line, ) { - self.goto_line_popup_is_open = true; self.hide_stacked(true); self.visible = true; self.queue.push(InternalEvent::OpenPopup( @@ -365,7 +363,6 @@ impl BlameFilePopup { git_sender: env.sender_git.clone(), blame: None, repo: env.repo.clone(), - goto_line_popup_is_open: false, } } @@ -400,7 +397,6 @@ impl BlameFilePopup { ))); self.table_state.get_mut().select(Some(0)); self.visible = true; - self.goto_line_popup_is_open = false; self.update()?; Ok(()) From 6bdab9ddcb2b3d94929ffbba8700ec8c0e25604b Mon Sep 17 00:00:00 2001 From: Andrea Berlingieri Date: Sun, 30 Jun 2024 13:30:16 +0200 Subject: [PATCH 04/10] Preserve BlameProcess in BlamePopup when jumping lines Add Context enum to keep the blame status across line jumps Add a context field to the GotoLinePopup Add blame field to BlameFilePopup Use the blame field in BlameFilePopup.open if not None --- asyncgit/src/blame.rs | 6 ++++-- src/app.rs | 14 ++++++++++---- src/components/revision_files.rs | 1 + src/components/status_tree.rs | 1 + src/popups/blame_file.rs | 30 ++++++++++++++++++++---------- src/popups/file_revlog.rs | 1 + src/popups/goto_line.rs | 8 ++++++-- src/popups/mod.rs | 2 +- src/queue.rs | 15 +++++++++++---- src/ui/syntax_text.rs | 5 ++++- 10 files changed, 59 insertions(+), 24 deletions(-) diff --git a/asyncgit/src/blame.rs b/asyncgit/src/blame.rs index dcbb93aff0..0c99478512 100644 --- a/asyncgit/src/blame.rs +++ b/asyncgit/src/blame.rs @@ -14,7 +14,7 @@ use std::{ }; /// -#[derive(Hash, Clone, PartialEq, Eq)] +#[derive(Hash, Clone, PartialEq, Eq, Debug)] pub struct BlameParams { /// path to the file to blame pub file_path: String, @@ -22,15 +22,17 @@ pub struct BlameParams { pub commit_id: Option, } +#[derive(Debug)] struct Request(R, Option); -#[derive(Default, Clone)] +#[derive(Debug, Default, Clone)] struct LastResult { params: P, result: R, } /// +#[derive(Debug, Clone)] pub struct AsyncBlame { current: Arc>>, last: Arc>>>, diff --git a/src/app.rs b/src/app.rs index bd2de7b642..70cc9698ef 100644 --- a/src/app.rs +++ b/src/app.rs @@ -21,7 +21,7 @@ use crate::{ TagListPopup, }, queue::{ - Action, AppTabs, InternalEvent, NeedsUpdate, Queue, + Action, AppTabs, Context, InternalEvent, NeedsUpdate, Queue, StackablePopupOpen, }, setup_popups, @@ -675,8 +675,8 @@ impl App { StackablePopupOpen::CompareCommits(param) => { self.compare_commits_popup.open(param)?; } - StackablePopupOpen::GotoLine => { - self.goto_line_popup.open(); + StackablePopupOpen::GotoLine(context) => { + self.goto_line_popup.open(Some(context)); } } @@ -880,7 +880,7 @@ impl App { InternalEvent::CommitSearch(options) => { self.revlog.search(options); } - InternalEvent::GotoLine(line) => { + InternalEvent::GotoLine(line, context) => { if let Some(popup) = self.popup_stack.pop() { if let StackablePopupOpen::BlameFile(params) = popup @@ -889,6 +889,12 @@ impl App { StackablePopupOpen::BlameFile( BlameFileOpen { selection: Some(line), + blame: context.and_then(|ctx| { + let Context::Blame( + blame_process, + ) = ctx; + blame_process + }), ..params }, ), diff --git a/src/components/revision_files.rs b/src/components/revision_files.rs index 4c61179e77..99f8bf51cf 100644 --- a/src/components/revision_files.rs +++ b/src/components/revision_files.rs @@ -193,6 +193,7 @@ impl RevisionFilesComponent { file_path: path, commit_id: self.revision.as_ref().map(|c| c.id), selection: None, + blame: None, }), )); diff --git a/src/components/status_tree.rs b/src/components/status_tree.rs index af0cde79ec..d61c8d66c0 100644 --- a/src/components/status_tree.rs +++ b/src/components/status_tree.rs @@ -456,6 +456,7 @@ impl Component for StatusTreeComponent { file_path: status_item.path, commit_id: self.revision, selection: None, + blame: None, }, ), )); diff --git a/src/popups/blame_file.rs b/src/popups/blame_file.rs index 9f4987b6cc..fbf48889c9 100644 --- a/src/popups/blame_file.rs +++ b/src/popups/blame_file.rs @@ -7,7 +7,7 @@ use crate::{ }, keys::{key_match, SharedKeyConfig}, popups::{FileRevOpen, InspectCommitOpen}, - queue::{InternalEvent, Queue, StackablePopupOpen}, + queue::{Context, InternalEvent, Queue, StackablePopupOpen}, string_utils::tabs_to_spaces, strings, ui::{self, style::SharedTheme, AsyncSyntaxJob, SyntaxText}, @@ -35,7 +35,8 @@ static NO_AUTHOR: &str = ""; static MIN_AUTHOR_WIDTH: usize = 3; static MAX_AUTHOR_WIDTH: usize = 20; -struct SyntaxFileBlame { +#[derive(Debug, Clone)] +pub struct SyntaxFileBlame { pub file_blame: FileBlame, pub styled_text: Option, } @@ -54,7 +55,8 @@ impl SyntaxFileBlame { } } -enum BlameProcess { +#[derive(Clone, Debug)] +pub enum BlameProcess { GettingBlame(AsyncBlame), SyntaxHighlighting { unstyled_file_blame: SyntaxFileBlame, @@ -81,6 +83,7 @@ pub struct BlameFileOpen { pub file_path: String, pub commit_id: Option, pub selection: Option, + pub blame: Option, } pub struct BlameFilePopup { @@ -324,7 +327,9 @@ impl Component for BlameFilePopup { self.hide_stacked(true); self.visible = true; self.queue.push(InternalEvent::OpenPopup( - StackablePopupOpen::GotoLine, + StackablePopupOpen::GotoLine(Context::Blame( + self.blame.clone(), + )), )); } @@ -375,6 +380,7 @@ impl BlameFilePopup { file_path: request.file_path, commit_id: request.commit_id, selection: self.get_selection(), + blame: self.blame.clone(), }), )); } @@ -390,11 +396,15 @@ impl BlameFilePopup { file_path: open.file_path, commit_id: open.commit_id, }); - self.blame = - Some(BlameProcess::GettingBlame(AsyncBlame::new( - self.repo.borrow().clone(), - &self.git_sender, - ))); + self.blame = match open.blame { + None => { + Some(BlameProcess::GettingBlame(AsyncBlame::new( + self.repo.borrow().clone(), + &self.git_sender, + ))) + } + blame => blame, + }; self.table_state.get_mut().select(Some(0)); self.visible = true; self.update()?; @@ -457,7 +467,6 @@ impl BlameFilePopup { ), }, ); - self.set_open_selection(); self.highlight_blame_lines(); return Ok(()); @@ -468,6 +477,7 @@ impl BlameFilePopup { } } } + self.set_open_selection(); Ok(()) } diff --git a/src/popups/file_revlog.rs b/src/popups/file_revlog.rs index 1bfc0b266e..c3963e1714 100644 --- a/src/popups/file_revlog.rs +++ b/src/popups/file_revlog.rs @@ -536,6 +536,7 @@ impl Component for FileRevlogPopup { file_path: open_request.file_path, commit_id: self.selected_commit(), selection: None, + blame: None, }, ), )); diff --git a/src/popups/goto_line.rs b/src/popups/goto_line.rs index c6b2e8a832..7e7daf8eb3 100644 --- a/src/popups/goto_line.rs +++ b/src/popups/goto_line.rs @@ -5,7 +5,7 @@ use crate::{ DrawableComponent, EventState, }, keys::{key_match, SharedKeyConfig}, - queue::{InternalEvent, Queue}, + queue::{Context, InternalEvent, Queue}, ui::{self, style::SharedTheme}, }; @@ -25,6 +25,7 @@ pub struct GotoLinePopup { key_config: SharedKeyConfig, queue: Queue, theme: SharedTheme, + context: Option, } impl GotoLinePopup { @@ -35,11 +36,13 @@ impl GotoLinePopup { key_config: env.key_config.clone(), queue: env.queue.clone(), theme: env.theme.clone(), + context: None, } } - pub fn open(&mut self) { + pub fn open(&mut self, context: Option) { self.visible = true; + self.context = context; } } @@ -80,6 +83,7 @@ impl Component for GotoLinePopup { if !self.line.is_empty() { self.queue.push(InternalEvent::GotoLine( self.line.parse::().expect("This shouldn't happen since the input is constrained to ascii digits only"), + self.context.clone() )); } self.queue.push(InternalEvent::PopupStackPop); diff --git a/src/popups/mod.rs b/src/popups/mod.rs index 65b8c196d0..36de54c4f8 100644 --- a/src/popups/mod.rs +++ b/src/popups/mod.rs @@ -25,7 +25,7 @@ mod submodules; mod tag_commit; mod taglist; -pub use blame_file::{BlameFileOpen, BlameFilePopup}; +pub use blame_file::{BlameFileOpen, BlameFilePopup, BlameProcess}; pub use branchlist::BranchListPopup; pub use commit::CommitPopup; pub use compare_commits::CompareCommitsPopup; diff --git a/src/queue.rs b/src/queue.rs index e0da43e11a..8dbedc7efe 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -1,8 +1,8 @@ use crate::{ components::FuzzyFinderTarget, popups::{ - AppOption, BlameFileOpen, FileRevOpen, FileTreeOpen, - InspectCommitOpen, + AppOption, BlameFileOpen, BlameProcess, FileRevOpen, + FileTreeOpen, InspectCommitOpen, }, tabs::StashingOptions, }; @@ -56,6 +56,13 @@ pub enum Action { UndoCommit, } +#[derive(Debug, Clone)] +pub enum Context { + Blame(Option), + //FileView, + //PossibleRange(u32, u32), +} + #[derive(Debug)] pub enum StackablePopupOpen { /// @@ -69,7 +76,7 @@ pub enum StackablePopupOpen { /// CompareCommits(InspectCommitOpen), /// - GotoLine, + GotoLine(Context), } pub enum AppTabs { @@ -149,7 +156,7 @@ pub enum InternalEvent { /// CommitSearch(LogFilterSearchOptions), /// - GotoLine(usize), + GotoLine(usize, Option), } /// single threaded simple queue for components to communicate with each other diff --git a/src/ui/syntax_text.rs b/src/ui/syntax_text.rs index fba036e714..1a160d5817 100644 --- a/src/ui/syntax_text.rs +++ b/src/ui/syntax_text.rs @@ -22,10 +22,12 @@ use syntect::{ use crate::{AsyncAppNotification, SyntaxHighlightProgress}; +#[derive(Debug, Clone)] struct SyntaxLine { items: Vec<(Style, usize, Range)>, } +#[derive(Debug, Clone)] pub struct SyntaxText { text: String, lines: Vec, @@ -211,12 +213,13 @@ fn syntact_style_to_tui(style: &Style) -> ratatui::style::Style { res } +#[derive(Debug)] enum JobState { Request((String, String)), Response(SyntaxText), } -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct AsyncSyntaxJob { state: Arc>>, } From 20ed1d213dc7d6f1769ad833344c10006d14f135 Mon Sep 17 00:00:00 2001 From: Andrea Berlingieri Date: Sat, 6 Jul 2024 18:23:13 +0200 Subject: [PATCH 05/10] Load blame from the last blame file instead of context Remove context and references to it Addres https://github.com/extrawurst/gitui/pull/2262#discussion_r1660621933 Address https://github.com/extrawurst/gitui/pull/2262#discussion_r1660626111 --- src/app.rs | 17 +++++++---------- src/popups/blame_file.rs | 11 +++++------ src/popups/goto_line.rs | 10 +++------- src/popups/mod.rs | 2 +- src/queue.rs | 15 ++++----------- 5 files changed, 20 insertions(+), 35 deletions(-) diff --git a/src/app.rs b/src/app.rs index 70cc9698ef..fbd2277784 100644 --- a/src/app.rs +++ b/src/app.rs @@ -21,7 +21,7 @@ use crate::{ TagListPopup, }, queue::{ - Action, AppTabs, Context, InternalEvent, NeedsUpdate, Queue, + Action, AppTabs, InternalEvent, NeedsUpdate, Queue, StackablePopupOpen, }, setup_popups, @@ -675,8 +675,8 @@ impl App { StackablePopupOpen::CompareCommits(param) => { self.compare_commits_popup.open(param)?; } - StackablePopupOpen::GotoLine(context) => { - self.goto_line_popup.open(Some(context)); + StackablePopupOpen::GotoLine => { + self.goto_line_popup.open(); } } @@ -880,21 +880,18 @@ impl App { InternalEvent::CommitSearch(options) => { self.revlog.search(options); } - InternalEvent::GotoLine(line, context) => { + InternalEvent::GotoLine(line) => { if let Some(popup) = self.popup_stack.pop() { if let StackablePopupOpen::BlameFile(params) = popup { + let blame = + self.blame_file_popup.blame.clone(); self.popup_stack.push( StackablePopupOpen::BlameFile( BlameFileOpen { selection: Some(line), - blame: context.and_then(|ctx| { - let Context::Blame( - blame_process, - ) = ctx; - blame_process - }), + blame, ..params }, ), diff --git a/src/popups/blame_file.rs b/src/popups/blame_file.rs index fbf48889c9..4a15a523aa 100644 --- a/src/popups/blame_file.rs +++ b/src/popups/blame_file.rs @@ -7,7 +7,7 @@ use crate::{ }, keys::{key_match, SharedKeyConfig}, popups::{FileRevOpen, InspectCommitOpen}, - queue::{Context, InternalEvent, Queue, StackablePopupOpen}, + queue::{InternalEvent, Queue, StackablePopupOpen}, string_utils::tabs_to_spaces, strings, ui::{self, style::SharedTheme, AsyncSyntaxJob, SyntaxText}, @@ -96,7 +96,7 @@ pub struct BlameFilePopup { table_state: std::cell::Cell, key_config: SharedKeyConfig, current_height: std::cell::Cell, - blame: Option, + pub blame: Option, app_sender: Sender, git_sender: Sender, repo: RepoPathRef, @@ -327,9 +327,7 @@ impl Component for BlameFilePopup { self.hide_stacked(true); self.visible = true; self.queue.push(InternalEvent::OpenPopup( - StackablePopupOpen::GotoLine(Context::Blame( - self.blame.clone(), - )), + StackablePopupOpen::GotoLine, )); } @@ -751,7 +749,8 @@ impl BlameFilePopup { { let mut table_state = self.table_state.take(); let max_line_number = self.get_max_line_number(); - table_state.select(Some(selection.min(max_line_number))); + table_state + .select(Some(selection.clamp(0, max_line_number))); self.table_state.set(table_state); } } diff --git a/src/popups/goto_line.rs b/src/popups/goto_line.rs index 7e7daf8eb3..5313ac0140 100644 --- a/src/popups/goto_line.rs +++ b/src/popups/goto_line.rs @@ -5,7 +5,7 @@ use crate::{ DrawableComponent, EventState, }, keys::{key_match, SharedKeyConfig}, - queue::{Context, InternalEvent, Queue}, + queue::{InternalEvent, Queue}, ui::{self, style::SharedTheme}, }; @@ -25,7 +25,6 @@ pub struct GotoLinePopup { key_config: SharedKeyConfig, queue: Queue, theme: SharedTheme, - context: Option, } impl GotoLinePopup { @@ -36,13 +35,11 @@ impl GotoLinePopup { key_config: env.key_config.clone(), queue: env.queue.clone(), theme: env.theme.clone(), - context: None, } } - pub fn open(&mut self, context: Option) { + pub fn open(&mut self) { self.visible = true; - self.context = context; } } @@ -82,8 +79,7 @@ impl Component for GotoLinePopup { self.visible = false; if !self.line.is_empty() { self.queue.push(InternalEvent::GotoLine( - self.line.parse::().expect("This shouldn't happen since the input is constrained to ascii digits only"), - self.context.clone() + self.line.parse::()?, )); } self.queue.push(InternalEvent::PopupStackPop); diff --git a/src/popups/mod.rs b/src/popups/mod.rs index 36de54c4f8..65b8c196d0 100644 --- a/src/popups/mod.rs +++ b/src/popups/mod.rs @@ -25,7 +25,7 @@ mod submodules; mod tag_commit; mod taglist; -pub use blame_file::{BlameFileOpen, BlameFilePopup, BlameProcess}; +pub use blame_file::{BlameFileOpen, BlameFilePopup}; pub use branchlist::BranchListPopup; pub use commit::CommitPopup; pub use compare_commits::CompareCommitsPopup; diff --git a/src/queue.rs b/src/queue.rs index 8dbedc7efe..e0da43e11a 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -1,8 +1,8 @@ use crate::{ components::FuzzyFinderTarget, popups::{ - AppOption, BlameFileOpen, BlameProcess, FileRevOpen, - FileTreeOpen, InspectCommitOpen, + AppOption, BlameFileOpen, FileRevOpen, FileTreeOpen, + InspectCommitOpen, }, tabs::StashingOptions, }; @@ -56,13 +56,6 @@ pub enum Action { UndoCommit, } -#[derive(Debug, Clone)] -pub enum Context { - Blame(Option), - //FileView, - //PossibleRange(u32, u32), -} - #[derive(Debug)] pub enum StackablePopupOpen { /// @@ -76,7 +69,7 @@ pub enum StackablePopupOpen { /// CompareCommits(InspectCommitOpen), /// - GotoLine(Context), + GotoLine, } pub enum AppTabs { @@ -156,7 +149,7 @@ pub enum InternalEvent { /// CommitSearch(LogFilterSearchOptions), /// - GotoLine(usize, Option), + GotoLine(usize), } /// single threaded simple queue for components to communicate with each other From e1291f09728e277064b6981f5d55ba85ff9c7293 Mon Sep 17 00:00:00 2001 From: Andrea Berlingieri Date: Tue, 16 Jul 2024 22:39:24 +0200 Subject: [PATCH 06/10] Add a BlameRequest enum for re-using blame data in Goto line Modiy uses of BlameFileOpen accordingly Make the blame field of BlameFilePopup private again --- src/app.rs | 14 ++++++-------- src/components/revision_files.rs | 4 ++-- src/components/status_tree.rs | 4 ++-- src/popups/blame_file.rs | 22 +++++++++++++--------- src/popups/file_revlog.rs | 4 ++-- src/popups/mod.rs | 2 +- 6 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/app.rs b/src/app.rs index fbd2277784..9ac58004a5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -10,11 +10,11 @@ use crate::{ options::{Options, SharedOptions}, popup_stack::PopupStack, popups::{ - AppOption, BlameFileOpen, BlameFilePopup, BranchListPopup, - CommitPopup, CompareCommitsPopup, ConfirmPopup, - CreateBranchPopup, ExternalEditorPopup, FetchPopup, - FileRevlogPopup, FuzzyFindPopup, GotoLinePopup, HelpPopup, - InspectCommitPopup, LogSearchPopupPopup, MsgPopup, + AppOption, BlameFileOpen, BlameFilePopup, BlameRequest, + BranchListPopup, CommitPopup, CompareCommitsPopup, + ConfirmPopup, CreateBranchPopup, ExternalEditorPopup, + FetchPopup, FileRevlogPopup, FuzzyFindPopup, GotoLinePopup, + HelpPopup, InspectCommitPopup, LogSearchPopupPopup, MsgPopup, OptionsPopup, PullPopup, PushPopup, PushTagsPopup, RenameBranchPopup, ResetPopup, RevisionFilesPopup, StashMsgPopup, SubmodulesListPopup, TagCommitPopup, @@ -885,13 +885,11 @@ impl App { if let StackablePopupOpen::BlameFile(params) = popup { - let blame = - self.blame_file_popup.blame.clone(); self.popup_stack.push( StackablePopupOpen::BlameFile( BlameFileOpen { selection: Some(line), - blame, + blame: BlameRequest::KeepExisting, ..params }, ), diff --git a/src/components/revision_files.rs b/src/components/revision_files.rs index 99f8bf51cf..ea23eca0c3 100644 --- a/src/components/revision_files.rs +++ b/src/components/revision_files.rs @@ -6,7 +6,7 @@ use super::{ use crate::{ app::Environment, keys::{key_match, SharedKeyConfig}, - popups::{BlameFileOpen, FileRevOpen}, + popups::{BlameFileOpen, BlameRequest, FileRevOpen}, queue::{InternalEvent, Queue, StackablePopupOpen}, strings::{self, order, symbol}, try_or_popup, @@ -193,7 +193,7 @@ impl RevisionFilesComponent { file_path: path, commit_id: self.revision.as_ref().map(|c| c.id), selection: None, - blame: None, + blame: BlameRequest::StartNew, }), )); diff --git a/src/components/status_tree.rs b/src/components/status_tree.rs index d61c8d66c0..a6a34efe24 100644 --- a/src/components/status_tree.rs +++ b/src/components/status_tree.rs @@ -9,7 +9,7 @@ use crate::{ app::Environment, components::{CommandInfo, Component, EventState}, keys::{key_match, SharedKeyConfig}, - popups::{BlameFileOpen, FileRevOpen}, + popups::{BlameFileOpen, BlameRequest, FileRevOpen}, queue::{InternalEvent, NeedsUpdate, Queue, StackablePopupOpen}, strings::{self, order}, ui::{self, style::SharedTheme}, @@ -456,7 +456,7 @@ impl Component for StatusTreeComponent { file_path: status_item.path, commit_id: self.revision, selection: None, - blame: None, + blame: BlameRequest::StartNew, }, ), )); diff --git a/src/popups/blame_file.rs b/src/popups/blame_file.rs index 4a15a523aa..3ebe3e5dbc 100644 --- a/src/popups/blame_file.rs +++ b/src/popups/blame_file.rs @@ -78,12 +78,18 @@ impl BlameProcess { } } +#[derive(Clone, Debug)] +pub enum BlameRequest { + StartNew, + KeepExisting, +} + #[derive(Clone, Debug)] pub struct BlameFileOpen { pub file_path: String, pub commit_id: Option, pub selection: Option, - pub blame: Option, + pub blame: BlameRequest, } pub struct BlameFilePopup { @@ -96,7 +102,7 @@ pub struct BlameFilePopup { table_state: std::cell::Cell, key_config: SharedKeyConfig, current_height: std::cell::Cell, - pub blame: Option, + blame: Option, app_sender: Sender, git_sender: Sender, repo: RepoPathRef, @@ -378,7 +384,7 @@ impl BlameFilePopup { file_path: request.file_path, commit_id: request.commit_id, selection: self.get_selection(), - blame: self.blame.clone(), + blame: BlameRequest::KeepExisting, }), )); } @@ -394,15 +400,13 @@ impl BlameFilePopup { file_path: open.file_path, commit_id: open.commit_id, }); - self.blame = match open.blame { - None => { + if matches!(open.blame, BlameRequest::StartNew) { + self.blame = Some(BlameProcess::GettingBlame(AsyncBlame::new( self.repo.borrow().clone(), &self.git_sender, - ))) - } - blame => blame, - }; + ))); + } self.table_state.get_mut().select(Some(0)); self.visible = true; self.update()?; diff --git a/src/popups/file_revlog.rs b/src/popups/file_revlog.rs index c3963e1714..011fed22e7 100644 --- a/src/popups/file_revlog.rs +++ b/src/popups/file_revlog.rs @@ -28,7 +28,7 @@ use ratatui::{ Frame, }; -use super::{BlameFileOpen, InspectCommitOpen}; +use super::{BlameFileOpen, BlameRequest, InspectCommitOpen}; const SLICE_SIZE: usize = 1200; @@ -536,7 +536,7 @@ impl Component for FileRevlogPopup { file_path: open_request.file_path, commit_id: self.selected_commit(), selection: None, - blame: None, + blame: BlameRequest::StartNew, }, ), )); diff --git a/src/popups/mod.rs b/src/popups/mod.rs index 65b8c196d0..7b148f72d2 100644 --- a/src/popups/mod.rs +++ b/src/popups/mod.rs @@ -25,7 +25,7 @@ mod submodules; mod tag_commit; mod taglist; -pub use blame_file::{BlameFileOpen, BlameFilePopup}; +pub use blame_file::{BlameFileOpen, BlameFilePopup, BlameRequest}; pub use branchlist::BranchListPopup; pub use commit::CommitPopup; pub use compare_commits::CompareCommitsPopup; From 4855ca6b677e24f6c75dbcc0dca9f1ce262b8b51 Mon Sep 17 00:00:00 2001 From: Andrea Berlingieri Date: Sat, 7 Sep 2024 16:57:04 +0200 Subject: [PATCH 07/10] Add input validation to Go to line popup Add a context struct for the go to line popup to keep the max line number allowed Add support for negative values for the go to line popup input (go to the -n-th to last line) Make the go to line input box red when invalid values are provided Add an error message to the Go to line popup when invalid values are used Allow arbitrarily large values in the Go to line input box --- src/app.rs | 4 +- src/popups/blame_file.rs | 8 +++- src/popups/goto_line.rs | 80 +++++++++++++++++++++++++++++++--------- src/popups/mod.rs | 2 +- src/queue.rs | 4 +- 5 files changed, 74 insertions(+), 24 deletions(-) diff --git a/src/app.rs b/src/app.rs index 9ac58004a5..aad0de7236 100644 --- a/src/app.rs +++ b/src/app.rs @@ -675,8 +675,8 @@ impl App { StackablePopupOpen::CompareCommits(param) => { self.compare_commits_popup.open(param)?; } - StackablePopupOpen::GotoLine => { - self.goto_line_popup.open(); + StackablePopupOpen::GotoLine(param) => { + self.goto_line_popup.open(param); } } diff --git a/src/popups/blame_file.rs b/src/popups/blame_file.rs index 3ebe3e5dbc..433a62321e 100644 --- a/src/popups/blame_file.rs +++ b/src/popups/blame_file.rs @@ -30,6 +30,8 @@ use ratatui::{ }; use std::path::Path; +use super::{goto_line::GotoLineContext, GotoLineOpen}; + static NO_COMMIT_ID: &str = "0000000"; static NO_AUTHOR: &str = ""; static MIN_AUTHOR_WIDTH: usize = 3; @@ -333,7 +335,11 @@ impl Component for BlameFilePopup { self.hide_stacked(true); self.visible = true; self.queue.push(InternalEvent::OpenPopup( - StackablePopupOpen::GotoLine, + StackablePopupOpen::GotoLine(GotoLineOpen { + context: GotoLineContext { + max_line: self.get_max_line_number(), + }, + }), )); } diff --git a/src/popups/goto_line.rs b/src/popups/goto_line.rs index 5313ac0140..39d6625c87 100644 --- a/src/popups/goto_line.rs +++ b/src/popups/goto_line.rs @@ -11,6 +11,7 @@ use crate::{ use ratatui::{ layout::Rect, + style::{Color, Style}, widgets::{Block, Clear, Paragraph}, Frame, }; @@ -19,27 +20,44 @@ use anyhow::Result; use crossterm::event::{Event, KeyCode}; +#[derive(Debug)] +pub struct GotoLineContext { + pub max_line: usize, +} + +#[derive(Debug)] +pub struct GotoLineOpen { + pub context: GotoLineContext, +} + pub struct GotoLinePopup { visible: bool, - line: String, + input: String, + line_number: usize, key_config: SharedKeyConfig, queue: Queue, theme: SharedTheme, + invalid_input: bool, + context: GotoLineContext, } impl GotoLinePopup { pub fn new(env: &Environment) -> Self { Self { visible: false, - line: String::new(), + input: String::new(), key_config: env.key_config.clone(), queue: env.queue.clone(), theme: env.theme.clone(), + invalid_input: false, + context: GotoLineContext { max_line: 0 }, + line_number: 0, } } - pub fn open(&mut self) { + pub fn open(&mut self, open: GotoLineOpen) { self.visible = true; + self.context = open.context; } } @@ -63,32 +81,53 @@ impl Component for GotoLinePopup { if let Event::Key(key) = event { if key_match(key, self.key_config.keys.exit_popup) { self.visible = false; - self.line.clear(); + self.input.clear(); self.queue.push(InternalEvent::PopupStackPop); } else if let KeyCode::Char(c) = key.code { - if c.is_ascii_digit() { - // I'd assume it's unusual for people to blame - // files with milions of lines - if self.line.len() < 6 { - self.line.push(c); - } + if c.is_ascii_digit() || c == '-' { + self.input.push(c); } } else if key.code == KeyCode::Backspace { - self.line.pop(); + self.input.pop(); } else if key_match(key, self.key_config.keys.enter) { self.visible = false; - if !self.line.is_empty() { + if self.invalid_input { + self.queue.push(InternalEvent::ShowErrorMsg( + format!("Invalid input: only numbers between -{0} and {0} (included) are allowed",self.context.max_line)) + , + ); + } else if !self.input.is_empty() { self.queue.push(InternalEvent::GotoLine( - self.line.parse::()?, + self.line_number, )); } self.queue.push(InternalEvent::PopupStackPop); - self.line.clear(); + self.input.clear(); + self.invalid_input = false; + } + } + match self.input.parse::() { + Ok(input) => { + if input.unsigned_abs() > self.context.max_line { + self.invalid_input = true; + } else { + self.invalid_input = false; + self.line_number = if input > 0 { + input.unsigned_abs() + } else { + self.context.max_line + - input.unsigned_abs() + } + } + } + Err(_) => { + if !self.input.is_empty() { + self.invalid_input = true; + } } - return Ok(EventState::Consumed); } + return Ok(EventState::Consumed); } - Ok(EventState::NotConsumed) } } @@ -96,8 +135,13 @@ impl Component for GotoLinePopup { impl DrawableComponent for GotoLinePopup { fn draw(&self, f: &mut Frame, area: Rect) -> Result<()> { if self.is_visible() { - let input = Paragraph::new(self.line.as_str()) - .style(self.theme.text(true, false)) + let style = if self.invalid_input { + Style::default().fg(Color::Red) + } else { + self.theme.text(true, false) + }; + let input = Paragraph::new(self.input.as_str()) + .style(style) .block(Block::bordered().title("Go to Line")); let input_area = ui::centered_rect_absolute(15, 3, area); diff --git a/src/popups/mod.rs b/src/popups/mod.rs index 7b148f72d2..340189baea 100644 --- a/src/popups/mod.rs +++ b/src/popups/mod.rs @@ -35,7 +35,7 @@ pub use externaleditor::ExternalEditorPopup; pub use fetch::FetchPopup; pub use file_revlog::{FileRevOpen, FileRevlogPopup}; pub use fuzzy_find::FuzzyFindPopup; -pub use goto_line::GotoLinePopup; +pub use goto_line::{GotoLineOpen, GotoLinePopup}; pub use help::HelpPopup; pub use inspect_commit::{InspectCommitOpen, InspectCommitPopup}; pub use log_search::LogSearchPopupPopup; diff --git a/src/queue.rs b/src/queue.rs index e0da43e11a..16847b5fbb 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -2,7 +2,7 @@ use crate::{ components::FuzzyFinderTarget, popups::{ AppOption, BlameFileOpen, FileRevOpen, FileTreeOpen, - InspectCommitOpen, + GotoLineOpen, InspectCommitOpen, }, tabs::StashingOptions, }; @@ -69,7 +69,7 @@ pub enum StackablePopupOpen { /// CompareCommits(InspectCommitOpen), /// - GotoLine, + GotoLine(GotoLineOpen), } pub enum AppTabs { From 56732c20f17be5bb08b6d1b13a82a2cf7935dc83 Mon Sep 17 00:00:00 2001 From: Andrea Berlingieri Date: Wed, 18 Sep 2024 18:02:09 +0200 Subject: [PATCH 08/10] Fix up negative indexing and add changelog entry --- CHANGELOG.md | 4 ++++ src/popups/goto_line.rs | 18 ++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2593b46b7c..b7781ec49d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixes * respect env vars like `GIT_CONFIG_GLOBAL` ([#2298](https://github.com/extrawurst/gitui/issues/2298)) +### Added + +* Add "go to line" command for the blame view [[@andrea-berling](https://github.com/andrea-berling)] ([#2262](https://github.com/extrawurst/gitui/pull/2262)) + ## [0.26.3] - 2024-06-02 ### Breaking Changes diff --git a/src/popups/goto_line.rs b/src/popups/goto_line.rs index 39d6625c87..78f70fe05e 100644 --- a/src/popups/goto_line.rs +++ b/src/popups/goto_line.rs @@ -93,7 +93,7 @@ impl Component for GotoLinePopup { self.visible = false; if self.invalid_input { self.queue.push(InternalEvent::ShowErrorMsg( - format!("Invalid input: only numbers between -{0} and {0} (included) are allowed",self.context.max_line)) + format!("Invalid input: only numbers between -{} and {} (included) are allowed (-1 means denotes the last line, -2 denotes the second to last line, and so on)",self.context.max_line + 1, self.context.max_line)) , ); } else if !self.input.is_empty() { @@ -108,15 +108,21 @@ impl Component for GotoLinePopup { } match self.input.parse::() { Ok(input) => { - if input.unsigned_abs() > self.context.max_line { + let mut max_value_allowed_abs = + self.context.max_line; + // negative indices are 1 based + if input < 0 { + max_value_allowed_abs += 1; + } + let input_abs = input.unsigned_abs(); + if input_abs > max_value_allowed_abs { self.invalid_input = true; } else { self.invalid_input = false; - self.line_number = if input > 0 { - input.unsigned_abs() + self.line_number = if input >= 0 { + input_abs } else { - self.context.max_line - - input.unsigned_abs() + max_value_allowed_abs - input_abs } } } From bea05c22cbc05adaae7d349a02dd5a635ad45aa0 Mon Sep 17 00:00:00 2001 From: Andrea Berlingieri Date: Tue, 1 Oct 2024 20:35:08 +0200 Subject: [PATCH 09/10] Prevent GotoLine popup from opening before blame load and fix typo --- src/popups/blame_file.rs | 30 +++++++++++++++++++++--------- src/popups/goto_line.rs | 2 +- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/popups/blame_file.rs b/src/popups/blame_file.rs index 433a62321e..b3e04ec8a4 100644 --- a/src/popups/blame_file.rs +++ b/src/popups/blame_file.rs @@ -332,15 +332,27 @@ impl Component for BlameFilePopup { key, self.key_config.keys.goto_line, ) { - self.hide_stacked(true); - self.visible = true; - self.queue.push(InternalEvent::OpenPopup( - StackablePopupOpen::GotoLine(GotoLineOpen { - context: GotoLineContext { - max_line: self.get_max_line_number(), - }, - }), - )); + let maybe_blame_result = &self + .blame + .as_ref() + .and_then(|blame| blame.result()); + if maybe_blame_result.is_some() { + let max_line = maybe_blame_result + .expect("This can not be None") + .lines() + .len() - 1; + self.hide_stacked(true); + self.visible = true; + self.queue.push(InternalEvent::OpenPopup( + StackablePopupOpen::GotoLine( + GotoLineOpen { + context: GotoLineContext { + max_line, + }, + }, + ), + )); + } } return Ok(EventState::Consumed); diff --git a/src/popups/goto_line.rs b/src/popups/goto_line.rs index 78f70fe05e..f6b860ea62 100644 --- a/src/popups/goto_line.rs +++ b/src/popups/goto_line.rs @@ -93,7 +93,7 @@ impl Component for GotoLinePopup { self.visible = false; if self.invalid_input { self.queue.push(InternalEvent::ShowErrorMsg( - format!("Invalid input: only numbers between -{} and {} (included) are allowed (-1 means denotes the last line, -2 denotes the second to last line, and so on)",self.context.max_line + 1, self.context.max_line)) + format!("Invalid input: only numbers between -{} and {} (included) are allowed (-1 denotes the last line, -2 denotes the second to last line, and so on)",self.context.max_line + 1, self.context.max_line)) , ); } else if !self.input.is_empty() { From 583eb7cf8de1832791c4e33c1e6a3c61801174a3 Mon Sep 17 00:00:00 2001 From: Andrea Berlingieri Date: Sat, 22 Mar 2025 19:12:15 +0100 Subject: [PATCH 10/10] Add hints for the Go To Line popup --- src/popups/goto_line.rs | 24 ++++++++++++++++++++++-- src/strings.rs | 11 +++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/popups/goto_line.rs b/src/popups/goto_line.rs index f6b860ea62..f900b2aa14 100644 --- a/src/popups/goto_line.rs +++ b/src/popups/goto_line.rs @@ -6,6 +6,7 @@ use crate::{ }, keys::{key_match, SharedKeyConfig}, queue::{InternalEvent, Queue}, + strings, ui::{self, style::SharedTheme}, }; @@ -65,9 +66,28 @@ impl Component for GotoLinePopup { /// fn commands( &self, - _out: &mut Vec, - _force_all: bool, + out: &mut Vec, + force_all: bool, ) -> CommandBlocking { + if self.is_visible() || force_all { + out.push( + CommandInfo::new( + strings::commands::close_popup(&self.key_config), + true, + true, + ) + .order(1), + ); + out.push( + CommandInfo::new( + strings::commands::goto_line(&self.key_config), + true, + true, + ) + .order(1), + ); + } + visibility_blocking(self) } diff --git a/src/strings.rs b/src/strings.rs index 53787ce618..62a2c6e564 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -1882,4 +1882,15 @@ pub mod commands { CMD_GROUP_LOG, ) } + + pub fn goto_line(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Go To Line [{}]", + key_config.get_hint(key_config.keys.enter), + ), + "Go to the given line", + CMD_GROUP_GENERAL, + ) + } }