Finalize first draft of RollingMatcher
This commit is contained in:
parent
caf72c9aef
commit
b2f28bb739
|
@ -26,7 +26,7 @@ pub enum Event {
|
||||||
VirtualSeparator,
|
VirtualSeparator,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Key {
|
pub enum Key {
|
||||||
// Modifiers
|
// Modifiers
|
||||||
Alt,
|
Alt,
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::any::Any;
|
|
||||||
use event::Event;
|
use event::Event;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -27,5 +26,5 @@ pub mod event;
|
||||||
pub mod rolling;
|
pub mod rolling;
|
||||||
|
|
||||||
pub trait Matcher<'a, State, Id> where Id: Clone {
|
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/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use unicase::UniCase;
|
use super::{
|
||||||
use crate::Matcher;
|
tree::{MatcherTreeNode, MatcherTreeRef},
|
||||||
|
RollingMatch,
|
||||||
|
};
|
||||||
use crate::event::{Event, Key};
|
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> {
|
pub struct RollingMatcherState<'a, Id> {
|
||||||
nodes: Vec<&'a MatcherTreeNode<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 {
|
fn default() -> Self {
|
||||||
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>,
|
root: MatcherTreeNode<Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, Id> Matcher<'a, RollingMatcherState<'a, Id>, Id> for RollingMatcher<Id>
|
||||||
impl <'a, Id> Matcher<'a, RollingMatcherState<'a, Id>, Id> for RollingMatcher<Id> where Id: Clone {
|
where
|
||||||
|
Id: Clone,
|
||||||
|
{
|
||||||
fn process(
|
fn process(
|
||||||
&'a self,
|
&'a self,
|
||||||
prev_state: Option<&'a RollingMatcherState<'a, Id>>,
|
prev_state: Option<&RollingMatcherState<'a, Id>>,
|
||||||
event: Event,
|
event: Event,
|
||||||
) -> (RollingMatcherState<'a, Id>, Vec<Id>) {
|
) -> (RollingMatcherState<'a, Id>, Vec<Id>) {
|
||||||
let mut next_refs = Vec::new();
|
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) => {
|
MatcherTreeRef::Matches(matches) => {
|
||||||
// Reset the state and return the matches
|
// Reset the state and return the matches
|
||||||
return (RollingMatcherState::default(), matches.clone());
|
return (RollingMatcherState::default(), matches.clone());
|
||||||
},
|
}
|
||||||
MatcherTreeRef::Node(node) => {
|
MatcherTreeRef::Node(node) => {
|
||||||
next_nodes.push(node.as_ref());
|
next_nodes.push(node.as_ref());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_state = RollingMatcherState {
|
let current_state = RollingMatcherState { nodes: next_nodes };
|
||||||
nodes: next_nodes,
|
|
||||||
};
|
|
||||||
|
|
||||||
(current_state, Vec::new())
|
(current_state, Vec::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <Id> RollingMatcher<Id> {
|
impl<Id: Clone> RollingMatcher<Id> {
|
||||||
pub fn new(matches: &[RollingMatch<Id>]) -> Self {
|
pub fn new(matches: &[RollingMatch<Id>], opt: RollingMatcherOptions) -> Self {
|
||||||
todo!()
|
let root = MatcherTreeNode::from_matches(matches);
|
||||||
// Self {
|
Self {
|
||||||
|
root,
|
||||||
// }
|
char_word_separators: opt.char_word_separators,
|
||||||
|
key_word_separators: opt.key_word_separators,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
fn find_refs<'a>(
|
||||||
fn find_refs<'a>(&'a self, node: &'a MatcherTreeNode<Id>, event: &Event) -> Vec<&'a MatcherTreeRef<Id>> {
|
&'a self,
|
||||||
|
node: &'a MatcherTreeNode<Id>,
|
||||||
|
event: &Event,
|
||||||
|
) -> Vec<&'a MatcherTreeRef<Id>> {
|
||||||
let mut refs = Vec::new();
|
let mut refs = Vec::new();
|
||||||
|
|
||||||
if let Event::Key { key, chars } = event {
|
if let Event::Key { key, chars } = event {
|
||||||
|
@ -140,7 +161,6 @@ impl <Id> RollingMatcher<Id> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::VirtualSeparator => true,
|
Event::VirtualSeparator => true,
|
||||||
_ => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,38 +168,107 @@ impl <Id> RollingMatcher<Id> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn test() { // TODO: convert to actual test
|
fn matcher_process_simple_strings() {
|
||||||
let root = MatcherTreeNode {
|
let matcher = RollingMatcher::new(
|
||||||
chars: vec![
|
&[
|
||||||
("h".to_string(), MatcherTreeRef::Node(Box::new(MatcherTreeNode {
|
RollingMatch::from_string(1, "hi", &StringMatchOptions::default()),
|
||||||
chars: vec![
|
RollingMatch::from_string(2, "hey", &StringMatchOptions::default()),
|
||||||
("i".to_string(), MatcherTreeRef::Matches(vec![1])),
|
RollingMatch::from_string(3, "my", &StringMatchOptions::default()),
|
||||||
],
|
RollingMatch::from_string(4, "myself", &StringMatchOptions::default()),
|
||||||
..Default::default()
|
RollingMatch::from_string(5, "hi", &StringMatchOptions::default()),
|
||||||
}))
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
..Default::default()
|
RollingMatcherOptions {
|
||||||
};
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let matcher = RollingMatcher {
|
assert_eq!(get_matches_after_str("hi", &matcher), vec![1, 5]);
|
||||||
char_word_separators: vec![".".to_owned()],
|
assert_eq!(get_matches_after_str("my", &matcher), vec![3]);
|
||||||
key_word_separators: vec![Key::ArrowUp],
|
assert_eq!(get_matches_after_str("invalid", &matcher), vec![]);
|
||||||
root,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let (state, matches) = matcher.process(None, Event::Key {
|
#[test]
|
||||||
key: Key::Other,
|
fn matcher_process_word_matches() {
|
||||||
chars: Some("h".to_string()),
|
let matcher = RollingMatcher::new(
|
||||||
});
|
&[
|
||||||
assert_eq!(matches.len(), 0);
|
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 {
|
assert_eq!(get_matches_after_str("hi", &matcher), vec![]);
|
||||||
key: Key::Other,
|
assert_eq!(get_matches_after_str(".hi,", &matcher), vec![1]);
|
||||||
chars: Some("i".to_string()),
|
}
|
||||||
});
|
|
||||||
assert_eq!(matches, 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 matcher;
|
||||||
mod tree;
|
mod tree;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum RollingItem {
|
pub enum RollingItem {
|
||||||
WordSeparator,
|
WordSeparator,
|
||||||
Key(Key),
|
Key(Key),
|
||||||
|
@ -68,6 +68,13 @@ impl<Id> RollingMatch<Id> {
|
||||||
|
|
||||||
Self { id, items }
|
Self { id, items }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_items(id: Id, items: &[RollingItem]) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
items: items.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StringMatchOptions {
|
pub struct StringMatchOptions {
|
||||||
|
|
|
@ -21,15 +21,15 @@ use unicase::UniCase;
|
||||||
|
|
||||||
use crate::event::Key;
|
use crate::event::Key;
|
||||||
|
|
||||||
use super::RollingItem;
|
use super::{RollingItem, RollingMatch};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub(crate) enum MatcherTreeRef<Id> {
|
pub(crate) enum MatcherTreeRef<Id> {
|
||||||
Matches(Vec<Id>),
|
Matches(Vec<Id>),
|
||||||
Node(Box<MatcherTreeNode<Id>>),
|
Node(Box<MatcherTreeNode<Id>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub(crate) struct MatcherTreeNode<Id> {
|
pub(crate) struct MatcherTreeNode<Id> {
|
||||||
pub word_separators: Option<MatcherTreeRef<Id>>,
|
pub word_separators: Option<MatcherTreeRef<Id>>,
|
||||||
pub keys: Vec<(Key, 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>)>,
|
pub chars_insensitive: Vec<(UniCase<String>, MatcherTreeRef<Id>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <Id> Default for MatcherTreeNode<Id> {
|
impl<Id> Default for MatcherTreeNode<Id> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
word_separators: None,
|
word_separators: None,
|
||||||
|
@ -48,10 +48,312 @@ impl <Id> Default for MatcherTreeNode<Id> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <Id> MatcherTreeNode<Id> {
|
impl<Id> MatcherTreeNode<Id>
|
||||||
// TODO: test
|
where
|
||||||
pub fn from_items(items: &[RollingItem]) -> MatcherTreeNode<Id> {
|
Id: Clone,
|
||||||
// TODO: implement the tree building algorithm
|
{
|
||||||
todo!()
|
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