Finalize first draft of RollingMatcher
This commit is contained in:
		
							parent
							
								
									caf72c9aef
								
							
						
					
					
						commit
						b2f28bb739
					
				| 
						 | 
				
			
			@ -26,7 +26,7 @@ pub enum Event {
 | 
			
		|||
  VirtualSeparator,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq)]
 | 
			
		||||
pub enum Key {
 | 
			
		||||
  // Modifiers
 | 
			
		||||
  Alt,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,6 @@
 | 
			
		|||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
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<Id>);
 | 
			
		||||
  fn process(&'a self, prev_state: Option<&State>, event: Event) -> (State,  Vec<Id>);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
pub enum RollingItem {
 | 
			
		||||
  WordSeparator,
 | 
			
		||||
  Key(Key),
 | 
			
		||||
  Char(String),
 | 
			
		||||
  CharInsensitive(String),
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -17,19 +17,35 @@
 | 
			
		|||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
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<Id>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<String>,
 | 
			
		||||
  key_word_separators: Vec<Key>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<Id> {
 | 
			
		|||
  root: MatcherTreeNode<Id>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
impl <'a, Id> Matcher<'a, RollingMatcherState<'a, Id>, Id> for RollingMatcher<Id> where Id: Clone {
 | 
			
		||||
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>>,
 | 
			
		||||
    prev_state: Option<&RollingMatcherState<'a, Id>>,
 | 
			
		||||
    event: Event,
 | 
			
		||||
  ) -> (RollingMatcherState<'a, Id>, Vec<Id>) {
 | 
			
		||||
    let mut next_refs = Vec::new();
 | 
			
		||||
| 
						 | 
				
			
			@ -68,31 +86,34 @@ impl <'a, Id> Matcher<'a, RollingMatcherState<'a, Id>, Id> for RollingMatcher<Id
 | 
			
		|||
        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,
 | 
			
		||||
    };
 | 
			
		||||
    let current_state = RollingMatcherState { nodes: next_nodes };
 | 
			
		||||
 | 
			
		||||
    (current_state, Vec::new())
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl <Id> RollingMatcher<Id> {
 | 
			
		||||
  pub fn new(matches: &[RollingMatch<Id>]) -> Self {
 | 
			
		||||
    todo!()
 | 
			
		||||
    // Self {
 | 
			
		||||
 | 
			
		||||
    // }
 | 
			
		||||
impl<Id: Clone> RollingMatcher<Id> {
 | 
			
		||||
  pub fn new(matches: &[RollingMatch<Id>], 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<Id>, event: &Event) -> Vec<&'a MatcherTreeRef<Id>> {
 | 
			
		||||
  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, chars } = event {
 | 
			
		||||
| 
						 | 
				
			
			@ -140,7 +161,6 @@ impl <Id> RollingMatcher<Id> {
 | 
			
		|||
        }
 | 
			
		||||
      }
 | 
			
		||||
      Event::VirtualSeparator => true,
 | 
			
		||||
      _ => false,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -148,38 +168,107 @@ impl <Id> RollingMatcher<Id> {
 | 
			
		|||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
  use super::*;
 | 
			
		||||
  use crate::rolling::{StringMatchOptions};
 | 
			
		||||
 | 
			
		||||
  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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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![]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Id> RollingMatch<Id> {
 | 
			
		|||
 | 
			
		||||
    Self { id, items }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pub fn from_items(id: Id, items: &[RollingItem]) -> Self {
 | 
			
		||||
    Self {
 | 
			
		||||
      id,
 | 
			
		||||
      items: items.to_vec(),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct StringMatchOptions {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Id> {
 | 
			
		||||
  Matches(Vec<Id>),
 | 
			
		||||
  Node(Box<MatcherTreeNode<Id>>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
pub(crate) struct MatcherTreeNode<Id> {
 | 
			
		||||
  pub word_separators: Option<MatcherTreeRef<Id>>,
 | 
			
		||||
  pub keys: Vec<(Key, MatcherTreeRef<Id>)>,
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +37,7 @@ pub(crate) struct MatcherTreeNode<Id> {
 | 
			
		|||
  pub chars_insensitive: Vec<(UniCase<String>, MatcherTreeRef<Id>)>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl <Id> Default for MatcherTreeNode<Id> {
 | 
			
		||||
impl<Id> Default for MatcherTreeNode<Id> {
 | 
			
		||||
  fn default() -> Self {
 | 
			
		||||
    Self {
 | 
			
		||||
      word_separators: None,
 | 
			
		||||
| 
						 | 
				
			
			@ -48,10 +48,312 @@ impl <Id> Default for MatcherTreeNode<Id> {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl <Id> MatcherTreeNode<Id> {
 | 
			
		||||
  // TODO: test
 | 
			
		||||
  pub fn from_items(items: &[RollingItem]) -> MatcherTreeNode<Id> {
 | 
			
		||||
    // TODO: implement the tree building algorithm
 | 
			
		||||
    todo!()
 | 
			
		||||
impl<Id> MatcherTreeNode<Id>
 | 
			
		||||
where
 | 
			
		||||
  Id: Clone,
 | 
			
		||||
{
 | 
			
		||||
  pub fn from_matches(matches: &[RollingMatch<Id>]) -> MatcherTreeNode<Id> {
 | 
			
		||||
    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: Id, node: &mut MatcherTreeNode<Id>, 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()
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user