mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-10 03:19:59 -04:00
fix(tui): scope prompt drafts to sessions
This commit is contained in:
committed by
opencode-agent[bot]
parent
84fd347afa
commit
ea74a84ea3
@@ -130,7 +130,7 @@ function formatEditorContext(selection: EditorSelection) {
|
||||
return `<system-reminder>${ranges.join("\n")} This may or may not be relevant to the current task.</system-reminder>\n`
|
||||
}
|
||||
|
||||
let stashed: { prompt: PromptInfo; cursor: number } | undefined
|
||||
const drafts = new Map<string | undefined, { prompt: PromptInfo; cursor: number }>()
|
||||
|
||||
function argumentSlash(input: string, commands: readonly KeymapCommand[]) {
|
||||
const head = parseSlashHead(input, /\s/)
|
||||
@@ -600,22 +600,40 @@ export function Prompt(props: PromptProps) {
|
||||
},
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const saved = stashed
|
||||
stashed = undefined
|
||||
if (store.prompt.text) return
|
||||
if (saved && saved.prompt.text) {
|
||||
input.setText(saved.prompt.text)
|
||||
setStore("prompt", saved.prompt)
|
||||
restoreExtmarksFromPrompt(saved.prompt)
|
||||
input.cursorOffset = saved.cursor
|
||||
function saveDraft(sessionID: string | undefined) {
|
||||
if (!store.prompt.text) {
|
||||
drafts.delete(sessionID)
|
||||
return
|
||||
}
|
||||
})
|
||||
drafts.set(sessionID, { prompt: unwrap(store.prompt), cursor: input.cursorOffset })
|
||||
}
|
||||
|
||||
function restoreDraft(sessionID: string | undefined) {
|
||||
const saved = drafts.get(sessionID)
|
||||
drafts.delete(sessionID)
|
||||
ref.reset()
|
||||
if (!saved?.prompt.text) return
|
||||
ref.set(saved.prompt)
|
||||
input.cursorOffset = saved.cursor
|
||||
}
|
||||
|
||||
let draftSessionID = props.sessionID
|
||||
onMount(() => restoreDraft(draftSessionID))
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => props.sessionID,
|
||||
(sessionID) => {
|
||||
saveDraft(draftSessionID)
|
||||
draftSessionID = sessionID
|
||||
restoreDraft(sessionID)
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
|
||||
onCleanup(() => {
|
||||
if (store.prompt.text) {
|
||||
stashed = { prompt: unwrap(store.prompt), cursor: input.cursorOffset }
|
||||
}
|
||||
saveDraft(draftSessionID)
|
||||
setInputTarget(undefined)
|
||||
props.ref?.(undefined)
|
||||
})
|
||||
|
||||
@@ -294,3 +294,72 @@ test("session startup prompt is submitted exactly once", async () => {
|
||||
await server.stop()
|
||||
}
|
||||
})
|
||||
|
||||
test("new session does not inherit the current session prompt draft", async () => {
|
||||
const setup = await createTestRenderer({ width: 80, height: 24, useThread: false })
|
||||
const events = createEventStream()
|
||||
const cwd = process.cwd()
|
||||
const location = { directory: cwd, project: { id: "project", directory: cwd } }
|
||||
const session = {
|
||||
id: "dummy",
|
||||
title: "Demo session",
|
||||
projectID: "project",
|
||||
location: { directory: cwd },
|
||||
agent: "build",
|
||||
model: { providerID: "provider", id: "model" },
|
||||
cost: 0,
|
||||
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
time: { created: 0, updated: 0 },
|
||||
}
|
||||
const calls = createFetch((url) => {
|
||||
if (url.pathname === "/api/location") return json(location)
|
||||
if (url.pathname === "/api/session") return json({ data: [session], cursor: {} })
|
||||
if (url.pathname === "/api/session/dummy") return json({ data: session })
|
||||
if (url.pathname === "/api/session/dummy/message") return json({ data: [], cursor: {} })
|
||||
if (url.pathname === "/api/session/dummy/pending") return json({ data: [] })
|
||||
if (url.pathname === "/api/session/dummy/permission") return json({ data: [] })
|
||||
if (url.pathname === "/api/agent")
|
||||
return json({ location, data: [{ id: "build", mode: "primary", hidden: false, permissions: [] }] })
|
||||
if (url.pathname === "/api/model")
|
||||
return json({ location, data: [{ id: "model", providerID: "provider", name: "Model", variants: [] }] })
|
||||
}, events)
|
||||
const server = Bun.serve({ port: 0, fetch: (request) => calls.fetch(request) })
|
||||
|
||||
try {
|
||||
const { run } = await import("../src/app")
|
||||
const task = Effect.runPromise(
|
||||
run({
|
||||
app: { name: "test", version: "test", channel: "test" },
|
||||
server: { endpoint: { url: server.url.toString() } },
|
||||
config: { get: async () => ({}), update: async () => ({}) },
|
||||
packages: { resolve: async () => undefined },
|
||||
terminalHandoff: async () => ({ renderer: setup.renderer, mode: "dark", complete: () => {} }),
|
||||
args: { sessionID: "dummy" },
|
||||
log: () => {},
|
||||
}).pipe(Effect.provide(AppNodeBuilder.build(Global.node)), Effect.provide(FileSystem.layerNoop({}))),
|
||||
)
|
||||
|
||||
await Promise.race([
|
||||
(async () => {
|
||||
while (!setup.renderer.currentFocusedEditor) await Bun.sleep(10)
|
||||
})(),
|
||||
Bun.sleep(2_000).then(() => {
|
||||
throw new Error("session prompt did not focus")
|
||||
}),
|
||||
])
|
||||
await setup.mockInput.typeText("keep this draft")
|
||||
expect(setup.renderer.currentFocusedEditor?.plainText).toBe("keep this draft")
|
||||
|
||||
setup.mockInput.pressKey("x", { ctrl: true })
|
||||
await Bun.sleep(10)
|
||||
setup.mockInput.pressKey("n")
|
||||
await Bun.sleep(20)
|
||||
|
||||
expect(setup.renderer.currentFocusedEditor?.plainText).toBe("")
|
||||
setup.renderer.destroy()
|
||||
await task
|
||||
} finally {
|
||||
if (!setup.renderer.isDestroyed) setup.renderer.destroy()
|
||||
await server.stop()
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user