From 97b789e9462f78119ce51fa6b22191f0557ae0b8 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Sun, 18 Apr 2021 12:21:26 +0200 Subject: [PATCH] feat(match): provide word separators in the output of Rolling Match result --- espanso-match/src/lib.rs | 4 + espanso-match/src/regex/mod.rs | 4 + espanso-match/src/rolling/matcher.rs | 38 +++-- espanso-match/src/rolling/util.rs | 223 +++++++++++++++++++-------- 4 files changed, 194 insertions(+), 75 deletions(-) diff --git a/espanso-match/src/lib.rs b/espanso-match/src/lib.rs index 735ae6a..f79c0a5 100644 --- a/espanso-match/src/lib.rs +++ b/espanso-match/src/lib.rs @@ -30,6 +30,8 @@ mod util; pub struct MatchResult { pub id: Id, pub trigger: String, + pub left_separator: Option, + pub right_separator: Option, pub vars: HashMap, } @@ -38,6 +40,8 @@ impl Default for MatchResult { Self { id: Id::default(), trigger: "".to_string(), + left_separator: None, + right_separator: None, vars: HashMap::new(), } } diff --git a/espanso-match/src/regex/mod.rs b/espanso-match/src/regex/mod.rs index e23846a..b0fd3f8 100644 --- a/espanso-match/src/regex/mod.rs +++ b/espanso-match/src/regex/mod.rs @@ -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, } } diff --git a/espanso-match/src/rolling/matcher.rs b/espanso-match/src/rolling/matcher.rs index 7576243..f4cae13 100644 --- a/espanso-match/src/rolling/matcher.rs +++ b/espanso-match/src/rolling/matcher.rs @@ -31,6 +31,8 @@ use crate::{ }; use unicase::UniCase; +pub(crate) type IsWordSeparator = bool; + #[derive(Clone)] pub struct RollingMatcherState<'a, Id> { paths: Vec>, @@ -45,7 +47,7 @@ impl<'a, Id> Default for RollingMatcherState<'a, Id> { #[derive(Clone)] struct RollingMatcherStatePath<'a, Id> { node: &'a MatcherTreeNode, - events: Vec, + 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 RollingMatcher { node: &'a MatcherTreeNode, event: &Event, has_previous_state: bool, - ) -> Vec<&'a MatcherTreeRef> { + ) -> Vec<(&'a MatcherTreeRef, 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 RollingMatcher { .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: Id, trigger: &str, left: Option<&str>, right: Option<&str>) -> MatchResult { + 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(","))] ); } diff --git a/espanso-match/src/rolling/util.rs b/espanso-match/src/rolling/util.rs index a5a84b7..bb87aba 100644 --- a/espanso-match/src/rolling/util.rs +++ b/espanso-match/src/rolling/util.rs @@ -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, Option) { 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) ); } }