diff --git a/engine/translator/Cargo.toml b/engine/translator/Cargo.toml index a5af8ed..8a057a2 100644 --- a/engine/translator/Cargo.toml +++ b/engine/translator/Cargo.toml @@ -17,12 +17,13 @@ default = ["rhai", "strsim"] rhai = ["dep:rhai"] rhai-wasm = ["rhai", "rhai/wasm-bindgen"] strsim = ["dep:strsim"] -serde = ["indexmap/serde", "rhai?/serde"] +serde = ["indexmap/serde", "rhai?/serde", "dep:serde"] [dependencies] rhai = { version = "1.17.1", optional = true, features = ["only_i32", "no_float", "no_closure", "unchecked", "no_position", "no_custom_syntax"] } indexmap = { version = "2.2.6" } strsim = { version = "0.11.0", optional = true } +serde = { version = "1.0.197", optional = true } [dev-dependencies] criterion = "0.5.1" diff --git a/engine/translator/src/lib.rs b/engine/translator/src/lib.rs index fa2e155..7361939 100644 --- a/engine/translator/src/lib.rs +++ b/engine/translator/src/lib.rs @@ -20,7 +20,7 @@ //! # Example //! //! ``` -//! use afrim_translator::Translator; +//! use afrim_translator::{Predicate, Translator}; //! use indexmap::IndexMap; //! //! // Prepares the dictionary. @@ -35,19 +35,19 @@ //! assert_eq!( //! translator.translate("jump"), //! vec![ -//! ( -//! "jump".to_owned(), -//! "".to_owned(), -//! vec!["sauter".to_owned()], -//! true -//! ), +//! Predicate { +//! code: "jump".to_owned(), +//! remaining_code: "".to_owned(), +//! texts: vec!["sauter".to_owned()], +//! can_commit: true +//! }, //! // Auto-completion. -//! ( -//! "jumper".to_owned(), -//! "er".to_owned(), -//! vec!["sauteur".to_owned()], -//! false -//! ) +//! Predicate { +//! code: "jumper".to_owned(), +//! remaining_code: "er".to_owned(), +//! texts: vec!["sauteur".to_owned()], +//! can_commit: false +//! } //! ] //! ); //! ``` @@ -55,7 +55,7 @@ //! # Example with the strsim feature //! //! ``` -//! use afrim_translator::Translator; +//! use afrim_translator::{Predicate, Translator}; //! use indexmap::IndexMap; //! //! // Prepares the dictionary. @@ -70,12 +70,12 @@ //! #[cfg(feature = "strsim")] //! assert_eq!( //! translator.translate("junp"), -//! vec![( -//! "jump".to_owned(), -//! "".to_owned(), -//! vec!["sauter".to_owned()], -//! false -//! )] +//! vec![Predicate { +//! code: "jump".to_owned(), +//! remaining_code: "".to_owned(), +//! texts: vec!["sauter".to_owned()], +//! can_commit: false +//! }] //! ); //! ``` //! @@ -84,7 +84,7 @@ //! ``` //! #[cfg(feature = "rhai")] //! use afrim_translator::Engine; -//! use afrim_translator::Translator; +//! use afrim_translator::{Translator, Predicate}; //! use indexmap::IndexMap; //! //! // Prepares the dictionary. @@ -115,27 +115,27 @@ //! assert_eq!( //! translator.translate("jump"), //! vec![ -//! ( -//! "jump".to_owned(), -//! "".to_owned(), -//! vec!["sauter".to_owned()], -//! true -//! ), +//! Predicate { +//! code: "jump".to_owned(), +//! remaining_code: "".to_owned(), +//! texts: vec!["sauter".to_owned()], +//! can_commit: true +//! }, //! #[cfg(feature = "rhai")] //! // Programmable translation. -//! ( -//! "jump".to_owned(), -//! "".to_owned(), -//! vec!["\n".to_owned()], -//! false -//! ), +//! Predicate { +//! code: "jump".to_owned(), +//! remaining_code: "".to_owned(), +//! texts: vec!["\n".to_owned()], +//! can_commit: false +//! }, //! // Auto-completion. -//! ( -//! "jumper".to_owned(), -//! "er".to_owned(), -//! vec!["sauteur".to_owned()], -//! false -//! ) +//! Predicate { +//! code: "jumper".to_owned(), +//! remaining_code: "er".to_owned(), +//! texts: vec!["sauteur".to_owned()], +//! can_commit: false +//! } //! ] //! ); //! ``` @@ -149,7 +149,19 @@ use std::cmp::Ordering; #[cfg(feature = "strsim")] use strsim::{self}; -type P = (String, String, Vec, bool); +/// Struct representing the predicate. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Predicate { + /// The predicate code. + pub code: String, + /// The remaining code to match the predicate. + pub remaining_code: String, + /// The resulting predicate possible outputs. + pub texts: Vec, + /// Whether the predicate can be commit. + pub can_commit: bool, +} /// Core structure of the translator. pub struct Translator { @@ -189,7 +201,7 @@ impl Translator { /// # Example /// /// ``` - /// use afrim_translator::{Engine, Translator}; + /// use afrim_translator::{Engine, Predicate, Translator}; /// use indexmap::IndexMap; /// /// // We prepare the script. @@ -240,8 +252,12 @@ impl Translator { /// assert_eq!( /// translator.translate("09/02/2024"), /// vec![ - /// ("09/02/2024".to_owned(), "".to_owned(), - /// vec!["9, Feb 2024".to_owned()], true) + /// Predicate { + /// code: "09/02/2024".to_owned(), + /// remaining_code: "".to_owned(), + /// texts: vec!["9, Feb 2024".to_owned()], + /// can_commit: true + /// } /// ] /// ); /// ``` @@ -254,7 +270,7 @@ impl Translator { /// /// # Example /// ``` - /// use afrim_translator::{Engine, Translator}; + /// use afrim_translator::{Engine, Predicate, Translator}; /// use indexmap::IndexMap; /// /// // We prepare the script. @@ -266,7 +282,17 @@ impl Translator { /// /// // We register the erase translator. /// translator.register("erase".to_owned(), erase_translator); - /// assert_eq!(translator.translate("hello"), vec![("hello".to_owned(), "".to_owned(), vec![], true)]); + /// assert_eq!( + /// translator.translate("hello"), + /// vec![ + /// Predicate { + /// code: "hello".to_owned(), + /// remaining_code: "".to_owned(), + /// texts: vec![], + /// can_commit: true + /// } + /// ] + /// ); /// /// // We unregister the erase translator. /// translator.unregister("erase"); @@ -282,7 +308,7 @@ impl Translator { /// /// ``` /// use indexmap::IndexMap; - /// use afrim_translator::Translator; + /// use afrim_translator::{Predicate, Translator}; /// /// // We prepares the dictionary. /// let mut dictionary = IndexMap::new(); @@ -294,37 +320,39 @@ impl Translator { /// assert_eq!( /// translator.translate("sal"), /// vec![ - /// ( - /// "salut!".to_owned(), "ut!".to_owned(), - /// vec!["hello!".to_owned(), "hi!".to_owned()], - /// false - /// ), - /// ( - /// "salade".to_owned(), "ade".to_owned(), - /// vec!["vegetable".to_owned()], - /// false - /// ) + /// Predicate { + /// code: "salut!".to_owned(), + /// remaining_code: "ut!".to_owned(), + /// texts: vec!["hello!".to_owned(), "hi!".to_owned()], + /// can_commit: false + /// }, + /// Predicate { + /// code: "salade".to_owned(), + /// remaining_code: "ade".to_owned(), + /// texts: vec!["vegetable".to_owned()], + /// can_commit: false + /// } /// ] /// ) /// ``` - pub fn translate(&self, input: &str) -> Vec

