use compact_str::CompactString;
use serde::{Deserialize, Serialize};
use super::candidate::CandidateList;
use super::preedit::Preedit;
#[derive(Debug, Clone, Default)]
pub struct ImeState {
pub active: bool,
pub mode: ImeMode,
pub preedit: Preedit,
pub candidates: CandidateList,
pub composing: bool,
}
impl ImeState {
pub fn new() -> Self {
Self::default()
}
pub fn activate(&mut self) {
self.active = true;
}
pub fn deactivate(&mut self) {
self.active = false;
self.composing = false;
self.preedit.clear();
self.candidates.clear();
}
pub fn set_mode(&mut self, mode: ImeMode) {
self.mode = mode;
}
pub fn start_composition(&mut self) {
self.composing = true;
}
pub fn end_composition(&mut self) {
self.composing = false;
self.preedit.clear();
self.candidates.clear();
}
pub fn update_preedit(&mut self, text: &str, cursor: usize) {
self.preedit.set_text(text);
self.preedit.set_cursor(cursor);
}
pub fn is_composing(&self) -> bool {
self.composing && !self.preedit.is_empty()
}
pub fn has_candidates(&self) -> bool {
!self.candidates.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum ImeMode {
#[default]
Direct,
Hiragana,
Katakana,
HalfKatakana,
FullWidthAlpha,
Pinyin,
Wubi,
Hangul,
}
impl ImeMode {
pub fn display_name(&self) -> &'static str {
match self {
ImeMode::Direct => "A",
ImeMode::Hiragana => "あ",
ImeMode::Katakana => "ア",
ImeMode::HalfKatakana => "ア",
ImeMode::FullWidthAlpha => "A",
ImeMode::Pinyin => "拼",
ImeMode::Wubi => "五",
ImeMode::Hangul => "한",
}
}
pub fn is_japanese(&self) -> bool {
matches!(
self,
ImeMode::Hiragana | ImeMode::Katakana | ImeMode::HalfKatakana
)
}
pub fn is_chinese(&self) -> bool {
matches!(self, ImeMode::Pinyin | ImeMode::Wubi)
}
pub fn is_korean(&self) -> bool {
matches!(self, ImeMode::Hangul)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ImeEvent {
Enabled,
Disabled,
ModeChanged(ImeMode),
CompositionStart,
PreeditUpdate {
text: CompactString,
cursor: usize,
},
Commit(CompactString),
CompositionEnd,
CandidatesUpdate {
candidates: Vec<CompactString>,
selected: usize,
},
}
#[cfg(test)]
mod tests {
use super::{ImeMode, ImeState};
#[test]
fn test_ime_state_new() {
let state = ImeState::new();
assert!(!state.active);
assert!(!state.composing);
assert_eq!(state.mode, ImeMode::Direct);
}
#[test]
fn test_ime_activate() {
let mut state = ImeState::new();
state.activate();
assert!(state.active);
}
#[test]
fn test_ime_composition() {
let mut state = ImeState::new();
state.activate();
state.start_composition();
state.update_preedit("にほん", 3);
assert!(state.is_composing());
assert_eq!(state.preedit.text(), "にほん");
state.end_composition();
assert!(!state.is_composing());
}
#[test]
fn test_ime_mode_display() {
assert_eq!(ImeMode::Hiragana.display_name(), "あ");
assert_eq!(ImeMode::Katakana.display_name(), "ア");
assert_eq!(ImeMode::Direct.display_name(), "A");
}
#[test]
fn test_ime_mode_language() {
assert!(ImeMode::Hiragana.is_japanese());
assert!(ImeMode::Pinyin.is_chinese());
assert!(ImeMode::Hangul.is_korean());
assert!(!ImeMode::Direct.is_japanese());
}
}