| author | |
| committer | |
| log | 18f6d034897d9cc5429dcb88c6a9795d183f1687 |
| tree | 6dd9b275708c619ad2700d91aa59ef677865c18f |
| parent | 6d5c6c4a671c7b6fc9723c63c1003916fb3b8ab0 |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 28 insertions(+), 2 deletions(-)
apps/web/src/terminal/ghostty/core.ts+6-1| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { | 1 | import { |
| 2 | type GhosttyKeyboardLayoutMap, | 2 | type GhosttyKeyboardLayoutMap, |
| 3 | ghosttyConsumedMods, | ||
| 3 | ghosttyKeyForCode, | 4 | ghosttyKeyForCode, |
| 4 | ghosttyUnshiftedCodepoint, | 5 | ghosttyUnshiftedCodepoint, |
| 5 | loadGhosttyKeyboardLayoutMap, | 6 | loadGhosttyKeyboardLayoutMap, |
| ... | @@ -464,7 +465,11 @@ export class GhosttyTerminalCore { | ... | @@ -464,7 +465,11 @@ export class GhosttyTerminalCore { |
| 464 | (event.getModifierState("CapsLock") ? 1 << 4 : 0) | | 465 | (event.getModifierState("CapsLock") ? 1 << 4 : 0) | |
| 465 | (event.getModifierState("NumLock") ? 1 << 5 : 0); | 466 | (event.getModifierState("NumLock") ? 1 << 5 : 0); |
| 466 | this.runtime.call("ghostty_key_event_set_mods", this.keyEvent, mods); | 467 | this.runtime.call("ghostty_key_event_set_mods", this.keyEvent, mods); |
| 467 | this.runtime.call("ghostty_key_event_set_consumed_mods", this.keyEvent, 0); | 468 | this.runtime.call( |
| 469 | "ghostty_key_event_set_consumed_mods", | ||
| 470 | this.keyEvent, | ||
| 471 | ghosttyConsumedMods(event), | ||
| 472 | ); | ||
| 468 | this.runtime.call("ghostty_key_event_set_composing", this.keyEvent, event.isComposing ? 1 : 0); | 473 | this.runtime.call("ghostty_key_event_set_composing", this.keyEvent, event.isComposing ? 1 : 0); |
| 469 | this.runtime.call( | 474 | this.runtime.call( |
| 470 | "ghostty_key_event_set_unshifted_codepoint", | 475 | "ghostty_key_event_set_unshifted_codepoint", |
apps/web/src/terminal/ghostty/keyCodes.test.ts+13-1| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | import { describe, expect, it } from "vite-plus/test"; | 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | 2 | ||
| 3 | import { ghosttyKeyForCode, ghosttyUnshiftedCodepoint } from "./keyCodes"; | 3 | import { ghosttyConsumedMods, ghosttyKeyForCode, ghosttyUnshiftedCodepoint } from "./keyCodes"; |
| 4 | 4 | ||
| 5 | describe("ghosttyKeyForCode", () => { | 5 | describe("ghosttyKeyForCode", () => { |
| 6 | it("keeps the tail of the pinned Ghostty key enum in order", () => { | 6 | it("keeps the tail of the pinned Ghostty key enum in order", () => { |
| ... | @@ -11,6 +11,18 @@ describe("ghosttyKeyForCode", () => { | ... | @@ -11,6 +11,18 @@ describe("ghosttyKeyForCode", () => { |
| 11 | }); | 11 | }); |
| 12 | }); | 12 | }); |
| 13 | 13 | ||
| 14 | describe("ghosttyConsumedMods", () => { | ||
| 15 | const shifted = { altKey: false, ctrlKey: false, key: "@", metaKey: false, shiftKey: true }; | ||
| 16 | |||
| 17 | it("only consumes a lone Shift producing a character", () => { | ||
| 18 | expect(ghosttyConsumedMods(shifted)).toBe(1); | ||
| 19 | expect(ghosttyConsumedMods({ ...shifted, ctrlKey: true })).toBe(0); | ||
| 20 | expect(ghosttyConsumedMods({ ...shifted, key: "Tab" })).toBe(0); | ||
| 21 | // Deliberate: Shift+Space collapses to Space so it still types one. | ||
| 22 | expect(ghosttyConsumedMods({ ...shifted, key: " " })).toBe(1); | ||
| 23 | }); | ||
| 24 | }); | ||
| 25 | |||
| 14 | describe("ghosttyUnshiftedCodepoint", () => { | 26 | describe("ghosttyUnshiftedCodepoint", () => { |
| 15 | it("provides the logical base character for Kitty keyboard encoding", () => { | 27 | it("provides the logical base character for Kitty keyboard encoding", () => { |
| 16 | expect(ghosttyUnshiftedCodepoint({ code: "KeyC", key: "c", shiftKey: false })).toBe( | 28 | expect(ghosttyUnshiftedCodepoint({ code: "KeyC", key: "c", shiftKey: false })).toBe( |
apps/web/src/terminal/ghostty/keyCodes.ts+9| ... | @@ -233,6 +233,15 @@ export function loadGhosttyKeyboardLayoutMap(): Promise<GhosttyKeyboardLayoutMap | ... | @@ -233,6 +233,15 @@ export function loadGhosttyKeyboardLayoutMap(): Promise<GhosttyKeyboardLayoutMap |
| 233 | return promise; | 233 | return promise; |
| 234 | } | 234 | } |
| 235 | 235 | ||
| 236 | // Browsers do not expose consumed modifiers; treat Shift as consumed for | ||
| 237 | // unchorded character input. | ||
| 238 | export function ghosttyConsumedMods( | ||
| 239 | event: Pick<KeyboardEvent, "altKey" | "ctrlKey" | "key" | "metaKey" | "shiftKey">, | ||
| 240 | ): number { | ||
| 241 | if (!event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) return 0; | ||
| 242 | return [...event.key].length === 1 ? 1 : 0; | ||
| 243 | } | ||
| 244 | |||
| 236 | export function ghosttyUnshiftedCodepoint( | 245 | export function ghosttyUnshiftedCodepoint( |
| 237 | event: Pick<KeyboardEvent, "code" | "key" | "shiftKey">, | 246 | event: Pick<KeyboardEvent, "code" | "key" | "shiftKey">, |
| 238 | layoutMap?: GhosttyKeyboardLayoutMap, | 247 | layoutMap?: GhosttyKeyboardLayoutMap, |