Skip to content

Commit

Permalink
Restore old buffers if no additional interactions happened
Browse files Browse the repository at this point in the history
This makes terminals only restore their current session's buffer if
there was some interaction by the user, similar to how the initial
process doesn't get revived if no interaction occurred.

Fixes #142855
  • Loading branch information
Tyriar committed Jun 16, 2022
1 parent cb18a48 commit c8ac68b
Showing 1 changed file with 55 additions and 17 deletions.
72 changes: 55 additions & 17 deletions src/vs/platform/terminal/node/ptyService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ export class PtyService extends Disposable implements IPtyService {
true,
terminal.processDetails.workspaceId,
terminal.processDetails.workspaceName,
true
true,
terminal.replayEvent.events[0].data
);
// Don't start the process here as there's no terminal to answer CPR
this._revivedPtyIdMap.set(terminal.id, { newId, state: terminal });
Expand All @@ -175,7 +176,8 @@ export class PtyService extends Disposable implements IPtyService {
shouldPersist: boolean,
workspaceId: string,
workspaceName: string,
isReviving?: boolean
isReviving?: boolean,
rawReviveBuffer?: string
): Promise<number> {
if (shellLaunchConfig.attachPersistentProcess) {
throw new Error('Attempt to create a process when attach object was provided');
Expand All @@ -188,7 +190,7 @@ export class PtyService extends Disposable implements IPtyService {
executableEnv,
options
};
const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, processLaunchOptions, unicodeVersion, this._reconnectConstants, this._logService, isReviving ? shellLaunchConfig.initialText : undefined, shellLaunchConfig.icon, shellLaunchConfig.color, shellLaunchConfig.name, shellLaunchConfig.fixedDimensions);
const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, processLaunchOptions, unicodeVersion, this._reconnectConstants, this._logService, isReviving ? shellLaunchConfig.initialText : undefined, rawReviveBuffer, shellLaunchConfig.icon, shellLaunchConfig.color, shellLaunchConfig.name, shellLaunchConfig.fixedDimensions);
process.onDidChangeProperty(property => this._onDidChangeProperty.fire({ id, property }));
process.onProcessExit(event => {
persistentProcess.dispose();
Expand Down Expand Up @@ -409,6 +411,15 @@ export class PtyService extends Disposable implements IPtyService {
}
}

const enum InteractionState {
/** The terminal has not been interacted with. */
None = 0,
/** The terminal has only been interacted with by the replay mechanism. */
ReplayOnly = 1,
/** The terminal has been directly interacted with this session. */
Session = 2
}

export class PersistentTerminalProcess extends Disposable {

private readonly _bufferer: TerminalDataBufferer;
Expand All @@ -417,7 +428,7 @@ export class PersistentTerminalProcess extends Disposable {
private readonly _pendingCommands = new Map<number, { resolve: (data: any) => void; reject: (err: any) => void }>();

private _isStarted: boolean = false;
private _hasWrittenData: boolean = false;
private _interactionState: InteractionState = InteractionState.None;

private _orphanQuestionBarrier: AutoOpenBarrier | null;
private _orphanQuestionReplyTime: number;
Expand Down Expand Up @@ -451,21 +462,29 @@ export class PersistentTerminalProcess extends Disposable {

get pid(): number { return this._pid; }
get shellLaunchConfig(): IShellLaunchConfig { return this._terminalProcess.shellLaunchConfig; }
get hasWrittenData(): boolean { return this._hasWrittenData; }
get hasWrittenData(): boolean { return this._interactionState !== InteractionState.None; }
get title(): string { return this._title || this._terminalProcess.currentTitle; }
get titleSource(): TitleEventSource { return this._titleSource; }
get icon(): TerminalIcon | undefined { return this._icon; }
get color(): string | undefined { return this._color; }
get fixedDimensions(): IFixedTerminalDimensions | undefined { return this._fixedDimensions; }

setTitle(title: string, titleSource: TitleEventSource): void {
this._hasWrittenData = true;
if (titleSource === TitleEventSource.Api) {
this._interactionState = InteractionState.Session;
this._serializer.freeRawReviveBuffer();
}
this._title = title;
this._titleSource = titleSource;
}

setIcon(icon: TerminalIcon, color?: string): void {
this._hasWrittenData = true;
if (!this._icon || 'id' in icon && 'id' in this._icon && icon.id !== this._icon.id ||
!this.color || color !== this._color) {

this._serializer.freeRawReviveBuffer();
this._interactionState = InteractionState.Session;
}
this._icon = icon;
this._color = color;
}
Expand All @@ -487,6 +506,7 @@ export class PersistentTerminalProcess extends Disposable {
reconnectConstants: IReconnectConstants,
private readonly _logService: ILogService,
reviveBuffer: string | undefined,
rawReviveBuffer: string | undefined,
private _icon?: TerminalIcon,
private _color?: string,
name?: string,
Expand All @@ -504,6 +524,7 @@ export class PersistentTerminalProcess extends Disposable {
reconnectConstants.scrollback,
unicodeVersion,
reviveBuffer,
rawReviveBuffer,
this._logService
);
this._fixedDimensions = fixedDimensions;
Expand Down Expand Up @@ -559,7 +580,7 @@ export class PersistentTerminalProcess extends Disposable {
}

serializeNormalBuffer(): Promise<IPtyHostProcessReplayEvent> {
return this._serializer.generateReplayEvent(true);
return this._serializer.generateReplayEvent(true, this._interactionState !== InteractionState.Session);
}

async refreshProperty<T extends ProcessPropertyType>(type: T): Promise<IProcessPropertyMap[T]> {
Expand Down Expand Up @@ -604,7 +625,8 @@ export class PersistentTerminalProcess extends Disposable {
return this._terminalProcess.shutdown(immediate);
}
input(data: string): void {
this._hasWrittenData = true;
this._interactionState = InteractionState.Session;
this._serializer.freeRawReviveBuffer();
if (this._inReplay) {
return;
}
Expand Down Expand Up @@ -652,7 +674,9 @@ export class PersistentTerminalProcess extends Disposable {
}

async triggerReplay(): Promise<void> {
this._hasWrittenData = true;
if (this._interactionState === InteractionState.None) {
this._interactionState = InteractionState.ReplayOnly;
}
const ev = await this._serializer.generateReplayEvent();
let dataLength = 0;
for (const e of ev.events) {
Expand Down Expand Up @@ -736,18 +760,24 @@ class XtermSerializer implements ITerminalSerializer {
rows: number,
scrollback: number,
unicodeVersion: '6' | '11',
reviveBuffer: string | undefined,
reviveBufferWithRestoreMessage: string | undefined,
private _rawReviveBuffer: string | undefined,
logService: ILogService
) {
this._xterm = new XtermTerminal({ cols, rows, scrollback });
if (reviveBuffer) {
this._xterm.writeln(reviveBuffer);
if (reviveBufferWithRestoreMessage) {
this._xterm.writeln(reviveBufferWithRestoreMessage);
}
this.setUnicodeVersion(unicodeVersion);
this._shellIntegrationAddon = new ShellIntegrationAddon(true, undefined, logService);
this._xterm.loadAddon(this._shellIntegrationAddon);
}

freeRawReviveBuffer(): void {
// Free the memory if the terminal if it will need to be re-serialized
this._rawReviveBuffer = undefined;
}

handleData(data: string): void {
this._xterm.write(data);
}
Expand All @@ -756,15 +786,22 @@ class XtermSerializer implements ITerminalSerializer {
this._xterm.resize(cols, rows);
}

async generateReplayEvent(normalBufferOnly?: boolean): Promise<IPtyHostProcessReplayEvent> {
async generateReplayEvent(normalBufferOnly?: boolean, restoreToLastReviveBuffer?: boolean): Promise<IPtyHostProcessReplayEvent> {
const serialize = new (await this._getSerializeConstructor());
this._xterm.loadAddon(serialize);
const options: ISerializeOptions = { scrollback: this._xterm.getOption('scrollback') };
const options: ISerializeOptions = {
scrollback: this._xterm.getOption('scrollback')
};
if (normalBufferOnly) {
options.excludeAltBuffer = true;
options.excludeModes = true;
}
const serialized = serialize.serialize(options);
let serialized: string;
if (restoreToLastReviveBuffer && this._rawReviveBuffer) {
serialized = this._rawReviveBuffer;
} else {
serialized = serialize.serialize(options);
}
return {
events: [
{
Expand Down Expand Up @@ -831,7 +868,8 @@ function printTime(ms: number): string {

export interface ITerminalSerializer {
handleData(data: string): void;
freeRawReviveBuffer(): void;
handleResize(cols: number, rows: number): void;
generateReplayEvent(normalBufferOnly?: boolean): Promise<IPtyHostProcessReplayEvent>;
generateReplayEvent(normalBufferOnly?: boolean, restoreToLastReviveBuffer?: boolean): Promise<IPtyHostProcessReplayEvent>;
setUnicodeVersion?(version: '6' | '11'): void;
}

0 comments on commit c8ac68b

Please sign in to comment.