Skip to content

Commit 4f855c7

Browse files
committed
fix(dict): sortMode 跟随方案,ProtectTopN 仅看系统码表
CompositeDict 不再持有 sortMode 字段,改由调用方通过 SearchOptions.SortMode 显式传入。删除 DictManager.SetSortMode / CompositeDict.SetSortMode / GetSortMode, 从根本上避免多方案共享同一 dm 时 sortMode 残留串味—— 之前在 wubi86 (frequency) + tigress (natural) 共存场景下, 切到 tigress 再切回 wubi86 时,composite 内残留的 natural 会让临时词 (NaturalOrder=0)排到系统高频词之前,配合 ProtectTopN=1 把首位钉死。 ProtectTopN 改走新增的 CompositeDict.SearchSystemOnly, 只看 LayerTypeCell + LayerTypeSystem,跳过 user/temp/phrase—— "锁定码表原始顺序"的本意是保护用户对系统码表的肌肉记忆, 不该被临时词 / 用户造词的高排位污染。 - composite.go: 新增 SearchOptions / SearchSystemOnly;Search/SearchPrefix 签名改为接 SearchOptions - manager.go: 删除 SetSortMode 与未使用的 Search/SearchPrefix wrapper - codetable.go: ConvertEx 各 composite 调用显式传 SortMode;ProtectTopN 改走 SearchSystemOnly,测试场景无 dm 时降级到 e.codeTable.Lookup - factory.go / manager_config.go: 删除 dm.SetSortMode 调用 - AGENTS.md: 同步描述
1 parent 49821c3 commit 4f855c7

9 files changed

Lines changed: 102 additions & 70 deletions

File tree

wind_input/internal/candidate/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
- 排序规则(`Better`):权重降序 → 文本升序 → 编码升序 → 消耗长度降序
2020
- `BetterNatural`:自然顺序排序(不调整词频权重,按码表原始顺序为主)
2121
- `CandidateSource` 枚举: `None / Codetable / Pinyin / English / Phrase`; `SourcePhrase` (2026-05-16 引入) 标记 PhraseLayer 候选, 在混输引擎中走 `mixed.PhraseWeightBoost` 独立 tier (1M), 不与码表词 (10M) 抢首位
22-
- `CandidateSortMode` 常量由 Schema 的 `candidate_sort_mode` 字段设置,传递给 `CompositeDict.SetSortMode`
22+
- `CandidateSortMode` 常量由 Schema 的 `candidate_sort_mode` 字段设置;引擎在每次 `CompositeDict.Search/SearchPrefix` 调用时通过 `dict.SearchOptions.SortMode` 显式传入,**不再**经由 `CompositeDict.SetSortMode` 全局缓存(避免多方案共享 dm 时跨方案污染)
2323
- `IsCommon` 字段由 `dict.InitCommonCharsWithPath` 初始化的通用字符表决定
2424
- `IsCommand` 标识 uuid/date/time 等命令候选,UI 渲染时可能有特殊样式
2525
- `IsGroupMember` (2026-05-17 引入) 标识 `$AA` 字符组、`$SS` 字符串数组**展开后**的子项候选; 右键菜单 pin/delete/前移/后移/置顶/恢复默认 全 disable —— 顺序由源 marker (`$AA(chars)` / `$SS(elem...)`) 完整定义, 走"编辑短语"路径修改 yaml, 不允许 Shadow 双轨漂移。导航候选 (`IsGroup=true`) 本身****标 (组入口不展开), 普通短语 / 用户词 / 系统词 / 单 `$CC` 命令亦不标

