Improve the RollingMatcher implementation
This commit is contained in:
		
							parent
							
								
									1103278ccd
								
							
						
					
					
						commit
						784e074795
					
				| 
						 | 
				
			
			@ -21,7 +21,7 @@
 | 
			
		|||
pub enum Event {
 | 
			
		||||
  Key {
 | 
			
		||||
    key: Key, 
 | 
			
		||||
    char: Option<String>
 | 
			
		||||
    chars: Option<String>
 | 
			
		||||
  },
 | 
			
		||||
  VirtualSeparator,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,7 @@
 | 
			
		|||
 */
 | 
			
		||||
 | 
			
		||||
use std::any::Any;
 | 
			
		||||
use event::Event;
 | 
			
		||||
 | 
			
		||||
#[macro_use]
 | 
			
		||||
extern crate lazy_static;
 | 
			
		||||
| 
						 | 
				
			
			@ -25,7 +26,6 @@ extern crate lazy_static;
 | 
			
		|||
pub mod event;
 | 
			
		||||
pub mod rolling;
 | 
			
		||||
 | 
			
		||||
pub trait Matcher {
 | 
			
		||||
  // TODO: create suitable event type
 | 
			
		||||
  fn process(&self, prev_state: Option<&dyn Any>, event: Option<bool>) -> (Box<dyn Any>,  Vec<i32>);
 | 
			
		||||
pub trait Matcher<'a, State, Id> where Id: Clone {
 | 
			
		||||
  fn process(&'a self, prev_state: Option<&'a State>, event: Event) -> (State,  Vec<Id>);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -18,44 +18,90 @@
 | 
			
		|||
 */
 | 
			
		||||
 | 
			
		||||
use unicase::UniCase;
 | 
			
		||||
 | 
			
		||||
use crate::Matcher;
 | 
			
		||||
use crate::event::{Event, Key};
 | 
			
		||||
use super::{RollingItem, RollingMatch, tree::{MatcherTreeNode, MatcherTreeRef}};
 | 
			
		||||
 | 
			
		||||
use super::tree::{MatcherTreeNode, MatcherTreeRef};
 | 
			
		||||
pub struct RollingMatcherState<'a, Id> {
 | 
			
		||||
  nodes: Vec<&'a MatcherTreeNode<Id>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct RollingMatcher {
 | 
			
		||||
impl <'a, Id> Default for RollingMatcherState<'a, Id> {
 | 
			
