diff --git a/espanso-match/src/event.rs b/espanso-match/src/event.rs
index 1d3b87a..d6f5b34 100644
--- a/espanso-match/src/event.rs
+++ b/espanso-match/src/event.rs
@@ -26,7 +26,7 @@ pub enum Event {
VirtualSeparator,
}
-#[derive(Debug, PartialEq)]
+#[derive(Debug, Clone, PartialEq)]
pub enum Key {
// Modifiers
Alt,
diff --git a/espanso-match/src/lib.rs b/espanso-match/src/lib.rs
index 185a04c..86851e4 100644
--- a/espanso-match/src/lib.rs
+++ b/espanso-match/src/lib.rs
@@ -17,7 +17,6 @@
* along with espanso. If not, see .
*/
-use std::any::Any;
use event::Event;
#[macro_use]
@@ -27,5 +26,5 @@ pub mod event;
pub mod rolling;
pub trait Matcher<'a, State, Id> where Id: Clone {
- fn process(&'a self, prev_state: Option<&'a State>, event: Event) -> (State, Vec);
+ fn process(&'a self, prev_state: Option<&State>, event: Event) -> (State, Vec);
}
\ No newline at end of file
diff --git a/espanso-match/src/rolling/item.rs b/espanso-match/src/rolling/item.rs
deleted file mode 100644
index fce6f07..0000000
--- a/espanso-match/src/rolling/item.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-use crate::event::Key;
-
-/*
- * 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 .
- */
-
-
-pub enum RollingItem {
- WordSeparator,
- Key(Key),
- Char(String),
- CharInsensitive(String),
-}
\ No newline at end of file
diff --git a/espanso-match/src/rolling/matcher.rs b/espanso-match/src/rolling/matcher.rs
index f9a4bf9..c65e113 100644
--- a/espanso-match/src/rolling/matcher.rs
+++ b/espanso-match/src/rolling/matcher.rs
@@ -17,19 +17,35 @@
* along with espanso. If not, see .
*/
-use unicase::UniCase;
-use crate::Matcher;
+use super::{
+ tree::{MatcherTreeNode, MatcherTreeRef},
+ RollingMatch,
+};
use crate::event::{Event, Key};
-use super::{RollingItem, RollingMatch, tree::{MatcherTreeNode, MatcherTreeRef}};
+use crate::Matcher;
+use unicase::UniCase;
+#[derive(Clone)]
pub struct RollingMatcherState<'a, Id> {
nodes: Vec<&'a MatcherTreeNode>,
}
-impl <'a, Id> Default for RollingMatcherState<'a, Id> {
+impl<'a, Id> Default for RollingMatcherState<'a, Id> {
+ fn default() -> Self {
+ Self { nodes: Vec::new() }
+ }
+}
+
+pub struct RollingMatcherOptions {
+ char_word_separators: Vec,
+ key_word_separators: Vec,
+}
+
+impl Default for RollingMatcherOptions {
fn default() -> Self {
Self {
- nodes: Vec::new(),
+ char_word_separators: Vec::new(),
+ key_word_separators: Vec::new(),
}
}
}
@@ -41,11 +57,13 @@ pub struct RollingMatcher {
root: MatcherTreeNode,
}
-
-impl <'a, Id> Matcher<'a, RollingMatcherState<'a, Id>, Id> for RollingMatcher where Id: Clone {
+impl<'a, Id> Matcher<'a, RollingMatcherState<'a, Id>, Id> for RollingMatcher
+where
+ Id: Clone,
+{
fn process(
&'a self,
- prev_state: Option<&'a RollingMatcherState<'a, Id>>,
+ prev_state: Option<&RollingMatcherState<'a, Id>>,
event: Event,
) -> (RollingMatcherState<'a, Id>, Vec) {
let mut next_refs = Vec::new();
@@ -68,31 +86,34 @@ impl <'a, Id> Matcher<'a, RollingMatcherState<'a, Id>, Id> for RollingMatcher {
// Reset the state and return the matches
return (RollingMatcherState::default(), matches.clone());
- },
+ }
MatcherTreeRef::Node(node) => {
next_nodes.push(node.as_ref());
}
}
}
- let current_state = RollingMatcherState {
- nodes: next_nodes,
- };
+ let current_state = RollingMatcherState { nodes: next_nodes };
(current_state, Vec::new())
}
}
-impl RollingMatcher {
- pub fn new(matches: &[RollingMatch]) -> Self {
- todo!()
- // Self {
-
- // }
+impl RollingMatcher {
+ pub fn new(matches: &[RollingMatch], opt: RollingMatcherOptions) -> Self {
+ let root = MatcherTreeNode::from_matches(matches);
+ Self {
+ root,
+ char_word_separators: opt.char_word_separators,
+ key_word_separators: opt.key_word_separators,
+ }
}
- // TODO: test
- fn find_refs<'a>(&'a self, node: &'a MatcherTreeNode, event: &Event) -> Vec<&'a MatcherTreeRef> {
+ fn find_refs<'a>(
+ &'a self,
+ node: &'a MatcherTreeNode,
+ event: &Event,
+ ) -> Vec<&'a MatcherTreeRef> {
let mut refs = Vec::new();
if let Event::Key { key, chars } = event {
@@ -140,7 +161,6 @@ impl RollingMatcher {
}
}
Event::VirtualSeparator => true,
- _ => false,
}
}
}
@@ -148,38 +168,107 @@ impl RollingMatcher {
#[cfg(test)]
mod tests {
use super::*;
+ use crate::rolling::{StringMatchOptions};
+
+ fn get_matches_after_str(string: &str, matcher: &RollingMatcher) -> Vec {
+ 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
+ }
#[test]
- fn test() { // TODO: convert to actual test
- let root = MatcherTreeNode {
- chars: vec![
- ("h".to_string(), MatcherTreeRef::Node(Box::new(MatcherTreeNode {
- chars: vec![
- ("i".to_string(), MatcherTreeRef::Matches(vec![1])),
- ],
- ..Default::default()
- }))
- )
+ fn matcher_process_simple_strings() {
+ let matcher = RollingMatcher::new(
+ &[
+ RollingMatch::from_string(1, "hi", &StringMatchOptions::default()),
+ RollingMatch::from_string(2, "hey", &StringMatchOptions::default()),
+ RollingMatch::from_string(3, "my", &StringMatchOptions::default()),
+ RollingMatch::from_string(4, "myself", &StringMatchOptions::default()),
+ RollingMatch::from_string(5, "hi", &StringMatchOptions::default()),
],
- ..Default::default()
- };
+ RollingMatcherOptions {
+ ..Default::default()
+ },
+ );
- let matcher = RollingMatcher {
- char_word_separators: vec![".".to_owned()],
- key_word_separators: vec![Key::ArrowUp],
- root,
- };
+ assert_eq!(get_matches_after_str("hi", &matcher), vec![1, 5]);
+ assert_eq!(get_matches_after_str("my", &matcher), vec![3]);
+ assert_eq!(get_matches_after_str("invalid", &matcher), vec![]);
+ }
- let (state, matches) = matcher.process(None, Event::Key {
- key: Key::Other,
- chars: Some("h".to_string()),
- });
- assert_eq!(matches.len(), 0);
+ #[test]
+ fn matcher_process_word_matches() {
+ let matcher = RollingMatcher::new(
+ &[
+ RollingMatch::from_string(
+ 1,
+ "hi",
+ &StringMatchOptions {
+ left_word: true,
+ right_word: true,
+ ..Default::default()
+ },
+ ),
+ RollingMatch::from_string(2, "hey", &StringMatchOptions::default()),
+ ],
+ RollingMatcherOptions {
+ char_word_separators: vec![".".to_string(), ",".to_string()],
+ ..Default::default()
+ },
+ );
- let (state, matches) = matcher.process(Some(&state), Event::Key {
- key: Key::Other,
- chars: Some("i".to_string()),
- });
- assert_eq!(matches, vec![1]);
+ assert_eq!(get_matches_after_str("hi", &matcher), vec![]);
+ assert_eq!(get_matches_after_str(".hi,", &matcher), vec![1]);
+ }
+
+ #[test]
+ fn matcher_process_case_insensitive() {
+ let matcher = RollingMatcher::new(
+ &[
+ RollingMatch::from_string(
+ 1,
+ "hi",
+ &StringMatchOptions {
+ case_insensitive: true,
+ ..Default::default()
+ },
+ ),
+ RollingMatch::from_string(2, "hey", &StringMatchOptions::default()),
+ RollingMatch::from_string(
+ 3,
+ "arty",
+ &StringMatchOptions {
+ case_insensitive: true,
+ preserve_case_markers: true,
+ ..Default::default()
+ },
+ ),
+ ],
+ RollingMatcherOptions {
+ char_word_separators: vec![".".to_string(), ",".to_string()],
+ ..Default::default()
+ },
+ );
+
+ assert_eq!(get_matches_after_str("hi", &matcher), vec![1]);
+ assert_eq!(get_matches_after_str("Hi", &matcher), vec![1]);
+ assert_eq!(get_matches_after_str("HI", &matcher), vec![1]);
+ assert_eq!(get_matches_after_str("arty", &matcher), vec![3]);
+ assert_eq!(get_matches_after_str("arTY", &matcher), vec![3]);
+ assert_eq!(get_matches_after_str("ARTY", &matcher), vec![]);
}
}
diff --git a/espanso-match/src/rolling/mod.rs b/espanso-match/src/rolling/mod.rs
index 8322a82..e69450c 100644
--- a/espanso-match/src/rolling/mod.rs
+++ b/espanso-match/src/rolling/mod.rs
@@ -22,7 +22,7 @@ use crate::event::Key;
mod matcher;
mod tree;
-#[derive(Debug, PartialEq)]
+#[derive(Debug, Clone, PartialEq)]
pub enum RollingItem {
WordSeparator,
Key(Key),
@@ -68,6 +68,13 @@ impl RollingMatch {
Self { id, items }
}
+
+ pub fn from_items(id: Id, items: &[RollingItem]) -> Self {
+ Self {
+ id,
+ items: items.to_vec(),
+ }
+ }
}
pub struct StringMatchOptions {
diff --git a/espanso-match/src/rolling/tree.rs b/espanso-match/src/rolling/tree.rs
index e8f3dcf..b42bb0d 100644
--- a/espanso-match/src/rolling/tree.rs
+++ b/espanso-match/src/rolling/tree.rs
@@ -21,15 +21,15 @@ use unicase::UniCase;
use crate::event::Key;
-use super::RollingItem;
+use super::{RollingItem, RollingMatch};
-#[derive(Debug)]
+#[derive(Debug, PartialEq)]
pub(crate) enum MatcherTreeRef {
Matches(Vec),
Node(Box>),
}
-#[derive(Debug)]
+#[derive(Debug, PartialEq)]
pub(crate) struct MatcherTreeNode {
pub word_separators: Option>,
pub keys: Vec<(Key, MatcherTreeRef)>,
@@ -37,7 +37,7 @@ pub(crate) struct MatcherTreeNode {
pub chars_insensitive: Vec<(UniCase, MatcherTreeRef)>,
}
-impl Default for MatcherTreeNode {
+impl Default for MatcherTreeNode {
fn default() -> Self {
Self {
word_separators: None,
@@ -48,10 +48,312 @@ impl Default for MatcherTreeNode {
}
}
-impl MatcherTreeNode {
- // TODO: test
- pub fn from_items(items: &[RollingItem]) -> MatcherTreeNode {
- // TODO: implement the tree building algorithm
- todo!()
+impl MatcherTreeNode
+where
+ Id: Clone,
+{
+ pub fn from_matches(matches: &[RollingMatch]) -> MatcherTreeNode {
+ let mut root = MatcherTreeNode::default();
+ for m in matches {
+ insert_items_recursively(m.id.clone(), &mut root, &m.items);
+ }
+ root
+ }
+}
+
+fn insert_items_recursively(id: Id, node: &mut MatcherTreeNode, items: &[RollingItem]) {
+ if items.is_empty() {
+ return;
+ }
+
+ if items.len() == 1 {
+ let item = items.get(0).unwrap();
+ match item {
+ RollingItem::WordSeparator => {
+ let mut new_matches = Vec::new();
+ if let Some(node_ref) = node.word_separators.take() {
+ if let MatcherTreeRef::Matches(matches) = node_ref {
+ new_matches.extend(matches);
+ }
+ }
+ new_matches.push(id);
+ node.word_separators = Some(MatcherTreeRef::Matches(new_matches))
+ }
+ RollingItem::Key(key) => {
+ if let Some(entry) = node.keys.iter_mut().find(|(_key, _)| _key == key) {
+ if let MatcherTreeRef::Matches(matches) = &mut entry.1 {
+ matches.push(id)
+ } else {
+ entry.1 = MatcherTreeRef::Matches(vec![id])
+ };
+ } else {
+ node
+ .keys
+ .push((key.clone(), MatcherTreeRef::Matches(vec![id])));
+ }
+ }
+ RollingItem::Char(c) => {
+ if let Some(entry) = node.chars.iter_mut().find(|(_c, _)| _c == c) {
+ if let MatcherTreeRef::Matches(matches) = &mut entry.1 {
+ matches.push(id)
+ } else {
+ entry.1 = MatcherTreeRef::Matches(vec![id])
+ };
+ } else {
+ node
+ .chars
+ .push((c.clone(), MatcherTreeRef::Matches(vec![id])));
+ }
+ }
+ RollingItem::CharInsensitive(c) => {
+ let uni_char = UniCase::new(c.clone());
+ if let Some(entry) = node
+ .chars_insensitive
+ .iter_mut()
+ .find(|(_c, _)| _c == &uni_char)
+ {
+ if let MatcherTreeRef::Matches(matches) = &mut entry.1 {
+ matches.push(id)
+ } else {
+ entry.1 = MatcherTreeRef::Matches(vec![id])
+ };
+ } else {
+ node
+ .chars_insensitive
+ .push((uni_char, MatcherTreeRef::Matches(vec![id])));
+ }
+ }
+ }
+ } else {
+ let item = items.get(0).unwrap();
+ match item {
+ RollingItem::WordSeparator => match node.word_separators.as_mut() {
+ Some(MatcherTreeRef::Node(next_node)) => {
+ insert_items_recursively(id, next_node.as_mut(), &items[1..])
+ }
+ None => {
+ let mut next_node = Box::new(MatcherTreeNode::default());
+ insert_items_recursively(id, next_node.as_mut(), &items[1..]);
+ node.word_separators = Some(MatcherTreeRef::Node(next_node));
+ }
+ _ => {}
+ },
+ RollingItem::Key(key) => {
+ if let Some(entry) = node.keys.iter_mut().find(|(_key, _)| _key == key) {
+ if let MatcherTreeRef::Node(next_node) = &mut entry.1 {
+ insert_items_recursively(id, next_node, &items[1..])
+ }
+ } else {
+ let mut next_node = Box::new(MatcherTreeNode::default());
+ insert_items_recursively(id, next_node.as_mut(), &items[1..]);
+ node
+ .keys
+ .push((key.clone(), MatcherTreeRef::Node(next_node)));
+ }
+ }
+ RollingItem::Char(c) => {
+ if let Some(entry) = node.chars.iter_mut().find(|(_c, _)| _c == c) {
+ if let MatcherTreeRef::Node(next_node) = &mut entry.1 {
+ insert_items_recursively(id, next_node, &items[1..])
+ }
+ } else {
+ let mut next_node = Box::new(MatcherTreeNode::default());
+ insert_items_recursively(id, next_node.as_mut(), &items[1..]);
+ node
+ .chars
+ .push((c.clone(), MatcherTreeRef::Node(next_node)));
+ }
+ }
+ RollingItem::CharInsensitive(c) => {
+ let uni_char = UniCase::new(c.clone());
+ if let Some(entry) = node
+ .chars_insensitive
+ .iter_mut()
+ .find(|(_c, _)| _c == &uni_char)
+ {
+ if let MatcherTreeRef::Node(next_node) = &mut entry.1 {
+ insert_items_recursively(id, next_node, &items[1..])
+ }
+ } else {
+ let mut next_node = Box::new(MatcherTreeNode::default());
+ insert_items_recursively(id, next_node.as_mut(), &items[1..]);
+ node
+ .chars_insensitive
+ .push((uni_char, MatcherTreeRef::Node(next_node)));
+ }
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::rolling::StringMatchOptions;
+
+ #[test]
+ fn generate_tree_from_items_simple_strings() {
+ let root = MatcherTreeNode::from_matches(&[
+ RollingMatch::from_string(1, "hi", &StringMatchOptions::default()),
+ RollingMatch::from_string(2, "hey", &StringMatchOptions::default()),
+ RollingMatch::from_string(3, "my", &StringMatchOptions::default()),
+ RollingMatch::from_string(4, "myself", &StringMatchOptions::default()),
+ ]);
+
+ assert_eq!(
+ root,
+ MatcherTreeNode {
+ chars: vec![
+ (
+ "h".to_string(),
+ MatcherTreeRef::Node(Box::new(MatcherTreeNode {
+ chars: vec![
+ ("i".to_string(), MatcherTreeRef::Matches(vec![1])),
+ (
+ "e".to_string(),
+ MatcherTreeRef::Node(Box::new(MatcherTreeNode {
+ chars: vec![("y".to_string(), MatcherTreeRef::Matches(vec![2]))],
+ ..Default::default()
+ }))
+ ),
+ ],
+ ..Default::default()
+ }))
+ ),
+ (
+ "m".to_string(),
+ MatcherTreeRef::Node(Box::new(MatcherTreeNode {
+ chars: vec![("y".to_string(), MatcherTreeRef::Matches(vec![3])),],
+ ..Default::default()
+ }))
+ )
+ ],
+ ..Default::default()
+ }
+ )
+ }
+
+ #[test]
+ fn generate_tree_from_items_keys() {
+ let root = MatcherTreeNode::from_matches(&[
+ RollingMatch::from_items(
+ 1,
+ &[
+ RollingItem::Key(Key::ArrowUp),
+ RollingItem::Key(Key::ArrowLeft),
+ ],
+ ),
+ RollingMatch::from_items(
+ 2,
+ &[
+ RollingItem::Key(Key::ArrowUp),
+ RollingItem::Key(Key::ArrowRight),
+ ],
+ ),
+ ]);
+
+ assert_eq!(
+ root,
+ MatcherTreeNode {
+ keys: vec![(
+ Key::ArrowUp,
+ MatcherTreeRef::Node(Box::new(MatcherTreeNode {
+ keys: vec![
+ (Key::ArrowLeft, MatcherTreeRef::Matches(vec![1])),
+ (Key::ArrowRight, MatcherTreeRef::Matches(vec![2])),
+ ],
+ ..Default::default()
+ }))
+ ),],
+ ..Default::default()
+ }
+ )
+ }
+
+ #[test]
+ fn generate_tree_from_items_mixed() {
+ let root = MatcherTreeNode::from_matches(&[
+ RollingMatch::from_items(
+ 1,
+ &[
+ RollingItem::Key(Key::ArrowUp),
+ RollingItem::Key(Key::ArrowLeft),
+ ],
+ ),
+ RollingMatch::from_string(
+ 2,
+ "my",
+ &StringMatchOptions {
+ left_word: true,
+ ..Default::default()
+ },
+ ),
+ RollingMatch::from_string(
+ 3,
+ "hi",
+ &StringMatchOptions {
+ left_word: true,
+ right_word: true,
+ ..Default::default()
+ },
+ ),
+ RollingMatch::from_string(
+ 4,
+ "no",
+ &StringMatchOptions {
+ case_insensitive: true,
+ ..Default::default()
+ },
+ ),
+ ]);
+
+ assert_eq!(
+ root,
+ MatcherTreeNode {
+ keys: vec![(
+ Key::ArrowUp,
+ MatcherTreeRef::Node(Box::new(MatcherTreeNode {
+ keys: vec![(Key::ArrowLeft, MatcherTreeRef::Matches(vec![1])),],
+ ..Default::default()
+ }))
+ ),],
+ word_separators: Some(MatcherTreeRef::Node(Box::new(MatcherTreeNode {
+ chars: vec![
+ (
+ "m".to_string(),
+ MatcherTreeRef::Node(Box::new(MatcherTreeNode {
+ chars: vec![("y".to_string(), MatcherTreeRef::Matches(vec![2])),],
+ ..Default::default()
+ }))
+ ),
+ (
+ "h".to_string(),
+ MatcherTreeRef::Node(Box::new(MatcherTreeNode {
+ chars: vec![(
+ "i".to_string(),
+ MatcherTreeRef::Node(Box::new(MatcherTreeNode {
+ word_separators: Some(MatcherTreeRef::Matches(vec![3])),
+ ..Default::default()
+ }))
+ ),],
+ ..Default::default()
+ }))
+ ),
+ ],
+ ..Default::default()
+ }))),
+ chars_insensitive: vec![(
+ UniCase::new("n".to_string()),
+ MatcherTreeRef::Node(Box::new(MatcherTreeNode {
+ chars_insensitive: vec![(
+ UniCase::new("o".to_string()),
+ MatcherTreeRef::Matches(vec![4])
+ ),],
+ ..Default::default()
+ }))
+ ),],
+ ..Default::default()
+ }
+ )
}
}