Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore old buffers if no additional interactions happened #152408

Merged
merged 3 commits into from
Jun 17, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 58 additions & 20 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,15 +506,13 @@ 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,
fixedDimensions?: IFixedTerminalDimensions
) {
super();
if (name) {
this.setTitle(name, TitleEventSource.Api);
}
this._logService.trace('persistentTerminalProcess#ctor', _persistentProcessId, arguments);
this._wasRevived = reviveBuffer !== undefined;
this._serializer = new XtermSerializer(
Expand All @@ -504,8 +521,12 @@ export class PersistentTerminalProcess extends Disposable {
reconnectConstants.scrollback,
unicodeVersion,
reviveBuffer,
shouldPersistTerminal ? rawReviveBuffer : undefined,
this._logService
);
if (name) {
this.setTitle(name, TitleEventSource.Api);
}
this._fixedDimensions = fixedDimensions;
this._orphanQuestionBarrier = null;
this._orphanQuestionReplyTime = 0;
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 of 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;
}