diff options
| author | Khang Ha (Kelvin) <[email protected]> | 2026-02-07 05:16:56 +0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-02-06 16:16:56 -0600 |
| commit | fde0b39b7c97dacb78cb55f3d963aa54f61650ea (patch) | |
| tree | 066ebb3f0a2716c9e0ee8e72b55239ad249cd5d1 /packages/app/src | |
| parent | e9a3cfc083bf480ba2c8aaa585a4e914549e3e56 (diff) | |
| download | opencode-fde0b39b7c97dacb78cb55f3d963aa54f61650ea.tar.gz opencode-fde0b39b7c97dacb78cb55f3d963aa54f61650ea.zip | |
fix: properly encode file URLs with special characters (#12424)
Diffstat (limited to 'packages/app/src')
| -rw-r--r-- | packages/app/src/components/file-tree.tsx | 10 | ||||
| -rw-r--r-- | packages/app/src/components/prompt-input/build-request-parts.ts | 10 | ||||
| -rw-r--r-- | packages/app/src/context/file/path.ts | 19 |
3 files changed, 34 insertions, 5 deletions
diff --git a/packages/app/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx index 183c1555b..4a3e27672 100644 --- a/packages/app/src/components/file-tree.tsx +++ b/packages/app/src/components/file-tree.tsx @@ -19,6 +19,14 @@ import { import { Dynamic } from "solid-js/web" import type { FileNode } from "@opencode-ai/sdk/v2" +function pathToFileUrl(filepath: string): string { + const encodedPath = filepath + .split("/") + .map((segment) => encodeURIComponent(segment)) + .join("/") + return `file://${encodedPath}` +} + type Kind = "add" | "del" | "mix" type Filter = { @@ -247,7 +255,7 @@ export default function FileTree(props: { onDragStart={(e: DragEvent) => { if (!draggable()) return e.dataTransfer?.setData("text/plain", `file:${local.node.path}`) - e.dataTransfer?.setData("text/uri-list", `file://${local.node.path}`) + e.dataTransfer?.setData("text/uri-list", pathToFileUrl(local.node.path)) if (e.dataTransfer) e.dataTransfer.effectAllowed = "copy" const dragImage = document.createElement("div") diff --git a/packages/app/src/components/prompt-input/build-request-parts.ts b/packages/app/src/components/prompt-input/build-request-parts.ts index 4cf2f29ac..7010a1fd8 100644 --- a/packages/app/src/components/prompt-input/build-request-parts.ts +++ b/packages/app/src/components/prompt-input/build-request-parts.ts @@ -30,6 +30,12 @@ type BuildRequestPartsInput = { const absolute = (directory: string, path: string) => path.startsWith("/") ? path : (directory + "/" + path).replace("//", "/") +const encodeFilePath = (filepath: string): string => + filepath + .split("/") + .map((segment) => encodeURIComponent(segment)) + .join("/") + const fileQuery = (selection: FileSelection | undefined) => selection ? `?start=${selection.startLine}&end=${selection.endLine}` : "" @@ -99,7 +105,7 @@ export function buildRequestParts(input: BuildRequestPartsInput) { id: Identifier.ascending("part"), type: "file", mime: "text/plain", - url: `file://${path}${fileQuery(attachment.selection)}`, + url: `file://${encodeFilePath(path)}${fileQuery(attachment.selection)}`, filename: getFilename(attachment.path), source: { type: "file", @@ -129,7 +135,7 @@ export function buildRequestParts(input: BuildRequestPartsInput) { const used = new Set(files.map((part) => part.url)) const context = input.context.flatMap((item) => { const path = absolute(input.sessionDirectory, item.path) - const url = `file://${path}${fileQuery(item.selection)}` + const url = `file://${encodeFilePath(path)}${fileQuery(item.selection)}` const comment = item.comment?.trim() if (!comment && used.has(url)) return [] used.add(url) diff --git a/packages/app/src/context/file/path.ts b/packages/app/src/context/file/path.ts index ced30d0fd..155f05aaf 100644 --- a/packages/app/src/context/file/path.ts +++ b/packages/app/src/context/file/path.ts @@ -72,12 +72,27 @@ export function unquoteGitPath(input: string) { return new TextDecoder().decode(new Uint8Array(bytes)) } +export function decodeFilePath(input: string) { + try { + return decodeURIComponent(input) + } catch { + return input + } +} + +export function encodeFilePath(filepath: string): string { + return filepath + .split("/") + .map((segment) => encodeURIComponent(segment)) + .join("/") +} + export function createPathHelpers(scope: () => string) { const normalize = (input: string) => { const root = scope() const prefix = root.endsWith("/") ? root : root + "/" - let path = unquoteGitPath(stripQueryAndHash(stripFileProtocol(input))) + let path = unquoteGitPath(decodeFilePath(stripQueryAndHash(stripFileProtocol(input)))) if (path.startsWith(prefix)) { path = path.slice(prefix.length) @@ -100,7 +115,7 @@ export function createPathHelpers(scope: () => string) { const tab = (input: string) => { const path = normalize(input) - return `file://${path}` + return `file://${encodeFilePath(path)}` } const pathFromTab = (tabValue: string) => { |
