summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src
diff options
context:
space:
mode:
authorKhang Ha (Kelvin) <[email protected]>2026-02-07 05:16:56 +0700
committerGitHub <[email protected]>2026-02-06 16:16:56 -0600
commitfde0b39b7c97dacb78cb55f3d963aa54f61650ea (patch)
tree066ebb3f0a2716c9e0ee8e72b55239ad249cd5d1 /packages/app/src
parente9a3cfc083bf480ba2c8aaa585a4e914549e3e56 (diff)
downloadopencode-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.tsx10
-rw-r--r--packages/app/src/components/prompt-input/build-request-parts.ts10
-rw-r--r--packages/app/src/context/file/path.ts19
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) => {