authorgravatar for 61359614+iamshadmantaqi@users.noreply.github.comShadman Taqi <61359614+iamshadmantaqi@users.noreply.github.com> 2026-09-08 15:48:54+06:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-08 15:18:54+05:30
log83b865fec704b177804aa6b9b695aefea4ccd04a
tree2b396489bfecdabad24eac9dd29c13cff76b4732
parentd6dbe8dd67facf4a43030993bebb58cc7e7ac141
signature Signed by PGP key B5690EEEBB952194

fix(web): copy terminal selection with Ctrl+Insert (#8541)


2 files changed, 22 insertions(+), 4 deletions(-)

apps/web/src/terminal/ghostty/surface.test.ts+14
...@@ -556,6 +556,20 @@ describe("isTerminalCopyShortcut", () => {...@@ -556,6 +556,20 @@ describe("isTerminalCopyShortcut", () => {
556 expect(isTerminalCopyShortcut(event({ key: "C", metaKey: true }), "MacIntel")).toBe(true);556 expect(isTerminalCopyShortcut(event({ key: "C", metaKey: true }), "MacIntel")).toBe(true);
557 expect(isTerminalCopyShortcut(event({ key: "j", metaKey: true }), "MacIntel")).toBe(false);557 expect(isTerminalCopyShortcut(event({ key: "j", metaKey: true }), "MacIntel")).toBe(false);
558 });558 });
559
560 it("supports the conventional Ctrl+Insert copy shortcut", () => {
561 expect(isTerminalCopyShortcut(event({ key: "Insert", ctrlKey: true }), "Linux x86_64")).toBe(
562 true,
563 );
564 expect(isTerminalCopyShortcut(event({ key: "Insert" }), "Linux x86_64")).toBe(false);
565 expect(
566 isTerminalCopyShortcut(
567 event({ key: "Insert", ctrlKey: true, shiftKey: true }),
568 "Linux x86_64",
569 ),
570 ).toBe(false);
571 expect(isTerminalCopyShortcut(event({ key: "Insert", ctrlKey: true }), "MacIntel")).toBe(false);
572 });
559});573});
560574
561describe("applyTerminalCopyEvent", () => {575describe("applyTerminalCopyEvent", () => {
apps/web/src/terminal/ghostty/surface.ts+8-4
...@@ -321,7 +321,11 @@ export function isTerminalCopyShortcut(...@@ -321,7 +321,11 @@ export function isTerminalCopyShortcut(
321 event: Pick<KeyboardEvent, "ctrlKey" | "key" | "metaKey" | "shiftKey">,321 event: Pick<KeyboardEvent, "ctrlKey" | "key" | "metaKey" | "shiftKey">,
322 platform = navigator.platform,322 platform = navigator.platform,
323) {323) {
324 if (event.key.toLowerCase() !== "c") return false;324 const key = event.key.toLowerCase();
325 if (key === "insert" && !isMacPlatform(platform)) {
326 return event.ctrlKey && !event.shiftKey && !event.metaKey;
327 }
328 if (key !== "c") return false;
325 return isMacPlatform(platform) ? event.metaKey : event.ctrlKey;329 return isMacPlatform(platform) ? event.metaKey : event.ctrlKey;
326}330}
327331
...@@ -1027,12 +1031,12 @@ export class GhosttyTerminalSurface {...@@ -1027,12 +1031,12 @@ export class GhosttyTerminalSurface {
1027 // A plain Ctrl+C/Cmd+C fires the browser's native copy event, caught in1031 // A plain Ctrl+C/Cmd+C fires the browser's native copy event, caught in
1028 // onCopyEvent; not preventing the default keeps that path alive. WebKit1032 // onCopyEvent; not preventing the default keeps that path alive. WebKit
1029 // omits the keyboard copy event without a DOM selection, so race the1033 // omits the keyboard copy event without a DOM selection, so race the
1030 // clipboard write against it the same way paste races its read. The1034 // clipboard write against it the same way paste races its read. Ctrl+Shift+C
1031 // Shift variant has no native event (Chrome binds Ctrl+Shift+C to1035 // and Ctrl+Insert have no native copy event (Chrome binds the former to
1032 // inspect), so synthesize one with execCommand("copy").1036 // inspect), so synthesize one with execCommand("copy").
1033 const selection = this.getSelection();1037 const selection = this.getSelection();
1034 this.primeCopy(selection);1038 this.primeCopy(selection);
1035 if (event.shiftKey) {1039 if (event.shiftKey || event.key.toLowerCase() === "insert") {
1036 event.preventDefault();1040 event.preventDefault();
1037 document.execCommand("copy");1041 document.execCommand("copy");
1038 } else {1042 } else {