@sussudio/platform
Version:
Internal APIs for VS Code's service injection the base services.
432 lines (431 loc) • 15.4 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate =
(this && this.__decorate) ||
function (decorators, target, key, desc) {
var c = arguments.length,
r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc,
d;
if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function')
r = Reflect.decorate(decorators, target, key, desc);
else
for (var i = decorators.length - 1; i >= 0; i--)
if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param =
(this && this.__param) ||
function (paramIndex, decorator) {
return function (target, key) {
decorator(target, key, paramIndex);
};
};
import { Emitter } from '@sussudio/base/common/event.mjs';
import { Disposable, toDisposable } from '@sussudio/base/common/lifecycle.mjs';
import { FileAccess } from '@sussudio/base/common/network.mjs';
import { isWindows } from '@sussudio/base/common/platform.mjs';
import { ProxyChannel } from '@sussudio/base/parts/ipc/common/ipc.mjs';
import { Client } from '@sussudio/base/parts/ipc/node/ipc.cp.mjs';
import { IConfigurationService } from '../../configuration/common/configuration.mjs';
import { IEnvironmentService } from '../../environment/common/environment.mjs';
import { parsePtyHostPort } from '../../environment/common/environmentService.mjs';
import { getResolvedShellEnv } from '../../shell/node/shellEnv.mjs';
import { ILogService } from '../../log/common/log.mjs';
import { LogLevelChannelClient } from '../../log/common/logIpc.mjs';
import { RequestStore } from '../common/requestStore.mjs';
import { HeartbeatConstants, TerminalIpcChannels } from '../common/terminal.mjs';
import { registerTerminalPlatformConfiguration } from '../common/terminalPlatformConfiguration.mjs';
import { detectAvailableProfiles } from './terminalProfiles.mjs';
var Constants;
(function (Constants) {
Constants[(Constants['MaxRestarts'] = 5)] = 'MaxRestarts';
})(Constants || (Constants = {}));
/**
* Tracks the last terminal ID from the pty host so we can give it to the new pty host if it's
* restarted and avoid ID conflicts.
*/
let lastPtyId = 0;
/**
* This service implements IPtyService by launching a pty host process, forwarding messages to and
* from the pty host process and manages the connection.
*/
let PtyHostService = class PtyHostService extends Disposable {
_reconnectConstants;
_configurationService;
_environmentService;
_logService;
_client;
// ProxyChannel is not used here because events get lost when forwarding across multiple proxies
_proxy;
_shellEnv;
_resolveVariablesRequestStore;
_restartCount = 0;
_isResponsive = true;
_isDisposed = false;
_heartbeatFirstTimeout;
_heartbeatSecondTimeout;
_onPtyHostExit = this._register(new Emitter());
onPtyHostExit = this._onPtyHostExit.event;
_onPtyHostStart = this._register(new Emitter());
onPtyHostStart = this._onPtyHostStart.event;
_onPtyHostUnresponsive = this._register(new Emitter());
onPtyHostUnresponsive = this._onPtyHostUnresponsive.event;
_onPtyHostResponsive = this._register(new Emitter());
onPtyHostResponsive = this._onPtyHostResponsive.event;
_onPtyHostRequestResolveVariables = this._register(new Emitter());
onPtyHostRequestResolveVariables = this._onPtyHostRequestResolveVariables.event;
_onProcessData = this._register(new Emitter());
onProcessData = this._onProcessData.event;
_onProcessReady = this._register(new Emitter());
onProcessReady = this._onProcessReady.event;
_onProcessReplay = this._register(new Emitter());
onProcessReplay = this._onProcessReplay.event;
_onProcessOrphanQuestion = this._register(new Emitter());
onProcessOrphanQuestion = this._onProcessOrphanQuestion.event;
_onDidRequestDetach = this._register(new Emitter());
onDidRequestDetach = this._onDidRequestDetach.event;
_onDidChangeProperty = this._register(new Emitter());
onDidChangeProperty = this._onDidChangeProperty.event;
_onProcessExit = this._register(new Emitter());
onProcessExit = this._onProcessExit.event;
constructor(_reconnectConstants, _configurationService, _environmentService, _logService) {
super();
this._reconnectConstants = _reconnectConstants;
this._configurationService = _configurationService;
this._environmentService = _environmentService;
this._logService = _logService;
// Platform configuration is required on the process running the pty host (shared process or
// remote server).
registerTerminalPlatformConfiguration();
this._shellEnv = this._resolveShellEnv();
this._register(toDisposable(() => this._disposePtyHost()));
this._resolveVariablesRequestStore = this._register(new RequestStore(undefined, this._logService));
this._resolveVariablesRequestStore.onCreateRequest(
this._onPtyHostRequestResolveVariables.fire,
this._onPtyHostRequestResolveVariables,
);
[this._client, this._proxy] = this._startPtyHost();
this._register(
this._configurationService.onDidChangeConfiguration(async (e) => {
if (
e.affectsConfiguration('terminal.integrated.ignoreProcessNames' /* TerminalSettingId.IgnoreProcessNames */)
) {
await this._refreshIgnoreProcessNames();
}
}),
);
}
initialize() {
this._refreshIgnoreProcessNames();
}
get _ignoreProcessNames() {
return this._configurationService.getValue(
'terminal.integrated.ignoreProcessNames' /* TerminalSettingId.IgnoreProcessNames */,
);
}
async _refreshIgnoreProcessNames() {
return this._proxy.refreshIgnoreProcessNames?.(this._ignoreProcessNames);
}
async _resolveShellEnv() {
if (isWindows) {
return process.env;
}
try {
return await getResolvedShellEnv(this._configurationService, this._logService, { _: [] }, process.env);
} catch (error) {
this._logService.error('ptyHost was unable to resolve shell environment', error);
return {};
}
}
_startPtyHost() {
const opts = {
serverName: 'Pty Host',
args: ['--type=ptyHost', '--logsPath', this._environmentService.logsPath],
env: {
VSCODE_LAST_PTY_ID: lastPtyId,
VSCODE_AMD_ENTRYPOINT: 'vs/platform/terminal/node/ptyHostMain',
VSCODE_PIPE_LOGGING: 'true',
VSCODE_VERBOSE_LOGGING: 'true',
VSCODE_RECONNECT_GRACE_TIME: this._reconnectConstants.graceTime,
VSCODE_RECONNECT_SHORT_GRACE_TIME: this._reconnectConstants.shortGraceTime,
VSCODE_RECONNECT_SCROLLBACK: this._reconnectConstants.scrollback,
},
};
const ptyHostDebug = parsePtyHostPort(this._environmentService.args, this._environmentService.isBuilt);
if (ptyHostDebug) {
if (ptyHostDebug.break && ptyHostDebug.port) {
opts.debugBrk = ptyHostDebug.port;
} else if (!ptyHostDebug.break && ptyHostDebug.port) {
opts.debug = ptyHostDebug.port;
}
}
const client = new Client(FileAccess.asFileUri('bootstrap-fork').fsPath, opts);
this._onPtyHostStart.fire();
// Setup heartbeat service and trigger a heartbeat immediately to reset the timeouts
const heartbeatService = ProxyChannel.toService(client.getChannel(TerminalIpcChannels.Heartbeat));
heartbeatService.onBeat(() => this._handleHeartbeat());
this._handleHeartbeat();
// Handle exit
this._register(
client.onDidProcessExit((e) => {
this._onPtyHostExit.fire(e.code);
if (!this._isDisposed) {
if (this._restartCount <= Constants.MaxRestarts) {
this._logService.error(`ptyHost terminated unexpectedly with code ${e.code}`);
this._restartCount++;
this.restartPtyHost();
} else {
this._logService.error(`ptyHost terminated unexpectedly with code ${e.code}, giving up`);
}
}
}),
);
// Setup logging
const logChannel = client.getChannel(TerminalIpcChannels.Log);
LogLevelChannelClient.setLevel(logChannel, this._logService.getLevel());
this._register(
this._logService.onDidChangeLogLevel(() => {
LogLevelChannelClient.setLevel(logChannel, this._logService.getLevel());
}),
);
// Create proxy and forward events
const proxy = ProxyChannel.toService(client.getChannel(TerminalIpcChannels.PtyHost));
this._register(proxy.onProcessData((e) => this._onProcessData.fire(e)));
this._register(proxy.onProcessReady((e) => this._onProcessReady.fire(e)));
this._register(proxy.onProcessExit((e) => this._onProcessExit.fire(e)));
this._register(proxy.onDidChangeProperty((e) => this._onDidChangeProperty.fire(e)));
this._register(proxy.onProcessReplay((e) => this._onProcessReplay.fire(e)));
this._register(proxy.onProcessOrphanQuestion((e) => this._onProcessOrphanQuestion.fire(e)));
this._register(proxy.onDidRequestDetach((e) => this._onDidRequestDetach.fire(e)));
return [client, proxy];
}
dispose() {
this._isDisposed = true;
super.dispose();
}
async createProcess(
shellLaunchConfig,
cwd,
cols,
rows,
unicodeVersion,
env,
executableEnv,
options,
shouldPersist,
workspaceId,
workspaceName,
) {
const timeout = setTimeout(() => this._handleUnresponsiveCreateProcess(), HeartbeatConstants.CreateProcessTimeout);
const id = await this._proxy.createProcess(
shellLaunchConfig,
cwd,
cols,
rows,
unicodeVersion,
env,
executableEnv,
options,
shouldPersist,
workspaceId,
workspaceName,
);
clearTimeout(timeout);
lastPtyId = Math.max(lastPtyId, id);
return id;
}
updateTitle(id, title, titleSource) {
return this._proxy.updateTitle(id, title, titleSource);
}
updateIcon(id, userInitiated, icon, color) {
return this._proxy.updateIcon(id, userInitiated, icon, color);
}
attachToProcess(id) {
return this._proxy.attachToProcess(id);
}
detachFromProcess(id, forcePersist) {
return this._proxy.detachFromProcess(id, forcePersist);
}
listProcesses() {
return this._proxy.listProcesses();
}
reduceConnectionGraceTime() {
return this._proxy.reduceConnectionGraceTime();
}
start(id) {
return this._proxy.start(id);
}
shutdown(id, immediate) {
return this._proxy.shutdown(id, immediate);
}
input(id, data) {
return this._proxy.input(id, data);
}
processBinary(id, data) {
return this._proxy.processBinary(id, data);
}
resize(id, cols, rows) {
return this._proxy.resize(id, cols, rows);
}
acknowledgeDataEvent(id, charCount) {
return this._proxy.acknowledgeDataEvent(id, charCount);
}
setUnicodeVersion(id, version) {
return this._proxy.setUnicodeVersion(id, version);
}
getInitialCwd(id) {
return this._proxy.getInitialCwd(id);
}
getCwd(id) {
return this._proxy.getCwd(id);
}
getLatency(id) {
return this._proxy.getLatency(id);
}
orphanQuestionReply(id) {
return this._proxy.orphanQuestionReply(id);
}
installAutoReply(match, reply) {
return this._proxy.installAutoReply(match, reply);
}
uninstallAllAutoReplies() {
return this._proxy.uninstallAllAutoReplies();
}
uninstallAutoReply(match) {
return this._proxy.uninstallAutoReply(match);
}
getDefaultSystemShell(osOverride) {
return this._proxy.getDefaultSystemShell(osOverride);
}
async getProfiles(workspaceId, profiles, defaultProfile, includeDetectedProfiles = false) {
const shellEnv = await this._shellEnv;
return detectAvailableProfiles(
profiles,
defaultProfile,
includeDetectedProfiles,
this._configurationService,
shellEnv,
undefined,
this._logService,
this._resolveVariables.bind(this, workspaceId),
);
}
getEnvironment() {
return this._proxy.getEnvironment();
}
getWslPath(original, direction) {
return this._proxy.getWslPath(original, direction);
}
getRevivedPtyNewId(id) {
return this._proxy.getRevivedPtyNewId(id);
}
setTerminalLayoutInfo(args) {
return this._proxy.setTerminalLayoutInfo(args);
}
async getTerminalLayoutInfo(args) {
return await this._proxy.getTerminalLayoutInfo(args);
}
async requestDetachInstance(workspaceId, instanceId) {
return this._proxy.requestDetachInstance(workspaceId, instanceId);
}
async acceptDetachInstanceReply(requestId, persistentProcessId) {
return this._proxy.acceptDetachInstanceReply(requestId, persistentProcessId);
}
async freePortKillProcess(port) {
if (!this._proxy.freePortKillProcess) {
throw new Error('freePortKillProcess does not exist on the pty proxy');
}
return this._proxy.freePortKillProcess(port);
}
async serializeTerminalState(ids) {
return this._proxy.serializeTerminalState(ids);
}
async reviveTerminalProcesses(state, dateTimeFormatLocate) {
return this._proxy.reviveTerminalProcesses(state, dateTimeFormatLocate);
}
async refreshProperty(id, property) {
return this._proxy.refreshProperty(id, property);
}
async updateProperty(id, property, value) {
return this._proxy.updateProperty(id, property, value);
}
async restartPtyHost() {
this._isResponsive = true;
this._disposePtyHost();
[this._client, this._proxy] = this._startPtyHost();
}
_disposePtyHost() {
this._proxy.shutdownAll?.();
this._client.dispose();
}
_handleHeartbeat() {
this._clearHeartbeatTimeouts();
this._heartbeatFirstTimeout = setTimeout(
() => this._handleHeartbeatFirstTimeout(),
HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier,
);
if (!this._isResponsive) {
this._isResponsive = true;
this._onPtyHostResponsive.fire();
}
}
_handleHeartbeatFirstTimeout() {
this._logService.warn(
`No ptyHost heartbeat after ${
(HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier) / 1000
} seconds`,
);
this._heartbeatFirstTimeout = undefined;
this._heartbeatSecondTimeout = setTimeout(
() => this._handleHeartbeatSecondTimeout(),
HeartbeatConstants.BeatInterval * HeartbeatConstants.SecondWaitMultiplier,
);
}
_handleHeartbeatSecondTimeout() {
this._logService.error(
`No ptyHost heartbeat after ${
(HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier +
HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier) /
1000
} seconds`,
);
this._heartbeatSecondTimeout = undefined;
if (this._isResponsive) {
this._isResponsive = false;
this._onPtyHostUnresponsive.fire();
}
}
_handleUnresponsiveCreateProcess() {
this._clearHeartbeatTimeouts();
this._logService.error(
`No ptyHost response to createProcess after ${HeartbeatConstants.CreateProcessTimeout / 1000} seconds`,
);
if (this._isResponsive) {
this._isResponsive = false;
this._onPtyHostUnresponsive.fire();
}
}
_clearHeartbeatTimeouts() {
if (this._heartbeatFirstTimeout) {
clearTimeout(this._heartbeatFirstTimeout);
this._heartbeatFirstTimeout = undefined;
}
if (this._heartbeatSecondTimeout) {
clearTimeout(this._heartbeatSecondTimeout);
this._heartbeatSecondTimeout = undefined;
}
}
_resolveVariables(workspaceId, text) {
return this._resolveVariablesRequestStore.createRequest({ workspaceId, originalText: text });
}
async acceptPtyHostResolvedVariables(requestId, resolved) {
this._resolveVariablesRequestStore.acceptReply(requestId, resolved);
}
};
PtyHostService = __decorate(
[__param(1, IConfigurationService), __param(2, IEnvironmentService), __param(3, ILogService)],
PtyHostService,
);
export { PtyHostService };