Improve the RollingMatcher implementation
This commit is contained in:
parent
1103278ccd
commit
784e074795
|
@ -21,7 +21,7 @@
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Key {
|
Key {
|
||||||
key: Key,
|
key: Key,
|
||||||
char: Option<String>
|
chars: Option<String>
|
||||||
},
|
},
|
||||||
VirtualSeparator,
|
VirtualSeparator,
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
use event::Event;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
@ -25,7 +26,6 @@ extern crate lazy_static;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod rolling;
|
pub mod rolling;
|
||||||
|
|
||||||
pub trait Matcher {
|
pub trait Matcher<'a, State, Id> where Id: Clone {
|
||||||
// TODO: create suitable event type
|
fn process(&'a self, prev_state: Option<&'a State>, event: Event) -> (State, Vec<Id>);
|
||||||
fn process(&self, prev_state: Option<&dyn Any>, event: Option<bool>) -> (Box<dyn Any>, Vec<i32>);
|
|
||||||
}
|
}
|
|
@ -18,44 +18,90 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
|
use crate::Matcher;
|
||||||
use crate::event::{Event, Key};
|
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>,
|
char_word_separators: Vec<String>,
|
||||||
key_word_separators: Vec<Key>,
|
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 {
|
impl <'a, Id> Matcher<'a, RollingMatcherState<'a, Id>, Id> for RollingMatcher<Id> where Id: Clone {
|
||||||
// TODO: to find the matches, we first call the `find_refs` to get the list of matching nodes
|
fn process(
|
||||||
// then we scan them and if any of those references is of variant `Matches`, then we return those
|
&'a self,
|
||||||
// match ids, otherwise None
|
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
|
// 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();
|
let mut refs = Vec::new();
|
||||||
|
|
||||||
if let Event::Key { key, char } = event {
|
if let Event::Key { key, chars } = event {
|
||||||
// Key matching
|
// Key matching
|
||||||
if let Some((_, node_ref)) = node.keys.iter().find(|(_key, _)| _key == key) {
|
if let Some((_, node_ref)) = node.keys.iter().find(|(_key, _)| _key == key) {
|
||||||
refs.push(node_ref);
|
refs.push(node_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(char) = char {
|
if let Some(char) = chars {
|
||||||
// Char matching
|
// Char matching
|
||||||
if let Some((_, node_ref)) = node.chars.iter().find(|(_char, _)| _char == char) {
|
if let Some((_, node_ref)) = node.chars.iter().find(|(_char, _)| _char == char) {
|
||||||
refs.push(node_ref);
|
refs.push(node_ref);
|
||||||
|
@ -84,10 +130,10 @@ impl RollingMatcher {
|
||||||
|
|
||||||
fn is_word_separator(&self, event: &Event) -> bool {
|
fn is_word_separator(&self, event: &Event) -> bool {
|
||||||
match event {
|
match event {
|
||||||
Event::Key { key, char } => {
|
Event::Key { key, chars } => {
|
||||||
if self.key_word_separators.contains(&key) {
|
if self.key_word_separators.contains(&key) {
|
||||||
true
|
true
|
||||||
} else if let Some(char) = char {
|
} else if let Some(char) = chars {
|
||||||
self.char_word_separators.contains(&char)
|
self.char_word_separators.contains(&char)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -104,7 +150,36 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[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.
|
* 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
|
* espanso is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* 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/>.
|
* 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 matcher;
|
||||||
|
mod tree;
|
||||||
|
|
||||||
pub struct RollingMatch {
|
#[derive(Debug, PartialEq)]
|
||||||
id: i32,
|
pub enum RollingItem {
|
||||||
|
WordSeparator,
|
||||||
|
Key(Key),
|
||||||
|
Char(String),
|
||||||
|
CharInsensitive(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct RollingMatch<Id> {
|
||||||
|
id: Id,
|
||||||
items: Vec<RollingItem>,
|
items: Vec<RollingItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RollingMatch {
|
impl<Id> RollingMatch<Id> {
|
||||||
pub fn new(id: i32, items: Vec<RollingItem>) -> Self {
|
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 }
|
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 crate::event::Key;
|
||||||
|
|
||||||
use super::item::RollingItem;
|
use super::RollingItem;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum MatcherTreeRef {
|
pub(crate) enum MatcherTreeRef<Id> {
|
||||||
Matches(Vec<i32>),
|
Matches(Vec<Id>),
|
||||||
Node(Box<MatcherTreeNode>),
|
Node(Box<MatcherTreeNode<Id>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct MatcherTreeNode {
|
pub(crate) struct MatcherTreeNode<Id> {
|
||||||
pub word_separators: Option<MatcherTreeRef>,
|
pub word_separators: Option<MatcherTreeRef<Id>>,
|
||||||
pub keys: Vec<(Key, MatcherTreeRef)>,
|
pub keys: Vec<(Key, MatcherTreeRef<Id>)>,
|
||||||
pub chars: Vec<(String, MatcherTreeRef)>,
|
pub chars: Vec<(String, MatcherTreeRef<Id>)>,
|
||||||
pub chars_insensitive: Vec<(UniCase<String>, MatcherTreeRef)>,
|
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
|
// TODO: test
|
||||||
pub fn from_items(items: &[RollingItem]) -> MatcherTreeNode {
|
pub fn from_items(items: &[RollingItem]) -> MatcherTreeNode<Id> {
|
||||||
|
// TODO: implement the tree building algorithm
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user