Răsfoiți Sursa

fix(opencode): image paste on Windows Terminal 1.25+ with kitty keyboard (#17674)

Luke Parker 3 săptămâni în urmă
părinte
comite
1a4a6eabe2

+ 1 - 1
packages/opencode/src/cli/cmd/tui/app.tsx

@@ -186,7 +186,7 @@ export function tui(input: {
         targetFps: 60,
         gatherStats: false,
         exitOnCtrlC: false,
-        useKittyKeyboard: {},
+        useKittyKeyboard: { events: process.platform === "win32" },
         autoFocus: false,
         openConsoleOnError: false,
         consoleOptions: {

+ 21 - 5
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

@@ -18,7 +18,7 @@ import { usePromptStash } from "./stash"
 import { DialogStash } from "../dialog-stash"
 import { type AutocompleteRef, Autocomplete } from "./autocomplete"
 import { useCommandDialog } from "../dialog-command"
-import { useRenderer } from "@opentui/solid"
+import { useKeyboard, useRenderer } from "@opentui/solid"
 import { Editor } from "@tui/util/editor"
 import { useExit } from "../../context/exit"
 import { Clipboard } from "../../util/clipboard"
@@ -356,6 +356,20 @@ export function Prompt(props: PromptProps) {
     ]
   })
 
+  // Windows Terminal 1.25+ handles Ctrl+V on keydown when kitty events are
+  // enabled, but still reports the kitty key-release event. Probe on release.
+  if (process.platform === "win32") {
+    useKeyboard(
+      (evt) => {
+        if (!input.focused) return
+        if (evt.name === "v" && evt.ctrl && evt.eventType === "release") {
+          command.trigger("prompt.paste")
+        }
+      },
+      { release: true },
+    )
+  }
+
   const ref: PromptRef = {
     get focused() {
       return input.focused
@@ -850,10 +864,9 @@ export function Prompt(props: PromptProps) {
                   e.preventDefault()
                   return
                 }
-                // Handle clipboard paste (Ctrl+V) - check for images first on Windows
-                // This is needed because Windows terminal doesn't properly send image data
-                // through bracketed paste, so we need to intercept the keypress and
-                // directly read from clipboard before the terminal handles it
+                // Check clipboard for images before terminal-handled paste runs.
+                // This helps terminals that forward Ctrl+V to the app; Windows
+                // Terminal 1.25+ usually handles Ctrl+V before this path.
                 if (keybind.match("input_paste", e)) {
                   const content = await Clipboard.read()
                   if (content?.mime.startsWith("image/")) {
@@ -936,6 +949,9 @@ export function Prompt(props: PromptProps) {
                 // Replace CRLF first, then any remaining CR
                 const normalizedText = decodePasteBytes(event.bytes).replace(/\r\n/g, "\n").replace(/\r/g, "\n")
                 const pastedContent = normalizedText.trim()
+
+                // Windows Terminal <1.25 can surface image-only clipboard as an
+                // empty bracketed paste. Windows Terminal 1.25+ does not.
                 if (!pastedContent) {
                   command.trigger("prompt.paste")
                   return

+ 10 - 0
packages/opencode/src/cli/cmd/tui/util/clipboard.ts

@@ -28,6 +28,14 @@ export namespace Clipboard {
     mime: string
   }
 
+  // Checks clipboard for images first, then falls back to text.
+  //
+  // On Windows prompt/ can call this from multiple paste signals because
+  // terminals surface image paste differently:
+  //   1. A forwarded Ctrl+V keypress
+  //   2. An empty bracketed-paste hint for image-only clipboard in Windows
+  //      Terminal <1.25
+  //   3. A kitty Ctrl+V key-release fallback for Windows Terminal 1.25+
   export async function read(): Promise<Content | undefined> {
     const os = platform()
 
@@ -58,6 +66,8 @@ export namespace Clipboard {
       }
     }
 
+    // Windows/WSL: probe clipboard for images via PowerShell.
+    // Bracketed paste can't carry image data so we read it directly.
     if (os === "win32" || release().includes("WSL")) {
       const script =
         "Add-Type -AssemblyName System.Windows.Forms; $img = [System.Windows.Forms.Clipboard]::GetImage(); if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); [System.Convert]::ToBase64String($ms.ToArray()) }"