diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 7b60ba4a101..62b41dab29a 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -10,6 +10,9 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import * as errors from 'vs/base/common/errors'; import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; import { VSBuffer } from 'vs/base/common/buffer'; +import { revive } from 'vs/base/common/marshalling'; +import { isUndefinedOrNull } from 'vs/base/common/types'; +import { isUpperAsciiLetter } from 'vs/base/common/strings'; /** * An `IChannel` is an abstraction over a collection of commands. @@ -832,3 +835,131 @@ export class StaticRouter implements IClientRouter return await this.route(hub); } } + + +//#region Channel Sender/Receiver Generator + +/** + * Use both `createChannelReceiver` and `createChannelSender` + * for automated process <=> process communication over methods + * and events. You do not need to spell out each method on both + * sides, a proxy will take care of this. + * + * Rules: + * - if marshalling is enabled, only `URI` and `RegExp` is converted + * automatically for you + * - events must follow the naming convention `onUppercase` + * - `CancellationToken` is currently not supported + * - if a context is provided, you can use `AddFirstParameterToFunctions` + * utility to signal this in the receiving side type + */ + +export interface IBaseChannelOptions { + + /** + * Disables automatic marshalling of `URI`. + * If marshalling is disabled, `UriComponents` + * must be used instead. + */ + disableMarshalling?: boolean; +} + +export interface IChannelReceiverOptions extends IBaseChannelOptions { } + +export function createChannelReceiver(service: unknown, options?: IChannelReceiverOptions): IServerChannel { + const handler = service as { [key: string]: unknown }; + const disableMarshalling = options && options.disableMarshalling; + + // Buffer any event that should be supported by + // iterating over all property keys and finding them + const mapEventNameToEvent = new Map>(); + for (const key in handler) { + if (propertyIsEvent(key)) { + mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event, true)); + } + } + + return new class implements IServerChannel { + + listen(_: unknown, event: string): Event { + const eventImpl = mapEventNameToEvent.get(event); + if (eventImpl) { + return eventImpl as Event; + } + + throw new Error(`Event not found: ${event}`); + } + + call(_: unknown, command: string, args?: any[]): Promise { + const target = handler[command]; + if (typeof target === 'function') { + + // Revive unless marshalling disabled + if (!disableMarshalling && Array.isArray(args)) { + for (let i = 0; i < args.length; i++) { + args[i] = revive(args[i]); + } + } + + return target.apply(handler, args); + } + + throw new Error(`Method not found: ${command}`); + } + }; +} + +export interface IChannelSenderOptions extends IBaseChannelOptions { + + /** + * If provided, will add the value of `context` + * to each method call to the target. + */ + context?: unknown; +} + +export function createChannelSender(channel: IChannel, options?: IChannelSenderOptions): T { + const disableMarshalling = options && options.disableMarshalling; + + return new Proxy({}, { + get(_target, propKey, _receiver) { + if (typeof propKey === 'string') { + + // Event + if (propertyIsEvent(propKey)) { + return channel.listen(propKey); + } + + // Function + return async function (...args: any[]) { + + // Add context if any + let methodArgs: any[]; + if (options && !isUndefinedOrNull(options.context)) { + methodArgs = [options.context, ...args]; + } else { + methodArgs = args; + } + + const result = await channel.call(propKey, methodArgs); + + // Revive unless marshalling disabled + if (!disableMarshalling) { + return revive(result); + } + + return result; + }; + } + + throw new Error(`Property not found: ${String(propKey)}`); + } + }) as T; +} + +function propertyIsEvent(name: string): boolean { + // Assume a property is an event if it has a form of "onSomething" + return name[0] === 'o' && name[1] === 'n' && isUpperAsciiLetter(name.charCodeAt(2)); +} + +//#endregion diff --git a/src/vs/base/parts/ipc/node/ipcChannelCreator.ts b/src/vs/base/parts/ipc/node/ipcChannelCreator.ts deleted file mode 100644 index c25c6e42034..00000000000 --- a/src/vs/base/parts/ipc/node/ipcChannelCreator.ts +++ /dev/null @@ -1,124 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { revive } from 'vs/base/common/marshalling'; -import { isUndefinedOrNull } from 'vs/base/common/types'; -import { isUpperAsciiLetter } from 'vs/base/common/strings'; - -// -// Use both `createChannelReceiver` and `createChannelSender` -// for automated process <=> process communication over methods -// and events. -// - -export interface IBaseChannelOptions { - - /** - * Disables automatic marshalling of `URI`. - * If marshalling is disabled, `UriComponents` - * must be used instead. - */ - disableMarshalling?: boolean; -} - -export interface IChannelReceiverOptions extends IBaseChannelOptions { } - -export function createChannelReceiver(service: unknown, options?: IChannelReceiverOptions): IServerChannel { - const handler = service as { [key: string]: unknown }; - const disableMarshalling = options && options.disableMarshalling; - - // Buffer any event that should be supported by - // iterating over all property keys and finding them - const mapEventNameToEvent = new Map>(); - for (const key in handler) { - if (propertyIsEvent(key)) { - mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event, true)); - } - } - - return new class implements IServerChannel { - - listen(_: unknown, event: string): Event { - const eventImpl = mapEventNameToEvent.get(event); - if (eventImpl) { - return eventImpl as Event; - } - - throw new Error(`Event not found: ${event}`); - } - - call(_: unknown, command: string, args?: any[]): Promise { - const target = handler[command]; - if (typeof target === 'function') { - - // Revive unless marshalling disabled - if (!disableMarshalling && Array.isArray(args)) { - for (let i = 0; i < args.length; i++) { - args[i] = revive(args[i]); - } - } - - return target.apply(handler, args); - } - - throw new Error(`Method not found: ${command}`); - } - }; -} - -export interface IChannelSenderOptions extends IBaseChannelOptions { - - /** - * If provided, will add the value of `context` - * to each method call to the target. - */ - context?: unknown; -} - -export function createChannelSender(channel: IChannel, options?: IChannelSenderOptions): T { - const disableMarshalling = options && options.disableMarshalling; - - return new Proxy({}, { - get(_target, propKey, _receiver) { - if (typeof propKey === 'string') { - - // Event - if (propertyIsEvent(propKey)) { - return channel.listen(propKey); - } - - // Function - return async function (...args: any[]) { - - // Add context if any - let methodArgs: any[]; - if (options && !isUndefinedOrNull(options.context)) { - methodArgs = [options.context, ...args]; - } else { - methodArgs = args; - } - - const result = await channel.call(propKey, methodArgs); - - // Revive unless marshalling disabled - if (!disableMarshalling) { - return revive(result); - } - - return result; - }; - } - - throw new Error(`Property not found: ${String(propKey)}`); - } - }) as T; -} - -function propertyIsEvent(name: string): boolean { - // Assume a property is an event if it has a form of "onSomething" - return name[0] === 'o' && name[1] === 'n' && isUpperAsciiLetter(name.charCodeAt(2)); -} diff --git a/src/vs/base/parts/ipc/test/node/ipc.test.ts b/src/vs/base/parts/ipc/test/node/ipc.test.ts index eff117e0ae7..1b2ccd1beac 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.test.ts @@ -4,13 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IChannel, IServerChannel, IMessagePassingProtocol, IPCServer, ClientConnectionEvent, IPCClient } from 'vs/base/parts/ipc/common/ipc'; +import { IChannel, IServerChannel, IMessagePassingProtocol, IPCServer, ClientConnectionEvent, IPCClient, createChannelSender, createChannelReceiver } from 'vs/base/parts/ipc/common/ipc'; import { Emitter, Event } from 'vs/base/common/event'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; import { timeout } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; -import { createChannelSender, createChannelReceiver } from 'vs/base/parts/ipc/node/ipcChannelCreator'; import { URI } from 'vs/base/common/uri'; import { isEqual } from 'vs/base/common/resources'; diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 604280d61c1..a85ec3a9c59 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -15,7 +15,7 @@ import * as os from 'os'; import { debounce } from 'vs/base/common/decorators'; import * as platform from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; -import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; +import { getDelayedChannel, createChannelSender } from 'vs/base/parts/ipc/common/ipc'; import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; @@ -40,7 +40,6 @@ import { withUndefinedAsNull } from 'vs/base/common/types'; import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { createChannelSender } from 'vs/base/parts/ipc/node/ipcChannelCreator'; const MAX_URL_LENGTH = 2045; diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 5fd0fd2efc6..d369fbe8956 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -36,7 +36,7 @@ import { LocalizationsChannel } from 'vs/platform/localizations/node/localizatio import { combinedDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { DownloadService } from 'vs/platform/download/common/downloadService'; import { IDownloadService } from 'vs/platform/download/common/download'; -import { IChannel, IServerChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; +import { IChannel, IServerChannel, StaticRouter, createChannelSender } from 'vs/base/parts/ipc/common/ipc'; import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner'; import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner'; import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner'; @@ -55,7 +55,6 @@ import { UserDataSyncService, UserDataAutoSync } from 'vs/platform/userDataSync/ import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { SettingsMergeChannelClient } from 'vs/platform/userDataSync/common/settingsSyncIpc'; -import { createChannelSender } from 'vs/base/parts/ipc/node/ipcChannelCreator'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { LoggerService } from 'vs/platform/log/node/loggerService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 79c17053429..a9e4a22f7c7 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -31,8 +31,7 @@ import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; -import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; -import { createChannelReceiver } from 'vs/base/parts/ipc/node/ipcChannelCreator'; +import { getDelayedChannel, StaticRouter, createChannelReceiver } from 'vs/base/parts/ipc/common/ipc'; import product from 'vs/platform/product/common/product'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; import { Disposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/platform/issue/electron-browser/issueService.ts b/src/vs/platform/issue/electron-browser/issueService.ts index 2126bae573a..8e54cae7888 100644 --- a/src/vs/platform/issue/electron-browser/issueService.ts +++ b/src/vs/platform/issue/electron-browser/issueService.ts @@ -5,7 +5,7 @@ import { IIssueService } from 'vs/platform/issue/node/issue'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; -import { createChannelSender } from 'vs/base/parts/ipc/node/ipcChannelCreator'; +import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; export class IssueService { diff --git a/src/vs/platform/menubar/electron-browser/menubarService.ts b/src/vs/platform/menubar/electron-browser/menubarService.ts index 4c3030b4a49..16aab5d5334 100644 --- a/src/vs/platform/menubar/electron-browser/menubarService.ts +++ b/src/vs/platform/menubar/electron-browser/menubarService.ts @@ -5,7 +5,7 @@ import { IMenubarService } from 'vs/platform/menubar/node/menubar'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; -import { createChannelSender } from 'vs/base/parts/ipc/node/ipcChannelCreator'; +import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; export class MenubarService { diff --git a/src/vs/workbench/services/electron/electron-browser/electronService.ts b/src/vs/workbench/services/electron/electron-browser/electronService.ts index 3f7b30464fa..d3e7e2e633e 100644 --- a/src/vs/workbench/services/electron/electron-browser/electronService.ts +++ b/src/vs/workbench/services/electron/electron-browser/electronService.ts @@ -5,9 +5,9 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; -import { createChannelSender } from 'vs/base/parts/ipc/node/ipcChannelCreator'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; export class ElectronService { diff --git a/src/vs/workbench/services/url/electron-browser/urlService.ts b/src/vs/workbench/services/url/electron-browser/urlService.ts index add50be5bba..b37f908d1b2 100644 --- a/src/vs/workbench/services/url/electron-browser/urlService.ts +++ b/src/vs/workbench/services/url/electron-browser/urlService.ts @@ -12,7 +12,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import product from 'vs/platform/product/common/product'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; -import { createChannelSender } from 'vs/base/parts/ipc/node/ipcChannelCreator'; +import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; export class RelayURLService extends URLService implements IURLHandler { diff --git a/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts b/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts index 61fc335fb36..8d471cf1ea5 100644 --- a/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts @@ -7,7 +7,7 @@ import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; -import { createChannelSender } from 'vs/base/parts/ipc/node/ipcChannelCreator'; +import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; export class NativeWorkspacesService {