* feat(engine,server): 五笔支持 T 模式与 V 模式 五笔方案此前被排除在 Shift+T 日期时间模式与 V 模式之外。根因是方案层就把 raw 归一成小写 a–y、丢弃数字与运算符,Server 的形状判定读不到大写 T/V 前缀。 - 引擎 WubiScheme 新增 raw_input_with_cases_:保留大小写与 T/V 模式的非字母键 (数字、运算符、: /),但按码字域丢掉越域字母(默认配置下 z)。查询码仍由 normalize_wubi_code 派生,普通码下两者长度相等——Server 用原样串长度数四码。 - Server:T 触发新增独立谓词 date_time_scheme(chinese_scheme || Wubi),不并入 chinese_scheme 本身;FormatVModeWorkerPayload 对五笔返回 UppercaseOnly。 - 自动上屏 / 顶字两个谓词新增 special_mode_active 前置:T 模式关键词归一后可能与 合法四码重合(Tdate → tdat),不挡会在第四个字母上结束模式。 - 引擎与 Server 测试、README 与两份配置模板文案同步。 * fix(engine,server,ui-html): 五笔 T/V 模式由宿主标注字面输入 原实现让五笔原样串不再裁到码长,普通码下原样串可以比码长(混输关、z 通配时 wqazb 对 wqaz), Server 读回后光标、退格和上屏都会错位;守卫也只认 T 模式的全小写主体,TdatE 仍会顶出 tdat。 - 引擎:InputSession::set_wubi_literal_input 随 pending 序列一起下发,只作用于同一次设置。 普通码的原样串就是带大小写的码,与查询码逐位对应、一起截断;字面输入原样保留, 不查码表与拼音,也不算完整四码。退格从原样串重新派生码,字母键两份视图取同一个字母。 - Server:ApplyCompositionEditKey 按 IsWubiLiteralComposition(T 模式触发标记或 V 模式形状) 标注字面输入;两个五笔上屏策略恢复原签名,不再需要 special_mode_active。 - 设置页 T/V 模式说明补上五笔。 --------- Co-authored-by: fanlusky <1730976608@qq.com>
输入方案
一个「输入方案」负责把按键序列变成一次候选查询。全拼、双拼、五笔、日文罗马字四种方案共用同一个接口,彼此不知道对方存在——加一种新方案(注音、粤拼、仓颉……)不需要改动任何一个已有方案。
这一页说明加一种方案要动哪几处、每一处要做什么。招募文档把「实现新的输入方案」列为门槛最高的方向,但此前这个目录下只有一个 17 行、零注释的纯虚基类。
接口
input_scheme.h 定义了全部五个方法:
class IInputScheme
{
public:
virtual ~IInputScheme() = default;
virtual void reset() = 0;
virtual void handle_key(ImeKeyCode vk, ImeModifierMask modifiers_down, ImeCharacter wch) = 0;
virtual QueryRequest build_request() const = 0;
virtual std::string get_preedit() const = 0;
virtual SchemeType type() const = 0;
};
| 方法 | 职责 | 注意 |
|---|---|---|
reset() |
清空当前编码串与按键记录 | 上屏、取消、切换方案时都会调用,必须回到干净状态 |
handle_key() |
收一个按键,更新内部状态 | 不查词库、不发网络请求、不碰全局状态。 这个方法在每一次击键的关键路径上,宿主进程等着它返回 |
build_request() |
把当前状态打包成 QueryRequest |
const 方法,不改状态;同样的状态调用两次必须得到同样的结果 |
get_preedit() |
返回要显示在编辑区的文本 | 用户看到的正在输入的内容,不一定等于原始按键 |
type() |
返回本方案的 SchemeType |
候选提供者据此路由 |
handle_key 只接受方案自己认得的键。哪些键该由方案处理、哪些该交回宿主(翻页、选词、方向键),由调用方 ImeSession 决定,方案不需要关心。
QueryRequest:方案与候选查询之间的契约
../core/query_request.h 里的这个结构是方案唯一的输出。字段不必全填,但 valid 必须准确——valid == false 表示当前编码还不足以查询,会话不会去查词库。
几个容易搞错的字段:
raw_input是规范化后的编码串(通常是小写),raw_input_with_cases保留原始大小写。英文混输和以词定字要用后者。normalized_input是拿去查库的键。全拼里它是纠错、简拼展开之后的结果;一个不做这类处理的方案直接填成和raw_input一样即可。segmentation/raw_segmentation/normalized_segmentation是分词结果,音节之间用'分隔。不分音节的方案(五笔)留空。key_strokes是原始按键序列。候选窗要还原用户实际敲了什么时用它,比如显示原始编码。enable_*那几个开关由会话按用户配置填写,方案不要自己设。
加一种新方案要动的地方
以五笔为例走一遍(wubi_scheme.h / wubi_scheme.cpp 是最短的一个实现,适合照着看):
-
../core/scheme_type.h— 在SchemeType里加一个枚举值。 -
schemes/<name>_scheme.{h,cpp}— 实现IInputScheme。构造函数可以带参数:双拼方案就接收一个ShuangpinProfile,方案表由调用方注入而不是硬编码在方案里。 -
../core/ime_session.cpp的create_scheme()— 在 switch 里加一个分支返回你的实现。这是唯一注册点,没有别的注册表或工厂宏。 -
候选提供者 — 决定查询走哪条路。
../providers/provider_registry.h的resolve(SchemeType)把方案映射到一个ICandidateProvider。如果新方案查的是已有的库(比如注音可以复用拼音库,只是编码转换不同),映射到已有的 provider 即可,不必新写一个。查全新的表才需要实现../providers/candidate_provider.h的ICandidateProvider——注意它有八个方法,除了query()还包括造词、调频、删词和动态候选缓存,都要实现。 -
词库 — 新方案如果需要自己的表,表结构受
../contracts/dictionary/format.json约束。建库流水线不在本仓库内(见../UPSTREAM.md),词库是product-lock.json钉住的发布产物,所以新表要先有人出一版带这张表的词库发布,再把锁指过去;在那之前方案只能落在已有的表上。数据的再分发授权要求见../NOTICE.md。 -
../CMakeLists.txt与../tests/CMakeLists.txt— 两份源码列表都要加上新文件。只加前者会导致测试链接失败,只加后者会导致产品里没有这个方案。 -
测试 —
tests/src/下加一个test_<name>_scheme.cpp,并登记到tests/CMakeLists.txt。参考test_shuangpin.cpp(方案层的按键与 preedit)与test_pinyin.cpp(编码与查询)。最低限度要覆盖:一串典型按键得到的get_preedit()、build_request()的valid何时为真、reset()之后状态确实干净、以及非法按键不会破坏状态。
提交之前
cmake -S . -B build && cmake --build build && ctest --test-dir build全绿。- 说明你实际敲了什么、看到什么。输入法的正确性很难靠读代码判断,PR 里请写清验证方式(见组织的贡献指南)。
- 方案本身不联网、不读配置文件、不写日志。这些都由上层负责。