feat(tui): route pasted question answers
This commit is contained in:
parent
6281e35a18
commit
44f4211357
2 changed files with 97 additions and 2 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { createStore } from "solid-js/store"
|
||||
import { createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js"
|
||||
import { useRenderer } from "@opentui/solid"
|
||||
import type { TextareaRenderable } from "@opentui/core"
|
||||
import { usePaste, useRenderer } from "@opentui/solid"
|
||||
import { decodePasteBytes, type TextareaRenderable } from "@opentui/core"
|
||||
import { selectedForeground, tint, useTheme } from "../../context/theme"
|
||||
import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2"
|
||||
import { useSDK } from "../../context/sdk"
|
||||
|
|
@ -130,6 +130,19 @@ export function QuestionPrompt(props: { request: QuestionRequest; directory?: st
|
|||
onCleanup(popMode)
|
||||
})
|
||||
|
||||
usePaste((event) => {
|
||||
if (store.editing) return
|
||||
|
||||
// Question options do not own a text input. Capture bracketed paste at the
|
||||
// renderer level and use it to start editing the custom answer.
|
||||
event.preventDefault()
|
||||
if (confirm() || !custom() || !decodePasteBytes(event.bytes).trim()) return
|
||||
|
||||
setStore("selected", options().length)
|
||||
setStore("editing", true)
|
||||
queueMicrotask(() => textarea?.handlePaste(event))
|
||||
})
|
||||
|
||||
useBindings(() => ({
|
||||
mode: QUESTION_MODE,
|
||||
enabled: store.editing && !confirm(),
|
||||
|
|
|
|||
82
packages/tui/test/cli/tui/question-paste.test.tsx
Normal file
82
packages/tui/test/cli/tui/question-paste.test.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/** @jsxImportSource @opentui/solid */
|
||||
import { TextareaRenderable } from "@opentui/core"
|
||||
import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui"
|
||||
import { testRender, useRenderer } from "@opentui/solid"
|
||||
import type { QuestionRequest } from "@opencode-ai/sdk/v2"
|
||||
import { expect, test } from "bun:test"
|
||||
import { mkdir } from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
import { onCleanup } from "solid-js"
|
||||
import { tmpdir } from "../../fixture/fixture"
|
||||
import { TestTuiContexts } from "../../fixture/tui-environment"
|
||||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||
import { TuiConfigProvider } from "../../../src/config"
|
||||
import { KVProvider } from "../../../src/context/kv"
|
||||
import { SDKProvider } from "../../../src/context/sdk"
|
||||
import { ThemeProvider } from "../../../src/context/theme"
|
||||
import { OpencodeKeymapProvider, registerOpencodeKeymap } from "../../../src/keymap"
|
||||
import { QuestionPrompt } from "../../../src/routes/session/question"
|
||||
|
||||
async function wait(fn: () => boolean, timeout = 2000) {
|
||||
const start = Date.now()
|
||||
while (!fn()) {
|
||||
if (Date.now() - start > timeout) throw new Error("timed out waiting for condition")
|
||||
await Bun.sleep(10)
|
||||
}
|
||||
}
|
||||
|
||||
test("pasting while selecting starts a custom answer", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const state = path.join(tmp.path, "state")
|
||||
await mkdir(state, { recursive: true })
|
||||
await Bun.write(path.join(state, "kv.json"), "{}")
|
||||
|
||||
const request = {
|
||||
id: "question-1",
|
||||
sessionID: "session-1",
|
||||
questions: [
|
||||
{
|
||||
header: "Approach",
|
||||
question: "How should this be implemented?",
|
||||
options: [{ label: "Suggested", description: "Use the suggested approach" }],
|
||||
},
|
||||
],
|
||||
} satisfies QuestionRequest
|
||||
|
||||
function Harness() {
|
||||
const renderer = useRenderer()
|
||||
const config = createTuiResolvedConfig()
|
||||
const keymap = createDefaultOpenTuiKeymap(renderer)
|
||||
const off = registerOpencodeKeymap(keymap, renderer, config)
|
||||
onCleanup(off)
|
||||
|
||||
return (
|
||||
<TestTuiContexts directory={tmp.path} paths={{ state }}>
|
||||
<SDKProvider url="http://test" events={{ subscribe: async () => () => {} }}>
|
||||
<OpencodeKeymapProvider keymap={keymap}>
|
||||
<TuiConfigProvider config={config}>
|
||||
<KVProvider>
|
||||
<ThemeProvider mode="dark">
|
||||
<QuestionPrompt request={request} />
|
||||
</ThemeProvider>
|
||||
</KVProvider>
|
||||
</TuiConfigProvider>
|
||||
</OpencodeKeymapProvider>
|
||||
</SDKProvider>
|
||||
</TestTuiContexts>
|
||||
)
|
||||
}
|
||||
|
||||
const app = await testRender(() => <Harness />, { width: 80, height: 20 })
|
||||
try {
|
||||
await wait(() => app.captureCharFrame().includes("How should this be implemented?"))
|
||||
await app.mockInput.pasteBracketedText("Use the existing state machine")
|
||||
await wait(() => app.renderer.currentFocusedEditor instanceof TextareaRenderable)
|
||||
|
||||
const textarea = app.renderer.currentFocusedEditor
|
||||
if (!(textarea instanceof TextareaRenderable)) throw new Error("expected focused custom answer textarea")
|
||||
expect(textarea.plainText).toBe("Use the existing state machine")
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue