mirror of
https://github.com/nushell/reedline.git
synced 2024-09-11 15:55:50 +03:00
backward history bang (#344)
This commit is contained in:
parent
87a32d5147
commit
4e5a20d127
@ -1,3 +1,5 @@
|
||||
use crate::menu::IndexDirection;
|
||||
|
||||
use {
|
||||
crate::{
|
||||
completion::{CircularCompletionHandler, Completer, DefaultCompleter},
|
||||
@ -1039,28 +1041,31 @@ impl Reedline {
|
||||
/// Parses the ! command to replace entries from the history
|
||||
fn parse_bang_command(&mut self) -> Option<ReedlineEvent> {
|
||||
let buffer = self.editor.get_buffer();
|
||||
let (remainder, index) = parse_selection_char(buffer, &'!');
|
||||
let parsed = parse_selection_char(buffer, &'!');
|
||||
|
||||
if let Some(last) = remainder.chars().last() {
|
||||
if let Some(last) = parsed.remainder.chars().last() {
|
||||
if last != ' ' {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let history_result = index.and_then(|(index, indicator)| {
|
||||
if indicator == "!!" {
|
||||
self.history
|
||||
.iter_chronologic()
|
||||
.rev()
|
||||
.next()
|
||||
.map(|history| (remainder.len(), indicator.len(), history.clone()))
|
||||
} else {
|
||||
self.history
|
||||
.iter_chronologic()
|
||||
.nth(index)
|
||||
.map(|history| (remainder.len(), indicator.len(), history.clone()))
|
||||
}
|
||||
});
|
||||
let history_result = parsed
|
||||
.index
|
||||
.zip(parsed.marker)
|
||||
.and_then(|(index, indicator)| {
|
||||
if matches!(parsed.direction, IndexDirection::Backward) {
|
||||
self.history
|
||||
.iter_chronologic()
|
||||
.rev()
|
||||
.nth(index.saturating_sub(1))
|
||||
.map(|history| (parsed.remainder.len(), indicator.len(), history.clone()))
|
||||
} else {
|
||||
self.history
|
||||
.iter_chronologic()
|
||||
.nth(index)
|
||||
.map(|history| (parsed.remainder.len(), indicator.len(), history.clone()))
|
||||
}
|
||||
});
|
||||
|
||||
if let Some((start, size, history)) = history_result {
|
||||
let edits = vec![
|
||||
|
@ -125,8 +125,8 @@ impl HistoryMenu {
|
||||
self
|
||||
}
|
||||
|
||||
fn update_row_pos(&mut self, new_pos: Option<(usize, &str)>) {
|
||||
if let (Some((row, _)), Some(page)) = (new_pos, self.pages.get(self.page)) {
|
||||
fn update_row_pos(&mut self, new_pos: Option<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::<Page>().size;
|
||||
let row = row.saturating_sub(values_before_page);
|
||||
if row < page.size {
|
||||
@ -364,23 +364,21 @@ impl Menu for HistoryMenu {
|
||||
None => (line_buffer.get_insertion_point(), ""),
|
||||
};
|
||||
|
||||
let (query, row) = parse_selection_char(input, &self.selection_char);
|
||||
self.update_row_pos(row);
|
||||
let parsed = parse_selection_char(input, &self.selection_char);
|
||||
self.update_row_pos(parsed.index);
|
||||
|
||||
// If there are no row selector and the menu has an Edit event, this clears
|
||||
// the position together with the pages vector
|
||||
if let Some(MenuEvent::Edit(_)) = self.event {
|
||||
if row.is_none() {
|
||||
self.reset_position();
|
||||
}
|
||||
if matches!(self.event, Some(MenuEvent::Edit(_))) && parsed.index.is_none() {
|
||||
self.reset_position();
|
||||
}
|
||||
|
||||
let values = if query.is_empty() {
|
||||
let values = if parsed.remainder.is_empty() {
|
||||
self.history_size = Some(history.max_values());
|
||||
self.create_values_no_query(history)
|
||||
} else {
|
||||
self.history_size = None;
|
||||
history.query_entries(query)
|
||||
history.query_entries(parsed.remainder)
|
||||
};
|
||||
|
||||
self.values = values
|
||||
|
143
src/menu/mod.rs
143
src/menu/mod.rs
@ -118,6 +118,18 @@ pub trait Menu: Send {
|
||||
fn get_values(&self) -> &[(Span, String)];
|
||||
}
|
||||
|
||||
pub(crate) enum IndexDirection {
|
||||
Forward,
|
||||
Backward,
|
||||
}
|
||||
|
||||
pub(crate) struct ParseResult<'buffer> {
|
||||
pub remainder: &'buffer str,
|
||||
pub index: Option<usize>,
|
||||
pub marker: Option<&'buffer str>,
|
||||
pub direction: IndexDirection,
|
||||
}
|
||||
|
||||
/// Splits a string that contains a marker character
|
||||
/// e.g: this is an example!10
|
||||
/// returns:
|
||||
@ -126,44 +138,72 @@ pub trait Menu: Send {
|
||||
pub(crate) fn parse_selection_char<'buffer>(
|
||||
buffer: &'buffer str,
|
||||
marker: &char,
|
||||
) -> (&'buffer str, Option<(usize, &'buffer str)>) {
|
||||
) -> ParseResult<'buffer> {
|
||||
if buffer.is_empty() {
|
||||
return (buffer, None);
|
||||
return ParseResult {
|
||||
remainder: buffer,
|
||||
index: None,
|
||||
marker: None,
|
||||
direction: IndexDirection::Forward,
|
||||
};
|
||||
}
|
||||
|
||||
let mut input = buffer.chars().peekable();
|
||||
|
||||
fn is_valid_char(c: &char) -> bool {
|
||||
c.is_ascii_digit() || c == &'-'
|
||||
}
|
||||
|
||||
let mut index = 0;
|
||||
let mut direction = IndexDirection::Forward;
|
||||
while let Some(char) = input.next() {
|
||||
if &char == marker {
|
||||
match input.peek() {
|
||||
Some(x) if x == marker => {
|
||||
return (&buffer[0..index], Some((0, &buffer[index..index + 2])));
|
||||
return ParseResult {
|
||||
remainder: &buffer[0..index],
|
||||
index: Some(0),
|
||||
marker: Some(&buffer[index..index + 2]),
|
||||
direction: IndexDirection::Backward,
|
||||
}
|
||||
}
|
||||
Some(x) if x.is_ascii_digit() => {
|
||||
Some(x) if is_valid_char(x) => {
|
||||
let mut count: usize = 0;
|
||||
let mut size: usize = 1;
|
||||
while let Some(&c) = input.peek() {
|
||||
if c.is_ascii_digit() {
|
||||
if c == '-' {
|
||||
let _ = input.next();
|
||||
size += 1;
|
||||
direction = IndexDirection::Backward;
|
||||
} else 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, &buffer[index..index + size])),
|
||||
);
|
||||
return ParseResult {
|
||||
remainder: &buffer[0..index],
|
||||
index: Some(count),
|
||||
marker: Some(&buffer[index..index + size]),
|
||||
direction,
|
||||
};
|
||||
}
|
||||
}
|
||||
return (
|
||||
&buffer[0..index],
|
||||
Some((count, &buffer[index..index + size])),
|
||||
);
|
||||
return ParseResult {
|
||||
remainder: &buffer[0..index],
|
||||
index: Some(count),
|
||||
marker: Some(&buffer[index..index + size]),
|
||||
direction,
|
||||
};
|
||||
}
|
||||
None => {
|
||||
return (&buffer[0..index], Some((0, &buffer[index..buffer.len()])));
|
||||
return ParseResult {
|
||||
remainder: &buffer[0..index],
|
||||
index: Some(0),
|
||||
marker: Some(&buffer[index..buffer.len()]),
|
||||
direction,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
index += 1;
|
||||
@ -174,7 +214,12 @@ pub(crate) fn parse_selection_char<'buffer>(
|
||||
index += 1
|
||||
}
|
||||
|
||||
(buffer, None)
|
||||
ParseResult {
|
||||
remainder: buffer,
|
||||
index: None,
|
||||
marker: None,
|
||||
direction,
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds common string in a list of values
|
||||
@ -219,72 +264,92 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_row_test() {
|
||||
let input = "search:6";
|
||||
let (res, row) = parse_selection_char(input, &':');
|
||||
let res = parse_selection_char(input, &':');
|
||||
|
||||
assert_eq!(res, "search");
|
||||
assert_eq!(row, Some((6, ":6")))
|
||||
assert_eq!(res.remainder, "search");
|
||||
assert_eq!(res.index, Some(6));
|
||||
assert_eq!(res.marker, Some(":6"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_double_char() {
|
||||
let input = "search!!";
|
||||
let (res, row) = parse_selection_char(input, &'!');
|
||||
let res = parse_selection_char(input, &'!');
|
||||
|
||||
assert_eq!(res, "search");
|
||||
assert_eq!(row, Some((0, "!!")))
|
||||
assert_eq!(res.remainder, "search");
|
||||
assert_eq!(res.index, Some(0));
|
||||
assert_eq!(res.marker, Some("!!"));
|
||||
assert!(matches!(res.direction, IndexDirection::Backward));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_row_other_marker_test() {
|
||||
let input = "search?9";
|
||||
let (res, row) = parse_selection_char(input, &'?');
|
||||
let res = parse_selection_char(input, &'?');
|
||||
|
||||
assert_eq!(res, "search");
|
||||
assert_eq!(row, Some((9, "?9")))
|
||||
assert_eq!(res.remainder, "search");
|
||||
assert_eq!(res.index, Some(9));
|
||||
assert_eq!(res.marker, Some("?9"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_row_double_test() {
|
||||
let input = "ls | where:16";
|
||||
let (res, row) = parse_selection_char(input, &':');
|
||||
let res = parse_selection_char(input, &':');
|
||||
|
||||
assert_eq!(res, "ls | where");
|
||||
assert_eq!(row, Some((16, ":16")))
|
||||
assert_eq!(res.remainder, "ls | where");
|
||||
assert_eq!(res.index, Some(16));
|
||||
assert_eq!(res.marker, Some(":16"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_row_empty_test() {
|
||||
let input = ":10";
|
||||
let (res, row) = parse_selection_char(input, &':');
|
||||
let res = parse_selection_char(input, &':');
|
||||
|
||||
assert_eq!(res, "");
|
||||
assert_eq!(row, Some((10, ":10")))
|
||||
assert_eq!(res.remainder, "");
|
||||
assert_eq!(res.index, Some(10));
|
||||
assert_eq!(res.marker, Some(":10"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_row_fake_indicator_test() {
|
||||
let input = "let a: another :10";
|
||||
let (res, row) = parse_selection_char(input, &':');
|
||||
let res = parse_selection_char(input, &':');
|
||||
|
||||
assert_eq!(res, "let a: another ");
|
||||
assert_eq!(row, Some((10, ":10")))
|
||||
assert_eq!(res.remainder, "let a: another ");
|
||||
assert_eq!(res.index, Some(10));
|
||||
assert_eq!(res.marker, Some(":10"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_row_no_number_test() {
|
||||
let input = "let a: another:";
|
||||
let (res, row) = parse_selection_char(input, &':');
|
||||
let res = parse_selection_char(input, &':');
|
||||
|
||||
assert_eq!(res, "let a: another");
|
||||
assert_eq!(row, Some((0, ":")))
|
||||
assert_eq!(res.remainder, "let a: another");
|
||||
assert_eq!(res.index, Some(0));
|
||||
assert_eq!(res.marker, Some(":"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_empty_buffer_test() {
|
||||
let input = "";
|
||||
let (res, row) = parse_selection_char(input, &':');
|
||||
let res = parse_selection_char(input, &':');
|
||||
|
||||
assert_eq!(res, "");
|
||||
assert_eq!(row, None)
|
||||
assert_eq!(res.remainder, "");
|
||||
assert_eq!(res.index, None);
|
||||
assert_eq!(res.marker, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_negative_direction() {
|
||||
let input = "!-2";
|
||||
let res = parse_selection_char(input, &'!');
|
||||
|
||||
assert_eq!(res.remainder, "");
|
||||
assert_eq!(res.index, Some(2));
|
||||
assert_eq!(res.marker, Some("!-2"));
|
||||
assert!(matches!(res.direction, IndexDirection::Backward));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user