Fix restoring commands not working reliably

This commit is contained in:
Daniel Imms 2022-03-11 13:53:44 -08:00
parent 9addadc0b0
commit 5738c241b1
12 changed files with 58 additions and 34 deletions

View file

@ -104,6 +104,7 @@ export interface ICommandDetectionCapability {
* Set the command line explicitly.
*/
setCommandLine(commandLine: string): void;
serializeCommands(): ISerializedCommand[];
restoreCommands(serialized: ISerializedCommand[]): void;
}

View file

@ -225,8 +225,29 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
this._currentCommand.command = commandLine;
}
serializeCommands(): ISerializedCommand[] {
const serialized = this.commands.map(e => {
return {
startLine: e.marker?.line,
endLine: e.endMarker?.line,
cwd: e.cwd,
exitCode: e.exitCode,
timestamp: e.timestamp
};
});
if (this._currentCommand.commandStartMarker) {
serialized.push({
startLine: this._currentCommand.commandStartMarker.line,
endLine: undefined,
cwd: undefined,
exitCode: undefined,
timestamp: 0,
});
}
return serialized;
}
restoreCommands(serialized: ISerializedCommand[]): void {
console.log('restoreCommands', serialized);
const buffer = this._terminal.buffer.normal;
for (const e of serialized) {
const marker = e.startLine !== undefined ? this._terminal.registerMarker(e.startLine - (buffer.baseY + buffer.cursorY)) : undefined;
@ -241,6 +262,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
timestamp: e.timestamp,
cwd: e.cwd,
exitCode: e.exitCode,
// TODO: Implement correctly
hasOutput: false,
getOutput: () => ''
// hasOutput: !!(this._currentCommand.commandExecutedMarker && this._currentCommand.commandFinishedMarker && this._currentCommand.commandExecutedMarker?.line < this._currentCommand.commandFinishedMarker!.line),

View file

@ -598,6 +598,7 @@ export interface ITerminalChildProcess {
onProcessReady: Event<IProcessReadyEvent>;
onDidChangeProperty: Event<IProcessProperty<any>>;
onProcessExit: Event<number | undefined>;
onRestoreCommands?: Event<ISerializedCommand[]>;
/**
* Starts the process.

View file

@ -191,6 +191,14 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
return false;
}
serializeCommands(): ISerializedCommand[] {
if (!this._terminal || !this.capabilities.has(TerminalCapability.CommandDetection)) {
return [];
}
const result = this._createOrGetCommandDetection(this._terminal).serializeCommands();
return result;
}
restoreCommands(serialized: ISerializedCommand[]): void {
if (!this._terminal) {
throw new Error('Cannot restore commands before addon is activated');

View file

@ -18,7 +18,7 @@ import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnviro
import { Terminal as XtermTerminal } from 'xterm-headless';
import type { ISerializeOptions, SerializeAddon as XtermSerializeAddon } from 'xterm-addon-serialize';
import type { Unicode11Addon as XtermUnicode11Addon } from 'xterm-addon-unicode11';
import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISerializedCommand, ISetTerminalLayoutInfoArgs, ITerminalTabLayoutInfoDto } from 'vs/platform/terminal/common/terminalProcess';
import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs, ITerminalTabLayoutInfoDto } from 'vs/platform/terminal/common/terminalProcess';
import { getWindowsBuildNumber } from 'vs/platform/terminal/node/terminalEnvironment';
import { TerminalProcess } from 'vs/platform/terminal/node/terminalProcess';
import { localize } from 'vs/nls';
@ -26,7 +26,6 @@ import { ignoreProcessNames } from 'vs/platform/terminal/node/childProcessMonito
import { TerminalAutoResponder } from 'vs/platform/terminal/common/terminalAutoResponder';
import { ErrorNoTelemetry } from 'vs/base/common/errors';
import { ShellIntegrationAddon } from 'vs/platform/terminal/common/xterm/shellIntegrationAddon';
import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
type WorkspaceId = string;
@ -772,7 +771,7 @@ class XtermSerializer implements ITerminalSerializer {
data: serialized
}
],
commands: this._getSerializedCommands()
commands: this._shellIntegrationAddon.serializeCommands()
};
}
@ -803,22 +802,6 @@ class XtermSerializer implements ITerminalSerializer {
}
return SerializeAddon;
}
private _getSerializedCommands(): ISerializedCommand[] {
const commandDetection = this._shellIntegrationAddon.capabilities.get(TerminalCapability.CommandDetection);
if (!commandDetection) {
return [];
}
return commandDetection.commands.map(e => {
return {
cwd: e.cwd,
startLine: e.marker?.line,
endLine: e.endMarker?.line,
exitCode: e.exitCode,
timestamp: e.timestamp
};
});
}
}
function printTime(ms: number): string {

View file

@ -9,7 +9,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { IProcessDataEvent, ITerminalChildProcess, ITerminalLaunchError, IProcessProperty, IProcessPropertyMap, ProcessPropertyType, IProcessReadyEvent } from 'vs/platform/terminal/common/terminal';
import { IPtyHostProcessReplayEvent } from 'vs/platform/terminal/common/terminalProcess';
import { IPtyHostProcessReplayEvent, ISerializedCommand } from 'vs/platform/terminal/common/terminalProcess';
import { RemoteTerminalChannelClient } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
@ -22,6 +22,8 @@ export class RemotePty extends Disposable implements ITerminalChildProcess {
readonly onDidChangeProperty = this._onDidChangeProperty.event;
private readonly _onProcessExit = this._register(new Emitter<number | undefined>());
readonly onProcessExit = this._onProcessExit.event;
private readonly _onRestoreCommands = this._register(new Emitter<ISerializedCommand[]>());
readonly onRestoreCommands = this._onRestoreCommands.event;
private _startBarrier: Barrier;
@ -178,6 +180,10 @@ export class RemotePty extends Disposable implements ITerminalChildProcess {
this._inReplay = false;
}
if (e.commands.length > 0) {
this._onRestoreCommands.fire(e.commands);
}
// remove size override
this._onDidChangeProperty.fire({ type: ProcessPropertyType.OverrideDimensions, value: undefined });
}

View file

@ -708,6 +708,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._areLinksReady = true;
this._onLinksReady.fire(this);
});
this._processManager.onRestoreCommands(e => this.xterm?.shellIntegration.restoreCommands(e));
this._loadTypeAheadAddon(xterm);

View file

@ -32,6 +32,7 @@ import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilitie
import { NaiveCwdDetectionCapability } from 'vs/platform/terminal/common/capabilities/naiveCwdDetectionCapability';
import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
import { URI } from 'vs/base/common/uri';
import { ISerializedCommand } from 'vs/platform/terminal/common/terminalProcess';
/** The amount of time to consider terminal errors to be related to the launch */
const LAUNCHING_DURATION = 500;
@ -105,6 +106,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
readonly onEnvironmentVariableInfoChanged = this._onEnvironmentVariableInfoChange.event;
private readonly _onProcessExit = this._register(new Emitter<number | undefined>());
readonly onProcessExit = this._onProcessExit.event;
private readonly _onRestoreCommands = this._register(new Emitter<ISerializedCommand[]>());
readonly onRestoreCommands = this._onRestoreCommands.event;
get persistentProcessId(): number | undefined { return this._process?.id; }
get shouldPersist(): boolean { return this._process ? this._process.shouldPersist : false; }
@ -334,6 +337,11 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
this._onDidChangeProperty.fire({ type, value });
})
];
if (newProcess.onRestoreCommands) {
this._processListeners.push(newProcess.onRestoreCommands(e => {
this._onRestoreCommands.fire(e);
}));
}
setTimeout(() => {
if (this.processState === ProcessState.Launching) {

View file

@ -281,9 +281,6 @@ export class TerminalService implements ITerminalService {
}
}
});
backend.onRestoreCommands(e => {
this.getInstanceFromId(e.id)?.xterm?.shellIntegration?.restoreCommands(e.commands);
});
}
}

