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 {