#![cfg_attr(
not(target_os = "macos"),
allow(dead_code, reason = "only the macOS view tells, for now")
)]
use super::TakenKey;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct KeyPress {
interpreted: bool,
interpreting: u32,
characters: Option<String>,
acted: bool,
committed: bool,
typed: String,
commanded: bool,
marked_left_over: bool,
}
impl KeyPress {
pub(crate) fn begin_interpreting(&mut self) {
self.interpreted = true;
self.interpreting = self.interpreting.saturating_add(1);
}
pub(crate) fn end_interpreting(&mut self) {
self.interpreting = self.interpreting.saturating_sub(1);
}
pub(crate) fn note_characters(&mut self, characters: Option<&str>) {
if !self.interpreted {
self.characters = characters.map(str::to_owned);
}
}
pub(crate) fn note_composed(&mut self) {
self.marked_left_over = false;
if self.interpreting > 0 {
self.acted = true;
}
}
pub(crate) fn note_commit_through(&mut self) {
self.marked_left_over = true;
}
pub(crate) fn marked_text_is_left_over(&self) -> bool {
self.marked_left_over
}
pub(crate) fn note_committed(&mut self) {
if self.interpreting > 0 {
self.acted = true;
self.committed = true;
self.typed.clear();
}
}
pub(crate) fn note_typed(&mut self, text: &str) {
if self.interpreting > 0 {
self.acted = true;
self.committed = false;
self.typed.push_str(text);
}
}
pub(crate) fn note_commanded(&mut self) {
if self.interpreting > 0 {
self.acted = true;
self.commanded = true;
}
}
pub(crate) fn taken(&self) -> Option<TakenKey> {
if !self.interpreted || self.interpreting > 0 || self.commanded {
None
} else if !self.acted {
Some(TakenKey::Swallowed)
} else if self.committed {
Some(TakenKey::Committed)
} else if !self.typed.is_empty()
&& self.typed != self.characters.as_deref().unwrap_or_default()
{
Some(TakenKey::Substituted(self.typed.clone()))
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn press_of(characters: &str, acts: impl FnOnce(&mut KeyPress)) -> KeyPress {
let mut press = KeyPress::default();
press.note_characters(Some(characters));
press.begin_interpreting();
acts(&mut press);
press.end_interpreting();
press
}
fn press(acts: impl FnOnce(&mut KeyPress)) -> KeyPress {
press_of(" ", acts)
}
#[test]
fn a_commits_marked_text_is_a_leftover_until_the_next_mark() {
let mut press = KeyPress::default();
press.begin_interpreting();
press.note_committed();
assert!(
!press.marked_text_is_left_over(),
"not before the commit is through: winit asks while it commits"
);
press.note_commit_through();
assert!(press.marked_text_is_left_over());
press.note_composed();
assert!(!press.marked_text_is_left_over(), "marked again");
press.end_interpreting();
}
#[test]
fn a_key_the_input_method_never_saw_is_not_taken() {
assert_eq!(KeyPress::default().taken(), None, "no input method");
}
#[test]
fn a_typed_letter_is_not_taken() {
assert_eq!(press_of("m", |press| press.note_typed("m")).taken(), None);
assert_eq!(press(|press| press.note_typed(" ")).taken(), None);
}
#[test]
fn a_command_or_a_composition_is_not_taken() {
assert_eq!(press(KeyPress::note_commanded).taken(), None);
assert_eq!(press(KeyPress::note_composed).taken(), None);
}
#[test]
fn a_key_the_input_method_kept_is_swallowed() {
let press = press_of("e", |_| {});
assert_eq!(
press.taken(),
Some(TakenKey::Swallowed),
"the accent popup took the repeat"
);
}
#[test]
fn korean_space_with_nothing_composed_is_the_input_methods_commit() {
let press = press(|press| {
press.note_composed();
press.note_committed();
press.note_composed();
});
assert_eq!(press.taken(), Some(TakenKey::Committed));
}
#[test]
fn kotoeri_space_with_nothing_composed_types_its_full_width_space() {
let press = press(|press| press.note_typed("\u{3000}"));
assert_eq!(
press.taken(),
Some(TakenKey::Substituted("\u{3000}".to_string()))
);
}
#[test]
fn pinyin_punctuation_types_its_full_width_form() {
let press = press_of(";", |press| press.note_typed(";"));
assert_eq!(press.taken(), Some(TakenKey::Substituted(";".to_string())));
}
#[test]
fn several_insertions_in_one_key_are_typed_in_order() {
let press = press_of(";", |press| {
press.note_typed(";");
press.note_typed("“");
});
assert_eq!(
press.taken(),
Some(TakenKey::Substituted(";“".to_string()))
);
let press = press_of("ab", |press| {
press.note_typed("a");
press.note_typed("b");
});
assert_eq!(press.taken(), None);
}
#[test]
fn a_string_the_window_does_not_take_types_the_key() {
let press = press_of("\r", |press| press.note_typed(""));
assert_eq!(press.taken(), None);
}
#[test]
fn a_key_with_no_characters_types_what_the_input_method_typed() {
let mut press = KeyPress::default();
press.note_characters(None);
press.begin_interpreting();
press.note_typed("x");
press.end_interpreting();
assert_eq!(press.taken(), Some(TakenKey::Substituted("x".to_string())));
}
#[test]
fn a_command_after_a_commit_hands_the_key_back() {
let press = press(|press| {
press.note_composed();
press.note_committed();
press.note_composed();
press.note_commanded();
});
assert_eq!(press.taken(), None);
}
#[test]
fn a_command_before_a_commit_hands_the_key_back_too() {
let press = press(|press| {
press.note_composed();
press.note_commanded();
press.note_committed();
});
assert_eq!(press.taken(), None);
}
#[test]
fn typing_after_a_commit_types_the_key_as_typed() {
let own = press(|press| {
press.note_committed();
press.note_composed();
press.note_typed(" ");
});
assert_eq!(own.taken(), None);
let other = press(|press| {
press.note_committed();
press.note_composed();
press.note_typed("\u{3000}");
});
assert_eq!(
other.taken(),
Some(TakenKey::Substituted("\u{3000}".to_string()))
);
}
#[test]
fn a_commit_after_typing_is_the_answer() {
let press = press(|press| {
press.note_typed("\u{3000}");
press.note_composed();
press.note_committed();
});
assert_eq!(press.taken(), Some(TakenKey::Committed));
}
#[test]
fn a_commit_then_a_new_composition_is_still_the_commit() {
let press = press(|press| {
press.note_committed();
press.note_composed();
press.note_composed();
});
assert_eq!(press.taken(), Some(TakenKey::Committed));
}
#[test]
fn nothing_is_decided_while_the_input_method_has_the_key() {
let mut press = KeyPress::default();
press.begin_interpreting();
assert_eq!(press.taken(), None);
press.note_committed();
press.note_characters(Some("x"));
press.begin_interpreting();
press.end_interpreting();
assert_eq!(press.taken(), None, "still inside the outer hand-over");
press.end_interpreting();
assert_eq!(press.taken(), Some(TakenKey::Committed));
}
#[test]
fn a_nested_hand_over_keeps_the_first_characters() {
let mut press = KeyPress::default();
press.note_characters(Some(" "));
press.begin_interpreting();
press.note_characters(Some("\u{3000}"));
press.begin_interpreting();
press.note_typed("\u{3000}");
press.end_interpreting();
press.end_interpreting();
assert_eq!(
press.taken(),
Some(TakenKey::Substituted("\u{3000}".to_string()))
);
}
#[test]
fn an_action_outside_the_hand_over_is_not_an_answer() {
let mut press = KeyPress::default();
press.note_commanded();
press.note_typed("x");
press.begin_interpreting();
press.end_interpreting();
press.note_committed();
press.note_composed();
press.note_typed("y");
assert_eq!(
press.taken(),
Some(TakenKey::Swallowed),
"acting before or after the input method had the key does not count"
);
}
#[test]
fn an_unbalanced_end_does_not_underflow() {
let mut press = KeyPress::default();
press.end_interpreting();
assert_eq!(press.taken(), None);
}
}