diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx
index 54cc86a40d0a..c8e9fe75e506 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx
@@ -46,15 +46,62 @@ function filetype(input?: string) {
return language
}
+interface FileEntry {
+ relativePath: string
+ patch: string
+ filePath: string
+ type: string
+ additions: number
+ deletions: number
+}
+
+function fileTitle(file?: FileEntry) {
+ if (!file) return ""
+ if (file.type === "delete") return "Deleted " + file.relativePath
+ if (file.type === "add") return "Created " + file.relativePath
+ return "Patched " + file.relativePath
+}
+
function EditBody(props: { request: PermissionRequest }) {
const themeState = useTheme()
const theme = themeState.theme
const syntax = themeState.syntax
const config = useTuiConfig()
const dimensions = useTerminalDimensions()
+ const dialog = useDialog()
+
+ const files = createMemo(() => {
+ const raw = props.request.metadata?.files
+ return Array.isArray(raw) ? (raw as FileEntry[]) : []
+ })
+
+ const [store, setStore] = createStore({ fileIndex: 0 })
+
+ const isMultiFile = createMemo(() => files().length > 1)
+ const currentFile = createMemo(() => files()[store.fileIndex])
- const filepath = createMemo(() => (props.request.metadata?.filepath as string) ?? "")
- const diff = createMemo(() => (props.request.metadata?.diff as string) ?? "")
+ const filepath = createMemo(() => {
+ if (isMultiFile()) return currentFile()?.filePath ?? ""
+ return (props.request.metadata?.filepath as string) ?? ""
+ })
+ const diff = createMemo(() => {
+ if (isMultiFile()) return currentFile()?.patch ?? ""
+ return (props.request.metadata?.diff as string) ?? ""
+ })
+
+ useKeyboard((evt) => {
+ if (dialog.stack.length > 0) return
+ if (!isMultiFile()) return
+
+ if (evt.name === "]") {
+ evt.preventDefault()
+ setStore("fileIndex", (i) => (i + 1) % files().length)
+ }
+ if (evt.name === "[") {
+ evt.preventDefault()
+ setStore("fileIndex", (i) => (i - 1 + files().length) % files().length)
+ }
+ })
const view = createMemo(() => {
const diffStyle = config.diff_style
@@ -67,6 +114,14 @@ function EditBody(props: { request: PermissionRequest }) {
return (
+
+
+ {fileTitle(currentFile())}
+
+ {store.fileIndex + 1}/{files().length}
+
+
+
1
return {
icon: "→",
title: `Edit ${normalizePath(filepath)}`,
body: ,
+ hints: hasMultipleFiles ? (
+
+ {"[ ]"} files
+
+ ) : undefined,
}
}
@@ -437,6 +499,7 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
options={{ once: "Allow once", always: "Allow always", reject: "Reject" }}
escapeKey="reject"
fullscreen
+ hints={current.hints}
onSelect={(option) => {
if (option === "always") {
setStore("stage", "always")
@@ -550,6 +613,7 @@ function Prompt>(props: {
options: T
escapeKey?: keyof T
fullscreen?: boolean
+ hints?: JSX.Element
onSelect: (option: keyof T) => void
}) {
const { theme } = useTheme()
@@ -667,6 +731,7 @@ function Prompt>(props: {
+ {props.hints}
{"ctrl+f"} {hint()}