mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-01 05:02:35 +01:00
Merge pull request #185243 from microsoft/tyriar/ptyhost_trace
Improve pty host diagnostics and introduce hidden simulated latency settings
This commit is contained in:
@@ -935,7 +935,7 @@ export class CodeApplication extends Disposable {
|
||||
graceTime: LocalReconnectConstants.GraceTime,
|
||||
shortGraceTime: LocalReconnectConstants.ShortGraceTime,
|
||||
scrollback: this.configurationService.getValue<number>(TerminalSettingId.PersistentSessionScrollback) ?? 100
|
||||
}, this.environmentMainService, this.lifecycleMainService, this.logService);
|
||||
}, this.configurationService, this.environmentMainService, this.lifecycleMainService, this.logService);
|
||||
const ptyHostService = new PtyHostService(
|
||||
ptyHostStarter,
|
||||
this.configurationService,
|
||||
|
||||
@@ -107,7 +107,14 @@ export const enum TerminalSettingId {
|
||||
ShellIntegrationCommandHistory = 'terminal.integrated.shellIntegration.history',
|
||||
ShellIntegrationSuggestEnabled = 'terminal.integrated.shellIntegration.suggestEnabled',
|
||||
EnableImages = 'terminal.integrated.enableImages',
|
||||
SmoothScrolling = 'terminal.integrated.smoothScrolling'
|
||||
SmoothScrolling = 'terminal.integrated.smoothScrolling',
|
||||
|
||||
// Debug settings that are hidden from user
|
||||
|
||||
/** Simulated latency applied to all calls made to the pty host */
|
||||
DeveloperPtyHostLatency = 'terminal.integrated.developer.ptyHost.latency',
|
||||
/** Simulated startup delay of the pty host process */
|
||||
DeveloperPtyHostStartupDelay = 'terminal.integrated.developer.ptyHost.startupDelay',
|
||||
}
|
||||
|
||||
export const enum PosixShellType {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { parsePtyHostDebugPort } from 'vs/platform/environment/node/environmentS
|
||||
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { IReconnectConstants } from 'vs/platform/terminal/common/terminal';
|
||||
import { IReconnectConstants, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { IPtyHostConnection, IPtyHostStarter } from 'vs/platform/terminal/node/ptyHost';
|
||||
import { UtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess';
|
||||
import { Client as MessagePortClient } from 'vs/base/parts/ipc/electron-main/ipc.mp';
|
||||
@@ -17,6 +17,7 @@ import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export class ElectronPtyHostStarter implements IPtyHostStarter {
|
||||
|
||||
@@ -29,6 +30,7 @@ export class ElectronPtyHostStarter implements IPtyHostStarter {
|
||||
|
||||
constructor(
|
||||
private readonly _reconnectConstants: IReconnectConstants,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IEnvironmentService private readonly _environmentService: INativeEnvironmentService,
|
||||
@ILifecycleMainService private readonly _lifecycleMainService: ILifecycleMainService,
|
||||
@ILogService private readonly _logService: ILogService
|
||||
@@ -73,7 +75,7 @@ export class ElectronPtyHostStarter implements IPtyHostStarter {
|
||||
}
|
||||
|
||||
private _createPtyHostConfiguration(lastPtyId: number) {
|
||||
return {
|
||||
const config: { [key: string]: string } = {
|
||||
...deepClone(process.env),
|
||||
VSCODE_LAST_PTY_ID: String(lastPtyId),
|
||||
VSCODE_AMD_ENTRYPOINT: 'vs/platform/terminal/node/ptyHostMain',
|
||||
@@ -81,8 +83,17 @@ export class ElectronPtyHostStarter implements IPtyHostStarter {
|
||||
VSCODE_VERBOSE_LOGGING: 'true', // transmit console logs from server to client,
|
||||
VSCODE_RECONNECT_GRACE_TIME: String(this._reconnectConstants.graceTime),
|
||||
VSCODE_RECONNECT_SHORT_GRACE_TIME: String(this._reconnectConstants.shortGraceTime),
|
||||
VSCODE_RECONNECT_SCROLLBACK: String(this._reconnectConstants.scrollback)
|
||||
VSCODE_RECONNECT_SCROLLBACK: String(this._reconnectConstants.scrollback),
|
||||
};
|
||||
const simulatedLatency = this._configurationService.getValue(TerminalSettingId.DeveloperPtyHostLatency);
|
||||
if (simulatedLatency && typeof simulatedLatency === 'number') {
|
||||
config.VSCODE_LATENCY = String(simulatedLatency);
|
||||
}
|
||||
const startupDelay = this._configurationService.getValue(TerminalSettingId.DeveloperPtyHostStartupDelay);
|
||||
if (startupDelay && typeof startupDelay === 'number') {
|
||||
config.VSCODE_STARTUP_DELAY = String(startupDelay);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private _onWindowConnection(e: IpcMainEvent, nonce: string) {
|
||||
|
||||
@@ -20,49 +20,73 @@ import { IReconnectConstants, TerminalIpcChannels } from 'vs/platform/terminal/c
|
||||
import { HeartbeatService } from 'vs/platform/terminal/node/heartbeatService';
|
||||
import { PtyService } from 'vs/platform/terminal/node/ptyService';
|
||||
import { isUtilityProcess } from 'vs/base/parts/sandbox/node/electronTypes';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
const _isUtilityProcess = isUtilityProcess(process);
|
||||
startPtyHost();
|
||||
|
||||
let server: ChildProcessServer<string> | UtilityProcessServer;
|
||||
if (_isUtilityProcess) {
|
||||
server = new UtilityProcessServer();
|
||||
} else {
|
||||
server = new ChildProcessServer(TerminalIpcChannels.PtyHost);
|
||||
async function startPtyHost() {
|
||||
// Parse environment variables
|
||||
const startupDelay = parseInt(process.env.VSCODE_STARTUP_DELAY ?? '0');
|
||||
const simulatedLatency = parseInt(process.env.VSCODE_LATENCY ?? '0');
|
||||
const reconnectConstants: IReconnectConstants = {
|
||||
graceTime: parseInt(process.env.VSCODE_RECONNECT_GRACE_TIME || '0'),
|
||||
shortGraceTime: parseInt(process.env.VSCODE_RECONNECT_SHORT_GRACE_TIME || '0'),
|
||||
scrollback: parseInt(process.env.VSCODE_RECONNECT_SCROLLBACK || '100')
|
||||
};
|
||||
const lastPtyId = parseInt(process.env.VSCODE_LAST_PTY_ID || '0');
|
||||
|
||||
// Sanitize environment
|
||||
delete process.env.VSCODE_RECONNECT_GRACE_TIME;
|
||||
delete process.env.VSCODE_RECONNECT_SHORT_GRACE_TIME;
|
||||
delete process.env.VSCODE_RECONNECT_SCROLLBACK;
|
||||
delete process.env.VSCODE_LATENCY;
|
||||
delete process.env.VSCODE_STARTUP_DELAY;
|
||||
delete process.env.VSCODE_LAST_PTY_ID;
|
||||
|
||||
// Setup RPC
|
||||
const _isUtilityProcess = isUtilityProcess(process);
|
||||
let server: ChildProcessServer<string> | UtilityProcessServer;
|
||||
if (_isUtilityProcess) {
|
||||
server = new UtilityProcessServer();
|
||||
} else {
|
||||
server = new ChildProcessServer(TerminalIpcChannels.PtyHost);
|
||||
}
|
||||
|
||||
// Services
|
||||
const productService: IProductService = { _serviceBrand: undefined, ...product };
|
||||
const environmentService = new NativeEnvironmentService(parseArgs(process.argv, OPTIONS), productService);
|
||||
const loggerService = new LoggerService(getLogLevel(environmentService), environmentService.logsHome);
|
||||
server.registerChannel(TerminalIpcChannels.Logger, new LoggerChannel(loggerService, () => DefaultURITransformer));
|
||||
const logger = loggerService.createLogger('ptyhost', { name: localize('ptyHost', "Pty Host") });
|
||||
const logService = new LogService(logger, [new ConsoleLogger()]);
|
||||
|
||||
// Log and apply developer config
|
||||
if (startupDelay) {
|
||||
logService.warn(`Pty Host startup is delayed ${startupDelay}ms`);
|
||||
await timeout(startupDelay);
|
||||
}
|
||||
if (simulatedLatency) {
|
||||
logService.warn(`Pty host is simulating ${simulatedLatency}ms latency`);
|
||||
}
|
||||
|
||||
// Heartbeat responsiveness tracking
|
||||
const heartbeatService = new HeartbeatService();
|
||||
server.registerChannel(TerminalIpcChannels.Heartbeat, ProxyChannel.fromService(heartbeatService));
|
||||
|
||||
// Init pty service
|
||||
const ptyService = new PtyService(lastPtyId, logService, productService, reconnectConstants, simulatedLatency);
|
||||
const ptyServiceChannel = ProxyChannel.fromService(ptyService);
|
||||
server.registerChannel(TerminalIpcChannels.PtyHost, ptyServiceChannel);
|
||||
|
||||
// Register a channel for direct communication via Message Port
|
||||
if (_isUtilityProcess) {
|
||||
server.registerChannel(TerminalIpcChannels.PtyHostWindow, ptyServiceChannel);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
process.once('exit', () => {
|
||||
logService.dispose();
|
||||
heartbeatService.dispose();
|
||||
ptyService.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
const lastPtyId = parseInt(process.env.VSCODE_LAST_PTY_ID || '0');
|
||||
delete process.env.VSCODE_LAST_PTY_ID;
|
||||
|
||||
const productService: IProductService = { _serviceBrand: undefined, ...product };
|
||||
const environmentService = new NativeEnvironmentService(parseArgs(process.argv, OPTIONS), productService);
|
||||
|
||||
// Logging
|
||||
const loggerService = new LoggerService(getLogLevel(environmentService), environmentService.logsHome);
|
||||
server.registerChannel(TerminalIpcChannels.Logger, new LoggerChannel(loggerService, () => DefaultURITransformer));
|
||||
const logger = loggerService.createLogger('ptyhost', { name: localize('ptyHost', "Pty Host") });
|
||||
const logService = new LogService(logger, [new ConsoleLogger()]);
|
||||
|
||||
const heartbeatService = new HeartbeatService();
|
||||
server.registerChannel(TerminalIpcChannels.Heartbeat, ProxyChannel.fromService(heartbeatService));
|
||||
|
||||
const reconnectConstants: IReconnectConstants = {
|
||||
graceTime: parseInt(process.env.VSCODE_RECONNECT_GRACE_TIME || '0'),
|
||||
shortGraceTime: parseInt(process.env.VSCODE_RECONNECT_SHORT_GRACE_TIME || '0'),
|
||||
scrollback: parseInt(process.env.VSCODE_RECONNECT_SCROLLBACK || '100')
|
||||
};
|
||||
delete process.env.VSCODE_RECONNECT_GRACE_TIME;
|
||||
delete process.env.VSCODE_RECONNECT_SHORT_GRACE_TIME;
|
||||
delete process.env.VSCODE_RECONNECT_SCROLLBACK;
|
||||
|
||||
const ptyService = new PtyService(lastPtyId, logService, productService, reconnectConstants);
|
||||
const ptyServiceChannel = ProxyChannel.fromService(ptyService);
|
||||
server.registerChannel(TerminalIpcChannels.PtyHost, ptyServiceChannel);
|
||||
if (_isUtilityProcess) {
|
||||
server.registerChannel(TerminalIpcChannels.PtyHostWindow, ptyServiceChannel);
|
||||
}
|
||||
|
||||
process.once('exit', () => {
|
||||
logService.dispose();
|
||||
heartbeatService.dispose();
|
||||
ptyService.dispose();
|
||||
});
|
||||
|
||||
@@ -149,6 +149,7 @@ export class PtyHostService extends Disposable implements IPtyService {
|
||||
// Setup heartbeat service and trigger a heartbeat immediately to reset the timeouts
|
||||
const heartbeatService = ProxyChannel.toService<IHeartbeatService>(client.getChannel(TerminalIpcChannels.Heartbeat));
|
||||
heartbeatService.onBeat(() => this._handleHeartbeat());
|
||||
// TODO: Starting the heartbeat tracking now causes problems
|
||||
this._handleHeartbeat();
|
||||
|
||||
// Handle exit
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { execFile, exec } from 'child_process';
|
||||
import { AutoOpenBarrier, ProcessTimeRunOnceScheduler, Promises, Queue } from 'vs/base/common/async';
|
||||
import { AutoOpenBarrier, ProcessTimeRunOnceScheduler, Promises, Queue, timeout } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IProcessEnvironment, isWindows, OperatingSystem, OS } from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { getSystemShell } from 'vs/base/node/shell';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
|
||||
import { RequestStore } from 'vs/platform/terminal/common/requestStore';
|
||||
import { IProcessDataEvent, IProcessReadyEvent, IPtyService, IRawTerminalInstanceLayoutInfo, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalInstanceLayoutInfoById, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalTabLayoutInfoById, TerminalIcon, IProcessProperty, TitleEventSource, ProcessPropertyType, IProcessPropertyMap, IFixedTerminalDimensions, IPersistentTerminalProcessLaunchConfig, ICrossVersionSerializedTerminalState, ISerializedTerminalState, ITerminalProcessOptions } from 'vs/platform/terminal/common/terminal';
|
||||
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
|
||||
@@ -30,6 +30,39 @@ import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalSt
|
||||
import { IPtyHostProcessReplayEvent } from 'vs/platform/terminal/common/capabilities/capabilities';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { join } from 'path';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
|
||||
export function traceRpc(_target: any, key: string, descriptor: any) {
|
||||
let fnKey: string | null = null;
|
||||
let fn: Function | null = null;
|
||||
|
||||
if (typeof descriptor.value === 'function') {
|
||||
fnKey = 'value';
|
||||
fn = descriptor.value;
|
||||
|
||||
if (fn!.length !== 0) {
|
||||
console.warn('Memoize should only be used in functions with zero parameters');
|
||||
}
|
||||
}
|
||||
|
||||
if (!fn) {
|
||||
throw new Error('not supported');
|
||||
}
|
||||
|
||||
descriptor[fnKey!] = async function (...args: any[]) {
|
||||
if (this.traceRpcArgs.logService.getLevel() === LogLevel.Trace) {
|
||||
this.traceRpcArgs.logService.trace(`[RPC Request] PtyService#${fnKey}(${args.map(e => JSON.stringify(e)).join(', ')})`);
|
||||
}
|
||||
if (this.traceRpcArgs.simulatedLatency) {
|
||||
await timeout(this.traceRpcArgs.simulatedLatency);
|
||||
}
|
||||
const result = await fn!.apply(this, args);
|
||||
if (this.traceRpcArgs.logService.getLevel() === LogLevel.Trace) {
|
||||
this.traceRpcArgs.logService.trace(`[RPC Response] PtyService#${fnKey}`, result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
type WorkspaceId = string;
|
||||
|
||||
@@ -63,11 +96,20 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
private readonly _onDidChangeProperty = this._register(new Emitter<{ id: number; property: IProcessProperty<any> }>());
|
||||
readonly onDidChangeProperty = this._onDidChangeProperty.event;
|
||||
|
||||
@memoize
|
||||
get traceRpcArgs(): { logService: ILogService; simulatedLatency: number } {
|
||||
return {
|
||||
logService: this._logService,
|
||||
simulatedLatency: this._simulatedLatency
|
||||
};
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _lastPtyId: number,
|
||||
private readonly _logService: ILogService,
|
||||
private readonly _productService: IProductService,
|
||||
private readonly _reconnectConstants: IReconnectConstants
|
||||
private readonly _reconnectConstants: IReconnectConstants,
|
||||
private readonly _simulatedLatency: number
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -82,6 +124,7 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
this._detachInstanceRequestStore.onCreateRequest(this._onDidRequestDetach.fire, this._onDidRequestDetach);
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async refreshIgnoreProcessNames(names: string[]): Promise<void> {
|
||||
ignoreProcessNames.length = 0;
|
||||
ignoreProcessNames.push(...names);
|
||||
@@ -93,10 +136,12 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
onPtyHostResponsive?: Event<void> | undefined;
|
||||
onPtyHostRequestResolveVariables?: Event<IRequestResolveVariablesEvent> | undefined;
|
||||
|
||||
@traceRpc
|
||||
async requestDetachInstance(workspaceId: string, instanceId: number): Promise<IProcessDetails | undefined> {
|
||||
return this._detachInstanceRequestStore.createRequest({ workspaceId, instanceId });
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async acceptDetachInstanceReply(requestId: number, persistentProcessId: number): Promise<void> {
|
||||
let processDetails: IProcessDetails | undefined = undefined;
|
||||
const pty = this._ptys.get(persistentProcessId);
|
||||
@@ -106,6 +151,7 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
this._detachInstanceRequestStore.acceptReply(requestId, processDetails);
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async freePortKillProcess(port: string): Promise<{ port: string; processId: string }> {
|
||||
const stdout = await new Promise<string>((resolve, reject) => {
|
||||
exec(isWindows ? `netstat -ano | findstr "${port}"` : `lsof -nP -iTCP -sTCP:LISTEN | grep ${port}`, {}, (err, stdout) => {
|
||||
@@ -131,6 +177,7 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
throw new Error(`Could not kill process with port ${port}`);
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async serializeTerminalState(ids: number[]): Promise<string> {
|
||||
const promises: Promise<ISerializedTerminalState>[] = [];
|
||||
for (const [persistentProcessId, persistentProcess] of this._ptys.entries()) {
|
||||
@@ -156,6 +203,7 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
return JSON.stringify(serialized);
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async reviveTerminalProcesses(state: ISerializedTerminalState[], dateTimeFormatLocale: string) {
|
||||
for (const terminal of state) {
|
||||
const restoreMessage = localize('terminal-history-restored', "History restored");
|
||||
@@ -189,10 +237,12 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
}
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async shutdownAll(): Promise<void> {
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async createProcess(
|
||||
shellLaunchConfig: IShellLaunchConfig,
|
||||
cwd: string,
|
||||
@@ -239,6 +289,7 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
return id;
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async attachToProcess(id: number): Promise<void> {
|
||||
try {
|
||||
await this._throwIfNoPty(id).attach();
|
||||
@@ -249,36 +300,44 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
}
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise<void> {
|
||||
this._throwIfNoPty(id).setTitle(title, titleSource);
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async updateIcon(id: number, userInitiated: boolean, icon: URI | { light: URI; dark: URI } | { id: string; color?: { id: string } }, color?: string): Promise<void> {
|
||||
this._throwIfNoPty(id).setIcon(userInitiated, icon, color);
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async clearBuffer(id: number): Promise<void> {
|
||||
this._throwIfNoPty(id).clearBuffer();
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async refreshProperty<T extends ProcessPropertyType>(id: number, type: T): Promise<IProcessPropertyMap[T]> {
|
||||
return this._throwIfNoPty(id).refreshProperty(type);
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async updateProperty<T extends ProcessPropertyType>(id: number, type: T, value: IProcessPropertyMap[T]): Promise<void> {
|
||||
return this._throwIfNoPty(id).updateProperty(type, value);
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async detachFromProcess(id: number, forcePersist?: boolean): Promise<void> {
|
||||
return this._throwIfNoPty(id).detach(forcePersist);
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async reduceConnectionGraceTime(): Promise<void> {
|
||||
for (const pty of this._ptys.values()) {
|
||||
pty.reduceGraceTime();
|
||||
}
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async listProcesses(): Promise<IProcessDetails[]> {
|
||||
const persistentProcesses = Array.from(this._ptys.entries()).filter(([_, pty]) => pty.shouldPersistTerminal);
|
||||
|
||||
@@ -288,45 +347,55 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
return allTerminals.filter(entry => entry.isOrphan);
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async start(id: number): Promise<ITerminalLaunchError | { injectedArgs: string[] } | undefined> {
|
||||
this._logService.trace('ptyService#start', id);
|
||||
const pty = this._ptys.get(id);
|
||||
return pty ? pty.start() : { message: `Could not find pty with id "${id}"` };
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async shutdown(id: number, immediate: boolean): Promise<void> {
|
||||
// Don't throw if the pty is already shutdown
|
||||
this._logService.trace('ptyService#shutDown', id, immediate);
|
||||
return this._ptys.get(id)?.shutdown(immediate);
|
||||
}
|
||||
@traceRpc
|
||||
async input(id: number, data: string): Promise<void> {
|
||||
return this._throwIfNoPty(id).input(data);
|
||||
}
|
||||
@traceRpc
|
||||
async processBinary(id: number, data: string): Promise<void> {
|
||||
return this._throwIfNoPty(id).writeBinary(data);
|
||||
}
|
||||
@traceRpc
|
||||
async resize(id: number, cols: number, rows: number): Promise<void> {
|
||||
return this._throwIfNoPty(id).resize(cols, rows);
|
||||
}
|
||||
@traceRpc
|
||||
async getInitialCwd(id: number): Promise<string> {
|
||||
return this._throwIfNoPty(id).getInitialCwd();
|
||||
}
|
||||
@traceRpc
|
||||
async getCwd(id: number): Promise<string> {
|
||||
return this._throwIfNoPty(id).getCwd();
|
||||
}
|
||||
@traceRpc
|
||||
async acknowledgeDataEvent(id: number, charCount: number): Promise<void> {
|
||||
return this._throwIfNoPty(id).acknowledgeDataEvent(charCount);
|
||||
}
|
||||
@traceRpc
|
||||
async setUnicodeVersion(id: number, version: '6' | '11'): Promise<void> {
|
||||
return this._throwIfNoPty(id).setUnicodeVersion(version);
|
||||
}
|
||||
@traceRpc
|
||||
async getLatency(id: number): Promise<number> {
|
||||
return 0;
|
||||
}
|
||||
@traceRpc
|
||||
async orphanQuestionReply(id: number): Promise<void> {
|
||||
return this._throwIfNoPty(id).orphanQuestionReply();
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async installAutoReply(match: string, reply: string) {
|
||||
this._autoReplies.set(match, reply);
|
||||
// If the auto reply exists on any existing terminals it will be overridden
|
||||
@@ -334,6 +403,7 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
p.installAutoReply(match, reply);
|
||||
}
|
||||
}
|
||||
@traceRpc
|
||||
async uninstallAllAutoReplies() {
|
||||
for (const match of this._autoReplies.keys()) {
|
||||
for (const p of this._ptys.values()) {
|
||||
@@ -341,20 +411,24 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
}
|
||||
}
|
||||
}
|
||||
@traceRpc
|
||||
async uninstallAutoReply(match: string) {
|
||||
for (const p of this._ptys.values()) {
|
||||
p.uninstallAutoReply(match);
|
||||
}
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async getDefaultSystemShell(osOverride: OperatingSystem = OS): Promise<string> {
|
||||
return getSystemShell(osOverride, process.env);
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async getEnvironment(): Promise<IProcessEnvironment> {
|
||||
return { ...process.env };
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async getWslPath(original: string, direction: 'unix-to-win' | 'win-to-unix' | unknown): Promise<string> {
|
||||
if (direction === 'win-to-unix') {
|
||||
if (!isWindows) {
|
||||
@@ -407,6 +481,7 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async getRevivedPtyNewId(id: number): Promise<number | undefined> {
|
||||
try {
|
||||
return this._revivedPtyIdMap.get(id)?.newId;
|
||||
@@ -416,18 +491,18 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise<void> {
|
||||
this._logService.trace('ptyService#setLayoutInfo', args.tabs);
|
||||
this._workspaceLayoutInfos.set(args.workspaceId, args);
|
||||
}
|
||||
|
||||
@traceRpc
|
||||
async getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise<ITerminalsLayoutInfo | undefined> {
|
||||
const layout = this._workspaceLayoutInfos.get(args.workspaceId);
|
||||
this._logService.trace('ptyService#getLayoutInfo', args);
|
||||
if (layout) {
|
||||
const expandedTabs = await Promise.all(layout.tabs.map(async tab => this._expandTerminalTab(tab)));
|
||||
const tabs = expandedTabs.filter(t => t.terminals.length > 0);
|
||||
this._logService.trace('ptyService#returnLayoutInfo', tabs);
|
||||
this._logService.trace('PtyService.getTerminalLayoutInfo result', tabs);
|
||||
return { tabs };
|
||||
}
|
||||
return undefined;
|
||||
@@ -605,7 +680,6 @@ class PersistentTerminalProcess extends Disposable {
|
||||
fixedDimensions?: IFixedTerminalDimensions
|
||||
) {
|
||||
super();
|
||||
this._logService.trace('persistentTerminalProcess#ctor', _persistentProcessId, arguments);
|
||||
this._interactionState = new MutationLogger(`Persistent process "${this._persistentProcessId}" interaction state`, InteractionState.None, this._logService);
|
||||
this._wasRevived = reviveBuffer !== undefined;
|
||||
this._serializer = new XtermSerializer(
|
||||
@@ -659,7 +733,6 @@ class PersistentTerminalProcess extends Disposable {
|
||||
}
|
||||
|
||||
async attach(): Promise<void> {
|
||||
this._logService.trace('persistentTerminalProcess#attach', this._persistentProcessId);
|
||||
// Something wrong happened if the disconnect runner is not canceled, this likely means
|
||||
// multiple windows attempted to attach.
|
||||
if (!await this._isOrphaned()) {
|
||||
@@ -673,7 +746,6 @@ class PersistentTerminalProcess extends Disposable {
|
||||
}
|
||||
|
||||
async detach(forcePersist?: boolean): Promise<void> {
|
||||
this._logService.trace('persistentTerminalProcess#detach', this._persistentProcessId, forcePersist);
|
||||
// Keep the process around if it was indicated to persist and it has had some iteraction or
|
||||
// was replayed
|
||||
if (this.shouldPersistTerminal && (this._interactionState.value !== InteractionState.None || forcePersist)) {
|
||||
@@ -698,7 +770,6 @@ class PersistentTerminalProcess extends Disposable {
|
||||
}
|
||||
|
||||
async start(): Promise<ITerminalLaunchError | { injectedArgs: string[] } | undefined> {
|
||||
this._logService.trace('persistentTerminalProcess#start', this._persistentProcessId, this._isStarted);
|
||||
if (!this._isStarted) {
|
||||
const result = await this._terminalProcess.start();
|
||||
if (result && 'message' in result) {
|
||||
|
||||
@@ -236,7 +236,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
}
|
||||
return undefined;
|
||||
} catch (err) {
|
||||
this._logService.trace('IPty#spawn native exception', err);
|
||||
this._logService.trace('node-pty.node-pty.IPty#spawn native exception', err);
|
||||
return { message: `A native exception occurred during launch (${err.message})` };
|
||||
}
|
||||
}
|
||||
@@ -294,7 +294,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
): Promise<void> {
|
||||
const args = shellIntegrationInjection?.newArgs || shellLaunchConfig.args || [];
|
||||
await this._throttleKillSpawn();
|
||||
this._logService.trace('IPty#spawn', shellLaunchConfig.executable, args, options);
|
||||
this._logService.trace('node-pty.IPty#spawn', shellLaunchConfig.executable, args, options);
|
||||
const ptyProcess = spawn(shellLaunchConfig.executable!, args, options);
|
||||
this._ptyProcess = ptyProcess;
|
||||
this._childProcessMonitor = this._register(new ChildProcessMonitor(ptyProcess.pid, this._logService));
|
||||
@@ -312,7 +312,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
}
|
||||
|
||||
// Refire the data event
|
||||
this._logService.trace('IPty#onData', data);
|
||||
this._logService.trace('node-pty.IPty#onData', data);
|
||||
this._onProcessData.fire(data);
|
||||
if (this._closeTimeout) {
|
||||
this._queueProcessExit();
|
||||
@@ -374,7 +374,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
try {
|
||||
if (this._ptyProcess) {
|
||||
await this._throttleKillSpawn();
|
||||
this._logService.trace('IPty#kill');
|
||||
this._logService.trace('node-pty.IPty#kill');
|
||||
this._ptyProcess.kill();
|
||||
}
|
||||
} catch (ex) {
|
||||
@@ -508,7 +508,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
|
||||
private _doWrite(): void {
|
||||
const object = this._writeQueue.shift()!;
|
||||
this._logService.trace('IPty#write', object.data);
|
||||
this._logService.trace('node-pty.IPty#write', object.data);
|
||||
if (object.isBinary) {
|
||||
this._ptyProcess!.write(Buffer.from(object.data, 'binary') as any);
|
||||
} else {
|
||||
@@ -537,12 +537,12 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
return;
|
||||
}
|
||||
|
||||
this._logService.trace('IPty#resize', cols, rows);
|
||||
this._logService.trace('node-pty.IPty#resize', cols, rows);
|
||||
try {
|
||||
this._ptyProcess.resize(cols, rows);
|
||||
} catch (e) {
|
||||
// Swallow error if the pty has already exited
|
||||
this._logService.trace('IPty#resize exception ' + e.message);
|
||||
this._logService.trace('node-pty.IPty#resize exception ' + e.message);
|
||||
if (this._exitCode !== undefined &&
|
||||
e.message !== 'ioctl(2) failed, EBADF' &&
|
||||
e.message !== 'Cannot resize a pty that has already exited') {
|
||||
@@ -594,7 +594,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
resolve(this._initialCwd);
|
||||
return;
|
||||
}
|
||||
this._logService.trace('IPty#pid');
|
||||
this._logService.trace('node-pty.IPty#pid');
|
||||
exec('lsof -OPln -p ' + this._ptyProcess.pid + ' | grep cwd', { env: { ...process.env, LANG: 'en_US.UTF-8' } }, (error, stdout, stderr) => {
|
||||
if (!error && stdout !== '') {
|
||||
resolve(stdout.substring(stdout.indexOf('/'), stdout.length - 1));
|
||||
@@ -610,7 +610,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
if (!this._ptyProcess) {
|
||||
return this._initialCwd;
|
||||
}
|
||||
this._logService.trace('IPty#pid');
|
||||
this._logService.trace('node-pty.IPty#pid');
|
||||
try {
|
||||
return await Promises.readlink(`/proc/${this._ptyProcess.pid}/cwd`);
|
||||
} catch (error) {
|
||||
|
||||
@@ -183,6 +183,7 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke
|
||||
shouldPersist: boolean
|
||||
): Promise<ITerminalChildProcess> {
|
||||
const executableEnv = await this._shellEnvironmentService.getShellEnv();
|
||||
// TODO: Using _proxy here bypasses the lastPtyId tracking on the main process
|
||||
const id = await this._proxy.createProcess(shellLaunchConfig, cwd, cols, rows, unicodeVersion, env, executableEnv, options, shouldPersist, this._getWorkspaceId(), this._getWorkspaceName());
|
||||
const pty = this._instantiationService.createInstance(LocalPty, id, shouldPersist);
|
||||
this._ptys.set(id, pty);
|
||||
|
||||
Reference in New Issue
Block a user