// KeymapService — 快捷键绑定管理 + 冲突检测(FR-K3/K4/K5) export type Scope = 'global' | 'app' | 'editor'; export interface Binding { id: string; scope: Scope; combo: string; defaultCombo: string; } export class KeymapService { private bindings = new Map(); register(b: Binding) { this.bindings.set(b.id, b); } setCombo(id: string, combo: string) { const b = this.bindings.get(id); if (b) this.bindings.set(id, { ...b, combo }); } resetAll() { for (const [id, b] of this.bindings) this.bindings.set(id, { ...b, combo: b.defaultCombo }); } getAll(): Binding[] { return [...this.bindings.values()]; } // 冲突检测:同一 combo 绑定到多个 action(不同 id)即冲突 conflicts(): Record { const byCombo = new Map(); for (const b of this.bindings.values()) { const arr = byCombo.get(b.combo) ?? []; arr.push(b.id); byCombo.set(b.combo, arr); } const out: Record = {}; for (const [combo, ids] of byCombo) if (ids.length > 1) out[combo] = ids; return out; } conflictCount(): number { return Object.keys(this.conflicts()).length; } // 作用域裁决:给定当前是否编辑器聚焦,返回某 combo 应触发哪个 binding(编辑器聚焦时 global 让位规则按需) resolve(combo: string, editorFocused: boolean): Binding | null { const matches = [...this.bindings.values()].filter(b => b.combo === combo); if (matches.length === 0) return null; // 优先 editor(编辑器聚焦时),再 app,再 global if (editorFocused) { const e = matches.find(m => m.scope === 'editor'); if (e) return e; } return matches.find(m => m.scope === 'global') ?? matches[0]; } }