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 {
|
||||
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 {
|
||||
pub fn from_items(items: &[RollingItem]) -> MatcherTreeNode<Id> {
|
||||
// TODO: implement the tree building algorithm
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user