#![deny(missing_docs)]
mod message;
pub use crate::message::Command;
use afrim_memory::Cursor;
pub use afrim_memory::Node;
pub use keyboard_types::{Key, KeyState, KeyboardEvent, NamedKey};
use std::{collections::VecDeque, rc::Rc};
pub mod utils {
pub use afrim_memory::utils::{build_map, load_data};
pub use keyboard_types::webdriver;
}
#[derive(Debug)]
pub struct Preprocessor {
cursor: Cursor,
queue: VecDeque<Command>,
}
impl Preprocessor {
pub fn new(memory: Rc<Node>, buffer_size: usize) -> Self {
let cursor = Cursor::new(memory, buffer_size);
let queue = VecDeque::with_capacity(15);
Self { cursor, queue }
}
fn rollback(&mut self) -> bool {
let (_, _, _curr_char) = self.cursor.state();
if let Some(out) = self.cursor.undo() {
self.queue.push_back(Command::Delete(out));
while let (None, 1.., ..) = self.cursor.state() {
self.cursor.undo();
}
if let (Some(_in), ..) = self.cursor.state() {
self.queue.push_back(Command::CommitText(_in));
}
true
} else {
self.queue
.push_back(Command::Delete(_curr_char.to_string()));
self.cursor.resume();
false
}
}
pub fn process(&mut self, event: KeyboardEvent) -> (bool, bool) {
let (mut changed, mut committed) = (false, false);
match (event.state, event.key) {
(KeyState::Down, Key::Named(NamedKey::Backspace)) => {
{
self.pause();
committed = self.rollback();
self.resume();
}
changed = true;
}
(KeyState::Down, Key::Character(character))
if character
.chars()
.next()
.map(|e| e.is_alphanumeric() || e.is_ascii_punctuation())
.unwrap_or(false) =>
{
let character = character.chars().next().unwrap();
if let Some(_in) = self.cursor.hit(character) {
self.pause();
let mut prev_cursor = self.cursor.clone();
prev_cursor.undo();
self.queue.push_back(Command::Delete(character.to_string()));
while let (None, 1.., _c) = prev_cursor.state() {
prev_cursor.undo();
self.queue.push_back(Command::Delete(_c.to_string()));
}
if let (Some(out), ..) = prev_cursor.state() {
self.queue.push_back(Command::Delete(out))
}
self.queue.push_back(Command::CommitText(_in));
self.resume();
committed = true;
};
changed = true;
}
(KeyState::Down, Key::Named(NamedKey::Shift) | Key::Named(NamedKey::CapsLock)) => (),
(KeyState::Down, _) => {
self.cursor.clear();
changed = true;
}
_ => (),
};
(changed, committed)
}
pub fn commit(&mut self, text: String) {
self.pause();
while !self.cursor.is_empty() {
self.rollback();
}
self.queue.push_back(Command::CommitText(text));
self.resume();
self.cursor.clear();
}
fn pause(&mut self) {
self.queue.push_back(Command::Pause);
}
fn resume(&mut self) {
self.queue.push_back(Command::Resume);
}
pub fn get_input(&self) -> String {
self.cursor
.to_sequence()
.into_iter()
.filter(|c| *c != '\0')
.collect::<String>()
}
pub fn pop_queue(&mut self) -> Option<Command> {
self.queue.pop_front()
}
pub fn clear_queue(&mut self) {
self.queue.clear();
}
}
#[cfg(test)]
mod tests {
use crate::message::Command;
use crate::Preprocessor;
use crate::{
utils::{self, webdriver},
Key::{Character, Named},
KeyboardEvent, NamedKey, Node,
};
use std::collections::VecDeque;
#[test]
fn test_process() {
use std::rc::Rc;
let data = utils::load_data("ccced ç\ncc ç");
let memory = utils::build_map(data);
let mut preprocessor = Preprocessor::new(Rc::new(memory), 8);
webdriver::send_keys("ccced").into_iter().for_each(|e| {
match e {
webdriver::Event::Keyboard(e) => preprocessor.process(e),
_ => unimplemented!(),
};
});
let mut expecteds = VecDeque::from(vec![
Command::Pause,
Command::Delete("c".to_string()),
Command::Delete("c".to_string()),
Command::CommitText("ç".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("d".to_string()),
Command::Delete("e".to_string()),
Command::Delete("c".to_string()),
Command::Delete("ç".to_string()),
Command::CommitText("ç".to_owned()),
Command::Resume,
]);
while let Some(command) = preprocessor.pop_queue() {
assert_eq!(command, expecteds.pop_front().unwrap());
}
assert!(expecteds.is_empty());
}
#[test]
fn test_commit() {
let mut preprocessor = Preprocessor::new(Node::default().into(), 8);
preprocessor.process(KeyboardEvent {
key: Character("a".to_owned()),
..Default::default()
});
preprocessor.commit("word".to_owned());
let mut expecteds = VecDeque::from(vec![
Command::Pause,
Command::Delete("a".to_string()),
Command::CommitText("word".to_owned()),
Command::Resume,
]);
while let Some(command) = preprocessor.pop_queue() {
assert_eq!(command, expecteds.pop_front().unwrap());
}
assert!(expecteds.is_empty());
}
#[test]
fn test_rollback() {
use std::rc::Rc;
let data = utils::load_data("ccced ç\ncc ç");
let memory = utils::build_map(data);
let mut preprocessor = Preprocessor::new(Rc::new(memory), 8);
let backspace_event = KeyboardEvent {
key: Named(NamedKey::Backspace),
..Default::default()
};
webdriver::send_keys("ccced").into_iter().for_each(|e| {
match e {
webdriver::Event::Keyboard(e) => preprocessor.process(e),
_ => unimplemented!(),
};
});
preprocessor.clear_queue();
assert_eq!(preprocessor.get_input(), "ccced".to_owned());
preprocessor.process(backspace_event.clone());
assert_eq!(preprocessor.get_input(), "cc".to_owned());
preprocessor.process(backspace_event);
assert_eq!(preprocessor.get_input(), "".to_owned());
let mut expecteds = VecDeque::from(vec![
Command::Pause,
Command::Delete("ç".to_string()),
Command::CommitText("ç".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("ç".to_string()),
Command::Resume,
]);
while let Some(command) = preprocessor.pop_queue() {
assert_eq!(command, expecteds.pop_front().unwrap());
}
assert!(expecteds.is_empty());
}
#[test]
fn test_advanced() {
use std::rc::Rc;
let data = include_str!("../data/sample.txt");
let data = utils::load_data(data);
let memory = utils::build_map(data);
let mut preprocessor = Preprocessor::new(Rc::new(memory), 64);
webdriver::send_keys(
"u\u{E003}uu\u{E003}uc_ceduuaf3afafaff3uu3\
\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}"
).into_iter().for_each(|e| {
match e {
webdriver::Event::Keyboard(e) => preprocessor.process(e),
_ => unimplemented!(),
};
});
let mut expecteds = VecDeque::from(vec![
Command::Pause,
Command::Delete("u".to_string()),
Command::Resume,
Command::Pause,
Command::Delete("u".to_string()),
Command::Delete("u".to_string()),
Command::CommitText("ʉ".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("ʉ".to_string()),
Command::Resume,
Command::Pause,
Command::Delete("_".to_string()),
Command::Delete("c".to_string()),
Command::CommitText("ç".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("d".to_string()),
Command::Delete("e".to_string()),
Command::Delete("c".to_string()),
Command::Delete("ç".to_string()),
Command::CommitText("ç".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("u".to_string()),
Command::Delete("u".to_string()),
Command::CommitText("ʉ".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("3".to_string()),
Command::Delete("f".to_string()),
Command::Delete("a".to_string()),
Command::Delete("ʉ".to_string()),
Command::CommitText("ʉ\u{304}ɑ\u{304}".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("f".to_string()),
Command::Delete("a".to_string()),
Command::CommitText("ɑ".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("f".to_string()),
Command::Delete("a".to_string()),
Command::CommitText("ɑ".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("f".to_string()),
Command::Delete("a".to_string()),
Command::CommitText("ɑ".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("f".to_string()),
Command::Delete("ɑ".to_string()),
Command::CommitText("ɑɑ".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("3".to_string()),
Command::Delete("ɑɑ".to_string()),
Command::CommitText("ɑ\u{304}ɑ\u{304}".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("u".to_string()),
Command::Delete("u".to_string()),
Command::CommitText("ʉ".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("3".to_string()),
Command::Delete("ʉ".to_string()),
Command::CommitText("ʉ\u{304}".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("ʉ\u{304}".to_string()),
Command::CommitText("ʉ".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("ʉ".to_string()),
Command::Resume,
Command::Pause,
Command::Delete("ɑ\u{304}ɑ\u{304}".to_string()),
Command::CommitText("ɑɑ".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("ɑɑ".to_string()),
Command::CommitText("ɑ".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("ɑ".to_string()),
Command::Resume,
Command::Pause,
Command::Delete("ɑ".to_string()),
Command::Resume,
Command::Pause,
Command::Delete("ɑ".to_string()),
Command::Resume,
Command::Pause,
Command::Delete("ʉ\u{304}ɑ\u{304}".to_string()),
Command::CommitText("ʉ".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("ʉ".to_string()),
Command::Resume,
Command::Pause,
Command::Delete("ç".to_string()),
Command::CommitText("ç".to_owned()),
Command::Resume,
Command::Pause,
Command::Delete("ç".to_string()),
Command::Resume,
Command::Pause,
Command::Delete("\0".to_string()),
Command::Resume,
]);
while let Some(command) = preprocessor.pop_queue() {
let c = expecteds.pop_front().unwrap();
assert_eq!(command, c);
}
assert!(expecteds.is_empty());
}
}