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