diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 18f4dd64f9917..47ee346072671 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -81,6 +81,9 @@ import { DeprecatedExtensionsCleaner } from 'vs/code/electron-browser/sharedProc import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { join } from 'vs/base/common/path'; +import { TerminalIpcChannels } from 'vs/platform/terminal/common/terminal'; +import { LocalPtyService } from 'vs/platform/terminal/electron-browser/localPtyService'; +import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; class SharedProcessMain extends Disposable { @@ -259,6 +262,9 @@ class SharedProcessMain extends Disposable { services.set(IUserDataSyncResourceEnablementService, new SyncDescriptor(UserDataSyncResourceEnablementService)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); + // Terminal + services.set(ILocalPtyService, new SyncDescriptor(LocalPtyService)); + return new InstantiationService(services); } @@ -304,6 +310,11 @@ class SharedProcessMain extends Disposable { const userDataAutoSync = this._register(accessor.get(IInstantiationService).createInstance(UserDataAutoSyncService)); const userDataAutoSyncChannel = new UserDataAutoSyncChannel(userDataAutoSync); this.server.registerChannel('userDataAutoSync', userDataAutoSyncChannel); + + // Terminal + const localPtyService = accessor.get(ILocalPtyService); + const localPtyChannel = ProxyChannel.fromService(localPtyService); + this.server.registerChannel(TerminalIpcChannels.LocalPty, localPtyChannel); } private registerErrorHandler(logService: ILogService): void { diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index c3b419991652a..ff58b3f5664e3 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -89,7 +89,6 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService'; import { once } from 'vs/base/common/functional'; -import { ILocalPtyMainService, LocalPtyMainService } from 'vs/platform/terminal/electron-main/localPtyMainService'; export class CodeApplication extends Disposable { private windowsMainService: IWindowsMainService | undefined; @@ -528,7 +527,6 @@ export class CodeApplication extends Disposable { services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService)); services.set(IDiagnosticsService, ProxyChannel.toService(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics'))))); - services.set(ILocalPtyMainService, new SyncDescriptor(LocalPtyMainService)); services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv])); services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService, [machineId])); services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService)); @@ -616,10 +614,6 @@ export class CodeApplication extends Disposable { const updateChannel = new UpdateChannel(updateService); electronIpcServer.registerChannel('update', updateChannel); - const localPtyMainService = accessor.get(ILocalPtyMainService); - const localPtyChannel = createChannelReceiver(localPtyMainService); - electronIpcServer.registerChannel('localPty', localPtyChannel); - const issueMainService = accessor.get(IIssueMainService); const issueChannel = ProxyChannel.fromService(issueMainService); electronIpcServer.registerChannel('issue', issueChannel); diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 5f5b8ea0fdbc3..679136eb7340f 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -7,7 +7,18 @@ import { Event } from 'vs/base/common/event'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -export interface ICommonLocalPtyService { +export enum TerminalIpcChannels { + /** + * Communicates between the renderer process and shared process. + */ + LocalPty = 'localPty', + /** + * Communicates between the shared process and the pty host process. + */ + PtyHost = 'ptyHost' +} + +export interface ILocalPtyService { readonly _serviceBrand: undefined; readonly onProcessData: Event<{ id: number, event: IProcessDataEvent | string }>; diff --git a/src/vs/platform/terminal/electron-browser/localPtyService.ts b/src/vs/platform/terminal/electron-browser/localPtyService.ts new file mode 100644 index 0000000000000..f3eca7ea16816 --- /dev/null +++ b/src/vs/platform/terminal/electron-browser/localPtyService.ts @@ -0,0 +1,104 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ILocalPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalIpcChannels } from 'vs/platform/terminal/common/terminal'; +import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; +import { FileAccess } from 'vs/base/common/network'; +import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { Emitter } from 'vs/base/common/event'; + +export class LocalPtyService extends Disposable implements ILocalPtyService { + declare readonly _serviceBrand: undefined; + + // ProxyChannel is not used here because events get lost when forwarding across multiple proxies + private readonly _proxy: ILocalPtyService; + + private readonly _onProcessData = this._register(new Emitter<{ id: number, event: IProcessDataEvent | string }>()); + readonly onProcessData = this._onProcessData.event; + private readonly _onProcessExit = this._register(new Emitter<{ id: number, event: number | undefined }>()); + readonly onProcessExit = this._onProcessExit.event; + private readonly _onProcessReady = this._register(new Emitter<{ id: number, event: { pid: number, cwd: string } }>()); + readonly onProcessReady = this._onProcessReady.event; + private readonly _onProcessTitleChanged = this._register(new Emitter<{ id: number, event: string }>()); + readonly onProcessTitleChanged = this._onProcessTitleChanged.event; + private readonly _onProcessOverrideDimensions = this._register(new Emitter<{ id: number, event: ITerminalDimensionsOverride | undefined }>()); + readonly onProcessOverrideDimensions = this._onProcessOverrideDimensions.event; + private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter<{ id: number, event: IShellLaunchConfig }>()); + readonly onProcessResolvedShellLaunchConfig = this._onProcessResolvedShellLaunchConfig.event; + + constructor( + @ILogService private readonly _logService: ILogService + ) { + super(); + + const client = this._register(new Client( + FileAccess.asFileUri('bootstrap-fork', require).fsPath, + { + serverName: 'Pty Host', + args: ['--type=ptyHost'], + env: { + VSCODE_AMD_ENTRYPOINT: 'vs/platform/terminal/node/ptyHostMain', + VSCODE_PIPE_LOGGING: 'true', + VSCODE_VERBOSE_LOGGING: 'true' // transmit console logs from server to client + } + } + )); + + // TODO: Handle exit gracefully + this._register(client.onDidProcessExit(e => { + this._logService.info('ptyHost exit', e); + // // our watcher app should never be completed because it keeps on watching. being in here indicates + // // that the watcher process died and we want to restart it here. we only do it a max number of times + // if (!this.isDisposed) { + // if (this.restartCounter <= FileWatcher.MAX_RESTARTS) { + // this.error('terminated unexpectedly and is restarted again...'); + // this.restartCounter++; + // this.startWatching(); + // } else { + // this.error('failed to start after retrying for some time, giving up. Please report this as a bug report!'); + // } + // } + })); + + this._proxy = ProxyChannel.toService(client.getChannel(TerminalIpcChannels.PtyHost)); + this._register(this._proxy.onProcessData(e => this._onProcessData.fire(e))); + this._register(this._proxy.onProcessExit(e => this._onProcessExit.fire(e))); + this._register(this._proxy.onProcessReady(e => this._onProcessReady.fire(e))); + this._register(this._proxy.onProcessTitleChanged(e => this._onProcessTitleChanged.fire(e))); + this._register(this._proxy.onProcessOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e))); + this._register(this._proxy.onProcessResolvedShellLaunchConfig(e => this._onProcessResolvedShellLaunchConfig.fire(e))); + } + + createProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, executableEnv: IProcessEnvironment, windowsEnableConpty: boolean): Promise { + return this._proxy.createProcess(shellLaunchConfig, cwd, cols, rows, env, executableEnv, windowsEnableConpty); + } + start(id: number): Promise { + return this._proxy.start(id); + } + shutdown(id: number, immediate: boolean): Promise { + return this._proxy.shutdown(id, immediate); + } + input(id: number, data: string): Promise { + return this._proxy.input(id, data); + } + resize(id: number, cols: number, rows: number): Promise { + return this._proxy.resize(id, cols, rows); + } + acknowledgeDataEvent(id: number, charCount: number): Promise { + return this._proxy.acknowledgeDataEvent(id, charCount); + } + getInitialCwd(id: number): Promise { + return this._proxy.getInitialCwd(id); + } + getCwd(id: number): Promise { + return this._proxy.getCwd(id); + } + getLatency(id: number): Promise { + return this._proxy.getLatency(id); + } +} diff --git a/src/vs/platform/terminal/electron-sandbox/terminal.ts b/src/vs/platform/terminal/electron-sandbox/terminal.ts index bf381cda52b2d..a987c576787f6 100644 --- a/src/vs/platform/terminal/electron-sandbox/terminal.ts +++ b/src/vs/platform/terminal/electron-sandbox/terminal.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ICommonLocalPtyService } from 'vs/platform/terminal/common/terminal'; +import { ILocalPtyService as ICommonLocalPtyService } from 'vs/platform/terminal/common/terminal'; export const ILocalPtyService = createDecorator('localPtyService'); diff --git a/src/vs/platform/terminal/node/ptyHostMain.ts b/src/vs/platform/terminal/node/ptyHostMain.ts new file mode 100644 index 0000000000000..4fbdbd729be49 --- /dev/null +++ b/src/vs/platform/terminal/node/ptyHostMain.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Server } from 'vs/base/parts/ipc/node/ipc.cp'; +import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; +import { PtyService } from 'vs/platform/terminal/node/ptyService'; +import { TerminalIpcChannels } from 'vs/platform/terminal/common/terminal'; + +const server = new Server('ptyHost'); +const service = new PtyService(); +server.registerChannel(TerminalIpcChannels.PtyHost, ProxyChannel.fromService(service)); diff --git a/src/vs/platform/terminal/electron-main/localPtyMainService.ts b/src/vs/platform/terminal/node/ptyService.ts similarity index 80% rename from src/vs/platform/terminal/electron-main/localPtyMainService.ts rename to src/vs/platform/terminal/node/ptyService.ts index e8bc16679d189..3c7460ad16015 100644 --- a/src/vs/platform/terminal/electron-main/localPtyMainService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -3,24 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IProcessEnvironment } from 'vs/base/common/platform'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ILogService } from 'vs/platform/log/common/log'; -import { ICommonLocalPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal'; +import { ILocalPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal'; import { TerminalProcess } from 'vs/platform/terminal/node/terminalProcess'; +import { Emitter } from 'vs/base/common/event'; +import { LogService, ConsoleLogger } from 'vs/platform/log/common/log'; -export const ILocalPtyMainService = createDecorator('localPtyMainService'); - -export interface ILocalPtyMainService extends ICommonLocalPtyService { } - -let currentLocalPtyId = 0; +let currentPtyId = 0; -export class LocalPtyMainService extends Disposable implements ICommonLocalPtyService { +export class PtyService extends Disposable implements ILocalPtyService { declare readonly _serviceBrand: undefined; - private readonly _localPtys: Map = new Map(); + private readonly _ptys: Map = new Map(); private readonly _onProcessData = this._register(new Emitter<{ id: number, event: IProcessDataEvent | string }>()); readonly onProcessData = this._onProcessData.event; @@ -36,14 +31,14 @@ export class LocalPtyMainService extends Disposable implements ICommonLocalPtySe readonly onProcessResolvedShellLaunchConfig = this._onProcessResolvedShellLaunchConfig.event; constructor( - @ILogService private readonly _logService: ILogService ) { super(); } async createProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, executableEnv: IProcessEnvironment, windowsEnableConpty: boolean): Promise { - const id = ++currentLocalPtyId; - const process = new TerminalProcess(shellLaunchConfig, cwd, cols, rows, env, executableEnv, windowsEnableConpty, this._logService); + const id = ++currentPtyId; + // TODO: Impl proper logging, level doesn't get passed over + const process = new TerminalProcess(shellLaunchConfig, cwd, cols, rows, env, executableEnv, windowsEnableConpty, new LogService(new ConsoleLogger())); process.onProcessData(event => this._onProcessData.fire({ id, event })); process.onProcessExit(event => this._onProcessExit.fire({ id, event })); process.onProcessReady(event => this._onProcessReady.fire({ id, event })); @@ -54,7 +49,7 @@ export class LocalPtyMainService extends Disposable implements ICommonLocalPtySe if (process.onProcessResolvedShellLaunchConfig) { process.onProcessResolvedShellLaunchConfig(event => this._onProcessResolvedShellLaunchConfig.fire({ id, event })); } - this._localPtys.set(id, process); + this._ptys.set(id, process); return id; } @@ -91,7 +86,7 @@ export class LocalPtyMainService extends Disposable implements ICommonLocalPtySe } private _throwIfNoPty(id: number): ITerminalChildProcess { - const pty = this._localPtys.get(id); + const pty = this._ptys.get(id); if (!pty) { throw new Error(`Could not find pty with id "${id}"`); } diff --git a/src/vs/workbench/buildfile.desktop.js b/src/vs/workbench/buildfile.desktop.js index 89939f33214cd..2272b7bbabe0e 100644 --- a/src/vs/workbench/buildfile.desktop.js +++ b/src/vs/workbench/buildfile.desktop.js @@ -28,6 +28,8 @@ exports.collectModules = function () { createModuleDescription('vs/platform/files/node/watcher/unix/watcherApp', []), createModuleDescription('vs/platform/files/node/watcher/nsfw/watcherApp', []), + createModuleDescription('vs/platform/terminal/node/ptyHostMain', []), + createModuleDescription('vs/workbench/services/extensions/node/extensionHostProcess', []), ]; -}; \ No newline at end of file +}; diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts index 2b150def37913..e7d8c6756d346 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts @@ -79,7 +79,6 @@ export class TerminalInstanceService implements ITerminalInstanceService { public async createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): Promise { const id = await this._localPtyService.createProcess(shellLaunchConfig, cwd, cols, rows, env, process.env as IProcessEnvironment, windowsEnableConpty); - console.log('local pty id ' + id); return this._instantiationService.createInstance(TerminalProcessMainProxy, id); } diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localPtyService.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localPtyServiceProxy.ts similarity index 50% rename from src/vs/workbench/contrib/terminal/electron-sandbox/localPtyService.ts rename to src/vs/workbench/contrib/terminal/electron-sandbox/localPtyServiceProxy.ts index 963f947f35dc9..86ac30f140957 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localPtyService.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localPtyServiceProxy.ts @@ -4,15 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; -import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; +import { TerminalIpcChannels } from 'vs/platform/terminal/common/terminal'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; // @ts-ignore: interface is implemented via proxy -export class LocalPtyService implements ILocalPtyService { +export class LocalPtyServiceProxy implements ILocalPtyService { declare readonly _serviceBrand: undefined; - constructor(@IMainProcessService mainProcessService: IMainProcessService) { - return createChannelSender(mainProcessService.getChannel('localPty')); + constructor( + @ISharedProcessService sharedProcessService: ISharedProcessService + ) { + return ProxyChannel.toService(sharedProcessService.getChannel(TerminalIpcChannels.LocalPty)); } } diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts index d2201d3b67613..66c8d6b6630dc 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts @@ -5,6 +5,6 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; -import { LocalPtyService } from 'vs/workbench/contrib/terminal/electron-sandbox/localPtyService'; +import { LocalPtyServiceProxy } from 'vs/workbench/contrib/terminal/electron-sandbox/localPtyServiceProxy'; -registerSingleton(ILocalPtyService, LocalPtyService, true); +registerSingleton(ILocalPtyService, LocalPtyServiceProxy, true);