Improve Rolling matcher implementation and add Regex matcher
This commit is contained in:
parent
fcfca92bc5
commit
beab299aa0
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -301,7 +301,6 @@ name = "espanso-match"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
|
@ -9,5 +9,4 @@ log = "0.4.14"
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
thiserror = "1.0.23"
|
thiserror = "1.0.23"
|
||||||
regex = "1.4.3"
|
regex = "1.4.3"
|
||||||
lazy_static = "1.4.0"
|
|
||||||
unicase = "2.6.0"
|
unicase = "2.6.0"
|
|
@ -17,7 +17,7 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Key {
|
Key {
|
||||||
key: Key,
|
key: Key,
|
||||||
|
|
|
@ -17,16 +17,35 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use event::Event;
|
use event::Event;
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod rolling;
|
|
||||||
pub mod regex;
|
pub mod regex;
|
||||||
|
pub mod rolling;
|
||||||
|
mod util;
|
||||||
|
|
||||||
// TODO: instead of returning Vec<Id>, return a Vec of structs which contain Id, trigger string and variables(if any)
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub trait Matcher<'a, State, Id> where Id: Clone {
|
pub struct MatchResult<Id> {
|
||||||
fn process(&'a self, prev_state: Option<&State>, event: Event) -> (State, Vec<Id>);
|
id: Id,
|
||||||
|
trigger: String,
|
||||||
|
vars: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Id: Default> Default for MatchResult<Id> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: Id::default(),
|
||||||
|
trigger: "".to_string(),
|
||||||
|
vars: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Matcher<'a, State, Id>
|
||||||
|
where
|
||||||
|
Id: Clone,
|
||||||
|
{
|
||||||
|
fn process(&'a self, prev_state: Option<&State>, event: Event) -> (State, Vec<MatchResult<Id>>);
|
||||||
}
|
}
|
|
@ -17,10 +17,12 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use log::error;
|
use log::error;
|
||||||
use regex::{Regex, RegexSet};
|
use regex::{Regex, RegexSet};
|
||||||
|
|
||||||
use crate::event::Event;
|
use crate::{MatchResult, event::Event};
|
||||||
use crate::Matcher;
|
use crate::Matcher;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -68,7 +70,7 @@ where
|
||||||
&'a self,
|
&'a self,
|
||||||
prev_state: Option<&RegexMatcherState>,
|
prev_state: Option<&RegexMatcherState>,
|
||||||
event: Event,
|
event: Event,
|
||||||
) -> (RegexMatcherState, Vec<Id>) {
|
) -> (RegexMatcherState, Vec<MatchResult<Id>>) {
|
||||||
let mut buffer = if let Some(prev_state) = prev_state {
|
let mut buffer = if let Some(prev_state) = prev_state {
|
||||||
prev_state.buffer.clone()
|
prev_state.buffer.clone()
|
||||||
} else {
|
} else {
|
||||||
|
@ -83,13 +85,38 @@ where
|
||||||
|
|
||||||
// Find matches
|
// Find matches
|
||||||
if self.regex_set.is_match(&buffer) {
|
if self.regex_set.is_match(&buffer) {
|
||||||
|
let mut matches = Vec::new();
|
||||||
|
|
||||||
for index in self.regex_set.matches(&buffer) {
|
for index in self.regex_set.matches(&buffer) {
|
||||||
if let (Some(id), Some(regex)) = (self.ids.get(index), self.regexes.get(index)) {
|
if let (Some(id), Some(regex)) = (self.ids.get(index), self.regexes.get(index)) {
|
||||||
// TODO: find complete match and captures
|
if let Some(captures) = regex.captures(&buffer) {
|
||||||
|
let full_match = captures.get(0).map_or("", |m| m.as_str());
|
||||||
|
if !full_match.is_empty() {
|
||||||
|
|
||||||
|
// Now extract the captured names as variables
|
||||||
|
let variables: HashMap<String, String> = regex
|
||||||
|
.capture_names()
|
||||||
|
.flatten()
|
||||||
|
.filter_map(|n| Some((n.to_string(), captures.name(n)?.as_str().to_string())))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let result = MatchResult {
|
||||||
|
id: (*id).clone(),
|
||||||
|
trigger: full_match.to_string(),
|
||||||
|
vars: variables,
|
||||||
|
};
|
||||||
|
|
||||||
|
matches.push(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
error!("received inconsistent index from regex set with index: {}", index);
|
error!("received inconsistent index from regex set with index: {}", index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !matches.is_empty() {
|
||||||
|
return (RegexMatcherState::default(), matches)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_state = RegexMatcherState { buffer };
|
let current_state = RegexMatcherState { buffer };
|
||||||
|
@ -122,4 +149,46 @@ impl<Id: Clone> RegexMatcher<Id> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::util::tests::get_matches_after_str;
|
||||||
|
|
||||||
|
fn match_result<Id: Default>(id: Id, trigger: &str, vars: &[(&str, &str)]) -> MatchResult<Id> {
|
||||||
|
let vars: HashMap<String, String> = vars.iter().map(|(key, value)| {
|
||||||
|
(key.to_string(), value.to_string())
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
MatchResult {
|
||||||
|
id,
|
||||||
|
trigger: trigger.to_string(),
|
||||||
|
vars,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn matcher_simple_matches() {
|
||||||
|
let matcher = RegexMatcher::new(&[
|
||||||
|
RegexMatch::new(1, "hello"),
|
||||||
|
RegexMatch::new(2, "num\\d{1,3}s"),
|
||||||
|
]);
|
||||||
|
assert_eq!(get_matches_after_str("hi", &matcher), vec![]);
|
||||||
|
assert_eq!(get_matches_after_str("hello", &matcher), vec![match_result(1, "hello", &[])]);
|
||||||
|
assert_eq!(get_matches_after_str("say hello", &matcher), vec![match_result(1, "hello", &[])]);
|
||||||
|
assert_eq!(get_matches_after_str("num1s", &matcher), vec![match_result(2, "num1s", &[])]);
|
||||||
|
assert_eq!(get_matches_after_str("num134s", &matcher), vec![match_result(2, "num134s", &[])]);
|
||||||
|
assert_eq!(get_matches_after_str("nums", &matcher), vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn matcher_with_variables() {
|
||||||
|
let matcher = RegexMatcher::new(&[
|
||||||
|
RegexMatch::new(1, "hello\\((?P<name>.*?)\\)"),
|
||||||
|
RegexMatch::new(2, "multi\\((?P<name1>.*?),(?P<name2>.*?)\\)"),
|
||||||
|
]);
|
||||||
|
assert_eq!(get_matches_after_str("hi", &matcher), vec![]);
|
||||||
|
assert_eq!(get_matches_after_str("say hello(mary)", &matcher), vec![match_result(1, "hello(mary)", &[("name", "mary")])]);
|
||||||
|
assert_eq!(get_matches_after_str("hello(mary", &matcher), vec![]);
|
||||||
|
assert_eq!(get_matches_after_str("multi(mary,jane)", &matcher), vec![match_result(2, "multi(mary,jane)", &[("name1", "mary"), ("name2", "jane")])]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,25 +17,30 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::{
|
use std::collections::HashMap;
|
||||||
tree::{MatcherTreeNode, MatcherTreeRef},
|
|
||||||
RollingMatch,
|
use super::{RollingMatch, tree::{MatcherTreeNode, MatcherTreeRef}, util::extract_string_from_events};
|
||||||
};
|
use crate::{MatchResult, event::{Event, Key}};
|
||||||
use crate::event::{Event, Key};
|
|
||||||
use crate::Matcher;
|
use crate::Matcher;
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RollingMatcherState<'a, Id> {
|
pub struct RollingMatcherState<'a, Id> {
|
||||||
nodes: Vec<&'a MatcherTreeNode<Id>>,
|
paths: Vec<RollingMatcherStatePath<'a, Id>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Id> Default for RollingMatcherState<'a, Id> {
|
impl<'a, Id> Default for RollingMatcherState<'a, Id> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { nodes: Vec::new() }
|
Self { paths: Vec::new() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct RollingMatcherStatePath<'a, Id> {
|
||||||
|
node: &'a MatcherTreeNode<Id>,
|
||||||
|
events: Vec<Event>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RollingMatcherOptions {
|
pub struct RollingMatcherOptions {
|
||||||
char_word_separators: Vec<String>,
|
char_word_separators: Vec<String>,
|
||||||
key_word_separators: Vec<Key>,
|
key_word_separators: Vec<Key>,
|
||||||
|
@ -65,35 +70,53 @@ where
|
||||||
&'a self,
|
&'a self,
|
||||||
prev_state: Option<&RollingMatcherState<'a, Id>>,
|
prev_state: Option<&RollingMatcherState<'a, Id>>,
|
||||||
event: Event,
|
event: Event,
|
||||||
) -> (RollingMatcherState<'a, Id>, Vec<Id>) {
|
) -> (RollingMatcherState<'a, Id>, Vec<MatchResult<Id>>) {
|
||||||
let mut next_refs = Vec::new();
|
let mut next_refs = Vec::new();
|
||||||
|
|
||||||
// First compute the old refs
|
// First compute the old refs
|
||||||
if let Some(prev_state) = prev_state {
|
if let Some(prev_state) = prev_state {
|
||||||
for node_ref in prev_state.nodes.iter() {
|
for node_path in prev_state.paths.iter() {
|
||||||
next_refs.extend(self.find_refs(node_ref, &event));
|
next_refs.extend(self.find_refs(node_path.node, &event).into_iter().map(|node_ref| {
|
||||||
|
let mut new_events = node_path.events.clone();
|
||||||
|
new_events.push(event.clone());
|
||||||
|
(node_ref, new_events)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate new ones
|
// Calculate new ones
|
||||||
let root_refs = self.find_refs(&self.root, &event);
|
let root_refs = self.find_refs(&self.root, &event);
|
||||||
next_refs.extend(root_refs);
|
next_refs.extend(root_refs.into_iter().map(|node_ref| {
|
||||||
|
(node_ref, vec![event.clone()])
|
||||||
|
}));
|
||||||
|
|
||||||
let mut next_nodes = Vec::new();
|
let mut next_paths = Vec::new();
|
||||||
|
|
||||||
for node_ref 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 results = matches.iter().map(|id| {
|
||||||
|
MatchResult {
|
||||||
|
id: id.clone(),
|
||||||
|
trigger: trigger.clone(),
|
||||||
|
vars: HashMap::new(),
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
|
||||||
// Reset the state and return the matches
|
// Reset the state and return the matches
|
||||||
return (RollingMatcherState::default(), matches.clone());
|
return (RollingMatcherState::default(), results);
|
||||||
}
|
}
|
||||||
MatcherTreeRef::Node(node) => {
|
MatcherTreeRef::Node(node) => {
|
||||||
next_nodes.push(node.as_ref());
|
next_paths.push(RollingMatcherStatePath {
|
||||||
|
node: node.as_ref(),
|
||||||
|
events,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_state = RollingMatcherState { nodes: next_nodes };
|
let current_state = RollingMatcherState { paths: next_paths };
|
||||||
|
|
||||||
(current_state, Vec::new())
|
(current_state, Vec::new())
|
||||||
}
|
}
|
||||||
|
@ -169,25 +192,14 @@ impl<Id: Clone> RollingMatcher<Id> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::rolling::{StringMatchOptions};
|
use crate::rolling::{StringMatchOptions};
|
||||||
|
use crate::util::tests::get_matches_after_str;
|
||||||
|
|
||||||
fn get_matches_after_str<Id: Clone>(string: &str, matcher: &RollingMatcher<Id>) -> Vec<Id> {
|
fn match_result<Id: Default>(id: Id, trigger: &str) -> MatchResult<Id> {
|
||||||
let mut prev_state = None;
|
MatchResult {
|
||||||
let mut matches = Vec::new();
|
id,
|
||||||
|
trigger: trigger.to_string(),
|
||||||
for c in string.chars() {
|
..Default::default()
|
||||||
let (state, _matches) = matcher.process(
|
|
||||||
prev_state.as_ref(),
|
|
||||||
Event::Key {
|
|
||||||
key: Key::Other,
|
|
||||||
chars: Some(c.to_string()),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
prev_state = Some(state);
|
|
||||||
matches = _matches;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
matches
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -205,8 +217,9 @@ mod tests {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(get_matches_after_str("hi", &matcher), vec![1, 5]);
|
assert_eq!(get_matches_after_str("hi", &matcher), vec![match_result(1, "hi"), match_result(5, "hi")]);
|
||||||
assert_eq!(get_matches_after_str("my", &matcher), vec![3]);
|
assert_eq!(get_matches_after_str("my", &matcher), vec![match_result(3, "my")]);
|
||||||
|
assert_eq!(get_matches_after_str("mmy", &matcher), vec![match_result(3, "my")]);
|
||||||
assert_eq!(get_matches_after_str("invalid", &matcher), vec![]);
|
assert_eq!(get_matches_after_str("invalid", &matcher), vec![]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,7 +245,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(get_matches_after_str("hi", &matcher), vec![]);
|
assert_eq!(get_matches_after_str("hi", &matcher), vec![]);
|
||||||
assert_eq!(get_matches_after_str(".hi,", &matcher), vec![1]);
|
assert_eq!(get_matches_after_str(".hi,", &matcher), vec![match_result(1, ".hi,")]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -264,11 +277,11 @@ mod tests {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(get_matches_after_str("hi", &matcher), vec![1]);
|
assert_eq!(get_matches_after_str("hi", &matcher), vec![match_result(1, "hi")]);
|
||||||
assert_eq!(get_matches_after_str("Hi", &matcher), vec![1]);
|
assert_eq!(get_matches_after_str("Hi", &matcher), vec![match_result(1, "Hi")]);
|
||||||
assert_eq!(get_matches_after_str("HI", &matcher), vec![1]);
|
assert_eq!(get_matches_after_str("HI", &matcher), vec![match_result(1, "HI")]);
|
||||||
assert_eq!(get_matches_after_str("arty", &matcher), vec![3]);
|
assert_eq!(get_matches_after_str("arty", &matcher), vec![match_result(3, "arty")]);
|
||||||
assert_eq!(get_matches_after_str("arTY", &matcher), vec![3]);
|
assert_eq!(get_matches_after_str("arTY", &matcher), vec![match_result(3, "arTY")]);
|
||||||
assert_eq!(get_matches_after_str("ARTY", &matcher), vec![]);
|
assert_eq!(get_matches_after_str("ARTY", &matcher), vec![]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,9 @@
|
||||||
|
|
||||||
use crate::event::Key;
|
use crate::event::Key;
|
||||||
|
|
||||||
mod matcher;
|
pub mod matcher;
|
||||||
mod tree;
|
mod tree;
|
||||||
|
mod util;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum RollingItem {
|
pub enum RollingItem {
|
||||||
|
|
73
espanso-match/src/rolling/util.rs
Normal file
73
espanso-match/src/rolling/util.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* This file is part of espanso.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 Federico Terzi
|
||||||
|
*
|
||||||
|
* espanso is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* espanso is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::Event;
|
||||||
|
|
||||||
|
// TODO: test
|
||||||
|
pub(crate) fn extract_string_from_events(events: &[Event]) -> String {
|
||||||
|
let mut string = String::new();
|
||||||
|
|
||||||
|
for event in events {
|
||||||
|
if let Event::Key { key: _, chars } = event {
|
||||||
|
if let Some(chars) = chars {
|
||||||
|
string.push_str(chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::event::Key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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()) },
|
||||||
|
]), "hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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 },
|
||||||
|
]), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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 },
|
||||||
|
]), "hello");
|
||||||
|
}
|
||||||
|
}
|
43
espanso-match/src/util.rs
Normal file
43
espanso-match/src/util.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* This file is part of espanso.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 Federico Terzi
|
||||||
|
*
|
||||||
|
* espanso is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* espanso is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod tests {
|
||||||
|
use crate::{MatchResult, Matcher, event::{Event, Key}};
|
||||||
|
|
||||||
|
pub(crate) fn get_matches_after_str<'a, Id: Clone, S, M: Matcher<'a, S, Id>>(string: &str, matcher: &'a M) -> Vec<MatchResult<Id>> {
|
||||||
|
let mut prev_state = None;
|
||||||
|
let mut matches = Vec::new();
|
||||||
|
|
||||||
|
for c in string.chars() {
|
||||||
|
let (state, _matches) = matcher.process(
|
||||||
|
prev_state.as_ref(),
|
||||||
|
Event::Key {
|
||||||
|
key: Key::Other,
|
||||||
|
chars: Some(c.to_string()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
prev_state = Some(state);
|
||||||
|
matches = _matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
matches
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user