afrim_preprocessor/lib.rs
1#![deny(missing_docs)]
2//! Preprocess keyboard events for an input method.
3//!
4//! Enables the generation of keyboard event responses from a keyboard input event in an input method
5//! engine.
6//! The `afrim-preprocessor` crate is built on the top of the [`afrim-memory`](afrim_memory) crate.
7//!
8//! # Example
9//!
10//! ```
11//! use afrim_preprocessor::{utils, Command, Preprocessor};
12//! use keyboard_types::{
13//! webdriver::{self, Event},
14//! };
15//! use std::{collections::VecDeque, rc::Rc};
16//!
17//! // Prepares the memory.
18//! let data = utils::load_data("cc ç");
19//! let text_buffer = utils::build_map(data);
20//! let memory = Rc::new(text_buffer);
21//!
22//! // Builds the preprocessor.
23//! let mut preprocessor = Preprocessor::new(memory, 8);
24//!
25//! // Process an input.
26//! let input = "cc";
27//! webdriver::send_keys(input)
28//! .into_iter()
29//! .for_each(|event| {
30//! match event {
31//! // Triggers the generated keyboard input event.
32//! Event::Keyboard(event) => preprocessor.process(event),
33//! _ => unimplemented!(),
34//! };
35//! });
36//!
37//! // Now let's look at the generated commands.
38//! // The expected results without `inhibit` feature.
39//! #[cfg(not(feature = "inhibit"))]
40//! let mut expecteds = VecDeque::from(vec![
41//! Command::Pause,
42//! Command::Delete,
43//! Command::Delete,
44//! Command::CommitText("ç".to_owned()),
45//! Command::Resume,
46//! ]);
47//!
48//! // The expected results with `inhibit` feature.
49//! #[cfg(feature = "inhibit")]
50//! let mut expecteds = VecDeque::from(vec![
51//! Command::Pause,
52//! Command::Delete,
53//! Command::Resume,
54//! Command::Pause,
55//! Command::Delete,
56//! Command::CommitText("ç".to_owned()),
57//! Command::Resume,
58//! ]);
59//!
60//! // Verification.
61//! while let Some(command) = preprocessor.pop_queue() {
62//! assert_eq!(command, expecteds.pop_front().unwrap());
63//! }
64//! ```
65//! **Note**: When dealing with non latin languages. The `inhibit` feature allows for the removal of
66//! unwanted characters typically latin characters, as much as posssible.
67
68mod message;
69
70pub use crate::message::Command;
71pub use afrim_memory::utils;
72use afrim_memory::{Cursor, Node};
73pub use keyboard_types::{Key, KeyState, KeyboardEvent, NamedKey};
74use std::{collections::VecDeque, rc::Rc};
75
76/// The main structure of the preprocessor.
77#[derive(Debug)]
78pub struct Preprocessor {
79 cursor: Cursor,
80 queue: VecDeque<Command>,
81}
82
83impl Preprocessor {
84 /// Initializes a new preprocessor.
85 ///
86 /// The preprocessor needs a memory to operate. You have two options to build this memory.
87 /// - Use the [`afrim-memory`] crate.
88 /// - Use the [`utils`] module.
89 ///
90 /// It also needs you set the capacity of his cursor. We recommend to set a capacity equal
91 /// or greater than N times the maximun sequence length that you want to handle.
92 /// Where N is the number of sequences that you want track in the cursor.
93 ///
94 /// Note that the cursor is the internal memory of the `afrim_preprocessor`.
95 ///
96 /// # Example
97 ///
98 /// ```
99 /// use afrim_preprocessor::{Preprocessor, utils};
100 /// use std::rc::Rc;
101 ///
102 /// // We prepare the memory.
103 /// let data = utils::load_data("uuaf3 ʉ̄ɑ̄");
104 /// let text_buffer = utils::build_map(data);
105 /// let memory = Rc::new(text_buffer);
106 ///
107 /// // We initialize our preprocessor.
108 /// let preprocessor = Preprocessor::new(memory, 8);
109 /// ```
110 pub fn new(memory: Rc<Node>, buffer_size: usize) -> Self {
111 let cursor = Cursor::new(memory, buffer_size);
112 let queue = VecDeque::with_capacity(15);
113
114 Self { cursor, queue }
115 }
116
117 // Cancel the previous operation.
118 fn rollback(&mut self) -> bool {
119 if let Some(out) = self.cursor.undo() {
120 #[cfg(feature = "inhibit")]
121 let start = 0;
122 #[cfg(not(feature = "inhibit"))]
123 let start = 1;
124 let end = out.chars().count();
125
126 (start..end).for_each(|_| self.queue.push_back(Command::Delete));
127
128 // Clear the remaining code
129 while let (None, 1.., ..) = self.cursor.state() {
130 self.cursor.undo();
131 }
132
133 if let (Some(_in), ..) = self.cursor.state() {
134 self.queue.push_back(Command::CommitText(_in));
135 }
136
137 true
138 } else {
139 self.cursor.resume();
140
141 false
142 }
143 }
144
145 // Cancel the previous operation.
146 //
147 // Note that it handles the delete by itself.
148 #[cfg(not(feature = "inhibit"))]
149 fn hard_rollback(&mut self) -> bool {
150 self.queue.push_back(Command::Delete);
151 self.rollback()
152 }
153
154 // Cancel the previous opeartion.
155 //
156 // Note that the delete is supposed already executed.
157 fn soft_rollback(&mut self) -> bool {
158 self.queue.push_back(Command::CleanDelete);
159 self.rollback()
160 }
161
162 /// Preprocess the keyboard input event and returns infos on his internal changes (change on
163 /// the cursor and/or something to commit).
164 ///
165 /// It's useful when you process keyboard input events in bulk. Whether there is something that
166 /// you want to do based on this information, you can decide how to continue.
167 ///
168 /// # Example
169 ///
170 /// ```
171 /// use afrim_preprocessor::{Command, Preprocessor, utils};
172 /// use keyboard_types::{Key::Character, KeyboardEvent};
173 /// use std::{collections::VecDeque, rc::Rc};
174 ///
175 /// // We prepare the memory.
176 /// let data = utils::load_data("i3 ī");
177 /// let text_buffer = utils::build_map(data);
178 /// let memory = Rc::new(text_buffer);
179 ///
180 /// let mut preprocessor = Preprocessor::new(memory, 8);
181 ///
182 /// // We process the input.
183 /// // let input = "si3";
184 ///
185 /// let info = preprocessor.process(KeyboardEvent {
186 /// key: Character("s".to_string()),
187 /// ..Default::default()
188 /// });
189 /// assert_eq!(info, (true, false));
190 ///
191 /// let info = preprocessor.process(KeyboardEvent {
192 /// key: Character("i".to_string()),
193 /// ..Default::default()
194 /// });
195 /// assert_eq!(info, (true, false));
196 ///
197 /// let info = preprocessor.process(KeyboardEvent {
198 /// key: Character("3".to_string()),
199 /// ..Default::default()
200 /// });
201 /// assert_eq!(info, (true, true));
202 ///
203 /// // The input inside the preprocessor.
204 /// assert_eq!(preprocessor.get_input(), "si3".to_owned());
205 ///
206 /// // The generated commands.
207 /// // The expected results without inhibit feature.
208 /// #[cfg(not(feature = "inhibit"))]
209 /// let mut expecteds = VecDeque::from(vec![
210 /// Command::Pause,
211 /// Command::Delete,
212 /// Command::Delete,
213 /// Command::CommitText("ī".to_owned()),
214 /// Command::Resume,
215 /// ]);
216 ///
217 /// // The expected results with inhibit feature.
218 /// #[cfg(feature = "inhibit")]
219 /// let mut expecteds = VecDeque::from(vec![
220 /// Command::Pause,
221 /// Command::Delete,
222 /// Command::Resume,
223 /// Command::Pause,
224 /// Command::Delete,
225 /// Command::Resume,
226 /// Command::Pause,
227 /// Command::Delete,
228 /// Command::CommitText("ī".to_owned()),
229 /// Command::Resume,
230 /// ]);
231 ///
232 /// // Verification.
233 /// while let Some(command) = preprocessor.pop_queue() {
234 /// assert_eq!(command, expecteds.pop_front().unwrap());
235 /// }
236 /// ```
237 pub fn process(&mut self, event: KeyboardEvent) -> (bool, bool) {
238 let (mut changed, mut committed) = (false, false);
239
240 match (event.state, event.key) {
241 (KeyState::Down, Key::Named(NamedKey::Backspace)) => {
242 #[cfg(not(feature = "inhibit"))]
243 {
244 self.pause();
245 committed = self.soft_rollback();
246 self.resume();
247 }
248 #[cfg(feature = "inhibit")]
249 self.cursor.clear();
250 changed = true;
251 }
252 (KeyState::Down, Key::Character(character))
253 if character
254 .chars()
255 .next()
256 .map(|e| e.is_alphanumeric() || e.is_ascii_punctuation())
257 .unwrap_or(false) =>
258 {
259 #[cfg(feature = "inhibit")]
260 self.pause();
261 #[cfg(feature = "inhibit")]
262 self.queue.push_back(Command::Delete);
263
264 let character = character.chars().next().unwrap();
265
266 if let Some(_in) = self.cursor.hit(character) {
267 #[cfg(not(feature = "inhibit"))]
268 self.pause();
269 let mut prev_cursor = self.cursor.clone();
270 prev_cursor.undo();
271 #[cfg(not(feature = "inhibit"))]
272 self.queue.push_back(Command::Delete);
273
274 // Remove the remaining code
275 while let (None, 1.., ..) = prev_cursor.state() {
276 prev_cursor.undo();
277 #[cfg(not(feature = "inhibit"))]
278 self.queue.push_back(Command::Delete);
279 }
280
281 if let (Some(out), ..) = prev_cursor.state() {
282 (0..out.chars().count()).for_each(|_| self.queue.push_back(Command::Delete))
283 }
284
285 self.queue.push_back(Command::CommitText(_in));
286 #[cfg(not(feature = "inhibit"))]
287 self.resume();
288 committed = true;
289 };
290
291 #[cfg(feature = "inhibit")]
292 self.resume();
293 changed = true;
294 }
295 (KeyState::Down, Key::Named(NamedKey::Shift) | Key::Named(NamedKey::CapsLock)) => (),
296 (KeyState::Down, _) => {
297 self.cursor.clear();
298 changed = true;
299 }
300 _ => (),
301 };
302
303 (changed, committed)
304 }
305
306 /// Commit a text.
307 ///
308 /// Generate a command to ensure the commitment of this text.
309 /// Useful when you want deal with auto-completion.
310 ///
311 /// **Note**: Before any commitment, the preprocessor make sure to discard the current input.
312 ///
313 /// # Example
314 ///
315 /// ```
316 /// use afrim_preprocessor::{Command, Preprocessor, utils};
317 /// use keyboard_types::{Key::Character, KeyboardEvent};
318 /// use std::{collections::VecDeque, rc::Rc};
319 ///
320 /// // We prepare the memory.
321 /// let data = utils::load_data("i3 ī");
322 /// let text_buffer = utils::build_map(data);
323 /// let memory = Rc::new(text_buffer);
324 ///
325 /// let mut preprocessor = Preprocessor::new(memory, 8);
326 ///
327 /// // We process the input.
328 /// // let input = "si3";
329 /// preprocessor.process(KeyboardEvent {
330 /// key: Character("s".to_string()),
331 /// ..Default::default()
332 /// });
333 ///
334 /// preprocessor.commit("sī".to_owned());
335 ///
336 /// // The generated commands.
337 /// // The expected results without inhibit feature.
338 /// #[cfg(not(feature = "inhibit"))]
339 /// let mut expecteds = VecDeque::from(vec![
340 /// Command::Pause,
341 /// Command::Delete,
342 /// Command::CommitText("sī".to_owned()),
343 /// Command::Resume,
344 /// ]);
345 ///
346 /// // The expected results with inhibit feature.
347 /// #[cfg(feature = "inhibit")]
348 /// let mut expecteds = VecDeque::from(vec![
349 /// Command::Pause,
350 /// Command::Delete,
351 /// Command::Resume,
352 /// Command::Pause,
353 /// Command::CleanDelete,
354 /// Command::CommitText("sī".to_owned()),
355 /// Command::Resume,
356 /// ]);
357 ///
358 /// // Verification.
359 /// while let Some(command) = preprocessor.pop_queue() {
360 /// assert_eq!(command, expecteds.pop_front().unwrap());
361 /// }
362 /// ```
363 pub fn commit(&mut self, text: String) {
364 self.pause();
365
366 while !self.cursor.is_empty() {
367 #[cfg(not(feature = "inhibit"))]
368 self.hard_rollback();
369 #[cfg(feature = "inhibit")]
370 self.soft_rollback();
371 }
372 #[cfg(feature = "inhibit")]
373 self.cursor.clear();
374 self.queue.push_back(Command::CommitText(text));
375 self.resume();
376 // We clear the buffer
377 self.cursor.clear();
378 }
379
380 // Pauses the keyboard event listerner.
381 fn pause(&mut self) {
382 self.queue.push_back(Command::Pause);
383 }
384
385 // Resumes the keyboard event listener.
386 fn resume(&mut self) {
387 self.queue.push_back(Command::Resume);
388 }
389
390 /// Returns the input present in the internal memory.
391 ///
392 /// It's always useful to know what is inside the memory of the preprocessor for debugging.
393 /// **Note**: The input inside the preprocessor is not always the same than the original because
394 /// of the limited capacity of his internal cursor.
395 ///
396 /// # Example
397 ///
398 /// ```
399 /// use afrim_preprocessor::{Command, Preprocessor, utils};
400 /// use keyboard_types::webdriver::{self, Event};
401 /// use std::{collections::VecDeque, rc::Rc};
402 ///
403 /// // We prepare the memory.
404 /// let data = utils::load_data("i3 ī");
405 /// let text_buffer = utils::build_map(data);
406 /// let memory = Rc::new(text_buffer);
407 ///
408 /// let mut preprocessor = Preprocessor::new(memory, 4);
409 ///
410 /// // We process the input.
411 /// let input = "si3";
412 /// webdriver::send_keys(input)
413 /// .into_iter()
414 /// .for_each(|event| {
415 /// match event {
416 /// // Triggers the generated keyboard input event.
417 /// Event::Keyboard(event) => preprocessor.process(event),
418 /// _ => unimplemented!(),
419 /// };
420 /// });
421 ///
422 /// // The input inside the processor.
423 /// assert_eq!(preprocessor.get_input(), "si3".to_owned());
424 pub fn get_input(&self) -> String {
425 self.cursor
426 .to_sequence()
427 .into_iter()
428 .filter(|c| *c != '\0')
429 .collect::<String>()
430 }
431
432 /// Returns the next command to be executed.
433 ///
434 /// The next command is dropped from the queue and can't be returned anymore.
435 ///
436 /// # Example
437 ///
438 /// ```
439 /// use afrim_preprocessor::{Command, Preprocessor, utils};
440 /// use std::{collections::VecDeque, rc::Rc};
441 ///
442 /// // We prepare the memory.
443 /// let text_buffer = utils::build_map(vec![]);
444 /// let memory = Rc::new(text_buffer);
445 ///
446 /// let mut preprocessor = Preprocessor::new(memory, 8);
447 /// preprocessor.commit("hello".to_owned());
448 ///
449 /// // The expected results.
450 /// let mut expecteds = VecDeque::from(vec![
451 /// Command::Pause,
452 /// Command::CommitText("hello".to_owned()),
453 /// Command::Resume,
454 /// ]);
455 ///
456 /// // Verification.
457 /// while let Some(command) = preprocessor.pop_queue() {
458 /// assert_eq!(command, expecteds.pop_front().unwrap());
459 /// }
460 pub fn pop_queue(&mut self) -> Option<Command> {
461 self.queue.pop_front()
462 }
463
464 /// Clears the queue.
465 ///
466 /// # Example
467 ///
468 /// ```
469 /// use afrim_preprocessor::{Preprocessor, utils};
470 /// use std::rc::Rc;
471 ///
472 /// let data =
473 /// utils::load_data("n* ŋ");
474 /// let text_buffer = utils::build_map(data);
475 /// let memory = Rc::new(text_buffer);
476 ///
477 /// let mut preprocessor = Preprocessor::new(memory, 8);
478 /// preprocessor.commit("hi".to_owned());
479 /// preprocessor.clear_queue();
480 ///
481 /// assert_eq!(preprocessor.pop_queue(), None);
482 /// ```
483 pub fn clear_queue(&mut self) {
484 self.queue.clear();
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use crate::message::Command;
491 use crate::utils;
492 use crate::Preprocessor;
493 use keyboard_types::{
494 webdriver::{self, Event},
495 Key::*,
496 NamedKey,
497 };
498 use std::collections::VecDeque;
499
500 #[test]
501 fn test_process() {
502 use std::rc::Rc;
503
504 let data = utils::load_data("ccced ç\ncc ç");
505 let memory = utils::build_map(data);
506 let mut preprocessor = Preprocessor::new(Rc::new(memory), 8);
507 webdriver::send_keys("ccced").into_iter().for_each(|e| {
508 match e {
509 Event::Keyboard(e) => preprocessor.process(e),
510 _ => unimplemented!(),
511 };
512 });
513 let mut expecteds = VecDeque::from(vec![
514 // c c
515 Command::Pause,
516 Command::Delete,
517 #[cfg(feature = "inhibit")]
518 Command::Resume,
519 #[cfg(feature = "inhibit")]
520 Command::Pause,
521 Command::Delete,
522 Command::CommitText("ç".to_owned()),
523 Command::Resume,
524 // c e d
525 Command::Pause,
526 Command::Delete,
527 #[cfg(feature = "inhibit")]
528 Command::Resume,
529 #[cfg(feature = "inhibit")]
530 Command::Pause,
531 Command::Delete,
532 #[cfg(feature = "inhibit")]
533 Command::Resume,
534 #[cfg(feature = "inhibit")]
535 Command::Pause,
536 Command::Delete,
537 Command::Delete,
538 Command::CommitText("ç".to_owned()),
539 Command::Resume,
540 ]);
541
542 while let Some(command) = preprocessor.pop_queue() {
543 assert_eq!(command, expecteds.pop_front().unwrap());
544 }
545 }
546
547 #[test]
548 fn test_commit() {
549 use afrim_memory::Node;
550 use keyboard_types::KeyboardEvent;
551
552 let mut preprocessor = Preprocessor::new(Node::default().into(), 8);
553 preprocessor.process(KeyboardEvent {
554 key: Character("a".to_owned()),
555 ..Default::default()
556 });
557 preprocessor.commit("word".to_owned());
558
559 let mut expecteds = VecDeque::from(vec![
560 Command::Pause,
561 #[cfg(feature = "inhibit")]
562 Command::Delete,
563 #[cfg(feature = "inhibit")]
564 Command::Resume,
565 #[cfg(feature = "inhibit")]
566 Command::Pause,
567 #[cfg(feature = "inhibit")]
568 Command::CleanDelete,
569 #[cfg(not(feature = "inhibit"))]
570 Command::Delete,
571 Command::CommitText("word".to_owned()),
572 Command::Resume,
573 ]);
574
575 while let Some(command) = preprocessor.pop_queue() {
576 assert_eq!(command, expecteds.pop_front().unwrap());
577 }
578 }
579
580 #[test]
581 fn test_rollback() {
582 use keyboard_types::KeyboardEvent;
583 use std::rc::Rc;
584
585 let data = utils::load_data("ccced ç\ncc ç");
586 let memory = utils::build_map(data);
587 let mut preprocessor = Preprocessor::new(Rc::new(memory), 8);
588 let backspace_event = KeyboardEvent {
589 key: Named(NamedKey::Backspace),
590 ..Default::default()
591 };
592
593 webdriver::send_keys("ccced").into_iter().for_each(|e| {
594 match e {
595 Event::Keyboard(e) => preprocessor.process(e),
596 _ => unimplemented!(),
597 };
598 });
599
600 preprocessor.clear_queue();
601 assert_eq!(preprocessor.get_input(), "ccced".to_owned());
602 preprocessor.process(backspace_event.clone());
603 #[cfg(not(feature = "inhibit"))]
604 assert_eq!(preprocessor.get_input(), "cc".to_owned());
605 #[cfg(not(feature = "inhibit"))]
606 preprocessor.process(backspace_event);
607 assert_eq!(preprocessor.get_input(), "".to_owned());
608
609 let mut expecteds = VecDeque::from(vec![
610 Command::Pause,
611 #[cfg(not(feature = "inhibit"))]
612 Command::CleanDelete,
613 Command::CommitText("ç".to_owned()),
614 Command::Resume,
615 #[cfg(not(feature = "inhibit"))]
616 Command::Pause,
617 #[cfg(not(feature = "inhibit"))]
618 Command::CleanDelete,
619 #[cfg(not(feature = "inhibit"))]
620 Command::Resume,
621 ]);
622
623 while let Some(command) = preprocessor.pop_queue() {
624 assert_eq!(command, expecteds.pop_front().unwrap());
625 }
626 }
627
628 #[test]
629 fn test_advanced() {
630 use std::rc::Rc;
631
632 let data = include_str!("../data/sample.txt");
633 let data = utils::load_data(data);
634 let memory = utils::build_map(data);
635 let mut preprocessor = Preprocessor::new(Rc::new(memory), 64);
636
637 webdriver::send_keys(
638 "u\u{E003}uu\u{E003}uc_ceduuaf3afafaff3uu3\
639 \u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}"
640 ).into_iter().for_each(|e| {
641 match e {
642 Event::Keyboard(e) => preprocessor.process(e),
643 _ => unimplemented!(),
644 };
645 });
646
647 let mut expecteds = VecDeque::from(vec![
648 // Process
649 // u backspace
650 Command::Pause,
651 #[cfg(feature = "inhibit")]
652 Command::Delete,
653 #[cfg(feature = "inhibit")]
654 Command::Resume,
655 #[cfg(not(feature = "inhibit"))]
656 Command::CleanDelete,
657 #[cfg(not(feature = "inhibit"))]
658 Command::Resume,
659 // u u backspace
660 Command::Pause,
661 Command::Delete,
662 #[cfg(feature = "inhibit")]
663 Command::Resume,
664 #[cfg(feature = "inhibit")]
665 Command::Pause,
666 Command::Delete,
667 Command::CommitText("ʉ".to_owned()),
668 Command::Resume,
669 #[cfg(not(feature = "inhibit"))]
670 Command::Pause,
671 #[cfg(not(feature = "inhibit"))]
672 Command::CleanDelete,
673 #[cfg(not(feature = "inhibit"))]
674 Command::Resume,
675 // u
676 #[cfg(feature = "inhibit")]
677 Command::Pause,
678 #[cfg(feature = "inhibit")]
679 Command::Delete,
680 #[cfg(feature = "inhibit")]
681 Command::Resume,
682 // c _
683 Command::Pause,
684 Command::Delete,
685 #[cfg(feature = "inhibit")]
686 Command::Resume,
687 #[cfg(feature = "inhibit")]
688 Command::Pause,
689 Command::Delete,
690 Command::CommitText("ç".to_owned()),
691 Command::Resume,
692 // c e d
693 Command::Pause,
694 Command::Delete,
695 #[cfg(feature = "inhibit")]
696 Command::Resume,
697 #[cfg(feature = "inhibit")]
698 Command::Pause,
699 Command::Delete,
700 #[cfg(feature = "inhibit")]
701 Command::Resume,
702 #[cfg(feature = "inhibit")]
703 Command::Pause,
704 Command::Delete,
705 Command::Delete,
706 Command::CommitText("ç".to_owned()),
707 Command::Resume,
708 // u u
709 Command::Pause,
710 Command::Delete,
711 #[cfg(feature = "inhibit")]
712 Command::Resume,
713 #[cfg(feature = "inhibit")]
714 Command::Pause,
715 Command::Delete,
716 Command::CommitText("ʉ".to_owned()),
717 Command::Resume,
718 // a f 3
719 Command::Pause,
720 Command::Delete,
721 #[cfg(feature = "inhibit")]
722 Command::Resume,
723 #[cfg(feature = "inhibit")]
724 Command::Pause,
725 Command::Delete,
726 #[cfg(feature = "inhibit")]
727 Command::Resume,
728 #[cfg(feature = "inhibit")]
729 Command::Pause,
730 Command::Delete,
731 Command::Delete,
732 Command::CommitText("ʉ\u{304}ɑ\u{304}".to_owned()),
733 Command::Resume,
734 // a f
735 Command::Pause,
736 Command::Delete,
737 #[cfg(feature = "inhibit")]
738 Command::Resume,
739 #[cfg(feature = "inhibit")]
740 Command::Pause,
741 Command::Delete,
742 Command::CommitText("ɑ".to_owned()),
743 Command::Resume,
744 // a f
745 Command::Pause,
746 Command::Delete,
747 #[cfg(feature = "inhibit")]
748 Command::Resume,
749 #[cfg(feature = "inhibit")]
750 Command::Pause,
751 Command::Delete,
752 Command::CommitText("ɑ".to_owned()),
753 Command::Resume,
754 // a f
755 Command::Pause,
756 Command::Delete,
757 #[cfg(feature = "inhibit")]
758 Command::Resume,
759 #[cfg(feature = "inhibit")]
760 Command::Pause,
761 Command::Delete,
762 Command::CommitText("ɑ".to_owned()),
763 Command::Resume,
764 // f
765 Command::Pause,
766 Command::Delete,
767 Command::Delete,
768 Command::CommitText("ɑɑ".to_owned()),
769 Command::Resume,
770 // 3
771 Command::Pause,
772 Command::Delete,
773 Command::Delete,
774 Command::Delete,
775 Command::CommitText("ɑ\u{304}ɑ\u{304}".to_owned()),
776 Command::Resume,
777 // uu
778 Command::Pause,
779 Command::Delete,
780 #[cfg(feature = "inhibit")]
781 Command::Resume,
782 #[cfg(feature = "inhibit")]
783 Command::Pause,
784 Command::Delete,
785 Command::CommitText("ʉ".to_owned()),
786 Command::Resume,
787 // 3
788 Command::Pause,
789 Command::Delete,
790 Command::Delete,
791 Command::CommitText("ʉ\u{304}".to_owned()),
792 Command::Resume,
793 // Rollback
794 Command::Pause,
795 Command::CleanDelete,
796 Command::Delete,
797 Command::CommitText("ʉ".to_owned()),
798 Command::Resume,
799 Command::Pause,
800 Command::CleanDelete,
801 Command::Resume,
802 Command::Pause,
803 Command::CleanDelete,
804 Command::Delete,
805 Command::Delete,
806 Command::Delete,
807 Command::CommitText("ɑɑ".to_owned()),
808 Command::Resume,
809 Command::Pause,
810 Command::CleanDelete,
811 Command::Delete,
812 Command::CommitText("ɑ".to_owned()),
813 Command::Resume,
814 Command::Pause,
815 Command::CleanDelete,
816 Command::Resume,
817 Command::Pause,
818 Command::CleanDelete,
819 Command::Resume,
820 Command::Pause,
821 Command::CleanDelete,
822 Command::Resume,
823 Command::Pause,
824 Command::CleanDelete,
825 Command::Delete,
826 Command::Delete,
827 Command::Delete,
828 Command::CommitText("ʉ".to_owned()),
829 Command::Resume,
830 Command::Pause,
831 Command::CleanDelete,
832 Command::Resume,
833 Command::Pause,
834 Command::CleanDelete,
835 Command::CommitText("ç".to_owned()),
836 Command::Resume,
837 Command::Pause,
838 Command::CleanDelete,
839 Command::Resume,
840 Command::Pause,
841 Command::CleanDelete,
842 Command::Resume,
843 ]);
844
845 while let Some(command) = preprocessor.pop_queue() {
846 assert_eq!(command, expecteds.pop_front().unwrap());
847 }
848 }
849}