use crcbl_core::KeyCode;
use crcbl_core::input::Modifiers;
use super::ffi::value;
const EXTENDED_BIT: isize = 1 << 24;
const PREVIOUS_STATE_BIT: isize = 1 << 30;
const EXTENDED_PREFIX: u32 = 0xE000;
#[must_use]
pub const fn scancode(l_param: isize) -> u32 {
let code = ((l_param >> 16) & 0xFF) as u32;
if l_param & EXTENDED_BIT == 0 {
code
} else {
EXTENDED_PREFIX | code
}
}
#[must_use]
pub const fn was_already_down(l_param: isize) -> bool {
l_param & PREVIOUS_STATE_BIT != 0
}
#[must_use]
pub const fn key_code(scancode: u32) -> Option<KeyCode> {
Some(match scancode {
0x0001 => KeyCode::Escape,
0x0002 => KeyCode::Digit1,
0x0003 => KeyCode::Digit2,
0x0004 => KeyCode::Digit3,
0x0005 => KeyCode::Digit4,
0x0006 => KeyCode::Digit5,
0x0007 => KeyCode::Digit6,
0x0008 => KeyCode::Digit7,
0x0009 => KeyCode::Digit8,
0x000A => KeyCode::Digit9,
0x000B => KeyCode::Digit0,
0x000C => KeyCode::Minus,
0x000D => KeyCode::Equal,
0x000E => KeyCode::Backspace,
0x000F => KeyCode::Tab,
0x0010 => KeyCode::KeyQ,
0x0011 => KeyCode::KeyW,
0x0012 => KeyCode::KeyE,
0x0013 => KeyCode::KeyR,
0x0014 => KeyCode::KeyT,
0x0015 => KeyCode::KeyY,
0x0016 => KeyCode::KeyU,
0x0017 => KeyCode::KeyI,
0x0018 => KeyCode::KeyO,
0x0019 => KeyCode::KeyP,
0x001A => KeyCode::BracketLeft,
0x001B => KeyCode::BracketRight,
0x001C => KeyCode::Enter,
0x001D => KeyCode::ControlLeft,
0x001E => KeyCode::KeyA,
0x001F => KeyCode::KeyS,
0x0020 => KeyCode::KeyD,
0x0021 => KeyCode::KeyF,
0x0022 => KeyCode::KeyG,
0x0023 => KeyCode::KeyH,
0x0024 => KeyCode::KeyJ,
0x0025 => KeyCode::KeyK,
0x0026 => KeyCode::KeyL,
0x0027 => KeyCode::Semicolon,
0x0028 => KeyCode::Quote,
0x0029 => KeyCode::Backquote,
0x002A => KeyCode::ShiftLeft,
0x002B => KeyCode::Backslash,
0x002C => KeyCode::KeyZ,
0x002D => KeyCode::KeyX,
0x002E => KeyCode::KeyC,
0x002F => KeyCode::KeyV,
0x0030 => KeyCode::KeyB,
0x0031 => KeyCode::KeyN,
0x0032 => KeyCode::KeyM,
0x0033 => KeyCode::Comma,
0x0034 => KeyCode::Period,
0x0035 => KeyCode::Slash,
0x0036 => KeyCode::ShiftRight,
0x0037 => KeyCode::NumpadMultiply,
0x0038 => KeyCode::AltLeft,
0x0039 => KeyCode::Space,
0x003A => KeyCode::CapsLock,
0x003B => KeyCode::F1,
0x003C => KeyCode::F2,
0x003D => KeyCode::F3,
0x003E => KeyCode::F4,
0x003F => KeyCode::F5,
0x0040 => KeyCode::F6,
0x0041 => KeyCode::F7,
0x0042 => KeyCode::F8,
0x0043 => KeyCode::F9,
0x0044 => KeyCode::F10,
0x0045 => KeyCode::Pause,
0x0046 => KeyCode::ScrollLock,
0x0047 => KeyCode::Numpad7,
0x0048 => KeyCode::Numpad8,
0x0049 => KeyCode::Numpad9,
0x004A => KeyCode::NumpadSubtract,
0x004B => KeyCode::Numpad4,
0x004C => KeyCode::Numpad5,
0x004D => KeyCode::Numpad6,
0x004E => KeyCode::NumpadAdd,
0x004F => KeyCode::Numpad1,
0x0050 => KeyCode::Numpad2,
0x0051 => KeyCode::Numpad3,
0x0052 => KeyCode::Numpad0,
0x0053 => KeyCode::NumpadDecimal,
0x0054 => KeyCode::PrintScreen,
0x0056 => KeyCode::IntlBackslash,
0x0057 => KeyCode::F11,
0x0058 => KeyCode::F12,
0x0073 => KeyCode::IntlRo,
0x007D => KeyCode::IntlYen,
0xE01C => KeyCode::NumpadEnter,
0xE01D => KeyCode::ControlRight,
0xE035 => KeyCode::NumpadDivide,
0xE037 => KeyCode::PrintScreen,
0xE038 => KeyCode::AltRight,
0xE045 => KeyCode::NumLock,
0xE047 => KeyCode::Home,
0xE048 => KeyCode::ArrowUp,
0xE049 => KeyCode::PageUp,
0xE04B => KeyCode::ArrowLeft,
0xE04D => KeyCode::ArrowRight,
0xE04F => KeyCode::End,
0xE050 => KeyCode::ArrowDown,
0xE051 => KeyCode::PageDown,
0xE052 => KeyCode::Insert,
0xE053 => KeyCode::Delete,
0xE05B => KeyCode::SuperLeft,
0xE05C => KeyCode::SuperRight,
0xE05D => KeyCode::ContextMenu,
_ => return None,
})
}
const PAUSE_E1: u32 = 0xE11D;
const PAUSE: u32 = 0x0045;
const fn maps_without_its_prefix(virtual_key: u16) -> bool {
matches!(virtual_key, 0x21..=0x28 | 0x2D | 0x2E) || virtual_key as usize == value::VK_NUM_LOCK
}
#[must_use]
pub const fn recovered_scancode(virtual_key: u16, extended: bool, mapped: u32) -> u32 {
if mapped == PAUSE_E1 {
return PAUSE;
}
if extended || maps_without_its_prefix(virtual_key) {
return EXTENDED_PREFIX | mapped;
}
mapped
}
pub fn key_code_or_recovered(
scancode: u32,
virtual_key: u16,
map: impl FnOnce(u16) -> u32,
) -> Option<KeyCode> {
if let Some(key) = key_code(scancode) {
return Some(key);
}
if scancode & !EXTENDED_PREFIX != 0 {
return None;
}
let extended = scancode & EXTENDED_PREFIX != 0;
key_code(recovered_scancode(virtual_key, extended, map(virtual_key)))
}
pub use crate::keysym::named_keysym;
const fn is_down(keys: &[u8; 256], key: usize) -> bool {
keys[key] & 0x80 != 0
}
const fn is_toggled(keys: &[u8; 256], key: usize) -> bool {
keys[key] & 0x01 != 0
}
#[must_use]
pub fn modifiers(keys: &[u8; 256]) -> Modifiers {
let left_control = is_down(keys, value::VK_L_CONTROL);
let right_control = is_down(keys, value::VK_R_CONTROL);
let left_alt = is_down(keys, value::VK_L_MENU);
let right_alt = is_down(keys, value::VK_R_MENU);
let alt_gr = right_alt && left_control;
let unsided_control = is_down(keys, value::VK_CONTROL) && !left_control;
let unsided_alt = is_down(keys, value::VK_MENU) && !right_alt;
let mut modifiers = Modifiers::empty();
modifiers.set(
Modifiers::SHIFT,
is_down(keys, value::VK_L_SHIFT)
|| is_down(keys, value::VK_R_SHIFT)
|| is_down(keys, value::VK_SHIFT),
);
modifiers.set(
Modifiers::CTRL,
right_control || unsided_control || (left_control && !alt_gr),
);
modifiers.set(
Modifiers::ALT,
left_alt || unsided_alt || (right_alt && !alt_gr),
);
modifiers.set(
Modifiers::SUPER,
is_down(keys, value::VK_L_WIN) || is_down(keys, value::VK_R_WIN),
);
modifiers.set(Modifiers::ALT_GR, alt_gr);
modifiers.set(Modifiers::CAPS_LOCK, is_toggled(keys, value::VK_CAPITAL));
modifiers.set(Modifiers::NUM_LOCK, is_toggled(keys, value::VK_NUM_LOCK));
modifiers
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Utf16 {
pending: Option<u16>,
}
const HIGH_SURROGATE_START: u16 = 0xD800;
const LOW_SURROGATE_START: u16 = 0xDC00;
const SURROGATE_END: u16 = 0xE000;
const ASTRAL_START: u32 = 0x1_0000;
impl Utf16 {
pub fn push(&mut self, unit: u16) -> Option<char> {
let high = self.pending.take();
if let Some(high) = high
&& (LOW_SURROGATE_START..SURROGATE_END).contains(&unit)
{
let codepoint = ASTRAL_START
+ (((high - HIGH_SURROGATE_START) as u32) << 10)
+ (unit - LOW_SURROGATE_START) as u32;
return char::from_u32(codepoint);
}
if (HIGH_SURROGATE_START..LOW_SURROGATE_START).contains(&unit) {
self.pending = Some(unit);
return None;
}
char::from_u32(unit as u32)
}
}
#[must_use]
pub fn is_text(character: char) -> bool {
!character.is_control()
}
#[must_use]
pub const fn without_composition_window(l_param: isize) -> isize {
l_param & !(value::ISC_SHOW_UI_COMPOSITION_WINDOW as isize)
}
#[must_use]
pub const fn composition_changed(l_param: isize) -> bool {
(l_param as u32) & value::GCS_COMP_STR != 0
}
#[must_use]
pub const fn composition_committed(l_param: isize) -> bool {
(l_param as u32) & value::GCS_RESULT_STR != 0
}
#[derive(Debug, Default)]
pub struct Consumed {
scancodes: Vec<u32>,
}
impl Consumed {
pub fn report(&mut self, virtual_key: u16, scancode: u32, pressed: bool) -> bool {
let known = self.scancodes.iter().position(|&code| code == scancode);
if pressed && virtual_key == value::VK_PROCESS_KEY {
if known.is_none() {
self.scancodes.push(scancode);
}
return false;
}
let Some(known) = known else { return true };
self.scancodes.swap_remove(known);
pressed
}
pub fn clear(&mut self) {
self.scancodes.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_a_changed_composition_string_is_a_pre_edit() {
const GCS_COMP_ATTR: isize = 0x0010;
let comp_str = value::GCS_COMP_STR as isize;
let result_str = value::GCS_RESULT_STR as isize;
assert!(composition_changed(comp_str));
assert!(composition_changed(comp_str | GCS_COMP_ATTR));
assert!(composition_changed(comp_str | result_str));
assert!(!composition_changed(result_str));
assert!(!composition_changed(0));
}
#[test]
fn only_a_result_is_a_commit() {
let comp_str = value::GCS_COMP_STR as isize;
let result_str = value::GCS_RESULT_STR as isize;
assert!(composition_committed(result_str));
assert!(
composition_committed(result_str | comp_str),
"a partial commit carries the rest of the composition too"
);
assert!(!composition_committed(comp_str));
assert!(!composition_committed(0));
}
#[test]
fn the_input_methods_composition_window_is_turned_off_and_nothing_else() {
const ISC_SHOW_UI_CANDIDATE_WINDOWS: isize = 0x0000_000F;
const ISC_SHOW_UI_GUIDELINE: isize = 0x4000_0000;
let composition = value::ISC_SHOW_UI_COMPOSITION_WINDOW as isize;
let all = composition | ISC_SHOW_UI_GUIDELINE | ISC_SHOW_UI_CANDIDATE_WINDOWS;
assert_eq!(
without_composition_window(all),
ISC_SHOW_UI_GUIDELINE | ISC_SHOW_UI_CANDIDATE_WINDOWS,
"the composition window goes, the guideline and candidates stay"
);
assert_eq!(without_composition_window(composition), 0);
assert_eq!(
without_composition_window(ISC_SHOW_UI_CANDIDATE_WINDOWS),
ISC_SHOW_UI_CANDIDATE_WINDOWS,
"a context with no composition window is left alone"
);
}
#[test]
fn a_message_with_no_scan_code_takes_its_key_from_the_virtual_key() {
const ANSWERS: [(u16, u32); 12] = [
(0x41, 0x001E), (0x1B, 0x0001), (0x26, 0x0048), (0x24, 0x0047), (0x67, 0x0047), (0x2E, 0x0053), (0x90, 0x0045), (0x13, 0xE11D), (0xA3, 0xE01D), (0x11, 0x001D), (0x2C, 0x0054), (0xE5, 0x0000), ];
let map = |virtual_key: u16| {
ANSWERS
.iter()
.find(|(key, _)| *key == virtual_key)
.map_or(0, |(_, mapped)| *mapped)
};
let recover = |extended: bool, virtual_key| {
let scancode = if extended { EXTENDED_PREFIX } else { 0 };
key_code_or_recovered(scancode, virtual_key, map)
};
assert_eq!(recover(false, 0x41), Some(KeyCode::KeyA));
assert_eq!(recover(false, 0x1B), Some(KeyCode::Escape));
assert_eq!(recover(false, 0x26), Some(KeyCode::ArrowUp), "not Numpad8");
assert_eq!(recover(false, 0x24), Some(KeyCode::Home), "not Numpad7");
assert_eq!(recover(false, 0x67), Some(KeyCode::Numpad7));
assert_eq!(recover(false, 0x2E), Some(KeyCode::Delete));
assert_eq!(recover(false, 0x90), Some(KeyCode::NumLock), "not Pause");
assert_eq!(recover(false, 0x13), Some(KeyCode::Pause));
assert_eq!(recover(false, 0xA3), Some(KeyCode::ControlRight));
assert_eq!(recover(false, 0x11), Some(KeyCode::ControlLeft));
assert_eq!(recover(true, 0x11), Some(KeyCode::ControlRight));
assert_eq!(recover(true, 0xA3), Some(KeyCode::ControlRight));
assert_eq!(recover(true, 0x26), Some(KeyCode::ArrowUp));
assert_eq!(recover(false, 0x2C), Some(KeyCode::PrintScreen));
assert_eq!(recover(false, 0xE5), None);
assert_eq!(recover(true, 0xE5), None);
let untouched = |_| panic!("the virtual key is not asked about");
assert_eq!(
key_code_or_recovered(0x0011, 0x41, untouched),
Some(KeyCode::KeyW),
"the position, not the layout's label"
);
assert_eq!(key_code_or_recovered(0x0059, 0x41, untouched), None);
assert_eq!(key_code_or_recovered(0xE022, 0x41, untouched), None);
}
#[test]
fn a_key_the_input_method_consumed_is_reported_on_neither_edge() {
const VK_BACK: u16 = 0x08;
const VK_RETURN: u16 = 0x0D;
const VK_W: u16 = 0x57;
const VK_A: u16 = 0x41;
let process = value::VK_PROCESS_KEY;
let mut consumed = Consumed::default();
assert!(!consumed.report(process, 0x000E, true));
assert!(!consumed.report(process, 0x000E, true), "nor its repeat");
assert!(!consumed.report(VK_BACK, 0x000E, false));
assert!(consumed.report(VK_BACK, 0x000E, true));
assert!(consumed.report(VK_BACK, 0x000E, false));
assert!(!consumed.report(process, 0x001C, true));
assert!(!consumed.report(process, 0x001C, false));
assert!(consumed.report(VK_RETURN, 0x001C, false), "already matched");
assert!(!consumed.report(process, 0x0011, true));
assert!(consumed.report(VK_W, 0x0011, true));
assert!(consumed.report(VK_W, 0x0011, false));
assert!(consumed.report(VK_W, 0x0011, true));
assert!(consumed.report(process, 0x0011, false));
assert!(!consumed.report(process, 0x001E, true));
consumed.clear();
assert!(consumed.report(VK_A, 0x001E, false));
}
const fn l_param(code: isize, extended: bool, previous: bool) -> isize {
let mut packed = code << 16;
if extended {
packed |= EXTENDED_BIT;
}
if previous {
packed |= PREVIOUS_STATE_BIT;
}
packed | 1
}
fn held(keys: &[usize]) -> [u8; 256] {
let mut state = [0u8; 256];
for key in keys {
state[*key] = 0x80;
}
state
}
#[test]
fn the_extended_bit_is_folded_into_the_scan_code_rather_than_dropped() {
assert_eq!(scancode(l_param(0x1C, false, false)), 0x001C);
assert_eq!(scancode(l_param(0x1C, true, false)), 0xE01C);
assert_eq!(key_code(0x001C), Some(KeyCode::Enter));
assert_eq!(key_code(0xE01C), Some(KeyCode::NumpadEnter));
assert_eq!(key_code(0x001D), Some(KeyCode::ControlLeft));
assert_eq!(key_code(0xE01D), Some(KeyCode::ControlRight));
assert_eq!(key_code(0x0038), Some(KeyCode::AltLeft));
assert_eq!(key_code(0xE038), Some(KeyCode::AltRight));
assert_eq!(key_code(0x0047), Some(KeyCode::Numpad7));
assert_eq!(key_code(0xE047), Some(KeyCode::Home));
assert_eq!(scancode(l_param(0x39, false, true)), 0x0039);
assert_eq!(scancode(-1), 0xE0FF, "every bit set is still one byte");
}
#[test]
fn pause_is_the_bare_forty_five_and_num_lock_is_the_extended_one() {
assert_eq!(key_code(0x0045), Some(KeyCode::Pause));
assert_eq!(key_code(0xE045), Some(KeyCode::NumLock));
}
#[test]
fn the_previous_state_bit_is_what_marks_an_auto_repeat() {
assert!(!was_already_down(l_param(0x11, false, false)));
assert!(was_already_down(l_param(0x11, false, true)));
}
#[test]
fn wasd_and_the_gameplay_keys_map_to_their_set_one_codes() {
assert_eq!(key_code(0x0011), Some(KeyCode::KeyW));
assert_eq!(key_code(0x001E), Some(KeyCode::KeyA));
assert_eq!(key_code(0x001F), Some(KeyCode::KeyS));
assert_eq!(key_code(0x0020), Some(KeyCode::KeyD));
assert_eq!(key_code(0x0039), Some(KeyCode::Space));
assert_eq!(key_code(0x0001), Some(KeyCode::Escape));
assert_eq!(key_code(0xE05B), Some(KeyCode::SuperLeft));
}
#[cfg(target_os = "linux")]
#[test]
fn the_two_numberings_coincide_for_the_letters_and_for_nothing_above_them() {
assert_eq!(crate::linux::keymap::key_code(17), Some(KeyCode::KeyW));
assert_eq!(key_code(17), Some(KeyCode::KeyW), "0x11 is W in both");
assert_eq!(crate::linux::keymap::key_code(30), Some(KeyCode::KeyA));
assert_eq!(key_code(30), Some(KeyCode::KeyA), "0x1E is A in both");
assert_eq!(
crate::linux::keymap::key_code(103),
Some(KeyCode::ArrowUp),
"evdev continues flat"
);
assert_eq!(key_code(103), None, "set 1 assigns nothing at 0x67");
assert_eq!(key_code(0xE048), Some(KeyCode::ArrowUp), "it is prefixed");
assert_eq!(
crate::linux::keymap::key_code(96),
Some(KeyCode::NumpadEnter)
);
assert_eq!(key_code(96), None);
assert_eq!(key_code(0xE01C), Some(KeyCode::NumpadEnter));
}
fn all_scancodes() -> impl Iterator<Item = u32> {
(0..=0xFFu32).chain((0..=0xFFu32).map(|code| EXTENDED_PREFIX | code))
}
#[test]
fn every_named_key_is_reachable_from_some_scan_code() {
let reachable: Vec<KeyCode> = all_scancodes().filter_map(key_code).collect();
let mut missing: Vec<&'static str> = Vec::new();
for key in KeyCode::ALL {
if !reachable.contains(key) {
missing.push(key.as_str());
}
}
assert!(missing.is_empty(), "unreachable KeyCodes: {missing:?}");
}
#[test]
fn print_screen_is_the_only_key_two_scan_codes_name() {
let mut seen: Vec<KeyCode> = all_scancodes().filter_map(key_code).collect();
let count = seen.len();
seen.sort_unstable();
seen.dedup();
assert_eq!(
count - seen.len(),
1,
"exactly one scan code pair may share a KeyCode"
);
assert_eq!(
seen.len(),
KeyCode::ALL.len(),
"the table is the vocabulary"
);
assert_eq!(key_code(0x0054), Some(KeyCode::PrintScreen));
assert_eq!(key_code(0xE037), Some(KeyCode::PrintScreen));
}
#[test]
fn unnamed_keys_degrade_to_none_rather_than_to_a_wrong_key() {
for code in [
0x0000, 0x0055, 0x0059, 0x0070, 0x0079, 0xE010, 0xE022, 0xE06F,
] {
assert_eq!(key_code(code), None, "scan code {code:#06x}");
}
}
#[test]
fn alt_gr_is_reported_as_itself_and_not_as_control_plus_alt() {
let alt_gr = modifiers(&held(&[value::VK_R_MENU, value::VK_L_CONTROL]));
assert!(alt_gr.contains(Modifiers::ALT_GR));
assert!(!alt_gr.contains(Modifiers::CTRL), "{alt_gr:?}");
assert!(!alt_gr.contains(Modifiers::ALT), "{alt_gr:?}");
assert_eq!(alt_gr.chord(), Modifiers::empty());
let plain = modifiers(&held(&[value::VK_R_MENU]));
assert_eq!(plain, Modifiers::ALT);
let real = modifiers(&held(&[value::VK_L_CONTROL, value::VK_L_MENU]));
assert_eq!(real, Modifiers::CTRL | Modifiers::ALT);
let both = modifiers(&held(&[
value::VK_R_MENU,
value::VK_L_CONTROL,
value::VK_R_CONTROL,
]));
assert!(both.contains(Modifiers::ALT_GR | Modifiers::CTRL));
}
#[test]
fn an_unsided_modifier_counts_where_no_sided_one_is_down() {
assert_eq!(modifiers(&held(&[value::VK_CONTROL])), Modifiers::CTRL);
assert_eq!(modifiers(&held(&[value::VK_MENU])), Modifiers::ALT);
assert_eq!(modifiers(&held(&[value::VK_SHIFT])), Modifiers::SHIFT);
let right = modifiers(&held(&[value::VK_CONTROL, value::VK_R_CONTROL]));
assert_eq!(right, Modifiers::CTRL);
let left = modifiers(&held(&[value::VK_CONTROL, value::VK_L_CONTROL]));
assert_eq!(left, Modifiers::CTRL);
let alt_gr = modifiers(&held(&[
value::VK_L_CONTROL,
value::VK_R_MENU,
value::VK_CONTROL,
value::VK_MENU,
]));
assert_eq!(alt_gr, Modifiers::ALT_GR);
let injected = modifiers(&held(&[value::VK_CONTROL, value::VK_R_MENU]));
assert_eq!(injected, Modifiers::CTRL | Modifiers::ALT);
}
#[test]
fn shift_and_super_come_from_either_side_and_the_locks_from_the_toggle_bit() {
assert_eq!(modifiers(&held(&[value::VK_L_SHIFT])), Modifiers::SHIFT);
assert_eq!(modifiers(&held(&[value::VK_R_SHIFT])), Modifiers::SHIFT);
assert_eq!(modifiers(&held(&[value::VK_L_WIN])), Modifiers::SUPER);
assert_eq!(modifiers(&held(&[value::VK_R_WIN])), Modifiers::SUPER);
assert_eq!(modifiers(&[0u8; 256]), Modifiers::empty());
let mut state = [0u8; 256];
state[value::VK_CAPITAL] = 0x01;
state[value::VK_NUM_LOCK] = 0x01;
assert_eq!(
modifiers(&state),
Modifiers::CAPS_LOCK | Modifiers::NUM_LOCK
);
let mut pressing = [0u8; 256];
pressing[value::VK_CAPITAL] = 0x80;
assert_eq!(modifiers(&pressing), Modifiers::empty());
}
#[test]
fn a_surrogate_pair_becomes_one_character_and_a_lone_half_becomes_none() {
let mut text = Utf16::default();
assert_eq!(text.push(0xD83C), None, "the high half commits nothing");
assert_eq!(text.push(0xDFAE), Some('🎮'));
assert_eq!(text, Utf16::default(), "and the pair is not held on to");
assert_eq!(text.push(0x3042), Some('あ'));
assert_eq!(text.push(u16::from(b'q')), Some('q'));
assert_eq!(text.push(0xD83C), None);
assert_eq!(text.push(u16::from(b'a')), Some('a'));
assert_eq!(text.push(0xDFAE), None);
assert_eq!(text.push(u16::from(b'b')), Some('b'));
}
#[test]
fn control_characters_are_not_text() {
for control in ['\u{8}', '\r', '\n', '\t', '\u{1b}', '\u{7f}'] {
assert!(!is_text(control), "{control:?}");
}
for text in ['a', ' ', 'é', '🎮', '日'] {
assert!(is_text(text), "{text:?}");
}
}
}