From 2b8bf937d96e5ff80e19f97de5be594802d90b5d Mon Sep 17 00:00:00 2001 From: Evan Date: Sun, 6 Sep 2026 11:44:30 +0900 Subject: [PATCH] fix(server): adopt isolated engine sessions and helpcode annotations (#178) --- AGENTS.md | 4 ++ product-lock.json | 2 +- server/src/ipc/event_listener.cpp | 2 +- server/src/session/engine_input_session.cpp | 28 ++++++++-- server/src/session/engine_input_session.h | 5 ++ server/src/session/input_session.h | 1 + .../src/test_engine_shuangpin_session.cpp | 53 +++++++++++++++++++ vendor/MetasequoiaImeEngine | 2 +- 8 files changed, 91 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 11f15f56..93ece6c9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,6 +28,10 @@ Windows 端的全部一方源码在本仓。**合仓改变的是仓库数量, - **候选与输入状态的权威在 Server 和引擎**,页面只负责展示和发出用户动作,不要在网页侧复制状态机。 - `ui-html/webview2/shared/` 是 Engine web 契约的副本,由 `ui-html/scripts/sync-contracts.py` 生成,CI 用 `--check` 验证它和 submodule 一致。手改这个目录会被 CI 拦下来,改契约要改 Engine。 +Server 当前仍使用 Engine 的兼容 `InputSession`,尚未迁移公共 `Session` facade。 +辅助码筛选与候选提示都按会话配置,禁止用全局默认码表驱动活动会话。 +`RuntimePaths::legacy()` 在适配器创建时捕获现有安装布局;完整资源包和用户数据代际切换仍待接入。 + ## 构建 每个组件是独立的 CMake 工程,各自带 `vcpkg.json` 和 preset,从仓库根目录指定 `-S`: diff --git a/product-lock.json b/product-lock.json index f31a61e2..54e4808f 100644 --- a/product-lock.json +++ b/product-lock.json @@ -3,7 +3,7 @@ "repositories": { "engine": { "repository": "metasequoiaime/MSIME-Engine", - "commit": "cc21e42916cf93ce43051fec474e87eeeba305bf" + "commit": "c63ba774ac03c9ff8ba82776d50e97d27fd459a3" } }, "dictionary": { diff --git a/server/src/ipc/event_listener.cpp b/server/src/ipc/event_listener.cpp index 6bf8ad16..cc80d03b 100644 --- a/server/src/ipc/event_listener.cpp +++ b/server/src/ipc/event_listener.cpp @@ -925,7 +925,7 @@ std::string BuildCurrentCandidatePage() if (show_helpcodes && item.source != CandidateSource::EnglishDictionary && item.source != CandidateSource::QuickPhrase && item.source != CandidateSource::Emoji && item.source != CandidateSource::Kaomoji && item.source != CandidateSource::Generated) - view.annotation = HelpcodeUtils::compute_helpcodes(item.word, uppercase_all_helpcodes); + view.annotation = g_inputSession->get_helpcode_annotation(item.word, uppercase_all_helpcodes); if (item.source == CandidateSource::CloudSuggestion) view.badge = " ☁️"; else if (item.source == CandidateSource::AiSuggestion) diff --git a/server/src/session/engine_input_session.cpp b/server/src/session/engine_input_session.cpp index 6df07826..4d95765e 100644 --- a/server/src/session/engine_input_session.cpp +++ b/server/src/session/engine_input_session.cpp @@ -2,7 +2,8 @@ #include "config/ime_config.h" #include "MetasequoiaImeEngine/common/helpcode_utils.h" -EngineInputSession::EngineInputSession(SchemeType scheme, const ShuangpinProfile &profile) : session_(scheme, profile) +EngineInputSession::EngineInputSession(SchemeType scheme, const ShuangpinProfile &profile) + : paths_(metasequoia::RuntimePaths::legacy()), session_(scheme, profile, paths_) { ApplyConfiguration(); } @@ -11,8 +12,21 @@ void EngineInputSession::ApplyConfiguration() { const auto scheme = session_.scheme(); if (scheme == SchemeType::Quanpin || scheme == SchemeType::Shuangpin) - HelpcodeUtils::select_helpcode_schema(scheme == SchemeType::Quanpin ? GetConfiguredQuanpinHelpcodeSchema() - : GetConfiguredShuangpinHelpcodeSchema()); + { + const auto &schema = scheme == SchemeType::Quanpin ? GetConfiguredQuanpinHelpcodeSchema() + : GetConfiguredShuangpinHelpcodeSchema(); + if (schema != helpcode_schema_) + { + // Keep filtering and annotations on this session's captured resource layout. + // Applying unchanged settings on each key must not reload the tables. + auto keymap = HelpcodeUtils::load_helpcode_keymap(paths_.resources, schema); + if (session_.set_helpcode_schema(schema)) + { + helpcode_schema_ = schema; + helpcode_keymap_ = std::move(keymap); + } + } + } session_.set_shuangpin_helpcode_enabled(GetConfiguredShuangpinHelpcodeEnabled()); session_.set_quanpin_helpcode_enabled(GetConfiguredQuanpinHelpcodeEnabled()); session_.set_quanpin_autocorrect_enabled(GetConfiguredQuanpinAutocorrectEnabled()); @@ -160,3 +174,11 @@ IInputSession::CreatingWordProgress EngineInputSession::update_creating_word_pro { return session_.update_creating_word_progress(current_pinyin, current_word, selected_word, selection_transition); } + +std::string EngineInputSession::get_helpcode_annotation(const std::string &word, bool uppercase_all) const +{ + const auto scheme = session_.scheme(); + if (!helpcode_keymap_ || (scheme != SchemeType::Quanpin && scheme != SchemeType::Shuangpin)) + return {}; + return HelpcodeUtils::compute_helpcodes(word, uppercase_all, helpcode_keymap_.get()); +} diff --git a/server/src/session/engine_input_session.h b/server/src/session/engine_input_session.h index 0b7ce28b..1f936e94 100644 --- a/server/src/session/engine_input_session.h +++ b/server/src/session/engine_input_session.h @@ -1,6 +1,7 @@ #pragma once #include "input_session.h" +#include "MetasequoiaImeEngine/common/helpcode_utils.h" #include "MetasequoiaImeEngine/core/input_session.h" class EngineInputSession : public IInputSession @@ -19,6 +20,7 @@ class EngineInputSession : public IInputSession const std::vector &get_candidates() const override; bool expand_initial_candidates() override; + std::string get_helpcode_annotation(const std::string &word, bool uppercase_all) const override; std::optional find_candidate(const std::string &key, const std::string &value) override; const std::string &get_pinyin_sequence() const override; @@ -49,5 +51,8 @@ class EngineInputSession : public IInputSession private: void ApplyConfiguration(); + const metasequoia::RuntimePaths paths_; metasequoia::InputSession session_; + std::string helpcode_schema_; + HelpcodeUtils::SharedKeymap helpcode_keymap_; }; diff --git a/server/src/session/input_session.h b/server/src/session/input_session.h index 7c91b466..d1ec0675 100644 --- a/server/src/session/input_session.h +++ b/server/src/session/input_session.h @@ -28,6 +28,7 @@ class IInputSession virtual const std::vector &get_candidates() const = 0; virtual bool expand_initial_candidates() = 0; + virtual std::string get_helpcode_annotation(const std::string &word, bool uppercase_all) const = 0; virtual std::optional find_candidate(const std::string &, const std::string &) { return std::nullopt; diff --git a/server/tests/src/test_engine_shuangpin_session.cpp b/server/tests/src/test_engine_shuangpin_session.cpp index fff0c4c5..72a8fb4a 100644 --- a/server/tests/src/test_engine_shuangpin_session.cpp +++ b/server/tests/src/test_engine_shuangpin_session.cpp @@ -1,5 +1,6 @@ #include "tests/includes/test_framework.h" #include "src/session/engine_input_session.h" +#include "src/config/ime_config.h" #include "MetasequoiaImeEngine/common/helpcode_utils.h" #include "MetasequoiaImeEngine/quanpin/quanpin_query.h" #include "MetasequoiaImeEngine/shuangpin/shuangpin_dictionary.h" @@ -784,3 +785,55 @@ TEST_CASE(EngineShuangpinSegmentedInitialCandidatesExpandInPlace) REQUIRE(count_initials(expanded_candidates) > 24); REQUIRE_EQ(count_other_candidates(expanded_candidates), other_count); } + +TEST_CASE(EngineSessionsKeepHelpcodeFilteringAndAnnotationsTogether) +{ + // The integration runner supplies an isolated config and the locked release dictionaries. + InitImeConfig(); + struct RestoreConfiguration + { + std::string quanpin = GetConfiguredQuanpinHelpcodeSchema(); + std::string shuangpin = GetConfiguredShuangpinHelpcodeSchema(); + bool quanpin_enabled = GetConfiguredQuanpinHelpcodeEnabled(); + bool shuangpin_enabled = GetConfiguredShuangpinHelpcodeEnabled(); + ~RestoreConfiguration() + { + SetConfiguredQuanpinHelpcodeSchema(quanpin); + SetConfiguredShuangpinHelpcodeSchema(shuangpin); + SetConfiguredQuanpinHelpcodeEnabled(quanpin_enabled); + SetConfiguredShuangpinHelpcodeEnabled(shuangpin_enabled); + } + } restore; + REQUIRE(SetConfiguredQuanpinHelpcodeEnabled(true)); + REQUIRE(SetConfiguredShuangpinHelpcodeEnabled(true)); + REQUIRE(SetConfiguredQuanpinHelpcodeSchema("lantian")); + REQUIRE(SetConfiguredShuangpinHelpcodeSchema("ziranma")); + + EngineInputSession quanpin(SchemeType::Quanpin); + EngineInputSession shuangpin(SchemeType::Shuangpin); + REQUIRE_EQ(quanpin.get_helpcode_annotation("你", true), std::string("(RX)")); + REQUIRE_EQ(shuangpin.get_helpcode_annotation("你", false), std::string("(rE)")); + + InputLetters(quanpin, "niRX"); + REQUIRE(!quanpin.get_candidates().empty()); + REQUIRE(std::any_of(quanpin.get_candidates().begin(), quanpin.get_candidates().end(), + [](const auto &item) { return item.word == "你"; })); + InputLetters(shuangpin, "ni"); + shuangpin.recompute_candidates(); + quanpin.recompute_candidates(); + REQUIRE_EQ(quanpin.get_helpcode_annotation("你", true), std::string("(RX)")); + REQUIRE_EQ(shuangpin.get_helpcode_annotation("你", false), std::string("(rE)")); + REQUIRE(std::any_of(quanpin.get_candidates().begin(), quanpin.get_candidates().end(), + [](const auto &item) { return item.word == "你"; })); + + // A settings change is adopted by the next refresh, including candidate annotations. + REQUIRE(SetConfiguredQuanpinHelpcodeSchema("ziranma")); + quanpin.reset_state(); + InputLetters(quanpin, "niRE"); + REQUIRE_EQ(quanpin.get_helpcode_annotation("你", true), std::string("(RE)")); + REQUIRE(std::any_of(quanpin.get_candidates().begin(), quanpin.get_candidates().end(), + [](const auto &item) { return item.word == "你"; })); + REQUIRE_EQ(shuangpin.get_helpcode_annotation("你", false), std::string("(rE)")); + quanpin.switch_scheme(SchemeType::Shuangpin); + REQUIRE_EQ(quanpin.get_helpcode_annotation("你", false), std::string("(rE)")); +} diff --git a/vendor/MetasequoiaImeEngine b/vendor/MetasequoiaImeEngine index cc21e429..c63ba774 160000 --- a/vendor/MetasequoiaImeEngine +++ b/vendor/MetasequoiaImeEngine @@ -1 +1 @@ -Subproject commit cc21e42916cf93ce43051fec474e87eeeba305bf +Subproject commit c63ba774ac03c9ff8ba82776d50e97d27fd459a3