From 1dda642dccc18b32fb3f2b196b084fb0a5387195 Mon Sep 17 00:00:00 2001 From: CialloKing <932398292@qq.com> Date: Thu, 17 Sep 2026 20:34:03 +0800 Subject: [PATCH 1/2] =?UTF-8?q?test:=20=E8=A6=86=E7=9B=96=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E9=94=AE=E8=B7=A8=E5=B9=B3=E5=8F=B0=E7=AD=89=E4=BB=B7?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E7=9A=84=E5=86=B2=E7=AA=81=E5=9B=9E=E5=BD=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin-runtime/test/hotkey-scope.test.ts | 130 +++++++++++++----- 1 file changed, 95 insertions(+), 35 deletions(-) diff --git a/packages/plugin-runtime/test/hotkey-scope.test.ts b/packages/plugin-runtime/test/hotkey-scope.test.ts index 23188085..4ff84e69 100644 --- a/packages/plugin-runtime/test/hotkey-scope.test.ts +++ b/packages/plugin-runtime/test/hotkey-scope.test.ts @@ -1,6 +1,7 @@ import type { CommandContext, ComponentId, + HotkeyBinding, ManagedResource, NexusDiagnostic, PluginId, @@ -97,42 +98,101 @@ describe("HotkeyRegistry", () => { ]); }); - it("diagnoses equally ranked conflicts instead of using registration order", () => { - const resources: ManagedResource[] = []; - const diagnostics: NexusDiagnostic[] = []; - const commands = new CommandRegistry(); - const first = commands.createService(owner("first"), (resource) => resources.push(resource)); - const second = commands.createService(owner("second"), (resource) => resources.push(resource)); - first.registerCommand({ - id: "one", - name: "One", - callback: vi.fn(), - defaultHotkeys: [{ key: "K", modifiers: ["Mod"] }], - }); - second.registerCommand({ - id: "two", - name: "Two", - callback: vi.fn(), - defaultHotkeys: [{ key: "k", modifiers: ["Ctrl"] }], - }); - activate(resources); - const hotkeys = new HotkeyRegistry(commands, { - platform: "windows", - reportDiagnostic: (diagnostic) => diagnostics.push(diagnostic), - }); + it.each(["default", "custom"] as const)( + "resolves %s bindings per platform without changing their stored semantics", + async (mode) => { + const resources: ManagedResource[] = []; + const action = vi.fn(); + const reportDiagnostic = vi.fn(); + const commands = new CommandRegistry(); + const service = commands.createService(owner(), (resource) => resources.push(resource)); + const bindings: readonly HotkeyBinding[] = [ + { key: "k", modifiers: ["Mod"] }, + { key: "k", modifiers: ["Ctrl"] }, + { key: "k", modifiers: ["Meta"] }, + ]; + service.registerCommand({ + id: "run", + name: "Run", + callback: action, + defaultHotkeys: mode === "default" ? bindings : [], + }); + activate(resources); + const preferences = new MemoryHotkeyPreferenceStore(); + if (mode === "custom") { + await new HotkeyRegistry(commands, { platform: "windows", preferences }) + .setPreference("sample-tools:run", { mode, bindings }); + } - expect(hotkeys.findConflicts()).toMatchObject([ - { - scopeId: "application", - normalizedHotkey: "Ctrl+k", - commandIds: ["first:one", "second:two"], - }, - ]); - const event = key("k", { ctrlKey: true }); - expect(hotkeys.dispatchKeyboardEvent(event)).toMatchObject({ status: "conflict" }); - expect(event.defaultPrevented).toBe(false); - expect(diagnostics).toHaveLength(1); - }); + for (const platform of ["windows", "linux", "macos"] as const) { + const hotkeys = new HotkeyRegistry(commands, { platform, preferences, reportDiagnostic }); + expect(hotkeys.findConflicts()).toEqual([]); + for (const modifiers of [{ ctrlKey: true }, { metaKey: true }]) { + const event = key("k", modifiers); + const dispatched = hotkeys.dispatchKeyboardEvent(event); + expect(dispatched).toMatchObject({ status: "handled", commandId: "sample-tools:run" }); + expect(event.defaultPrevented).toBe(true); + if (dispatched.status === "handled") await dispatched.completion; + expect(action).toHaveBeenCalledTimes(1); + action.mockClear(); + } + expect(hotkeys.getBindings("sample-tools:run")).toEqual(bindings); + expect(hotkeys.getPreference("sample-tools:run")).toEqual( + mode === "custom" ? { mode, bindings } : { mode }, + ); + } + expect(reportDiagnostic).not.toHaveBeenCalled(); + }, + ); + + it.each(["windows", "linux", "macos"] as const)( + "diagnoses distinct command conflicts after resolving aliases on %s", + (platform) => { + const resources: ManagedResource[] = []; + const diagnostics: NexusDiagnostic[] = []; + const action = vi.fn(); + const commands = new CommandRegistry(); + const modifier = platform === "macos" ? "Meta" : "Ctrl"; + const first = commands.createService(owner("first"), (resource) => resources.push(resource)); + const second = commands.createService(owner("second"), (resource) => resources.push(resource)); + first.registerCommand({ + id: "one", + name: "One", + callback: action, + defaultHotkeys: [ + { key: "K", modifiers: ["Mod"] }, + { key: "K", modifiers: [modifier] }, + ], + }); + second.registerCommand({ + id: "two", + name: "Two", + callback: action, + defaultHotkeys: [{ key: "k", modifiers: [modifier] }], + }); + activate(resources); + const hotkeys = new HotkeyRegistry(commands, { + platform, + reportDiagnostic: (diagnostic) => diagnostics.push(diagnostic), + }); + + expect(hotkeys.findConflicts()).toMatchObject([ + { + scopeId: "application", + normalizedHotkey: `${modifier}+k`, + commandIds: ["first:one", "second:two"], + }, + ]); + const event = key("k", { ctrlKey: platform !== "macos", metaKey: platform === "macos" }); + expect(hotkeys.dispatchKeyboardEvent(event)).toMatchObject({ + status: "conflict", + conflict: { commandIds: ["first:one", "second:two"] }, + }); + expect(event.defaultPrevented).toBe(false); + expect(action).not.toHaveBeenCalled(); + expect(diagnostics).toHaveLength(1); + }, + ); it("synchronously consumes a unique command hotkey without fabricating clipboard events", async () => { const resources: ManagedResource[] = []; From 42fb1c75d1030bbe4b0e53b508e436e2ec8b777d Mon Sep 17 00:00:00 2001 From: CialloKing <932398292@qq.com> Date: Thu, 17 Sep 2026 20:35:35 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E6=8C=89=E5=91=BD=E4=BB=A4=E5=8E=BB?= =?UTF-8?q?=E9=87=8D=E5=B9=B3=E5=8F=B0=E5=BD=92=E4=B8=80=E5=8C=96=E5=90=8E?= =?UTF-8?q?=E7=9A=84=E5=BF=AB=E6=8D=B7=E9=94=AE=E5=80=99=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/plugin-runtime/src/commands/hotkey-registry.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/plugin-runtime/src/commands/hotkey-registry.ts b/packages/plugin-runtime/src/commands/hotkey-registry.ts index 88179b91..3eeeafe2 100644 --- a/packages/plugin-runtime/src/commands/hotkey-registry.ts +++ b/packages/plugin-runtime/src/commands/hotkey-registry.ts @@ -155,8 +155,12 @@ export class HotkeyRegistry implements HotkeyService { private candidatesByHotkey(): Map { const groups = new Map(); for (const command of this.commands.listHotkeyCandidates()) { + const seen = new Set(); for (const binding of this.getBindings(command.id)) { const normalizedHotkey = hotkeyToString(binding, this.options.platform); + // Mod and a native modifier can resolve to the same key for this command. + if (seen.has(normalizedHotkey)) continue; + seen.add(normalizedHotkey); const candidate: ResolvedCandidate = { commandId: command.id, normalizedHotkey,