mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-09-04 10:11:46 +03:00
fix(tui): preserve streamed code on completion
This commit is contained in:
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user