src/vs/platform/agentHost/node/osc633Parser.ts
348 LOC · 333 covered · 15 uncovered · 70 ranges · 2283 concepts · 32 introducers · 1064 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
/*---------------------------------------------------------------------------------------------
osc633Parser.ts ×8
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Lightweight parser for OSC 633 (VS Code shell integration) sequences in raw
* PTY output. Designed for the agent host where we don't have a full xterm.js
* instance - it scans data chunks for the sequences, extracts events, and
* removes the sequences from the data stream.
*
* Handles partial sequences that span across data chunk boundaries.
*/
/** OSC 633 event types we care about. */
export const enum Osc633EventType {
/** 633;A - Prompt start. Used to detect shell integration is active. */
PromptStart,
/** 633;B - Command start (where user inputs command). */
CommandStart,
/** 633;C - Command executed (output begins). */
CommandExecuted,
/** 633;D[;exitCode] - Command finished. */
CommandFinished,
/** 633;E;commandLine[;nonce] - Explicit command line. */
CommandLine,
/** 633;P;Key=Value - Property (e.g. Cwd). */
Property,
}
export interface IOsc633PromptStartEvent {
type: Osc633EventType.PromptStart;
}
export interface IOsc633CommandStartEvent {
type: Osc633EventType.CommandStart;
}
export interface IOsc633CommandExecutedEvent {
type: Osc633EventType.CommandExecuted;
}
export interface IOsc633CommandFinishedEvent {
type: Osc633EventType.CommandFinished;
exitCode: number | undefined;
}
export interface IOsc633CommandLineEvent {
type: Osc633EventType.CommandLine;
commandLine: string;
nonce: string | undefined;
}
export interface IOsc633PropertyEvent {
type: Osc633EventType.Property;
key: string;
value: string;
}
export type Osc633Event =
| IOsc633PromptStartEvent
| IOsc633CommandStartEvent
| IOsc633CommandExecutedEvent
| IOsc633CommandFinishedEvent
| IOsc633CommandLineEvent
| IOsc633PropertyEvent;
export interface IOsc633ParseResult {
/** Data with all OSC 633 sequences stripped. */
cleanedData: string;
/** Parsed events in order of appearance. */
events: Osc633Event[];
}
/**
* A single segment of parsed PTY data: either a run of cleaned output data or
* an OSC 633 event. Segments are emitted in stream order so that output which
* arrives before an event (e.g. a `CommandFinished` marker) can be attributed
* to the command before the event is handled — see {@link Osc633Parser.parseSegments}.
*/
export type Osc633ParseSegment =
| { readonly kind: 'data'; readonly data: string }
| { readonly kind: 'event'; readonly event: Osc633Event };
/**
* Decode escaped values in OSC 633 messages.
* Handles `\\` -> `\` and `\xAB` -> character with code 0xAB.
*/
if (message.indexOf('\\') === -1) {
}
/\\(\\|x([0-9a-f]{2}))/gi,
(_match: string, op: string, hex?: string) => hex ? String.fromCharCode(parseInt(hex, 16)) : op,
);
}
const semiIdx = payload.indexOf(';');
if ((semiIdx === -1 ? payload.length : semiIdx) !== 1) {
return undefined;
}
const command = payload[0];
const argsRaw = semiIdx === -1 ? '' : payload.substring(semiIdx + 1);
switch (command) {
case 'A':
return {
type: Osc633EventType.CommandFinished,
exitCode: exitCode !== undefined && !isNaN(exitCode) ? exitCode : undefined,
};
}
const commandLine = deserializeOscMessage(nonceIdx === -1 ? argsRaw : argsRaw.substring(0, nonceIdx));
const nonce = nonceIdx === -1 ? undefined : argsRaw.substring(nonceIdx + 1);
return { type: Osc633EventType.CommandLine, commandLine, nonce };
}
const eqIdx = deserialized.indexOf('=');
if (eqIdx === -1) {
}
type: Osc633EventType.Property,
key: deserialized.substring(0, eqIdx),
value: deserialized.substring(eqIdx + 1),
};
}
return undefined;
}
// OSC introducer is ESC ] (0x1b 0x5d)
const ESC = '\x1b';
const OSC_START = ESC + ']';
// Terminators: BEL (0x07) or ST (ESC \)
const BEL = '\x07';
const ST = ESC + '\\';
/**
* Stateful parser that handles data chunks, correctly dealing with
* partial sequences that span multiple chunks.
*/
export class Osc633Parser {
/** Buffer for an incomplete OSC sequence (from ESC] up to but not including the terminator). */
osc633Parser.ts ×5
private _pendingOsc = '';
/** Whether we are currently accumulating an OSC sequence. */
private _inOsc = false;
/** Set when the previous chunk ended with ESC inside an OSC body (potential ST start). */
private _pendingEscInOsc = false;
/**
* Parse a chunk of PTY data.
* Returns cleaned data (all OSC 633 sequences removed) and extracted events.
*
* This is a convenience view over {@link parseSegments} that concatenates the
* cleaned-data segments and collects the events. Callers that need to know
* whether a run of output arrived before or after an event (for correct
* command-output attribution) should use {@link parseSegments} instead.
*/
parse(data: string): IOsc633ParseResult {
let cleanedData = '';
for (const segment of this.parseSegments(data)) {
}
}
/**
* Parse a chunk of PTY data into an ordered list of segments, preserving the
* relative order of cleaned output data and OSC 633 events as they appear in
* the stream. Handles partial sequences that span multiple chunks.
*
* Preserving order matters because a single PTY read frequently contains a
* command's output immediately followed by its `CommandFinished` marker;
* consumers must append that output to the command before handling the
* finished event, otherwise the output is lost from the command result.
*/
parseSegments(data: string): Osc633ParseSegment[] {
let pending = '';
const appendData = (value: string): void => {
pending += value;
};
const flushData = (): void => {
if (pending.length > 0) {
pending = '';
}
const emitEvent = (event: Osc633Event): void => {
segments.push({ kind: 'event', event });
};
if (!this._inOsc && data.indexOf(OSC_START) === -1) {
flushData();
return segments;
}
let i = 0;
while (i < data.length) {
if (this._inOsc) {
if (this._pendingEscInOsc) {
if (data[i] === '\\') {
// ESC \ = ST terminator, sequence is complete.
i++;
this._inOsc = false;
const payload = this._pendingOsc;
this._pendingOsc = '';
this._handleOscPayload(payload, emitEvent, appendData, ST);
continue;
}
// ESC was not followed by \, malformed: complete the OSC anyway.
this._inOsc = false;
const payload = this._pendingOsc;
this._pendingOsc = '';
this._handleOscPayload(payload, emitEvent, appendData);
continue;
}
// We're inside an OSC sequence, look for the terminator.
const result = this._consumeOscBody(data, i);
i = result.nextIndex;
if (result.complete) {
this._inOsc = false;
const payload = this._pendingOsc;
this._pendingOsc = '';
this._handleOscPayload(payload, emitEvent, appendData, result.terminator);
} else if (result.pendingEsc) {
this._pendingEscInOsc = true;
}
// If not complete, _pendingOsc has been extended, and we're at end of data.
osc633Parser.ts ×3
continue;
}
// Look for the next ESC ] which starts an OSC sequence
const escIdx = data.indexOf(OSC_START, i);
if (escIdx === -1) {
i = data.length;
continue;
}
// Copy everything before the OSC start to cleaned output.
appendData(data.substring(i, escIdx));
// Start of OSC: check if it's 633.
i = escIdx + 2; // skip past ESC ]
this._pendingOsc = '';
this._inOsc = true;
// Try to consume the OSC body in this same chunk.
const result = this._consumeOscBody(data, i);
i = result.nextIndex;
if (result.complete) {
const payload = this._pendingOsc;
this._pendingOsc = '';
// If it's a 633 sequence, extract event; otherwise put it back in cleaned.
this._handleOscPayload(payload, emitEvent, appendData, result.terminator);
}
}
flushData();
return segments;
/**
* Consume characters from the OSC body, appending to _pendingOsc until a
* terminator (BEL or ST) is found.
*/
private _consumeOscBody(data: string, startIdx: number): { nextIndex: number; complete: boolean; pendingEsc?: boolean; terminator?: string } {
const escIdx = data.indexOf(ESC, startIdx);
if (belIdx !== -1 && (escIdx === -1 || belIdx < escIdx)) {
return { nextIndex: belIdx + 1, complete: true, terminator: BEL };
}
if (escIdx !== -1) {
return { nextIndex: data.length, complete: false, pendingEsc: true };
}
this._pendingOsc += data.substring(startIdx, escIdx);
if (data[escIdx + 1] === '\\') {
return { nextIndex: escIdx + 2, complete: true, terminator: ST };
}
return { nextIndex: escIdx, complete: true };
}
this._pendingOsc += data.substring(startIdx);
return { nextIndex: data.length, complete: false };
/**
* Process a complete OSC payload. If it's a 633; sequence, extract the
* event via {@link emitEvent}. Otherwise, reconstruct the original bytes and
* pass them through to the cleaned output via {@link appendData}.
*/
private _handleOscPayload(
emitEvent: (event: Osc633Event) => void,
appendData: (data: string) => void,
terminator = BEL,
): void {
if (payload.startsWith('633;')) {
const event = parseOsc633Payload(oscContent);
if (event) {
}
appendData(OSC_START + payload + terminator);
}