		||||
  fn default() -> Self {
 | 
			
		||||
    Self {
 | 
			
		||||
      nodes: Vec::new(),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct RollingMatcher<Id> {
 | 
			
		||||
  char_word_separators: Vec<String>,
 | 
			
		||||
  key_word_separators: Vec<Key>,
 | 
			
		||||
 | 
			
		||||
  root: MatcherTreeNode,
 | 
			
		||||
  root: MatcherTreeNode<Id>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// impl Matcher for RollingMatcher {
 | 
			
		||||
//   fn process(
 | 
			
		||||
//     &self,
 | 
			
		||||
//     prev_state: &dyn std::any::Any,
 | 
			
		||||
//     event: Option<bool>,
 | 
			
		||||
//   ) -> (Box<dyn std::any::Any>, Vec<i32>) {
 | 
			
		||||
//     todo!()
 | 
			
		||||
//   }
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
impl RollingMatcher {
 | 
			
		||||
  // TODO: to find the matches, we first call the `find_refs` to get the list of matching nodes
 | 
			
		||||
  // then we scan them and if any of those references is of variant `Matches`, then we return those
 | 
			
		||||
  // match ids, otherwise None
 | 
			
		||||
impl <'a, Id> Matcher<'a, RollingMatcherState<'a, Id>, Id> for RollingMatcher<Id> where Id: Clone {
 | 
			
		||||
  fn process(
 | 
			
		||||
    &'a self,
 | 
			
		||||
    prev_state: Option<&'a RollingMatcherState<'a, Id>>,
 | 
			
		||||
    event: Event,
 | 
			
		||||
  ) -> (RollingMatcherState<'a, Id>, Vec<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));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate new ones
 | 
			
		||||
    let root_refs = self.find_refs(&self.root, &event);
 | 
			
		||||
    next_refs.extend(root_refs);
 | 
			
		||||
 | 
			
		||||
    let mut next_nodes = Vec::new();
 | 
			
		||||
 | 
			
		||||
    for node_ref in next_refs {
 | 
			
		||||
      match node_ref {
 | 
			
		||||
        MatcherTreeRef::Matches(matches) => {
 | 
			
		||||
          // 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,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    (current_state, Vec::new())
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl <Id> RollingMatcher<Id> {
 | 
			
		||||
  pub fn new(matches: &[RollingMatch<Id>]) -> Self {
 | 
			
		||||
    todo!()
 | 
			
		||||
    // Self {
 | 
			
		||||
 | 
			
		||||
    // }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 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<Id>, event: &Event) -> Vec<&'a MatcherTreeRef<Id>> {
 | 
			
		||||
    let mut refs = Vec::new();
 | 
			
		||||
 | 
			
		||||
    if let Event::Key { key, char } = event {
 | 
			
		||||
    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);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if let Some(char) = char {
 | 
			
		||||
      if let Some(char) = chars {
 | 
			
		||||
        // Char matching
 | 
			
		||||
        if let Some((_, node_ref)) = node.chars.iter().find(|(_char, _)| _char == char) {
 | 
			
		||||
          refs.push(node_ref);
 | 
			
		||||
| 
						 | 
				
			
			@ -84,10 +130,10 @@ impl RollingMatcher {
 | 
			
		|||
 | 
			
		||||
  fn is_word_separator(&self, event: &Event) -> bool {
 | 
			
		||||
    match event {
 | 
			
		||||
      Event::Key { key, char } => {
 | 
			
		||||
      Event::Key { key, chars } => {
 | 
			
		||||
        if self.key_word_separators.contains(&key) {
 | 
			
		||||
          true
 | 
			
		||||
        } else if let Some(char) = char {
 | 
			
		||||
        } else if let Some(char) = chars {
 | 
			
		||||
          self.char_word_separators.contains(&char)
 | 
			
		||||
        } else {
 | 
			
		||||
          false
 | 
			
		||||
| 
						 | 
				
			
			@ -104,7 +150,36 @@ mod tests {
 | 
			
		|||
  use super::*;
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
  fn 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()
 | 
			
		||||
          }))
 | 
			
		||||
        )
 | 
			
		||||
      ],
 | 
			
		||||
      ..Default::default()
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let matcher = RollingMatcher {
 | 
			
		||||
      char_word_separators: vec![".".to_owned()],
 | 
			
		||||
      key_word_separators: vec![Key::ArrowUp],
 | 
			
		||||
      root,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let (state, matches) = matcher.process(None, Event::Key {
 | 
			
		||||
      key: Key::Other,
 | 
			
		||||
      chars: Some("h".to_string()),
 | 
			
		||||
    });
 | 
			
		||||
    assert_eq!(matches.len(), 0);
 | 
			
		||||
 | 
			
		||||
    let (state, matches) = matcher.process(Some(&state), Event::Key {
 | 
			
		||||
      key: Key::Other,
 | 
			
		||||
      chars: Some("i".to_string()),
 | 
			
		||||
    });
 | 
			
		||||
    assert_eq!(matches, vec![1]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
/*
 | 
			
		||||
 * This file is part of espanso.
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2019-2021 Federico Terzi
 | 
			
		||||
 * Copyright (C) 2019-202 case_insensitive: (), preserve_case_markers: (), left_word: (), right_word: ()1 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
 | 
			
		||||
| 
						 | 
				
			
			@ -17,19 +17,173 @@
 | 
			
		|||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use self::item::RollingItem;
 | 
			
		||||
use crate::event::Key;
 | 
			
		||||
 | 
			
		||||
mod item;
 | 
			
		||||
mod tree;
 | 
			
		||||
mod matcher;
 | 
			
		||||
mod tree;
 | 
			
		||||
 | 
			
		||||
pub struct RollingMatch {
 | 
			
		||||
  id: i32,
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
pub enum RollingItem {
 | 
			
		||||
  WordSeparator,
 | 
			
		||||
  Key(Key),
 | 
			
		||||
  Char(String),
 | 
			
		||||
  CharInsensitive(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
pub struct RollingMatch<Id> {
 | 
			
		||||
  id: Id,
 | 
			
		||||
  items: Vec<RollingItem>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RollingMatch {
 | 
			
		||||
  pub fn new(id: i32, items: Vec<RollingItem>) -> Self {
 | 
			
		||||
impl<Id> RollingMatch<Id> {
 | 
			
		||||
  pub fn new(id: Id, items: Vec<RollingItem>) -> Self {
 | 
			
		||||
    Self { id, items }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub fn from_string(id: Id, string: &str, opt: &StringMatchOptions) -> Self {
 | 
			
		||||
    let mut items = Vec::new();
 | 
			
		||||
 | 
			
		||||
    if opt.left_word {
 | 
			
		||||
      items.push(RollingItem::WordSeparator);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (index, c) in string.chars().enumerate() {
 | 
			
		||||
      if opt.case_insensitive {
 | 
			
		||||
        // If preserve case markers is true, we want to keep the first two chars
 | 
			
		||||
        // as case-sensitive. This is needed to implement the "propagate_case" option.
 | 
			
		||||
        if opt.preserve_case_markers && index < 2 {
 | 
			
		||||
          items.push(RollingItem::Char(c.to_string()))
 | 
			
		||||
        } else {
 | 
			
		||||
          items.push(RollingItem::CharInsensitive(c.to_string()))
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        items.push(RollingItem::Char(c.to_string()))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if opt.right_word {
 | 
			
		||||
      items.push(RollingItem::WordSeparator);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Self { id, items }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct StringMatchOptions {
 | 
			
		||||
  case_insensitive: bool,
 | 
			
		||||
  preserve_case_markers: bool,
 | 
			
		||||
  left_word: bool,
 | 
			
		||||
  right_word: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for StringMatchOptions {
 | 
			
		||||
  fn default() -> Self {
 | 
			
		||||
    Self {
 | 
			
		||||
      case_insensitive: false,
 | 
			
		||||
      preserve_case_markers: false,
 | 
			
		||||
      left_word: false,
 | 
			
		||||
      right_word: false,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
  use super::*;
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
  fn test_match_from_string_base_case() {
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
      RollingMatch::from_string(1, "test", &StringMatchOptions::default()),
 | 
			
		||||
      RollingMatch {
 | 
			
		||||
        id: 1,
 | 
			
		||||
        items: vec![
 | 
			
		||||
          RollingItem::Char("t".to_string()),
 | 
			
		||||
          RollingItem::Char("e".to_string()),
 | 
			
		||||
          RollingItem::Char("s".to_string()),
 | 
			
		||||
          RollingItem::Char("t".to_string()),
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
  fn test_match_from_string_left_word() {
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
      RollingMatch::from_string(1, "test", &StringMatchOptions {
 | 
			
		||||
        left_word: true,
 | 
			
		||||
        ..Default::default()
 | 
			
		||||
      }),
 | 
			
		||||
      RollingMatch {
 | 
			
		||||
        id: 1,
 | 
			
		||||
        items: vec![
 | 
			
		||||
          RollingItem::WordSeparator,
 | 
			
		||||
          RollingItem::Char("t".to_string()),
 | 
			
		||||
          RollingItem::Char("e".to_string()),
 | 
			
		||||
          RollingItem::Char("s".to_string()),
 | 
			
		||||
          RollingItem::Char("t".to_string()),
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
  fn test_match_from_string_right_word() {
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
      RollingMatch::from_string(1, "test", &StringMatchOptions {
 | 
			
		||||
        right_word: true,
 | 
			
		||||
        ..Default::default()
 | 
			
		||||
      }),
 | 
			
		||||
      RollingMatch {
 | 
			
		||||
        id: 1,
 | 
			
		||||
        items: vec![
 | 
			
		||||
          RollingItem::Char("t".to_string()),
 | 
			
		||||
          RollingItem::Char("e".to_string()),
 | 
			
		||||
          RollingItem::Char("s".to_string()),
 | 
			
		||||
          RollingItem::Char("t".to_string()),
 | 
			
		||||
          RollingItem::WordSeparator,
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
  fn test_match_from_string_case_insensitive() {
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
      RollingMatch::from_string(1, "test", &StringMatchOptions {
 | 
			
		||||
        case_insensitive: true,
 | 
			
		||||
        ..Default::default()
 | 
			
		||||
      }),
 | 
			
		||||
      RollingMatch {
 | 
			
		||||
        id: 1,
 | 
			
		||||
        items: vec![
 | 
			
		||||
          RollingItem::CharInsensitive("t".to_string()),
 | 
			
		||||
          RollingItem::CharInsensitive("e".to_string()),
 | 
			
		||||
          RollingItem::CharInsensitive("s".to_string()),
 | 
			
		||||
          RollingItem::CharInsensitive("t".to_string()),
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
  fn test_match_from_string_preserve_case_markers() {
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
      RollingMatch::from_string(1, "test", &StringMatchOptions {
 | 
			
		||||
        case_insensitive: true,
 | 
			
		||||
        preserve_case_markers: true,
 | 
			
		||||
        ..Default::default()
 | 
			
		||||
      }),
 | 
			
		||||
      RollingMatch {
 | 
			
		||||
        id: 1,
 | 
			
		||||
        items: vec![
 | 
			
		||||
          RollingItem::Char("t".to_string()),
 | 
			
		||||
          RollingItem::Char("e".to_string()),
 | 
			
		||||
          RollingItem::CharInsensitive("s".to_string()),
 | 
			
		||||
          RollingItem::CharInsensitive("t".to_string()),
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,26 +21,37 @@ use unicase::UniCase;
 | 
			
		|||
 | 
			
		||||
use crate::event::Key;
 | 
			
		||||
 | 
			
		||||
use super::item::RollingItem;
 | 
			
		||||
use super::RollingItem;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub(crate) enum MatcherTreeRef {
 | 
			
		||||
  Matches(Vec<i32>),
 | 
			
		||||
  Node(Box<MatcherTreeNode>),
 | 
			
		||||
pub(crate) enum MatcherTreeRef<Id> {
 | 
			
		||||
  Matches(Vec<Id>),
 | 
			
		||||
  Node(Box<MatcherTreeNode<Id>>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub(crate) struct MatcherTreeNode {
 | 
			
		||||
  pub word_separators: Option<MatcherTreeRef>,
 | 
			
		||||
  pub keys: Vec<(Key, MatcherTreeRef)>,
 | 
			
		||||
  pub chars: Vec<(String, MatcherTreeRef)>,
 | 
			
		||||
  pub chars_insensitive: Vec<(UniCase<String>, MatcherTreeRef)>,
 | 
			
		||||
pub(crate) struct MatcherTreeNode<Id> {
 | 
			
		||||
  pub word_separators: Option<MatcherTreeRef<Id>>,
 | 
			
		||||
  pub keys: Vec<(Key, MatcherTreeRef<Id>)>,
 | 
			
		||||
  pub chars: Vec<(String, MatcherTreeRef<Id>)>,
 | 
			
		||||
  pub chars_insensitive: Vec<(UniCase<String>, MatcherTreeRef<Id>)>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MatcherTreeNode {
 | 
			
		||||
  // TODO: test
 | 
			
		||||
  pub fn from_items(items: &[RollingItem]) -> MatcherTreeNode {
 | 
			
		||||
    todo!()
 | 
			
		||||
impl <Id> Default for MatcherTreeNode<Id> {
 | 
			
		||||
  fn default() -> Self {
 | 
			
		||||
    Self {
 | 
			
		||||
      word_separators: None,
 | 
			
		||||
      keys: Vec::new(),
 | 
			
		||||
      chars: Vec::new(),
 | 
			
		||||
      chars_insensitive: Vec::new(),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl <Id> MatcherTreeNode<Id> {
 | 
			
		||||
  // TODO: test
 | 
			
		||||
  pub fn from_items(items: &[RollingItem]) -> MatcherTreeNode<Id> {
 | 
			
		||||
    // TODO: implement the tree building algorithm
 | 
			
		||||
    todo!()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user