diff --git a/engine/contracts/direct_helpcode.h b/engine/contracts/direct_helpcode.h index 390fdf99..cf29d992 100644 --- a/engine/contracts/direct_helpcode.h +++ b/engine/contracts/direct_helpcode.h @@ -13,10 +13,34 @@ // TSF 在同步吃键阶段先判断,Server 随后按同一条规则决定收不收、引擎按它解析。三处必须一致,否则 // 两边的输入串会分叉。TSF 不链接引擎、没有音节表,所以这里只看形状,/ 到底凑不凑得成四码由引擎 // 判断,凑不成时引擎把它当分隔符处理,不会让整串作废。 +// +// 四码也可以用第二位辅码大写来结束(uiaB,设置里和 / 各一个勾)。大写字母本来就是编码键,TSF 不用 +// 另外判断;但 / 没勾时 TSF 不能再吃它,所以 DirectHelpcodeChanged 的载荷要带上 / 勾没勾。 namespace FanyImeDirectHelpcode { inline constexpr char kSlash = '/'; +// DirectHelpcodeChanged 的载荷(一个字符):"0" 关,"1" 开且 / 是四码的终止键,"2" 开但 / 不是编码键。 +inline constexpr wchar_t PayloadFor(bool enabled, bool slash) +{ + return !enabled ? L'0' : slash ? L'1' : L'2'; +} + +inline constexpr bool IsValidPayload(wchar_t payload) +{ + return payload == L'0' || payload == L'1' || payload == L'2'; +} + +inline constexpr bool EnabledFromPayload(wchar_t payload) +{ + return payload == L'1' || payload == L'2'; +} + +inline constexpr bool SlashFromPayload(wchar_t payload) +{ + return payload == L'1'; +} + template constexpr bool IsAsciiLetter(Char ch) { return (ch >= Char('a') && ch <= Char('z')) || (ch >= Char('A') && ch <= Char('Z')); diff --git a/engine/contracts/tests/windows_ipc_contract.cpp b/engine/contracts/tests/windows_ipc_contract.cpp index 181115b0..2db7c50f 100644 --- a/engine/contracts/tests/windows_ipc_contract.cpp +++ b/engine/contracts/tests/windows_ipc_contract.cpp @@ -182,6 +182,13 @@ int main() CHECK(FanyImeDirectHelpcode::AcceptsSemicolonFinalAt("uiax", 4, 4) && !FanyImeDirectHelpcode::AcceptsSemicolonFinalAt("uix;", 4, 4) && !FanyImeDirectHelpcode::AcceptsSemicolonFinalAt("", 0, 0)); + // DirectHelpcodeChanged 的载荷:开关和 / 勾没勾。旧 DLL 认的 "0"/"1" 意思不变。 + CHECK(FanyImeDirectHelpcode::PayloadFor(false, true) == L'0' && + FanyImeDirectHelpcode::PayloadFor(true, true) == L'1' && + FanyImeDirectHelpcode::PayloadFor(true, false) == L'2'); + CHECK(FanyImeDirectHelpcode::EnabledFromPayload(L'2') && !FanyImeDirectHelpcode::SlashFromPayload(L'2') && + FanyImeDirectHelpcode::SlashFromPayload(L'1') && !FanyImeDirectHelpcode::EnabledFromPayload(L'0') && + !FanyImeDirectHelpcode::IsValidPayload(L'3')); // TSF(WCHAR)与引擎(char)共用同一条句中辅助码形状规则。 const auto accepts = [](const std::wstring &text) { return FanyImeMidSentenceHelpcode::AcceptsMarker(text.data(), text.size()); diff --git a/engine/contracts/windows_ipc.h b/engine/contracts/windows_ipc.h index 54b87c01..056cbe3d 100644 --- a/engine/contracts/windows_ipc.h +++ b/engine/contracts/windows_ipc.h @@ -416,9 +416,11 @@ constexpr std::uint32_t MidSentenceHelpcodeChanged = 28; // Same as MidSentenceHelpcodeChanged for the optional ';' trigger. Payload "0"/"1". A separate // opcode keeps 28 meaning "'`' is a trigger" for older DLLs, which ignore this one. constexpr std::uint32_t MidSentenceHelpcodeSemicolonChanged = 29; -// Whether shuangpin direct helpcode (Wanxiang style, no guide key) is on. Payload "0"/"1". TSF then eats '/' after -// a four-key code (FanyImeDirectHelpcode::AcceptsSlashAt) and stops requiring an odd chunk before the ';' final -// (FanyImeDirectHelpcode::AcceptsSemicolonFinalAt). Same single "0"/"1" payload rule as opcode 28. +// Whether shuangpin direct helpcode (Wanxiang style, no guide key) is on. Payload "0" off, "1" on with '/' ending a +// four-key code, "2" on with '/' left as punctuation (FanyImeDirectHelpcode::PayloadFor). When on, TSF stops requiring +// an odd chunk before the ';' final (FanyImeDirectHelpcode::AcceptsSemicolonFinalAt); with "1" it also eats '/' after a +// four-key code (FanyImeDirectHelpcode::AcceptsSlashAt). Single-character payload, same rule as opcode 28; a DLL that +// only knows "0"/"1" drops "2" and keeps its previous state. constexpr std::uint32_t DirectHelpcodeChanged = 30; // Whether the uppercase trigger of shuangpin mid-sentence helpcode is in effect (mid-sentence helpcode on, uppercase // trigger checked, shuangpin, direct helpcode off). Payload "0"/"1". TSF still eats letters as before; the flag only diff --git a/engine/core/ime_session.cpp b/engine/core/ime_session.cpp index 599a9096..ce0e63a6 100644 --- a/engine/core/ime_session.cpp +++ b/engine/core/ime_session.cpp @@ -67,6 +67,12 @@ void ImeSession::set_direct_helpcode_enabled(bool enabled) bind_shuangpin_scheme(); } +void ImeSession::set_direct_helpcode_markers(bool slash, bool uppercase) +{ + direct_helpcode_slash_marker_ = slash; + direct_helpcode_uppercase_marker_ = uppercase; +} + void ImeSession::set_mid_sentence_helpcode_enabled(bool enabled) { enable_mid_sentence_helpcode_ = enabled; @@ -353,6 +359,8 @@ void ImeSession::apply_request_options(QueryRequest &request) const request.enable_shuangpin_helpcode = false; request.enable_mid_sentence_helpcode = false; request.enable_direct_helpcode = true; + request.direct_helpcode_slash_marker = direct_helpcode_slash_marker_; + request.direct_helpcode_uppercase_marker = direct_helpcode_uppercase_marker_; } request.enable_quanpin_helpcode = enable_quanpin_helpcode_; request.enable_quanpin_autocorrect_transposition = diff --git a/engine/core/ime_session.h b/engine/core/ime_session.h index 00537895..5cd8f97e 100644 --- a/engine/core/ime_session.h +++ b/engine/core/ime_session.h @@ -35,6 +35,8 @@ class ImeSession { return enable_direct_helpcode_; } + // 直接辅助码在句中用什么结束四码:补 /、第二位辅码大写,可以都开。 + void set_direct_helpcode_markers(bool slash, bool uppercase); void set_quanpin_helpcode_enabled(bool enabled); void set_quanpin_autocorrect_types(unsigned autocorrect_types); void set_fuzzy_pinyin_options(metasequoia::FuzzyPinyinOptions options) @@ -124,6 +126,8 @@ class ImeSession bool enable_shuangpin_helpcode_ = false; bool enable_mid_sentence_helpcode_ = false; bool enable_direct_helpcode_ = false; + bool direct_helpcode_slash_marker_ = true; + bool direct_helpcode_uppercase_marker_ = false; bool enable_mid_sentence_uppercase_trigger_ = false; bool enable_quanpin_helpcode_ = false; unsigned quanpin_autocorrect_types_ = 0; diff --git a/engine/core/input_session.cpp b/engine/core/input_session.cpp index ef326524..49eda11c 100644 --- a/engine/core/input_session.cpp +++ b/engine/core/input_session.cpp @@ -409,9 +409,22 @@ void InputSession::set_direct_helpcode_enabled(bool enabled) online_requests_.invalidate(); } +void InputSession::set_direct_helpcode_markers(bool slash, bool uppercase) +{ + if (direct_helpcode_slash_marker_ == slash && direct_helpcode_uppercase_marker_ == uppercase) + { + return; + } + direct_helpcode_slash_marker_ = slash; + direct_helpcode_uppercase_marker_ = uppercase; + engine_.set_direct_helpcode_markers(slash, uppercase); + update_mixed_candidates(); + online_requests_.invalidate(); +} + bool InputSession::accepts_direct_helpcode_slash_at(std::size_t caret) const { - if (!direct_helpcode_enabled_ || !is_shuangpin() || dedicated_english_mode_ || + if (!direct_helpcode_enabled_ || !direct_helpcode_slash_marker_ || !is_shuangpin() || dedicated_english_mode_ || local_input_mode_ != LocalInputMode::None) { return false; diff --git a/engine/core/input_session.h b/engine/core/input_session.h index 57188d76..f026ae38 100644 --- a/engine/core/input_session.h +++ b/engine/core/input_session.h @@ -72,6 +72,8 @@ class InputSession { 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; @@ -355,6 +357,8 @@ class InputSession bool shuangpin_helpcode_enabled_ = true; 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; diff --git a/engine/core/query_request.h b/engine/core/query_request.h index 7b9cf2f5..ba0e8b37 100644 --- a/engine/core/query_request.h +++ b/engine/core/query_request.h @@ -44,6 +44,9 @@ struct QueryRequest std::vector> syllable_helpcode_decorations; // 会话开着直接辅助码(不论这次请求有没有被改写)。词典层据此让词格与解析器共用一份跨度查询缓存。 bool enable_direct_helpcode = false; + // 直接辅助码在句中用什么结束四码:补 /(uiab/),或第二位辅码大写(uiaB),见 direct_helpcode/spelling_graph.h。 + bool direct_helpcode_slash_marker = true; + bool direct_helpcode_uppercase_marker = false; // Autocorrection is type-gated (bit0 transposition, bit1 neighbor in the session-level // mask); both default off, so a fresh install never rewrites the user's spelling. bool enable_quanpin_autocorrect_transposition = false; diff --git a/engine/direct_helpcode/direct_resolver.cpp b/engine/direct_helpcode/direct_resolver.cpp index ff4e5cf8..24429be7 100644 --- a/engine/direct_helpcode/direct_resolver.cpp +++ b/engine/direct_helpcode/direct_resolver.cpp @@ -203,14 +203,25 @@ bool Resolver::resolve(QueryRequest &request, const ResolveContext &context) aux_index_built_ = false; indexed_keymap_ = context.keymap; } + SpellingOptions spelling_options; + spelling_options.slash_marker = request.direct_helpcode_slash_marker; + spelling_options.uppercase_marker = request.direct_helpcode_uppercase_marker; + if (spelling_options != spelling_options_) + { + // 同一个原串换了四码标记就是另一张拼写图,按原串记的切分作废。 + resolution_cache_.clear(); + spelling_options_ = spelling_options; + } auto cached = resolution_cache_.find(typed); if (cached == resolution_cache_.end()) { - const auto graph = - build_spelling_graph(typed, profile_, [&](const std::string &quanpin, char first, char second) { + const auto graph = build_spelling_graph( + typed, profile_, + [&](const std::string &quanpin, char first, char second) { return has_aux(quanpin, first, second, context); - }); + }, + spelling_options_); Resolution resolution; // 只有一条完整切分时没有可比的切法,跳过整句解码;整句留给词典层自己的词格去解。 if (!graph.empty() && !single_path(graph, resolution.spellings)) diff --git a/engine/direct_helpcode/direct_resolver.h b/engine/direct_helpcode/direct_resolver.h index 15b9c4c0..eb3808dd 100644 --- a/engine/direct_helpcode/direct_resolver.h +++ b/engine/direct_helpcode/direct_resolver.h @@ -77,6 +77,8 @@ class Resolver // 搭配分(尾窗 + 词 + 是否句尾 → 分数),整句打分选项变了随 reset_cache 一起清。 std::unordered_map collocation_memo_; std::string collocation_key_; + // resolution_cache_ 里的切分是按这组四码标记解出来的。 + SpellingOptions spelling_options_; std::unordered_map resolution_cache_; const DecodedPath *last_sentence_ = nullptr; }; diff --git a/engine/direct_helpcode/spelling_graph.cpp b/engine/direct_helpcode/spelling_graph.cpp index 4da51124..bfbb426c 100644 --- a/engine/direct_helpcode/spelling_graph.cpp +++ b/engine/direct_helpcode/spelling_graph.cpp @@ -13,6 +13,11 @@ bool is_letter(char ch) return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); } +bool is_upper(char ch) +{ + return ch >= 'A' && ch <= 'Z'; +} + char to_lower(char ch) { return ch >= 'A' && ch <= 'Z' ? static_cast(ch + ('a' - 'A')) : ch; @@ -60,29 +65,32 @@ std::vector forward_reach(const SpellingGraph &graph, bool normal_only) } SpellingGraph build_attempt(const std::string &typed, const ShuangpinProfile &profile, const AuxPredicate &has_aux, - bool symbols_as_delimiters) + const SpellingOptions &options, bool lenient) { SpellingGraph graph; const std::size_t n = typed.size(); graph.size = n; graph.edges.resize(n); graph.skip.resize(n, false); + // 大写标记开着、又不是兜底那一遍时,大写字母只能当四码的第二位辅码。 + const bool strict_case = options.uppercase_marker && !lenient; + const auto usable = [&](std::size_t index) { return !strict_case || !is_upper(typed[index]); }; for (std::size_t pos = 0; pos < n; ++pos) { const char ch = typed[pos]; - graph.skip[pos] = ch == '\'' || (symbols_as_delimiters && (ch == '/' || ch == ';')); - if (!is_letter(ch)) + graph.skip[pos] = ch == '\'' || (lenient && (ch == '/' || ch == ';')); + if (!is_letter(ch) || !usable(pos)) continue; auto &out = graph.edges[pos]; - if (pos + 1 < n && (is_letter(typed[pos + 1]) || typed[pos + 1] == ';')) + if (pos + 1 < n && ((is_letter(typed[pos + 1]) && usable(pos + 1)) || typed[pos + 1] == ';')) { std::string code{to_lower(ch), to_lower(typed[pos + 1])}; std::string quanpin = syllable_quanpin(code, profile); if (!quanpin.empty()) { out.push_back(make_spelling(pos, pos + 2, code, quanpin, SpellingKind::Plain)); - if (pos + 2 < n && is_letter(typed[pos + 2])) + if (pos + 2 < n && is_letter(typed[pos + 2]) && usable(pos + 2)) { const char first = to_lower(typed[pos + 2]); if (has_aux && has_aux(quanpin, first, 0)) @@ -92,11 +100,15 @@ SpellingGraph build_attempt(const std::string &typed, const ShuangpinProfile &pr const char second = to_lower(typed[pos + 3]); if (has_aux && has_aux(quanpin, first, second)) { - if (pos + 4 < n && typed[pos + 4] == '/') + if (options.slash_marker && pos + 4 < n && typed[pos + 4] == '/') out.push_back( make_spelling(pos, pos + 5, code, quanpin, SpellingKind::Aux2Slash, first, second)); - out.push_back( - make_spelling(pos, pos + 4, code, quanpin, SpellingKind::Aux2Abbrev, first, second)); + if (options.uppercase_marker && is_upper(typed[pos + 3])) + out.push_back( + make_spelling(pos, pos + 4, code, quanpin, SpellingKind::Aux2Upper, first, second)); + else + out.push_back(make_spelling(pos, pos + 4, code, quanpin, SpellingKind::Aux2Abbrev, + first, second)); } } } @@ -149,15 +161,15 @@ SpellingGraph build_attempt(const std::string &typed, const ShuangpinProfile &pr } // namespace SpellingGraph build_spelling_graph(const std::string &typed, const ShuangpinProfile &profile, - const AuxPredicate &has_aux) + const AuxPredicate &has_aux, const SpellingOptions &options) { if (typed.empty()) return {}; - SpellingGraph graph = build_attempt(typed, profile, has_aux, false); + SpellingGraph graph = build_attempt(typed, profile, has_aux, options, false); if (graph.empty()) { - // 用不上的 / 和 ; 退回成分隔符,候选照常出,不至于整串作废。 - graph = build_attempt(typed, profile, has_aux, true); + // 用不上的 / 和 ; 退回成分隔符、大写字母不再只当第二位辅码,候选照常出,不至于整串作废。 + graph = build_attempt(typed, profile, has_aux, options, true); } return graph; } diff --git a/engine/direct_helpcode/spelling_graph.h b/engine/direct_helpcode/spelling_graph.h index 5c064d3e..3c60aeb3 100644 --- a/engine/direct_helpcode/spelling_graph.h +++ b/engine/direct_helpcode/spelling_graph.h @@ -15,8 +15,9 @@ // // ui 双拼两键 Plain // uia 双拼 + 第一位辅码 Aux1 -// uiab/ 双拼 + 两位辅码 + / Aux2Slash -// uiab 双拼 + 两位辅码(不带 /) Aux2Abbrev 缩写,见下 +// uiab/ 双拼 + 两位辅码 + / Aux2Slash SpellingOptions::slash_marker +// uiaB 双拼 + 两位辅码,第二位大写 Aux2Upper SpellingOptions::uppercase_marker +// uiab 双拼 + 两位辅码(不带标记) Aux2Abbrev 缩写,见下 // u 单个声母键 Initial 缩写,见下 // // 辅码那一段只有在「这个音节下真有字的辅码以它开头」时才成立(AuxPredicate),这对应万象词库里 @@ -25,7 +26,10 @@ // 缩写(Aux2Abbrev、Initial)对应 librime 的 kAbbreviation:只要存在一条全由非缩写拼写组成、 // 走到输入末尾的路径,所有缩写边就被剪掉(syllabifier.cc 的 stale edge 清理)。所以 uiab 单独 // 敲时能当一个带两位辅码的字,放进句子里就会让位给 ui'ab 这类正常切分,要在句中用四码必须 -// 补 /。这正是万象的行为,不是这里的简化。 +// 补 /(或开着大写标记时把第二位辅码敲成大写)。这正是万象的行为,不是这里的简化。 +// +// 开着大写标记时大写字母只能当四码的第二位辅码:别处出现大写字母的切法先不考虑,四码因此和补 / +// 一样确定。一条都走不通时(比如习惯把第一位辅码也敲成大写)再退回不分大小写的旧规则。 namespace direct_helpcode { @@ -34,6 +38,7 @@ enum class SpellingKind Plain, Aux1, Aux2Slash, + Aux2Upper, Aux2Abbrev, Initial, }; @@ -80,9 +85,27 @@ struct SpellingGraph } }; +// 句中四码用什么结束,可以两个都开。 +struct SpellingOptions +{ + // uiab/:两位辅码后补 /。 + bool slash_marker = true; + // uiaB:第二位辅码敲成大写。 + bool uppercase_marker = false; + + bool operator==(const SpellingOptions &other) const + { + return slash_marker == other.slash_marker && uppercase_marker == other.uppercase_marker; + } + bool operator!=(const SpellingOptions &other) const + { + return !(*this == other); + } +}; + // 走不通时返回空图。 SpellingGraph build_spelling_graph(const std::string &typed, const ShuangpinProfile &profile, - const AuxPredicate &has_aux); + const AuxPredicate &has_aux, const SpellingOptions &options = {}); // 下一个出字的位置:跳过 pos 起连续的分隔符。 std::size_t next_spelling_position(const SpellingGraph &graph, std::size_t pos); diff --git a/engine/tests/src/test_direct_helpcode.cpp b/engine/tests/src/test_direct_helpcode.cpp index 599824cd..19ce296d 100644 --- a/engine/tests/src/test_direct_helpcode.cpp +++ b/engine/tests/src/test_direct_helpcode.cpp @@ -180,6 +180,42 @@ void run_spelling_graph_tests() require(!find_edge(graph, 0, SpellingKind::Plain), "ui|ui survived although the slash cannot be consumed."); } + { + // 第二位辅码大写也结束四码:和补 / 一样是正常拼写,uiaB 之后接着打。 + direct_helpcode::SpellingOptions options; + options.uppercase_marker = true; + const auto graph = direct_helpcode::build_spelling_graph("uiaBui", profile, has_aux, options); + const auto *edge = find_edge(graph, 0, SpellingKind::Aux2Upper); + require(edge && edge->end == 4 && edge->first == 'a' && edge->second == 'b', + "An uppercase second code did not end the four-key code."); + require(!find_edge(graph, 0, SpellingKind::Plain) && !find_edge(graph, 0, SpellingKind::Aux1), + "A segmentation consuming the uppercase letter elsewhere survived."); + // 大写标记关着时同一串里的大写字母不算标记。 + const auto off = direct_helpcode::build_spelling_graph("uiaB", profile, has_aux); + require(!find_edge(off, 0, SpellingKind::Aux2Upper) && find_edge(off, 0, SpellingKind::Aux2Abbrev), + "An uppercase second code was a marker with the uppercase marker off."); + } + + { + // 大写标记开着但大写字母放不进第二位辅码(第一位辅码敲成大写):退回不分大小写的旧规则。 + direct_helpcode::SpellingOptions options; + options.uppercase_marker = true; + const auto graph = direct_helpcode::build_spelling_graph("uiAuiQ", profile, has_aux, options); + require(find_edge(graph, 0, SpellingKind::Aux1) && find_edge(graph, 3, SpellingKind::Aux1), + "Uppercase first codes stopped working with the uppercase marker on."); + } + + { + // / 标记关着:uiui/ 不再是四码,/ 退回成分隔符。 + direct_helpcode::SpellingOptions options; + options.slash_marker = false; + options.uppercase_marker = true; + const auto graph = direct_helpcode::build_spelling_graph("uiui/", profile, has_aux, options); + require(!find_edge(graph, 0, SpellingKind::Aux2Slash) && graph.skip[4] && + find_edge(graph, 0, SpellingKind::Plain), + "The slash still ended a four-key code with the slash marker off."); + } + { // 凑不成四码的 / 退回成分隔符,整串不作废。 const auto graph = direct_helpcode::build_spelling_graph("ui/", profile, has_aux); @@ -321,6 +357,22 @@ void run_session_tests() "The four-key code and its slash were not one unit."); } + { + // 只留大写标记:/ 不再收,第二位辅码大写结束四码,后面的音节照常解码。 + auto session = direct_session(); + session->set_direct_helpcode_markers(false, true); + type(*session, "uiab"); + require(!session->handle_character('/').handled, "A slash was accepted with the slash marker off."); + require(session->handle_command(metasequoia::Command::Backspace).handled && session->preedit() == "uia", + "Backspace did not remove the second code."); + type(*session, "Bui"); + require(first_word(*session) == "石狮", "uiaBui did not keep 石 on the first syllable."); + require(session->get_pinyin_segmentation_with_cases() == "uiaB'ui", + "The uppercase four-key code was not shown after its syllable."); + require(session->segment_raw_boundaries() == std::vector({0, 2, 4, 6}), + "The uppercase four-key code was not one unit."); + } + { // 开关关着:/ 不收,三码不当辅码。 auto session = direct_session(false); diff --git a/installer/default_config/config.default.toml b/installer/default_config/config.default.toml index f75ae7c4..3f3612a9 100644 --- a/installer/default_config/config.default.toml +++ b/installer/default_config/config.default.toml @@ -318,6 +318,10 @@ shuangpin_mid_sentence_helpcode_uppercase = false # 辅助码互斥:设置页开启其中一个会自动关闭另一个;两个都写成 true 时按直接辅助码生效。开启后末尾辅助码 # 也让位给它 shuangpin_direct_helpcode = false +# 直接辅助码句中四码的结束方式,可多选、至少留一个:两位辅码后补 /(如 uiab/),或把第二位辅码敲成大写 +# (如 uiaB)。开启大写后大写字母只当四码的第二位辅码;都写成 false 时按补 / 生效 +shuangpin_direct_helpcode_slash = true +shuangpin_direct_helpcode_uppercase = false # 全拼辅助码 quanpin_helpcode = true # 全拼辅助码方案,可选:"lantian", "ziranma", "shouyou2_0", "shouyouplus", "xiaohe", "jiajia", diff --git a/server/assets/config/config.toml b/server/assets/config/config.toml index 0d56ce81..d9291cde 100644 --- a/server/assets/config/config.toml +++ b/server/assets/config/config.toml @@ -317,6 +317,10 @@ shuangpin_mid_sentence_helpcode_uppercase = false # 辅助码互斥:设置页开启其中一个会自动关闭另一个;两个都写成 true 时按直接辅助码生效。开启后末尾辅助码 # 也让位给它 shuangpin_direct_helpcode = false +# 直接辅助码句中四码的结束方式,可多选、至少留一个:两位辅码后补 /(如 uiab/),或把第二位辅码敲成大写 +# (如 uiaB)。开启大写后大写字母只当四码的第二位辅码;都写成 false 时按补 / 生效 +shuangpin_direct_helpcode_slash = true +shuangpin_direct_helpcode_uppercase = false # 全拼辅助码 quanpin_helpcode = true # 全拼辅助码方案,可选:"lantian", "ziranma", "shouyou2_0", "shouyouplus", "xiaohe", "jiajia", diff --git a/server/src/config/ime_config.cpp b/server/src/config/ime_config.cpp index 08ddbbce..044ca58b 100644 --- a/server/src/config/ime_config.cpp +++ b/server/src/config/ime_config.cpp @@ -87,6 +87,8 @@ bool g_shuangpin_mid_sentence_helpcode_backtick = true; bool g_shuangpin_mid_sentence_helpcode_semicolon = false; bool g_shuangpin_mid_sentence_helpcode_uppercase = false; bool g_shuangpin_direct_helpcode_enabled = false; +bool g_shuangpin_direct_helpcode_slash = true; +bool g_shuangpin_direct_helpcode_uppercase = false; bool g_quanpin_helpcode_enabled = true; std::string g_shuangpin_helpcode_schema = "lantian"; std::string g_quanpin_helpcode_schema = "lantian"; @@ -428,6 +430,13 @@ bool LoadImeConfig() g_shuangpin_mid_sentence_helpcode_uppercase = tbl["helpcode"]["shuangpin_mid_sentence_helpcode_uppercase"].value_or(false); g_shuangpin_direct_helpcode_enabled = tbl["helpcode"]["shuangpin_direct_helpcode"].value_or(false); + g_shuangpin_direct_helpcode_slash = tbl["helpcode"]["shuangpin_direct_helpcode_slash"].value_or(true); + g_shuangpin_direct_helpcode_uppercase = tbl["helpcode"]["shuangpin_direct_helpcode_uppercase"].value_or(false); + // 至少留一个(设置页取消不掉最后一个);手改成两个都关时按默认的 / 生效,不改写配置文件。 + if (!g_shuangpin_direct_helpcode_slash && !g_shuangpin_direct_helpcode_uppercase) + { + g_shuangpin_direct_helpcode_slash = true; + } // 两者互斥(设置页开一个会关另一个)。互斥之前存下的配置可能两个都开着,那时实际生效的本来就是 // 直接辅助码;这里只在内存里把句中辅助码视为关闭,让设置页显示与行为一致,不改写用户的配置文件。 if (g_shuangpin_direct_helpcode_enabled) diff --git a/server/src/config/ime_config.h b/server/src/config/ime_config.h index 778c1724..27b8b7e4 100644 --- a/server/src/config/ime_config.h +++ b/server/src/config/ime_config.h @@ -295,7 +295,14 @@ std::wstring FormatMidSentenceHelpcodeSemicolonWorkerPayload(); // 触发键一律不生效(IsConfiguredMidSentenceHelpcodeTrigger 返回 false),末尾单码/双码辅助也让位给它。 bool GetConfiguredShuangpinDirectHelpcodeEnabled(); bool SetConfiguredShuangpinDirectHelpcodeEnabled(bool enabled); -// DirectHelpcodeChanged 的载荷:"1" 表示开关开着且当前方案是双拼。 +// 直接辅助码在句中用什么结束四码,可多选、至少留一个:补 /(helpcode.shuangpin_direct_helpcode_slash,默认开) +// 和第二位辅码大写(helpcode.shuangpin_direct_helpcode_uppercase,默认关)。 +bool GetConfiguredShuangpinDirectHelpcodeSlash(); +bool SetConfiguredShuangpinDirectHelpcodeSlash(bool enabled); +bool GetConfiguredShuangpinDirectHelpcodeUppercase(); +bool SetConfiguredShuangpinDirectHelpcodeUppercase(bool enabled); +// DirectHelpcodeChanged 的载荷(FanyImeDirectHelpcode::PayloadFor):"0" 表示开关关着或当前方案不是双拼, +// "1" 开着且勾了 /,"2" 开着但没勾 /。 std::wstring FormatDirectHelpcodeWorkerPayload(); const std::string &GetConfiguredShuangpinHelpcodeSchema(); bool SetConfiguredShuangpinHelpcodeSchema(const std::string &schema); diff --git a/server/src/config/ime_config_input.cpp b/server/src/config/ime_config_input.cpp index bd4f1725..7d4e1e28 100644 --- a/server/src/config/ime_config_input.cpp +++ b/server/src/config/ime_config_input.cpp @@ -7,6 +7,7 @@ #include "global/globals.h" #include "defines/defines.h" #include "engine/common/helpcode_utils.h" +#include "engine/contracts/direct_helpcode.h" #include "engine/core/data_path.h" #include "engine/shuangpin/shuangpin_profile.h" @@ -461,10 +462,50 @@ bool SetConfiguredShuangpinDirectHelpcodeEnabled(bool enabled) return true; } +bool GetConfiguredShuangpinDirectHelpcodeSlash() +{ + return g_shuangpin_direct_helpcode_slash; +} + +bool SetConfiguredShuangpinDirectHelpcodeSlash(bool enabled) +{ + // 至少留一个:设置页取消不掉最后一个,这里同样不收。 + if (!enabled && !g_shuangpin_direct_helpcode_uppercase) + { + return false; + } + if (!WriteConfiguredValue("helpcode", "shuangpin_direct_helpcode_slash", enabled ? "true" : "false")) + { + return false; + } + g_shuangpin_direct_helpcode_slash = enabled; + return true; +} + +bool GetConfiguredShuangpinDirectHelpcodeUppercase() +{ + return g_shuangpin_direct_helpcode_uppercase; +} + +bool SetConfiguredShuangpinDirectHelpcodeUppercase(bool enabled) +{ + if (!enabled && !g_shuangpin_direct_helpcode_slash) + { + return false; + } + if (!WriteConfiguredValue("helpcode", "shuangpin_direct_helpcode_uppercase", enabled ? "true" : "false")) + { + return false; + } + g_shuangpin_direct_helpcode_uppercase = enabled; + return true; +} + std::wstring FormatDirectHelpcodeWorkerPayload() { - return g_shuangpin_direct_helpcode_enabled && GetConfiguredActiveInputScheme() == SchemeType::Shuangpin ? L"1" - : L"0"; + const bool enabled = + g_shuangpin_direct_helpcode_enabled && GetConfiguredActiveInputScheme() == SchemeType::Shuangpin; + return std::wstring(1, FanyImeDirectHelpcode::PayloadFor(enabled, g_shuangpin_direct_helpcode_slash)); } bool IsConfiguredMidSentenceHelpcodeTrigger(wchar_t ch) diff --git a/server/src/config/ime_config_internal.h b/server/src/config/ime_config_internal.h index 94f153d9..a508baff 100644 --- a/server/src/config/ime_config_internal.h +++ b/server/src/config/ime_config_internal.h @@ -140,6 +140,8 @@ extern bool g_shuangpin_mid_sentence_helpcode_backtick; extern bool g_shuangpin_mid_sentence_helpcode_semicolon; extern bool g_shuangpin_mid_sentence_helpcode_uppercase; extern bool g_shuangpin_direct_helpcode_enabled; +extern bool g_shuangpin_direct_helpcode_slash; +extern bool g_shuangpin_direct_helpcode_uppercase; extern bool g_quanpin_helpcode_enabled; extern std::string g_shuangpin_helpcode_schema; extern std::string g_quanpin_helpcode_schema; diff --git a/server/src/session/engine_input_session.cpp b/server/src/session/engine_input_session.cpp index 2553c33a..04829759 100644 --- a/server/src/session/engine_input_session.cpp +++ b/server/src/session/engine_input_session.cpp @@ -58,6 +58,8 @@ void EngineInputSession::ApplyConfiguration() session_.set_shuangpin_helpcode_enabled(GetConfiguredShuangpinHelpcodeEnabled()); session_.set_mid_sentence_helpcode_enabled(GetConfiguredShuangpinMidSentenceHelpcodeEnabled()); session_.set_direct_helpcode_enabled(GetConfiguredShuangpinDirectHelpcodeEnabled()); + session_.set_direct_helpcode_markers(GetConfiguredShuangpinDirectHelpcodeSlash(), + GetConfiguredShuangpinDirectHelpcodeUppercase()); session_.set_mid_sentence_uppercase_trigger_enabled(GetConfiguredShuangpinMidSentenceHelpcodeUppercase()); session_.set_quanpin_helpcode_enabled(GetConfiguredQuanpinHelpcodeEnabled()); const unsigned autocorrect_types = @@ -223,7 +225,8 @@ bool EngineInputSession::accepts_mid_sentence_helpcode_marker(std::size_t caret) bool EngineInputSession::accepts_direct_helpcode_slash(std::size_t caret) const { // 同上,按当前配置判断。 - return GetConfiguredShuangpinDirectHelpcodeEnabled() && session_.accepts_direct_helpcode_slash_at(caret); + return GetConfiguredShuangpinDirectHelpcodeEnabled() && GetConfiguredShuangpinDirectHelpcodeSlash() && + session_.accepts_direct_helpcode_slash_at(caret); } bool EngineInputSession::has_mid_sentence_helpcode() const diff --git a/server/src/settings/settings_app.cpp b/server/src/settings/settings_app.cpp index f478d7de..7c35da49 100644 --- a/server/src/settings/settings_app.cpp +++ b/server/src/settings/settings_app.cpp @@ -666,6 +666,8 @@ std::wstring BuildConfigMessage(bool refresh_skin_catalog) {"shuangpin_mid_sentence_helpcode_semicolon", GetConfiguredShuangpinMidSentenceHelpcodeSemicolon()}, {"shuangpin_mid_sentence_helpcode_uppercase", GetConfiguredShuangpinMidSentenceHelpcodeUppercase()}, {"shuangpin_direct_helpcode", GetConfiguredShuangpinDirectHelpcodeEnabled()}, + {"shuangpin_direct_helpcode_slash", GetConfiguredShuangpinDirectHelpcodeSlash()}, + {"shuangpin_direct_helpcode_uppercase", GetConfiguredShuangpinDirectHelpcodeUppercase()}, {"shuangpin_helpcode_schema", GetConfiguredShuangpinHelpcodeSchema()}, {"quanpin_helpcode", GetConfiguredQuanpinHelpcodeEnabled()}, {"quanpin_helpcode_schema", GetConfiguredQuanpinHelpcodeSchema()}, @@ -1046,6 +1048,10 @@ bool ApplyConfigUpdate(const json::object &data) return SetConfiguredShuangpinMidSentenceHelpcodeUppercase(json::value_to(data.at("value"))); if (path == "helpcode.shuangpin_direct_helpcode") return SetConfiguredShuangpinDirectHelpcodeEnabled(json::value_to(data.at("value"))); + if (path == "helpcode.shuangpin_direct_helpcode_slash") + return SetConfiguredShuangpinDirectHelpcodeSlash(json::value_to(data.at("value"))); + if (path == "helpcode.shuangpin_direct_helpcode_uppercase") + return SetConfiguredShuangpinDirectHelpcodeUppercase(json::value_to(data.at("value"))); if (path == "helpcode.shuangpin_helpcode_schema") return SetConfiguredShuangpinHelpcodeSchema(json::value_to(data.at("value"))); if (path == "helpcode.quanpin_helpcode") diff --git a/server/src/webview2/windows_webview2_settings.cpp b/server/src/webview2/windows_webview2_settings.cpp index 94bca62a..a69b74eb 100644 --- a/server/src/webview2/windows_webview2_settings.cpp +++ b/server/src/webview2/windows_webview2_settings.cpp @@ -1115,6 +1115,20 @@ static void ApplyHelpcodeSubkey(const std::string &path, const json::object &dat PostSettingsConfig(); } } + if (path == "helpcode.shuangpin_direct_helpcode_slash" || path == "helpcode.shuangpin_direct_helpcode_uppercase") + { + const bool value = json::value_to(data.at("value")); + // / 勾没勾随 DirectHelpcodeChanged 的载荷发给 TSF;大写字母本来就是编码键,TSF 不用知道。 + const bool saved = path == "helpcode.shuangpin_direct_helpcode_slash" + ? SetConfiguredShuangpinDirectHelpcodeSlash(value) + : SetConfiguredShuangpinDirectHelpcodeUppercase(value); + if (saved) + { + BroadcastMidSentenceHelpcodeTriggers(); + } + // 没存上(取消最后一个)也回推快照,让设置页的勾回到实际状态。 + PostSettingsConfig(); + } if (path == "helpcode.shuangpin_helpcode_schema") { const std::string value = json::value_to(data.at("value")); @@ -1745,6 +1759,8 @@ void PostSettingsConfig() {"shuangpin_mid_sentence_helpcode_semicolon", GetConfiguredShuangpinMidSentenceHelpcodeSemicolon()}, {"shuangpin_mid_sentence_helpcode_uppercase", GetConfiguredShuangpinMidSentenceHelpcodeUppercase()}, {"shuangpin_direct_helpcode", GetConfiguredShuangpinDirectHelpcodeEnabled()}, + {"shuangpin_direct_helpcode_slash", GetConfiguredShuangpinDirectHelpcodeSlash()}, + {"shuangpin_direct_helpcode_uppercase", GetConfiguredShuangpinDirectHelpcodeUppercase()}, {"shuangpin_helpcode_schema", GetConfiguredShuangpinHelpcodeSchema()}, {"quanpin_helpcode", GetConfiguredQuanpinHelpcodeEnabled()}, {"quanpin_helpcode_schema", GetConfiguredQuanpinHelpcodeSchema()}, diff --git a/server/tests/src/test_engine_shuangpin_session.cpp b/server/tests/src/test_engine_shuangpin_session.cpp index 613174e4..8ff7cd56 100644 --- a/server/tests/src/test_engine_shuangpin_session.cpp +++ b/server/tests/src/test_engine_shuangpin_session.cpp @@ -1524,6 +1524,15 @@ TEST_CASE(EngineShuangpinDirectHelpcodeDecodesWithoutGuideKey) apply("uiab"); REQUIRE(session.accepts_direct_helpcode_slash(4)); REQUIRE(!session.accepts_direct_helpcode_slash(3)); + // 四码标记至少留一个;只留大写时 / 不再收,TSF 收到 "2"。 + REQUIRE(!SetConfiguredShuangpinDirectHelpcodeSlash(false)); + REQUIRE(SetConfiguredShuangpinDirectHelpcodeUppercase(true)); + REQUIRE(SetConfiguredShuangpinDirectHelpcodeSlash(false)); + REQUIRE(!session.accepts_direct_helpcode_slash(4)); + REQUIRE_EQ(FormatDirectHelpcodeWorkerPayload(), std::wstring(shuangpin_active ? L"2" : L"0")); + REQUIRE(!SetConfiguredShuangpinDirectHelpcodeUppercase(false)); + REQUIRE(SetConfiguredShuangpinDirectHelpcodeSlash(true)); + REQUIRE(session.accepts_direct_helpcode_slash(4)); REQUIRE(SetConfiguredShuangpinDirectHelpcodeEnabled(false)); REQUIRE(!session.accepts_direct_helpcode_slash(4)); REQUIRE_EQ(FormatDirectHelpcodeWorkerPayload(), std::wstring(L"0")); diff --git a/ui-html/webview2/settings/ime-settings/src/modules/config-sync.ts b/ui-html/webview2/settings/ime-settings/src/modules/config-sync.ts index 0b218ca9..dd18269c 100644 --- a/ui-html/webview2/settings/ime-settings/src/modules/config-sync.ts +++ b/ui-html/webview2/settings/ime-settings/src/modules/config-sync.ts @@ -349,6 +349,14 @@ function applyConfigData(data: Record, target?: string): void { const checkbox = findElement('midSentenceHelpcodeUppercaseCheckbox') as HTMLInputElement | null; if (checkbox) checkbox.checked = data.helpcode.shuangpin_mid_sentence_helpcode_uppercase; } + if (typeof data?.helpcode?.shuangpin_direct_helpcode_slash === 'boolean') { + const checkbox = findElement('directHelpcodeSlashCheckbox') as HTMLInputElement | null; + if (checkbox) checkbox.checked = data.helpcode.shuangpin_direct_helpcode_slash; + } + if (typeof data?.helpcode?.shuangpin_direct_helpcode_uppercase === 'boolean') { + const checkbox = findElement('directHelpcodeUppercaseCheckbox') as HTMLInputElement | null; + if (checkbox) checkbox.checked = data.helpcode.shuangpin_direct_helpcode_uppercase; + } applyDropdownValue( 'shuangpinHelpcodeSchemeBtn', 'shuangpinHelpcodeSchemeMenu', diff --git a/ui-html/webview2/settings/ime-settings/src/modules/helpcode.ts b/ui-html/webview2/settings/ime-settings/src/modules/helpcode.ts index 7d8ff330..c3a48ea7 100644 --- a/ui-html/webview2/settings/ime-settings/src/modules/helpcode.ts +++ b/ui-html/webview2/settings/ime-settings/src/modules/helpcode.ts @@ -43,6 +43,22 @@ function refreshCustomHelpcodes(): void { webview.postMessage(serializeHostMessage({ type: 'configRequest' })); } +// 一组可多选的配置勾选框:最后一个勾着的取消不掉。 +function setupAtLeastOneCheckboxes(checkboxes: [string, string][]): void { + for (const [id, path] of checkboxes) { + const checkbox = document.getElementById(id) as HTMLInputElement | null; + checkbox?.addEventListener('change', () => { + const anyChecked = checkboxes.some( + ([otherId]) => (document.getElementById(otherId) as HTMLInputElement | null)?.checked); + if (!anyChecked) { + checkbox.checked = true; + return; + } + updateConfig(path, checkbox.checked); + }); + } +} + export function setupHelpcode(): void { // 双拼辅助码方案 setupDropdownMenu('shuangpinHelpcodeSchemeBtn', 'shuangpinHelpcodeSchemeMenu', 'changeShuangpinScheme', true, @@ -70,23 +86,17 @@ export function setupHelpcode(): void { }); // 句中辅助码触发键,可多选;至少留一个,最后一个取消不掉 - const triggerCheckboxes: [string, string][] = [ + setupAtLeastOneCheckboxes([ ['midSentenceHelpcodeBacktickCheckbox', 'helpcode.shuangpin_mid_sentence_helpcode_backtick'], ['midSentenceHelpcodeSemicolonCheckbox', 'helpcode.shuangpin_mid_sentence_helpcode_semicolon'], ['midSentenceHelpcodeUppercaseCheckbox', 'helpcode.shuangpin_mid_sentence_helpcode_uppercase'], - ]; - for (const [id, path] of triggerCheckboxes) { - const checkbox = document.getElementById(id) as HTMLInputElement | null; - checkbox?.addEventListener('change', () => { - const anyChecked = triggerCheckboxes.some( - ([otherId]) => (document.getElementById(otherId) as HTMLInputElement | null)?.checked); - if (!anyChecked) { - checkbox.checked = true; - return; - } - updateConfig(path, checkbox.checked); - }); - } + ]); + + // 直接辅助码句中四码的结束方式,同样可多选、至少留一个 + setupAtLeastOneCheckboxes([ + ['directHelpcodeSlashCheckbox', 'helpcode.shuangpin_direct_helpcode_slash'], + ['directHelpcodeUppercaseCheckbox', 'helpcode.shuangpin_direct_helpcode_uppercase'], + ]); // 全拼辅助码开关 setupToggleButton('quanpinHelpcodeToggleBtn', (active) => { diff --git a/ui-html/webview2/settings/ime-settings/src/partials/helpcode.html b/ui-html/webview2/settings/ime-settings/src/partials/helpcode.html index 4e961e43..4d5c70a8 100644 --- a/ui-html/webview2/settings/ime-settings/src/partials/helpcode.html +++ b/ui-html/webview2/settings/ime-settings/src/partials/helpcode.html @@ -56,7 +56,7 @@
-
直接辅助码(万象式)
+
直接辅助码(万象式)
@@ -64,6 +64,15 @@
与上面的句中辅助码互斥:开启其中一个会自动关闭另一个。开启后末尾辅助码也不再生效,由它统一识别
+
+
直接辅助码句中四码标记
+
+
+ +
+ +
至少保留一个。四码在句中要标记出来,否则会让位给普通切分。斜杠:两位辅码后补一个 /;不勾时 / 照常当标点。第二位辅码大写:第二位辅码用大写敲;开启后大写字母只当四码的第二位辅码
+
diff --git a/windows/src/Composition/CompositionProcessorEngine_KeyClassify.cpp b/windows/src/Composition/CompositionProcessorEngine_KeyClassify.cpp index 63c3f9d3..9e787165 100644 --- a/windows/src/Composition/CompositionProcessorEngine_KeyClassify.cpp +++ b/windows/src/Composition/CompositionProcessorEngine_KeyClassify.cpp @@ -52,7 +52,7 @@ bool IsMicrosoftShuangpinIngKeyAt(UINT uCode, WCHAR wch, const WCHAR *buffer, DW bool IsDirectHelpcodeSlashKey(UINT uCode, WCHAR wch, const WCHAR *buffer, DWORD_PTR length, DWORD_PTR caret) { return uCode == VK_OEM_2 && wch == L'/' && Global::DirectHelpcodeEnabled.load(std::memory_order_relaxed) && - buffer != nullptr && length > 0 && + Global::DirectHelpcodeSlashEnabled.load(std::memory_order_relaxed) && buffer != nullptr && length > 0 && FanyImeDirectHelpcode::AcceptsSlashAt(buffer, static_cast(length), static_cast(min(caret, length))); } diff --git a/windows/src/IME/MetasequoiaIME_IpcWorker.cpp b/windows/src/IME/MetasequoiaIME_IpcWorker.cpp index e19dc33d..47cfeef2 100644 --- a/windows/src/IME/MetasequoiaIME_IpcWorker.cpp +++ b/windows/src/IME/MetasequoiaIME_IpcWorker.cpp @@ -407,7 +407,6 @@ void CMetasequoiaIME::IpcWorkerThread(CMetasequoiaIME *pIME) buf.msg_type == Global::DataToTsfWorkerThreadMsgType::MicrosoftShuangpinChanged || buf.msg_type == Global::DataToTsfWorkerThreadMsgType::MidSentenceHelpcodeChanged || buf.msg_type == Global::DataToTsfWorkerThreadMsgType::MidSentenceHelpcodeSemicolonChanged || - buf.msg_type == Global::DataToTsfWorkerThreadMsgType::DirectHelpcodeChanged || buf.msg_type == Global::DataToTsfWorkerThreadMsgType::MidSentenceHelpcodeUppercaseChanged || buf.msg_type == Global::DataToTsfWorkerThreadMsgType::InputModeChanged || buf.msg_type == Global::DataToTsfWorkerThreadMsgType::CapsLockChanged || @@ -440,6 +439,10 @@ void CMetasequoiaIME::IpcWorkerThread(CMetasequoiaIME *pIME) validFrame = hasTerminator && (buf.data[0] == L'0' || buf.data[0] == L'1' || buf.data[0] == L'2') && buf.data[1] == L'\0'; } + if (validFrame && buf.msg_type == Global::DataToTsfWorkerThreadMsgType::DirectHelpcodeChanged) + { + validFrame = FanyImeDirectHelpcode::IsValidPayload(buf.data[0]) && buf.data[1] == L'\0'; + } if (validFrame && (buf.msg_type == Global::DataToTsfWorkerThreadMsgType::UpdateVoiceComposition || buf.msg_type == Global::DataToTsfWorkerThreadMsgType::CommitVoiceComposition)) { @@ -625,7 +628,10 @@ void CMetasequoiaIME::IpcWorkerThread(CMetasequoiaIME *pIME) } else if (buf.msg_type == Global::DataToTsfWorkerThreadMsgType::DirectHelpcodeChanged) { - Global::DirectHelpcodeEnabled.store(buf.data[0] == L'1', std::memory_order_relaxed); + Global::DirectHelpcodeSlashEnabled.store(FanyImeDirectHelpcode::SlashFromPayload(buf.data[0]), + std::memory_order_relaxed); + Global::DirectHelpcodeEnabled.store(FanyImeDirectHelpcode::EnabledFromPayload(buf.data[0]), + std::memory_order_relaxed); } else if (buf.msg_type == Global::DataToTsfWorkerThreadMsgType::MidSentenceHelpcodeUppercaseChanged) { diff --git a/windows/src/IPC/Ipc.h b/windows/src/IPC/Ipc.h index 9ffac75f..8bd5ffb7 100644 --- a/windows/src/IPC/Ipc.h +++ b/windows/src/IPC/Ipc.h @@ -10,6 +10,7 @@ #include #include "../../../engine/contracts/windows_ipc.h" +#include "../../../engine/contracts/direct_helpcode.h" #include "../../../engine/contracts/v_mode_input.h" int InitIpc(); @@ -221,6 +222,8 @@ inline std::atomic_bool MidSentenceHelpcodeUppercaseEnabled{false}; // 双拼直接辅助码(万象式)开着且当前是双拼:四码后的 / 当编码键吃掉,; 韵母不再看奇偶,规则见 // engine/contracts/direct_helpcode.h。 inline std::atomic_bool DirectHelpcodeEnabled{false}; +// 直接辅助码开着时 / 是不是四码的终止键(设置里可以只留「第二位辅码大写」)。没勾时 / 仍按标点处理。 +inline std::atomic_bool DirectHelpcodeSlashEnabled{true}; // V 模式(数字转中文、算式计算)由哪个前缀开启:双拼只认大写 V,全拼 V、v 都认,关着或不是全拼/双拼时 // 不开。V 后面的数字和 . + - * / ( ) 按 engine/contracts/v_mode_input.h 的规则当编码键吃掉。 inline std::atomic VModeTrigger{FanyImeVModeInput::Trigger::Off};