Просмотр исходного кода

VSCode - fix for Copy to clipboard button

paviko 2 месяцев назад
Родитель
Сommit
eae71b3a2c

+ 19 - 17
hosts/scripts/build_vscode.bat

@@ -150,29 +150,31 @@ if "%MISSING_BINARIES%"=="true" (
 )
 
 echo [INFO] Creating VSCode extension package
-where vsce >nul 2>&1
-if errorlevel 1 (
-    echo [INFO] Installing vsce (VSCode Extension Manager)
-    call pnpm run install:vsce
-)
-set "YEAR=%DATE:~10,4%"
-set "MONTH=%DATE:~4,2%"
-set "DAY=%DATE:~7,2%"
-set "HOUR=%TIME:~0,2%"
-if "%HOUR:~0,1%"==" " set "HOUR=0%HOUR:~1,1%"
-set "MINUTE=%TIME:~3,2%"
-set "SECOND=%TIME:~6,2%"
-set "STAMP=%YEAR%%MONTH%%DAY%-%HOUR%%MINUTE%%SECOND%"
-set "VSIX_NAME=opencode-vscode-dev-%STAMP%.vsix"
-if "%BUILD_TYPE%"=="production" set "VSIX_NAME=opencode-vscode-%STAMP%.vsix"
+REM Prefer local vsce (node_modules/.bin) to avoid global installs.
+where pnpm >nul 2>&1
+if not errorlevel 1 (
+    set "VSCE_CMD=pnpm exec vsce"
+) else (
+    set "VSCE_CMD=npx --yes vsce"
+)
+
+REM Use package.json version in the output .vsix name.
+set "PLUGIN_VERSION="
+for /f "usebackq delims=" %%V in (`node -p "require('./package.json').version" 2^>nul`) do set "PLUGIN_VERSION=%%V"
+if "%PLUGIN_VERSION%"=="" set "PLUGIN_VERSION=0.0.0"
+
+set "VSIX_NAME=opencode-vscode-%PLUGIN_VERSION%-dev.vsix"
+if "%BUILD_TYPE%"=="production" set "VSIX_NAME=opencode-vscode-%PLUGIN_VERSION%.vsix"
 
 if "%BUILD_TYPE%"=="production" goto package_production
 
-call vsce package --pre-release --no-dependencies --out "%VSIX_NAME%"
+call %VSCE_CMD% package --pre-release --no-dependencies --out "%VSIX_NAME%"
+if errorlevel 1 exit /b 1
 goto package_complete
 
 :package_production
-call vsce package --no-dependencies --out "%VSIX_NAME%"
+call %VSCE_CMD% package --no-dependencies --out "%VSIX_NAME%"
+if errorlevel 1 exit /b 1
 
 :package_complete
 

+ 5 - 5
hosts/vscode-plugin/package-lock.json

@@ -1,13 +1,13 @@
 {
-  "name": "opencode",
-  "version": "0.1.0",
+  "name": "opencode-ux-plus",
+  "version": "26.1.29",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
-      "name": "opencode",
-      "version": "0.1.0",
-      "license": "Apache-2.0",
+      "name": "opencode-ux-plus",
+      "version": "26.1.29",
+      "license": "MIT",
       "devDependencies": {
         "@types/glob": "^9.0.0",
         "@types/mocha": "^10.0.6",

+ 14 - 5
hosts/vscode-plugin/src/ui/CommunicationBridge.ts

@@ -459,21 +459,29 @@ export class CommunicationBridge implements PluginCommunicator {
                 await this.handleOpenFile(m.payload?.path ?? m.path)
                 // reply if id present
                 if (m.id) {
-                  this.webview?.postMessage({ replyTo: m.id, ok: true })
+                  this.webview?.postMessage({ type: "__ideBridgeReply", replyTo: m.id, ok: true })
+                }
+              } else if (m && m.type === "clipboardWrite") {
+                const text = m.payload?.text
+                if (typeof text === "string") {
+                  await vscode.env.clipboard.writeText(text)
+                }
+                if (m.id) {
+                  this.webview?.postMessage({ type: "__ideBridgeReply", replyTo: m.id, ok: true })
                 }
               } else if (m && m.type === "openUrl") {
                 await this.handleOpenUrl(m.payload?.url ?? m.url)
                 if (m.id) {
-                  this.webview?.postMessage({ replyTo: m.id, ok: true })
+                  this.webview?.postMessage({ type: "__ideBridgeReply", replyTo: m.id, ok: true })
                 }
               } else if (m && m.type === "reloadPath") {
                 await this.handleReloadPath(m.payload?.path)
                 if (m.id) {
-                  this.webview?.postMessage({ replyTo: m.id, ok: true })
+                  this.webview?.postMessage({ type: "__ideBridgeReply", replyTo: m.id, ok: true })
                 }
               } else {
                 // Generic ack for unknown types
-                if (m && m.id) this.webview?.postMessage({ replyTo: m.id, ok: true })
+                if (m && m.id) this.webview?.postMessage({ type: "__ideBridgeReply", replyTo: m.id, ok: true })
               }
             } catch (e) {
               try {
@@ -484,7 +492,8 @@ export class CommunicationBridge implements PluginCommunicator {
                     return undefined
                   }
                 })()
-                if (id) this.webview?.postMessage({ replyTo: id, ok: false, error: String(e) })
+                if (id)
+                  this.webview?.postMessage({ type: "__ideBridgeReply", replyTo: id, ok: false, error: String(e) })
               } catch {}
               logger.appendLine(`Failed to process __ideBridgeSend: ${e}`)
             }

