From a1f3d21cef4a492258922ed61e3d91a6ac368294 Mon Sep 17 00:00:00 2001 From: Nitish Agarwal <1592163+nitishagar@users.noreply.github.com> Date: Sat, 18 Apr 2026 12:07:16 +0530 Subject: [PATCH] fix: add file cycling in multi-file permission prompt --- .../cli/cmd/tui/routes/session/permission.tsx | 69 ++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) 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()}