{ + pub fn translate(&self, input: &str) -> Vec { #[cfg(feature = "rhai")] let mut scope = Scope::new(); #[cfg(feature = "rhai")] let engine = Engine::new(); - let predicates = self.dictionary.iter().filter_map(|(key, value)| { + let predicates = self.dictionary.iter().filter_map(|(key, values)| { if input.len() < 2 || input.len() > key.len() || key[0..1] != input[0..1] { return None; }; let predicate = (key == input).then_some(( 1.0, - ( - key.to_owned(), - "".to_owned(), - value.to_owned(), - self.auto_commit, - ), + Predicate { + code: key.to_owned(), + remaining_code: "".to_owned(), + texts: values.to_owned(), + can_commit: self.auto_commit, + }, )); #[cfg(feature = "strsim")] let predicate = predicate.or_else(|| { @@ -336,7 +364,12 @@ impl Translator { (confidence > 0.7).then(|| { ( confidence, - (key.to_owned(), "".to_owned(), value.to_owned(), false), + Predicate { + code: key.to_owned(), + remaining_code: "".to_owned(), + texts: values.to_owned(), + can_commit: false, + }, ) }) } else { @@ -346,12 +379,12 @@ impl Translator { predicate.or_else(|| { key.starts_with(input).then_some(( 0.5, - ( - key.to_owned(), - key.chars().skip(input.len()).collect(), - value.to_owned(), - false, - ), + Predicate { + code: key.to_owned(), + remaining_code: key.chars().skip(input.len()).collect(), + texts: values.to_owned(), + can_commit: false, + }, )) }) }); @@ -365,22 +398,30 @@ impl Translator { (data.len() == 4).then(|| { let code = data.remove(0).into_string().unwrap(); let remaining_code = data.remove(0).into_string().unwrap(); - let texts = data.remove(0); - let texts = if texts.is_array() { - texts.into_array().unwrap() + let value = data.remove(0); + let values = if value.is_array() { + value.into_array().unwrap() } else { - vec![texts] + vec![value] }; - let texts = texts + let values = values .into_iter() .map(|e| e.into_string().unwrap()) .collect(); let translated = data.remove(0).as_bool().unwrap(); - (1.0, (code, remaining_code, texts, translated)) + ( + 1.0, + Predicate { + code, + remaining_code, + texts: values, + can_commit: translated, + }, + ) }) })); - let mut predicates = predicates.collect::>(); + let mut predicates = predicates.collect::>(); // from the best to the worst predicates.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal)); @@ -398,7 +439,7 @@ mod tests { fn test_translate() { #[cfg(feature = "rhai")] use crate::Engine; - use crate::Translator; + use crate::{Predicate, Translator}; use indexmap::IndexMap; // We build the translation @@ -436,31 +477,31 @@ mod tests { #[cfg(feature = "rhai")] assert_eq!( translator.translate("hi"), - vec![( - "hi".to_owned(), - "".to_owned(), - vec!["hello".to_owned()], - true - )] + vec![Predicate { + code: "hi".to_owned(), + remaining_code: "".to_owned(), + texts: vec!["hello".to_owned()], + can_commit: true + }] ); assert_eq!( translator.translate("ha"), - vec![( - "halo".to_owned(), - "lo".to_owned(), - vec!["hello".to_owned()], - false - )] + vec![Predicate { + code: "halo".to_owned(), + remaining_code: "lo".to_owned(), + texts: vec!["hello".to_owned()], + can_commit: false + }] ); #[cfg(feature = "strsim")] assert_eq!( translator.translate("helo"), - vec![( - "halo".to_owned(), - "".to_owned(), - vec!["hello".to_owned()], - false - )] + vec![Predicate { + code: "halo".to_owned(), + remaining_code: "".to_owned(), + texts: vec!["hello".to_owned()], + can_commit: false + }] ); } } diff --git a/service/src/frontend.rs b/service/src/frontend.rs index 49bc9e7..0160936 100644 --- a/service/src/frontend.rs +++ b/service/src/frontend.rs @@ -2,6 +2,8 @@ //! API to develop a frontend interface for the afrim. //! +pub use afrim_translator::Predicate; + /// Trait that every afrim frontend should implement. pub trait Frontend { /// Updates the frontend screen size. @@ -13,7 +15,7 @@ pub trait Frontend { /// Sets the maximun number of predicates to be display. fn set_page_size(&mut self, _size: usize) {} /// Adds a predicate in the list of predicates. - fn add_predicate(&mut self, _code: &str, _remaining_code: &str, _text: &str) {} + fn add_predicate(&mut self, _predicate: Predicate) {} /// Refreshs the display. fn display(&self) {} /// Clears the list of predicates. @@ -23,7 +25,7 @@ pub trait Frontend { /// Selects the next predicate. fn next_predicate(&mut self) {} /// Returns the selected predicate. - fn get_selected_predicate(&self) -> Option<&(String, String, String)> { + fn get_selected_predicate(&self) -> Option<&Predicate> { Option::None } } @@ -37,7 +39,7 @@ impl Frontend for None {} #[derive(Default)] pub struct Console { page_size: usize, - predicates: Vec<(String, String, String)>, + predicates: Vec, current_predicate_id: usize, input: String, } @@ -66,17 +68,19 @@ impl Frontend for Console { .chain(self.predicates.iter().enumerate()) .skip(self.current_predicate_id) .take(page_size) - .map(|(id, (_code, remaining_code, text))| format!( - "{}{}. {} ~{}\t ", - if id == self.current_predicate_id { - "*" - } else { - "" - }, - id + 1, - text, - remaining_code - )) + .map(|(id, predicate)| { + format!( + "{}{}. {} ~{}\t ", + if id == self.current_predicate_id { + "*" + } else { + "" + }, + id + 1, + predicate.texts[0], + predicate.remaining_code + ) + }) .collect::>() .join("\t") ); @@ -87,9 +91,17 @@ impl Frontend for Console { self.current_predicate_id = 0; } - fn add_predicate(&mut self, code: &str, remaining_code: &str, text: &str) { - self.predicates - .push((code.to_owned(), remaining_code.to_owned(), text.to_owned())); + fn add_predicate(&mut self, predicate: Predicate) { + predicate + .texts + .iter() + .filter(|text| !text.is_empty()) + .for_each(|text| { + let mut predicate = predicate.clone(); + predicate.texts = vec![text.to_owned()]; + + self.predicates.push(predicate); + }); } fn previous_predicate(&mut self) { @@ -111,7 +123,7 @@ impl Frontend for Console { self.display(); } - fn get_selected_predicate(&self) -> Option<&(String, String, String)> { + fn get_selected_predicate(&self) -> Option<&Predicate> { self.predicates.get(self.current_predicate_id) } } @@ -120,6 +132,7 @@ impl Frontend for Console { mod tests { #[test] fn test_none() { + use crate::frontend::Predicate; use crate::frontend::{Frontend, None}; let mut none = None; @@ -128,7 +141,7 @@ mod tests { none.update_position((64.0, 64.0)); none.set_input("input"); none.set_page_size(10); - none.add_predicate("hey", "y", "hello"); + none.add_predicate(Predicate::default()); none.display(); none.clear_predicates(); none.previous_predicate(); @@ -138,7 +151,7 @@ mod tests { #[test] fn test_console() { - use crate::frontend::{Console, Frontend}; + use crate::frontend::{Console, Frontend, Predicate}; let mut console = Console::default(); console.set_page_size(10); @@ -146,19 +159,51 @@ mod tests { console.update_position((0.0, 0.0)); console.set_input("he"); - console.add_predicate("hell", "llo", "hello"); - console.add_predicate("helip", "lip", "helicopter"); - console.add_predicate("heal", "al", "health"); + console.add_predicate(Predicate { + code: "hell".to_owned(), + remaining_code: "llo".to_owned(), + texts: vec!["hello".to_owned()], + can_commit: false, + }); + console.add_predicate(Predicate { + code: "helip".to_owned(), + remaining_code: "lip".to_owned(), + texts: vec![], + can_commit: false, + }); + console.add_predicate(Predicate { + code: "helio".to_owned(), + remaining_code: "s".to_owned(), + texts: vec!["".to_owned()], + can_commit: false, + }); + console.add_predicate(Predicate { + code: "heal".to_owned(), + remaining_code: "al".to_owned(), + texts: vec!["health".to_owned()], + can_commit: false, + }); + assert_eq!(console.predicates.len(), 2); console.display(); console.previous_predicate(); assert_eq!( console.get_selected_predicate(), - Some(&("heal".to_owned(), "al".to_owned(), "health".to_owned())) + Some(&Predicate { + code: "heal".to_owned(), + remaining_code: "al".to_owned(), + texts: vec!["health".to_owned()], + can_commit: false + }) ); console.next_predicate(); assert_eq!( console.get_selected_predicate(), - Some(&("hell".to_owned(), "llo".to_owned(), "hello".to_owned())) + Some(&Predicate { + code: "hell".to_owned(), + remaining_code: "llo".to_owned(), + texts: vec!["hello".to_owned()], + can_commit: false + }) ); console.clear_predicates(); diff --git a/service/src/lib.rs b/service/src/lib.rs index 87550a7..6635729 100644 --- a/service/src/lib.rs +++ b/service/src/lib.rs @@ -90,8 +90,14 @@ pub fn run(config: Config, mut frontend: impl Frontend) -> Result<()> { rdev::simulate(&EventType::KeyRelease(E_Key::ControlLeft)) .expect("We couldn't cancel the special function key"); - if let Some((_code, _remaining_code, text)) = frontend.get_selected_predicate() { - preprocessor.commit(text.to_owned()); + if let Some(predicate) = frontend.get_selected_predicate() { + preprocessor.commit( + predicate + .texts + .first() + .unwrap_or(&String::default()) + .to_owned(), + ); frontend.clear_predicates(); } } @@ -111,16 +117,15 @@ pub fn run(config: Config, mut frontend: impl Frontend) -> Result<()> { translator .translate(&input) - .iter() + .into_iter() .take(page_size * 2) - .for_each(|(code, remaining_code, texts, translated)| { - texts.iter().for_each(|text| { - if auto_commit && *translated { - preprocessor.commit(text.to_owned()); - } else if !text.is_empty() { - frontend.add_predicate(code, remaining_code, text); - } - }); + .for_each(|predicate| { + if predicate.texts.is_empty() { + } else if auto_commit && predicate.can_commit { + preprocessor.commit(predicate.texts[0].to_owned()); + } else { + frontend.add_predicate(predicate); + } }); frontend.set_input(&input);