wind_input/internal/dict/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
|------|-------------|
2020
| `manager.go` | `DictManager`:管理 `CompositeDict``ShadowLayer``UserDict``PhraseLayer` 的生命周期;`RegisterSystemLayer`/`UnregisterSystemLayer` 供引擎热插拔词库层;`SwitchSchema` 切换方案时切换用户数据文件 |
2121
| `layer.go` | `DictLayer` 接口(`Name`/`Type`/`Search`),`LayerType` 常量,`ShadowProvider` 接口 |
22-
| `composite.go` | `CompositeDict`:按 LayerType 优先级聚合多层查询结果,持有 `ShadowProvider` 在搜索后应用 pin/delete 规则;`SetSortMode` 控制候选排序模式`HasLongerCode(input)` 短路检查任一层是否存在 `code != input` 且以 input 为前缀的条目(供 codetable 引擎全码自动顶屏判定) |
22+
| `composite.go` | `CompositeDict`:按 LayerType 优先级聚合多层查询结果,持有 `ShadowProvider` 在搜索后应用 pin/delete 规则;`Search`/`SearchPrefix` 接受 `SearchOptions{Limit, SortMode}`,排序模式不再由 dict 层持有,由引擎每次调用时传入(避免跨方案污染);`SearchSystemOnly` 仅查 `LayerTypeCell+LayerTypeSystem`,供 ProtectTopN 等"只看系统码表原始顺序"的语义使用`HasLongerCode(input)` 短路检查任一层是否存在 `code != input` 且以 input 为前缀的条目(供 codetable 引擎全码自动顶屏判定) |
2323
| `pinyin_dict.go` | 拼音词库实现(基于 binformat 的 mmap 读取) |
2424
| `codetable.go` | 五笔码表加载(文本格式和二进制 wdb 格式),含 `BuildReverseIndex`(全量)和 `BuildSingleCharReverseIndex`(仅单字,过滤权重 < maxWeight/10 的低频简码);支持 Rime 词库合并结果;`HasLongerCode(input)` 检查码表中是否存在 `code != input` 且以 input 为前缀的条目(mmap 与内存模式均支持,供全码自动顶屏判定) |
2525
| `phrase.go` | `PhraseLayer`:短语和命令处理,支持模板变量;`SetCmdbarHook(CmdbarPhraseHook)` 装配命令直通车 hook (`$CC`/`$CC1`), `SetCmdbarArrayHook(CmdbarArrayHook)` 装配字符串数组 hook (`$SS`); 含 cmdbar marker 的动态短语在 `SearchCommand` 改走 hook 求值得到 `(display, actions, modifiers)`; `SearchPrefix` 同时扫 `dynamicPhrases` 带 marker 的条目, 末尾用 `filterCmdbarExactOnly` (走 `candidateIsExactOnly` 综合 `Modifiers["prefix"]` 与字符串扫描); hook 报错时退化为字面量记 WARN (不带 value 内容)。**权重 (2026-05-16 修订)**: 候选 `Weight` 由 `resolvePhraseWeight(weight)` 计算 (单参), `weight<=0 → 1000` (默认), 否则 clamp 到 `NormalizedWeightMax`; `position` 仅作 `sortPhraseCandidates` 同 code 内 tie-break (升序, 0=未调整, 已调整优先)。**字符组 (2026-05-16)**: `PhraseGroup.Kind` 区分 `aa` (字符) / `ss` (字符串), Kind=ss 时 SearchCommand 精确码命中走 `expandSSGroup` 调 ArrayHook 运行时展开; Kind=aa 走 staticPhrases 字符级 entry; 两类共享前缀 nav 候选路径。**字符组双路径 `GroupName` 一致性约束 (2026-05-17)**: `Search` (静态精确, staticPhrases 命中且 PhraseGroup.Kind==aa) 与 `SearchCommand` (情况 2a 字符串数组 / 情况 2b 字符组精确) 两条出口的字符级 / 元素级候选都必须填 `Candidate.GroupCode + GroupName`, 否则 coordinator 的 `collapseGroupMembersIfMixed` 无法在混合场景下展示组名 nav。**SearchCommand 锁升级 (2026-05-17)**: 改 double-checked locking — 1st pass `pl.mu.RLock()` 读 cmdCache 命中即返回 (并发读不互相阻塞); 未命中升级到 `pl.mu.Lock()` + double-check + 计算 + 写 cache。`expandSSGroup` / `expandDynamicEntry` 仍要求调用方持有 W-Lock, 由 2nd pass 保证。 |

