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"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "log",
 | 
			
		||||
 "regex",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,5 +9,4 @@ log = "0.4.14"
 | 
			
		|||
anyhow = "1.0.38"
 | 
			
		||||
thiserror = "1.0.23"
 | 
			
		||||
regex = "1.4.3"
 | 
			
		||||
lazy_static = "1.4.0"
 | 
			
		||||
unicase = "2.6.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +17,7 @@
 | 
			
		|||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq)]
 | 
			
		||||
pub enum Event {
 | 
			
		||||
  Key {
 | 
			
		||||
    key: Key, 
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,16 +17,35 @@
 | 
			
		|||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
use event::Event;
 | 
			
		||||
 | 
			
		||||
#[macro_use]
 | 
			
		||||
extern crate lazy_static;
 | 
			
		||||
 | 
			
		||||
pub mod event;
 | 
			
		||||
pub mod rolling;
 | 
			
		||||
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)
 | 
			
		||||
pub trait Matcher<'a, State, Id> where Id: Clone {
 | 
			
		||||
  fn process(&'a self, prev_state: Option<&State>, event: Event) -> (State,  Vec<Id>);
 | 
			
		||||
}
 | 
			
		||||
#[derive(Debug, Clone, PartialEq)]
 | 
			
		||||
pub struct MatchResult<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/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
use log::error;
 | 
			
		||||
use regex::{Regex, RegexSet};
 | 
			
		||||
 | 
			
		||||
use crate::event::Event;
 | 
			
		||||
use crate::{MatchResult, event::Event};
 | 
			
		||||
use crate::Matcher;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
| 
						 | 
				
			
			@ -68,7 +70,7 @@ where
 | 
			
		|||
    &'a self,
 | 
			
		||||
    prev_state: Option<&RegexMatcherState>,
 | 
			
		||||
    event: Event,
 | 
			
		||||
  ) -> (RegexMatcherState, Vec<Id>) {
 | 
			
		||||
  ) -> (RegexMatcherState, Vec<MatchResult<Id>>) {
 | 
			
		||||
    let mut buffer = if let Some(prev_state) = prev_state {
 | 
			
		||||
      prev_state.buffer.clone()
 | 
			
		||||
    } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -83,13 +85,38 @@ where
 | 
			
		|||
 | 
			
		||||
    // Find matches
 | 
			
		||||
    if self.regex_set.is_match(&buffer) {
 | 
			
		||||
      let mut matches = Vec::new();
 | 
			
		||||
 | 
			
		||||
      for index in self.regex_set.matches(&buffer) {
 | 
			
		||||
        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 {
 | 
			
		||||
          error!("received inconsistent index from regex set with index: {}", index);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if !matches.is_empty() {
 | 
			
		||||
        return (RegexMatcherState::default(), matches)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
  tree::{MatcherTreeNode, MatcherTreeRef},
 | 
			
		||||
  RollingMatch,
 | 
			
		||||
};
 | 
			
		||||
use crate::event::{Event, Key};
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
use super::{RollingMatch, tree::{MatcherTreeNode, MatcherTreeRef}, util::extract_string_from_events};
 | 
			
		||||
use crate::{MatchResult, event::{Event, Key}};
 | 
			
		||||
use crate::Matcher;
 | 
			
		||||
use unicase::UniCase;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct RollingMatcherState<'a, Id> {
 | 
			
		||||
  nodes: Vec<&'a MatcherTreeNode<Id>>,
 | 
			
		||||
  paths: Vec<RollingMatcherStatePath<'a, Id>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a, Id> Default for RollingMatcherState<'a, Id> {
 | 
			
		||||
  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 {
 | 
			
		||||
  char_word_separators: Vec<String>,
 | 
			
		||||
  key_word_separators: Vec<Key>,
 | 
			
		||||
| 
						 | 
				
			
			@ -65,35 +70,53 @@ where
 | 
			
		|||
    &'a self,
 | 
			
		||||
    prev_state: Option<&RollingMatcherState<'a, Id>>,
 | 
			
		||||
    event: Event,
 | 
			
		||||
  ) -> (RollingMatcherState<'a, Id>, Vec<Id>) {
 | 
			
		||||
  ) -> (RollingMatcherState<'a, Id>, Vec<MatchResult<Id>>) {
 | 
			
		||||
    let mut next_refs = Vec::new();
 | 
			
		||||
 | 
			
		||||
    // First compute the old refs
 | 
			
		||||
    if let Some(prev_state) = prev_state {
 | 
			
		||||
      for node_ref in prev_state.nodes.iter() {
 | 
			
		||||
        next_refs.extend(self.find_refs(node_ref, &event));
 | 
			
		||||
      for node_path in prev_state.paths.iter() {
 | 
			
		||||
        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
 | 
			
		||||
    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 {
 | 
			
		||||
        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
 | 
			
		||||
          return (RollingMatcherState::default(), matches.clone());
 | 
			
		||||
          return (RollingMatcherState::default(), results);
 | 
			
		||||
        }
 | 
			
		||||
        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())
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -169,25 +192,14 @@ impl<Id: Clone> RollingMatcher<Id> {
 | 
			
		|||
mod tests {
 | 
			
		||||
  use super::*;
 | 
			
		||||
  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> {
 | 
			
		||||
    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;
 | 
			
		||||
  fn match_result<Id: Default>(id: Id, trigger: &str) -> MatchResult<Id> {
 | 
			
		||||
    MatchResult {
 | 
			
		||||
      id,
 | 
			
		||||
      trigger: trigger.to_string(),
 | 
			
		||||
      ..Default::default()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    matches
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
| 
						 | 
				
			
			@ -205,8 +217,9 @@ mod tests {
 | 
			
		|||
      },
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    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("hi", &matcher), vec![match_result(1, "hi"), match_result(5, "hi")]);
 | 
			
		||||
    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![]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -232,7 +245,7 @@ mod tests {
 | 
			
		|||
    );
 | 
			
		||||
 | 
			
		||||
    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]
 | 
			
		||||
| 
						 | 
				
			
			@ -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![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("hi", &matcher), vec![match_result(1, "hi")]);
 | 
			
		||||
    assert_eq!(get_matches_after_str("Hi", &matcher), vec![match_result(1, "Hi")]);
 | 
			
		||||
    assert_eq!(get_matches_after_str("HI", &matcher), vec![match_result(1, "HI")]);
 | 
			
		||||
    assert_eq!(get_matches_after_str("arty", &matcher), vec![match_result(3, "arty")]);
 | 
			
		||||
    assert_eq!(get_matches_after_str("arTY", &matcher), vec![match_result(3, "arTY")]);
 | 
			
		||||
    assert_eq!(get_matches_after_str("ARTY", &matcher), vec![]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,8 +19,9 @@
 | 
			
		|||
 | 
			
		||||
use crate::event::Key;
 | 
			
		||||
 | 
			
		||||
mod matcher;
 | 
			
		||||
pub mod matcher;
 | 
			
		||||
mod tree;
 | 
			
		||||
mod util;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq)]
 | 
			
		||||
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