refactor(tui): keep fake renderer setup out of app code
Move fake renderer creation into simulation/renderer.ts, which keeps the TestRendererSetup in a module-side WeakMap. createHarness looks it up by renderer, so app.tsx only picks which renderer factory to call.
This commit is contained in:
parent
6fbce7b045
commit
7a76efc4f8
3 changed files with 54 additions and 46 deletions
|
|
@ -8,8 +8,7 @@ import { ClipboardProvider, useClipboard } from "./context/clipboard"
|
||||||
import { ExitProvider, useExit } from "./context/exit"
|
import { ExitProvider, useExit } from "./context/exit"
|
||||||
import { EpilogueProvider } from "./context/epilogue"
|
import { EpilogueProvider } from "./context/epilogue"
|
||||||
import * as Selection from "./util/selection"
|
import * as Selection from "./util/selection"
|
||||||
import { createCliRenderer, MouseButton, type CliRenderer } from "@opentui/core"
|
import { createCliRenderer, MouseButton } from "@opentui/core"
|
||||||
import type { TestRendererSetup } from "@opentui/core/testing"
|
|
||||||
import { RouteProvider, useRoute } from "./context/route"
|
import { RouteProvider, useRoute } from "./context/route"
|
||||||
import {
|
import {
|
||||||
Switch,
|
Switch,
|
||||||
|
|
@ -184,43 +183,31 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
||||||
const simulationEnabled = process.env.OPENCODE_SIMULATION === "1" || process.env.OPENCODE_SIMULATION === "true"
|
const simulationEnabled = process.env.OPENCODE_SIMULATION === "1" || process.env.OPENCODE_SIMULATION === "true"
|
||||||
const result = yield* Effect.scoped(
|
const result = yield* Effect.scoped(
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const acquired = yield* Effect.acquireRelease(
|
const renderer = yield* Effect.acquireRelease(
|
||||||
Effect.tryPromise({
|
Effect.tryPromise({
|
||||||
try: async (): Promise<{ renderer: CliRenderer; setup?: TestRendererSetup }> => {
|
try: () =>
|
||||||
// The fake renderer is a real CliRenderer backed by an in-memory
|
simulationEnabled && process.env.OPENCODE_SIMULATION_RENDERER === "fake"
|
||||||
// screen buffer; everything downstream (keymap, harness, render)
|
? import("./simulation/renderer").then(({ SimulationRenderer }) => SimulationRenderer.create())
|
||||||
// treats both renderer kinds identically.
|
: createCliRenderer({
|
||||||
if (simulationEnabled && process.env.OPENCODE_SIMULATION_RENDERER === "fake") {
|
externalOutputMode: "passthrough",
|
||||||
const { createTestRenderer } = await import("@opentui/core/testing")
|
targetFps: 60,
|
||||||
const setup = await createTestRenderer({
|
gatherStats: false,
|
||||||
width: Number(process.env.OPENCODE_SIMULATION_TUI_WIDTH) || 100,
|
exitOnCtrlC: false,
|
||||||
height: Number(process.env.OPENCODE_SIMULATION_TUI_HEIGHT) || 40,
|
useKittyKeyboard: {},
|
||||||
})
|
autoFocus: false,
|
||||||
return { renderer: setup.renderer, setup }
|
openConsoleOnError: false,
|
||||||
}
|
useMouse: !Flag.OPENCODE_DISABLE_MOUSE && input.config.mouse,
|
||||||
const renderer = await createCliRenderer({
|
consoleOptions: {
|
||||||
externalOutputMode: "passthrough",
|
keyBindings: [{ name: "y", ctrl: true, action: "copy-selection" }],
|
||||||
targetFps: 60,
|
},
|
||||||
gatherStats: false,
|
}),
|
||||||
exitOnCtrlC: false,
|
|
||||||
useKittyKeyboard: {},
|
|
||||||
autoFocus: false,
|
|
||||||
openConsoleOnError: false,
|
|
||||||
useMouse: !Flag.OPENCODE_DISABLE_MOUSE && input.config.mouse,
|
|
||||||
consoleOptions: {
|
|
||||||
keyBindings: [{ name: "y", ctrl: true, action: "copy-selection" }],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return { renderer }
|
|
||||||
},
|
|
||||||
catch: (error) => (error instanceof Error ? error : new Error(String(error))),
|
catch: (error) => (error instanceof Error ? error : new Error(String(error))),
|
||||||
}),
|
}),
|
||||||
(acquired) =>
|
(renderer) =>
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
destroyRenderer(acquired.renderer)
|
destroyRenderer(renderer)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const renderer = acquired.renderer
|
|
||||||
win32DisableProcessedInput()
|
win32DisableProcessedInput()
|
||||||
const keymap = createDefaultOpenTuiKeymap(renderer)
|
const keymap = createDefaultOpenTuiKeymap(renderer)
|
||||||
yield* Effect.acquireRelease(
|
yield* Effect.acquireRelease(
|
||||||
|
|
@ -250,7 +237,7 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
||||||
const simulation = yield* Effect.promise(async () => {
|
const simulation = yield* Effect.promise(async () => {
|
||||||
const { SimulationActions } = await import("./simulation/actions")
|
const { SimulationActions } = await import("./simulation/actions")
|
||||||
const { SimulationServer } = await import("./simulation/server")
|
const { SimulationServer } = await import("./simulation/server")
|
||||||
return SimulationServer.start(SimulationActions.createHarness(renderer, acquired.setup))
|
return SimulationServer.start(SimulationActions.createHarness(renderer))
|
||||||
})
|
})
|
||||||
if (simulation) {
|
if (simulation) {
|
||||||
process.stderr.write(`opencode simulation websocket: ${simulation.url}\n`)
|
process.stderr.write(`opencode simulation websocket: ${simulation.url}\n`)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
import type { CliRenderer, Renderable } from "@opentui/core"
|
import type { CliRenderer, Renderable } from "@opentui/core"
|
||||||
import {
|
import { createMockKeys, createMockMouse, type MockInput, type MockMouse } from "@opentui/core/testing"
|
||||||
createMockKeys,
|
import { SimulationRenderer } from "./renderer"
|
||||||
createMockMouse,
|
|
||||||
type MockInput,
|
|
||||||
type MockMouse,
|
|
||||||
type TestRendererSetup,
|
|
||||||
} from "@opentui/core/testing"
|
|
||||||
import { SimulationTrace } from "./trace"
|
import { SimulationTrace } from "./trace"
|
||||||
|
|
||||||
export interface KeyModifiers {
|
export interface KeyModifiers {
|
||||||
|
|
@ -77,12 +72,13 @@ function hit(renderer: CliRenderer, renderable: Renderable) {
|
||||||
/**
|
/**
|
||||||
* Builds the harness the simulation server drives.
|
* Builds the harness the simulation server drives.
|
||||||
*
|
*
|
||||||
* When the renderer came from `createTestRenderer` (fake renderer), pass its
|
* When the renderer is the fake simulation renderer, its TestRendererSetup
|
||||||
* `TestRendererSetup` so the harness uses the supported testing APIs. Without
|
* provides the supported testing APIs. For the visible terminal renderer the
|
||||||
* it (visible terminal renderer) the harness falls back to `requestRender` +
|
* harness falls back to `requestRender` + `idle` and reading the private
|
||||||
* `idle` and reading the private `currentRenderBuffer`.
|
* `currentRenderBuffer`.
|
||||||
*/
|
*/
|
||||||
export function createHarness(renderer: CliRenderer, setup?: TestRendererSetup): Harness {
|
export function createHarness(renderer: CliRenderer): Harness {
|
||||||
|
const setup = SimulationRenderer.setupFor(renderer)
|
||||||
return {
|
return {
|
||||||
renderer,
|
renderer,
|
||||||
mockInput: setup?.mockInput ?? createMockKeys(renderer),
|
mockInput: setup?.mockInput ?? createMockKeys(renderer),
|
||||||
|
|
|
||||||
25
packages/tui/src/simulation/renderer.ts
Normal file
25
packages/tui/src/simulation/renderer.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import type { CliRenderer } from "@opentui/core"
|
||||||
|
import { createTestRenderer, type TestRendererSetup } from "@opentui/core/testing"
|
||||||
|
|
||||||
|
const setups = new WeakMap<CliRenderer, TestRendererSetup>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the fake simulation renderer: a real CliRenderer backed by an
|
||||||
|
* in-memory screen buffer instead of a terminal. The TestRendererSetup is
|
||||||
|
* kept module-side (keyed by renderer) so the harness can use the supported
|
||||||
|
* testing APIs without app code carrying it around.
|
||||||
|
*/
|
||||||
|
export async function create(): Promise<CliRenderer> {
|
||||||
|
const setup = await createTestRenderer({
|
||||||
|
width: Number(process.env.OPENCODE_SIMULATION_TUI_WIDTH) || 100,
|
||||||
|
height: Number(process.env.OPENCODE_SIMULATION_TUI_HEIGHT) || 40,
|
||||||
|
})
|
||||||
|
setups.set(setup.renderer, setup)
|
||||||
|
return setup.renderer
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupFor(renderer: CliRenderer): TestRendererSetup | undefined {
|
||||||
|
return setups.get(renderer)
|
||||||
|
}
|
||||||
|
|
||||||
|
export * as SimulationRenderer from "./renderer"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue