fix(tui): preserve streamed code on completion

This commit is contained in:
Xubin Ren
2026-09-03 15:45:57 +08:00
parent 0573cefa3b
commit 41477c2510
4 changed files with 78 additions and 14 deletions
+50
View File
@@ -9,6 +9,7 @@ import {
} from "@opentui/core"
import {
MockTreeSitterClient,
TestRecorder,
createTestRenderer,
type TestRendererSetup,
} from "@opentui/core/testing"
@@ -491,6 +492,17 @@ describe("NanobotTui layout", () => {
expect(ui.composer.plainText).toContain("replacement")
expect(ui.composer.plainText).not.toContain("Image #1")
ui.composer.setText("")
setup.mockInput.pressKey("v", { ctrl: true })
await waitUntil(() => ui.composer.plainText === "[Image #1] ")
ui.composer.cursorOffset = 10
setup.mockInput.pressArrow("left", { shift: true })
await waitUntil(() => ui.composer.cursorOffset === 0)
await setup.mockInput.typeText("replacement")
await waitUntil(() => ui.draft.imageCount === 0)
expect(ui.composer.plainText).toContain("replacement")
expect(ui.composer.plainText).not.toContain("Image #1")
ui.composer.setText("")
setup.mockInput.pressKey("v", { ctrl: true })
await waitUntil(() => ui.composer.plainText === "[Image #1] ")
@@ -2052,6 +2064,44 @@ describe("NanobotTui layout", () => {
}
})
test("keeps streamed fenced code visible while completing the response", async () => {
setup = await createRenderer({ width: 100, height: 30, screenMode: "alternate-screen" })
const app = mount(setup)
const response = [
"Commit types:",
"",
"```text",
"feat:",
"fix:",
"perf:",
"docs:",
"test:",
"refactor:",
"chore:",
"```",
"",
"Include the reason in the body.",
].join("\n")
app.accept({ event: "attached", chat_id: "chat" })
app.accept({ event: "delta", chat_id: "chat", text: response })
await setup.flush()
expect(setup.captureCharFrame()).toContain("feat:")
const recorder = new TestRecorder(setup.renderer)
recorder.rec()
app.accept({ event: "stream_end", chat_id: "chat" })
app.accept({ event: "turn_end", chat_id: "chat" })
await setup.flush()
recorder.stop()
expect(recorder.recordedFrames.length).toBeGreaterThan(0)
expect(recorder.recordedFrames.every(({ frame }) => frame.includes("feat:"))).toBeTrue()
expect(recorder.recordedFrames.every(({ frame }) => (
frame.includes("Include the reason in the body.")
))).toBeTrue()
})
test("renders assistant LaTeX as Unicode text without changing code", async () => {
setup = await createRenderer({ width: 96, height: 24, screenMode: "alternate-screen" })
const app = mount(setup)
+17 -3
View File
@@ -1738,16 +1738,30 @@ export class NanobotTui {
key.preventDefault()
return
}
if (!key.ctrl && !key.meta && !key.shift && (key.name === "left" || key.name === "right")) {
if (!key.ctrl && !key.meta && (key.name === "left" || key.name === "right")) {
const direction = key.name === "left" ? -1 : 1
const cursor = this.composerStringCursor()
const target = this.draft.moveImageCursor(
this.composer.plainText,
this.composerStringCursor(),
cursor,
direction,
)
if (target !== null) {
this.composerCursor = target
this.setComposerStringCursor(this.composer.plainText, target)
if (key.shift) {
const cursorOffset = this.composerOffsetForStringIndex(this.composer.plainText, cursor)
const targetOffset = this.composerOffsetForStringIndex(this.composer.plainText, target)
this.composer.setSelection(
Math.min(cursorOffset, targetOffset),
Math.max(cursorOffset, targetOffset),
)
// OpenTUI 0.5.10 clears the selection through the public cursor
// setter. Move the native edit cursor directly so the placeholder
// remains one selected, replaceable unit.
this.composer.editBuffer.setCursorByOffset(targetOffset)
} else {
this.setComposerStringCursor(this.composer.plainText, target)
}
key.preventDefault()
return
}