From 5682df50973c0049b3d206cc46f0ecdeffcb25a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=A4der?= Date: Mon, 23 Sep 2024 14:19:18 +0200 Subject: [PATCH 1/3] Wrap api objects returned to clients in a proxy and bind member functions to the object. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #13522 Contributed on behalf of STMicroelectronics Signed-off-by: Thomas Mäder --- .../src/plugin/file-system-ext-impl.ts | 10 ++-- .../plugin-ext/src/plugin/plugin-context.ts | 48 +++++++++++++------ packages/plugin-ext/src/plugin/scm.ts | 9 ++-- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/packages/plugin-ext/src/plugin/file-system-ext-impl.ts b/packages/plugin-ext/src/plugin/file-system-ext-impl.ts index 9eb59c06cc12c..b54be1a8dc093 100644 --- a/packages/plugin-ext/src/plugin/file-system-ext-impl.ts +++ b/packages/plugin-ext/src/plugin/file-system-ext-impl.ts @@ -40,8 +40,9 @@ import { State, StateMachine, LinkComputer, Edge } from '../common/link-computer import { commonPrefixLength } from '@theia/core/lib/common/strings'; import { CharCode } from '@theia/core/lib/common/char-code'; import { BinaryBuffer } from '@theia/core/lib/common/buffer'; -import { Emitter } from '@theia/core/shared/vscode-languageserver-protocol'; import { MarkdownString } from '../common/plugin-api-rpc-model'; +import { Emitter } from '@theia/core/lib/common'; +import { createAPIObject } from './plugin-context'; type IDisposable = vscode.Disposable; @@ -137,8 +138,11 @@ export class FsLinkProvider { } class ConsumerFileSystem implements vscode.FileSystem { + apiObject: vscode.FileSystem; - constructor(private _proxy: FileSystemMain, private _capabilities: Map) { } + constructor(private _proxy: FileSystemMain, private _capabilities: Map) { + this.apiObject = createAPIObject(this); + } stat(uri: vscode.Uri): Promise { return this._proxy.$stat(uri).catch(ConsumerFileSystem._handleError); @@ -210,7 +214,7 @@ export class FileSystemExtImpl implements FileSystemExt { private _handlePool: number = 0; - readonly fileSystem: vscode.FileSystem; + readonly fileSystem: ConsumerFileSystem; constructor(rpc: RPCProtocol) { this._proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.FILE_SYSTEM_MAIN); diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 8a36492244edc..f2faed2ab8eb1 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -278,6 +278,21 @@ import { NotebookEditorsExtImpl } from './notebook/notebook-editors'; import { TestingExtImpl } from './tests'; import { UriExtImpl } from './uri-ext'; +export function createAPIObject(rawObject: T): T { + return new Proxy({}, { + get(target, p, receiver) { + const isOwnProperty = !!Object.getOwnPropertyDescriptor(rawObject, p); + const val = Reflect.get(rawObject, p); + if (!isOwnProperty && typeof val === 'function') { + // bind functions that are inherited from the prototype to the object itself. + // This should handle the case of events. + return val.bind(rawObject); + } + return val; + }, + }) as T; +} + export function createAPIFactory( rpc: RPCProtocol, pluginManager: PluginManager, @@ -492,7 +507,8 @@ export function createAPIFactory( return quickOpenExt.showQuickPick(plugin, items, options, token); }, createQuickPick(): theia.QuickPick { - return quickOpenExt.createQuickPick(plugin); + + return createAPIObject(quickOpenExt.createQuickPick(plugin)); }, showWorkspaceFolderPick(options?: theia.WorkspaceFolderPickOptions): PromiseLike { return workspaceExt.pickWorkspaceFolder(options); @@ -531,9 +547,12 @@ export function createAPIFactory( priority = priorityOrAlignment; } + // TODO: here return statusBarMessageRegistryExt.createStatusBarItem(alignment, priority, id); }, createOutputChannel(name: string, options?: { log: true }): any { + + // TODO: here return !options ? outputChannelRegistryExt.createOutputChannel(name, pluginToPluginInfo(plugin)) : outputChannelRegistryExt.createOutputChannel(name, pluginToPluginInfo(plugin), options); @@ -542,7 +561,7 @@ export function createAPIFactory( title: string, showOptions: theia.ViewColumn | theia.WebviewPanelShowOptions, options: theia.WebviewPanelOptions & theia.WebviewOptions = {}): theia.WebviewPanel { - return webviewExt.createWebview(viewType, title, showOptions, options, plugin); + return createAPIObject(webviewExt.createWebview(viewType, title, showOptions, options, plugin)); }, registerWebviewPanelSerializer(viewType: string, serializer: theia.WebviewPanelSerializer): theia.Disposable { return webviewExt.registerWebviewPanelSerializer(viewType, serializer, plugin); @@ -570,19 +589,19 @@ export function createAPIFactory( createTerminal(nameOrOptions: theia.TerminalOptions | theia.ExtensionTerminalOptions | theia.ExtensionTerminalOptions | (string | undefined), shellPath?: string, shellArgs?: string[] | string): theia.Terminal { - return terminalExt.createTerminal(plugin, nameOrOptions, shellPath, shellArgs); + return createAPIObject(terminalExt.createTerminal(plugin, nameOrOptions, shellPath, shellArgs)); }, onDidChangeTerminalState, onDidCloseTerminal, onDidOpenTerminal, createTextEditorDecorationType(options: theia.DecorationRenderOptions): theia.TextEditorDecorationType { - return editors.createTextEditorDecorationType(options); + return createAPIObject(editors.createTextEditorDecorationType(options)); }, registerTreeDataProvider(viewId: string, treeDataProvider: theia.TreeDataProvider): Disposable { return treeViewsExt.registerTreeDataProvider(plugin, viewId, treeDataProvider); }, createTreeView(viewId: string, options: theia.TreeViewOptions): theia.TreeView { - return treeViewsExt.createTreeView(plugin, viewId, options); + return createAPIObject(treeViewsExt.createTreeView(plugin, viewId, options)); }, withScmProgress(task: (progress: theia.Progress) => Thenable) { const options: ProgressOptions = { location: ProgressLocation.SourceControl }; @@ -601,7 +620,7 @@ export function createAPIFactory( return uriExt.registerUriHandler(handler, pluginToPluginInfo(plugin)); }, createInputBox(): theia.InputBox { - return quickOpenExt.createInputBox(plugin); + return createAPIObject(quickOpenExt.createInputBox(plugin)); }, registerTerminalLinkProvider(provider: theia.TerminalLinkProvider): theia.Disposable { return terminalExt.registerTerminalLinkProvider(provider); @@ -649,7 +668,7 @@ export function createAPIFactory( const workspace: typeof theia.workspace = { get fs(): theia.FileSystem { - return fileSystemExt.fileSystem; + return fileSystemExt.fileSystem.apiObject; }, get rootPath(): string | undefined { @@ -752,7 +771,7 @@ export function createAPIFactory( return notebooksExt.getNotebookDocument(uri).apiNotebook; }, createFileSystemWatcher: (pattern, ignoreCreate, ignoreChange, ignoreDelete): theia.FileSystemWatcher => - extHostFileSystemEvent.createFileSystemWatcher(fromGlobPattern(pattern), ignoreCreate, ignoreChange, ignoreDelete), + createAPIObject(extHostFileSystemEvent.createFileSystemWatcher(fromGlobPattern(pattern), ignoreCreate, ignoreChange, ignoreDelete)), findFiles(include: theia.GlobPattern, exclude?: theia.GlobPattern | null, maxResults?: number, token?: CancellationToken): PromiseLike { return workspaceExt.findFiles(include, exclude, maxResults, token); }, @@ -845,7 +864,7 @@ export function createAPIFactory( return telemetryExt.onDidChangeTelemetryEnabled; }, createTelemetryLogger(sender: theia.TelemetrySender, options?: theia.TelemetryLoggerOptions): theia.TelemetryLogger { - return telemetryExt.createTelemetryLogger(sender, options); + return createAPIObject(telemetryExt.createTelemetryLogger(sender, options)); }, get remoteName(): string | undefined { return envExt.remoteName; }, get machineId(): string { return envExt.machineId; }, @@ -920,7 +939,7 @@ export function createAPIFactory( return languagesExt.getDiagnostics(resource); }, createDiagnosticCollection(name?: string): theia.DiagnosticCollection { - return languagesExt.createDiagnosticCollection(name); + return createAPIObject(languagesExt.createDiagnosticCollection(name)); }, setLanguageConfiguration(language: string, configuration: theia.LanguageConfiguration): theia.Disposable { return languagesExt.setLanguageConfiguration(language, configuration); @@ -1057,7 +1076,7 @@ export function createAPIFactory( const tests: typeof theia.tests = { createTestController(id, label: string) { - return testingExt.createTestController(id, label); + return createAPIObject(testingExt.createTestController(id, label)); } }; /* End of Tests API */ @@ -1169,6 +1188,7 @@ export function createAPIFactory( }, get taskExecutions(): ReadonlyArray { + // TODO: here return tasksExt.taskExecutions; }, onDidStartTask(listener, thisArg?, disposables?) { @@ -1189,19 +1209,19 @@ export function createAPIFactory( get inputBox(): theia.SourceControlInputBox { const inputBox = scmExt.getLastInputBox(plugin); if (inputBox) { - return inputBox; + return inputBox.apiObject; } else { throw new Error('Input box not found!'); } }, createSourceControl(id: string, label: string, rootUri?: URI): theia.SourceControl { - return scmExt.createSourceControl(plugin, id, label, rootUri); + return createAPIObject(scmExt.createSourceControl(plugin, id, label, rootUri)); } }; const comments: typeof theia.comments = { createCommentController(id: string, label: string): theia.CommentController { - return commentsExt.createCommentController(plugin, id, label); + return createAPIObject(commentsExt.createCommentController(plugin, id, label)); } }; diff --git a/packages/plugin-ext/src/plugin/scm.ts b/packages/plugin-ext/src/plugin/scm.ts index 4703ab3626f14..f34556cee2ae8 100644 --- a/packages/plugin-ext/src/plugin/scm.ts +++ b/packages/plugin-ext/src/plugin/scm.ts @@ -39,6 +39,7 @@ import { URI, ThemeIcon } from './types-impl'; import { ScmCommandArg } from '../common/plugin-api-rpc'; import { sep } from '@theia/core/lib/common/paths'; import { PluginIconPath } from './plugin-icon-path'; +import { createAPIObject } from './plugin-context'; type ProviderHandle = number; type GroupHandle = number; type ResourceStateHandle = number; @@ -290,6 +291,7 @@ interface ValidateInput { export class ScmInputBoxImpl implements theia.SourceControlInputBox { private _value: string = ''; + apiObject: theia.SourceControlInputBox; get value(): string { return this._value; @@ -354,7 +356,7 @@ export class ScmInputBoxImpl implements theia.SourceControlInputBox { } constructor(private plugin: Plugin, private proxy: ScmMain, private sourceControlHandle: number) { - // noop + this.apiObject = createAPIObject(this); } onInputBoxValueChange(value: string): void { @@ -543,8 +545,7 @@ class SourceControlImpl implements theia.SourceControl { return this._rootUri; } - private _inputBox: ScmInputBoxImpl; - get inputBox(): ScmInputBoxImpl { return this._inputBox; } + readonly inputBox: ScmInputBoxImpl; private _count: number | undefined = undefined; @@ -642,7 +643,7 @@ class SourceControlImpl implements theia.SourceControl { private _label: string, private _rootUri?: theia.Uri ) { - this._inputBox = new ScmInputBoxImpl(plugin, this.proxy, this.handle); + this.inputBox = new ScmInputBoxImpl(plugin, this.proxy, this.handle); this.proxy.$registerSourceControl(this.handle, _id, _label, _rootUri); } From 344197a0dc021cad36c8737172748c97d23190ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=A4der?= Date: Mon, 23 Sep 2024 15:03:13 +0200 Subject: [PATCH 2/3] Fix linter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Mäder --- packages/plugin-ext/src/plugin/plugin-context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index f2faed2ab8eb1..8ef61dc347232 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -284,7 +284,7 @@ export function createAPIObject(rawObject: T): T { const isOwnProperty = !!Object.getOwnPropertyDescriptor(rawObject, p); const val = Reflect.get(rawObject, p); if (!isOwnProperty && typeof val === 'function') { - // bind functions that are inherited from the prototype to the object itself. + // bind functions that are inherited from the prototype to the object itself. // This should handle the case of events. return val.bind(rawObject); } From 0031b3d5fa04ba99ac9e5f81bb2e42fe19cd3ebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=A4der?= Date: Wed, 2 Oct 2024 14:42:56 +0200 Subject: [PATCH 3/3] Use wrapped object as proxy target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Mäder --- packages/plugin-ext/src/plugin/plugin-context.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 8ef61dc347232..3029f268f6921 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -279,14 +279,14 @@ import { TestingExtImpl } from './tests'; import { UriExtImpl } from './uri-ext'; export function createAPIObject(rawObject: T): T { - return new Proxy({}, { + return new Proxy(rawObject, { get(target, p, receiver) { - const isOwnProperty = !!Object.getOwnPropertyDescriptor(rawObject, p); - const val = Reflect.get(rawObject, p); + const isOwnProperty = !!Object.getOwnPropertyDescriptor(target, p); + const val = Reflect.get(target, p); if (!isOwnProperty && typeof val === 'function') { // bind functions that are inherited from the prototype to the object itself. // This should handle the case of events. - return val.bind(rawObject); + return val.bind(target); } return val; },