wind_input/internal/dict/composite.go

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ func formatLayerTrace(trace []layerHit) string {
4444

4545
// CompositeDict 聚合词库
4646
// 按优先级组合多个词库层,实现分层叠加查询
47+
//
48+
// 排序模式不由 CompositeDict 持有 —— 它属于当前活跃方案,
49+
// 由调用方(引擎)通过 SearchOptions.SortMode 每次显式传入。
50+
// 这样设计避免多方案共享同一 dm 时 sortMode 跨方案污染。
4751
type CompositeDict struct {
4852
mu sync.RWMutex
4953
layers []DictLayer // 按优先级排序(LayerType 小的在前)
@@ -53,9 +57,14 @@ type CompositeDict struct {
5357

5458
// 词频评分器(可选)
5559
freqScorer FreqScorer
60+
}
5661

57-
// 排序模式
58-
sortMode candidate.CandidateSortMode
62+
// SearchOptions 查询选项。
63+
// 引入 struct 是为后续扩展(如 IncludeLayers、FilterMode 等查询级配置)留口子,
64+
// 当前仅有 Limit / SortMode 两个字段。零值(SortMode == "")等价于按词频排序(Better)。
65+
type SearchOptions struct {
66+
Limit int // 最大返回数量,0 表示不限制
67+
SortMode candidate.CandidateSortMode // 排序模式,空值默认为词频排序
5968
}
6069

6170
// seenIdxPool 复用 searchInternal 中的去重 map。每次按键都会触发一轮 search,
@@ -133,44 +142,70 @@ func (c *CompositeDict) SetFreqScorer(scorer FreqScorer) {
133142
c.freqScorer = scorer
134143
}
135144

136-
// SetSortMode 设置候选排序模式
137-
func (c *CompositeDict) SetSortMode(mode candidate.CandidateSortMode) {
138-
c.mu.Lock()
139-
defer c.mu.Unlock()
140-
c.sortMode = mode
141-
}
142-
143-
// GetSortMode 获取当前排序模式
144-
func (c *CompositeDict) GetSortMode() candidate.CandidateSortMode {
145+
// Search 聚合查询
146+
// 按优先级遍历所有层,合并结果。
147+
// opt.SortMode 由调用方传入;空值视为词频排序。
148+
func (c *CompositeDict) Search(code string, opt SearchOptions) []candidate.Candidate {
145149
c.mu.RLock()
146150
defer c.mu.RUnlock()
147-
if c.sortMode == "" {
148-
return candidate.SortByFrequency
149-
}
150-
return c.sortMode
151+
152+
return c.searchInternal(code, opt, false)
151153
}
152154

153-
// Search 聚合查询
154-
// 按优先级遍历所有层,合并结果,应用 Shadow 规则
155-
func (c *CompositeDict) Search(code string, limit int) []candidate.Candidate {
155+
// SearchPrefix 聚合前缀查询
156+
func (c *CompositeDict) SearchPrefix(prefix string, opt SearchOptions) []candidate.Candidate {
156157
c.mu.RLock()
157158
defer c.mu.RUnlock()
158159

159-
return c.searchInternal(code, limit, false)
160+
return c.searchInternal(prefix, opt, true)
160161
}
161162

162-
// SearchPrefix 聚合前缀查询
163-
func (c *CompositeDict) SearchPrefix(prefix string, limit int) []candidate.Candidate {
163+
// SearchSystemOnly 仅查询系统码表 / 细胞词库层(LayerTypeCell + LayerTypeSystem),
164+
// 跳过 user/temp/shadow/phrase 等层。
165+
// 用于 ProtectTopN: "锁定码表原始顺序"语义只看系统层,避免被用户词/临时词污染。
166+
// 不应用 freqScorer——保护的是码表静态权重序,而不是当前调频后的实时顺序。
167+
func (c *CompositeDict) SearchSystemOnly(code string, opt SearchOptions) []candidate.Candidate {
164168
c.mu.RLock()
165169
defer c.mu.RUnlock()
166170

167-
return c.searchInternal(prefix, limit, true)
171+
results := make([]candidate.Candidate, 0, 32)
172+
seen := make(map[string]int, 16)
173+
for _, layer := range c.layers {
174+
t := layer.Type()
175+
if t != LayerTypeSystem && t != LayerTypeCell {
176+
continue
177+
}
178+
for _, cand := range layer.Search(code, 0) {
179+
if idx, exists := seen[cand.Text]; exists {
180+
if cand.Weight > results[idx].Weight {
181+
results[idx].Weight = cand.Weight
182+
}
183+
continue
184+
}
185+
seen[cand.Text] = len(results)
186+
results = append(results, cand)
187+
}
188+
}
189+
190+
comparator := candidate.Better
191+
if opt.SortMode == candidate.SortByNatural {
192+
comparator = candidate.BetterNatural
193+
}
194+
sort.SliceStable(results, func(i, j int) bool {
195+
return comparator(results[i], results[j])
196+
})
197+
198+
if opt.Limit > 0 && len(results) > opt.Limit {
199+
results = results[:opt.Limit]
200+
}
201+
return results
168202
}
169203

170204
// searchInternal 内部查询逻辑
171205
// Shadow 的 pin/delete 不在此处处理——统一由引擎层 Phase 6(ApplyShadowPins)在最终排序后应用。
172206
// CompositeDict 只负责层级合并、去重和基础排序。
173-
func (c *CompositeDict) searchInternal(code string, limit int, isPrefix bool) []candidate.Candidate {
207+
func (c *CompositeDict) searchInternal(code string, opt SearchOptions, isPrefix bool) []candidate.Candidate {
208+
limit := opt.Limit
174209
// 1. 遍历所有层收集候选词
175210
// 去重策略:保留高优先级层(先出现)的词条信息,但继承后续层中同 Text 词条的更高权重。
176211
// 这确保用户词不会因为低权重而丢失码表词的自然排序位置。
@@ -243,7 +278,7 @@ func (c *CompositeDict) searchInternal(code string, limit int, isPrefix bool) []
243278

244279
// 3. 排序
245280
comparator := candidate.Better
246-
if c.sortMode == candidate.SortByNatural {
281+
if opt.SortMode == candidate.SortByNatural {
247282
comparator = candidate.BetterNatural
248283
}
249284
sort.SliceStable(results, func(i, j int) bool {
@@ -459,11 +494,9 @@ func (c *CompositeDict) GetLayersByType(layerType LayerType) []DictLayer {
459494
// 查询便捷方法
460495
// ============================================================
461496

462-
// Lookup 按编码查询候选词
497+
// Lookup 按编码查询候选词(便捷方法,使用默认排序模式)
463498
func (c *CompositeDict) Lookup(pinyin string) []candidate.Candidate {
464-
results := c.Search(pinyin, 0)
465-
// log.Printf("[CompositeDict] Lookup: pinyin=%q results=%d", pinyin, len(results))
466-
return results
499+
return c.Search(pinyin, SearchOptions{})
467500
}
468501

469502
// LookupPhrase 将音节列表拼接后查询
@@ -478,12 +511,12 @@ func (c *CompositeDict) LookupPhrase(syllables []string) []candidate.Candidate {
478511
code += s
479512
}
480513

481-
return c.Search(code, 0)
514+
return c.Search(code, SearchOptions{})
482515
}
483516

484517
// LookupPrefix 实现 dict.PrefixSearchable 接口
485518
func (c *CompositeDict) LookupPrefix(prefix string, limit int) []candidate.Candidate {
486-
return c.SearchPrefix(prefix, limit)
519+
return c.SearchPrefix(prefix, SearchOptions{Limit: limit})
487520
}
488521

489522
// LookupCommand 实现 dict.CommandSearchable 接口

wind_input/internal/dict/manager.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"strings"
88
"sync"
99

10-
"github.com/huanfeng/wind_input/internal/candidate"
1110
"github.com/huanfeng/wind_input/internal/store"
1211
)
1312

@@ -403,13 +402,6 @@ func (dm *DictManager) SetFreqProfile(profile *store.FreqProfile) {
403402
dm.freqScorers = make(map[string]*StoreFreqScorer)
404403
}
405404

406-
// SetSortMode 设置候选排序模式
407-
func (dm *DictManager) SetSortMode(mode candidate.CandidateSortMode) {
408-
dm.mu.Lock()
409-
defer dm.mu.Unlock()
410-
dm.compositeDict.SetSortMode(mode)
411-
}
412-
413405
// GetShadowProvider 获取当前活跃的 ShadowProvider。
414406
// 当 activeStoreShadow 是 nil 指针时显式返回 untyped nil, 避免接口 typed-nil
415407
// 陷阱 (调方法时 panic, 即使 `if p != nil { p.X() }` 也无效)。
@@ -708,16 +700,6 @@ func (dm *DictManager) Close() error {
708700
return nil
709701
}
710702

711-
// Search 搜索候选词(便捷方法)
712-
func (dm *DictManager) Search(code string, limit int) []candidate.Candidate {
713-
return dm.compositeDict.Search(code, limit)
714-
}
715-
716-
// SearchPrefix 前缀搜索(便捷方法)
717-
func (dm *DictManager) SearchPrefix(prefix string, limit int) []candidate.Candidate {
718-
return dm.compositeDict.SearchPrefix(prefix, limit)
719-
}
720-
721703
// ReloadPhrases 重新加载短语配置
722704
func (dm *DictManager) ReloadPhrases() error {
723705
dm.mu.Lock()

wind_input/internal/engine/codetable/AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
- `PunctCommit`:标点符号触发顶码上屏
2626
- `SingleCodeInput`:关闭前缀匹配,只做精确匹配(逐字键入模式)
2727
- `ShowCodeHint`:候选词显示四码编码提示
28-
- `ProtectTopN`:首选保护,前 N 位锁定码表原始顺序不受词频影响
29-
- `CandidateSortMode`:候选排序模式, `CompositeDict.SetSortMode` 同步
28+
- `ProtectTopN`:首选保护,前 N 位锁定**系统码表**原始顺序(通过 `CompositeDict.SearchSystemOnly` 取,仅看 `LayerTypeCell+LayerTypeSystem`,跳过 user/temp/phrase 避免用户词污染肌肉记忆)
29+
- `CandidateSortMode`:候选排序模式,由引擎在每次调用 `CompositeDict.Search/SearchPrefix` 时通过 `dict.SearchOptions.SortMode` 传入(**不再**经由全局 `SetSortMode`,避免多方案共享 dm 时跨方案污染)
3030
- `SkipShadow`:跳过引擎内部的 Shadow 应用,由外层(MixedEngine)统一处理
3131
- `HandleTopCode(input string)` 处理五码输入:截取前四码查找候选,剩余一码作为新输入
3232
- `ConvertEx` 返回 `*ConvertResult`,包含 `ShouldCommit``CommitText``IsEmpty``ShouldClear``ToEnglish`

wind_input/internal/engine/codetable/codetable.go

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -359,10 +359,12 @@ func (e *Engine) ConvertEx(input string, maxCandidates int) *ConvertResult {
359359
phaseStart := time.Now()
360360
exactCandidates := make([]candidate.Candidate, 0, 32)
361361

362+
sortMode := candidate.CandidateSortMode(e.config.CandidateSortMode)
362363
if e.dictManager != nil {
363364
// 通过 CompositeDict 查询(包含短语、用户词、系统码表,Shadow 已自动应用)
365+
// SortMode 由本引擎 Config 驱动,确保 composite 与 Phase 5 一致。
364366
compositeDict := e.dictManager.GetCompositeDict()
365-
exactCandidates = append(exactCandidates, compositeDict.Search(input, 0)...)
367+
exactCandidates = append(exactCandidates, compositeDict.Search(input, dict.SearchOptions{SortMode: sortMode})...)
366368
exactCandidates = append(exactCandidates, compositeDict.LookupCommand(input)...)
367369
}
368370

@@ -384,7 +386,7 @@ func (e *Engine) ConvertEx(input string, maxCandidates int) *ConvertResult {
384386
if prefixEnabled {
385387
if e.dictManager != nil {
386388
compositeDict := e.dictManager.GetCompositeDict()
387-
for _, c := range compositeDict.SearchPrefix(input, 0) {
389+
for _, c := range compositeDict.SearchPrefix(input, dict.SearchOptions{SortMode: sortMode}) {
388390
if c.Code != input {
389391
prefixCandidates = append(prefixCandidates, c)
390392
}
@@ -426,7 +428,7 @@ func (e *Engine) ConvertEx(input string, maxCandidates int) *ConvertResult {
426428
var completionCandidates []candidate.Candidate
427429
if e.dictManager != nil {
428430
compositeDict := e.dictManager.GetCompositeDict()
429-
for _, c := range compositeDict.SearchPrefix(input, 1) {
431+
for _, c := range compositeDict.SearchPrefix(input, dict.SearchOptions{Limit: 1, SortMode: sortMode}) {
430432
if c.Code != input {
431433
completionCandidates = append(completionCandidates, c)
432434
break
@@ -472,19 +474,36 @@ func (e *Engine) ConvertEx(input string, maxCandidates int) *ConvertResult {
472474

473475
// ========== Phase 5: 排序 + 过滤 + 截断 ==========
474476
phaseStart = time.Now()
475-
// 排序前记住精确匹配的原始 top-N(用于 ProtectTopN 锁定)
477+
// 排序前记住码表原始 top-N(用于 ProtectTopN 锁定)。
478+
// 只取系统码表 / 细胞词库层,跳过 user/temp/phrase ——
479+
// "锁定码表原始顺序"的本意是保护用户对系统码表的肌肉记忆,
480+
// 不应被临时词 / 用户造词的高排位污染。
476481
protectN := 0
477482
if e.config != nil {
478483
protectN = e.config.ProtectTopN
479484
}
480485
var protectedCandidates []candidate.Candidate
481-
if protectN > 0 && len(exactCandidates) > 0 {
486+
if protectN > 0 && e.dictManager != nil {
487+
if cd := e.dictManager.GetCompositeDict(); cd != nil {
488+
protectedCandidates = cd.SearchSystemOnly(input, dict.SearchOptions{Limit: protectN, SortMode: sortMode})
489+
}
490+
}
491+
// 降级路径:测试场景无 DictManager 时直接查 e.codeTable
492+
if protectN > 0 && len(protectedCandidates) == 0 && e.codeTable != nil && e.dictManager == nil {
493+
raw := e.codeTable.Lookup(input)
494+
fallbackComparator := candidate.Better
495+
if sortMode == candidate.SortByNatural {
496+
fallbackComparator = candidate.BetterNatural
497+
}
498+
sort.SliceStable(raw, func(i, j int) bool {
499+
return fallbackComparator(raw[i], raw[j])
500+
})
482501
n := protectN
483-
if n > len(exactCandidates) {
484-
n = len(exactCandidates)
502+
if n > len(raw) {
503+
n = len(raw)
485504
}
486505
protectedCandidates = make([]candidate.Candidate, n)
487-
copy(protectedCandidates, exactCandidates[:n])
506+
copy(protectedCandidates, raw[:n])
488507
}
489508

490509
comparator := candidate.Better
@@ -766,7 +785,8 @@ func (e *Engine) hasFullInputMatch(input string) bool {
766785
}
767786
if e.dictManager != nil {
768787
if cd := e.dictManager.GetCompositeDict(); cd != nil {
769-
if len(cd.Search(input, 1)) > 0 {
788+
// 仅判断是否存在命中,排序模式不影响结果数量
789+
if len(cd.Search(input, dict.SearchOptions{Limit: 1})) > 0 {
770790
return true
771791
}
772792
}

wind_input/internal/engine/manager_config.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package engine
22

33
import (
4-
"github.com/huanfeng/wind_input/internal/candidate"
54
"github.com/huanfeng/wind_input/internal/dict"
65
"github.com/huanfeng/wind_input/internal/engine/codetable"
76
"github.com/huanfeng/wind_input/internal/engine/mixed"
@@ -66,9 +65,7 @@ func (m *Manager) UpdateCodetableOptions(spec *schema.CodeTableSpec) {
6665
}
6766
}
6867

69-
if m.dictManager != nil && spec.CandidateSortMode != "" {
70-
m.dictManager.SetSortMode(candidate.CandidateSortMode(spec.CandidateSortMode))
71-
}
68+
// 排序模式不再由 dm 持有;引擎自己的 Config.CandidateSortMode 已在 updateCodetableConfig 中更新。
7269

7370
m.logger.Info("更新码表选项",
7471
"autoCommitAtFull", spec.AutoCommitAtFull != nil && *spec.AutoCommitAtFull || (spec.AutoCommitAtFull == nil && spec.AutoCommitUnique),

wind_input/internal/engine/manager_temp_pinyin.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package engine
22

33
import (
4+
"github.com/huanfeng/wind_input/internal/dict"
45
"github.com/huanfeng/wind_input/internal/engine/mixed"
56
"github.com/huanfeng/wind_input/internal/engine/pinyin"
67
"github.com/huanfeng/wind_input/internal/schema"
@@ -269,7 +270,8 @@ func (m *Manager) HasPrefix(prefix string) bool {
269270
}
270271
// 码表/用户词/静态短语前缀(聚合所有词库层)
271272
if composite := dm.GetCompositeDict(); composite != nil {
272-
if len(composite.SearchPrefix(prefix, 1)) > 0 {
273+
// 仅判断是否存在命中,排序模式不影响结果数量
274+
if len(composite.SearchPrefix(prefix, dict.SearchOptions{Limit: 1})) > 0 {
273275
return true
274276
}
275277
}

wind_input/internal/schema/factory.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"strings"
1212
"sync"
1313

14-
"github.com/huanfeng/wind_input/internal/candidate"
1514
"github.com/huanfeng/wind_input/internal/dict"
1615
"github.com/huanfeng/wind_input/internal/dict/binformat"
1716
"github.com/huanfeng/wind_input/internal/dict/dictcache"
@@ -229,8 +228,7 @@ func createCodeTableEngine(s *Schema, exeDir, dataDir string, dm *dict.DictManag
229228
ctSystemLayer = systemLayer
230229
}
231230
engine.SetDictManager(dm)
232-
// 同步排序模式到 CompositeDict,避免启动时使用默认的词频排序
233-
dm.SetSortMode(candidate.CandidateSortMode(spec.CandidateSortMode))
231+
// 排序模式不再由 dm 持有;引擎每次调用 composite.Search 时通过 SearchOptions 显式传入。
234232

235233
// 注入 FreqHandler(调频)
236234
if s.Learning.IsFreqEnabled() {
@@ -1086,7 +1084,7 @@ func createMixedEngine(s *Schema, exeDir, dataDir string, dm *dict.DictManager,
10861084
mixedSystemLayer = systemLayer
10871085
}
10881086
codetableEngine.SetDictManager(dm)
1089-
dm.SetSortMode(candidate.CandidateSortMode(codeTableSpec.CandidateSortMode))
1087+
// 排序模式不再由 dm 持有;引擎每次调用 composite.Search 时通过 SearchOptions 显式传入。
10901088

10911089
// 注入码表引擎的 FreqHandler 和 LearningStrategy
10921090
if s.Learning.IsFreqEnabled() {

0 commit comments

Comments
 (0)