Skip to content

Commit ff488e3

Browse files
authored
persist command in pty service, refactor how commands are restored on window reload for inline chat terminal (#274417)
1 parent 03079e9 commit ff488e3

File tree

31 files changed

+220
-224
lines changed

31 files changed

+220
-224
lines changed

src/vs/platform/terminal/common/capabilities/capabilities.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,12 @@ export interface ICommandDetectionCapability {
251251
* always be present when running the _builtin_ SI scripts.
252252
*/
253253
setCommandLine(commandLine: string, isTrusted: boolean): void;
254+
/**
255+
* Sets the command ID to use for the next command that starts.
256+
* This allows pre-assigning an ID before the shell sends the command start sequence,
257+
* which is useful for linking commands across renderer and ptyHost.
258+
*/
259+
setNextCommandId(command: string, commandId: string): void;
254260
serialize(): ISerializedCommandDetectionCapability;
255261
deserialize(serialized: ISerializedCommandDetectionCapability): void;
256262
}
@@ -270,6 +276,12 @@ export interface IHandleCommandOptions {
270276
* Properties for the mark
271277
*/
272278
markProperties?: IMarkProperties;
279+
280+
/**
281+
* An optional predefined command ID. When provided, this ID will be used instead of
282+
* generating a new one, allowing commands to be linked across renderer and ptyHost.
283+
*/
284+
commandId?: string;
273285
}
274286

275287
export interface INaiveCwdDetectionCapability {
@@ -291,7 +303,7 @@ interface IBaseTerminalCommand {
291303
isTrusted: boolean;
292304
timestamp: number;
293305
duration: number;
294-
id: string;
306+
id: string | undefined;
295307

296308
// Optional serializable
297309
cwd: string | undefined;

src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@
66
import { IMarkProperties, ISerializedTerminalCommand, ITerminalCommand } from '../capabilities.js';
77
import { ITerminalOutputMatcher, ITerminalOutputMatch } from '../../terminal.js';
88
import type { IBuffer, IBufferLine, IMarker, Terminal } from '@xterm/headless';
9-
import { generateUuid } from '../../../../../base/common/uuid.js';
109

1110
export interface ITerminalCommandProperties {
1211
command: string;
1312
commandLineConfidence: 'low' | 'medium' | 'high';
1413
isTrusted: boolean;
1514
timestamp: number;
1615
duration: number;
17-
id: string;
16+
id: string | undefined;
1817
marker: IMarker | undefined;
1918
cwd: string | undefined;
2019
exitCode: number | undefined;
@@ -276,7 +275,7 @@ export class PartialTerminalCommand implements ICurrentPartialCommand {
276275
cwd?: string;
277276
command?: string;
278277
commandLineConfidence?: 'low' | 'medium' | 'high';
279-
id: string;
278+
id: string | undefined;
280279

281280
isTrusted?: boolean;
282281
isInvalid?: boolean;
@@ -285,8 +284,7 @@ export class PartialTerminalCommand implements ICurrentPartialCommand {
285284
private readonly _xterm: Terminal,
286285
id?: string
287286
) {
288-
//TODO: this does not restore properly due to conflicting with the one created in the. PtyHost
289-
this.id = id ?? generateUuid();
287+
this.id = id;
290288
}
291289

292290
serialize(cwd: string | undefined): ISerializedTerminalCommand | undefined {

src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe
3535
private _handleCommandStartOptions?: IHandleCommandOptions;
3636
private _hasRichCommandDetection: boolean = false;
3737
get hasRichCommandDetection() { return this._hasRichCommandDetection; }
38+
private _nextCommandId: { command: string; commandId: string | undefined } | undefined;
3839

3940
private _ptyHeuristicsHooks: ICommandDetectionHeuristicsHooks;
4041
private readonly _ptyHeuristics: MandatoryMutableDisposable<IPtyHeuristics>;
@@ -342,6 +343,14 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe
342343
this._ptyHeuristics.value.handleCommandStart(options);
343344
}
344345

346+
/**
347+
* Sets the command ID to use for the next command that starts.
348+
* This is useful when you want to pre-assign an ID before the shell sends the command start sequence.
349+
*/
350+
setNextCommandId(command: string, commandId: string): void {
351+
this._nextCommandId = { command, commandId };
352+
}
353+
345354
handleCommandExecuted(options?: IHandleCommandOptions): void {
346355
this._ptyHeuristics.value.handleCommandExecuted(options);
347356
this._currentCommand.markExecutedTime();
@@ -355,7 +364,20 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe
355364
if (!this._currentCommand.commandExecutedMarker) {
356365
this.handleCommandExecuted();
357366
}
358-
367+
// If a custom command ID is provided, use it for the current command
368+
// Otherwise, check if there's a pending next command ID
369+
if (options?.commandId) {
370+
this._currentCommand.id = options.commandId;
371+
this._nextCommandId = undefined; // Clear the pending ID
372+
} else if (
373+
this._nextCommandId &&
374+
typeof this.currentCommand.command === 'string' &&
375+
typeof this._nextCommandId.command === 'string' &&
376+
this.currentCommand.command.trim() === this._nextCommandId.command.trim()
377+
) {
378+
this._currentCommand.id = this._nextCommandId.commandId;
379+
this._nextCommandId = undefined; // Clear after use
380+
}
359381
this._currentCommand.markFinishedTime();
360382
this._ptyHeuristics.value.preHandleCommandFinished?.();
361383

@@ -392,7 +414,9 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe
392414
this._logService.debug('CommandDetectionCapability#onCommandFinished', newCommand);
393415
this._onCommandFinished.fire(newCommand);
394416
}
395-
this._currentCommand = new PartialTerminalCommand(this._terminal);
417+
// Create new command for next execution, preserving command ID if one was specified
418+
const nextCommandId = this._handleCommandStartOptions?.commandId;
419+
this._currentCommand = new PartialTerminalCommand(this._terminal, nextCommandId);
396420
this._handleCommandStartOptions = undefined;
397421
}
398422

src/vs/platform/terminal/common/terminal.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ export interface IPtyService {
339339
getInitialCwd(id: number): Promise<string>;
340340
getCwd(id: number): Promise<string>;
341341
acknowledgeDataEvent(id: number, charCount: number): Promise<void>;
342+
setNextCommandId(id: number, commandLine: string, commandId: string): Promise<void>;
342343
setUnicodeVersion(id: number, version: '6' | '11'): Promise<void>;
343344
processBinary(id: number, data: string): Promise<void>;
344345
/** Confirm the process is _not_ an orphan. */
@@ -816,6 +817,12 @@ export interface ITerminalChildProcess {
816817
*/
817818
acknowledgeDataEvent(charCount: number): void;
818819

820+
/**
821+
* Pre-assigns the command identifier that should be associated with the next command detected by
822+
* shell integration. This keeps the pty host and renderer command stores aligned.
823+
*/
824+
setNextCommandId(commandLine: string, commandId: string): Promise<void>;
825+
819826
/**
820827
* Sets the unicode version for the process, this drives the size of some characters in the
821828
* xterm-headless instance.
@@ -976,6 +983,8 @@ export interface IShellIntegration {
976983
readonly onDidChangeSeenSequences: Event<ReadonlySet<string>>;
977984

978985
deserialize(serialized: ISerializedCommandDetectionCapability): void;
986+
987+
setNextCommandId(command: string, commandId: string): void;
979988
}
980989

981990
export interface IDecorationAddon {

src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,12 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
376376
this._createOrGetBufferMarkDetection(terminal).getMark(vscodeMarkerId);
377377
}
378378

379+
setNextCommandId(command: string, commandId: string): void {
380+
if (this._terminal) {
381+
this._createOrGetCommandDetection(this._terminal).setNextCommandId(command, commandId);
382+
}
383+
}
384+
379385
private _markSequenceSeen(sequence: string) {
380386
if (!this._seenSequences.has(sequence)) {
381387
this._seenSequences.add(sequence);

src/vs/platform/terminal/node/ptyHostService.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@ export class PtyHostService extends Disposable implements IPtyHostService {
266266
setUnicodeVersion(id: number, version: '6' | '11'): Promise<void> {
267267
return this._proxy.setUnicodeVersion(id, version);
268268
}
269+
setNextCommandId(id: number, commandLine: string, commandId: string): Promise<void> {
270+
return this._proxy.setNextCommandId(id, commandLine, commandId);
271+
}
269272
getInitialCwd(id: number): Promise<string> {
270273
return this._proxy.getInitialCwd(id);
271274
}

src/vs/platform/terminal/node/ptyService.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,11 @@ export class PtyService extends Disposable implements IPtyService {
455455
async setUnicodeVersion(id: number, version: '6' | '11'): Promise<void> {
456456
return this._throwIfNoPty(id).setUnicodeVersion(version);
457457
}
458+
459+
@traceRpc
460+
async setNextCommandId(id: number, commandLine: string, commandId: string): Promise<void> {
461+
return this._throwIfNoPty(id).setNextCommandId(commandLine, commandId);
462+
}
458463
@traceRpc
459464
async getLatency(): Promise<IPtyHostLatencyMeasurement[]> {
460465
return [];
@@ -892,6 +897,11 @@ class PersistentTerminalProcess extends Disposable {
892897
this._serializer.setUnicodeVersion?.(version);
893898
// TODO: Pass in unicode version in ctor
894899
}
900+
901+
async setNextCommandId(commandLine: string, commandId: string): Promise<void> {
902+
this._serializer.setNextCommandId?.(commandLine, commandId);
903+
}
904+
895905
acknowledgeDataEvent(charCount: number): void {
896906
if (this._inReplay) {
897907
return;
@@ -1039,6 +1049,10 @@ class XtermSerializer implements ITerminalSerializer {
10391049
this._xterm.clear();
10401050
}
10411051

1052+
setNextCommandId(commandLine: string, commandId: string): void {
1053+
this._shellIntegrationAddon.setNextCommandId(commandLine, commandId);
1054+
}
1055+
10421056
async generateReplayEvent(normalBufferOnly?: boolean, restoreToLastReviveBuffer?: boolean): Promise<IPtyHostProcessReplayEvent> {
10431057
const serialize = new (await this._getSerializeConstructor());
10441058
this._xterm.loadAddon(serialize);
@@ -1126,4 +1140,5 @@ interface ITerminalSerializer {
11261140
clearBuffer(): void;
11271141
generateReplayEvent(normalBufferOnly?: boolean, restoreToLastReviveBuffer?: boolean): Promise<IPtyHostProcessReplayEvent>;
11281142
setUnicodeVersion?(version: '6' | '11'): void;
1143+
setNextCommandId?(commandLine: string, commandId: string): void;
11291144
}

src/vs/platform/terminal/node/terminalProcess.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,10 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
614614
// No-op
615615
}
616616

617+
async setNextCommandId(commandLine: string, commandId: string): Promise<void> {
618+
// No-op: command IDs are tracked on the renderer and serializer only.
619+
}
620+
617621
getInitialCwd(): Promise<string> {
618622
return Promise.resolve(this._initialCwd);
619623
}

src/vs/server/node/remoteTerminalChannel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel<
151151
case RemoteTerminalChannelRequest.ReviveTerminalProcesses: return this._ptyHostService.reviveTerminalProcesses.apply(this._ptyHostService, args);
152152
case RemoteTerminalChannelRequest.GetRevivedPtyNewId: return this._ptyHostService.getRevivedPtyNewId.apply(this._ptyHostService, args);
153153
case RemoteTerminalChannelRequest.SetUnicodeVersion: return this._ptyHostService.setUnicodeVersion.apply(this._ptyHostService, args);
154+
case RemoteTerminalChannelRequest.SetNextCommandId: return this._ptyHostService.setNextCommandId.apply(this._ptyHostService, args);
154155
case RemoteTerminalChannelRequest.ReduceConnectionGraceTime: return this._reduceConnectionGraceTime();
155156
case RemoteTerminalChannelRequest.UpdateIcon: return this._ptyHostService.updateIcon.apply(this._ptyHostService, args);
156157
case RemoteTerminalChannelRequest.UpdateTitle: return this._ptyHostService.updateTitle.apply(this._ptyHostService, args);

src/vs/workbench/api/common/extHostTerminalService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,10 @@ class ExtHostPseudoterminal implements ITerminalChildProcess {
363363
// No-op, xterm-headless isn't used for extension owned terminals.
364364
}
365365

366+
async setNextCommandId(commandLine: string, commandId: string): Promise<void> {
367+
// No-op, command IDs are only tracked on the renderer for extension terminals.
368+
}
369+
366370
getInitialCwd(): Promise<string> {
367371
return Promise.resolve('');
368372
}

0 commit comments

Comments
 (0)