mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-19 13:34:41 +00:00
Merge pull request #900 from ozymandiashh/fix/ink-win-strip-sync-escapes
fix(ink-win): strip synchronized-update escapes instead of exact-matching them
This commit is contained in:
commit
5d66f073ad
2 changed files with 74 additions and 4 deletions
|
|
@ -1,14 +1,42 @@
|
|||
const BSU = '\x1b[?2026h'
|
||||
const ESU = '\x1b[?2026l'
|
||||
// Begin/End Synchronized Update (DEC private mode 2026); exported so callers
|
||||
// can emit them; on Windows the filter below strips them from every write, so
|
||||
// even a concatenated BSU+payload write cannot reach ConPTY (#195).
|
||||
export const BSU = '\x1b[?2026h'
|
||||
export const ESU = '\x1b[?2026l'
|
||||
let patched = false
|
||||
|
||||
// split/join removes every occurrence and is hot-path cheap because the
|
||||
// includes() gate below runs first.
|
||||
export function stripSyncUpdateEscapes(chunk: string): string {
|
||||
return chunk.split(BSU).join('').split(ESU).join('')
|
||||
}
|
||||
|
||||
export function patchStdoutForWindows(): void {
|
||||
if (process.platform !== 'win32' || patched) return
|
||||
patched = true
|
||||
|
||||
const origWrite = process.stdout.write.bind(process.stdout)
|
||||
process.stdout.write = function (chunk: unknown, ...args: unknown[]): boolean {
|
||||
if (chunk === BSU || chunk === ESU) return true
|
||||
return (origWrite as Function)(chunk, ...args)
|
||||
// Non-string chunks pass straight through unchanged; Buffers never carry
|
||||
// these escapes in this codebase, so scanning them is not worth the copy.
|
||||
if (typeof chunk !== 'string') {
|
||||
return (origWrite as Function)(chunk, ...args)
|
||||
}
|
||||
// Neither escape present: pass straight through.
|
||||
if (!chunk.includes(BSU) && !chunk.includes(ESU)) {
|
||||
return (origWrite as Function)(chunk, ...args)
|
||||
}
|
||||
const stripped = stripSyncUpdateEscapes(chunk)
|
||||
if (stripped.length > 0) {
|
||||
return (origWrite as Function)(stripped, ...args)
|
||||
}
|
||||
// The chunk was swallowed entirely. The old exact-match filter dropped the
|
||||
// callback too, which could wedge a callback-style writer; invoke it
|
||||
// asynchronously so a caller awaiting the callback never hangs.
|
||||
const last = args[args.length - 1]
|
||||
if (typeof last === 'function') {
|
||||
queueMicrotask(() => (last as () => void)())
|
||||
}
|
||||
return true
|
||||
} as typeof process.stdout.write
|
||||
}
|
||||
|
|
|
|||
42
tests/ink-win.test.ts
Normal file
42
tests/ink-win.test.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { BSU, ESU, stripSyncUpdateEscapes, patchStdoutForWindows } from '../src/ink-win.js'
|
||||
|
||||
describe('stripSyncUpdateEscapes', () => {
|
||||
it('strips an exact BSU chunk to empty', () => {
|
||||
expect(stripSyncUpdateEscapes(BSU)).toBe('')
|
||||
})
|
||||
|
||||
it('strips an exact ESU chunk to empty', () => {
|
||||
expect(stripSyncUpdateEscapes(ESU)).toBe('')
|
||||
})
|
||||
|
||||
it('strips a leading BSU from a concatenated clear write', () => {
|
||||
// #863 regression shape: the clear sequence glued to a BSU used to slip
|
||||
// through raw and hang Windows ConPTY.
|
||||
expect(stripSyncUpdateEscapes(BSU + '\x1b[2J\x1b[H')).toBe('\x1b[2J\x1b[H')
|
||||
})
|
||||
|
||||
it('strips a trailing ESU, and both ends at once', () => {
|
||||
expect(stripSyncUpdateEscapes('x' + ESU)).toBe('x')
|
||||
expect(stripSyncUpdateEscapes(BSU + 'x' + ESU)).toBe('x')
|
||||
})
|
||||
|
||||
it('removes every occurrence when escapes appear multiple times', () => {
|
||||
expect(stripSyncUpdateEscapes(BSU + 'a' + BSU + 'b' + ESU + 'c' + ESU)).toBe('abc')
|
||||
})
|
||||
|
||||
it('leaves a string without escapes untouched (same reference-equal content)', () => {
|
||||
const plain = 'status line \x1b[2J'
|
||||
expect(stripSyncUpdateEscapes(plain)).toBe(plain)
|
||||
})
|
||||
})
|
||||
|
||||
describe('patchStdoutForWindows', () => {
|
||||
it('is a no-op off win32: process.stdout.write stays reference-identical', () => {
|
||||
// Skip on actual Windows runners, where the patch legitimately applies.
|
||||
if (process.platform === 'win32') return
|
||||
const before = process.stdout.write
|
||||
patchStdoutForWindows()
|
||||
expect(process.stdout.write).toBe(before)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue