Track split OSC metadata in headless terminals (#4783)
This commit is contained in:
parent
6caebb27f5
commit
c91ea599d9
|
|
@ -109,6 +109,24 @@ describe('HeadlessEmulator', () => {
|
|||
|
||||
expect(emulator.getSnapshot().cwd).toBe('/path/here')
|
||||
})
|
||||
|
||||
it('tracks OSC-7 CWD across split PTY chunks', async () => {
|
||||
emulator = new HeadlessEmulator({ cols: 80, rows: 24 })
|
||||
|
||||
await emulator.write('\x1b]7;file:///split')
|
||||
await emulator.write('/project\x07')
|
||||
|
||||
expect(emulator.getSnapshot().cwd).toBe('/split/project')
|
||||
})
|
||||
|
||||
it('tracks OSC-7 CWD when ESC and OSC marker arrive in separate chunks', async () => {
|
||||
emulator = new HeadlessEmulator({ cols: 80, rows: 24 })
|
||||
|
||||
await emulator.write('\x1b')
|
||||
await emulator.write(']7;file:///split-escape\x07')
|
||||
|
||||
expect(emulator.getSnapshot().cwd).toBe('/split-escape')
|
||||
})
|
||||
})
|
||||
|
||||
describe('OSC title tracking', () => {
|
||||
|
|
@ -128,6 +146,15 @@ describe('HeadlessEmulator', () => {
|
|||
expect(emulator.getSnapshot().lastTitle).toBe('Codex idle')
|
||||
})
|
||||
|
||||
it('tracks OSC titles across split PTY chunks', async () => {
|
||||
emulator = new HeadlessEmulator({ cols: 80, rows: 24 })
|
||||
|
||||
await emulator.write('\x1b]0;Codex work')
|
||||
await emulator.write('ing\x07')
|
||||
|
||||
expect(emulator.getSnapshot().lastTitle).toBe('Codex working')
|
||||
})
|
||||
|
||||
it('adopts title metadata seeded from an external serializer', () => {
|
||||
emulator = new HeadlessEmulator({ cols: 80, rows: 24 })
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export type HeadlessSnapshotOptions = {
|
|||
}
|
||||
|
||||
const DEFAULT_SCROLLBACK = 5000
|
||||
const OSC_SCAN_TAIL_LIMIT = 4096
|
||||
// Why: PTY/SSH chunks can split a long combined DECSET before the final h/l.
|
||||
// Keep parser state far beyond normal mode lists while still bounding memory.
|
||||
const PRIVATE_MODE_SCAN_TAIL_LIMIT = 4096
|
||||
|
|
@ -53,6 +54,7 @@ export class HeadlessEmulator {
|
|||
private serializer: SerializeAddon
|
||||
private cwd: string | null = null
|
||||
private lastTitle: string | null = null
|
||||
private oscScanTail = ''
|
||||
private privateModeScanTail = ''
|
||||
private mouseTrackingMode: MouseTrackingMode = 'none'
|
||||
private sgrMouseMode = false
|
||||
|
|
@ -88,8 +90,10 @@ export class HeadlessEmulator {
|
|||
return Promise.resolve()
|
||||
}
|
||||
|
||||
this.scanOsc7(data)
|
||||
const lastTitle = extractLastOscTitle(data)
|
||||
const oscInput = this.oscScanTail + data
|
||||
this.oscScanTail = this.extractOscScanTail(oscInput)
|
||||
this.scanOsc7(oscInput)
|
||||
const lastTitle = extractLastOscTitle(oscInput)
|
||||
if (lastTitle !== null) {
|
||||
this.lastTitle = lastTitle
|
||||
}
|
||||
|
|
@ -165,6 +169,20 @@ export class HeadlessEmulator {
|
|||
}
|
||||
}
|
||||
|
||||
private extractOscScanTail(input: string): string {
|
||||
const lastOsc = input.lastIndexOf('\x1b]')
|
||||
const lastEscape = input.endsWith('\x1b') ? input.length - 1 : -1
|
||||
const start = Math.max(lastOsc, lastEscape)
|
||||
if (start === -1) {
|
||||
return ''
|
||||
}
|
||||
const suffix = input.slice(start)
|
||||
if (suffix.includes('\x07') || suffix.includes('\x1b\\')) {
|
||||
return ''
|
||||
}
|
||||
return suffix.slice(-OSC_SCAN_TAIL_LIMIT)
|
||||
}
|
||||
|
||||
private scanPrivateModes(data: string): void {
|
||||
const input = this.privateModeScanTail + data
|
||||
this.privateModeScanTail = this.extractPrivateModeScanTail(input)
|
||||
|
|
|
|||
Loading…
Reference in New Issue