From 87575597bba13989533c1fa6ba8028454cd32336 Mon Sep 17 00:00:00 2001 From: Andrea Berlingieri Date: Sat, 7 Sep 2024 16:57:04 +0200 Subject: [PATCH] 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 {