mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
feat: enhance openImage function to handle vscode webview CDN URLs
This commit is contained in:
parent
e71ec43d0b
commit
addd1bc6e7
2 changed files with 125 additions and 21 deletions
76
src/integrations/misc/__tests__/image-handler.spec.ts
Normal file
76
src/integrations/misc/__tests__/image-handler.spec.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
vi.mock("vscode", () => {
|
||||
const executeCommand = vi.fn()
|
||||
const writeText = vi.fn()
|
||||
const showInformationMessage = vi.fn()
|
||||
const showErrorMessage = vi.fn()
|
||||
const file = vi.fn((p: string) => ({ fsPath: p, path: p, scheme: "file" }))
|
||||
const parse = (input: string) => {
|
||||
if (input.startsWith("https://") && input.includes("vscode-cdn.net")) {
|
||||
const url = new URL(input)
|
||||
return {
|
||||
scheme: "https",
|
||||
authority: url.host,
|
||||
path: url.pathname,
|
||||
fsPath: url.pathname,
|
||||
with: vi.fn(),
|
||||
}
|
||||
}
|
||||
if (input.startsWith("file://")) {
|
||||
return {
|
||||
scheme: "file",
|
||||
authority: "",
|
||||
path: input.substring("file://".length),
|
||||
fsPath: input.substring("file://".length),
|
||||
with: vi.fn(),
|
||||
}
|
||||
}
|
||||
return {
|
||||
scheme: "file",
|
||||
authority: "",
|
||||
path: input,
|
||||
fsPath: input,
|
||||
with: vi.fn(),
|
||||
}
|
||||
}
|
||||
return {
|
||||
commands: { executeCommand },
|
||||
env: { clipboard: { writeText } },
|
||||
window: { showInformationMessage, showErrorMessage },
|
||||
Uri: { file, parse },
|
||||
}
|
||||
})
|
||||
|
||||
import * as vscode from "vscode"
|
||||
import { openImage } from "../image-handler"
|
||||
|
||||
describe("openImage - vscode webview CDN url handling", () => {
|
||||
const cdnUrlPosix = "https://file+.vscode-resource.vscode-cdn.net/file//Users/test/workspace/image.png"
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
test("opens image from vscode-cdn webview URL by stripping /file/ and normalizing", async () => {
|
||||
await openImage(cdnUrlPosix)
|
||||
|
||||
// Should normalize to /Users/test/workspace/image.png and open it
|
||||
expect((vscode.Uri.file as any).mock.calls.length).toBe(1)
|
||||
const calledWithPath = (vscode.Uri.file as any).mock.calls[0][0]
|
||||
expect(calledWithPath).toBe(require("path").normalize("/Users/test/workspace/image.png"))
|
||||
|
||||
expect(vscode.commands.executeCommand).toHaveBeenCalledTimes(1)
|
||||
expect(vscode.commands.executeCommand).toHaveBeenCalledWith(
|
||||
"vscode.open",
|
||||
expect.objectContaining({ fsPath: calledWithPath }),
|
||||
)
|
||||
expect(vscode.window.showErrorMessage).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("copy action writes normalized fs path to clipboard (no open)", async () => {
|
||||
await openImage(cdnUrlPosix, { values: { action: "copy" } })
|
||||
|
||||
const expectedPath = require("path").normalize("/Users/test/workspace/image.png")
|
||||
expect(vscode.env.clipboard.writeText).toHaveBeenCalledWith(expectedPath)
|
||||
expect(vscode.commands.executeCommand).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
|
@ -6,18 +6,51 @@ import { getWorkspacePath } from "../../utils/path"
|
|||
import { t } from "../../i18n"
|
||||
|
||||
export async function openImage(dataUriOrPath: string, options?: { values?: { action?: string } }) {
|
||||
// Check if it's a file path (absolute or relative)
|
||||
// Minimal handling for VS Code webview CDN URLs:
|
||||
// Example: https://file+.vscode-resource.vscode-cdn.net/file/<absolute_path_to_image>
|
||||
try {
|
||||
const u = vscode.Uri.parse(dataUriOrPath)
|
||||
if (u.scheme === "https" && u.authority.includes("vscode-cdn.net")) {
|
||||
let fsPath = decodeURIComponent(u.path || "")
|
||||
// Strip the leading "/file/" prefix if present
|
||||
if (fsPath.startsWith("/file/")) {
|
||||
fsPath = fsPath.slice("/file/".length)
|
||||
}
|
||||
|
||||
fsPath = path.normalize(fsPath)
|
||||
if (fsPath) {
|
||||
const fileUri = vscode.Uri.file(fsPath)
|
||||
await vscode.commands.executeCommand("vscode.open", fileUri)
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
|
||||
// Handle file:// URIs directly
|
||||
if (dataUriOrPath.startsWith("file://")) {
|
||||
try {
|
||||
const fileUri = vscode.Uri.parse(dataUriOrPath)
|
||||
|
||||
await vscode.commands.executeCommand("vscode.open", fileUri)
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(t("common:errors.error_opening_image", { error }))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Fallback: treat plain strings (absolute or relative) as file paths
|
||||
const isFilePath =
|
||||
!dataUriOrPath.startsWith("data:") &&
|
||||
!dataUriOrPath.startsWith("http:") &&
|
||||
!dataUriOrPath.startsWith("https:") &&
|
||||
!dataUriOrPath.startsWith("vscode-resource:") &&
|
||||
!dataUriOrPath.startsWith("file+.vscode-resource")
|
||||
!dataUriOrPath.startsWith("file+.vscode-resource") &&
|
||||
!dataUriOrPath.startsWith("vscode-webview-resource:")
|
||||
|
||||
if (isFilePath) {
|
||||
// Handle file path - open directly in VSCode
|
||||
try {
|
||||
// Resolve the path relative to workspace if needed
|
||||
let filePath = dataUriOrPath
|
||||
if (!path.isAbsolute(filePath)) {
|
||||
const workspacePath = getWorkspacePath()
|
||||
|
|
@ -28,14 +61,12 @@ export async function openImage(dataUriOrPath: string, options?: { values?: { ac
|
|||
|
||||
const fileUri = vscode.Uri.file(filePath)
|
||||
|
||||
// Check if this is a copy action
|
||||
if (options?.values?.action === "copy") {
|
||||
await vscode.env.clipboard.writeText(filePath)
|
||||
vscode.window.showInformationMessage(t("common:info.path_copied_to_clipboard"))
|
||||
return
|
||||
}
|
||||
|
||||
// Open the image file directly
|
||||
await vscode.commands.executeCommand("vscode.open", fileUri)
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(t("common:errors.error_opening_image", { error }))
|
||||
|
|
@ -43,47 +74,44 @@ export async function openImage(dataUriOrPath: string, options?: { values?: { ac
|
|||
return
|
||||
}
|
||||
|
||||
// Finally, handle base64 data URIs explicitly
|
||||
const matches = dataUriOrPath.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/)
|
||||
if (!matches) {
|
||||
vscode.window.showErrorMessage(t("common:errors.invalid_data_uri"))
|
||||
// Do not show an "invalid data URI" error for non-data URIs; try opening as a generic URI
|
||||
try {
|
||||
const generic = vscode.Uri.parse(dataUriOrPath)
|
||||
await vscode.commands.executeCommand("vscode.open", generic)
|
||||
} catch {
|
||||
vscode.window.showErrorMessage(t("common:errors.invalid_data_uri"))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const [, format, base64Data] = matches
|
||||
const imageBuffer = Buffer.from(base64Data, "base64")
|
||||
|
||||
// Default behavior: open the image
|
||||
const tempFilePath = path.join(os.tmpdir(), `temp_image_${Date.now()}.${format}`)
|
||||
try {
|
||||
await vscode.workspace.fs.writeFile(vscode.Uri.file(tempFilePath), imageBuffer)
|
||||
// Check if this is a copy action
|
||||
|
||||
if (options?.values?.action === "copy") {
|
||||
try {
|
||||
// Read the image file
|
||||
const imageData = await vscode.workspace.fs.readFile(vscode.Uri.file(tempFilePath))
|
||||
|
||||
// Convert to base64 for clipboard
|
||||
const base64Image = Buffer.from(imageData).toString("base64")
|
||||
const dataUri = `data:image/${format};base64,${base64Image}`
|
||||
|
||||
// Use vscode.env.clipboard to copy the data URI
|
||||
// Note: VSCode doesn't support copying binary image data directly to clipboard
|
||||
// So we copy the data URI which can be pasted in many applications
|
||||
await vscode.env.clipboard.writeText(dataUri)
|
||||
|
||||
vscode.window.showInformationMessage(t("common:info.image_copied_to_clipboard"))
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
vscode.window.showErrorMessage(t("common:errors.error_copying_image", { errorMessage }))
|
||||
} finally {
|
||||
// Clean up temp file
|
||||
try {
|
||||
await vscode.workspace.fs.delete(vscode.Uri.file(tempFilePath))
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
await vscode.commands.executeCommand("vscode.open", vscode.Uri.file(tempFilePath))
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(t("common:errors.error_opening_image", { error }))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue