#[cfg(any(target_os = "linux", target_os = "freebsd"))]
pub(crate) mod linux;
#[cfg(target_os = "windows")]
pub(crate) mod windows;
use gpui::{Bounds, Pixels};
#[cfg_attr(not(any(target_os = "linux", target_os = "freebsd")), allow(dead_code))]
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum ImeEvent {
Preedit { text: String, cursor: usize },
Commit(String),
End,
}
#[cfg_attr(not(any(target_os = "linux", target_os = "freebsd")), allow(dead_code))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum KeyRoute {
Key,
KeyInsteadOfCommit,
Ime,
}
#[cfg_attr(not(any(target_os = "linux", target_os = "freebsd")), allow(dead_code))]
pub(crate) fn route_key(
consumed: bool,
events: &[ImeEvent],
key_text: Option<char>,
composing: bool,
) -> KeyRoute {
if !consumed {
return KeyRoute::Key;
}
match (events, key_text) {
([ImeEvent::Commit(text)], Some(key))
if !composing && text.chars().eq(std::iter::once(key)) =>
{
KeyRoute::KeyInsteadOfCommit
}
_ => KeyRoute::Ime,
}
}
#[cfg_attr(not(any(target_os = "linux", target_os = "freebsd")), allow(dead_code))]
pub(crate) fn utf16_offset(text: &str, chars: usize) -> usize {
text.chars().take(chars).map(char::len_utf16).sum()
}
#[cfg_attr(
not(any(target_os = "linux", target_os = "freebsd", target_os = "windows")),
allow(dead_code)
)]
pub(crate) fn pixel_rect(bounds: Bounds<Pixels>, scale: f32) -> [i32; 4] {
let scaled = |value: Pixels| (f32::from(value) * scale).round() as i32;
let (left, top) = (scaled(bounds.origin.x), scaled(bounds.origin.y));
let right = scaled(bounds.origin.x + bounds.size.width).max(left + 1);
let bottom = scaled(bounds.origin.y + bounds.size.height).max(top + 1);
[left, top, right, bottom]
}
#[cfg(test)]
mod tests {
use super::*;
use gpui::{point, px, size};
fn commit(text: &str) -> ImeEvent {
ImeEvent::Commit(text.into())
}
fn preedit(text: &str) -> ImeEvent {
ImeEvent::Preedit {
text: text.into(),
cursor: utf16_offset(text, text.chars().count()),
}
}
#[test]
fn unwanted_keys_go_to_tao() {
assert_eq!(route_key(false, &[], Some('a'), false), KeyRoute::Key);
assert_eq!(route_key(false, &[], None, true), KeyRoute::Key);
}
#[test]
fn a_plain_character_is_typed_through_its_key() {
let events = [commit("a")];
assert_eq!(
route_key(true, &events, Some('a'), false),
KeyRoute::KeyInsteadOfCommit
);
let events = [commit("A")];
assert_eq!(
route_key(true, &events, Some('A'), false),
KeyRoute::KeyInsteadOfCommit
);
}
#[test]
fn composition_keeps_the_key() {
assert_eq!(
route_key(
true,
&[ImeEvent::Preedit {
text: "´".into(),
cursor: 1
}],
None,
false
),
KeyRoute::Ime
);
let events = [commit("é"), preedit(""), ImeEvent::End];
assert_eq!(route_key(true, &events, Some('e'), true), KeyRoute::Ime);
assert_eq!(
route_key(true, &[commit("a")], Some('a'), true),
KeyRoute::Ime
);
assert_eq!(
route_key(true, &[preedit("n")], Some('n'), false),
KeyRoute::Ime
);
}
#[test]
fn asynchronous_input_methods_take_every_key_first() {
assert_eq!(route_key(true, &[], Some('a'), false), KeyRoute::Ime);
assert_eq!(route_key(true, &[], None, false), KeyRoute::Ime);
}
#[test]
fn multi_character_commits_are_inserted() {
assert_eq!(
route_key(true, &[commit("ab")], Some('a'), false),
KeyRoute::Ime
);
assert_eq!(
route_key(true, &[commit("你好")], None, false),
KeyRoute::Ime
);
}
#[test]
fn preedit_cursor_counts_utf16_units() {
assert_eq!(utf16_offset("nihao", 2), 2);
assert_eq!(utf16_offset("你好", 1), 1);
assert_eq!(utf16_offset("😀a", 1), 2);
assert_eq!(utf16_offset("😀a", 2), 3);
assert_eq!(utf16_offset("ab", 5), 2);
}
#[test]
fn caret_rect_is_scaled_and_never_empty() {
let caret = Bounds::new(point(px(10.), px(20.)), size(px(0.), px(16.)));
assert_eq!(pixel_rect(caret, 1.), [10, 20, 11, 36]);
assert_eq!(pixel_rect(caret, 1.5), [15, 30, 16, 54]);
let range = Bounds::new(point(px(10.5), px(4.)), size(px(30.), px(0.)));
assert_eq!(pixel_rect(range, 2.), [21, 8, 81, 9]);
}
}