From 9bceead98322634767cae091d541721094ebe100 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 16 Nov 2022 14:05:27 +0100 Subject: [PATCH 1/4] feat: no ping timeout in dev mode Signed-off-by: Akos Kitta --- .vscode/launch.json | 6 ++++-- .../src/node/arduino-ide-backend-module.ts | 5 +++++ .../src/node/theia/core/messaging-contribution.ts | 11 +++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 arduino-ide-extension/src/node/theia/core/messaging-contribution.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 06f959e85..f892c3fc5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -21,7 +21,8 @@ "--plugins=local-dir:../plugins", "--hosted-plugin-inspect=9339", "--content-trace", - "--open-devtools" + "--open-devtools", + "--no-ping-timeout", ], "env": { "NODE_ENV": "development" @@ -56,7 +57,8 @@ "--remote-debugging-port=9222", "--no-app-auto-install", "--plugins=local-dir:../plugins", - "--hosted-plugin-inspect=9339" + "--hosted-plugin-inspect=9339", + "--no-ping-timeout", ], "env": { "NODE_ENV": "development" diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index 9d3ad3553..09294f96b 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -109,6 +109,8 @@ import { } from '../common/protocol/survey-service'; import { IsTempSketch } from './is-temp-sketch'; import { rebindNsfwFileSystemWatcher } from './theia/filesystem/nsfw-watcher/nsfw-bindings'; +import { MessagingContribution } from './theia/core/messaging-contribution'; +import { MessagingService } from '@theia/core/lib/node/messaging/messaging-service'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BackendApplication).toSelf().inSingletonScope(); @@ -379,6 +381,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { .inSingletonScope(); bind(IsTempSketch).toSelf().inSingletonScope(); + rebind(MessagingService.Identifier) + .to(MessagingContribution) + .inSingletonScope(); }); function bindChildLogger(bind: interfaces.Bind, name: string): void { diff --git a/arduino-ide-extension/src/node/theia/core/messaging-contribution.ts b/arduino-ide-extension/src/node/theia/core/messaging-contribution.ts new file mode 100644 index 000000000..7c03c7697 --- /dev/null +++ b/arduino-ide-extension/src/node/theia/core/messaging-contribution.ts @@ -0,0 +1,11 @@ +import { MessagingContribution as TheiaMessagingContribution } from '@theia/core/lib/node/messaging/messaging-contribution'; +import { injectable } from '@theia/core/shared/inversify'; +@injectable() +export class MessagingContribution extends TheiaMessagingContribution { + // https://github.com/eclipse-theia/theia/discussions/11543 + protected override checkAliveTimeout = process.argv.includes( + '--no-ping-timeout' + ) + ? 24 * 60 * 60 * 1_000 // one day + : 30_000; +} From de3da993edd6d4005442805935e4af6be3c8173f Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 16 Nov 2022 11:39:41 +0100 Subject: [PATCH 2/4] feat: Updated to `cortex-debug@1.5.1` Closes #246 Signed-off-by: Akos Kitta --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d9adf9b60..e0e268391 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.5.vsix", "vscode-builtin-json": "https://open-vsx.org/api/vscode/json/1.46.1/file/vscode.json-1.46.1.vsix", "vscode-builtin-json-language-features": "https://open-vsx.org/api/vscode/json-language-features/1.46.1/file/vscode.json-language-features-1.46.1.vsix", - "cortex-debug": "https://open-vsx.org/api/marus25/cortex-debug/0.3.10/file/marus25.cortex-debug-0.3.10.vsix", + "cortex-debug": "https://downloads.arduino.cc/marus25.cortex-debug/marus25.cortex-debug-1.5.1.vsix", "vscode-language-pack-bg": "https://open-vsx.org/api/MS-CEINTL/vscode-language-pack-bg/1.48.3/file/MS-CEINTL.vscode-language-pack-bg-1.48.3.vsix", "vscode-language-pack-cs": "https://open-vsx.org/api/MS-CEINTL/vscode-language-pack-cs/1.53.2/file/MS-CEINTL.vscode-language-pack-cs-1.53.2.vsix", "vscode-language-pack-de": "https://open-vsx.org/api/MS-CEINTL/vscode-language-pack-de/1.53.2/file/MS-CEINTL.vscode-language-pack-de-1.53.2.vsix", From c217bed6d36bba968c98f919510f06038610befa Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 23 Nov 2022 18:06:10 +0100 Subject: [PATCH 3/4] feat: patched the Theia debug functionality Patch for: - eclipse-theia/theia#11871 - eclipse-theia/theia#11879 - eclipse-theia/theia#11880 - eclipse-theia/theia#11885 - eclipse-theia/theia#11886 - eclipse-theia/theia#11916 Closes #1582 Signed-off-by: Akos Kitta --- arduino-ide-extension/package.json | 1 + .../browser/arduino-ide-frontend-module.ts | 46 +++- .../src/browser/style/index.css | 10 + .../src/browser/theia/debug/debug-action.tsx | 29 +++ .../theia/debug/debug-session-contribution.ts | 49 ++++ .../theia/debug/debug-session-manager.ts | 120 +++++++++ .../src/browser/theia/debug/debug-session.ts | 231 ++++++++++++++++++ .../browser/theia/debug/debug-stack-frame.ts | 32 +++ .../src/browser/theia/debug/debug-thread.ts | 22 ++ .../theia/debug/debug-toolbar-widget.tsx | 85 +++++++ .../browser/theia/plugin-ext/debug-main.ts | 66 +++++ .../browser/theia/plugin-ext/hosted-plugin.ts | 34 ++- .../plugin-debug-session-factory.ts | 37 +++ .../theia/plugin-ext/plugin-debug-session.ts | 62 +++++ .../plugin-ext/plugin-menu-command-adapter.ts | 73 ++++++ 15 files changed, 895 insertions(+), 2 deletions(-) create mode 100644 arduino-ide-extension/src/browser/theia/debug/debug-action.tsx create mode 100644 arduino-ide-extension/src/browser/theia/debug/debug-session-contribution.ts create mode 100644 arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts create mode 100644 arduino-ide-extension/src/browser/theia/debug/debug-session.ts create mode 100644 arduino-ide-extension/src/browser/theia/debug/debug-stack-frame.ts create mode 100644 arduino-ide-extension/src/browser/theia/debug/debug-thread.ts create mode 100644 arduino-ide-extension/src/browser/theia/debug/debug-toolbar-widget.tsx create mode 100644 arduino-ide-extension/src/browser/theia/plugin-ext/debug-main.ts create mode 100644 arduino-ide-extension/src/browser/theia/plugin-ext/plugin-debug-session-factory.ts create mode 100644 arduino-ide-extension/src/browser/theia/plugin-ext/plugin-debug-session.ts create mode 100644 arduino-ide-extension/src/browser/theia/plugin-ext/plugin-menu-command-adapter.ts diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 6822ec4cf..7d75f7217 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -60,6 +60,7 @@ "@types/react-virtualized": "^9.21.21", "@types/temp": "^0.8.34", "@types/which": "^1.3.1", + "@vscode/debugprotocol": "^1.51.0", "arduino-serial-plotter-webapp": "0.2.0", "async-mutex": "^0.3.0", "auth0-js": "^9.14.0", diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index ba13b1b11..22ea313f2 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -1,5 +1,5 @@ import '../../src/browser/style/index.css'; -import { ContainerModule } from '@theia/core/shared/inversify'; +import { Container, ContainerModule } from '@theia/core/shared/inversify'; import { WidgetFactory } from '@theia/core/lib/browser/widget-manager'; import { CommandContribution } from '@theia/core/lib/common/command'; import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contribution'; @@ -331,6 +331,18 @@ import { TypeHierarchyServiceProvider } from './theia/typehierarchy/type-hierarc import { TypeHierarchyServiceProvider as TheiaTypeHierarchyServiceProvider } from '@theia/typehierarchy/lib/browser/typehierarchy-service'; import { TypeHierarchyContribution } from './theia/typehierarchy/type-hierarchy-contribution'; import { TypeHierarchyContribution as TheiaTypeHierarchyContribution } from '@theia/typehierarchy/lib/browser/typehierarchy-contribution'; +import { DefaultDebugSessionFactory } from './theia/debug/debug-session-contribution'; +import { DebugSessionFactory } from '@theia/debug/lib/browser/debug-session-contribution'; +import { DebugToolbar } from './theia/debug/debug-toolbar-widget'; +import { DebugToolBar as TheiaDebugToolbar } from '@theia/debug/lib/browser/view/debug-toolbar-widget'; +import { PluginMenuCommandAdapter } from './theia/plugin-ext/plugin-menu-command-adapter'; +import { PluginMenuCommandAdapter as TheiaPluginMenuCommandAdapter } from '@theia/plugin-ext/lib/main/browser/menus/plugin-menu-command-adapter'; +import { DebugSessionManager } from './theia/debug/debug-session-manager'; +import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager'; +import { DebugWidget } from '@theia/debug/lib/browser/view/debug-widget'; +import { DebugViewModel } from '@theia/debug/lib/browser/view/debug-view-model'; +import { DebugSessionWidget } from '@theia/debug/lib/browser/view/debug-session-widget'; +import { DebugConfigurationWidget } from '@theia/debug/lib/browser/view/debug-configuration-widget'; export default new ContainerModule((bind, unbind, isBound, rebind) => { // Commands and toolbar items @@ -960,4 +972,36 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { ); bind(TypeHierarchyContribution).toSelf().inSingletonScope(); rebind(TheiaTypeHierarchyContribution).toService(TypeHierarchyContribution); + + // patched the debugger for `cortex-debug@1.5.1` + // https://github.com/eclipse-theia/theia/issues/11871 + // https://github.com/eclipse-theia/theia/issues/11879 + // https://github.com/eclipse-theia/theia/issues/11880 + // https://github.com/eclipse-theia/theia/issues/11885 + // https://github.com/eclipse-theia/theia/issues/11886 + // https://github.com/eclipse-theia/theia/issues/11916 + // based on: https://github.com/eclipse-theia/theia/compare/master...kittaakos:theia:%2311871 + bind(DefaultDebugSessionFactory).toSelf().inSingletonScope(); + rebind(DebugSessionFactory).toService(DefaultDebugSessionFactory); + bind(DebugSessionManager).toSelf().inSingletonScope(); + rebind(TheiaDebugSessionManager).toService(DebugSessionManager); + bind(DebugToolbar).toSelf().inSingletonScope(); + rebind(TheiaDebugToolbar).toService(DebugToolbar); + bind(PluginMenuCommandAdapter).toSelf().inSingletonScope(); + rebind(TheiaPluginMenuCommandAdapter).toService(PluginMenuCommandAdapter); + bind(WidgetFactory) + .toDynamicValue(({ container }) => ({ + id: DebugWidget.ID, + createWidget: () => { + const child = new Container({ defaultScope: 'Singleton' }); + child.parent = container; + child.bind(DebugViewModel).toSelf(); + child.bind(DebugToolbar).toSelf(); // patched toolbar + child.bind(DebugSessionWidget).toSelf(); + child.bind(DebugConfigurationWidget).toSelf(); + child.bind(DebugWidget).toSelf(); + return child.get(DebugWidget); + }, + })) + .inSingletonScope(); }); diff --git a/arduino-ide-extension/src/browser/style/index.css b/arduino-ide-extension/src/browser/style/index.css index 638403697..6aa967304 100644 --- a/arduino-ide-extension/src/browser/style/index.css +++ b/arduino-ide-extension/src/browser/style/index.css @@ -176,3 +176,13 @@ button.theia-button.message-box-dialog-button { outline: 1px dashed var(--theia-focusBorder); outline-offset: -2px; } + +.debug-toolbar .debug-action>div { + font-family: var(--theia-ui-font-family); + font-size: var(--theia-ui-font-size0); + display: flex; + align-items: center; + align-self: center; + justify-content: center; + min-height: inherit; +} diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-action.tsx b/arduino-ide-extension/src/browser/theia/debug/debug-action.tsx new file mode 100644 index 000000000..c0f691b49 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/debug/debug-action.tsx @@ -0,0 +1,29 @@ +import * as React from '@theia/core/shared/react'; +import { DebugAction as TheiaDebugAction } from '@theia/debug/lib/browser/view/debug-action'; +import { + codiconArray, + DISABLED_CLASS, +} from '@theia/core/lib/browser/widgets/widget'; + +// customized debug action to show the contributed command's label when there is no icon +export class DebugAction extends TheiaDebugAction { + override render(): React.ReactNode { + const { enabled, label, iconClass } = this.props; + const classNames = ['debug-action', ...codiconArray(iconClass, true)]; + if (enabled === false) { + classNames.push(DISABLED_CLASS); + } + return ( + + {!iconClass || + (iconClass.match(/plugin-icon-\d+/) &&
{label}
)} +
+ ); + } +} diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-session-contribution.ts b/arduino-ide-extension/src/browser/theia/debug/debug-session-contribution.ts new file mode 100644 index 000000000..d0ba503ef --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/debug/debug-session-contribution.ts @@ -0,0 +1,49 @@ +import { injectable } from '@theia/core/shared/inversify'; +import { DebugSessionConnection } from '@theia/debug/lib/browser/debug-session-connection'; +import { DefaultDebugSessionFactory as TheiaDefaultDebugSessionFactory } from '@theia/debug/lib/browser/debug-session-contribution'; +import { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options'; +import { + DebugAdapterPath, + DebugChannel, + ForwardingDebugChannel, +} from '@theia/debug/lib/common/debug-service'; +import { DebugSession } from './debug-session'; + +@injectable() +export class DefaultDebugSessionFactory extends TheiaDefaultDebugSessionFactory { + override get( + sessionId: string, + options: DebugConfigurationSessionOptions, + parentSession?: DebugSession + ): DebugSession { + const connection = new DebugSessionConnection( + sessionId, + () => + new Promise((resolve) => + this.connectionProvider.openChannel( + `${DebugAdapterPath}/${sessionId}`, + (wsChannel) => { + resolve(new ForwardingDebugChannel(wsChannel)); + }, + { reconnecting: false } + ) + ), + this.getTraceOutputChannel() + ); + // patched debug session + return new DebugSession( + sessionId, + options, + parentSession, + connection, + this.terminalService, + this.editorManager, + this.breakpoints, + this.labelProvider, + this.messages, + this.fileService, + this.debugContributionProvider, + this.workspaceService + ); + } +} diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts b/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts new file mode 100644 index 000000000..f641a6535 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts @@ -0,0 +1,120 @@ +import type { ContextKey } from '@theia/core/lib/browser/context-key-service'; +import { injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + DebugSession, + DebugState, +} from '@theia/debug/lib/browser/debug-session'; +import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager'; +import type { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options'; + +function debugStateLabel(state: DebugState): string { + switch (state) { + case DebugState.Initializing: + return 'initializing'; + case DebugState.Stopped: + return 'stopped'; + case DebugState.Running: + return 'running'; + default: + return 'inactive'; + } +} + +@injectable() +export class DebugSessionManager extends TheiaDebugSessionManager { + protected debugStateKey: ContextKey; + + @postConstruct() + protected override init(): void { + this.debugStateKey = this.contextKeyService.createKey( + 'debugState', + debugStateLabel(this.state) + ); + super.init(); + } + + protected override fireDidChange(current: DebugSession | undefined): void { + this.debugTypeKey.set(current?.configuration.type); + this.inDebugModeKey.set(this.inDebugMode); + this.debugStateKey.set(debugStateLabel(this.state)); + this.onDidChangeEmitter.fire(current); + } + + protected override async doStart( + sessionId: string, + options: DebugConfigurationSessionOptions + ): Promise { + const parentSession = + options.configuration.parentSession && + this._sessions.get(options.configuration.parentSession.id); + const contrib = this.sessionContributionRegistry.get( + options.configuration.type + ); + const sessionFactory = contrib + ? contrib.debugSessionFactory() + : this.debugSessionFactory; + const session = sessionFactory.get(sessionId, options, parentSession); + this._sessions.set(sessionId, session); + + this.debugTypeKey.set(session.configuration.type); + // this.onDidCreateDebugSessionEmitter.fire(session); // defer the didCreate event after start https://github.com/eclipse-theia/theia/issues/11916 + + let state = DebugState.Inactive; + session.onDidChange(() => { + if (state !== session.state) { + state = session.state; + if (state === DebugState.Stopped) { + this.onDidStopDebugSessionEmitter.fire(session); + } + } + this.updateCurrentSession(session); + }); + session.onDidChangeBreakpoints((uri) => + this.fireDidChangeBreakpoints({ session, uri }) + ); + session.on('terminated', async (event) => { + const restart = event.body && event.body.restart; + if (restart) { + // postDebugTask isn't run in case of auto restart as well as preLaunchTask + this.doRestart(session, !!restart); + } else { + await session.disconnect(false, () => + this.debug.terminateDebugSession(session.id) + ); + await this.runTask( + session.options.workspaceFolderUri, + session.configuration.postDebugTask + ); + } + }); + + // eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars + session.on('exited', async (event) => { + await session.disconnect(false, () => + this.debug.terminateDebugSession(session.id) + ); + }); + + session.onDispose(() => this.cleanup(session)); + session + .start() + .then(() => { + this.onDidCreateDebugSessionEmitter.fire(session); // now fire the didCreate event + this.onDidStartDebugSessionEmitter.fire(session); + }) + // eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars + .catch((e) => { + session.stop(false, () => { + this.debug.terminateDebugSession(session.id); + }); + }); + session.onDidCustomEvent(({ event, body }) => + this.onDidReceiveDebugSessionCustomEventEmitter.fire({ + event, + body, + session, + }) + ); + return session; + } +} diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-session.ts b/arduino-ide-extension/src/browser/theia/debug/debug-session.ts new file mode 100644 index 000000000..7db51c2ac --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/debug/debug-session.ts @@ -0,0 +1,231 @@ +import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; +import { Deferred } from '@theia/core/lib/common/promise-util'; +import { Mutable } from '@theia/core/lib/common/types'; +import { URI } from '@theia/core/lib/common/uri'; +import { DebugSession as TheiaDebugSession } from '@theia/debug/lib/browser/debug-session'; +import { DebugFunctionBreakpoint } from '@theia/debug/lib/browser/model/debug-function-breakpoint'; +import { DebugSourceBreakpoint } from '@theia/debug/lib/browser/model/debug-source-breakpoint'; +import { + DebugThreadData, + StoppedDetails, +} from '@theia/debug/lib/browser/model/debug-thread'; +import { DebugProtocol } from '@vscode/debugprotocol'; +import { DebugThread } from './debug-thread'; + +export class DebugSession extends TheiaDebugSession { + /** + * The `send('initialize')` request resolves later than `on('initialized')` emits the event. + * Hence, the `configure` would use the empty object `capabilities`. + * Using the empty `capabilities` could result in missing exception breakpoint filters, as + * always `capabilities.exceptionBreakpointFilters` is falsy. This deferred promise works + * around this timing issue. + * See: https://github.com/eclipse-theia/theia/issues/11886. + */ + protected didReceiveCapabilities = new Deferred(); + + protected override async initialize(): Promise { + const clientName = FrontendApplicationConfigProvider.get().applicationName; + try { + const response = await this.connection.sendRequest('initialize', { + clientID: clientName.toLocaleLowerCase().replace(/ /g, '_'), + clientName, + adapterID: this.configuration.type, + locale: 'en-US', + linesStartAt1: true, + columnsStartAt1: true, + pathFormat: 'path', + supportsVariableType: false, + supportsVariablePaging: false, + supportsRunInTerminalRequest: true, + }); + this.updateCapabilities(response?.body || {}); + this.didReceiveCapabilities.resolve(); + } catch (err) { + this.didReceiveCapabilities.reject(err); + throw err; + } + } + + protected override async configure(): Promise { + await this.didReceiveCapabilities.promise; + return super.configure(); + } + + override async stop(isRestart: boolean, callback: () => void): Promise { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const _this = this as any; + if (!_this.isStopping) { + _this.isStopping = true; + if (this.configuration.lifecycleManagedByParent && this.parentSession) { + await this.parentSession.stop(isRestart, callback); + } else { + if (this.canTerminate()) { + const terminated = this.waitFor('terminated', 5000); + try { + await this.connection.sendRequest( + 'terminate', + { restart: isRestart }, + 5000 + ); + await terminated; + } catch (e) { + console.error('Did not receive terminated event in time', e); + } + } else { + const terminateDebuggee = + this.initialized && this.capabilities.supportTerminateDebuggee; + // Related https://github.com/microsoft/vscode/issues/165138 + try { + await this.sendRequest( + 'disconnect', + { restart: isRestart, terminateDebuggee }, + 2000 + ); + } catch (err) { + if ( + 'message' in err && + typeof err.message === 'string' && + err.message.test(err.message) + ) { + // VS Code ignores errors when sending the `disconnect` request. + // Debug adapter might not send the `disconnected` event as a response. + } else { + throw err; + } + } + } + callback(); + } + } + } + + protected override async sendFunctionBreakpoints( + affectedUri: URI + ): Promise { + const all = this.breakpoints + .getFunctionBreakpoints() + .map( + (origin) => + new DebugFunctionBreakpoint(origin, this.asDebugBreakpointOptions()) + ); + const enabled = all.filter((b) => b.enabled); + if (this.capabilities.supportsFunctionBreakpoints) { + try { + const response = await this.sendRequest('setFunctionBreakpoints', { + breakpoints: enabled.map((b) => b.origin.raw), + }); + // Apparently, `body` and `breakpoints` can be missing. + // https://github.com/eclipse-theia/theia/issues/11885 + // https://github.com/microsoft/vscode/blob/80004351ccf0884b58359f7c8c801c91bb827d83/src/vs/workbench/contrib/debug/browser/debugSession.ts#L448-L449 + if (response && response.body) { + response.body.breakpoints.forEach((raw, index) => { + // node debug adapter returns more breakpoints sometimes + if (enabled[index]) { + enabled[index].update({ raw }); + } + }); + } + } catch (error) { + // could be error or promise rejection of DebugProtocol.SetFunctionBreakpoints + if (error instanceof Error) { + console.error(`Error setting breakpoints: ${error.message}`); + } else { + // handle adapters that send failed DebugProtocol.SetFunctionBreakpoints for invalid breakpoints + const genericMessage = + 'Function breakpoint not valid for current debug session'; + const message = error.message ? `${error.message}` : genericMessage; + console.warn( + `Could not handle function breakpoints: ${message}, disabling...` + ); + enabled.forEach((b) => + b.update({ + raw: { + verified: false, + message, + }, + }) + ); + } + } + } + this.setBreakpoints(affectedUri, all); + } + + protected override async sendSourceBreakpoints( + affectedUri: URI, + sourceModified?: boolean + ): Promise { + const source = await this.toSource(affectedUri); + const all = this.breakpoints + .findMarkers({ uri: affectedUri }) + .map( + ({ data }) => + new DebugSourceBreakpoint(data, this.asDebugBreakpointOptions()) + ); + const enabled = all.filter((b) => b.enabled); + try { + const breakpoints = enabled.map(({ origin }) => origin.raw); + const response = await this.sendRequest('setBreakpoints', { + source: source.raw, + sourceModified, + breakpoints, + lines: breakpoints.map(({ line }) => line), + }); + response.body.breakpoints.forEach((raw, index) => { + // node debug adapter returns more breakpoints sometimes + if (enabled[index]) { + enabled[index].update({ raw }); + } + }); + } catch (error) { + // could be error or promise rejection of DebugProtocol.SetBreakpointsResponse + if (error instanceof Error) { + console.error(`Error setting breakpoints: ${error.message}`); + } else { + // handle adapters that send failed DebugProtocol.SetBreakpointsResponse for invalid breakpoints + const genericMessage = 'Breakpoint not valid for current debug session'; + const message = error.message ? `${error.message}` : genericMessage; + console.warn( + `Could not handle breakpoints for ${affectedUri}: ${message}, disabling...` + ); + enabled.forEach((b) => + b.update({ + raw: { + verified: false, + message, + }, + }) + ); + } + } + this.setSourceBreakpoints(affectedUri, all); + } + + protected override doUpdateThreads( + threads: DebugProtocol.Thread[], + stoppedDetails?: StoppedDetails + ): void { + const existing = this._threads; + this._threads = new Map(); + for (const raw of threads) { + const id = raw.id; + const thread = existing.get(id) || new DebugThread(this); // patched debug thread + this._threads.set(id, thread); + const data: Partial> = { raw }; + if (stoppedDetails) { + if (stoppedDetails.threadId === id) { + data.stoppedDetails = stoppedDetails; + } else if (stoppedDetails.allThreadsStopped) { + data.stoppedDetails = { + // When a debug adapter notifies us that all threads are stopped, + // we do not know why the others are stopped, so we should default + // to something generic. + reason: '', + }; + } + } + thread.update(data); + } + this.updateCurrentThread(stoppedDetails); + } +} diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-stack-frame.ts b/arduino-ide-extension/src/browser/theia/debug/debug-stack-frame.ts new file mode 100644 index 000000000..c52e238ae --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/debug/debug-stack-frame.ts @@ -0,0 +1,32 @@ +import { WidgetOpenerOptions } from '@theia/core/lib/browser/widget-open-handler'; +import { Range } from '@theia/core/shared/vscode-languageserver-types'; +import { DebugStackFrame as TheiaDebugStackFrame } from '@theia/debug/lib/browser/model/debug-stack-frame'; +import { EditorWidget } from '@theia/editor/lib/browser/editor-widget'; + +export class DebugStackFrame extends TheiaDebugStackFrame { + override async open( + options: WidgetOpenerOptions = { + mode: 'reveal', + } + ): Promise { + if (!this.source) { + return undefined; + } + const { line, column, endLine, endColumn, source } = this.raw; + if (!source) { + return undefined; + } + // create selection based on VS Code + // https://github.com/eclipse-theia/theia/issues/11880 + const selection = Range.create( + line, + column, + endLine || line, + endColumn || column + ); + this.source.open({ + ...options, + selection, + }); + } +} diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-thread.ts b/arduino-ide-extension/src/browser/theia/debug/debug-thread.ts new file mode 100644 index 000000000..bb0d3313c --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/debug/debug-thread.ts @@ -0,0 +1,22 @@ +import { DebugStackFrame as TheiaDebugStackFrame } from '@theia/debug/lib/browser/model/debug-stack-frame'; +import { DebugThread as TheiaDebugThread } from '@theia/debug/lib/browser/model/debug-thread'; +import { DebugProtocol } from '@vscode/debugprotocol'; +import { DebugStackFrame } from './debug-stack-frame'; + +export class DebugThread extends TheiaDebugThread { + protected override doUpdateFrames( + frames: DebugProtocol.StackFrame[] + ): TheiaDebugStackFrame[] { + const result = new Set(); + for (const raw of frames) { + const id = raw.id; + const frame = + this._frames.get(id) || new DebugStackFrame(this, this.session); // patched debug stack frame + this._frames.set(id, frame); + frame.update({ raw }); + result.add(frame); + } + this.updateCurrentFrame(); + return [...result.values()]; + } +} diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-toolbar-widget.tsx b/arduino-ide-extension/src/browser/theia/debug/debug-toolbar-widget.tsx new file mode 100644 index 000000000..bc6e135e8 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/debug/debug-toolbar-widget.tsx @@ -0,0 +1,85 @@ +import { ContextKeyService } from '@theia/core/lib/browser/context-key-service'; +import { CommandRegistry } from '@theia/core/lib/common/command'; +import { + ActionMenuNode, + CompositeMenuNode, + MenuModelRegistry, +} from '@theia/core/lib/common/menu'; +import { nls } from '@theia/core/lib/common/nls'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import * as React from '@theia/core/shared/react'; +import { DebugState } from '@theia/debug/lib/browser/debug-session'; +import { DebugAction } from './debug-action'; +import { DebugToolBar as TheiaDebugToolbar } from '@theia/debug/lib/browser/view/debug-toolbar-widget'; + +@injectable() +export class DebugToolbar extends TheiaDebugToolbar { + @inject(CommandRegistry) private readonly commandRegistry: CommandRegistry; + @inject(MenuModelRegistry) + private readonly menuModelRegistry: MenuModelRegistry; + @inject(ContextKeyService) + private readonly contextKeyService: ContextKeyService; + + protected override render(): React.ReactNode { + const { state } = this.model; + return ( + + {this.renderContributedCommands()} + {this.renderContinue()} + + + + + {this.renderStart()} + + ); + } + + private renderContributedCommands(): React.ReactNode { + return this.menuModelRegistry + .getMenu(TheiaDebugToolbar.MENU) + .children.filter((node) => node instanceof CompositeMenuNode) + .map((node) => (node as CompositeMenuNode).children) + .reduce((acc, curr) => acc.concat(curr), []) + .filter((node) => node instanceof ActionMenuNode) + .map((node) => this.debugAction(node as ActionMenuNode)); + } + + private debugAction(node: ActionMenuNode): React.ReactNode { + const { label, command, when, icon: iconClass = '' } = node; + const run = () => this.commandRegistry.executeCommand(command); + const enabled = when ? this.contextKeyService.match(when) : true; + return ( + enabled && ( + + ) + ); + } +} diff --git a/arduino-ide-extension/src/browser/theia/plugin-ext/debug-main.ts b/arduino-ide-extension/src/browser/theia/plugin-ext/debug-main.ts new file mode 100644 index 000000000..0845d6196 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/plugin-ext/debug-main.ts @@ -0,0 +1,66 @@ +import { + Disposable, + DisposableCollection, +} from '@theia/core/lib/common/disposable'; +import { DebuggerDescription } from '@theia/debug/lib/common/debug-service'; +import { DebugMainImpl as TheiaDebugMainImpl } from '@theia/plugin-ext/lib/main/browser/debug/debug-main'; +import { PluginDebugAdapterContribution } from '@theia/plugin-ext/lib/main/browser/debug/plugin-debug-adapter-contribution'; +import { PluginDebugSessionFactory } from './plugin-debug-session-factory'; + +export class DebugMainImpl extends TheiaDebugMainImpl { + override async $registerDebuggerContribution( + description: DebuggerDescription + ): Promise { + const debugType = description.type; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const _this = this; + const terminalOptionsExt = await _this.debugExt.$getTerminalCreationOptions( + debugType + ); + + if (_this.toDispose.disposed) { + return; + } + + const debugSessionFactory = new PluginDebugSessionFactory( + _this.terminalService, + _this.editorManager, + _this.breakpointsManager, + _this.labelProvider, + _this.messages, + _this.outputChannelManager, + _this.debugPreferences, + async (sessionId: string) => { + const connection = await _this.connectionMain.ensureConnection( + sessionId + ); + return connection; + }, + _this.fileService, + terminalOptionsExt, + _this.debugContributionProvider, + _this.workspaceService + ); + + const toDispose = new DisposableCollection( + Disposable.create(() => _this.debuggerContributions.delete(debugType)) + ); + _this.debuggerContributions.set(debugType, toDispose); + toDispose.pushAll([ + _this.pluginDebugService.registerDebugAdapterContribution( + new PluginDebugAdapterContribution( + description, + _this.debugExt, + _this.pluginService + ) + ), + _this.sessionContributionRegistrator.registerDebugSessionContribution({ + debugType: description.type, + debugSessionFactory: () => debugSessionFactory, + }), + ]); + _this.toDispose.push( + Disposable.create(() => this.$unregisterDebuggerConfiguration(debugType)) + ); + } +} diff --git a/arduino-ide-extension/src/browser/theia/plugin-ext/hosted-plugin.ts b/arduino-ide-extension/src/browser/theia/plugin-ext/hosted-plugin.ts index 4ef4b1f55..666a6eedc 100644 --- a/arduino-ide-extension/src/browser/theia/plugin-ext/hosted-plugin.ts +++ b/arduino-ide-extension/src/browser/theia/plugin-ext/hosted-plugin.ts @@ -1,7 +1,17 @@ import { Emitter, Event, JsonRpcProxy } from '@theia/core'; import { injectable, interfaces } from '@theia/core/shared/inversify'; import { HostedPluginServer } from '@theia/plugin-ext/lib/common/plugin-protocol'; -import { HostedPluginSupport as TheiaHostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin'; +import { RPCProtocol } from '@theia/plugin-ext/lib/common/rpc-protocol'; +import { + HostedPluginSupport as TheiaHostedPluginSupport, + PluginHost, +} from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin'; +import { PluginWorker } from '@theia/plugin-ext/lib/hosted/browser/plugin-worker'; +import { setUpPluginApi } from '@theia/plugin-ext/lib/main/browser/main-context'; +import { PLUGIN_RPC_CONTEXT } from '@theia/plugin-ext/lib/common/plugin-api-rpc'; +import { DebugMainImpl } from './debug-main'; +import { ConnectionImpl } from '@theia/plugin-ext/lib/common/connection'; + @injectable() export class HostedPluginSupport extends TheiaHostedPluginSupport { private readonly onDidLoadEmitter = new Emitter(); @@ -31,4 +41,26 @@ export class HostedPluginSupport extends TheiaHostedPluginSupport { // eslint-disable-next-line @typescript-eslint/no-explicit-any return (this as any).server; } + + // to patch the VS Code extension based debugger + // eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars + protected override initRpc(host: PluginHost, pluginId: string): RPCProtocol { + const rpc = + host === 'frontend' ? new PluginWorker().rpc : this.createServerRpc(host); + setUpPluginApi(rpc, this.container); + this.patchDebugMain(rpc); + this.mainPluginApiProviders + .getContributions() + .forEach((p) => p.initialize(rpc, this.container)); + return rpc; + } + + private patchDebugMain(rpc: RPCProtocol): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const connectionMain = (rpc as any).locals.get( + PLUGIN_RPC_CONTEXT.CONNECTION_MAIN.id + ) as ConnectionImpl; + const debugMain = new DebugMainImpl(rpc, connectionMain, this.container); + rpc.set(PLUGIN_RPC_CONTEXT.DEBUG_MAIN, debugMain); + } } diff --git a/arduino-ide-extension/src/browser/theia/plugin-ext/plugin-debug-session-factory.ts b/arduino-ide-extension/src/browser/theia/plugin-ext/plugin-debug-session-factory.ts new file mode 100644 index 000000000..a84275e1a --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/plugin-ext/plugin-debug-session-factory.ts @@ -0,0 +1,37 @@ +import { DebugSession } from '@theia/debug/lib/browser/debug-session'; +import { DebugSessionConnection } from '@theia/debug/lib/browser/debug-session-connection'; +import { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options'; +import { PluginDebugSessionFactory as TheiaPluginDebugSessionFactory } from '@theia/plugin-ext/lib/main/browser/debug/plugin-debug-session-factory'; +import { PluginDebugSession } from './plugin-debug-session'; + +export class PluginDebugSessionFactory extends TheiaPluginDebugSessionFactory { + override get( + sessionId: string, + options: DebugConfigurationSessionOptions, + parentSession?: DebugSession + ): DebugSession { + const connection = new DebugSessionConnection( + sessionId, + this.connectionFactory, + this.getTraceOutputChannel() + ); + + return new PluginDebugSession( + sessionId, + options, + parentSession, + connection, + this.terminalService, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.editorManager as any, + this.breakpoints, + this.labelProvider, + this.messages, + this.fileService, + this.terminalOptionsExt, + this.debugContributionProvider, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.workspaceService as any + ); + } +} diff --git a/arduino-ide-extension/src/browser/theia/plugin-ext/plugin-debug-session.ts b/arduino-ide-extension/src/browser/theia/plugin-ext/plugin-debug-session.ts new file mode 100644 index 000000000..28b1de7ef --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/plugin-ext/plugin-debug-session.ts @@ -0,0 +1,62 @@ +import { ContributionProvider, MessageClient } from '@theia/core'; +import { LabelProvider } from '@theia/core/lib/browser'; +import { BreakpointManager } from '@theia/debug/lib/browser/breakpoint/breakpoint-manager'; +import { DebugContribution } from '@theia/debug/lib/browser/debug-contribution'; +import { DebugSession as TheiaDebugSession } from '@theia/debug/lib/browser/debug-session'; +import { DebugSessionConnection } from '@theia/debug/lib/browser/debug-session-connection'; +import { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options'; +import { FileService } from '@theia/filesystem/lib/browser/file-service'; +import { TerminalOptionsExt } from '@theia/plugin-ext'; +import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service'; +import { + TerminalWidget, + TerminalWidgetOptions, +} from '@theia/terminal/lib/browser/base/terminal-widget'; +import { DebugSession } from '../debug/debug-session'; +import { EditorManager } from '../editor/editor-manager'; +import { WorkspaceService } from '../workspace/workspace-service'; + +// This class extends the patched debug session, and not the default debug session from Theia +export class PluginDebugSession extends DebugSession { + constructor( + override readonly id: string, + override readonly options: DebugConfigurationSessionOptions, + override readonly parentSession: TheiaDebugSession | undefined, + protected override readonly connection: DebugSessionConnection, + protected override readonly terminalServer: TerminalService, + protected override readonly editorManager: EditorManager, + protected override readonly breakpoints: BreakpointManager, + protected override readonly labelProvider: LabelProvider, + protected override readonly messages: MessageClient, + protected override readonly fileService: FileService, + protected readonly terminalOptionsExt: TerminalOptionsExt | undefined, + protected override readonly debugContributionProvider: ContributionProvider, + protected override readonly workspaceService: WorkspaceService + ) { + super( + id, + options, + parentSession, + connection, + terminalServer, + editorManager, + breakpoints, + labelProvider, + messages, + fileService, + debugContributionProvider, + workspaceService + ); + } + + protected override async doCreateTerminal( + terminalWidgetOptions: TerminalWidgetOptions + ): Promise { + terminalWidgetOptions = Object.assign( + {}, + terminalWidgetOptions, + this.terminalOptionsExt + ); + return super.doCreateTerminal(terminalWidgetOptions); + } +} diff --git a/arduino-ide-extension/src/browser/theia/plugin-ext/plugin-menu-command-adapter.ts b/arduino-ide-extension/src/browser/theia/plugin-ext/plugin-menu-command-adapter.ts new file mode 100644 index 000000000..156bbb2cb --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/plugin-ext/plugin-menu-command-adapter.ts @@ -0,0 +1,73 @@ +import { MenuPath } from '@theia/core'; +import { TAB_BAR_TOOLBAR_CONTEXT_MENU } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; +import { injectable, postConstruct } from '@theia/core/shared/inversify'; +import { DebugToolBar } from '@theia/debug/lib/browser/view/debug-toolbar-widget'; +import { DebugVariablesWidget } from '@theia/debug/lib/browser/view/debug-variables-widget'; +import { + ArgumentAdapter, + PluginMenuCommandAdapter as TheiaPluginMenuCommandAdapter, +} from '@theia/plugin-ext/lib/main/browser/menus/plugin-menu-command-adapter'; +import { + codeToTheiaMappings, + ContributionPoint, +} from '@theia/plugin-ext/lib/main/browser/menus/vscode-theia-menu-mappings'; + +function patch( + toPatch: typeof codeToTheiaMappings, + key: string, + value: MenuPath[] +): void { + const loose = toPatch as Map; + if (!loose.has(key)) { + loose.set(key, value); + } +} +// mappings is a const and cannot be customized with DI +patch(codeToTheiaMappings, 'debug/variables/context', [ + DebugVariablesWidget.CONTEXT_MENU, +]); +patch(codeToTheiaMappings, 'debug/toolBar', [DebugToolBar.MENU]); + +@injectable() +export class PluginMenuCommandAdapter extends TheiaPluginMenuCommandAdapter { + @postConstruct() + protected override init(): void { + const toCommentArgs: ArgumentAdapter = (...args) => + this.toCommentArgs(...args); + const firstArgOnly: ArgumentAdapter = (...args) => [args[0]]; + const noArgs: ArgumentAdapter = () => []; + const toScmArgs: ArgumentAdapter = (...args) => this.toScmArgs(...args); + const selectedResource = () => this.getSelectedResources(); + const widgetURI: ArgumentAdapter = (widget) => + this.codeEditorUtil.is(widget) + ? [this.codeEditorUtil.getResourceUri(widget)] + : []; + (>[ + ['comments/comment/context', toCommentArgs], + ['comments/comment/title', toCommentArgs], + ['comments/commentThread/context', toCommentArgs], + ['debug/callstack/context', firstArgOnly], + ['debug/variables/context', firstArgOnly], + ['debug/toolBar', noArgs], + ['editor/context', selectedResource], + ['editor/title', widgetURI], + ['editor/title/context', selectedResource], + ['explorer/context', selectedResource], + ['scm/resourceFolder/context', toScmArgs], + ['scm/resourceGroup/context', toScmArgs], + ['scm/resourceState/context', toScmArgs], + ['scm/title', () => this.toScmArg(this.scmService.selectedRepository)], + ['timeline/item/context', (...args) => this.toTimelineArgs(...args)], + ['view/item/context', (...args) => this.toTreeArgs(...args)], + ['view/title', noArgs], + ]).forEach(([contributionPoint, adapter]) => { + if (adapter) { + const paths = codeToTheiaMappings.get(contributionPoint); + if (paths) { + paths.forEach((path) => this.addArgumentAdapter(path, adapter)); + } + } + }); + this.addArgumentAdapter(TAB_BAR_TOOLBAR_CONTEXT_MENU, widgetURI); + } +} From e58d3a713a22a8995f2400822436b6295c06a2fa Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Mon, 28 Nov 2022 17:16:03 +0100 Subject: [PATCH 4/4] fix: filtered undesired contributions: RTOS view Signed-off-by: Akos Kitta --- .../src/node/arduino-ide-backend-module.ts | 8 ++ .../node/theia/plugin-ext/plugin-reader.ts | 80 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 arduino-ide-extension/src/node/theia/plugin-ext/plugin-reader.ts diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index 09294f96b..3c968ae23 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -111,6 +111,8 @@ import { IsTempSketch } from './is-temp-sketch'; import { rebindNsfwFileSystemWatcher } from './theia/filesystem/nsfw-watcher/nsfw-bindings'; import { MessagingContribution } from './theia/core/messaging-contribution'; import { MessagingService } from '@theia/core/lib/node/messaging/messaging-service'; +import { HostedPluginReader } from './theia/plugin-ext/plugin-reader'; +import { HostedPluginReader as TheiaHostedPluginReader } from '@theia/plugin-ext/lib/hosted/node/plugin-reader'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BackendApplication).toSelf().inSingletonScope(); @@ -384,6 +386,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { rebind(MessagingService.Identifier) .to(MessagingContribution) .inSingletonScope(); + + // Removed undesired contributions from VS Code extensions + // Such as the RTOS view from the `cortex-debug` extension + // https://github.com/arduino/arduino-ide/pull/1706#pullrequestreview-1195595080 + bind(HostedPluginReader).toSelf().inSingletonScope(); + rebind(TheiaHostedPluginReader).toService(HostedPluginReader); }); function bindChildLogger(bind: interfaces.Bind, name: string): void { diff --git a/arduino-ide-extension/src/node/theia/plugin-ext/plugin-reader.ts b/arduino-ide-extension/src/node/theia/plugin-ext/plugin-reader.ts new file mode 100644 index 000000000..d68f34c9b --- /dev/null +++ b/arduino-ide-extension/src/node/theia/plugin-ext/plugin-reader.ts @@ -0,0 +1,80 @@ +import { injectable } from '@theia/core/shared/inversify'; +import type { + PluginContribution, + PluginPackage, +} from '@theia/plugin-ext/lib/common/plugin-protocol'; +import { HostedPluginReader as TheiaHostedPluginReader } from '@theia/plugin-ext/lib/hosted/node/plugin-reader'; + +@injectable() +export class HostedPluginReader extends TheiaHostedPluginReader { + override readContribution( + plugin: PluginPackage + ): PluginContribution | undefined { + const scanner = this.scanner.getScanner(plugin); + const contributions = scanner.getContribution(plugin); + return this.filterContribution(plugin.name, contributions); + } + private filterContribution( + pluginName: string, + contributions: PluginContribution | undefined + ): PluginContribution | undefined { + if (!contributions) { + return contributions; + } + const filter = pluginFilters.get(pluginName); + return filter ? filter(contributions) : contributions; + } +} + +type PluginContributionFilter = ( + contribution: PluginContribution +) => PluginContribution | undefined; +const cortexDebugFilter: PluginContributionFilter = ( + contribution: PluginContribution +) => { + if (contribution.viewsContainers) { + for (const location of Object.keys(contribution.viewsContainers)) { + const viewContainers = contribution.viewsContainers[location]; + for (let i = 0; i < viewContainers.length; i++) { + const viewContainer = viewContainers[i]; + if ( + viewContainer.id === 'cortex-debug' && + viewContainer.title === 'RTOS' + ) { + viewContainers.splice(i, 1); + } + } + } + } + if (contribution.views) { + for (const location of Object.keys(contribution.views)) { + if (location === 'cortex-debug') { + const views = contribution.views[location]; + for (let i = 0; i < views.length; i++) { + const view = views[i]; + if (view.id === 'cortex-debug.rtos') { + views.splice(i, 1); + } + } + } + } + } + if (contribution.menus) { + for (const location of Object.keys(contribution.menus)) { + if (location === 'commandPalette') { + const menus = contribution.menus[location]; + for (let i = 0; i < menus.length; i++) { + const menu = menus[i]; + if (menu.command === 'cortex-debug.rtos.toggleRTOSPanel') { + menu.when = 'false'; + } + } + } + } + } + return contribution; +}; + +const pluginFilters = new Map([ + ['cortex-debug', cortexDebugFilter], +]);