Files
Lu Fan(SonnyCalcr) 58a27f3edb fix(engine,server): 候选末页满页且无法再展开时置灰下一页箭头 (#1070)
下一页箭头原先在当前页满时一律可点,为首字母候选分批展开留口子;
但只有单个首字母且首字母候选恰好截在 24 条时才能展开,其余情况
末页满页的右箭头点了没反应也不会变灰。

引擎新增只判断不查库的 can_expand_initial_candidates,与实际展开
共用前置条件;Server 发布候选页快照时,满页只有在还能展开时才算
有下一页。

Refs #1069
2026-10-18 23:11:22 +08:00

402 lines
23 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include "ime_session.h"
#include "input_session_types.h"
#include "candidate_queries.h"
#include "punctuation_policy.h"
#include "online_request_guard.h"
#include "../local_modes/date_time_query.h"
#include "../english/english_dictionary.h"
#include "word_item.h"
#include "../quanpin/engine.h"
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace metasequoia
{
// Platform-neutral composition session shared by the native frontends. It owns an ImeSession and
// applies the key-handling and commit policy that each frontend would otherwise reimplement, so a
// frontend only has to translate platform key events into these calls.
class InputSession
{
public:
// Frontends pass their persisted options at session creation so every platform uses the same
// engine configuration and commit policy.
explicit InputSession(SchemeType scheme_type = SchemeType::Quanpin, unsigned quanpin_autocorrect_types = 0,
bool helpcode_enabled = true, bool chinese_punctuation_enabled = true,
bool candidate_learning_enabled = true, RuntimePaths paths = RuntimePaths::legacy());
InputSession(SchemeType scheme_type, const ShuangpinProfile &shuangpin_profile,
RuntimePaths paths = RuntimePaths::legacy());
// Feeds one lowercase ASCII letter or an in-composition apostrophe. Other input is rejected as
// unhandled so the frontend can pass it through to the client application.
KeyResult handle_character(char character, bool shift_only = false);
// Applies a command. Every command is unhandled while no composition is active, which keeps
// Backspace and Escape working normally in the client application.
KeyResult handle_command(Command command);
// Maps the visible 1-9 candidate keys and Chinese punctuation independently of platform UI.
KeyResult handle_candidate_key(char character);
KeyResult handle_punctuation(char character);
// Commit the selected prefix and retain any unconsumed pinyin. Hosts insert
// KeyResult::commit and then render the remaining preedit from this session.
KeyResult select_candidate(std::size_t index);
// Flush all remaining input for punctuation, scheme changes and host passthrough.
KeyResult finish_composition(std::size_t first_index = 0);
KeyResult select_candidate(const std::string &candidate);
KeyResult select_candidate_edge(std::size_t index, CandidateEdge edge);
KeyResult pin_candidate(std::size_t index);
KeyResult remove_candidate(std::size_t index);
KeyResult set_candidate_position(std::size_t index, int position);
void enable_fixed_positions();
void set_shuangpin_helpcode_enabled(bool enabled);
// 双拼末尾辅助码的触发方式。direct(默认)按输入形状认辅码:完整音节后多一个键是单码,末尾两键带
// 大写是双码。关掉后改由引导键触发:宿主把触发键(反引号或分号)换成 kEndMarker 交进来,
// uppercase_trigger 开着时完整音节后的大写字母也开一段,见 FanyImeMidSentenceHelpcode::kEndMarker。
void set_shuangpin_helpcode_triggers(bool direct, bool uppercase_trigger);
// 宿主在把触发键换成 kEndMarker 之前问这一句:双拼辅助码开着、关着直接、没开直接辅助码,且光标在
// 输入串末尾、前面是一个个完整的双拼音节。
bool accepts_end_helpcode_marker_at(std::size_t caret) const;
// 句中辅助码(双拼),见 core/syllable_helpcode.h。默认关闭。
void set_mid_sentence_helpcode_enabled(bool enabled);
// 宿主在决定是否把反引号当作编码键之前问这一句:开关开着、双拼,且光标前这一节能接一段句中
// 辅助码(光标可以在句中,见 FanyImeMidSentenceHelpcode::AcceptsMarkerAt)。不满足时反引号
// 仍按标点处理。不带参数的版本按会话自己的光标;光标由宿主维护时传宿主的光标。
bool accepts_mid_sentence_helpcode_marker() const;
bool accepts_mid_sentence_helpcode_marker_at(std::size_t caret) const;
// 双拼直接辅助码(万象式,不要引导键,见 engine/direct_helpcode/)。默认关闭;开着时末尾辅助码
// 和反引号句中辅助码都让位给它。
// 句中辅助码的大写触发:完整音节后的大写字母相当于「反引号 + 这个字母」(第二码规则不变),见
// FanyImeMidSentenceHelpcode::StartsUppercaseBlock。只在句中辅助码开着、直接辅助码关着时生效。
void set_mid_sentence_uppercase_trigger_enabled(bool enabled);
void set_direct_helpcode_enabled(bool enabled);
bool direct_helpcode_enabled() const
{
return direct_helpcode_enabled_;
}
// 句中四码用什么结束:补 /(默认)、第二位辅码大写,可以都开。/ 关着时不再是编码键。
void set_direct_helpcode_markers(bool slash, bool uppercase);
// 宿主在决定是否把 / 当作编码键(四码后的终止键)之前问这一句,规则见
// FanyImeDirectHelpcode::AcceptsSlashAt。不满足时 / 仍按标点处理。
bool accepts_direct_helpcode_slash_at(std::size_t caret) const;
// 当前输入带着生效的句中辅助码约束:候选是筛过的,排位不能当调频的参照。
bool has_mid_sentence_helpcode() const;
// 同一输入去掉句中辅助码约束后的引擎候选。用户下次不敲辅助码时看到的就是这份排序,
// 调频要在这份里给选中的词挪位置;在筛过的列表里它往往已经排第一,调了等于没调。
// 没有约束时就是当前引擎候选。
std::vector<WordItem> candidates_without_mid_sentence_helpcode();
void set_quanpin_helpcode_enabled(bool enabled);
static bool is_supported_helpcode_schema(const std::string &schema);
bool set_helpcode_schema(const std::string &schema);
// Compatibility default for subsequently created sessions.
static bool select_helpcode_schema(const std::string &schema);
bool set_frequency_adjustment(FrequencyAdjustmentOptions options);
const FrequencyAdjustmentOptions &frequency_adjustment() const;
void set_local_mode_options(LocalModeOptions options);
const LocalModeOptions &local_mode_options() const;
bool set_english_input_options(EnglishInputOptions options);
const EnglishInputOptions &english_input_options() const;
void set_mixed_expressive_options(MixedExpressiveOptions options);
void set_wubi_input_options(metasequoia::WubiInputOptions options);
// 读回当前五笔设置:公开 Session 的运行期开关要按字段改,不能整份覆盖(否则会把另一个
// 独立开关复位)。
const metasequoia::WubiInputOptions &wubi_input_options() const;
const MixedExpressiveOptions &mixed_expressive_options() const;
void set_dedicated_english_mode(bool enabled);
bool dedicated_english_mode() const;
LocalInputMode local_input_mode() const;
void set_local_date_time_provider(std::function<local_modes::LocalDateTime()> provider);
std::optional<OnlineQuery> online_query() const;
bool apply_online_candidate(const OnlineQuery &query, std::string candidate, CandidateSource source);
SchemeType scheme_type() const;
unsigned quanpin_autocorrect_types() const;
bool helpcode_enabled() const;
bool chinese_punctuation_enabled() const;
bool candidate_learning_enabled() const;
// Switching schemes discards the current composition. A frontend that promises to preserve
// typed text must commit it before calling this method.
void switch_scheme(SchemeType scheme_type);
SchemeType scheme() const;
bool has_composition() const;
const std::string &preedit() const;
std::string editing_text() const;
std::size_t caret_position() const;
const std::string &raw_segmentation() const;
const std::string &normalized_segmentation() const;
const std::vector<WordItem> &candidates() const;
// Advanced composition operations for hosts with their own asynchronous text insertion.
// They share the same engine/configuration as the portable character/command API.
struct SelectionTransition
{
bool continues_composition = false;
// 刚选中的候选由五笔码表产出。上屏推进与造词都按它决定:五笔候选一次消耗整串编码,
// 拼音候选则只消耗自己那段、把剩余字母继续留在组合里。混输组合里两类候选共存,
// 不能再按会话方案一刀切。
bool wubi_native = false;
std::string full_pure_pinyin;
std::string current_segmentation;
std::string current_segmentation_with_cases;
std::string selected_canonical_pinyin;
// Raw spelling the selection consumed from the active scheme's input,
// in the exact form the user typed it. A host that lets the user retract
// a selected segment must replay this spelling, not the pre-selection
// raw: the pre-selection raw also contains suffix characters the user
// may have deleted since the selection. Sources that consume no input
// (cloud, associative, whole-word commits) leave it empty.
std::string consumed_raw_input_with_cases;
};
struct CloudQueryState
{
bool should_query = false;
std::string query_text;
std::string cache_key;
std::string committed_pinyin;
};
struct CreatingWordProgress
{
std::string pinyin;
std::string word;
std::string preedit;
bool completed = false;
bool can_store = false;
};
void handle_engine_key(ImeKeyCode vk, ImeModifierMask modifiers_down, ImeCharacter wch);
void recompute_candidates();
// Moves the caret without editing the raw string. nullopt = end of string (full-string
// decoding, the default). The value is clamped to [0, editing_text().size()]. Setting it
// only updates state; candidates re-decode by the new boundary on the next
// recompute_candidates() or key handling.
void set_caret(std::optional<std::size_t> caret);
// Raw length consumed by the current decode: the caret moved onto the last complete
// syllable-unit boundary at or before it (floor). A caret inside the first unit rounds up to
// the end of that unit instead, so prefix_end() == 0 means caret == 0. The caret being
// unset, or a scheme without the unit model (segment_raw_boundaries() empty), decodes the
// whole string and this equals the raw length.
std::size_t prefix_end() const;
// raw[prefix_end, size) with its original casing: the pending input this decode did not
// consume. Empty unless the caret prefix is strictly shorter than the raw string.
std::string pending_suffix() const;
SchemeType current_scheme_type() const;
void reset_state();
void reset_cache();
// 神经重排结果回来后用:只失效整句排序,词库和切分缓存保持热,且不在这里重算,
// 由调用方接着 recompute_candidates()。
void reset_sentence_cache();
const std::vector<WordItem> &get_candidates() const;
bool can_expand_initial_candidates() const;
bool expand_initial_candidates();
std::optional<WordItem> find_candidate(const std::string &key, const std::string &value);
const std::string &get_pinyin_sequence() const;
const std::string &get_pinyin_sequence_with_cases() const;
const std::string &get_pure_pinyin_sequence() const;
const std::string &get_pinyin_segmentation() const;
std::string get_pinyin_segmentation_with_cases() const;
// 双拼会话的两种预编辑切分,不受 set_shuangpin_preedit_uses_raw 影响:raw 是按键原串的
// 切分(ni'hc),quanpin 是转换后的全拼切分(ni'hao),两者音节数一致、辅助码装饰相同。
// 非双拼会话两者都为空。
struct ShuangpinPreeditForms
{
std::string raw;
std::string quanpin;
};
ShuangpinPreeditForms get_shuangpin_preedit_forms() const;
// Offsets in get_pinyin_sequence_with_cases() where one input unit starts,
// always including 0 (when non-empty) and raw.size(). A unit is one syllable:
// the `ma` of ni'hao'ma, one 1-2 key syllable in shuangpin. Schemes and
// local modes without the unit model (wubi, japanese, U/K/E/M/J/Y/R and the
// dedicated English scheme) return an empty vector, and hosts then fall back
// to single-character editing. An active autocorrect/helpcode display is
// mapped back to the raw spans the engine actually cut, so a deleted unit
// can never leave half a segment or a stray separator behind. Consumed by
// segment deletion (Ctrl+Backspace), so any later caret-movement feature
// must use this same boundary set instead of re-deriving one.
std::vector<std::size_t> segment_raw_boundaries() const;
std::string get_quanpin() const;
bool is_all_complete_pure_pinyin() const;
// 整串输入能不能读成拼音:全拼按「完整音节或音节前缀」(含简拼声母,bus = bu + s)切完整串,
// 双拼按完整音节码、末尾允许单独一个声母键。不要求音节完整,也不看词库有没有词。混输里用它
// 决定英文前缀补全词的默认位置:能读成拼音的输入,用户多半在打中文,补全词默认退到首页末位。
bool reads_as_pinyin() const;
// The current composition is a complete four-letter wubi code the wubi table answered with
// exactly one row. Hosts decide whether to auto-commit on this; the engine only reports the fact.
// The pinyin candidates mixed input appends are deliberately not counted: a code the table did
// not answer is not a unique wubi code, and committing a pinyin answer as one would take away
// the extra letters mixed input exists to allow.
bool wubi_unique_four_code() const;
// The current composition is a complete four-letter wubi code the wubi table answered, regardless
// of how many candidates it has. Hosts use it to commit the first candidate when the user types
// past the fourth letter: a complete code that keeps growing must not silently swallow the extra
// letters. In mixed input the appended pinyin candidates do not count; only the code's own rows
// make it complete. See wubi_unique_four_code for the uniqueness part.
bool wubi_four_code_is_complete() const;
bool has_active_helpcode() const;
void set_pinyin_sequence(const std::string &pinyin_sequence);
void set_pinyin_sequence_with_cases(const std::string &pinyin_sequence);
// Marks the pending sequence as a special mode's literal input on a wubi session (T mode's
// Tdate, V mode's V1+2): kept as typed, never queried, never a complete code. Applies to the
// sequence set alongside it and is dropped with it, so a host that does not call it gets an
// ordinary code. Other schemes keep every typed character anyway and ignore it.
void set_wubi_literal_input(bool literal);
int store_user_phrase(std::string pinyin, std::string word);
int store_user_phrase_from_canonical_pinyin(std::string pinyin, std::string word);
// 混输组合里五笔与拼音候选共存,删除必须由调用方指明候选自己的方案,不能按会话方案。
int remove_candidate(std::string pinyin, std::string word, SchemeType scheme);
int cache_dynamic_candidate(const std::string &pinyin, const std::string &word, CandidateSource source);
SelectionTransition advance_composition_after_selection(const std::string &selected_pinyin,
const std::string &selected_word,
const std::string &selected_canonical_pinyin,
SchemeType selected_scheme = SchemeType::Quanpin);
CloudQueryState get_cloud_query_state() const;
CreatingWordProgress update_creating_word_progress(const std::string &current_pinyin,
const std::string &current_word,
const std::string &selected_word,
const SelectionTransition &selection_transition) const;
void set_quanpin_autocorrect_types(unsigned autocorrect_types);
void set_fuzzy_pinyin_options(metasequoia::FuzzyPinyinOptions options)
{
engine_.set_fuzzy_pinyin_options(options);
}
void set_sentence_association(const SentenceAssociationOptions &options)
{
engine_.set_sentence_association(options);
}
void set_rescoring_context(std::string context)
{
engine_.set_rescoring_context(std::move(context));
}
void set_chinese_punctuation_enabled(bool enabled)
{
chinese_punctuation_enabled_ = enabled;
}
void set_candidate_learning_enabled(bool enabled)
{
candidate_learning_enabled_ = enabled;
}
void set_shuangpin_preedit_uses_raw(bool enabled)
{
shuangpin_preedit_uses_raw_ = enabled;
}
private:
const QueryRequest &request() const;
// 拼音类方案的预编辑切分;shuangpin_raw 决定双拼显示按键原串还是转换后的全拼。
std::string build_pinyin_segmentation_with_cases(bool shuangpin_raw) const;
bool is_shuangpin() const;
bool is_wubi() const;
// 候选是否由五笔码表产出。混输组合里五笔候选在前、拼音候选追加在后,调频、删除、固定
// 位置与上屏推进都按候选自己的方案走,不再按会话方案一刀切。
static bool is_wubi_native_candidate(const WordItem &item);
// 当前列表里五笔码表候选的条数;顶字与四码自动上屏只认它,追加的拼音候选不算。
std::size_t wubi_native_candidate_count() const;
bool is_japanese() const;
void clear_pending_sequence();
void apply_pending_sequence();
KeyResult commit(std::size_t index);
KeyResult handle_local_character(char character);
KeyResult insert_at_caret(char character);
// 光标停在 caret 处时大写字母 character 能否作为句中辅助码收下:紧跟在反引号之后(第一码),
// 或紧跟在「反引号 + 第一码」之后(第二码)。
bool accepts_mid_sentence_code_at(std::size_t caret, char character) const;
KeyResult edit_at_caret(Command command);
KeyResult replace_editing_text(std::string text, std::size_t caret);
std::optional<std::size_t> caret_;
// Last complete unit boundary at or before the caret; only consumes segment_raw_boundaries()
// (segmentation contract #187). A caret inside the first unit rounds up to that unit's end,
// so only caret == 0 yields 0. Caret unset or no unit model yields the full raw length.
std::size_t quantized_prefix_end() const;
// Decodes the quantized caret prefix into prefix_candidates_ when it is strictly shorter
// than the raw string, caching by prefix so unchanged keystrokes skip the extra query.
void refresh_prefix_candidates();
std::vector<WordItem> prefix_candidates_;
// Lowercased prefix the cache was built from; also feeds mixed-candidate association so
// English/emoji suggestions follow the string being converted.
std::string prefix_query_input_;
// True while candidates()/mixed assembly must read prefix_candidates_ instead of engine_.
bool prefix_candidates_active_ = false;
std::optional<std::string> update_local_candidates();
void update_mixed_candidates();
void apply_candidate_positions(std::vector<WordItem> &items);
std::string position_context(bool english, bool wubi) const;
bool fixed_positions_enabled_ = false;
void update_dedicated_english_candidates();
void reset_composition();
void discard_abandoned_phrase_progress();
std::optional<std::string> learn_candidate(std::size_t index);
// 当前组合可以混入快捷短语时返回它的编码:全拼/双拼、非本地模式、未造词、无光标前缀和
// 辅助码,且原始输入全是小写字母。
std::optional<std::string> quick_phrase_code() const;
// candidates() 返回混排列表(固定位置、英文/表情/颜文字混排任一开启)的条件。
bool mixed_candidates_active() const;
// 选中快捷短语时把组前移,越过组选普通候选时把组后移。
std::optional<std::string> learn_quick_phrase_order(std::size_t index);
// 词格 / Google 解码器猜出来的整句在词库里没有对应行,选中后落成一条用户词组。
std::optional<std::string> learn_sentence_candidate(const WordItem &selected);
std::optional<std::string> adjust_candidate_frequency(std::size_t index, FrequencyAdjustmentOptions options,
bool force_top);
// 拼音候选的调频,排位参照 ranked 而不是当前列表;句中辅助码组合用不带约束的候选作参照。
std::optional<std::string> adjust_pinyin_candidate_frequency(const WordItem &selected,
const std::vector<WordItem> &ranked,
FrequencyAdjustmentOptions options, bool force_top);
CreatingWordProgress immediate_phrase_progress_;
bool shuangpin_preedit_uses_raw_ = true;
std::unique_ptr<QuanpinEngine> canonical_phrase_engine_;
std::string pending_pinyin_sequence_;
std::string pending_pinyin_sequence_with_cases_;
bool has_pending_pinyin_sequence_ = false;
bool has_pending_pinyin_sequence_with_cases_ = false;
bool pending_wubi_literal_input_ = false;
RuntimePaths paths_;
CandidateQueries candidate_queries_;
ImeSession engine_;
// 位掩码(quanpin::kAutocorrect* 位),不是 bool:bool 会把邻键位截断丢失。
unsigned quanpin_autocorrect_types_ = 0;
bool quanpin_helpcode_enabled_ = true;
bool shuangpin_helpcode_enabled_ = true;
bool shuangpin_helpcode_direct_ = true;
bool end_helpcode_uppercase_trigger_enabled_ = false;
bool mid_sentence_helpcode_enabled_ = false;
bool direct_helpcode_enabled_ = false;
bool direct_helpcode_slash_marker_ = true;
bool direct_helpcode_uppercase_marker_ = false;
bool mid_sentence_uppercase_trigger_enabled_ = false;
bool chinese_punctuation_enabled_ = true;
bool candidate_learning_enabled_ = true;
PunctuationPolicy punctuation_;
const ShuangpinProfile shuangpin_profile_;
FrequencyAdjustmentOptions frequency_adjustment_;
bool frequency_adjustment_configured_ = false;
LocalModeOptions local_mode_options_;
EnglishInputOptions english_input_options_;
MixedExpressiveOptions mixed_expressive_options_;
bool dedicated_english_mode_ = false;
std::string dedicated_english_preedit_;
std::vector<WordItem> dedicated_english_candidates_;
std::vector<WordItem> mixed_candidates_;
LocalInputMode local_input_mode_ = LocalInputMode::None;
std::optional<SchemeType> temporary_original_scheme_;
std::string local_preedit_;
std::vector<WordItem> local_candidates_;
std::function<local_modes::LocalDateTime()> local_date_time_provider_;
OnlineRequestGuard online_requests_;
};
} // namespace metasequoia