tui: simplify prompt input sync
Drop the burst-aware key interceptor that tried to keep derived prompt state current for every control binding. Frame-batch content updates only, and let exit, clear, stash, and autocomplete read or flush live textarea state at the command boundary instead.
This commit is contained in:
parent
9f72479515
commit
f870e771e9
6 changed files with 65 additions and 227 deletions
|
|
@ -1,5 +1,4 @@
|
|||
import { expect, mock, test } from "bun:test"
|
||||
import { TextareaRenderable } from "@opentui/core"
|
||||
import { createTestRenderer } from "@opentui/core/testing"
|
||||
import { Effect, FileSystem } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
|
|
@ -215,87 +214,3 @@ test("session startup prompt is submitted exactly once", async () => {
|
|||
mock.restore()
|
||||
}
|
||||
})
|
||||
|
||||
test("raw text bursts coalesce prompt synchronization without hiding text from control keys", async () => {
|
||||
const setup = await createTestRenderer({ width: 80, height: 24, useThread: false })
|
||||
const core = await import("@opentui/core")
|
||||
mock.module("@opentui/core", () => ({ ...core, createCliRenderer: async () => setup.renderer }))
|
||||
const events = createEventStream()
|
||||
const calls = createFetch(undefined, 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 () => ({ keybinds: { input_clear: "q, z" } }), update: async () => ({}) },
|
||||
packages: { resolve: async () => undefined },
|
||||
args: {},
|
||||
log: () => {},
|
||||
}).pipe(Effect.provide(AppNodeBuilder.build(Global.node)), Effect.provide(FileSystem.layerNoop({}))),
|
||||
)
|
||||
while (!(setup.renderer.currentFocusedEditor instanceof TextareaRenderable)) {
|
||||
await Bun.sleep(10)
|
||||
}
|
||||
const input = setup.renderer.currentFocusedEditor
|
||||
const readPlainText = Object.getOwnPropertyDescriptor(
|
||||
Object.getPrototypeOf(TextareaRenderable.prototype),
|
||||
"plainText",
|
||||
)?.get?.bind(input)
|
||||
if (!readPlainText) throw new Error("Textarea plainText getter is missing")
|
||||
let plainTextReads = 0
|
||||
Object.defineProperty(input, "plainText", {
|
||||
get() {
|
||||
plainTextReads++
|
||||
return readPlainText()
|
||||
},
|
||||
})
|
||||
const text = "x".repeat(999) + "!"
|
||||
setup.renderer.stdin.emit("data", Buffer.from(text))
|
||||
setup.renderer.stdin.emit("data", Buffer.from("\x04"))
|
||||
await Bun.sleep(50)
|
||||
|
||||
expect(setup.renderer.isDestroyed).toBe(false)
|
||||
expect(readPlainText()).toBe(text)
|
||||
expect(plainTextReads).toBeLessThanOrEqual(3)
|
||||
|
||||
plainTextReads = 0
|
||||
const dribbled = "y".repeat(100)
|
||||
for (const character of dribbled) {
|
||||
setup.renderer.stdin.emit("data", Buffer.from(character))
|
||||
await Promise.resolve()
|
||||
}
|
||||
await Bun.sleep(50)
|
||||
|
||||
expect(readPlainText()).toBe(text + dribbled)
|
||||
expect(plainTextReads).toBe(1)
|
||||
|
||||
plainTextReads = 0
|
||||
for (const backspace of "\x7f".repeat(dribbled.length)) {
|
||||
setup.renderer.stdin.emit("data", Buffer.from(backspace))
|
||||
await Promise.resolve()
|
||||
}
|
||||
await Bun.sleep(50)
|
||||
|
||||
expect(readPlainText()).toBe(text)
|
||||
expect(plainTextReads).toBe(1)
|
||||
|
||||
input.clear()
|
||||
await Bun.sleep(50)
|
||||
plainTextReads = 0
|
||||
setup.renderer.stdin.emit("data", Buffer.from("az"))
|
||||
await Bun.sleep(50)
|
||||
|
||||
expect(readPlainText()).toBe("")
|
||||
expect(plainTextReads).toBeLessThanOrEqual(2)
|
||||
|
||||
setup.renderer.destroy()
|
||||
await task
|
||||
} finally {
|
||||
if (!setup.renderer.isDestroyed) setup.renderer.destroy()
|
||||
await server.stop()
|
||||
mock.restore()
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -30,15 +30,4 @@ describe("prompt display", () => {
|
|||
expect(mentionTriggerIndex("foo@bar.com")).toBeUndefined()
|
||||
expect(mentionTriggerIndex("中文 @src file")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("skips display-width conversion when text has no mention", () => {
|
||||
const value = {
|
||||
includes: () => false,
|
||||
[Symbol.toPrimitive]() {
|
||||
throw new Error("display width conversion should be skipped")
|
||||
},
|
||||
}
|
||||
|
||||
expect(Reflect.apply(mentionTriggerIndex, undefined, [value])).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue