bashisms feature for reedline (#374)

* bashisms feature for reedline

* cargo fmt
This commit is contained in:
Fernando Herrera 2022-04-01 18:27:51 +01:00 committed by GitHub
parent 246706c971
commit accce4af7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 35 deletions

View File

@ -27,6 +27,7 @@ jobs:
- uses: actions-rs/cargo@v1
with:
command: test
args: --all
- uses: actions-rs/cargo@v1
with:

View File

@ -35,3 +35,4 @@ rstest = "0.12.0"
[features]
system_clipboard = ["clipboard"]
bashisms = []

View File

@ -7,10 +7,7 @@ use {
highlighter::SimpleMatchHighlighter,
hinter::{DefaultHinter, Hinter},
history::{FileBackedHistory, History, HistoryNavigationQuery},
menu::{
menu_functions::{parse_selection_char, IndexDirection},
Menu, MenuEvent, ReedlineMenu,
},
menu::{Menu, MenuEvent, ReedlineMenu},
painting::{Painter, PromptLines},
prompt::{PromptEditMode, PromptHistorySearchStatus},
utils::text_manipulation,
@ -25,6 +22,9 @@ use {
std::{borrow::Borrow, io, time::Duration},
};
#[cfg(feature = "bashisms")]
use crate::menu_functions::{parse_selection_char, ParseAction};
// The POLL_WAIT is used to specify for how long the POLL should wait for
// events, to accelerate the handling of paste or compound resize events. Having
// a POLL_WAIT of zero means that every single event is treated as soon as it
@ -733,6 +733,7 @@ impl Reedline {
}
}
#[cfg(feature = "bashisms")]
if let Some(event) = self.parse_bang_command() {
return self.handle_editor_event(prompt, event);
}
@ -1046,6 +1047,7 @@ impl Reedline {
}
}
#[cfg(feature = "bashisms")]
/// Parses the ! command to replace entries from the history
fn parse_bang_command(&mut self) -> Option<ReedlineEvent> {
let buffer = self.editor.get_buffer();
@ -1060,19 +1062,25 @@ impl Reedline {
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()))
}
.and_then(|(index, indicator)| match parsed.action {
ParseAction::BackwardSearch => self
.history
.iter_chronologic()
.rev()
.nth(index.saturating_sub(1))
.map(|history| (parsed.remainder.len(), indicator.len(), history.clone())),
ParseAction::ForwardSearch => self
.history
.iter_chronologic()
.nth(index)
.map(|history| (parsed.remainder.len(), indicator.len(), history.clone())),
ParseAction::LastToken => self
.history
.iter_chronologic()
.rev()
.next()
.and_then(|history| history.split_whitespace().rev().next())
.map(|token| (parsed.remainder.len(), indicator.len(), token.to_string())),
});
if let Some((start, size, history)) = history_result {

View File

@ -16,23 +16,25 @@ pub struct ParseResult<'buffer> {
/// Marker representation as string
pub marker: Option<&'buffer str>,
/// Direction of the search based on the marker
pub direction: IndexDirection,
pub action: ParseAction,
}
/// Direction of the index found in the string
#[derive(Debug, PartialEq)]
pub enum IndexDirection {
/// Forward index
Forward,
/// Backward index
Backward,
pub enum ParseAction {
/// Forward index search
ForwardSearch,
/// Backward index search
BackwardSearch,
/// Last token
LastToken,
}
/// Splits a string that contains a marker character
///
/// ## Example usage
/// ```
/// use reedline::menu_functions::{parse_selection_char, IndexDirection, ParseResult};
/// use reedline::menu_functions::{parse_selection_char, ParseAction, ParseResult};
///
/// let parsed = parse_selection_char("this is an example!10", '!');
///
@ -42,7 +44,7 @@ pub enum IndexDirection {
/// remainder: "this is an example",
/// index: Some(10),
/// marker: Some("!10"),
/// direction: IndexDirection::Forward
/// action: ParseAction::ForwardSearch
/// }
/// )
///
@ -53,23 +55,33 @@ pub fn parse_selection_char(buffer: &str, marker: char) -> ParseResult {
remainder: buffer,
index: None,
marker: None,
direction: IndexDirection::Forward,
action: ParseAction::ForwardSearch,
};
}
let mut input = buffer.chars().peekable();
let mut index = 0;
let mut direction = IndexDirection::Forward;
let mut action = ParseAction::ForwardSearch;
while let Some(char) = input.next() {
if char == marker {
match input.peek() {
#[cfg(feature = "bashisms")]
Some(&x) if x == marker => {
return ParseResult {
remainder: &buffer[0..index],
index: Some(0),
marker: Some(&buffer[index..index + 2]),
direction: IndexDirection::Backward,
action: ParseAction::BackwardSearch,
}
}
#[cfg(feature = "bashisms")]
Some(&x) if x == '$' => {
return ParseResult {
remainder: &buffer[0..index],
index: Some(0),
marker: Some(&buffer[index..index + 2]),
action: ParseAction::LastToken,
}
}
Some(&x) if x.is_ascii_digit() || x == '-' => {
@ -79,7 +91,7 @@ pub fn parse_selection_char(buffer: &str, marker: char) -> ParseResult {
if c == '-' {
let _ = input.next();
size += 1;
direction = IndexDirection::Backward;
action = ParseAction::BackwardSearch;
} else if c.is_ascii_digit() {
let c = c.to_digit(10).expect("already checked if is a digit");
let _ = input.next();
@ -91,7 +103,7 @@ pub fn parse_selection_char(buffer: &str, marker: char) -> ParseResult {
remainder: &buffer[0..index],
index: Some(count),
marker: Some(&buffer[index..index + size]),
direction,
action,
};
}
}
@ -99,7 +111,7 @@ pub fn parse_selection_char(buffer: &str, marker: char) -> ParseResult {
remainder: &buffer[0..index],
index: Some(count),
marker: Some(&buffer[index..index + size]),
direction,
action,
};
}
None => {
@ -107,7 +119,7 @@ pub fn parse_selection_char(buffer: &str, marker: char) -> ParseResult {
remainder: &buffer[0..index],
index: Some(0),
marker: Some(&buffer[index..buffer.len()]),
direction,
action,
}
}
_ => {
@ -123,7 +135,7 @@ pub fn parse_selection_char(buffer: &str, marker: char) -> ParseResult {
remainder: buffer,
index: None,
marker: None,
direction,
action,
}
}
@ -248,6 +260,7 @@ mod tests {
assert_eq!(res.marker, Some(":6"));
}
#[cfg(feature = "bashisms")]
#[test]
fn parse_double_char() {
let input = "search!!";
@ -256,7 +269,19 @@ mod tests {
assert_eq!(res.remainder, "search");
assert_eq!(res.index, Some(0));
assert_eq!(res.marker, Some("!!"));
assert!(matches!(res.direction, IndexDirection::Backward));
assert!(matches!(res.action, ParseAction::BackwardSearch));
}
#[cfg(feature = "bashisms")]
#[test]
fn parse_last_token() {
let input = "!$";
let res = parse_selection_char(input, '!');
assert_eq!(res.remainder, "");
assert_eq!(res.index, Some(0));
assert_eq!(res.marker, Some("!$"));
assert!(matches!(res.action, ParseAction::LastToken));
}
#[test]
@ -327,7 +352,7 @@ mod tests {
assert_eq!(res.remainder, "");
assert_eq!(res.index, Some(2));
assert_eq!(res.marker, Some("!-2"));
assert!(matches!(res.direction, IndexDirection::Backward));
assert!(matches!(res.action, ParseAction::BackwardSearch));
}
#[test]