mirror of
https://github.com/stablyai/orca.git
synced 2026-10-25 00:03:59 +00:00
Skip safe hidden TUI renderer writes
This commit is contained in:
+28
-11
@@ -61,7 +61,19 @@ describe('shouldSkipHiddenRendererOutput', () => {
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps startup query windows and terminal-control chunks live', () => {
|
||||
it('skips safe hidden synchronized redraw chunks when a snapshot restore is available', () => {
|
||||
expect(
|
||||
shouldSkipHiddenRendererOutput({
|
||||
foreground: false,
|
||||
canRestoreHiddenOutput: true,
|
||||
startupRendererQueryWindowActive: false,
|
||||
synchronizedOutputActive: true,
|
||||
data: '\x1b[?2026h\x1b[1;1H\x1b[2J\x1b[32mready\x1b[0m\x1b[?25l\x1b[?2026l\n'
|
||||
})
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps startup query windows live', () => {
|
||||
expect(
|
||||
shouldSkipHiddenRendererOutput({
|
||||
foreground: false,
|
||||
@@ -71,15 +83,6 @@ describe('shouldSkipHiddenRendererOutput', () => {
|
||||
data: 'plain\r\n'
|
||||
})
|
||||
).toBe(false)
|
||||
expect(
|
||||
shouldSkipHiddenRendererOutput({
|
||||
foreground: false,
|
||||
canRestoreHiddenOutput: true,
|
||||
startupRendererQueryWindowActive: false,
|
||||
synchronizedOutputActive: true,
|
||||
data: 'plain row inside synchronized frame\r\n'
|
||||
})
|
||||
).toBe(false)
|
||||
expect(
|
||||
shouldSkipHiddenRendererOutput({
|
||||
foreground: false,
|
||||
@@ -95,11 +98,25 @@ describe('shouldSkipHiddenRendererOutput', () => {
|
||||
canRestoreHiddenOutput: true,
|
||||
startupRendererQueryWindowActive: false,
|
||||
synchronizedOutputActive: false,
|
||||
data: '\x1b[?2026hredraw\x1b[?2026l'
|
||||
data: '\x1b[?1;2c'
|
||||
})
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps query and incomplete control chunks live', () => {
|
||||
for (const data of ['\x1b[6n', '\x1b[c', '\x1b[?25', '\x1b[?1049h']) {
|
||||
expect(
|
||||
shouldSkipHiddenRendererOutput({
|
||||
foreground: false,
|
||||
canRestoreHiddenOutput: true,
|
||||
startupRendererQueryWindowActive: false,
|
||||
synchronizedOutputActive: false,
|
||||
data
|
||||
})
|
||||
).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps non-title OSC and incomplete title OSC chunks live', () => {
|
||||
for (const data of ['\x1b]52;c;clipboard\x07', '\x1b]9;notify\x07', '\x1b]0;partial-title']) {
|
||||
expect(
|
||||
|
||||
@@ -36,11 +36,52 @@ function findTitleOscEnd(data: string, startIndex: number): number | null {
|
||||
return null
|
||||
}
|
||||
|
||||
function findSafeCsiEnd(data: string, startIndex: number): number | null {
|
||||
if (data.charCodeAt(startIndex) !== 0x1b || data.charCodeAt(startIndex + 1) !== 0x5b) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (let index = startIndex + 2; index < data.length; index++) {
|
||||
const code = data.charCodeAt(index)
|
||||
if (code < 0x40 || code > 0x7e) {
|
||||
continue
|
||||
}
|
||||
const body = data.slice(startIndex + 2, index)
|
||||
const final = data[index]
|
||||
if (isSafeHiddenRedrawCsi(body, final)) {
|
||||
return index + 1
|
||||
}
|
||||
return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function isSafeHiddenRedrawCsi(body: string, final: string): boolean {
|
||||
if (/[^0-9;?]/.test(body)) {
|
||||
return false
|
||||
}
|
||||
if (final === 'h' || final === 'l') {
|
||||
return body === '?2026' || body === '?25'
|
||||
}
|
||||
return (
|
||||
final === 'm' ||
|
||||
final === 'H' ||
|
||||
final === 'f' ||
|
||||
final === 'A' ||
|
||||
final === 'B' ||
|
||||
final === 'C' ||
|
||||
final === 'D' ||
|
||||
final === 'G' ||
|
||||
final === 'J' ||
|
||||
final === 'K'
|
||||
)
|
||||
}
|
||||
|
||||
function containsOnlyRestorableHiddenOutput(data: string): boolean {
|
||||
for (let index = 0; index < data.length; ) {
|
||||
const code = data.charCodeAt(index)
|
||||
if (code === 0x1b) {
|
||||
const nextIndex = findTitleOscEnd(data, index)
|
||||
const nextIndex = findTitleOscEnd(data, index) ?? findSafeCsiEnd(data, index)
|
||||
if (nextIndex === null) {
|
||||
return false
|
||||
}
|
||||
@@ -66,14 +107,12 @@ export function shouldSkipHiddenRendererOutput({
|
||||
foreground,
|
||||
canRestoreHiddenOutput,
|
||||
startupRendererQueryWindowActive,
|
||||
synchronizedOutputActive,
|
||||
data
|
||||
}: HiddenRendererSkipEligibility): boolean {
|
||||
if (
|
||||
foreground ||
|
||||
!canRestoreHiddenOutput ||
|
||||
startupRendererQueryWindowActive ||
|
||||
synchronizedOutputActive ||
|
||||
data.length === 0
|
||||
) {
|
||||
return false
|
||||
|
||||
@@ -2945,48 +2945,56 @@ describe('connectPanePty', () => {
|
||||
expect(transport.sendInput).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps hidden terminal-control bytes on the live xterm path', async () => {
|
||||
const pendingTimeouts: (() => void)[] = []
|
||||
const originalSetTimeout = globalThis.setTimeout
|
||||
globalThis.setTimeout = vi.fn((fn: () => void) => {
|
||||
pendingTimeouts.push(fn)
|
||||
return 999 as unknown as ReturnType<typeof setTimeout>
|
||||
}) as unknown as typeof setTimeout
|
||||
it('restores safe hidden terminal-control bytes from the main snapshot', async () => {
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
const transport = createMockTransport('pty-id')
|
||||
const capturedDataCallback: {
|
||||
current: ((data: string, meta?: { seq?: number; rawLength?: number }) => void) | null
|
||||
} = { current: null }
|
||||
transport.connect.mockImplementation(async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
|
||||
capturedDataCallback.current = callbacks.onData ?? null
|
||||
return 'pty-id'
|
||||
})
|
||||
transportFactoryQueue.push(transport)
|
||||
const getMainBufferSnapshot = window.api.pty.getMainBufferSnapshot as unknown as ReturnType<
|
||||
typeof vi.fn
|
||||
>
|
||||
getMainBufferSnapshot.mockResolvedValue({
|
||||
data: 'control snapshot\r\n',
|
||||
cols: 100,
|
||||
rows: 30,
|
||||
seq: 64
|
||||
})
|
||||
|
||||
try {
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
const transport = createMockTransport('pty-id')
|
||||
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
|
||||
transport.connect.mockImplementation(
|
||||
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
|
||||
capturedDataCallback.current = callbacks.onData ?? null
|
||||
return 'pty-id'
|
||||
}
|
||||
)
|
||||
transportFactoryQueue.push(transport)
|
||||
const pane = createPane(1)
|
||||
const manager = createManager(1)
|
||||
const deps = createDeps({
|
||||
isVisibleRef: { current: false }
|
||||
})
|
||||
|
||||
const pane = createPane(1)
|
||||
const manager = createManager(1)
|
||||
const deps = createDeps({
|
||||
isVisibleRef: { current: false }
|
||||
})
|
||||
connectPanePty(pane as never, manager as never, deps as never)
|
||||
await flushAsyncTicks(6)
|
||||
|
||||
connectPanePty(pane as never, manager as never, deps as never)
|
||||
await flushAsyncTicks(6)
|
||||
expect(capturedDataCallback.current).not.toBeNull()
|
||||
const controlOutput = '\x1b[2J\x1b[Hhello\r\n'
|
||||
capturedDataCallback.current?.(controlOutput, {
|
||||
seq: controlOutput.length,
|
||||
rawLength: controlOutput.length
|
||||
})
|
||||
expect(pane.terminal.write).not.toHaveBeenCalledWith(controlOutput, expect.any(Function))
|
||||
|
||||
expect(capturedDataCallback.current).not.toBeNull()
|
||||
const controlOutput = '\x1b[2J\x1b[Hhello\r\n'
|
||||
capturedDataCallback.current?.(controlOutput)
|
||||
expect(pane.terminal.write).not.toHaveBeenCalledWith(controlOutput)
|
||||
;(deps.isVisibleRef as { current: boolean }).current = true
|
||||
capturedDataCallback.current?.('visible\r\n', {
|
||||
seq: controlOutput.length + 'visible\r\n'.length,
|
||||
rawLength: 'visible\r\n'.length
|
||||
})
|
||||
await flushAsyncTicks(20)
|
||||
|
||||
for (const fn of pendingTimeouts) {
|
||||
fn()
|
||||
}
|
||||
|
||||
expect(pane.terminal.write).toHaveBeenCalledWith(controlOutput)
|
||||
} finally {
|
||||
globalThis.setTimeout = originalSetTimeout
|
||||
}
|
||||
expect(getMainBufferSnapshot).toHaveBeenCalledWith('pty-id', { scrollbackRows: 5000 })
|
||||
expect(pane.terminal.write).toHaveBeenCalledWith(
|
||||
expect.stringContaining('control snapshot'),
|
||||
expect.any(Function)
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps visually rich hidden PTY bytes on the live xterm path', async () => {
|
||||
@@ -3023,15 +3031,26 @@ describe('connectPanePty', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps split hidden synchronized output frames on the live xterm path', async () => {
|
||||
it('restores safe split hidden synchronized output frames from the main snapshot', async () => {
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
const transport = createMockTransport('pty-id')
|
||||
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
|
||||
const capturedDataCallback: {
|
||||
current: ((data: string, meta?: { seq?: number; rawLength?: number }) => void) | null
|
||||
} = { current: null }
|
||||
transport.connect.mockImplementation(async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
|
||||
capturedDataCallback.current = callbacks.onData ?? null
|
||||
return 'pty-id'
|
||||
})
|
||||
transportFactoryQueue.push(transport)
|
||||
const getMainBufferSnapshot = window.api.pty.getMainBufferSnapshot as unknown as ReturnType<
|
||||
typeof vi.fn
|
||||
>
|
||||
getMainBufferSnapshot.mockResolvedValue({
|
||||
data: 'snapshot table\r\nLONG_TABLE_SCROLL_RESTORE_marker\r\n',
|
||||
cols: 100,
|
||||
rows: 30,
|
||||
seq: 80
|
||||
})
|
||||
|
||||
const pane = createPane(1)
|
||||
const manager = createManager(1)
|
||||
@@ -3043,23 +3062,40 @@ describe('connectPanePty', () => {
|
||||
await flushAsyncTicks(6)
|
||||
|
||||
expect(capturedDataCallback.current).not.toBeNull()
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const startChunk = '\x1b[?2026h'
|
||||
const plainRowChunk = '| Sam Syntax | Compiler | Online |\r\n'
|
||||
const endChunk = 'LONG_TABLE_SCROLL_RESTORE_marker\r\n\x1b[?2026l'
|
||||
const startChunk = '\x1b[?2026h'
|
||||
const plainRowChunk = '| Sam Syntax | Compiler | Online |\r\n'
|
||||
const endChunk = 'LONG_TABLE_SCROLL_RESTORE_marker\r\n\x1b[?2026l'
|
||||
const visibleChunk = 'visible-after-hidden\r\n'
|
||||
|
||||
capturedDataCallback.current?.(startChunk)
|
||||
capturedDataCallback.current?.(plainRowChunk)
|
||||
capturedDataCallback.current?.(endChunk)
|
||||
capturedDataCallback.current?.(startChunk, {
|
||||
seq: startChunk.length,
|
||||
rawLength: startChunk.length
|
||||
})
|
||||
capturedDataCallback.current?.(plainRowChunk, {
|
||||
seq: startChunk.length + plainRowChunk.length,
|
||||
rawLength: plainRowChunk.length
|
||||
})
|
||||
capturedDataCallback.current?.(endChunk, {
|
||||
seq: startChunk.length + plainRowChunk.length + endChunk.length,
|
||||
rawLength: endChunk.length
|
||||
})
|
||||
|
||||
expect(pane.terminal.write).not.toHaveBeenCalledWith(plainRowChunk)
|
||||
vi.advanceTimersByTime(50)
|
||||
expect(pane.terminal.write).toHaveBeenCalledWith(`${startChunk}${plainRowChunk}${endChunk}`)
|
||||
expect(window.api.pty.getMainBufferSnapshot).not.toHaveBeenCalled()
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
expect(pane.terminal.write).not.toHaveBeenCalledWith(startChunk, expect.any(Function))
|
||||
expect(pane.terminal.write).not.toHaveBeenCalledWith(plainRowChunk, expect.any(Function))
|
||||
expect(pane.terminal.write).not.toHaveBeenCalledWith(endChunk, expect.any(Function))
|
||||
|
||||
;(deps.isVisibleRef as { current: boolean }).current = true
|
||||
capturedDataCallback.current?.(visibleChunk, {
|
||||
seq: startChunk.length + plainRowChunk.length + endChunk.length + visibleChunk.length,
|
||||
rawLength: visibleChunk.length
|
||||
})
|
||||
await flushAsyncTicks(20)
|
||||
|
||||
expect(getMainBufferSnapshot).toHaveBeenCalledWith('pty-id', { scrollbackRows: 5000 })
|
||||
expect(pane.terminal.write).toHaveBeenCalledWith(
|
||||
expect.stringContaining('snapshot table'),
|
||||
expect.any(Function)
|
||||
)
|
||||
})
|
||||
|
||||
it('queues visible split-pane PTY bytes when the pane is not active', async () => {
|
||||
|
||||
@@ -171,7 +171,11 @@ export async function runHiddenRealPtyPressureScenario<
|
||||
ackGate
|
||||
)
|
||||
|
||||
if (pressureOutputMode === 'plain' || pressureOutputMode === 'title') {
|
||||
if (
|
||||
pressureOutputMode === 'plain' ||
|
||||
pressureOutputMode === 'title' ||
|
||||
pressureOutputMode === 'tui'
|
||||
) {
|
||||
expect(debug?.hiddenRendererSkipCount ?? 0).toBeGreaterThan(0)
|
||||
expect(debug?.hiddenRendererSkippedChars ?? 0).toBeGreaterThan(0)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user