diff --git a/apps/web/src/terminal/ghostty/core.test.ts b/apps/web/src/terminal/ghostty/core.test.ts index 7fc9661555e49..5e51c7b4be378 100644 --- a/apps/web/src/terminal/ghostty/core.test.ts +++ b/apps/web/src/terminal/ghostty/core.test.ts @@ -146,6 +146,26 @@ describe("GhosttyTerminalCore snapshots", () => { expect(core.encodeKey(event)).toBe(expected); }); + it.each(["", "\x1b[>1u"])("encodes macOS Option text with keyboard mode %j", async (mode) => { + using _platform = vi.spyOn(navigator, "platform", "get").mockReturnValue("MacIntel"); + const core = await createCore(); + core.write(mode); + const option = { + code: "KeyL", + key: "@", + repeat: false, + shiftKey: false, + ctrlKey: false, + altKey: true, + metaKey: false, + isComposing: false, + getModifierState: () => false, + } as unknown as KeyboardEvent; + + expect(core.encodeKey(option)).toBe("@"); + expect(core.encodeKey({ ...option, code: "KeyA", key: "Å", shiftKey: true })).toBe("Å"); + }); + it("preserves styles, wide cells, and selection after shared memory grows", async () => { const core = await createCore(); const runtime = await loadGhosttyRuntime(); diff --git a/apps/web/src/terminal/ghostty/keyCodes.test.ts b/apps/web/src/terminal/ghostty/keyCodes.test.ts index 63b78363ec67e..7ec36bf4d7044 100644 --- a/apps/web/src/terminal/ghostty/keyCodes.test.ts +++ b/apps/web/src/terminal/ghostty/keyCodes.test.ts @@ -49,6 +49,16 @@ describe("ghosttyConsumedMods", () => { expect(ghosttyConsumedMods({ ...shifted, ctrlKey: true })).toBe(0); expect(ghosttyConsumedMods({ ...shifted, key: "Tab" })).toBe(0); expect(ghosttyConsumedMods({ ...shifted, key: " " })).toBe(1); + expect(ghosttyConsumedMods({ ...shifted, altKey: true }, "Linux x86_64")).toBe(0); + }); + + it("consumes macOS Option when it produced a character", () => { + const option = { ...shifted, altKey: true, key: "[", shiftKey: false }; + expect(ghosttyConsumedMods(option, "MacIntel")).toBe(1 << 2); + expect(ghosttyConsumedMods({ ...option, shiftKey: true }, "MacIntel")).toBe(1 | (1 << 2)); + expect(ghosttyConsumedMods({ ...option, key: "ArrowLeft" }, "MacIntel")).toBe(0); + expect(ghosttyConsumedMods({ ...option, ctrlKey: true }, "MacIntel")).toBe(0); + expect(ghosttyConsumedMods({ ...option, metaKey: true }, "MacIntel")).toBe(0); }); }); diff --git a/apps/web/src/terminal/ghostty/keyCodes.ts b/apps/web/src/terminal/ghostty/keyCodes.ts index 85d690922aea7..bb13c09c8d2e0 100644 --- a/apps/web/src/terminal/ghostty/keyCodes.ts +++ b/apps/web/src/terminal/ghostty/keyCodes.ts @@ -1,4 +1,4 @@ -import { US_SHIFTED_TO_UNSHIFTED_KEYS } from "@t3tools/shared/keybindings"; +import { isMacPlatform, US_SHIFTED_TO_UNSHIFTED_KEYS } from "@t3tools/shared/keybindings"; // This order mirrors GhosttyKey in ghostty/vt/key/event.h. The values are // intentionally derived from the official W3C-aligned enum instead of @@ -216,12 +216,14 @@ export function loadGhosttyKeyboardLayoutMap(): Promise, + platform = navigator.platform, ): number { - if (!event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) return 0; - return [...event.key].length === 1 ? 1 : 0; + if (event.ctrlKey || event.metaKey || [...event.key].length !== 1) return 0; + if (event.altKey && !isMacPlatform(platform)) return 0; + return (event.shiftKey ? 1 : 0) | (event.altKey ? 1 << 2 : 0); } export function ghosttyUnshiftedCodepoint(