Skip to content

Commit 2eb234b

Browse files
authored
feat: support register external ext api (#3933)
* refactor: replace AppConfig with ExtHostAppConfig * feat: support register external api to exthost * refactor: abstract external extension API * feat: support reguster main thread extender * fix: fix lint * fix: missing ExtHostAppConfig for test
1 parent b13dc21 commit 2eb234b

16 files changed

Lines changed: 209 additions & 78 deletions

packages/extension/__tests__/browser/main.thread.extensions.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { MainThreadWebview } from '../../src/browser/vscode/api/main.thread.api.
2929
import { MainThreadExtensionLog } from '../../src/browser/vscode/api/main.thread.log';
3030
import { MainThreadStorage } from '../../src/browser/vscode/api/main.thread.storage';
3131
import { ExtensionNodeServiceServerPath } from '../../src/common';
32+
import { ExtHostAppConfig } from '../../src/common/ext.process';
3233
import { MainThreadExtensionLogIdentifier } from '../../src/common/extension-log';
3334
import { ExtHostAPIIdentifier, MainThreadAPIIdentifier } from '../../src/common/vscode';
3435
import * as types from '../../src/common/vscode/ext-types';
@@ -69,6 +70,10 @@ describe('MainThreadExtensions Test Suites', () => {
6970
token: IReporter,
7071
useClass: DefaultReporter,
7172
},
73+
{
74+
token: ExtHostAppConfig,
75+
useValue: {},
76+
},
7277
);
7378
const injector = createBrowserInjector(
7479
[],

packages/extension/__tests__/hosted/api/vscode/ext.host.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { MainThreadExtensionService } from '../../../../__mocks__/api/mainthread
1010
import { MainThreadStorage } from '../../../../__mocks__/api/mathread.storage';
1111
import { mockExtensionProps, mockExtensionProps2 } from '../../../../__mocks__/extensions';
1212
import { createMockPairRPCProtocol } from '../../../../__mocks__/initRPCProtocol';
13+
import { ExtHostAppConfig } from '../../../../src/common/ext.process';
1314
import { ExtHostAPIIdentifier, IExtHostLocalization } from '../../../../src/common/vscode';
1415
import ExtensionHostServiceImpl from '../../../../src/hosted/ext.host';
1516

@@ -23,7 +24,7 @@ describe('Extension process test', () => {
2324
injector = createBrowserInjector([]);
2425
injector.addProviders(
2526
{
26-
token: AppConfig,
27+
token: ExtHostAppConfig,
2728
useValue: {
2829
builtinCommands: [
2930
{

packages/extension/src/browser/extension-node.service.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { WSChannelHandler } from '@opensumi/ide-connection/lib/browser';
44
import { BaseConnection } from '@opensumi/ide-connection/lib/common/connection';
55
import {
66
AppConfig,
7+
ContributionProvider,
78
Deferred,
9+
Disposable,
810
DisposableStore,
911
IApplicationService,
1012
IExtensionProps,
@@ -21,6 +23,7 @@ import {
2123
} from '../common';
2224
import { ActivatedExtensionJSON } from '../common/activator';
2325
import { AbstractNodeExtProcessService } from '../common/extension.service';
26+
import { IMainThreadExtenderService, MainThreadExtenderContribution } from '../common/main.thread.extender';
2427
import { ExtHostAPIIdentifier } from '../common/vscode';
2528
import { knownProtocols } from '../common/vscode/protocols';
2629

@@ -37,7 +40,7 @@ export class NodeExtProcessService implements AbstractNodeExtProcessService<IExt
3740
private readonly appConfig: AppConfig;
3841

3942
@Autowired(INJECTOR_TOKEN)
40-
private readonly injector: Injector;
43+
protected readonly injector: Injector;
4144

4245
@Autowired(ExtensionNodeServiceServerPath)
4346
private readonly extensionNodeClient: IExtensionNodeClientService;
@@ -48,6 +51,12 @@ export class NodeExtProcessService implements AbstractNodeExtProcessService<IExt
4851
@Autowired(WSChannelHandler)
4952
private readonly channelHandler: WSChannelHandler;
5053

54+
@Autowired(IMainThreadExtenderService)
55+
private readonly mainThreadExtenderService: IMainThreadExtenderService;
56+
57+
@Autowired(MainThreadExtenderContribution)
58+
private readonly mainThreadExtenderContributionProvider: ContributionProvider<MainThreadExtenderContribution>;
59+
5160
private _apiFactoryDisposables = new DisposableStore();
5261

5362
public ready: Deferred<void> = new Deferred();
@@ -125,9 +134,31 @@ export class NodeExtProcessService implements AbstractNodeExtProcessService<IExt
125134
this._apiFactoryDisposables.add(apiProxy);
126135
this._apiFactoryDisposables.add(toDisposable(initNodeThreadAPIProxy(this.protocol, this.injector, this)));
127136
this._apiFactoryDisposables.add(toDisposable(createSumiAPIFactory(this.protocol, this.injector)));
137+
this._apiFactoryDisposables.add(this.createExternalSumiAPIFactory());
128138
await apiProxy.setup();
129139
}
130140

141+
/**
142+
* register external main thread class
143+
* @returns disposer
144+
*/
145+
private createExternalSumiAPIFactory() {
146+
const disposer = new Disposable();
147+
// register main thread extender contribution
148+
const contributions = this.mainThreadExtenderContributionProvider.getContributions();
149+
for (const contribution of contributions) {
150+
contribution.registerMainThreadExtender(this.mainThreadExtenderService);
151+
}
152+
// instantiate this MainThreadExtenderClass
153+
const extenders = this.mainThreadExtenderService.getMainThreadExtenders();
154+
for (const extender of extenders) {
155+
const service = this.injector.get(extender.serviceClass, [this.protocol]);
156+
this.protocol.set(extender.identifier, service);
157+
disposer.addDispose(service);
158+
}
159+
return disposer;
160+
}
161+
131162
public async getActivatedExtensions(): Promise<ActivatedExtensionJSON[]> {
132163
const proxy = await this.getProxy();
133164
return await proxy.$getActivatedExtensions();

packages/extension/src/browser/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ import {
1919
AbstractViewExtProcessService,
2020
AbstractWorkerExtProcessService,
2121
} from '../common/extension.service';
22+
import {
23+
IMainThreadExtenderService,
24+
MainThreadExtenderContribution,
25+
MainThreadExtenderService,
26+
} from '../common/main.thread.extender';
2227

2328
import { ActivationEventServiceImpl } from './activation.service';
2429
import { ExtCommandManagementImpl as ExtCommandManagementImpl } from './extension-command-management';
@@ -37,7 +42,7 @@ import { VSCodeContributesService, VSCodeContributesServiceToken } from './vscod
3742

3843
@Injectable()
3944
export class ExtensionModule extends BrowserModule {
40-
contributionProvider = [RequireInterceptorContribution];
45+
contributionProvider = [RequireInterceptorContribution, MainThreadExtenderContribution];
4146
providers: Provider[] = [
4247
{
4348
token: ExtensionService,
@@ -93,6 +98,10 @@ export class ExtensionModule extends BrowserModule {
9398
token: SumiContributionsServiceToken,
9499
useClass: SumiContributionsService,
95100
},
101+
{
102+
token: IMainThreadExtenderService,
103+
useClass: MainThreadExtenderService,
104+
},
96105
ExtensionCommandContribution,
97106
ExtensionClientAppContribution,
98107
BrowserRequireInterceptorContribution,
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { SumiApiExtenders } from './sumi';
2+
import type { CommandHandler } from './vscode';
3+
import type { ConstructorOf, Injector } from '@opensumi/di';
4+
import type { ILogService, LogLevel } from '@opensumi/ide-core-common';
5+
import type Stream from 'stream';
6+
7+
export interface IBuiltInCommand {
8+
id: string;
9+
handler: CommandHandler;
10+
}
11+
12+
export interface CustomChildProcess {
13+
stdin: Stream.Writable;
14+
stdout: Stream.Readable;
15+
kill: () => void;
16+
}
17+
18+
export interface CustomChildProcessModule {
19+
spawn(command: string, args: string | string[], options: any): CustomChildProcess;
20+
}
21+
22+
export const ExtHostAppConfig = Symbol('ExtHostAppConfig');
23+
export interface ExtHostAppConfig {
24+
/**
25+
* exthost log service class
26+
*/
27+
LogServiceClass?: ConstructorOf<ILogService>;
28+
/**
29+
* 插件日志自定义实现路径
30+
*/
31+
extLogServiceClassPath?: string;
32+
logDir?: string;
33+
logLevel?: LogLevel;
34+
builtinCommands?: IBuiltInCommand[];
35+
customDebugChildProcess?: CustomChildProcessModule;
36+
/**
37+
* 集成方自定义 vscode.version 版本
38+
* 设置该参数可能会导致插件运行异常
39+
* @type {string} 插件版本号
40+
* @memberof ExtHostAppConfig
41+
*/
42+
customVSCodeEngineVersion?: string;
43+
/**
44+
* control rpcProtocol message timeout
45+
* default -1,it means disable
46+
*/
47+
rpcMessageTimeout?: number;
48+
/**
49+
* register external sumi extension api
50+
*/
51+
sumiApiExtenders?: SumiApiExtenders;
52+
}
53+
54+
export interface ExtProcessConfig extends ExtHostAppConfig {
55+
injector?: Injector;
56+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Injectable } from '@opensumi/di';
2+
import { IRPCProtocol, ProxyIdentifier } from '@opensumi/ide-connection';
3+
import { Disposable, IDisposable } from '@opensumi/ide-core-common';
4+
5+
export interface IMainThreadExtender<T = any> {
6+
identifier: ProxyIdentifier<T>;
7+
serviceClass: new (rpcProtocol: IRPCProtocol) => T;
8+
}
9+
10+
export const IMainThreadExtenderService = Symbol('IMainThreadExtenderService');
11+
export interface IMainThreadExtenderService<T = any> {
12+
registerMainThreadExtender(extender: IMainThreadExtender<T>): IDisposable;
13+
getMainThreadExtenders(): IMainThreadExtender<T>[];
14+
}
15+
16+
export const MainThreadExtenderContribution = Symbol('MainThreadExtenderContribution');
17+
18+
export interface MainThreadExtenderContribution {
19+
registerMainThreadExtender(registry: IMainThreadExtenderService): void;
20+
}
21+
22+
@Injectable()
23+
export class MainThreadExtenderService<T> implements IMainThreadExtenderService<T> {
24+
private readonly registry: IMainThreadExtender<T>[] = [];
25+
26+
registerMainThreadExtender(extender: IMainThreadExtender) {
27+
this.registry.push(extender);
28+
return Disposable.create(() => {
29+
const index = this.registry.indexOf(extender);
30+
if (index !== -1) {
31+
this.registry.splice(index, 1);
32+
}
33+
});
34+
}
35+
36+
getMainThreadExtenders() {
37+
return this.registry;
38+
}
39+
}

packages/extension/src/common/sumi/index.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { createExtHostContextProxyIdentifier, createMainContextProxyIdentifier } from '@opensumi/ide-connection';
1+
import {
2+
IRPCProtocol,
3+
ProxyIdentifier,
4+
createExtHostContextProxyIdentifier,
5+
createMainContextProxyIdentifier,
6+
} from '@opensumi/ide-connection';
27

38
import { IExtHostChatAgents, IMainThreadChatAgents } from './chat-agents';
49
import { IExtHostCommon, IMainThreadCommon } from './common';
@@ -27,3 +32,23 @@ export const ExtHostSumiAPIIdentifier = {
2732
ExtHostIDEWindow: createExtHostContextProxyIdentifier<IExtHostIDEWindow>('ExtHostIDEWindow'),
2833
ExtHostChatAgents: createExtHostContextProxyIdentifier<IExtHostChatAgents>('ExtHostChatAgents'),
2934
};
35+
36+
/**
37+
* sumi API extender
38+
*/
39+
export abstract class SumiApiExtender<T = any> {
40+
constructor(protected rpcProtocol: IRPCProtocol) {}
41+
/**
42+
* create rpc service when main thread could call
43+
*/
44+
createRPCService?: () => [identifier: ProxyIdentifier<T>, service: T];
45+
/**
46+
* api factory
47+
* can use rpc service to call main thread
48+
*/
49+
createApiFactory: (service?: T) => any;
50+
}
51+
52+
export interface SumiApiExtenders extends Record<string, any> {
53+
[api: string]: new (rpcProtocol: IRPCProtocol) => SumiApiExtender;
54+
}

packages/extension/src/hosted/api/sumi/ext.host.api.impl.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { IRPCProtocol } from '@opensumi/ide-connection';
2-
import { IReporter, REPORT_HOST, ReporterService } from '@opensumi/ide-core-common';
2+
import { IReporter, REPORT_HOST, ReporterService, isFunction } from '@opensumi/ide-core-common';
33

44
import { IExtensionHostService, IExtensionWorkerHost, WorkerHostAPIIdentifier } from '../../../common';
5-
import { ExtHostSumiAPIIdentifier } from '../../../common/sumi';
5+
import { ExtHostSumiAPIIdentifier, SumiApiExtenders } from '../../../common/sumi';
66
import { ExtHostAPIIdentifier, IExtensionDescription } from '../../../common/vscode';
77
import { ExtensionHostEditorService } from '../vscode/editor/editor.host';
88

@@ -21,6 +21,7 @@ export function createAPIFactory(
2121
extensionService: IExtensionHostService | IExtensionWorkerHost,
2222
type: string,
2323
reporterEmitter: IReporter,
24+
sumiApiExtenders: SumiApiExtenders = {},
2425
) {
2526
if (type === 'worker') {
2627
rpcProtocol.set(WorkerHostAPIIdentifier.ExtWorkerHostExtensionService, extensionService);
@@ -58,6 +59,20 @@ export function createAPIFactory(
5859
new ExtHostChatAgents(rpcProtocol),
5960
) as ExtHostChatAgents;
6061

62+
const externalSumiApis = Object.keys(sumiApiExtenders).reduce((acc, key) => {
63+
const SumiApiExtender = sumiApiExtenders[key];
64+
const extender = new SumiApiExtender(rpcProtocol);
65+
let rpcService;
66+
// register external api rpc protocol
67+
if (isFunction(extender.createRPCService)) {
68+
const [identifier, service] = extender.createRPCService();
69+
rpcService = service;
70+
rpcProtocol.set(identifier, service);
71+
}
72+
acc[key] = extender.createApiFactory(rpcService);
73+
return acc;
74+
}, {});
75+
6176
return (extension: IExtensionDescription) => {
6277
const reporter = new ReporterService(reporterEmitter, {
6378
extensionId: extension.extensionId,
@@ -75,6 +90,7 @@ export function createAPIFactory(
7590
commands: createCommandsApiFactory(extHostCommands, extHostEditors, extension),
7691
toolbar: createToolbarAPIFactory(extension, extHostToolbar),
7792
chat: createChatApiFactory(extension, extHostChatAgents),
93+
...externalSumiApis,
7894
};
7995
};
8096
}

packages/extension/src/hosted/api/vscode/debug/ext.host.debug.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
IDebuggerContribution,
88
} from '@opensumi/ide-debug';
99

10+
import { CustomChildProcessModule } from '../../../../common/ext.process';
1011
import {
1112
IExtHostCommands,
1213
IExtHostDebugService,
@@ -25,7 +26,6 @@ import {
2526
Uri,
2627
} from '../../../../common/vscode/ext-types';
2728
import { Breakpoint } from '../../../../common/vscode/models';
28-
import { CustomChildProcessModule } from '../../../ext.process-base';
2929

3030
import { IDebugConfigurationProvider } from './common';
3131
import { resolveDebugAdapterExecutable } from './extension-debug-adapter-excutable-resolver';

packages/extension/src/hosted/api/vscode/debug/extension-debug-adapter-starter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import net from 'net';
44

55
import { DebugAdapterForkExecutable, DebugStreamConnection } from '@opensumi/ide-debug';
66

7-
import { CustomChildProcess, CustomChildProcessModule } from '../../../ext.process-base';
7+
import { CustomChildProcess, CustomChildProcessModule } from '../../../../common/ext.process';
88

99
import { DirectDebugAdapter } from './abstract-debug-adapter-session';
1010

0 commit comments

Comments
 (0)