From 4885888a2a2bb6cada657c79dc38464588fd70aa Mon Sep 17 00:00:00 2001 From: ToastyTheBot Date: Mon, 13 Apr 2026 20:26:40 +0800 Subject: [PATCH] fix: handle OSC sequences split across buffer chunks (#11144) The OSCProcessor assumed OSC sequences (like OSC 52 clipboard) arrive in a single buffer chunk. However, when sequences exceed 1024 bytes, they get split across multiple chunks, causing the prefix and suffix to be in different calls. This fix adds buffering to accumulate incomplete OSC sequences until the terminator (BEL or ST) arrives, allowing proper handling of arbitrarily long OSC 52 clipboard data and other long OSC sequences. Fixes #6001 Co-authored-by: ToastyTheBot --- .../src/middleware/oscProcessing.ts | 62 ++++++++++++++----- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/tabby-terminal/src/middleware/oscProcessing.ts b/tabby-terminal/src/middleware/oscProcessing.ts index 9beff0ee..d67a4cd6 100644 --- a/tabby-terminal/src/middleware/oscProcessing.ts +++ b/tabby-terminal/src/middleware/oscProcessing.ts @@ -9,33 +9,62 @@ export class OSCProcessor extends SessionMiddleware { get cwdReported$ (): Observable { return this.cwdReported } private cwdReported = new Subject() + private buffer: Buffer | null = null feedFromSession (data: Buffer): void { + // Prepend any buffered data from previous chunks + if (this.buffer) { + data = Buffer.concat([this.buffer, data]) + this.buffer = null + } + let startIndex = 0 - while (data.includes(OSCPrefix, startIndex)) { - const si = startIndex - if (!OSCSuffixes.some(s => data.includes(s, si))) { + const processedData: Buffer[] = [] + + while (startIndex < data.length) { + const prefixIndex = data.indexOf(OSCPrefix, startIndex) + + if (prefixIndex === -1) { + // No more OSC sequences, pass remaining data + if (startIndex < data.length) { + processedData.push(data.subarray(startIndex)) + } break } - const params = data.subarray(data.indexOf(OSCPrefix, startIndex) + OSCPrefix.length) + // Pass data before this OSC sequence + if (prefixIndex > startIndex) { + processedData.push(data.subarray(startIndex, prefixIndex)) + } - const [closesSuffix, closestSuffixIndex] = OSCSuffixes - .map((suffix): [Buffer, number] => [suffix, params.indexOf(suffix)]) - .filter(([_, index]) => index !== -1) - .sort(([_, a], [__, b]) => a - b)[0] + // Look for suffix after the prefix + const suffixSearchStart = prefixIndex + OSCPrefix.length + let foundSuffix: [Buffer, number] | null = null - const oscString = params.subarray(0, closestSuffixIndex).toString() + for (const suffix of OSCSuffixes) { + const suffixIndex = data.indexOf(suffix, suffixSearchStart) + if (suffixIndex !== -1) { + if (!foundSuffix || suffixIndex < foundSuffix[1]) { + foundSuffix = [suffix, suffixIndex] + } + } + } - startIndex = data.indexOf(closesSuffix, startIndex) + closesSuffix.length + if (!foundSuffix) { + // No suffix found - buffer the rest and wait for next chunk + this.buffer = data.subarray(prefixIndex) + break + } + // Extract OSC string (between prefix and suffix) + const oscString = data.subarray(suffixSearchStart, foundSuffix[1]).toString() const [oscCodeString, ...oscParams] = oscString.split(';') const oscCode = parseInt(oscCodeString) if (oscCode === 1337) { const paramString = oscParams.join(';') if (paramString.startsWith('CurrentDir=')) { - let reportedCWD = paramString.split('=')[1] + let reportedCWD = paramString.split('=', 2)[1] if (reportedCWD.startsWith('~')) { reportedCWD = os.homedir() + reportedCWD.substring(1) } @@ -43,11 +72,16 @@ export class OSCProcessor extends SessionMiddleware { } else { console.debug('Unsupported OSC 1337 parameter:', paramString) } - } else { - continue } + + // Move past this OSC sequence + startIndex = foundSuffix[1] + foundSuffix[0].length + } + + // Pass through all processed data + if (processedData.length > 0) { + super.feedFromSession(Buffer.concat(processedData)) } - super.feedFromSession(data) } close (): void {