mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-23 22:34:53 +00:00
fix ctrl-c/fix flickering
This commit is contained in:
@@ -26,6 +26,7 @@ export class RunFooter implements FooterApi {
|
||||
private rows = TEXTAREA_MIN_ROWS
|
||||
private state: Accessor<FooterState>
|
||||
private setState: Setter<FooterState>
|
||||
private settle = false
|
||||
|
||||
constructor(
|
||||
private renderer: CliRenderer,
|
||||
@@ -91,12 +92,14 @@ export class RunFooter implements FooterApi {
|
||||
return
|
||||
}
|
||||
|
||||
this.setState((state) => ({
|
||||
phase: next.phase ?? state.phase,
|
||||
status: typeof next.status === "string" ? next.status : state.status,
|
||||
queue: typeof next.queue === "number" ? Math.max(0, next.queue) : state.queue,
|
||||
model: typeof next.model === "string" ? next.model : state.model,
|
||||
}))
|
||||
const prev = this.state()
|
||||
const state = {
|
||||
phase: next.phase ?? prev.phase,
|
||||
status: typeof next.status === "string" ? next.status : prev.status,
|
||||
queue: typeof next.queue === "number" ? Math.max(0, next.queue) : prev.queue,
|
||||
model: typeof next.model === "string" ? next.model : prev.model,
|
||||
}
|
||||
this.setState(state)
|
||||
}
|
||||
|
||||
public append(kind: EntryKind, text: string): void {
|
||||
@@ -109,6 +112,7 @@ export class RunFooter implements FooterApi {
|
||||
}
|
||||
|
||||
this.renderer.writeToScrollback(entryWriter(kind, text, new Date()))
|
||||
this.scheduleSettleRender()
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
@@ -117,10 +121,6 @@ export class RunFooter implements FooterApi {
|
||||
}
|
||||
|
||||
this.notifyClose()
|
||||
|
||||
if (!this.renderer.isDestroyed) {
|
||||
this.renderer.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
@@ -208,4 +208,21 @@ export class RunFooter implements FooterApi {
|
||||
private handleDestroy = (): void => {
|
||||
this.notifyClose()
|
||||
}
|
||||
|
||||
private scheduleSettleRender(): void {
|
||||
if (this.settle || this.destroyed || this.renderer.isDestroyed) {
|
||||
return
|
||||
}
|
||||
|
||||
this.settle = true
|
||||
void this.renderer.idle().then(() => {
|
||||
this.settle = false
|
||||
|
||||
if (this.destroyed || this.renderer.isDestroyed || this.closed) {
|
||||
return
|
||||
}
|
||||
|
||||
this.renderer.requestRender()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +166,7 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||
let area: Area | undefined
|
||||
let leader = false
|
||||
let timeout: NodeJS.Timeout | undefined
|
||||
let rowsTick = false
|
||||
|
||||
const clearLeader = () => {
|
||||
leader = false
|
||||
@@ -192,6 +193,18 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||
props.onRows(clampRows(area.virtualLineCount || 1))
|
||||
}
|
||||
|
||||
const scheduleRows = () => {
|
||||
if (rowsTick) {
|
||||
return
|
||||
}
|
||||
|
||||
rowsTick = true
|
||||
queueMicrotask(() => {
|
||||
rowsTick = false
|
||||
syncRows()
|
||||
})
|
||||
}
|
||||
|
||||
const push = (text: string) => {
|
||||
if (!text) {
|
||||
return
|
||||
@@ -289,6 +302,12 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||
}
|
||||
|
||||
const onKeyDown = (event: Key) => {
|
||||
if (event.ctrl && event.name === "c") {
|
||||
props.onExit()
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
if (handleCycle(event)) {
|
||||
return
|
||||
}
|
||||
@@ -329,36 +348,33 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||
|
||||
push(text)
|
||||
area.setText("")
|
||||
syncRows()
|
||||
scheduleRows()
|
||||
area.focus()
|
||||
}
|
||||
|
||||
const onLineInfoChange = () => {
|
||||
syncRows()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!area || area.isDestroyed) {
|
||||
return
|
||||
}
|
||||
|
||||
area.on("line-info-change", onLineInfoChange)
|
||||
syncRows()
|
||||
area.on("line-info-change", scheduleRows)
|
||||
scheduleRows()
|
||||
area.focus()
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
clearLeader()
|
||||
|
||||
if (!area || area.isDestroyed) {
|
||||
return
|
||||
}
|
||||
|
||||
area.off("line-info-change", onLineInfoChange)
|
||||
area.off("line-info-change", scheduleRows)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
term().width
|
||||
queueMicrotask(syncRows)
|
||||
scheduleRows()
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
@@ -443,7 +459,7 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||
keyBindings={bindings()}
|
||||
onSubmit={onSubmit}
|
||||
onKeyDown={onKeyDown}
|
||||
onContentChange={syncRows}
|
||||
onContentChange={scheduleRows}
|
||||
ref={(item) => {
|
||||
area = item as Area
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createCliRenderer } from "@opentui/core"
|
||||
import { createCliRenderer, type CliRenderer } from "@opentui/core"
|
||||
import { TuiConfig } from "../../../config/tui"
|
||||
import { RunFooter } from "./footer"
|
||||
import { formatUnknownError, runPromptTurn } from "./stream"
|
||||
@@ -13,6 +13,24 @@ const DEFAULT_KEYBINDS: FooterKeybinds = {
|
||||
inputNewline: "shift+return,ctrl+return,alt+return,ctrl+j",
|
||||
}
|
||||
|
||||
function shutdown(renderer: CliRenderer): void {
|
||||
if (renderer.isDestroyed) {
|
||||
return
|
||||
}
|
||||
|
||||
if (renderer.externalOutputMode === "capture-stdout") {
|
||||
renderer.externalOutputMode = "passthrough"
|
||||
}
|
||||
|
||||
if (renderer.screenMode === "split-footer") {
|
||||
renderer.screenMode = "main-screen"
|
||||
}
|
||||
|
||||
if (!renderer.isDestroyed) {
|
||||
renderer.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
function formatModelLabel(model: NonNullable<RunInput["model"]>, variant: string | undefined): string {
|
||||
const variantLabel = variant ? ` · ${variant}` : ""
|
||||
return `${model.modelID} · ${model.providerID}${variantLabel}`
|
||||
@@ -218,16 +236,15 @@ export async function runInteractiveMode(input: RunInput): Promise<void> {
|
||||
useMouse: false,
|
||||
autoFocus: false,
|
||||
openConsoleOnError: false,
|
||||
exitOnCtrlC: true,
|
||||
exitOnCtrlC: false,
|
||||
useKittyKeyboard: { events: process.platform === "win32" },
|
||||
screenMode: "split-footer",
|
||||
footerHeight: FOOTER_HEIGHT,
|
||||
externalOutputMode: "capture-stdout",
|
||||
consoleMode: "disabled",
|
||||
clearOnShutdown: false,
|
||||
})
|
||||
|
||||
renderer.start()
|
||||
|
||||
const footer = new RunFooter(renderer, {
|
||||
...footerLabels({
|
||||
agent: input.agent,
|
||||
@@ -278,8 +295,6 @@ export async function runInteractiveMode(input: RunInput): Promise<void> {
|
||||
})
|
||||
} finally {
|
||||
footer.destroy()
|
||||
if (!renderer.isDestroyed) {
|
||||
renderer.destroy()
|
||||
}
|
||||
shutdown(renderer)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user