summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/app/src/context/file.tsx58
-rw-r--r--packages/app/src/pages/session.tsx2
-rw-r--r--packages/opencode/src/file/index.ts10
-rw-r--r--packages/opencode/src/session/summary.ts69
-rw-r--r--packages/opencode/src/snapshot/index.ts4
5 files changed, 135 insertions, 8 deletions
diff --git a/packages/app/src/context/file.tsx b/packages/app/src/context/file.tsx
index 20fc980f6..afde451ff 100644
--- a/packages/app/src/context/file.tsx
+++ b/packages/app/src/context/file.tsx
@@ -57,6 +57,62 @@ function stripQueryAndHash(input: string) {
return input
}
+function unquoteGitPath(input: string) {
+ if (!input.startsWith('"')) return input
+ if (!input.endsWith('"')) return input
+ const body = input.slice(1, -1)
+ const bytes: number[] = []
+
+ for (let i = 0; i < body.length; i++) {
+ const char = body[i]!
+ if (char !== "\\") {
+ bytes.push(char.charCodeAt(0))
+ continue
+ }
+
+ const next = body[i + 1]
+ if (!next) {
+ bytes.push("\\".charCodeAt(0))
+ continue
+ }
+
+ if (next >= "0" && next <= "7") {
+ const chunk = body.slice(i + 1, i + 4)
+ const match = chunk.match(/^[0-7]{1,3}/)
+ if (!match) {
+ bytes.push(next.charCodeAt(0))
+ i++
+ continue
+ }
+ bytes.push(parseInt(match[0], 8))
+ i += match[0].length
+ continue
+ }
+
+ const escaped =
+ next === "n"
+ ? "\n"
+ : next === "r"
+ ? "\r"
+ : next === "t"
+ ? "\t"
+ : next === "b"
+ ? "\b"
+ : next === "f"
+ ? "\f"
+ : next === "v"
+ ? "\v"
+ : next === "\\" || next === '"'
+ ? next
+ : undefined
+
+ bytes.push((escaped ?? next).charCodeAt(0))
+ i++
+ }
+
+ return new TextDecoder().decode(new Uint8Array(bytes))
+}
+
export function selectionFromLines(range: SelectedLineRange): FileSelection {
const startLine = Math.min(range.start, range.end)
const endLine = Math.max(range.start, range.end)
@@ -197,7 +253,7 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
const root = directory()
const prefix = root.endsWith("/") ? root : root + "/"
- let path = stripQueryAndHash(stripFileProtocol(input))
+ let path = unquoteGitPath(stripQueryAndHash(stripFileProtocol(input)))
if (path.startsWith(prefix)) {
path = path.slice(prefix.length)
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index bc89b2a48..5e5cba69c 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -1016,7 +1016,7 @@ export default function Page() {
const activeTab = createMemo(() => {
const active = tabs().active()
- if (active) return active
+ if (active) return normalizeTab(active)
if (hasReview()) return "review"
const first = openedTabs()[0]
diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts
index 76b7be4b7..dfa6356a2 100644
--- a/packages/opencode/src/file/index.ts
+++ b/packages/opencode/src/file/index.ts
@@ -206,7 +206,11 @@ export namespace File {
const project = Instance.project
if (project.vcs !== "git") return []
- const diffOutput = await $`git diff --numstat HEAD`.cwd(Instance.directory).quiet().nothrow().text()
+ const diffOutput = await $`git -c core.quotepath=false diff --numstat HEAD`
+ .cwd(Instance.directory)
+ .quiet()
+ .nothrow()
+ .text()
const changedFiles: Info[] = []
@@ -223,7 +227,7 @@ export namespace File {
}
}
- const untrackedOutput = await $`git ls-files --others --exclude-standard`
+ const untrackedOutput = await $`git -c core.quotepath=false ls-files --others --exclude-standard`
.cwd(Instance.directory)
.quiet()
.nothrow()
@@ -248,7 +252,7 @@ export namespace File {
}
// Get deleted files
- const deletedOutput = await $`git diff --name-only --diff-filter=D HEAD`
+ const deletedOutput = await $`git -c core.quotepath=false diff --name-only --diff-filter=D HEAD`
.cwd(Instance.directory)
.quiet()
.nothrow()
diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts
index 611d5f1c6..91a520a9b 100644
--- a/packages/opencode/src/session/summary.ts
+++ b/packages/opencode/src/session/summary.ts
@@ -20,6 +20,62 @@ import { Agent } from "@/agent/agent"
export namespace SessionSummary {
const log = Log.create({ service: "session.summary" })
+ function unquoteGitPath(input: string) {
+ if (!input.startsWith('"')) return input
+ if (!input.endsWith('"')) return input
+ const body = input.slice(1, -1)
+ const bytes: number[] = []
+
+ for (let i = 0; i < body.length; i++) {
+ const char = body[i]!
+ if (char !== "\\") {
+ bytes.push(char.charCodeAt(0))
+ continue
+ }
+
+ const next = body[i + 1]
+ if (!next) {
+ bytes.push("\\".charCodeAt(0))
+ continue
+ }
+
+ if (next >= "0" && next <= "7") {
+ const chunk = body.slice(i + 1, i + 4)
+ const match = chunk.match(/^[0-7]{1,3}/)
+ if (!match) {
+ bytes.push(next.charCodeAt(0))
+ i++
+ continue
+ }
+ bytes.push(parseInt(match[0], 8))
+ i += match[0].length
+ continue
+ }
+
+ const escaped =
+ next === "n"
+ ? "\n"
+ : next === "r"
+ ? "\r"
+ : next === "t"
+ ? "\t"
+ : next === "b"
+ ? "\b"
+ : next === "f"
+ ? "\f"
+ : next === "v"
+ ? "\v"
+ : next === "\\" || next === '"'
+ ? next
+ : undefined
+
+ bytes.push((escaped ?? next).charCodeAt(0))
+ i++
+ }
+
+ return Buffer.from(bytes).toString()
+ }
+
export const summarize = fn(
z.object({
sessionID: z.string(),
@@ -116,7 +172,18 @@ export namespace SessionSummary {
messageID: Identifier.schema("message").optional(),
}),
async (input) => {
- return Storage.read<Snapshot.FileDiff[]>(["session_diff", input.sessionID]).catch(() => [])
+ const diffs = await Storage.read<Snapshot.FileDiff[]>(["session_diff", input.sessionID]).catch(() => [])
+ const next = diffs.map((item) => {
+ const file = unquoteGitPath(item.file)
+ if (file === item.file) return item
+ return {
+ ...item,
+ file,
+ }
+ })
+ const changed = next.some((item, i) => item.file !== diffs[i]?.file)
+ if (changed) Storage.write(["session_diff", input.sessionID], next).catch(() => {})
+ return next
},
)
diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts
index 135bd0944..1c1539090 100644
--- a/packages/opencode/src/snapshot/index.ts
+++ b/packages/opencode/src/snapshot/index.ts
@@ -163,7 +163,7 @@ export namespace Snapshot {
const git = gitdir()
await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow()
const result =
- await $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff ${hash} -- .`
+ await $`git -c core.autocrlf=false -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff ${hash} -- .`
.quiet()
.cwd(Instance.worktree)
.nothrow()
@@ -196,7 +196,7 @@ export namespace Snapshot {
export async function diffFull(from: string, to: string): Promise<FileDiff[]> {
const git = gitdir()
const result: FileDiff[] = []
- for await (const line of $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --no-renames --numstat ${from} ${to} -- .`
+ for await (const line of $`git -c core.autocrlf=false -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --no-renames --numstat ${from} ${to} -- .`
.quiet()
.cwd(Instance.directory)
.nothrow()