+ 37 - 14
packages/opencode/webgui/src/components/MessageList/ActionButtons.tsx

@@ -1,6 +1,7 @@
 import { useEffect, useRef, useState } from "react"
 import { IconButton } from "../common"
 import { MessageStats } from "./MessageStats"
+import { ideBridge } from "../../lib/ideBridge"
 
 interface TokenData {
   input: number
@@ -33,6 +34,29 @@ export function ActionButtons({ onFork, onRevert, revertBusy, tokens, cost, isUs
 
   const canCopy = typeof copyText === "string" && copyText.length > 0
 
+  const writeClipboard = async (value: string) => {
+    try {
+      const promise = navigator.clipboard?.writeText(value)
+      if (promise) {
+        await promise
+        return true
+      }
+    } catch {}
+
+    if (!ideBridge.isInstalled()) return false
+
+    try {
+      const res = await Promise.race([
+        ideBridge.request("clipboardWrite", { text: value }) as Promise<{ ok?: boolean }>,
+        new Promise<null>((resolve) => setTimeout(() => resolve(null), 1000)),
+      ])
+      if (!res) return false
+      return !!res.ok
+    } catch {
+      return false
+    }
+  }
+
   useEffect(() => {
     const anyOther = canCopy || !!(isUser && onFork) || !!(isUser && onRevert)
     const delay = anyOther ? 500 : 3000
@@ -52,20 +76,19 @@ export function ActionButtons({ onFork, onRevert, revertBusy, tokens, cost, isUs
   const handleCopy = () => {
     if (!canCopy) return
 
-    const promise = navigator.clipboard?.writeText(copyText)
-    if (!promise) return
-
-    void promise
-      .then(() => {
-        setCopied(true)
-        if (timeoutRef.current) {
-          clearTimeout(timeoutRef.current)
-        }
-        timeoutRef.current = setTimeout(() => setCopied(false), 1500)
-      })
-      .catch((err) => {
-        console.error("Failed to copy message:", err)
-      })
+    void (async () => {
+      const ok = await writeClipboard(copyText)
+      if (!ok) {
+        console.error("Failed to copy message")
+        return
+      }
+
+      setCopied(true)
+      if (timeoutRef.current) {
+        clearTimeout(timeoutRef.current)
+      }
+      timeoutRef.current = setTimeout(() => setCopied(false), 1500)
+    })()
   }
 
   if (!isVisible) return null

+ 4 - 0
packages/opencode/webgui/src/lib/ideBridge.ts

@@ -24,6 +24,10 @@ class IdeBridge {
   init() {
     const onMessage = (ev: MessageEvent) => {
       const msg = ev.data as Message
+      if (msg && msg.type === "__ideBridgeReply" && msg.replyTo) {
+        this.dispatch(msg)
+        return
+      }
       this.dispatch(msg)
     }
     window.addEventListener("message", onMessage)