diff --git a/src/engine.rs b/src/engine.rs index 82e5d5e4..23f0f892 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,4 +1,4 @@ -use crate::Span; +use crate::{menu::parse_selection_char, Span}; use { crate::{ @@ -596,7 +596,7 @@ impl Reedline { let index = index.min(value.len()); let matching = &value[0..index]; - if !matching.is_empty() { + if !matching.is_empty() && span.end > span.start { self.editor .line_buffer() .replace(span.start..span.end, matching); @@ -749,6 +749,11 @@ impl Reedline { return Ok(EventStatus::Handled); } } + + if let Some(event) = self.replace_history_with_bang() { + return Ok(event); + } + let buffer = self.editor.get_buffer().to_string(); if matches!(self.validator.validate(&buffer), ValidationResult::Complete) { self.hide_hints = true; @@ -1058,6 +1063,39 @@ impl Reedline { } } + /// Insert in the buffer a history input where the bang character is found + fn replace_history_with_bang(&mut self) -> Option { + let buffer = self.editor.get_buffer(); + let (string, index) = parse_selection_char(buffer, &'!'); + + let history_result = index.and_then(|(index, size)| { + self.history + .iter_chronologic() + .rev() + .nth(index) + .map(|history| { + let start = string.len(); + let end = start + size; + let span = Span { start, end }; + + (span, history.clone()) + }) + }); + + if let Some((span, history)) = history_result { + self.editor + .line_buffer() + .replace(span.start..span.end, &history); + + let offset = self.editor.line_buffer().len(); + self.editor.line_buffer().set_insertion_point(offset); + + Some(EventStatus::Handled) + } else { + None + } + } + /// Repaint logic for the history reverse search /// /// Overwrites the prompt indicator and highlights the search string diff --git a/src/menu/history_menu.rs b/src/menu/history_menu.rs index e72da75b..8f717e86 100644 --- a/src/menu/history_menu.rs +++ b/src/menu/history_menu.rs @@ -1,4 +1,4 @@ -use super::{Menu, MenuEvent, MenuTextStyle}; +use super::{parse_selection_char, Menu, MenuEvent, MenuTextStyle}; use crate::{ painter::{estimate_single_line_wraps, Painter}, Completer, History, LineBuffer, Span, @@ -39,9 +39,9 @@ pub struct HistoryMenu { page_size: usize, /// Menu marker displayed when the menu is active marker: String, - /// Character that will start a selection via a number. E.g let:5 will select + /// Character that will start a selection via a number. E.g let!5 will select /// the fifth entry in the current page - row_char: char, + selection_char: char, /// History menu active status active: bool, /// Cached values collected when querying the history. @@ -73,7 +73,7 @@ impl Default for HistoryMenu { Self { color: MenuTextStyle::default(), page_size: 10, - row_char: ':', + selection_char: '!', active: false, values: Vec::new(), row_position: 0, @@ -109,8 +109,8 @@ impl HistoryMenu { } /// Menu builder with row char - pub fn with_row_char(mut self, row_char: char) -> Self { - self.row_char = row_char; + pub fn with_selection_char(mut self, selection_char: char) -> Self { + self.selection_char = selection_char; self } @@ -126,8 +126,8 @@ impl HistoryMenu { self } - fn update_row_pos(&mut self, new_pos: Option) { - if let (Some(row), Some(page)) = (new_pos, self.pages.get(self.page)) { + fn update_row_pos(&mut self, new_pos: Option<(usize, usize)>) { + if let (Some((row, _)), Some(page)) = (new_pos, self.pages.get(self.page)) { let values_before_page = self.pages.iter().take(self.page).sum::().size; let row = row.saturating_sub(values_before_page); if row < page.size { @@ -346,7 +346,7 @@ impl Menu for HistoryMenu { history: &dyn History, _completer: &dyn Completer, ) { - let (query, row) = parse_row_selector(line_buffer.get_buffer(), &self.row_char); + let (query, row) = parse_selection_char(line_buffer.get_buffer(), &self.selection_char); self.update_row_pos(row); // If there are no row selector and the menu has an Edit event, this clears @@ -565,49 +565,6 @@ impl Menu for HistoryMenu { } } -fn parse_row_selector<'buffer>( - buffer: &'buffer str, - marker: &char, -) -> (&'buffer str, Option) { - if buffer.is_empty() { - return (buffer, None); - } - - let mut input = buffer.chars().peekable(); - - let mut index = 0; - while let Some(char) = input.next() { - if &char == marker { - match input.peek() { - Some(x) if x.is_ascii_digit() => { - let mut count: usize = 0; - while let Some(&c) = input.peek() { - if c.is_ascii_digit() { - let c = c.to_digit(10).expect("already checked if is a digit"); - let _ = input.next(); - count *= 10; - count += c as usize; - } else { - return (&buffer[0..index], Some(count)); - } - } - return (&buffer[0..index], Some(count)); - } - None => { - return (&buffer[0..index], Some(0)); - } - _ => { - index += 1; - continue; - } - } - } - index += 1 - } - - (buffer, None) -} - fn number_of_lines(entry: &str, max_lines: usize, terminal_columns: u16) -> u16 { let lines = if entry.contains('\n') { let total_lines = entry.lines().count(); @@ -635,69 +592,6 @@ fn number_of_lines(entry: &str, max_lines: usize, terminal_columns: u16) -> u16 mod tests { use super::*; - #[test] - fn parse_row_test() { - let input = "search:6"; - let (res, row) = parse_row_selector(input, &':'); - - assert_eq!(res, "search"); - assert_eq!(row, Some(6)) - } - - #[test] - fn parse_row_other_marker_test() { - let input = "search?9"; - let (res, row) = parse_row_selector(input, &'?'); - - assert_eq!(res, "search"); - assert_eq!(row, Some(9)) - } - - #[test] - fn parse_row_double_test() { - let input = "ls | where:16"; - let (res, row) = parse_row_selector(input, &':'); - - assert_eq!(res, "ls | where"); - assert_eq!(row, Some(16)) - } - - #[test] - fn parse_row_empty_test() { - let input = ":10"; - let (res, row) = parse_row_selector(input, &':'); - - assert_eq!(res, ""); - assert_eq!(row, Some(10)) - } - - #[test] - fn parse_row_fake_indicator_test() { - let input = "let a: another :10"; - let (res, row) = parse_row_selector(input, &':'); - - assert_eq!(res, "let a: another "); - assert_eq!(row, Some(10)) - } - - #[test] - fn parse_row_no_number_test() { - let input = "let a: another:"; - let (res, row) = parse_row_selector(input, &':'); - - assert_eq!(res, "let a: another"); - assert_eq!(row, Some(0)) - } - - #[test] - fn parse_empty_buffer_test() { - let input = ""; - let (res, row) = parse_row_selector(input, &':'); - - assert_eq!(res, ""); - assert_eq!(row, None) - } - #[test] fn number_of_lines_test() { let input = "let a: another:\nsomething\nanother"; diff --git a/src/menu/mod.rs b/src/menu/mod.rs index 4e8c358c..7f40ef3a 100644 --- a/src/menu/mod.rs +++ b/src/menu/mod.rs @@ -106,3 +106,129 @@ pub trait Menu: Send { /// Gets cached values from menu that will be displayed fn get_values(&self) -> &[(Span, String)]; } + +/// Parses a buffer looking for the selection char +pub(crate) fn parse_selection_char<'buffer>( + buffer: &'buffer str, + marker: &char, +) -> (&'buffer str, Option<(usize, usize)>) { + if buffer.is_empty() { + return (buffer, None); + } + + let mut input = buffer.chars().peekable(); + + let mut index = 0; + while let Some(char) = input.next() { + if &char == marker { + match input.peek() { + Some(x) if x == marker => { + return (&buffer[0..index], Some((0, 2))); + } + Some(x) if x.is_ascii_digit() => { + let mut count: usize = 0; + let mut size: usize = 1; + while let Some(&c) = input.peek() { + if c.is_ascii_digit() { + let c = c.to_digit(10).expect("already checked if is a digit"); + let _ = input.next(); + count *= 10; + count += c as usize; + size += 1; + } else { + return (&buffer[0..index], Some((count, size))); + } + } + return (&buffer[0..index], Some((count, size))); + } + None => { + return (&buffer[0..index], Some((0, 0))); + } + _ => { + index += 1; + continue; + } + } + } + index += 1 + } + + (buffer, None) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_row_test() { + let input = "search:6"; + let (res, row) = parse_selection_char(input, &':'); + + assert_eq!(res, "search"); + assert_eq!(row, Some((6, 2))) + } + + #[test] + fn parse_double_char() { + let input = "search!!"; + let (res, row) = parse_selection_char(input, &'!'); + + assert_eq!(res, "search"); + assert_eq!(row, Some((0, 2))) + } + + #[test] + fn parse_row_other_marker_test() { + let input = "search?9"; + let (res, row) = parse_selection_char(input, &'?'); + + assert_eq!(res, "search"); + assert_eq!(row, Some((9, 2))) + } + + #[test] + fn parse_row_double_test() { + let input = "ls | where:16"; + let (res, row) = parse_selection_char(input, &':'); + + assert_eq!(res, "ls | where"); + assert_eq!(row, Some((16, 3))) + } + + #[test] + fn parse_row_empty_test() { + let input = ":10"; + let (res, row) = parse_selection_char(input, &':'); + + assert_eq!(res, ""); + assert_eq!(row, Some((10, 3))) + } + + #[test] + fn parse_row_fake_indicator_test() { + let input = "let a: another :10"; + let (res, row) = parse_selection_char(input, &':'); + + assert_eq!(res, "let a: another "); + assert_eq!(row, Some((10, 3))) + } + + #[test] + fn parse_row_no_number_test() { + let input = "let a: another:"; + let (res, row) = parse_selection_char(input, &':'); + + assert_eq!(res, "let a: another"); + assert_eq!(row, Some((0, 0))) + } + + #[test] + fn parse_empty_buffer_test() { + let input = ""; + let (res, row) = parse_selection_char(input, &':'); + + assert_eq!(res, ""); + assert_eq!(row, None) + } +}