fanlusky
|
42cff6de4a
|
fix(server): WebView2 选词不再白等渲染同步超时
WebView2 下几乎每次空格/数字选词都等满渲染同步上限(日志 waited_ms 82-94,
rendered 恒比 current 小 1),体感为上屏顿一下。
每个按键 worker 会以相同内容发布两次页面:第一次走 content-only,回显在
异步 DOM 回调里;第二次命中 dedup,立即回显新代数。随后迟到的 DOM 回调把
回显改回旧代数,选词就一直等到超时。D2D 同步回显,不受影响。
- 渲染回显代数只进不退(AdvanceRenderedCandidateGeneration),迟到的旧回显
不再覆盖新回显。
- folded-inflight 分支补上回显:进行中的 FineTune 只回显它捕获的那一页,
之后折叠进来的页面此前永远没有回显。
|
2026-09-27 10:24:50 +08:00 |
|
fanlusky
|
d852a05687
|
fix(windows,server): 选词回复超时不再上屏未显示的候选
电池模式下数字选词常上屏与所见不同的候选。链路是:TSF 只等 50ms
回复;Server 在回复前同步写调频,渲染同步 30ms 到点后按未绘制的页面
选词;超时后 TSF 断管重放按键,Server 又调一次频。
- server:选词调频(拼音/五笔/英文)改为 worker 队列任务,在回复之后、
下一键之前执行,不再占用回复时间。
- server:渲染同步改为条件变量,绘制回显即唤醒;上限 30ms 提到 80ms。
- windows:上屏类回复等待 300ms;请求已送达仍超时时视为投递不确定,
只重建前缀并丢弃上屏键,不再无条件重放。
- server:同一选择 3s 内在新连接上再次到达时跳过调频,避免重放重复计数。
|
2026-09-27 00:47:36 +08:00 |
|
fanlusky
|
7d66b2556c
|
feat(server,ui,ui-html): 候选翻译文本支持在皮肤中单独配色
外部皮肤 skin.toml 的 [candidate.dark] / [candidate.light] 新增 translation 键。
不写时沿用候选文字色叠 62% 不透明度;写了就按原值绘制(含选中行)。
D2D(CandidateList::Appearance::translationColor)、WebView2 注入 CSS
和设置页外部皮肤预览三处一致;预览顺带补上此前漏掉的 number 配色。
|
2026-09-26 23:38:37 +08:00 |
|
fanlusky
|
c539d0cf2a
|
feat(server,ui-html): 设置窗口驻留时长可配置,驻留期间降低内存占用
- 关闭设置窗口后隐藏驻留时,把 WebView2 内存目标设为 LOW 并 TrySuspend 冻结页面;
重新打开时恢复 NORMAL、Resume 后再显示。本机驻留 60 秒后整棵进程树的工作集
由约 408 MB 降到约 62 MB,重开仍是瞬间恢复。
- 新增配置项 appearance.settings_window_linger,可选 off / 1m / 5m / 10m / 30m /
60m / forever,默认 10m。off 表示关闭即退出,forever 表示一直驻留。
- 同步出厂模板、开发配置、ime_config 读写校验和两个设置宿主的快照字段与写入分发;
设置页“外观 → 界面渲染”新增“设置窗口后台驻留”下拉框。
- 新增测试覆盖模板默认值、缺键、非法值、setter 拒绝和逐值往返。
|
2026-09-26 22:57:18 +08:00 |
|
fanlusky
|
030f2d4db8
|
feat(server): D2D 悬浮工具栏跟随候选窗皮肤配色
此前 D2D 工具栏只按明暗切换默认配色,不看皮肤。新增 floating_toolbar_skin,
按 ui-html/webview2/ftb/<skin>(_light).html 给出背景、边框、图标、悬停、分隔线、
拖动条颜色与圆角;外部皮肤沿用其 base 皮肤,因为 toolbar.css 只作用于 WebView2。
|
2026-09-26 21:49:04 +08:00 |
|
fanlusky
|
8278c065c9
|
feat(server,ui-html): 新增内置皮肤秋桂,杨柳青 D2D 高亮对齐 WebView2
秋桂 Autumn osmanthus(autumn_osmanthus):暗色底 #7D929F、高亮 #F97D0A,
亮色底 #D6ECF0、高亮 #FFE399。高亮内缩于外框,首项顶部两角与尾项底部两角
和外框同为 10px;预编辑行隐藏时首项才贴顶。WebView2 候选窗与工具栏、D2D
候选窗、设置页卡片与预览一并提供。
ApplyCandidateFrame 只隐藏多余的候选行,:last-child 找不到最后一个可见行,
因此为其加上 .last-visible,杨柳青的 CSS 同步改用它,候选不满一页时尾项的
补边与贴边高亮才会生效。
杨柳青在 D2D 下改为直角、左右贴边的整行高亮,只在外框四角处用 9px 贴合,
行距、行内边距与预编辑行边距按 CSS 对齐。
|
2026-09-26 21:49:04 +08:00 |
|
fanlusky
|
b3b3b3303c
|
feat(windows): 焦点切换到另一个输入框时提示当前中英文状态
新增开关 general.caret_state_indicator_on_focus(默认关闭,需同时开启光标
状态提示)。
- DLL 把以下情况视为“换了输入框”:焦点窗口变化(Win32 编辑框、对话框、
其他应用各有自己的窗口);焦点经过一段没有可编辑文档的间隙后落到可
编辑文档上(Chromium、WPF 等单窗口宿主在字段之间就是先失焦再聚焦);
焦点会话真正结束后重新开始。Chromium 打字时在两个可编辑文档之间直接
替换文档管理器,不会触发。从 Win+. 面板返回不计入;线程焦点在真正的
应用切换后返回计入。一次切换的多次回调由定时器合并为一次提示;组字
过程中、以及没有光标的控件都保持静默。每次判断在 TSF 诊断日志里记录
判定标志,不含任何文本
- 协议复用 IMESwitch:wch 改为 FanyImeCaretStateTrigger(UserToggle /
CapsLockEdge / FocusEntered),CapsLockEdge 保持首发时的 VK_CAPITAL
取值,Server 对未知值按 UserToggle 处理
- Server 在开关打开时显示当前的有效模式(Caps Lock 开启时为“英”);
配置项同步写入两份模板和两个设置窗口宿主的读写映射
- 设置页在光标状态提示卡片中增加“切换输入框时提示”开关
|
2026-09-26 17:09:34 +08:00 |
|
fanlusky
|
d99aaa0d7d
|
feat(server): 光标状态提示补全左下方、正下方、右下方
提示位置原先只有左上方、正上方、右上方和“下方”四种,且“下方”在设置页
预览里居中显示,Server 却把它放在光标左侧。
- 新增 bottom-left(左下方)与 bottom-right(右下方)
- bottom 保留原配置值、改名为“正下方”,并改为与 top 对称地居中,
使运行时位置与预览一致;已选“下方”的用户升级后不会被重置
- 合法位置与“是否在光标下方”的判断集中到 caret_state_indicator_policy.h,
配置读取、写入与定位共用一份定义
- 同步两份配置模板的注释、设置页下拉项、aria 文案与预览样式,并补充测试
|
2026-09-26 16:00:59 +08:00 |
|
fanlusky
|
597d087580
|
fix(server): 光标状态提示仅由用户快捷键触发并收紧协议边界
- DLL 只在 Shift/Ctrl 切换、Shift+Space、Ctrl+.、Caps Lock 与
Ctrl+Shift+F 处显式请求提示;Server 下发的模式同步、宿主转换模式
同步和激活初始化不再弹出提示,移除 compartment 回调里的推断逻辑
- 恢复文档切换时候选窗的 OnKillThreadFocus,改用 COM 身份比较
- 能力位改名为 CaretStateIndicator,DLL 仅在 Server 确认后发送切换
事件;Server 端这些事件只驱动提示,不再改写工具栏或状态快照
- 标点提示的中英模式由 DLL 随 PuncSwitch 携带
- UILess 宿主不显示提示,会话转为 UILess 时隐藏已显示的提示
- Ctrl+Shift+F 的锚点就地读取,不再覆盖 Global::Point,且只采信
协商了该能力的客户端
- 撤回对所有包默认 point 的修改;提示窗口创建失败不再阻断 Server
启动;提示内容改为结构化 CaretStateBadge
- Caps Lock 在同一按键重复进入 TestKeyDown 时只处理一次
|
2026-09-26 13:58:22 +08:00 |
|
fanlusky
|
dc7cfa0354
|
Merge branch 'develop' into feat/issue-229-ime-status-indicator
|
2026-09-26 13:58:03 +08:00 |
|
Jia Sui
|
6506a7a07f
|
fix(server): 退格删空 raw 不再吞掉已选词
输入 nihaoya、选「你好」、再敲一个字符后连按退格,删掉最后一个 raw 字符
的那一次会把造词态连同已选词一起清掉(尾部「raw 空即清」规则),用户看到
「你好」被直接删掉。这条规则的前提是「客户端会随空 raw 取消组合」,但协商
过 CompositionRestored 的客户端会应用帧、把组合留在「只有已选词」的状态
(与 R3 段删除一致),此时清态反而丢字。
- 新增 ShouldKeepCreatingWordAfterRawEmptied:仅协商过组合恢复、非 UILess
且有快照时保留造词态与快照;普通退格与 Ctrl+Backspace 段删除共用该谓词。
- 普通退格删空 raw 时置 keep:帧里 word 非空,DLL 走
_ApplyCreatingWordPayload 的「只有已选词」分支继续组合,不再 _HandleCancel;
下一次退格由「raw 为空时退格只能是撤销」把最近一次选词撤回,而不是丢整句。
- 未协商与 UILess 客户端维持原行为(本地取消 + 清态),避免两侧状态分叉。
- 测试补 deleting_the_last_raw_character_keeps_the_created_word。
|
2026-09-25 15:59:05 +08:00 |
|
Jia Sui
|
a596f8b1bc
|
fix(server): 撤销重高亮只在候选页前缀未变时应用
选词快照记的候选绝对位置对应的是选词那一刻用户眼前的候选页,而撤销
重建的页面由恢复后的 raw 与光标前缀决定:选词之后用 Del 或光标编辑改
动过后缀时,重建页装着另一批候选,旧位置会把高亮落到不相干的项上。
- 新增 FanyImeIpc::NormalizeCandidatePagePrefix,按引擎的页身份把光标
前缀折成小写(refresh_prefix_candidates 的同一套归一化)并夹到 raw 长度。
- 快照多带一个 selected_page_prefix,取在 advance_composition_after_selection
之前(那之后引擎已经换成结算后的 raw,不再是用户看到的那一页)。
- 一次性交接从裸 int 改成 RestoredSelectionHighlight(位置 + 页前缀),
HandleImeKey 重建页面后先比较前缀再套用位置,不一致就只发布新页不高亮。
- 测试更新位置交接用例并覆盖归一化谓词的空串、越界、大小写与改短后缀。
|
2026-09-25 09:43:38 +08:00 |
|
Jia Sui
|
eb6c6f84ce
|
fix(server): 造词态被退格清空时也回帧,避免 TSF 挂起烧满超时
答帧判定用的是按键之后的 creating_word.active,而退格删掉最后一个
raw 字符时尾部会清掉造词态与快照,形态随之消失:这样一次退格不发
帧,而 DLL 的 hold 只看按键前的镜像 word_for_creating_word,仍然
非空,于是白等满 50ms 才回退本地删除,正是本次改动想消除的那类等待。
- HandleImeKey 在 ApplyCompositionEditKey 之前抓一份客户端可见的
形态,答帧条件改为「撤销过 或 按键前形态 或 按键后形态」,用新的
ShouldAnswerRetreatBackspace 把这条不变量写成可测谓词。
- 该结局的 payload 是空 raw + 空 word,DLL 侧按 _ApplyCreatingWordPayload
的空 word 分支立即 _HandleCancel,语义与超时后一致,只是不再等。
- 候选窗收起的判据从「raw 空且造词态还在」放宽为「raw 空」:造词态
被这次退格终结时同样不能落入重建分支,否则会给空输入发布兜底假候选。
- 测试补 retreat_backspace_reply_survives_the_key_that_ends_the_word。
|
2026-09-25 09:37:57 +08:00 |
|
Jia Sui
|
c06dc17ae1
|
feat(server,windows): 造词中的退格优先撤销最近一次选词
把"退格撤销上次选词"从旧的两条窄触发(剩余输入只剩一个字符、或光标
正好停在这个词后面)放宽为造词态下的默认行为,对齐 Rime/微信:只要
造词还活着且有可选回的快照,首个退格先撤销最近一次选词,剩余输入
长度与光标位置不再参与判定。
- 选后编辑锁(selected_before_editing):选词后又输入了新字符,退格
继续删除这些新输入,而不是把选词从脚下抽走;只标记最新快照,与
Rime 一致。空输入例外——没有字符可删时退格只能是撤销,否则会退回
#35 的整句丢弃。
- 恢复改为前插:被选词消耗的拼音插回未删除的后缀之前,光标随恢复
长度平移("输入变长、光标不动"),不再整串覆盖 raw 而丢掉后缀。
- 撤销后重新高亮:选词快照记下所选候选的绝对位置,撤销重建候选页
后把高亮放回同一项(一次性消费),下次按键可直接改选,不用再找。
- 客户端契约拆成两层:DLL 只凭造词镜像(word_for_creating_word)
挂起等待回复,看不到 Server 侧的快照与编辑锁,因此新增
HasRetreatBackspaceShape 描述客户端可见形态,Server 对形态内每个
退格都必须答帧(撤销、锁定删除或空历史空操作),否则挂起白烧满
50ms 超时;ShouldRetreatCreatingWordSelection 只在真能改写状态时
为真。
- 撤销改为纯状态操作,引擎序列、候选重建与光标前缀由尾部统一执行
一次,替换原先每次撤销都额外跑一遍完整候选查询的 helper。
- DLL 侧:已消耗请求槽位的回复(或光标 0 处的 Normal 应答)之后
丢弃 request id,让 worker 回退本地读取,而不是为已消失的槽位
再烧一个 50ms 超时。
Signed-off-by: Jia Sui <jsfaint@gmail.com>
|
2026-09-24 20:52:12 +08:00 |
|
Jia Sui
|
eac9fa72b5
|
feat(server): 固定排位候选词显示可配置的徽标
固定排位徽标此前硬编码为 📌,嫌吵的用户既关不掉也换不了。
新增两个配置项:candidate_fixed_badge 总开关与 candidate_fixed_badge_style 样式枚举(paperclip/pushpin/dot),设置页外观分组提供开关与下拉,改动后立即刷新候选页。
配置存枚举名而非字面 emoji,TOML 与日志里不出现图形字符;展示层白名单与配置层同宽,手改 TOML 写错只会退成无徽标。默认样式改为 📎,比实心红钉安静。
|
2026-09-24 14:32:10 +08:00 |
|
Jia Sui
|
3821f3ede8
|
fix(server): 补回合并时丢失的测试用例花括号
|
2026-09-24 09:04:27 +08:00 |
|
Jia Sui
|
5b0be4e71d
|
Merge branch 'develop' into feat/caret-driven-resegmentation
|
2026-09-24 08:45:54 +08:00 |
|
Jia Sui
|
54c41d34b2
|
fix(engine): reset_cache 连带失效 caret 前缀候选缓存
|
2026-09-23 21:32:59 +08:00 |
|
AaGMix
|
902989abc4
|
feat(server): 显示光标输入状态提示
|
2026-09-22 23:52:28 +08:00 |
|
Jia Sui
|
4f26f35ac3
|
fix(server): 光标移回串尾时从引擎重建候选页
RefreshCandidatePageUi 只重排旧 candidate_ui.items、不重读引擎,而结算读的
正是 items。回串尾时引擎已整串重算,页面却停在旧前缀候选:选词只消费前缀,
ni'hao'ya 回串尾得「你好ya」而非「你好呀」。提取
ResolveCaretArrowCandidatePublish(前缀空收起;门控启用含回串尾一律
PrepareCandidateList 重读并复活窗口;未启用维持只刷页面,R7/AC8 零差异)。
|
2026-09-22 18:49:37 +08:00 |
|
Jia Sui
|
fceb0692cb
|
fix(server): 造词帧的 caret 字段只发给协商过组合恢复的客户端
未协商的旧版 DLL 会把第 2 个制表符后的整个尾部当 display_preedit,
追加的 caret 字段会污染一次 inline preedit。提取
ShouldCreateWordFrameCarryCaret 谓词(协商 && caret<剩余串),
未协商时帧输出与旧 Server 字节一致;CompositionRestored 帧本就只在
协商路径产生,无需改。
|
2026-09-22 16:39:50 +08:00 |
|
Jia Sui
|
096c0ccb10
|
feat(server): 前缀重算接线与仅前缀选词结算
编辑键后按门控(协商 composition-restore 且非 UILess、非英文/特殊模式)
把 caret 喂给会话做前缀重解;caret 回到串尾显式清 caret_,串尾行为
与现状一致。前缀选词复用 advance_composition_after_selection 的头部
消费语义:剩余 raw 即后缀、caret 归 0,NeedToCreateWord 帧带契约既有的
可选 caret 字段(协议零新增);学习/调频/AI 上下文走既有链路。
空前缀时收起 Server 自有候选窗并守卫假候选兜底与 ShowCandidate。
门控谓词与边界补真值表单测;回归测试钉住「移回串尾恢复整串解码」。
|
2026-09-22 16:09:05 +08:00 |
|
Jia Sui
|
1cfba58eba
|
feat(engine): 光标驱动的拼音前缀解码与候选重算
caret 从纯显示状态升级为查询管线输入:set_caret 量化到最后一个完整
音节单元边界(只消费 segment_raw_boundaries,#187 契约),前缀候选
独立解码、preedit 保持全串拼音风格;纯移动光标同样重算候选。
IInputScheme::set_raw_input 提升为接口方法以支持不动现场组合的
独立前缀查询。runtime_isolation 两处按 R3/R4 新语义改写(caret 在
音节中间 floor 到空前缀、候选为空是预期状态);新增 caret 前缀
会话单测(engine/tests + server CI 目标,隔离词库夹具,覆盖
全拼 floor、双拼贪心配对、五笔无单元模型、boundary=0 空候选)。
|
2026-09-22 14:56:22 +08:00 |
|
Jia Sui
|
c6632e8d85
|
feat(engine): 五笔前缀逐键提示与调频接线
- 码表查询从精确匹配改为前缀匹配:精确等长行优先(保住一级/二级简码),
前缀行按权重降序跟随,LIMIT 50 截断(引擎层不读 server 配置的分层边界)
- WubiCandidateProvider 注入 RuntimePaths:接通 update_weight / delete_by_key
到 user_dictionary journal;新增 bump_wubi_weight(同码组 MAX+1、主库更新
与 journal upsert 同事务,行缺失不造行)
- create_word / cache_dynamic_candidate 补「为何 no-op」注释(后续任务范畴)
- 两个 toml 的 wubi_schema 注释与实现对齐:当前仅支持 wubi86
- server 测试:前缀语义、LIMIT 截断、上界构造、调频+journal 回放、删词墓碑
|
2026-09-22 09:23:25 +08:00 |
|
Jia Sui
|
536d0a95ef
|
feat(windows): 五笔四码后继续输入不丢字(顶字)
四码(不论是否唯一)之后继续敲字母时,先上屏该码的首选候选,再把这个字母
保留为下一次组合的开头。根因是两侧对「组合里有几个字母」认知不一致:TSF 的
本地按键缓冲跟着敲键涨(raw 预编辑样式下 preedit 就是它),服务端的五笔 raw
被引擎裁在四码;空格提交时组合文本被整段替换成候选并清掉本地缓冲,多敲的
字母随之丢失。
- 新增 worker 消息 CommitCandidateAndContinue(27),payload "<consumed>\t<text>":
TSF 按服务端给出的消费数裁**自己**的组合缓冲,而不是套用服务端按旧视图算好
的剩余串——快打时用户已多敲的字母因此不会被覆盖
- TSF 处理序列全部复用既有原语:_InsertTextToComposition + _HandleCompleteCommitFirst
真提交,_StartComposition + AddVirtualKey + _HandleCompositionInputWorker(NO_REQUEST_ID)
重建余下字母的组合;无组合时退化为直写
- 服务端顶字不看自动上屏开关:开关只决定唯一码要不要多敲一键才上屏,
不决定丢不丢输入;自动上屏那条路也改用同一消息,顺带收掉快打竞态
- 引擎新增 wubi_four_code_is_complete(完整四码、码表作答、不要求唯一;
空码不算——没有候选可提交时不触发顶字)
已知限制:光标在组合中间时的改码行为维持现状;空码继续敲字母仍会被忽略。
|
2026-09-22 09:23:25 +08:00 |
|
Jia Sui
|
5d1a9a6cc2
|
feat(server): 五笔四码唯一自动上屏
五笔敲满四码且码表只答一个候选时不再需要空格:第四个字母落下即上屏,下一个
字母直接开新组合。行为无条件生效,不设配置开关:业界默认开启(微软五笔、
搜狗五笔、极点、Rime 主流五笔方案),唯一候选没有第二选择,确认空格是纯冗余;
而一条开关要牵动配置五处同步,换不来可形成预期的行为差异,不值。
- engine 只报告事实(wubi_unique_four_code:完整四码、码表作答、候选恰好一个),
是否上屏由 Server 决定;判定收在 input_key_policy.h 的纯函数里,随 portable
目标进 ASan/UBSan;组词(creating_word)激活时组合保持打开,唯一四码不会把
正在拼的词截断上屏
- 提交复用 ProcessSelectionKey,与空格上屏同一套记账;但只能走 worker 管道推送
CommitCandidate:字母键在默认 raw 预编辑样式下不读请求-回复管道,回一帧 Normal
既不会上屏,还会在 TSF 侧留下永不释放的回复帧,而 Server 已按 Normal 清掉组合
- 上屏前按当前组合同步重建候选页:候选页异步发布,提交路径读的 ui.items /
page_words 否则可能还停在第三码那一拍
- 非唯一四码不做顶字上屏:一个按键只能回一条管道消息,真顶字需要扩 IPC 协议
|
2026-09-22 09:23:24 +08:00 |
|
Jia Sui
|
566ff8b832
|
feat(engine): 新增加加辅助码(拼音加加),第六套辅助码方案
按笔顺拆前两个部件、各取读音首字母(妈 = 女 n + 马 m = nm),部件用加加俗称读音(亻=d、
扌=t、纟=j、忄=s、攵=f、夂=z、刂=l、彳=s);138 个独体/部首字取「本字声母 + 起笔」。码表
7968 条,覆盖《通用规范汉字表》7846 字,比小鹤少 422 个扩展区生僻字——缺码会让含这些字的
词组拿不到辅助码,是已知取舍。
编码对齐拼音加加 5.x 安装包内的 fzm.bin(双拼键位已换算为声母):6487 字取到两码,6349 字
逐字一致,138 字保留本表规则,覆盖外的 800 字按同一规则重建。来源与非官方性质写在 NOTICE.md
与随包的 THIRD_PARTY_NOTICES.txt。
- engine/contracts/assets 注册 helpcode_jiajia,assets.h 重新生成(5 → 6)
- 设置页两个下拉显示名「加加」,两份配置模板注释同步
- 两侧测试补支持列表;README / README.en.md / server/AGENTS.md 改「六套」
默认值不变(双拼 lantian、全拼 ziranma)。
|
2026-09-20 22:00:34 +08:00 |
|
fanlusky
|
94df9a7917
|
fix(stats): 修复统计功能审查发现的 8 个问题
- Server 退出时唤醒统计管道监听线程,避免从未有客户端连接时 join 永久阻塞
- DLL 发送批次时对 ERROR_FILE_NOT_FOUND 重试,覆盖服务端重建管道的窗口
- 新增 StatisticsEnabledChanged(26) 推送开关,DLL 侧在采集入口直接短路:
关闭时不读 range、不分类、不开管道
- stats_meta 的 schema_version 写入正确的键名(此前键与值都是 "1")
- dropped_count 仅在实际发送的批次中消费,空批次不再吞掉丢弃计数
- _CaptureCompositionStats 改为遍历 composition range 的克隆,避免移动宿主的活动区间
- FanyImeStatsEvent 显式声明 reserved 尾部,不再把未初始化的填充字节写入管道
- 透传统计去重标记在抑制一次后即消费,避免同一 tick 内的连击被漏计
|
2026-09-20 20:34:50 +08:00 |
|
Jia Sui
|
ffcd1aee28
|
feat: 输入统计(DLL 采集、Server 聚合存储与设置页展示)
- windows: 三上屏出口 + OnTestKeyDown 直通字符采集,修复半角数字恒零
- engine: 统计 Web 消息契约(statsRequest/statsResponse)
- server: 统计管道接收、聚合、SQLite 存储、保留策略与每日自动清理
- ui-html: 设置页「统计」模块(卡片、热图、明细、开关、清理)
- docs: PRIVACY / README 改为内置可选统计
|
2026-09-20 19:08:16 +08:00 |
|
fanlusky
|
1ea01d5e0a
|
fix(quanpin): 备选切分保护位只拉首页外的候选,不覆盖调频名次
保护位原来对任何排在 index 1 之后的备选切分都生效,于是调频刚把 吉安 排到
自然第 5 位,它就因为成了 ji'an 组里权重最高的词、过了 1/100 门槛而被重新
钉回第 2 位。用户选一次就跳到第二,之后再怎么调也只能停在第二——固定槽位把
调频写进权重的结果整个覆盖掉了。
改成只对自然排序已经掉出首页(默认 page_size 6)的备选切分生效。西安 在出货
词库里的自然位置是 16,仍旧提升到第 2 位;调频过的 吉安 留在它自己挣来的位置
上,之后按 promote 一步步往上走。
夹具同步补候选:engine 与 server 的 xian 夹具原来只有三条主候选,备选切分自然
就在首页内,测不到提升这条路径。
|
2026-09-19 22:42:29 +08:00 |
|
fanlusky
|
e32eeadec9
|
fix(engine): 调频比较集跨键合并,按权重而非显示顺序取基准
打 jian 时列表里混着另一种切分 ji'an 的词。吉安 在出货词库里权重是 1,压在同组的
积案(9420)/几案(5999) 底下,而 adjust_candidate_ranking 按音节数圈比较集,于是
吉安 只跟 ji'an 这一组比,可学权重封顶一万出头,永远追不上 见 的 3460998——用户
选多少次,这个候选都纹丝不动。
音节数这个尺度是 #400 加的,为的是挡住另一个 bug:显示列表里备选切分词会被保护位
顶到最前面,中点数学却假定列表按权重降序,于是拿 西鄂(w=6) 当基准把 写 写成 506。
根子在「显示顺序 != 权重顺序」,按音节数滤掉跨表行只是绕开了症状,代价是把每个候选
关进自己的组里。
改成在比较集上直接按权重降序排一次,不变式在源头恢复:任何行都不可能坐在高于自身
权重的位置上充当基准,西鄂 那条路被堵死;同时比较集不再按音节数切开,吉安 的基准
取到整个列表最重的 见,置顶一次即越过,promote 模式连选五次一路走到首位。写入仍只
落在 entry_key 的行上,#36 的护栏不变。
test_user_dictionary_journal 新增两条回归(跨键置顶、连续 promote 收敛到首位),
#400 的两条断言原样保留并仍然通过。
|
2026-09-19 22:14:57 +08:00 |
|
fanlusky
|
663f72301d
|
fix(server): 独立上屏的整句候选也落库
上一个提交改的是 InputSession::commit(),而 Windows 前端根本不走那条路:选词由
ProcessSelectionKey 自己驱动。那里 CandidateSource::Generated 走提前返回,落库的
闸门 ShouldStoreEarlyReturnPhrase 要求造词前缀非空,也就是只认"先选前缀、再用整句
收尾"这一种形状;用户直接选一条完整的整句时 creating_word 没激活,条件为假,什么
也不做就返回了。Google 解码器那条整句(Fallback)不在提前返回的名单里,走普通
路径,但同样没有行可以调频。
因此拆出 ShouldStoreStandaloneSentence:整句候选独立上屏时就该落库,带 7 音节
上限(对标 quanpin::WordLatticeOptions::max_phrase_syllables)。接在造词前缀
后面的那一支由用户一段段选出来,维持原样不设限。
顺带修掉同一处的读音拼接:前缀为空时不能带上那个分隔符,'na'yi'tiao 这种前导
撇号会让整条读音作废。
原先有一条测试把这个缺陷钉住了(断言没有前缀就不落库),按新的意图翻过来。
|
2026-09-19 13:48:26 +08:00 |
|
fanlusky
|
ccbaa3a631
|
fix(engine): 送进 Google 解码器前把 ü 换成 nue/lue/ju 写法
小鹤双拼 ntdddswu 出的是「女蛾黛动物」而不是「虐待动物」:nt 转成全拼后
是 nve(shuangpin_pinyin_set 把 nue 抹掉了,词库与词格那一侧统一用 v 形),
而本地 googlepinyinime-rev 的拼写表和 inputtools 云输入都不认 nve/lve/jv,
只有 nue/lue/ju/jue,也不做 v→ü 的转写。带分隔符的串照样送过去了,解码器
却把 nve 重新切成 nv + e,于是有了「女蛾」。
加一个 quanpin::to_google_spelling,按音节整体改写 nve→nue、lve→lue、
jv→ju 这一类拼写,只在两个 Google 解码器的边界上用:云输入的 query_text、
双拼与全拼的整句回退。词库查询和词格用的仍然是 canonical_syllable 那一侧的
v 形写法,没有动。nv'er、lv'se 这些 Google 也认的写法原样通过。
|
2026-09-19 12:29:01 +08:00 |
|
Lu Fan(SonnyCalcr)
|
e1d53dd8f0
|
Merge pull request #426 from ouchihao/codex/fix-299-double-helpcode-cache
fix(engine): isolate ordered double-helpcode query caches
|
2026-09-18 21:34:50 +08:00 |
|
Ouchihao
|
0925937bfe
|
fix(engine): isolate ordered double-helpcode query caches
|
2026-09-18 20:43:17 +08:00 |
|
Ouchihao
|
5e641fb5a0
|
fix(engine): accept yo as a complete shuangpin syllable
|
2026-09-18 20:35:14 +08:00 |
|
Lu Fan(SonnyCalcr)
|
4dea1cd12f
|
Merge pull request #420 from metasequoiaime/feat/ctrl-arrows-segment-caret
feat(input): 组词内按分词的光标与删除(#35 / #187 / #347)
|
2026-09-18 12:35:52 +08:00 |
|
Jia Sui
|
809f700998
|
test(server): 覆盖微软双拼 ';' 尾键的分段边界
- nihkb;(你好病 = ni|hk|b;)断言 {0,2,4,6}:';' 归属它开始的音节
- nihcb; 断言 {0,2,3,5,6}:贪心最长配对是契约,'cb' 是有效微软编码
(cou),按段删除跟随 preedit 显示的同一条切分
Refs #187
|
2026-09-18 08:59:19 +08:00 |
|
Jia Sui
|
2576986881
|
feat(windows): Ctrl+左右键按分词移动光标
组词中 Ctrl+Left/Right 按一个字的拼音(音节段)移动光标,
与 Ctrl+Backspace 的删除粒度共用同一套引擎分段边界。
- 闸门归纳为 SegmentEditFunction(code, modifiers):Ctrl-only + 组词存在,
VK_BACK/LEFT/RIGHT 分别映射到 segment 函数;三处判据与延迟投影共用
- 新增 FUNCTION_MOVE_LEFT_SEGMENT / FUNCTION_MOVE_RIGHT_SEGMENT,
编辑会话只按 CompositionRestored 的 caret 移动本地光标,不重建 buffer
- Server 用 segment_raw_boundaries() 取前/后边界,能力位与非 UiLess 时回包;
非拼音/本地模式/UiLess/旧端仍按单字符 ±1 回退
Refs #187
|
2026-09-17 23:36:34 +08:00 |
|
Jia Sui
|
b1ec3ab1e0
|
feat(server): Ctrl+Backspace 按分词删除前置拼音(#187)
组词中 Ctrl+Backspace 删除光标前的一个分词单元:剩余拼音非空时删
最后一个字的拼音(音节段),剩余拼音为空时删除最后一段已选分词。
engine:
- InputSession::segment_raw_boundaries() 作为按段删除/移动的唯一分段来源:
全拼把纠错显示串的分隔符映回 raw 下标,双拼与 pinyin_segmentation 共用贪心谓词,
2 字辅助码自成一段;非拼音方案与本地模式返回空表示无单位模型
server:
- IsSegmentBackspaceKey(Ctrl-only)、整段丢弃 / 边界截断 / 单字符回退三分支,
截断点残留分隔符清理
- 复用 CompositionRestored 回包(含 caret),仅协商能力位且非 UiLess 时发送;
新出现「只剩已选汉字、无拼音」的中间态
windows:
- 两处 Ctrl 闸门对组词中的 Ctrl+VK_BACK 放行,新增 FUNCTION_BACKSPACE_SEGMENT
与 _HandleCompositionBackspaceSegment(读回包重建,失败回退单字符)
- 空 raw 状态渲染、终态清理 word_for_creating_word,延迟投影失效处理
Closes #187
|
2026-09-17 23:01:31 +08:00 |
|
fanlusky
|
bc37ac9a9e
|
feat(installer): 安装时可自定义数据目录位置
词库、用户配置和皮肤此前固定在 %LOCALAPPDATA%\metasequoiaime,整包几百 MB
全压在 C 盘。安装向导新增一页,允许把这部分数据放到任意盘。
程序本体仍固定装在 Program Files:MetasequoiaImeServer.exe 带 uiAccess=true,
只有在受信任目录里这个标志才生效。
运行期的权威来源是 HKLM\Software\Metasequoia\MetasequoiaIME 的 DataDir。
Server、TSF DLL 和引擎三个组件各自按同一顺序解析:METASEQUOIA_IME_DATA_DIR
环境变量 -> 该注册表值 -> %LOCALAPPDATA%\metasequoiaime。三处各持一份实现,
因为组件边界规则禁止 windows/ 与 server/ 互相 include。32 位 TSF DLL 用
KEY_WOW64_64KEY 读,避开 WOW64 重定向。
安全相关的两个约束:
- 安装器在数据目录里写 .metasequoiaime-data 标记。覆盖安装的清理和卸载的整目录
删除只在看到这个标记(或目录就是历史默认位置)时才执行——用户可能把数据目录
指到一个本来就有自己文件的文件夹。
- 选择页与 /DATADIR= 走同一套校验:拒绝相对路径、盘符根目录、不存在的盘、程序
目录内部,以及 Windows/Program Files/各 AppData/用户主目录本身。
升级时改了位置,PrepareToInstall 在停掉输入法进程后用 robocopy /MOVE 搬运用户
拥有的数据(msime_user.db 及其 WAL/SHM/journal、config.toml、config.base.toml、
skins\),词库和前端资源由本次安装重新写入。跨卷移动目录 MoveFile 会失败,所以用
robocopy。用户词库搬运失败会中止安装:否则新版本对着空的 msime_user.db 启动,
ssPostInstall 的回放只记一句 skipped,用户的自造词就静悄悄没了。
测试侧新增 METASEQUOIA_IME_CONFIG_DIR,只改 config.toml 的位置而不搬词库。原先
靠重定向 LOCALAPPDATA 做隔离的办法在装了正式版的机器上已不再成立——注册表值
的优先级高于它。
|
2026-09-17 22:49:19 +08:00 |
|
fanlusky
|
a898c3b11c
|
feat(candidate): Ctrl+Enter 上屏候选右侧的译文
候选框开着且高亮候选带译文时,Ctrl+Enter 上屏那条译文而不是候选本身。
译文只有一条就直接上屏;有多条时把候选框整个换成这几条译义,空格/数字键
照常选一条上屏。
TSF 端原先在分类器最前面把所有 Ctrl 组合都还给应用,Ctrl+Enter 到不了
Server。现在在那条拦截之前先判一次:候选框开着才认,否则组合仍归应用。
认下来的键走既有的 FUNCTION_SERVER_CANDIDATE_KEY 通道,由 Server 决定
是上屏(CommitExactText)还是只开一个新候选页(NavigationIgnored)。
译文页复用同一份 candidate_ui.items,因此云/AI/英文/emoji/颜文字这些异步
回调和候选扩展都要在这个子模式下让路,否则会把译文冲掉。输入串全程没动,
退出时把存下来的 items 和页码原样放回去即可。译文页上的空格/数字键只回
Normal / OutofRange——SELECT_BY_NUMBER 的 TSF 路径只认这两种。
|
2026-09-17 20:21:56 +08:00 |
|
Jia Sui
|
e64195731e
|
feat(server): Backspace 删空剩余拼音后退选已选分词(#35)
已选分词是造词中间态:删空剩余拼音时不再整条 composition 丢弃,
而是退选最后一段并恢复其原始拼音与候选。
engine:
- SelectionTransition 暴露本次选择消费掉的原始输入(保留大小写与双拼按键)
engine/contracts:
- 新增回复类型 CompositionRestored=14 与能力位 CompositionRestore;
MaxKnown 同步抬高,Server 按协商能力位门控发送,旧 DLL 不会收到未知帧
server:
- 造词中间态压 (consumed_raw, previous_creating_word) 快照,边界 Backspace 弹栈退选
- 任何清空 raw 的路径同时清 creating_word 与快照历史,避免跨 composition 泄漏
windows:
- _HandleCompositionBackspace 提示退选时读回包重建 keystroke buffer / word / 预编辑 / 光标
- NeedToCreateWord 与 CompositionRestored 共用载荷解析;旧 Server 与 UiLess 回退旧行为
Closes #35
|
2026-09-17 18:45:01 +08:00 |
|
Jia Sui
|
d4a0796400
|
feat(windows): 重构智能标点
原「智能标点」是小狼毫 digit_separators 的语义:字母或数字后的 , . : 直出英文,
2 秒内再按同一标点转回中文——后半段靠 SendInput 往应用里补一个 Backspace 再打
字符,落点全靠光标当时在哪。这次换掉主路径:输入中文标点后按空格,用 TSF 编辑
会话就地把它改写成对应英文(。,!?;:、 → . , ! ? ; : /),2 秒内再按同一标点
键撤回中文;原来的直出降为子开关,整个家族默认全关、改为显式开启。
- 按键与会话:转换/撤回是本地文档改写,不进 IPC、不进延迟队列;判据落在
_IsKeyEaten,OnTestKeyDown 的预判与 _DispatchKeyDown 的第二遍预判共用同一份,
声称吃掉的键不交还宿主。改写走编辑会话,请求不上时另起一个最小会话把键本身
的字符写回去,避免已吃掉的键静默丢失。
- 状态:原来散落的九个成员收成一个 SmartPunctuationAction(None / ChineseCommitted
/ AsciiConverted),带焦点令牌、前台窗口和 beforeChar 指纹;清理以分类结果为准,
丢焦点与上下文栈变化一并清掉。
- 转换范围:。,!?;:、 与单独出现的 “”‘’【】《》() 都是映射表里的单字符
条目,路径完全一致;成对补全产生的一对(autoClosedPair)不 arm,按空格只落空格,
不会留下 <》 这类孤儿。
- 开关:总开关 + 中文标点后按空格转换 + 同键转回中文 + 数字后直出 + 字母后直出,
五键全部默认关;直出拆成数字/字母两个独立子开关(IPC 24 收窄为数字、25 追加为
字母),TSF 按前字符类型分别查键。
- 命名:子开关「独立符号」改名「中文标点后按空格转换」,配置键
smart_punctuation_standalone → smart_punctuation_space_convert,IPC 22 只改常量名
(SmartPunctuationSpaceConvertChanged),编号与语义不变、新旧测试包互通;设置页
文案同步重写,五处配置链路与死宿主一并跟改。
- 兼容:旧键未出厂、不迁移(升级合并按模板缺键丢弃、取默认 false);编号 23 随
「按空格整体转换一对」的设计退役并留空不复用。
- 验证:TSF x86/x64 与 Server Release 编译、Server 335 单测、windows_ipc_contract
x64/x86、设置页 build + 27 测试、clang-format 18.1.8 全绿。真机未跑。
Signed-off-by: Jia Sui <jsfaint@gmail.com>
|
2026-09-17 09:29:57 +08:00 |
|
Jia Sui
|
b149a500b0
|
fix(server): 按数字选词偶发选错——以屏幕上的候选列表为准
按数字 2 想选第 2 个词,有时上屏的却是第 1 个:后台按手里的最新候选列表
数序号结算,而用户看到的是窗口还没重画完的旧画面;调频(一次置顶)每次
选完都改顺序,两者的时间差就会撞出「看 2 选 1」。关掉调频或固定候选位置
后顺序不再变化,问题消失——与报障观察一致。
修法:候选列表带版本号,窗口画完后回报画到第几版;按数字或空格时如果
窗口还没画到最新,后台最多等 30 毫秒再按用户看到的那一版结算。等待上限
是硬约束:必须留在 TSF 50ms 回复窗口内,超时会触发管道重连与按键重放。
窗口不可见、宿主自绘(UILess)、鼠标点击不等待。
- 发布候选页时递增版本号,快照携带该值;
- D2D 绘制完成、WebView2 DOM 回调(含 dedup 分支)回写实际渲染快照的版本号,
异步回调捕获快照值而非回读实时值;
- 单测覆盖等待判定与 30ms 常量;阴性验证:等待失效时「落后 → 等待」用例
失败,恢复后 333 项测试全绿;
- 真机验证(调频 pin + 快速同拼音 + 连按数字)待跟进。
|
2026-09-15 11:02:39 +08:00 |
|
fanlusky
|
5a5dea6b7e
|
feat(japanese): 日语模式禁用 -/= 翻页并用 - 打长音符
日语模式下把 - 让给长音符(ー)输入,同时禁掉 -/= 的候选翻页:
- 罗马字方案接受 VK_OEM_MINUS,set_raw_input 不再把 - 过滤掉;
罗马字表里本来就有 - 到 ー 的条目,只缺按键准入。
- n 后面接 - 时按辅音处理,让前面的 n 收成 ん,避免 n- 卡在 pending。
- 空编码时单独按 - 也起头组合,候选框第一项是 ー、第二项是普通 -。
- 组词流程同样适用:判据只看日语模式加组合状态,不再走翻页和词转字。
- -/= 在日语模式下不翻页,_ = + 这些没有假名写法的字符退回标点上屏,
并把 = _ 补进服务端的标点集合,和 TSF 侧保持一致,否则会等不到回包。
TSF 侧的影子分类、IsVirtualKeyNeed(ForFreshComposition) 和服务端按键策略
三处同时改,统一以配置项 input_mode == japanese 为判据(R 模式不在范围内,
TSF 侧看不到临时会话,用会话类型判断会让两侧分类打架)。
|
2026-09-15 01:57:50 +08:00 |
|
Jia Sui
|
9bbd86b633
|
fix(engine): 调频权重基准限定同音节尺度
置顶/提升把一个候选的权重算成「显示列表首位权重 + 500」。显示列表会混入
备选切分词和不同切分的候选,它们的权重是另一套尺度(单字表是语料计数、
多字表是词组词权),于是基准取错就把候选写成垃圾值:用户机器上 xi'e 的西鄂
(w=6) 坐在 xie 列表首位时,置顶写会写出 506(= 6+500),低于些/血/谢,反而
被埋到三十名开外;将也以同样方式被写成 24240(= 激昂 23740+500)。
- 候选的 rank 与权重基准改在同音节尺度内计算:同音节数的表互相可比(九宫格
数字上下混多个单字表、简拼上下文混多个 key 都保留原语义),跨音节数不再
互相充当中点基准。选中词已是同尺度首位时不再写权重,避免被跨尺度反降。
- 写入仍按 entry_key 走,rebalance 不会落到别的 key 的行上(#36 的护栏不变)。
test_user_dictionary_journal 增加回归:混表列表里置顶写必须以些为基准,
且不再把些本身写成 506。
|
2026-09-14 20:14:28 +08:00 |
|
Jia Sui
|
809c62995b
|
fix(engine): 全拼模糊音下备选切分抢位
模糊音开启时 QuanpinDictionary::query 对整个候选列表按 pinyin 字符串长度降序
排序(本意是压住模糊/前缀候选)。备选切分的 canonical 键带撇号且音节更多
(xi'e / ji'ang / you'di'an),字符串比主切分键(xie / jiang / you'dian)更长,
于是权重只有 6 的西鄂、23740 的激昂从保护位第 2 跳到第 1,顶掉 xie/jiang 的
首候选;xie 连模糊变体都没有,仅因排序无条件执行就被波及。
- 排序键改为候选覆盖的输入字母数(去掉撇号),与用户实际敲的长度对齐:备选
切分键与主键同层交给词频,模糊/前缀候选仍排在整串匹配之后。
- 备选切分保护位加词频门槛:备选最佳词权重达到主切分首位 1/100 以上才提升。
真歧义保留(xian -> 西安 55K 对 先 1.66M),罕见重码被拒(xie -> 西鄂 6、
jiang -> 激昂 23.7K、youdian -> 尤迪安 7)。
test_fuzzy_pinyin 补齐四个场景回归断言(夹具权重取自出货词库);
test_quanpin_scheme 的保护位用例移到新契约:夹具备选词权重 1 -> 100,
让它留在门槛内继续覆盖「真歧义保留保护位」。
|
2026-09-14 20:14:28 +08:00 |
|
Evan
|
b10b8184d3
|
fix(ipc): reject malformed voice control fields (#398)
|
2026-09-14 10:40:29 +00:00 |
|
fanlusky
|
f05507ade8
|
fix(config): 升级时保住用户已填的 API token 等凭证
用户反映更新后 API token 被清空、需要重填。根因不在安装器(它早已用
onlyifdoesntexist 且清理时保留 config.toml / config.base.toml,从不覆盖用户
配置),而在 Server 启动时的合并路径 SyncConfigWithInstalledTemplate:当
config.toml 存在、非空但解析不过(上次写入被打断留下半截文件,或 token 里混入
破坏 TOML 的字符)时,旧逻辑直接拿出厂模板覆盖,等于把用户全部配置——尤其是
凭证——清零。
配置升级本质上已经是「逐条把用户值重放到新模板」(MergeTomlIntoTemplate),因此
不另建配置操作日志,而是把「逐条重放」精准用在最难重填的凭证上,并堵掉硬重置:
- ReapplyRealCredentials:合并结束时把用户填过真值的凭证逐条重放回去,有键改值、
无键则在其分节插入。覆盖 token / api_key / apikey / secret_id / secret_key /
app_id / asr_app_key 及所有 per-provider 槽位(token_* / asr_token[_*] /
polish_token[_*]),真值判定复用 VoiceInput::IsPlaceholderToken。这样模板漂移
漏掉某个 token 键也不会静默丢失;占位符不粘住,新默认值照常生效。
- SyncConfigWithInstalledTemplate 的 unparseable 分支:先把坏文件备份成
config.toml.corrupt-<时间戳> 留证,再逐行抢救可识别赋值重放到新模板;即便整体
抢救结果仍解析不过,也只回退到「模板 + 重放凭证」,而非纯模板。
新增回归测试:模板漏键时真 token 被写回、未填的占位符跟随新默认、损坏配置能捞回
真 token,以及读安装器真正分发的 config.default.toml 逐分节确认所有凭证键挺过升级。
|
2026-09-14 03:15:16 +08:00 |
|