From c61cc1b635af528c30bb0f5dea964fd45e19ca27 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 28 Sep 2025 03:44:11 -0400 Subject: [PATCH] undo/redo --- .../cmd/tui/component/prompt/autocomplete.tsx | 39 ++++++++++++++++++- .../src/cli/cmd/tui/context/keybind.tsx | 3 ++ .../src/cli/cmd/tui/routes/session/index.tsx | 32 +++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index e56673fd7..1353551ca 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -110,12 +110,47 @@ export function Autocomplete(props: { { display: "/undo", description: "undo the last message", - onSelect: () => {}, + onSelect: () => { + const revert = s.revert?.messageID + const message = (sync.data.message[s.id] ?? []).findLast( + (x) => (!revert || x.id < revert) && x.role === "user", + ) + if (!message) return + sdk.session.revert({ + path: { + id: s.id, + }, + body: { + messageID: message.id, + }, + }) + }, }, { display: "/redo", description: "redo the last message", - onSelect: () => {}, + onSelect: () => { + const messageID = s.revert?.messageID + if (!messageID) return + const messages = sync.data.message[s.id] ?? [] + const message = messages.find((x) => x.role === "user" && x.id > messageID) + if (!message) { + sdk.session.unrevert({ + path: { + id: s.id, + }, + }) + return + } + sdk.session.revert({ + path: { + id: s.id, + }, + body: { + messageID: message.id, + }, + }) + }, }, { display: "/compact", diff --git a/packages/opencode/src/cli/cmd/tui/context/keybind.tsx b/packages/opencode/src/cli/cmd/tui/context/keybind.tsx index dc221b7bf..3a11eab08 100644 --- a/packages/opencode/src/cli/cmd/tui/context/keybind.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/keybind.tsx @@ -56,6 +56,9 @@ export const { use: useKeybind, provider: KeybindProvider } = createSimpleContex if (store.leader && evt.name) { setImmediate(() => { + if (focus && renderer.currentFocusedRenderable === focus) { + focus.focus() + } leader(false) }) } diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 620ba0d88..1e0c9ba67 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -102,6 +102,19 @@ export function Session() { }, ]) + const revert = createMemo(() => { + const s = session() + if (!s) return + const messageID = s.revert?.messageID + if (!messageID) return + const reverted = messages().filter((x) => x.id >= messageID && x.role === "user") + + return { + messageID, + reverted, + } + }) + return ( @@ -118,6 +131,25 @@ export function Session() { {(message, index) => ( + + + + {revert()!.reverted.length} message reverted + + {keybind.print("messages_redo")} or /redo to restore + + + + + = revert()!.messageID}> + <> +