use crate::WindowId;
#[must_use]
pub fn preedit(units: &[u16], cursor: Option<usize>) -> (String, Option<usize>) {
let mut text = String::with_capacity(units.len());
let mut at = None;
let mut consumed = 0;
for character in char::decode_utf16(units.iter().copied()) {
let character = character.unwrap_or(char::REPLACEMENT_CHARACTER);
if at.is_none() && cursor.is_some_and(|cursor| consumed + character.len_utf16() > cursor) {
at = Some(text.len());
}
consumed += character.len_utf16();
text.push(character);
}
if cursor.is_some() && at.is_none() {
at = Some(text.len());
}
if text.is_empty() {
return (text, None);
}
(text, at)
}
#[derive(Debug, Default)]
pub struct Composing {
windows: Vec<WindowId>,
}
impl Composing {
pub fn report(&mut self, window: WindowId, text: &str) -> bool {
let showing = self.windows.contains(&window);
if text.is_empty() {
self.forget(window);
return showing;
}
if !showing {
self.windows.push(window);
}
true
}
pub fn forget(&mut self, window: WindowId) {
self.windows.retain(|&known| known != window);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_composition_cursor_is_counted_in_code_units_and_reported_in_bytes() {
let ka: Vec<u16> = "かな".encode_utf16().collect();
assert_eq!(preedit(&ka, Some(0)), ("かな".to_owned(), Some(0)));
assert_eq!(preedit(&ka, Some(1)), ("かな".to_owned(), Some(3)));
assert_eq!(preedit(&ka, Some(2)), ("かな".to_owned(), Some(6)));
assert_eq!(preedit(&ka, Some(9)), ("かな".to_owned(), Some(6)));
assert_eq!(preedit(&ka, None), ("かな".to_owned(), None));
let astral: Vec<u16> = "a𠀋b".encode_utf16().collect();
assert_eq!(preedit(&astral, Some(3)), ("a𠀋b".to_owned(), Some(5)));
assert_eq!(preedit(&astral, Some(2)), ("a𠀋b".to_owned(), Some(1)));
assert_eq!(preedit(&astral, Some(1)), ("a𠀋b".to_owned(), Some(1)));
assert_eq!(preedit(&[], Some(0)), (String::new(), None));
assert_eq!(
preedit(&[0xD800, 0x61], None),
("\u{FFFD}a".to_owned(), None)
);
}
#[test]
fn a_composition_ends_once_and_only_where_one_was_shown() {
let window = |index: u64| WindowId::from_bits(1 << 32 | index).expect("a valid handle");
let (a, b) = (window(0), window(1));
let mut composing = Composing::default();
assert!(!composing.report(a, ""));
assert!(composing.report(a, "か"));
assert!(composing.report(a, "かな"), "each change is reported");
assert!(composing.report(b, "な"));
assert!(composing.report(a, ""));
assert!(!composing.report(a, ""), "the second end is dropped");
composing.forget(b);
assert!(!composing.report(b, ""));
}
}