Files
Jia Suiandfanlusky d414268b5f [Feat] U 模式重构为部件拆字反查 (#973)
* feat(engine,server,windows,ui-html,installer): U 模式重构为部件拆字反查 (#971)

把 U 模式从 Unicode 十六进制码位输入改成部件拆字反查:Shift+U 后输入各
组成部分的拼音连写(晶 = ririri)即可出字,候选上反显注音。

- 引擎:新增 local_modes/radical_query(前缀匹配、字频降序),数据表
  engine/radical/decomposition.txt 与生成脚本;LocalInputMode::Unicode
  改为 Radical;删除 unicode_query。
- Server:g_radical_mode_triggered + IsRadicalCompositionActive + 候选分发,
  用 cpp-pinyin 反显注音;删除 Unicode 码位路径。
- 前端 TSF:删除 U 模式的十六进制/数字特例与影子预判,按普通组合处理。
- 配置:utility.unicode_mode 改为 utility.radical_mode。
- 部署:assets 登记 radical_decomposition,安装器与 CI 拷表。

数据来源 mirtlecn/rime-radical-pinyin (GPL-3.0),授权见 engine/radical/NOTICE.md。

* feat(server): U 模式反显拼音改为带调风格(jīng)

* fix(server,installer): 修复 U 模式 CI(拆字表打包夹具与 clang-format)

* fix(engine,server): 修正 U 模式按键错位、重复候选与配置迁移

- Server 在 U 模式不再吞掉撇号、分号、斜杠、大写字母等键:TSF 已把它们记进按键缓冲,
  吞掉又不重同步会让两侧串和光标错位。照常插入,含非 a-z 的码查不出候选
- 拆字查询全码命中排在仅前缀命中之前,同一个字的几种拆法只出一次
- 升级合并把旧键 utility.unicode_mode 的值带到 utility.radical_mode,读取时也兜底旧键
- 生成脚本只收汉字,重建拆字表去掉 abc 码下的 51 行数字、字母与符号
- 开着 U 模式时在后台预热拆字表与 cpp-pinyin 词典,避开首个按键的冷加载
- Server 按会话的 RuntimePaths 查拆字表,删掉走全局数据目录的便捷重载
- cpp-pinyin 初始化合并到 utils/hanzi_pinyin,设置页导入注音共用;U 模式与快捷短语的收键分支合并

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-16 01:01:18 +08:00

95 lines
3.9 KiB
C++

#pragma once
#include <cstddef>
// V 模式(数字转中文、算式计算)的输入形状。V 后面跟数字或左括号就进 V 模式:
//
// V123 一百二十三、壹佰贰拾叁……
// V123.45 壹佰贰拾叁元肆角伍分……
// V1+2*3 7
//
// 双拼和五笔只认大写 V(小写 v 是双拼的编码键,也是五笔的码字,五笔下 v+数字是选词);全拼大小写
// 都认,全拼里 v 不会出现在开头。小写 v 只有后面接了数字或左括号才算 V 模式,vip 这类输入照旧走
// 拼音和英文混输。
//
// 进了 V 模式后,数字和 . + - * / ( ) 都是编码键:数字不再选词,改用 Shift+数字选,
// 空格上屏高亮的那个。Shift+8/9/0 打出的 * ( ) 是编码键,所以只有 Shift+1~7 选词,V 模式的候选
// 也不会超过 7 个。
//
// TSF 在同步吃键阶段按这里判断,Server 按同一条规则改输入串,两边必须一致,否则输入串会分叉。
// TSF 看不到配置和方案,由 Server 用 VModeChanged 告诉它当前的 Trigger。
namespace FanyImeVModeInput
{
inline constexpr char kUpperPrefix = 'V';
inline constexpr char kLowerPrefix = 'v';
// VModeChanged 的载荷就是这个值的十进制字符:"0" / "1" / "2"。
enum class Trigger
{
Off = 0, // 关着,或者其他方案(日文、专用英文等)
UppercaseOnly = 1, // 双拼、五笔:只认大写 V
AnyCase = 2, // 全拼:V 和 v 都认
};
template <typename Char> constexpr bool IsDigit(Char ch)
{
return ch >= Char('0') && ch <= Char('9');
}
// 算式里除数字以外的字符。
template <typename Char> constexpr bool IsOperator(Char ch)
{
return ch == Char('.') || ch == Char('+') || ch == Char('-') || ch == Char('*') || ch == Char('/') ||
ch == Char('(') || ch == Char(')');
}
template <typename Char> constexpr bool IsInputChar(Char ch)
{
return IsDigit(ch) || IsOperator(ch);
}
// 紧跟在 V 后面、能开启 V 模式的字符。只认数字和左括号:小写 v 后面紧跟的 - . 等键在全拼里本来是翻页、
// 标点,不能被抢走。
template <typename Char> constexpr bool IsStartChar(Char ch)
{
return IsDigit(ch) || ch == Char('(');
}
template <typename Char> constexpr bool IsPrefix(Char ch, Trigger trigger)
{
return (trigger != Trigger::Off && ch == Char(kUpperPrefix)) ||
(trigger == Trigger::AnyCase && ch == Char(kLowerPrefix));
}
// text 是不是一段 V 模式输入:开头是 V(或全拼的 v),后面紧跟数字或左括号。单独一个 V / v 还不算,它照旧
// 是普通的组合(英文、拼音的开头),接上第一个数字才进 V 模式。V 后面再混进别的键(比如字母)仍算 V 模式,
// 只是没有候选。
template <typename Char> constexpr bool IsComposition(const Char *text, std::size_t size, Trigger trigger)
{
return text != nullptr && size >= 2 && IsPrefix(text[0], trigger) && IsStartChar(text[1]);
}
// 光标停在 text[caret] 时能否插入 ch:ch 是 V 模式的编码键,插入后整串仍是一段 V 模式输入。所以 V 后面
// 第一个键只能是数字或左括号,也不能把运算符插到 V 和第一个数字之间。
template <typename Char>
constexpr bool AcceptsAt(const Char *text, std::size_t size, std::size_t caret, Char ch, Trigger trigger)
{
if (text == nullptr || size == 0 || caret < 1 || caret > size || !IsInputChar(ch) || !IsPrefix(text[0], trigger))
{
return false;
}
const Char second = caret == 1 ? ch : text[1];
return IsStartChar(second);
}
constexpr Trigger TriggerFromPayload(wchar_t ch)
{
return ch == L'2' ? Trigger::AnyCase : ch == L'1' ? Trigger::UppercaseOnly : Trigger::Off;
}
constexpr wchar_t PayloadFromTrigger(Trigger trigger)
{
return trigger == Trigger::AnyCase ? L'2' : trigger == Trigger::UppercaseOnly ? L'1' : L'0';
}
} // namespace FanyImeVModeInput