View file

@ -114,7 +114,6 @@ export interface ITerminalBackend {
onPtyHostRestart: Event<void>;
onDidRequestDetach: Event<{ requestId: number; workspaceId: string; instanceId: number }>;
onRestoreCommands: Event<{ id: number; commands: ISerializedCommand[] }>;
attachToProcess(id: number): Promise<ITerminalChildProcess | undefined>;
listProcesses(): Promise<IProcessDetails[]>;
@ -383,6 +382,7 @@ export interface ITerminalProcessManager extends IDisposable {
readonly onEnvironmentVariableInfoChanged: Event<IEnvironmentVariableInfo>;
readonly onDidChangeProperty: Event<IProcessProperty<any>>;
readonly onProcessExit: Event<number | undefined>;
readonly onRestoreCommands: Event<ISerializedCommand[]>;
dispose(immediate?: boolean): void;
detachFromProcess(): Promise<void>;

View file

@ -141,6 +141,10 @@ export class LocalPty extends Disposable implements ITerminalChildProcess {
this._inReplay = false;
}
if (e.commands.length > 0) {
this._onRestoreCommands.fire(e.commands);
}
// remove size override
this._onDidChangeProperty.fire({ type: ProcessPropertyType.OverrideDimensions, value: undefined });
}

View file

@ -15,7 +15,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { Registry } from 'vs/platform/registry/common/platform';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IProcessPropertyMap, IShellLaunchConfig, ITerminalChildProcess, ITerminalEnvironment, ITerminalProcessOptions, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, ProcessPropertyType, TerminalSettingId, TitleEventSource } from 'vs/platform/terminal/common/terminal';
import { IGetTerminalLayoutInfoArgs, IProcessDetails, ISerializedCommand, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess';
import { IGetTerminalLayoutInfoArgs, IProcessDetails, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess';
import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
@ -47,8 +47,6 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke
private readonly _onDidRequestDetach = this._register(new Emitter<{ requestId: number; workspaceId: string; instanceId: number }>());
readonly onDidRequestDetach = this._onDidRequestDetach.event;
private readonly _onRestoreCommands = this._register(new Emitter<{ id: number; commands: ISerializedCommand[] }>());
readonly onRestoreCommands = this._onRestoreCommands.event;
constructor(
readonly remoteAuthority: string | undefined,
@ -82,12 +80,7 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke
}
});
this._localPtyService.onProcessReady(e => this._ptys.get(e.id)?.handleReady(e.event));
this._localPtyService.onProcessReplay(e => {
this._ptys.get(e.id)?.handleReplay(e.event);
if (e.event.commands.length > 0) {
this._onRestoreCommands.fire({ id: e.id, commands: e.event.commands });
}
});
this._localPtyService.onProcessReplay(e => this._ptys.get(e.id)?.handleReplay(e.event));
this._localPtyService.onProcessOrphanQuestion(e => this._ptys.get(e.id)?.handleOrphanQuestion());
this._localPtyService.onDidRequestDetach(e => this._onDidRequestDetach.fire(e));