feat(match): provide word separators in the output of Rolling Match result

This commit is contained in:
Federico Terzi 2021-04-18 12:21:26 +02:00
parent dd2cc9de17
commit 97b789e946
4 changed files with 194 additions and 75 deletions

View File

@ -30,6 +30,8 @@ mod util;
pub struct MatchResult<Id> {
pub id: Id,
pub trigger: String,
pub left_separator: Option<String>,
pub right_separator: Option<String>,
pub vars: HashMap<String, String>,
}
@ -38,6 +40,8 @@ impl<Id: Default> Default for MatchResult<Id> {
Self {
id: Id::default(),
trigger: "".to_string(),
left_separator: None,
right_separator: None,
vars: HashMap::new(),
}
}

View File

@ -102,6 +102,8 @@ where
let result = MatchResult {
id: (*id).clone(),
trigger: full_match.to_string(),
left_separator: None,
right_separator: None,
vars: variables,
};
@ -169,6 +171,8 @@ mod tests {
MatchResult {
id,
trigger: trigger.to_string(),
left_separator: None,
right_separator: None,
vars,
}
}

View File

@ -31,6 +31,8 @@ use crate::{
};
use unicase::UniCase;
pub(crate) type IsWordSeparator = bool;
#[derive(Clone)]
pub struct RollingMatcherState<'a, Id> {
paths: Vec<RollingMatcherStatePath<'a, Id>>,
@ -45,7 +47,7 @@ impl<'a, Id> Default for RollingMatcherState<'a, Id> {
#[derive(Clone)]
struct RollingMatcherStatePath<'a, Id> {
node: &'a MatcherTreeNode<Id>,
events: Vec<Event>,
events: Vec<(Event, IsWordSeparator)>,
}
pub struct RollingMatcherOptions {
@ -87,9 +89,9 @@ where
self
.find_refs(node_path.node, &event, true)
.into_iter()
.map(|node_ref| {
.map(|(node_ref, is_word_separator)| {
let mut new_events = node_path.events.clone();
new_events.push(event.clone());
new_events.push((event.clone(), is_word_separator));
(node_ref, new_events)
}),
);
@ -101,7 +103,7 @@ where
next_refs.extend(
root_refs
.into_iter()
.map(|node_ref| (node_ref, vec![event.clone()])),
.map(|(node_ref, is_word_separator)| (node_ref, vec![(event.clone(), is_word_separator)])),
);
let mut next_paths = Vec::new();
@ -109,12 +111,14 @@ where
for (node_ref, events) in next_refs {
match node_ref {
MatcherTreeRef::Matches(matches) => {
let trigger = extract_string_from_events(&events);
let (trigger, left_separator, right_separator) = extract_string_from_events(&events);
let results = matches
.iter()
.map(|id| MatchResult {
id: id.clone(),
trigger: trigger.clone(),
left_separator: left_separator.clone(),
right_separator: right_separator.clone(),
vars: HashMap::new(),
})
.collect();
@ -152,19 +156,19 @@ impl<Id: Clone> RollingMatcher<Id> {
node: &'a MatcherTreeNode<Id>,
event: &Event,
has_previous_state: bool,
) -> Vec<&'a MatcherTreeRef<Id>> {
) -> Vec<(&'a MatcherTreeRef<Id>, IsWordSeparator)> {
let mut refs = Vec::new();
if let Event::Key { key, chars } = event {
// Key matching
if let Some((_, node_ref)) = node.keys.iter().find(|(_key, _)| _key == key) {
refs.push(node_ref);
refs.push((node_ref, false));
}
if let Some(char) = chars {
// Char matching
if let Some((_, node_ref)) = node.chars.iter().find(|(_char, _)| _char == char) {
refs.push(node_ref);
refs.push((node_ref, false));
}
// Char case-insensitive
@ -174,14 +178,14 @@ impl<Id: Clone> RollingMatcher<Id> {
.iter()
.find(|(_char, _)| *_char == insensitive_char)
{
refs.push(node_ref);
refs.push((node_ref, false));
}
}
}
if self.is_word_separator(event) {
if let Some(node_ref) = node.word_separators.as_ref() {
refs.push(node_ref)
refs.push((node_ref, true))
}
}
@ -226,6 +230,16 @@ mod tests {
}
}
fn match_result_with_sep<Id: Default>(id: Id, trigger: &str, left: Option<&str>, right: Option<&str>) -> MatchResult<Id> {
MatchResult {
id,
trigger: trigger.to_string(),
left_separator: left.map(str::to_owned),
right_separator: right.map(str::to_owned),
..Default::default()
}
}
#[test]
fn matcher_process_simple_strings() {
let matcher = RollingMatcher::new(
@ -281,11 +295,11 @@ mod tests {
// Word matches are also triggered when there is no left separator but it's a new state
assert_eq!(
get_matches_after_str("hi,", &matcher),
vec![match_result(1, "hi,")]
vec![match_result_with_sep(1, "hi,", None, Some(","))]
);
assert_eq!(
get_matches_after_str(".hi,", &matcher),
vec![match_result(1, ".hi,")]
vec![match_result_with_sep(1, ".hi,", Some("."), Some(","))]
);
}

View File

@ -19,19 +19,33 @@
use crate::Event;
// TODO: test
pub(crate) fn extract_string_from_events(events: &[Event]) -> String {
use super::matcher::IsWordSeparator;
pub(crate) fn extract_string_from_events(
events: &[(Event, IsWordSeparator)],
) -> (String, Option<String>, Option<String>) {
let mut string = String::new();
for event in events {
let mut left_separator = None;
let mut right_separator = None;
for (i, (event, is_word_separator)) in events.iter().enumerate() {
if let Event::Key { key: _, chars } = event {
if let Some(chars) = chars {
string.push_str(chars);
if *is_word_separator {
if i == 0 {
left_separator = Some(chars.clone());
} else if i == (events.len() - 1) {
right_separator = Some(chars.clone());
}
}
}
}
}
string
(string, left_separator, right_separator)
}
#[cfg(test)]
@ -43,28 +57,84 @@ mod tests {
fn extract_string_from_events_all_chars() {
assert_eq!(
extract_string_from_events(&[
Event::Key {
key: Key::Other,
chars: Some("h".to_string())
},
Event::Key {
key: Key::Other,
chars: Some("e".to_string())
},
Event::Key {
key: Key::Other,
chars: Some("l".to_string())
},
Event::Key {
key: Key::Other,
chars: Some("l".to_string())
},
Event::Key {
key: Key::Other,
chars: Some("o".to_string())
},
(
Event::Key {
key: Key::Other,
chars: Some("h".to_string())
},
false
),
(
Event::Key {
key: Key::Other,
chars: Some("e".to_string())
},
false
),
(
Event::Key {
key: Key::Other,
chars: Some("l".to_string())
},
false
),
(
Event::Key {
key: Key::Other,
chars: Some("l".to_string())
},
false
),
(
Event::Key {
key: Key::Other,
chars: Some("o".to_string())
},
false
),
]),
"hello"
("hello".to_string(), None, None)
);
}
#[test]
fn extract_string_from_events_word_separators() {
assert_eq!(
extract_string_from_events(&[
(
Event::Key {
key: Key::Other,
chars: Some(".".to_string())
},
true
),
(
Event::Key {
key: Key::Other,
chars: Some("h".to_string())
},
false
),
(
Event::Key {
key: Key::Other,
chars: Some("i".to_string())
},
false
),
(
Event::Key {
key: Key::Other,
chars: Some(",".to_string())
},
true
),
]),
(
".hi,".to_string(),
Some(".".to_string()),
Some(",".to_string())
),
);
}
@ -72,20 +142,29 @@ mod tests {
fn extract_string_from_events_no_chars() {
assert_eq!(
extract_string_from_events(&[
Event::Key {
key: Key::ArrowUp,
chars: None
},
Event::Key {
key: Key::ArrowUp,
chars: None
},
Event::Key {
key: Key::ArrowUp,
chars: None
},
(
Event::Key {
key: Key::ArrowUp,
chars: None
},
false
),
(
Event::Key {
key: Key::ArrowUp,
chars: None
},
false
),
(
Event::Key {
key: Key::ArrowUp,
chars: None
},
false
),
]),
""
("".to_string(), None, None)
);
}
@ -93,32 +172,50 @@ mod tests {
fn extract_string_from_events_mixed() {
assert_eq!(
extract_string_from_events(&[
Event::Key {
key: Key::Other,
chars: Some("h".to_string())
},
Event::Key {
key: Key::Other,
chars: Some("e".to_string())
},
Event::Key {
key: Key::Other,
chars: Some("l".to_string())
},
Event::Key {
key: Key::Other,
chars: Some("l".to_string())
},
Event::Key {
key: Key::Other,
chars: Some("o".to_string())
},
Event::Key {
key: Key::ArrowUp,
chars: None
},
(
Event::Key {
key: Key::Other,
chars: Some("h".to_string())
},
false
),
(
Event::Key {
key: Key::Other,
chars: Some("e".to_string())
},
false
),
(
Event::Key {
key: Key::Other,
chars: Some("l".to_string())
},
false
),
(
Event::Key {
key: Key::Other,
chars: Some("l".to_string())
},
false
),
(
Event::Key {
key: Key::Other,
chars: Some("o".to_string())
},
false
),
(
Event::Key {
key: Key::ArrowUp,
chars: None
},
false
),
]),
"hello"
("hello".to_string(), None, None)
);
}
}