1mod candidates;
6mod caret_edit;
7mod chain;
8mod clock;
9mod commit;
10mod composition;
11mod conversion;
12mod editing;
13mod glide;
14mod helpcode_entry;
15mod input;
16mod learning;
17mod literal;
18mod online;
19pub mod options;
20mod plugin_commands;
21mod prediction;
22#[cfg(test)]
23mod tests;
24
25use std::path::PathBuf;
26use std::sync::Arc;
27
28use crate::diagnostics;
29use crate::error::{EngineError, Result};
30use crate::local::database::LocalDatabaseLease;
31use crate::nine_key::{KeyGrid, NineKeySession};
32use crate::pinyin::glide::{GlideKeyboard, GlidePoint};
33use crate::touch::{KeyGeometry, KeyTouch, TouchTuning};
34use crate::types::{
35 CandidateEdge, CandidateSource, Command, CommandTableEntry, CommandTranslationQuery, KeyResult,
36 LocalInputMode, MentionEntry, OnlineQuery, PluginCommand, PluginCommandQuery, QuickPhraseEntry,
37 SchemeType, WordItem,
38};
39use crate::user_dictionary::journal::{release_thread_journal, JournalUse};
40use crate::user_dictionary::ngram_store::{flush_journal, note_settled};
41
42use crate::types::FirstPageRows;
43pub use clock::{Clock, LocalClock};
44use input::InputSession;
45pub use options::{SessionOptions, SessionSnapshot};
46
47pub struct Session {
48 input: InputSession,
49 nine_key: NineKeySession,
50 key_grid: Option<KeyGrid>,
52 shuangpin_preedit_uses_raw: bool,
53 prediction: prediction::Prediction,
54 revision: u64,
56 journal: PathBuf,
58 journal_use: JournalUse,
59 _local_databases: LocalDatabaseLease,
61}
62
63const _: fn() = || {
65 fn moves_between_threads<T: Send>() {}
66 moves_between_threads::<Session>();
67};
68
69impl Session {
70 pub fn new(options: SessionOptions) -> Result<Session> {
72 options.paths.validate()?;
73 crate::user_dictionary::generation::check_read_only_layout(&options.paths)?;
74 let input = InputSession::new(&options)?;
75 let mut nine_key = NineKeySession::new(
76 &options.paths,
77 options.learning,
78 options.frequency,
79 options.fuzzy_pinyin,
80 options.english,
81 options.enabled_schemes.contains(SchemeType::Quanpin),
82 options.single_character_only,
83 );
84 nine_key.set_stroke_dictionary(options.stroke_dictionary.clone());
85 nine_key.set_mixed_expressive(options.expressive);
86 nine_key.set_mixed_candidates_enabled(options.mixed_candidates);
87 nine_key.set_inline_date_time(
88 options
89 .local_modes
90 .date_time_inline
91 .then(|| Arc::clone(&input.clock.local)),
92 );
93 nine_key.set_sentence_options(
95 options.sentence_association.clone(),
96 options.sentence_alternatives,
97 );
98 nine_key.set_first_page_rows(FirstPageRows::new(options.first_page_rows));
99 nine_key.set_rescoring_context(&options.rescoring_context);
100 nine_key.set_personal_context_enabled(options.personal_context);
102 let journal = options.paths.user(crate::assets::USER_JOURNAL);
103 Ok(Session {
104 input,
105 nine_key,
106 key_grid: None,
107 shuangpin_preedit_uses_raw: options.shuangpin_preedit_uses_raw,
108 prediction: prediction::Prediction::new(
109 options.next_word_prediction,
110 options.paths.dictionary(crate::assets::MAIN_DICTIONARY),
111 ),
112 revision: 0,
113 journal_use: JournalUse::new(journal.clone()),
114 journal,
115 _local_databases: LocalDatabaseLease::new([
116 options.paths.dictionaries.clone(),
117 options.paths.resources.clone(),
118 ]),
119 })
120 }
121
122 pub fn candidate_revision(&self) -> u64 {
124 self.revision
125 }
126
127 fn candidates_may_change(&mut self) {
128 self.revision = self.revision.wrapping_add(1);
129 }
130
131 pub(crate) fn shuangpin_layout(&self) -> &'static crate::shuangpin::ShuangpinProfile {
133 self.input.profile
134 }
135
136 pub(crate) fn helpcode_table(&self) -> Option<&crate::helpcode::HelpcodeKeymap> {
138 self.input.helpcode_keymap.as_deref()
139 }
140
141 pub fn set_clock(&mut self, clock: Clock) {
143 if self.input.local_mode_options.date_time_inline {
144 self.nine_key
145 .set_inline_date_time(Some(Arc::clone(&clock.local)));
146 }
147 self.input.clock = clock;
148 }
149
150 pub fn character(&mut self, value: u8, shift_only: bool) -> KeyResult {
152 self.candidates_may_change();
153 self.prediction.clear();
154 let touch = self.input.pending_touch.take();
155 let grid_digit = match self.key_grid {
157 Some(KeyGrid::NineKey) => (b'2'..=b'9').contains(&value),
158 Some(grid @ KeyGrid::NineKeyShuangpin(_)) => {
159 !self.input.dedicated_english && grid.is_code(value)
160 }
161 _ => false,
162 };
163 if grid_digit
164 && self.input.scheme() == SchemeType::Quanpin
165 && self.input.local_mode == LocalInputMode::None
166 && self.input.preedit().is_empty()
167 {
168 let result = self.nine_key.character(value);
169 return self.after_nine_key(result);
170 }
171 if self.nine_key.active() {
172 if value == b'\'' {
174 let result = self.nine_key.character(value);
175 return self.after_nine_key(result);
176 }
177 return KeyResult::unhandled();
178 }
179 self.input.pending_touch = touch;
180 self.input.handle_character(value, shift_only)
181 }
182
183 pub fn set_key_touch(&mut self, touch: Option<KeyTouch>) {
185 self.input.pending_touch = touch;
186 }
187
188 pub fn set_key_geometry(&mut self, geometry: Option<Arc<KeyGeometry>>) {
190 self.input.pending_touch = None;
192 self.input.engine.set_key_geometry(geometry);
193 }
194
195 pub fn set_touch_tuning(&mut self, tuning: TouchTuning) {
197 self.input.engine.set_touch_tuning(tuning);
198 }
199
200 pub fn glide(&mut self, keyboard: &GlideKeyboard, points: &[GlidePoint]) -> KeyResult {
202 self.candidates_may_change();
203 self.prediction.clear();
204 self.input.pending_touch = None;
205 if self.nine_key.active() {
206 return KeyResult::unhandled();
207 }
208 self.input.glide(keyboard, points)
209 }
210
211 pub fn key_grid(&self) -> Option<KeyGrid> {
213 self.key_grid
214 }
215
216 pub fn grid_composing(&self) -> bool {
218 self.nine_key.active() || self.input.wubi_grid_composing()
219 }
220
221 pub fn set_nine_key_enabled(&mut self, enabled: bool) {
223 self.set_key_grid(enabled.then_some(KeyGrid::NineKey));
224 }
225
226 pub fn set_key_grid(&mut self, grid: Option<KeyGrid>) {
228 self.candidates_may_change();
229 self.prediction.clear();
230 self.input.pending_touch = None;
231 self.input.helpcode_entry = None;
232 self.nine_key.command(Command::Cancel);
233 if let Some(grid) = grid {
234 self.nine_key.set_grid(grid);
235 }
236 self.key_grid = grid;
237 let nine_key = grid == Some(KeyGrid::NineKey);
238 self.nine_key
239 .set_english_only(nine_key && self.input.dedicated_english);
240 self.input.set_zhuyin_nine_key(nine_key);
241 }
242
243 pub fn grid_key(&mut self, letter: u8) -> KeyResult {
245 self.candidates_may_change();
246 self.prediction.clear();
247 self.input.pending_touch = None;
248 let Some(
249 grid @ (KeyGrid::FourteenKey
250 | KeyGrid::EighteenKey
251 | KeyGrid::FourteenKeyShuangpin(_)
252 | KeyGrid::EighteenKeyShuangpin(_)),
253 ) = self.key_grid
254 else {
255 return KeyResult::unhandled();
256 };
257 let Some(code) = grid.code_of(letter) else {
258 return KeyResult::unhandled();
259 };
260 if grid == KeyGrid::FourteenKey && self.input.scheme() == SchemeType::Wubi {
262 let Some(representative) = crate::wubi::grid::representative(code) else {
263 return KeyResult::unhandled();
264 };
265 return self.input.handle_wubi_grid_key(representative);
266 }
267 let supported = match grid {
268 KeyGrid::FourteenKey | KeyGrid::EighteenKey => {
269 self.input.scheme() == SchemeType::Quanpin
270 }
271 KeyGrid::FourteenKeyShuangpin(_) | KeyGrid::EighteenKeyShuangpin(_) => {
272 self.input.scheme() == SchemeType::Shuangpin
273 }
274 _ => false,
275 };
276 if !supported
277 || self.input.local_mode != LocalInputMode::None
278 || self.input.dedicated_english
279 || !(self.input.preedit().is_empty() || self.nine_key.active())
280 {
281 return KeyResult::unhandled();
282 }
283 let result = self.nine_key.character(code);
284 self.after_nine_key(result)
285 }
286
287 pub fn grid_limit_pick_ready(&self, value: u8, grid_key: bool) -> bool {
289 let code = match self.key_grid {
290 Some(
291 grid @ (KeyGrid::FourteenKey
292 | KeyGrid::EighteenKey
293 | KeyGrid::FourteenKeyShuangpin(_)
294 | KeyGrid::EighteenKeyShuangpin(_)),
295 ) if grid_key => grid.code_of(value),
296 Some(KeyGrid::NineKey | KeyGrid::NineKeyShuangpin(_)) if !grid_key => Some(value),
297 _ => None,
298 };
299 code.is_some_and(|code| self.nine_key.limit_pick_ready(code))
300 }
301
302 pub fn choose_nine_key_spelling(&mut self, index: usize) -> KeyResult {
304 self.candidates_may_change();
305 self.input.pending_touch = None;
306 if !self.nine_key.active() && self.input.zhuyin_rules_apply() {
307 return self.input.choose_zhuyin_spelling(index);
308 }
309 let result = self.nine_key.choose_spelling(index);
310 self.after_nine_key(result)
311 }
312
313 pub fn set_nine_key_filter(&mut self, single_character: bool, strokes: &str) -> KeyResult {
315 self.candidates_may_change();
316 self.input.pending_touch = None;
317 if !self.nine_key.active() {
318 return KeyResult::unhandled();
319 }
320 self.nine_key.set_filter(single_character, strokes)
321 }
322
323 pub fn command(&mut self, command: Command) -> KeyResult {
324 self.candidates_may_change();
325 self.input.pending_touch = None;
326 if !self.prediction.rows.is_empty() {
327 self.prediction.clear();
328 return if command == Command::Cancel {
329 KeyResult::handled()
330 } else {
331 self.input.handle_command(command)
332 };
333 }
334 let selected = (self.prediction.enabled && command == Command::CommitCandidate)
335 .then(|| self.snapshot().candidates.first().cloned())
336 .flatten();
337 if self.nine_key.active() {
338 let command = match command {
340 Command::ConversionLeft => Command::MoveLeft,
341 Command::ConversionRight => Command::MoveRight,
342 other => other,
343 };
344 let result = self.nine_key.command(command);
345 let result = self.after_nine_key(result);
346 self.predict_after_commit(selected, &result);
347 return result;
348 }
349 let result = self.input.handle_command(command);
350 self.predict_after_commit(selected, &result);
351 result
352 }
353
354 pub fn conversion_left_from(&mut self, index: usize) -> KeyResult {
356 self.candidates_may_change();
357 self.prediction.clear();
358 self.input.pending_touch = None;
359 if self.nine_key.active() {
360 return self.command(Command::MoveLeft);
361 }
362 self.input.conversion_left_from(index)
363 }
364
365 pub fn candidate_key(&mut self, value: u8) -> KeyResult {
367 self.candidates_may_change();
368 self.prediction.clear();
369 self.input.pending_touch = None;
370 if self.nine_key.active() {
371 if !(b'1'..=b'9').contains(&value) {
372 return KeyResult::unhandled();
373 }
374 let result = self.nine_key.select(usize::from(value - b'1'));
375 return self.after_nine_key(result);
376 }
377 self.input.handle_candidate_key(value)
378 }
379
380 pub fn punctuation(&mut self, value: u8) -> KeyResult {
382 self.candidates_may_change();
383 self.prediction.clear();
384 self.input.pending_touch = None;
385 if !self.nine_key.active() {
386 return self.input.handle_punctuation(value);
387 }
388 let mut result = self.input.handle_punctuation(value);
389 if !result.handled {
390 return result;
391 }
392 let composition = self.nine_key.finish(0);
394 if composition.commit.is_some() {
395 self.input.reset_commit_context();
396 }
397 let mut text = composition.commit.unwrap_or_default();
398 text.push_str(result.commit.as_deref().unwrap_or_default());
399 result.commit = Some(text);
400 result
401 }
402
403 pub fn set_chinese_punctuation_enabled(&mut self, enabled: bool) {
405 self.input.chinese_punctuation_enabled = enabled;
406 }
407
408 pub fn set_punctuation_lock(&mut self, lock: i32) -> Result<()> {
410 if !(0..=2).contains(&lock) {
411 return Err(EngineError::invalid(diagnostics::INVALID_PUNCTUATION_LOCK));
412 }
413 self.input.punctuation_lock = lock;
414 Ok(())
415 }
416
417 pub fn set_paired_punctuation_enabled(&mut self, enabled: bool) {
418 self.input.punctuation.set_paired_enabled(enabled);
419 }
420
421 pub fn set_punctuation_marks(&mut self, marks: crate::punctuation::PunctuationMarks) {
423 self.input.punctuation.set_marks(marks);
424 }
425
426 pub fn balance_paired_punctuation_after_auto_close(&mut self, opening: u8) {
428 self.input.punctuation.balance_after_auto_close(opening);
429 }
430
431 pub fn select(&mut self, index: usize) -> KeyResult {
432 self.candidates_may_change();
433 self.input.pending_touch = None;
434 if !self.prediction.rows.is_empty() {
435 let Some((row, word, pinyin)) = self.prediction.selected(index) else {
436 return KeyResult::unhandled();
437 };
438 let diagnostic = self.input.record_personal_context(Some(&row), false, false);
439 self.show_prediction(&word, &pinyin, &row.word);
440 return KeyResult::committed(row.word.to_string()).with_diagnostic(diagnostic);
441 }
442 let selected = self
443 .prediction
444 .enabled
445 .then(|| self.snapshot().candidates.get(index).cloned())
446 .flatten();
447 if self.nine_key.active() {
448 let result = self.nine_key.select(index);
449 let result = self.after_nine_key(result);
450 self.predict_after_commit(selected, &result);
451 return result;
452 }
453 let result = self.input.select_candidate(index);
454 self.predict_after_commit(selected, &result);
455 result
456 }
457
458 pub fn select_edge(&mut self, index: usize, edge: CandidateEdge) -> KeyResult {
460 self.candidates_may_change();
461 self.input.pending_touch = None;
462 if !self.prediction.rows.is_empty() {
463 return KeyResult::unhandled();
464 }
465 if self.nine_key.active() {
466 return KeyResult::unhandled();
467 }
468 self.input.select_candidate_edge(index, edge)
469 }
470
471 pub fn pin(&mut self, index: usize) -> KeyResult {
473 self.candidates_may_change();
474 self.input.pending_touch = None;
475 if !self.prediction.rows.is_empty() {
476 return KeyResult::unhandled();
477 }
478 if self.nine_key.active() {
479 return self.nine_key.pin(index);
480 }
481 self.input.pin_candidate(index)
482 }
483
484 pub fn remove(&mut self, index: usize) -> KeyResult {
486 self.candidates_may_change();
487 self.input.pending_touch = None;
488 if !self.prediction.rows.is_empty() {
489 return KeyResult::unhandled();
490 }
491 if self.nine_key.active() {
492 return self.nine_key.remove(index);
493 }
494 self.input.remove_candidate(index)
495 }
496
497 pub fn fix_position(&mut self, index: usize, position: i32) -> KeyResult {
499 self.candidates_may_change();
500 self.input.pending_touch = None;
501 if !self.prediction.rows.is_empty() {
502 return KeyResult::unhandled();
503 }
504 if !(1..=5).contains(&position) {
505 return KeyResult::unhandled();
506 }
507 if self.nine_key.active() {
508 return self.nine_key.set_position(index, position);
509 }
510 self.input.set_candidate_position(index, position)
511 }
512
513 pub fn clear_position(&mut self, index: usize) -> KeyResult {
514 self.candidates_may_change();
515 self.input.pending_touch = None;
516 if !self.prediction.rows.is_empty() {
517 return KeyResult::unhandled();
518 }
519 if self.nine_key.active() {
520 return self.nine_key.set_position(index, 0);
521 }
522 self.input.set_candidate_position(index, 0)
523 }
524
525 pub fn finish(&mut self, first_index: usize) -> KeyResult {
527 self.candidates_may_change();
528 self.prediction.clear();
529 self.input.pending_touch = None;
530 if self.nine_key.active() {
531 let result = self.nine_key.finish(first_index);
532 return self.after_nine_key(result);
533 }
534 self.input.finish_composition(first_index)
535 }
536
537 pub fn switch_scheme(&mut self, scheme: SchemeType) -> Result<()> {
539 self.candidates_may_change();
540 self.input.pending_touch = None;
541 self.input.switch_scheme(scheme)?;
542 self.prediction.clear();
543 self.nine_key.command(Command::Cancel);
544 Ok(())
545 }
546
547 pub fn is_supported_helpcode_schema(schema: &str) -> bool {
548 crate::helpcode::is_supported_helpcode_schema(schema)
549 }
550
551 pub fn set_helpcode_schema(&mut self, schema: &str) -> bool {
553 self.candidates_may_change();
554 self.input.set_helpcode_schema(schema)
555 }
556
557 pub fn set_helpcode_table(&mut self, table: crate::helpcode::SharedKeymap) {
559 self.candidates_may_change();
560 self.input.set_helpcode_table(table);
561 }
562
563 pub fn set_helpcode_enabled(&mut self, enabled: bool) {
565 self.candidates_may_change();
566 self.input.set_helpcode_enabled(enabled);
567 }
568
569 pub fn set_dedicated_english(&mut self, enabled: bool) {
571 self.candidates_may_change();
572 self.input.pending_touch = None;
573 if self.input.dedicated_english == enabled {
574 return;
575 }
576 self.prediction.clear();
577 self.nine_key.command(Command::Cancel);
578 self.input.set_dedicated_english_mode(enabled);
579 self.nine_key
581 .set_english_only(self.key_grid == Some(KeyGrid::NineKey) && enabled);
582 }
583
584 pub fn wubi_pinyin_continues(&self, value: u8, shift: bool) -> bool {
586 !shift
587 && !self.input.dedicated_english
588 && self.input.local_mode == LocalInputMode::None
589 && self.input.engine.wubi_pinyin_continues(value)
590 }
591
592 pub fn set_wubi_mixed_pinyin(&mut self, enabled: bool) {
593 self.candidates_may_change();
594 self.input.set_wubi_mixed_pinyin(enabled);
595 }
596
597 pub fn wubi_mixed_pinyin(&self) -> bool {
599 self.input.engine.wubi_input_options().mixed_pinyin
600 }
601
602 pub fn reset_cache(&mut self) {
604 self.candidates_may_change();
605 self.prediction.clear();
606 self.input.reset_cache();
607 crate::japanese::decoder::JapaneseDictionary::release_shared();
608 crate::release_idle_personal_models();
609 }
610
611 pub fn set_command_table(&mut self, table: &[CommandTableEntry]) -> Option<String> {
613 self.candidates_may_change();
614 self.input.set_command_table(table)
615 }
616
617 pub fn set_plugin_commands(&mut self, commands: &[PluginCommand]) -> Option<String> {
619 self.candidates_may_change();
620 self.input.set_plugin_commands(commands)
621 }
622
623 pub fn plugin_command_title(&self, trigger: &str) -> Option<&str> {
625 self.input.queries.plugin_command_title(trigger)
626 }
627
628 pub fn set_quick_phrase_table(&mut self, table: &[QuickPhraseEntry]) -> Option<String> {
630 self.candidates_may_change();
631 self.input.set_quick_phrase_table(table)
632 }
633
634 pub fn set_mention_entries(&mut self, entries: &[MentionEntry]) -> Option<String> {
636 self.candidates_may_change();
637 self.input.set_mention_entries(entries)
638 }
639
640 pub fn set_mention_places(&mut self, enabled: bool) -> Option<String> {
642 self.candidates_may_change();
643 self.input.set_mention_places(enabled)
644 }
645
646 pub fn reset_context(&mut self) {
648 self.candidates_may_change();
649 self.prediction.clear();
650 self.input.pending_touch = None;
651 self.input.reset_commit_context();
652 }
653
654 pub fn set_prediction_allowed(&mut self, allowed: bool) {
656 self.candidates_may_change();
657 self.prediction.set_allowed(allowed);
658 }
659
660 pub fn set_personal_context_enabled(&mut self, enabled: bool) {
662 self.candidates_may_change();
663 self.prediction.clear();
664 self.input.set_personal_context_enabled(enabled);
665 self.nine_key.set_personal_context_enabled(enabled);
666 }
667
668 pub fn cloud_candidates_enabled(&self) -> bool {
670 self.input.cloud_candidates_enabled
671 }
672
673 pub fn set_cloud_candidates_enabled(&mut self, enabled: bool) {
675 self.candidates_may_change();
676 self.input.set_cloud_candidates_enabled(enabled);
677 }
678
679 pub fn english_online_candidates_enabled(&self) -> bool {
680 self.input.english_online_candidates_enabled
681 }
682
683 pub fn set_english_online_candidates_enabled(&mut self, enabled: bool) {
684 self.candidates_may_change();
685 self.input.set_english_online_candidates_enabled(enabled);
686 }
687
688 pub fn set_rescoring_context(&mut self, context: &str) {
690 self.input.set_rescoring_context(context);
691 self.nine_key.set_rescoring_context(context);
692 }
693
694 pub fn set_first_page_rows(&mut self, rows: FirstPageRows) {
697 self.candidates_may_change();
698 self.input.set_first_page_rows(rows);
699 self.nine_key.set_first_page_rows(rows);
700 }
701
702 pub fn expand_initial_candidates(&mut self) -> bool {
704 self.input.pending_touch = None;
705 if self.nine_key.active() {
707 return false;
708 }
709 let grew = self.input.expand_initial_candidates();
710 if grew {
711 self.candidates_may_change();
712 }
713 grew
714 }
715
716 pub fn set_caret(&mut self, caret: Option<usize>) {
718 self.candidates_may_change();
719 self.prediction.clear();
720 self.input.pending_touch = None;
721 if self.nine_key.active() || self.input.wubi_grid_composing() {
723 return;
724 }
725 self.input.set_caret(caret);
726 }
727
728 pub fn prefix_end(&self) -> usize {
730 if self.nine_key.active() {
731 return 0;
732 }
733 self.input.prefix_end()
734 }
735
736 pub fn pending_suffix(&self) -> String {
738 if self.nine_key.active() {
739 return String::new();
740 }
741 self.input.pending_suffix()
742 }
743
744 pub fn snapshot(&self) -> SessionSnapshot {
745 self.snapshot_with_display_options(true, false)
746 }
747
748 pub fn display_snapshot(&self) -> SessionSnapshot {
750 self.snapshot_with_display_options(true, true)
751 }
752
753 pub(crate) fn snapshot_for_host(&self, show_helpcode: bool) -> SessionSnapshot {
754 self.snapshot_with_display_options(show_helpcode, true)
755 }
756
757 fn snapshot_with_display_options(
758 &self,
759 show_helpcode: bool,
760 segmented_quanpin: bool,
761 ) -> SessionSnapshot {
762 if !self.prediction.rows.is_empty() {
763 return SessionSnapshot {
764 prediction: true,
765 scheme: self.input.scheme(),
766 local_mode: self.input.local_mode,
767 spelling_symbols: self.input.spelling_symbols(),
768 shuangpin_profile: self.input.profile.kind.name().to_owned(),
769 candidates: self.prediction.rows.clone(),
770 candidate_sources: self.prediction.rows.iter().map(|row| row.source).collect(),
771 candidate_annotations: vec![String::new(); self.prediction.rows.len()],
772 candidate_answers_key: vec![false; self.prediction.rows.len()],
773 ..SessionSnapshot::default()
774 };
775 }
776 if self.nine_key.active() {
777 return self.nine_key.snapshot();
778 }
779 let hide_helpcode = !show_helpcode
780 && self.input.helpcode_enabled()
781 && !self.input.local_mode.annotates_rows();
782 let input = &self.input;
783 let mut candidates = input.candidates().to_vec();
784 for item in &mut candidates {
785 input.apply_english_word_case(item.source, &mut item.word);
786 }
787 let caret_edit_suffix = input.caret_edit.active && input.conversion.is_none();
789 let caret_edit_prefix = input
790 .caret_edit_prefix()
791 .filter(|_| caret_edit_suffix && !candidates.is_empty())
792 .unwrap_or_default();
793 let segmented_preedit = (segmented_quanpin && input.scheme() == SchemeType::Quanpin)
795 || (!self.shuangpin_preedit_uses_raw && input.scheme() == SchemeType::Shuangpin);
796 let preedit = if segmented_preedit
797 && input.local_mode == LocalInputMode::None
798 && !input.dedicated_english
799 {
800 input.pinyin_segmentation_with_cases().into_owned()
801 } else {
802 input.preedit()
803 };
804 SessionSnapshot {
805 prediction: false,
806 scheme: input.scheme(),
807 local_mode: input.local_mode,
808 spelling_symbols: input.spelling_symbols(),
809 preedit,
810 raw_segmentation: input.raw_segmentation(),
811 normalized_segmentation: input.normalized_segmentation(),
812 dedicated_english: input.dedicated_english,
813 editing_text: input.editing_text(),
814 caret_position: input.caret_position(),
815 nine_key_spellings: input.zhuyin_spellings(),
816 nine_key_reading: if input.wubi_grid_composing() {
818 input.preedit()
819 } else {
820 String::new()
821 },
822 nine_key_single_character: false,
823 nine_key_strokes: String::new(),
824 answered_by_pinyin_fallback: input.answered_by_pinyin_fallback(),
825 wubi_unique_four_code: input.wubi_unique_four_code(),
826 shuangpin_profile: input.profile.kind.name().to_owned(),
827 candidate_sources: candidates.iter().map(|item| item.source).collect(),
828 multilingual_conversion: candidates
829 .iter()
830 .any(|item| item.source == CandidateSource::Multilingual)
831 .then(|| input.multilingual_conversion.clone())
832 .flatten(),
833 candidate_annotations: input.candidate_annotations(hide_helpcode),
834 candidate_answers_key: candidates
836 .iter()
837 .map(|item| {
838 !matches!(
840 item.source,
841 CandidateSource::AiPending
842 | CandidateSource::CloudPending
843 | CandidateSource::Contact
844 ) && !input.is_url_completion(item)
845 && input.selection_completes_composition(item)
846 })
847 .collect(),
848 candidate_list_open: input.candidate_list_open(),
849 conversion: input
850 .conversion
851 .as_ref()
852 .map(|edit| edit.text())
853 .unwrap_or_default(),
854 conversion_focus: input
855 .conversion
856 .as_ref()
857 .map_or((0, 0), |edit| edit.focus_span()),
858 caret_edit_suffix,
859 caret_edit_prefix,
860 candidates,
861 }
862 }
863
864 pub fn candidate_wubi_code(&self, word: &str) -> Option<&str> {
866 if self.nine_key.active() {
867 return None;
868 }
869 self.input.engine.candidate_wubi_code(word)
870 }
871
872 pub(crate) fn candidate_wubi_code_at_if_item(
874 &self,
875 index: usize,
876 item: &WordItem,
877 ) -> Option<&str> {
878 if self.nine_key.active() {
879 return None;
880 }
881 self.input
882 .engine
883 .candidate_wubi_code_at_if_item(index, item)
884 }
885
886 pub(crate) fn candidate_wubi_code_if_item(&self, item: &WordItem) -> Option<&str> {
887 if self.nine_key.active() {
888 return None;
889 }
890 self.input.engine.candidate_wubi_code_if_item(item)
891 }
892
893 pub fn segment_raw_boundaries(&self) -> Vec<usize> {
895 if self.nine_key.active() {
896 return Vec::new();
897 }
898 self.input.segment_raw_boundaries()
899 }
900
901 pub fn online_query(&self) -> Option<OnlineQuery> {
902 if self.nine_key.active() {
903 return None;
904 }
905 self.input.online_query()
906 }
907
908 pub fn takes_local_separator(&self) -> bool {
910 !self.nine_key.active() && self.input.takes_local_separator()
911 }
912
913 pub fn command_translation_query(&self) -> Option<CommandTranslationQuery> {
915 if self.nine_key.active() {
916 return None;
917 }
918 self.input.command_translation_query()
919 }
920
921 pub fn apply_command_translation(
923 &mut self,
924 query: &CommandTranslationQuery,
925 translation: &str,
926 ) -> bool {
927 if self.nine_key.active() {
928 return false;
929 }
930 let applied = self.input.apply_command_translation(query, translation);
931 if applied {
932 self.candidates_may_change();
933 }
934 applied
935 }
936
937 pub fn plugin_command_query(&self) -> Option<PluginCommandQuery> {
939 if self.nine_key.active() {
940 return None;
941 }
942 self.input.plugin_command_query()
943 }
944
945 pub fn apply_plugin_command_rows(
947 &mut self,
948 query: &PluginCommandQuery,
949 rows: &[(String, String)],
950 ) -> bool {
951 if self.nine_key.active() {
952 return false;
953 }
954 let applied = self.input.apply_plugin_command_rows(query, rows);
955 if applied {
956 self.candidates_may_change();
957 }
958 applied
959 }
960
961 pub fn apply_online_candidates(
963 &mut self,
964 query: &OnlineQuery,
965 words: &[String],
966 source: CandidateSource,
967 ) -> bool {
968 if self.nine_key.active() {
969 return false;
970 }
971 let applied = self.input.apply_online_candidates(query, words, source);
972 if applied {
973 self.candidates_may_change();
974 }
975 applied
976 }
977
978 pub fn clear_online_candidates(&mut self, source: CandidateSource) {
980 self.candidates_may_change();
981 self.input.clear_online_candidates(source);
982 }
983
984 pub fn apply_online_candidate(
985 &mut self,
986 query: &OnlineQuery,
987 word: &str,
988 source: CandidateSource,
989 ) -> bool {
990 if self.nine_key.active() {
991 return false;
992 }
993 let applied = self.input.apply_online_candidate(query, word, source);
994 if applied {
995 self.candidates_may_change();
996 }
997 applied
998 }
999
1000 fn after_nine_key(&mut self, result: KeyResult) -> KeyResult {
1002 if result.commit.is_some() {
1003 self.input.note_external_commit();
1004 }
1005 result
1006 }
1007
1008 fn predict_after_commit(&mut self, selected: Option<WordItem>, result: &KeyResult) {
1009 if !self.prediction.enabled
1010 || self.nine_key.active()
1011 || self.input.has_composition()
1012 || self.input.dedicated_english
1013 || self.input.local_mode != LocalInputMode::None
1014 || !matches!(
1015 self.input.scheme(),
1016 SchemeType::Quanpin | SchemeType::Shuangpin
1017 )
1018 {
1019 return;
1020 }
1021 let Some(selected) = selected else { return };
1022 if result.commit.as_deref() != Some(selected.word.as_str()) {
1023 return;
1024 }
1025 let previous = selected
1026 .sentence_words
1027 .last()
1028 .map_or(selected.word.as_str(), String::as_str);
1029 self.show_prediction(&selected.word, &selected.canonical_pinyin, previous);
1030 }
1031
1032 fn show_prediction(&mut self, word: &str, pinyin: &str, previous: &str) {
1033 let personal = self
1034 .input
1035 .personal_context_applies()
1036 .then_some(self.input.personal_context.as_ref());
1037 self.prediction.show(word, pinyin, previous, personal);
1038 }
1039}
1040
1041impl Drop for Session {
1043 fn drop(&mut self) {
1044 let _ = flush_journal(&self.journal);
1046 release_thread_journal();
1048 self.journal_use.end();
1050 note_settled(&self.journal);
1052 }
1053}