UNPKG

sussudio

Version:

An unofficial VS Code Internal API

361 lines (360 loc) 16.8 kB
/*--------------------------------------------------------------------------------------------- * 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 "../../../base/common/event.mjs"; import { Disposable, toDisposable } from "../../../base/common/lifecycle.mjs"; import { FileAccess } from "../../../base/common/network.mjs"; import { isWindows } from "../../../base/common/platform.mjs"; import { ProxyChannel } from "../../../base/parts/ipc/common/ipc.mjs"; import { Client } from "../../../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 };