use crate::candidate_list::CandidateList;
use crate::cskkstate::CskkStateInfo::Complete;
use crate::dictionary::candidate::Candidate;
use crate::dictionary::CompositeKey;
use crate::form_changer::KanaFormChanger;
use crate::skk_modes::{CompositionMode, InputMode};
use crate::CskkStateInfo::{
CompositionSelection, Direct, PreComposition, PreCompositionOkurigana, Register,
};
use std::collections::VecDeque;
use std::fmt::{Debug, Formatter};
use xkbcommon::xkb::{keysym_get_name, Keysym};
const COMPOSITION_MODE_HISTORY_MAX: usize = 20;
pub(crate) struct CskkState {
pub(crate) input_mode: InputMode,
pub(crate) composition_mode: CompositionMode,
default_composition_mode: CompositionMode,
compositon_mode_history: VecDeque<CompositionMode>,
pub(crate) pre_conversion: Vec<Keysym>,
raw_to_composite: String,
converted_kana_to_composite: String,
converted_kana_to_okuri: String,
candidate_list: CandidateList,
composited_okuri: String,
confirmed: String,
capital_transition: bool,
use_okurigana: bool,
}
#[derive(Debug, PartialEq, Eq)]
pub enum CskkStateInfo {
Direct(DirectData),
PreComposition(PreCompositionData),
PreCompositionOkurigana(PreCompositionData),
CompositionSelection(CompositionSelectionData),
Register(RegisterData),
Complete(CompleteData),
}
#[derive(Debug, PartialEq, Eq)]
pub struct DirectData {
pub confirmed: String,
pub unconverted: Option<String>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct CompositionSelectionData {
pub confirmed: String,
pub composited: String,
pub okuri: Option<String>,
pub annotation: Option<String>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct PreCompositionData {
pub confirmed: String,
pub kana_to_composite: String,
pub okuri: Option<String>,
pub unconverted: Option<String>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct RegisterData {
pub confirmed: String,
pub kana_to_composite: String,
pub okuri: Option<String>,
pub postfix: Option<String>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct CompleteData {
pub confirmed: String,
pub complete_origin: String,
pub completed_midashi: String,
pub completed: String,
pub okuri: Option<String>,
pub annotation: Option<String>,
}
impl CskkState {
pub fn new(input_mode: InputMode, composition_mode: CompositionMode) -> Self {
CskkState {
input_mode,
composition_mode,
default_composition_mode: composition_mode,
compositon_mode_history: VecDeque::new(),
pre_conversion: vec![],
raw_to_composite: "".to_string(),
converted_kana_to_composite: "".to_string(),
converted_kana_to_okuri: "".to_string(),
composited_okuri: "".to_string(),
confirmed: "".to_string(),
candidate_list: CandidateList::new(),
capital_transition: false,
use_okurigana: false,
}
}
pub(crate) fn get_confirmed_string(&self) -> &str {
&self.confirmed
}
pub(crate) fn get_to_composite_string(&self) -> &str {
&self.converted_kana_to_composite
}
pub(crate) fn get_okuri_string(&self) -> &str {
&self.converted_kana_to_okuri
}
pub(crate) fn clear_preconverted_kanainputs(&mut self) {
self.pre_conversion.clear();
self.capital_transition = false;
}
pub(crate) fn clear_kanas(&mut self) {
self.clear_preconverted_kanainputs();
self.converted_kana_to_composite.clear();
self.converted_kana_to_okuri.clear();
self.use_okurigana = false;
}
pub(crate) fn clear_unconfirmed(&mut self) {
self.raw_to_composite.clear();
self.clear_candidate_list();
self.clear_kanas();
}
pub(crate) fn clear_all(&mut self) {
self.confirmed.clear();
self.clear_unconfirmed();
self.compositon_mode_history.clear();
}
pub(crate) fn flush_confirmed_string(&mut self) {
self.confirmed.clear();
}
pub(crate) fn delete(&mut self) -> bool {
match self.composition_mode {
CompositionMode::Direct => {
let mut deleted = self.pre_conversion.pop().is_some();
if !deleted {
deleted = self.confirmed.pop().is_some();
}
deleted
}
CompositionMode::PreComposition | CompositionMode::Abbreviation => {
let mut deleted = self.pre_conversion.pop().is_some();
if !deleted {
deleted = self.converted_kana_to_composite.pop().is_some();
self.raw_to_composite.pop();
}
if !deleted {
self.composition_mode = CompositionMode::Direct;
}
true
}
CompositionMode::PreCompositionOkurigana => {
let mut deleted = self.pre_conversion.pop().is_some();
if !deleted {
deleted = self.converted_kana_to_okuri.pop().is_some();
self.raw_to_composite.pop();
}
if self.pre_conversion.is_empty() && self.converted_kana_to_okuri.is_empty() {
self.composition_mode = CompositionMode::PreComposition;
}
deleted
}
_ => {
log::warn!("Unimplemented delete. Should not call in this mode.");
false
}
}
}
pub(crate) fn push_string(&mut self, letter_or_word: &str) {
self.push_string_for_composition_mode(letter_or_word, self.composition_mode)
}
pub(crate) fn push_string_for_composition_mode(
&mut self,
letter_or_word: &str,
composition_mode: CompositionMode,
) {
match composition_mode {
CompositionMode::Direct => {
self.confirmed.push_str(letter_or_word);
}
CompositionMode::PreComposition => {
self.converted_kana_to_composite.push_str(letter_or_word);
self.raw_to_composite.push_str(letter_or_word);
}
CompositionMode::PreCompositionOkurigana => {
self.converted_kana_to_okuri.push_str(letter_or_word);
self.use_okurigana = true;
}
CompositionMode::Abbreviation => {
self.converted_kana_to_composite.push_str(letter_or_word);
self.raw_to_composite.push_str(letter_or_word);
}
CompositionMode::CompositionSelection | CompositionMode::Completion => {
self.confirmed.push_str(letter_or_word);
}
_ => {
log::error!(
"Tried to enter kana in mode {:?}. This should never happen. Ignored kana input {}.",
self.composition_mode,
letter_or_word
)
}
}
}
pub(crate) fn consolidate_converted_to_to_composite(&mut self) {
let okuri = self.converted_kana_to_okuri.to_owned();
self.converted_kana_to_composite.push_str(&okuri);
self.raw_to_composite.push_str(&okuri);
self.converted_kana_to_okuri.clear();
self.use_okurigana = false;
}
pub(crate) fn set_converted_to_postfix(&mut self, letter_or_word: &str) {
self.converted_kana_to_okuri = letter_or_word.to_string();
self.use_okurigana = false;
}
pub(crate) fn preedit_string(
&self,
kana_form_changer: &KanaFormChanger,
current_input_mode: InputMode,
) -> String {
let stateinfo = self.preedit_detail(kana_form_changer, current_input_mode);
match stateinfo {
Direct(direct_data) => {
direct_data.confirmed.to_owned() + &direct_data.unconverted.unwrap_or_default()
}
PreComposition(precomposition_data) => {
precomposition_data.confirmed.to_owned()
+ "▽"
+ &precomposition_data.kana_to_composite
+ &precomposition_data.unconverted.unwrap_or_default()
}
PreCompositionOkurigana(precomposition_data) => {
precomposition_data.confirmed.to_owned()
+ "▽"
+ &precomposition_data.kana_to_composite
+ "*"
+ &precomposition_data.okuri.unwrap_or_default()
+ &precomposition_data.unconverted.unwrap_or_default()
}
CompositionSelection(composition_selection_data) => {
composition_selection_data.confirmed.to_owned()
+ "▼"
+ &composition_selection_data.composited
+ &composition_selection_data.okuri.unwrap_or_default()
}
Register(register_data) => {
if register_data.okuri.is_some() {
"▼".to_string()
+ ®ister_data.kana_to_composite
+ "*"
+ ®ister_data.okuri.unwrap_or_default()
+ ®ister_data.postfix.unwrap_or_default()
} else {
"▼".to_string()
+ ®ister_data.kana_to_composite
+ ®ister_data.postfix.unwrap_or_default()
}
}
Complete(complete_data) => {
complete_data.confirmed.to_owned()
+ "■"
+ &complete_data.completed
+ &complete_data.okuri.unwrap_or_default()
}
}
}
pub(crate) fn preedit_detail(
&self,
kana_form_changer: &KanaFormChanger,
current_input_mode: InputMode,
) -> CskkStateInfo {
let unconverted = if self.pre_conversion.is_empty() {
None
} else {
Some(
self.pre_conversion
.iter()
.map(|keysym| keysym_get_name(*keysym))
.collect::<Vec<_>>()
.join(""),
)
};
let okuri = if self.converted_kana_to_okuri.is_empty() {
None
} else {
Some(
kana_form_changer
.adjust_kana_string(current_input_mode, &self.converted_kana_to_okuri),
)
};
match self.composition_mode {
CompositionMode::Direct => Direct(DirectData {
confirmed: self.confirmed.to_owned(),
unconverted,
}),
CompositionMode::PreComposition | CompositionMode::Abbreviation => {
PreComposition(PreCompositionData {
confirmed: self.confirmed.to_owned(),
kana_to_composite: kana_form_changer
.adjust_kana_string(current_input_mode, &self.converted_kana_to_composite),
okuri,
unconverted,
})
}
CompositionMode::PreCompositionOkurigana => {
PreCompositionOkurigana(PreCompositionData {
confirmed: self.confirmed.to_owned(),
kana_to_composite: kana_form_changer
.adjust_kana_string(current_input_mode, &self.converted_kana_to_composite),
okuri,
unconverted,
})
}
CompositionMode::Register => {
let (okuri, postfix) = if !self.use_okurigana {
(None, okuri)
} else {
(okuri, None)
};
Register(RegisterData {
confirmed: self.confirmed.to_owned(),
kana_to_composite: kana_form_changer
.adjust_kana_string(current_input_mode, &self.converted_kana_to_composite),
okuri,
postfix,
})
}
CompositionMode::CompositionSelection => {
let current_candidate = self.candidate_list.get_current_candidate();
let fallback_candidate = Candidate::default();
let candidate = current_candidate.unwrap_or(&fallback_candidate).to_owned();
let composited = candidate.output;
let annotation = candidate.annotation;
CompositionSelection(CompositionSelectionData {
confirmed: self.confirmed.to_owned(),
composited,
okuri,
annotation,
})
}
CompositionMode::Completion => {
let complete_origin = self.converted_kana_to_composite.to_owned();
let current_candidate = self.candidate_list.get_current_candidate();
let fallback_candidate = Candidate::default();
let candidate = current_candidate.unwrap_or(&fallback_candidate).to_owned();
let completed_midashi = candidate.midashi;
let completed = candidate.output;
let annotation = candidate.annotation;
Complete(CompleteData {
confirmed: self.confirmed.to_owned(),
complete_origin,
completed_midashi,
completed,
okuri,
annotation,
})
}
}
}
pub(crate) fn get_composite_key(&self) -> CompositeKey {
if self.use_okurigana && !self.converted_kana_to_okuri.is_empty() {
return CompositeKey::new(
&self.raw_to_composite,
Some(self.converted_kana_to_okuri.to_owned()),
);
}
CompositeKey::new(&self.raw_to_composite, None)
}
pub(crate) fn set_capital_transition(&mut self, has_transitioned: bool) {
self.capital_transition = has_transitioned;
}
pub(crate) fn get_capital_transition(&self) -> bool {
self.capital_transition
}
pub(crate) fn abort_to_previous_mode(&mut self) {
if let Some(previous_mode) = self.compositon_mode_history.pop_back() {
if CompositionMode::PreCompositionOkurigana.eq(&previous_mode) {
self.consolidate_converted_to_to_composite();
self.composition_mode = CompositionMode::PreComposition;
} else {
self.composition_mode = previous_mode;
}
} else {
self.composition_mode = self.default_composition_mode;
}
}
pub(crate) fn reset_to_default_composition_mode(&mut self) {
self.compositon_mode_history.clear();
self.composition_mode = self.default_composition_mode;
}
pub(crate) fn change_composition_mode(&mut self, new_mode: CompositionMode) {
self.compositon_mode_history
.push_back(self.composition_mode);
if self.compositon_mode_history.len() > COMPOSITION_MODE_HISTORY_MAX {
self.compositon_mode_history.pop_front();
}
self.composition_mode = new_mode;
}
pub(crate) fn change_composition_mode_from_old_mode(
&mut self,
new_mode: CompositionMode,
old_mode: CompositionMode,
) {
self.compositon_mode_history.push_back(old_mode);
if self.compositon_mode_history.len() > COMPOSITION_MODE_HISTORY_MAX {
self.compositon_mode_history.pop_front();
}
self.composition_mode = new_mode;
}
}
impl CskkState {
pub(crate) fn get_candidate_list(&self) -> &CandidateList {
&self.candidate_list
}
pub(crate) fn forward_candidate(&mut self) -> bool {
self.candidate_list.forward_candidate()
}
pub(crate) fn backward_candidate(&mut self) -> bool {
self.candidate_list.backward_candidate()
}
pub(crate) fn set_candidate_pointer_index(&mut self, i: usize) -> bool {
self.candidate_list.set_selection_pointer(i)
}
pub(crate) fn add_new_candidates_for_existing_string_to_composite(
&mut self,
candidates: Vec<Candidate>,
) {
self.candidate_list.add_new_candidates(candidates);
self.composited_okuri = self.converted_kana_to_okuri.to_string();
}
pub(crate) fn clear_candidate_list(&mut self) {
self.candidate_list.clear();
self.composited_okuri.clear();
}
pub(crate) fn set_new_candidate_list(&mut self, candidates: Vec<Candidate>) {
let composite_key = self.get_composite_key();
self.candidate_list.set(composite_key, candidates);
self.composited_okuri = self.converted_kana_to_okuri.to_string();
}
}
impl Debug for CskkState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let keysyms: Vec<String> = self
.pre_conversion
.iter()
.map(|x| keysym_get_name(*x))
.collect();
f.debug_struct("CskkState")
.field("Mode", &(&self.composition_mode, &self.input_mode))
.field("previous_modes stack", &self.compositon_mode_history)
.field("pre_conversion", &keysyms)
.field("raw_to_composite", &self.raw_to_composite)
.field(
"converted_kana_to_composite",
&self.converted_kana_to_composite,
)
.field("converted_kana_to_okuri", &self.converted_kana_to_okuri)
.field("use_okurigana", &self.use_okurigana)
.field("composited_okuri", &self.composited_okuri)
.field("confirmed", &self.confirmed)
.field("capital_transition", &self.capital_transition)
.field("candidate_list", &self.candidate_list)
.finish()
}
}
#[cfg(test)]
impl CskkState {}