Compare commits

...

1 Commits

Author SHA1 Message Date
Ryan Vogel f32acba470 feat(tui): undo recent prompt with double escape
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-07-12 17:48:37 +00:00
3 changed files with 68 additions and 1 deletions
+22 -1
View File
@@ -57,6 +57,8 @@ import { usePromptWorkspace } from "./workspace"
import { usePromptMove } from "./move"
import { readLocalAttachment } from "./local-attachment"
import { useLocation } from "../../context/location"
import { Identifier } from "@opencode-ai/core/id/id"
import { createQuickUndo } from "./quick-undo"
registerOpencodeSpinner()
@@ -296,6 +298,7 @@ export function Prompt(props: PromptProps) {
extmarkToPartIndex: new Map(),
interrupt: 0,
})
const quickUndo = createQuickUndo<PromptInfo>()
createEffect(
on(
@@ -394,7 +397,7 @@ export function Prompt(props: PromptProps) {
category: "Session",
hidden: true,
enabled: status().type !== "idle",
run: () => {
run: async () => {
if (auto()?.visible) return
if (!input.focused) return
// TODO: this should be its own command
@@ -404,6 +407,18 @@ export function Prompt(props: PromptProps) {
}
if (!props.sessionID) return
const recent = quickUndo.escape()
if (recent) {
await sdk.client.session.abort({ sessionID: props.sessionID }).catch(() => {})
await sdk.client.session.revert({ sessionID: props.sessionID, messageID: recent.messageID })
input.setText(recent.value.input)
setStore("prompt", recent.value)
restoreExtmarksFromParts(recent.value.parts)
input.gotoBufferEnd()
input.focus()
return
}
setStore("interrupt", store.interrupt + 1)
setTimeout(() => {
@@ -1089,11 +1104,17 @@ export function Prompt(props: PromptProps) {
parts: nonTextParts.filter((x) => x.type === "file"),
})
} else {
const messageID = Identifier.ascending("message")
quickUndo.submitted(messageID, {
input: store.prompt.input,
parts: [...unwrap(store.prompt.parts)],
})
move.startSubmit()
sdk.client.session
.prompt(
{
sessionID,
messageID,
...selectedModel,
agent: agent.name,
model: selectedModel,
@@ -0,0 +1,21 @@
import { describe, expect, test } from "bun:test"
import { createQuickUndo } from "./quick-undo"
describe("quick undo", () => {
test("returns the submitted message on a second escape within two seconds", () => {
const undo = createQuickUndo<string>()
undo.submitted("msg_1", "hello", 1_000)
expect(undo.escape(1_500)).toBeUndefined()
expect(undo.escape(1_750)).toEqual({ messageID: "msg_1", value: "hello" })
expect(undo.escape(1_800)).toBeUndefined()
})
test("expires two seconds after submission", () => {
const undo = createQuickUndo<string>()
undo.submitted("msg_1", "hello", 1_000)
expect(undo.escape(2_500)).toBeUndefined()
expect(undo.escape(3_001)).toBeUndefined()
})
})
@@ -0,0 +1,25 @@
const WINDOW_MS = 2_000
export function createQuickUndo<T>() {
let submitted: { messageID: string; value: T; time: number } | undefined
let escapedAt: number | undefined
return {
submitted(messageID: string, value: T, time = Date.now()) {
submitted = { messageID, value, time }
escapedAt = undefined
},
escape(time = Date.now()) {
if (!submitted || time - submitted.time > WINDOW_MS) return
if (escapedAt === undefined || time - escapedAt > WINDOW_MS) {
escapedAt = time
return
}
const result = { messageID: submitted.messageID, value: submitted.value }
submitted = undefined
escapedAt = undefined
return result
},
}
}