From dae2cc91108c6a4ae512c7b43d6d496fea844ff1 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 10 Oct 2026 21:21:27 -0700 Subject: [PATCH] test(runtime): enforce ANSI preview-final pre-filter invariant (#27376) Follow-up to #27344. isTerminalPreviewLineControl now gates on isTerminalPreviewLineControlFinal, so the normalizeTerminalChunk pre-filter cannot drop a final the predicate accepts. Add a test over every CSI final 0x40-0x7e and replace String.prototype.charCodeAt spies with behavior assertions. --- .../runtime/terminal-ansi-normalization.ts | 10 ++- .../terminal-vertical-control-scan.test.ts | 63 +++++++++++-------- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/main/runtime/terminal-ansi-normalization.ts b/src/main/runtime/terminal-ansi-normalization.ts index 6a5cb984351..253f6c2d8f5 100644 --- a/src/main/runtime/terminal-ansi-normalization.ts +++ b/src/main/runtime/terminal-ansi-normalization.ts @@ -197,7 +197,7 @@ function trimPendingAnsiControl(value: string): string { return `${introducer}${value.slice(-suffixBudget)}` } -/** CSI controls that affect retained preview rows. */ +/** CSI finals that affect retained preview rows; the sole gate for isTerminalPreviewLineControl. */ function isTerminalPreviewLineControlFinal(code: number): boolean { return ( code === 0x4b || @@ -214,12 +214,16 @@ function isTerminalPreviewLineControl(parsed: { params: string firstParam: number | null }): boolean { - if (!hasCanonicalNumericCsiParams(parsed.params)) { + // Why: normalizeTerminalChunk pre-filters on this predicate, so any final accepted here must pass it first. + if ( + !isTerminalPreviewLineControlFinal(parsed.final.charCodeAt(0)) || + !hasCanonicalNumericCsiParams(parsed.params) + ) { return false } if (parsed.final === 'K') { const mode = parsed.firstParam ?? 0 return mode === 0 || mode === 1 || mode === 2 } - return isTerminalPreviewLineControlFinal(parsed.final.charCodeAt(0)) + return true } diff --git a/src/main/runtime/terminal-vertical-control-scan.test.ts b/src/main/runtime/terminal-vertical-control-scan.test.ts index 9608b7010ad..76d37603991 100644 --- a/src/main/runtime/terminal-vertical-control-scan.test.ts +++ b/src/main/runtime/terminal-vertical-control-scan.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it } from 'vitest' import { containsTerminalVerticalLineControl, normalizeTerminalChunk @@ -6,20 +6,42 @@ import { import { appendNormalizedToTailBuffer } from './terminal-tail-buffer' describe('terminal vertical-control scanning', () => { - it('scans retained CSI parameters once while normalizing', () => { + it('keeps a line control with long retained CSI parameters', () => { const input = `\x1b[${'1;'.repeat(2048)}A` - const charCodeAt = vi.spyOn(String.prototype, 'charCodeAt') - let normalized: ReturnType - let inspections: number - try { - normalized = normalizeTerminalChunk(input) - inspections = charCodeAt.mock.calls.length - } finally { - charCodeAt.mockRestore() - } + expect(normalizeTerminalChunk(input)).toEqual({ + text: input, + pendingAnsi: '' + }) + }) - expect(normalized).toEqual({ text: input, pendingAnsi: '' }) - expect(inspections).toBeLessThanOrEqual(input.length + 8) + it('retains exactly the preview line-control CSI finals', () => { + const retained: string[] = [] + for (let code = 0x40; code <= 0x7e; code += 1) { + const final = String.fromCharCode(code) + for (const params of ['', '2']) { + const control = `\x1b[${params}${final}` + const { text } = normalizeTerminalChunk(`a${control}b`) + if (text === `a${control}b`) { + retained.push(control) + } else { + expect(text).toBe('ab') + } + } + } + expect(retained).toEqual([ + '\x1b[A', + '\x1b[2A', + '\x1b[C', + '\x1b[2C', + '\x1b[D', + '\x1b[2D', + '\x1b[G', + '\x1b[2G', + '\x1b[K', + '\x1b[2K', + '\x1b[`', + '\x1b[2`' + ]) }) it('preserves printable spans and carried controls across chunk boundaries', () => { @@ -35,19 +57,8 @@ describe('terminal vertical-control scanning', () => { ['plain', 'log output '.repeat(8192), false], ['nonvertical CSI', `\x1b[31m${'log output '.repeat(8192)}\x1b[0m`, false], ['vertical CSI', `${'漢字😀 output '.repeat(8192)}\x1b[2A`, true] - ] as const)('bounds code-unit inspections on %s output', (_name, input, expected) => { - const charCodeAt = vi.spyOn(String.prototype, 'charCodeAt') - let actual: boolean - let inspections: number - try { - actual = containsTerminalVerticalLineControl(input) - inspections = charCodeAt.mock.calls.length - } finally { - charCodeAt.mockRestore() - } - - expect(actual).toBe(expected) - expect(inspections).toBeLessThan(16) + ] as const)('detects vertical controls in %s output', (_name, input, expected) => { + expect(containsTerminalVerticalLineControl(input)).toBe(expected) }) it.each([