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

View File

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

View File

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