diff --git a/.gitignore b/.gitignore index dcb4c0a780269..aad05c8fc4cc0 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ dev-packages/electron/compile_commands.json scripts/download license-check-summary.txt* *-trace.json +.tours diff --git a/CHANGELOG.md b/CHANGELOG.md index 11118d771151b..95e0ef273b037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,15 @@ - `commandService`, `instantiationService` removed from `MonacoEditor`. Use `StandaloneServices.get(IInstantationService / ICommandService)` instead. - `DecorationMiniMapOptions.position`, `DecorationOverviewRulerOptions.position` no longer optional. - Overrides used by `MonacoEditorFactory` accept the type `EditorServiceOverrides` rather than `{[key: string]: any}`. +- [debug] The interface method `DebugService#provideDynamicDebugConfigurations` changes the return type to `Record`. +This impacts the corresponding return type for `DebugConfigurationManager#provideDynamicDebugConfigurations`. +The following functions under `plugin-api-rpc.ts#DebugExt` and in the `PluginDebugAdapterContribution` are deprecated + * $provideDebugConfigurations + * $resolveDebugConfigurations + * $resolveDebugConfigurationWithSubstitutedVariablesByHandle + + The `PluginDebugAdapterContributionRegistrator` interface has been removed + ## v1.23.0 - 2/24/2022 diff --git a/packages/debug/src/browser/debug-configuration-manager.ts b/packages/debug/src/browser/debug-configuration-manager.ts index 50be2f63d5cd2..fa2e013aa2990 100644 --- a/packages/debug/src/browser/debug-configuration-manager.ts +++ b/packages/debug/src/browser/debug-configuration-manager.ts @@ -344,7 +344,7 @@ export class DebugConfigurationManager { await WaitUntilEvent.fire(this.onWillProvideDebugConfigurationEmitter, {}); } - async provideDynamicDebugConfigurations(): Promise<{ type: string, configurations: DebugConfiguration[] }[]> { + async provideDynamicDebugConfigurations(): Promise> { await this.fireWillProvideDynamicDebugConfiguration(); return this.debug.provideDynamicDebugConfigurations!(); } diff --git a/packages/debug/src/browser/debug-prefix-configuration.ts b/packages/debug/src/browser/debug-prefix-configuration.ts index d4682564484b3..57248ac2ada20 100644 --- a/packages/debug/src/browser/debug-prefix-configuration.ts +++ b/packages/debug/src/browser/debug-prefix-configuration.ts @@ -122,12 +122,11 @@ export class DebugPrefixConfiguration implements CommandContribution, CommandHan } // Resolve dynamic configurations from providers - const configurationsByType = await this.debugConfigurationManager.provideDynamicDebugConfigurations(); - for (const typeConfigurations of configurationsByType) { - const dynamicConfigurations = typeConfigurations.configurations; + const record = await this.debugConfigurationManager.provideDynamicDebugConfigurations(); + for (const [type, dynamicConfigurations] of Object.entries(record)) { if (dynamicConfigurations.length > 0) { items.push({ - label: typeConfigurations.type, + label: type, type: 'separator' }); } diff --git a/packages/debug/src/common/debug-service.ts b/packages/debug/src/common/debug-service.ts index cb025b5b17447..d34f9074d0bc0 100644 --- a/packages/debug/src/common/debug-service.ts +++ b/packages/debug/src/common/debug-service.ts @@ -71,10 +71,9 @@ export interface DebugService extends Disposable { provideDebugConfigurations(debugType: string, workspaceFolderUri: string | undefined): Promise; /** - * Provides dynamic debug configurations by a provider debug type - * @returns An Array of objects containing the debug type and corresponding dynamic debug configurations array + * @returns A Record of debug configuration provider types and a corresponding dynamic debug configurations array */ - provideDynamicDebugConfigurations?(): Promise<{ type: string, configurations: DebugConfiguration[] }[]>; + provideDynamicDebugConfigurations?(): Promise>; /** * Resolves a [debug configuration](#DebugConfiguration) by filling in missing values diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 655ee0de54862..b27e0c632eb50 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -1651,16 +1651,50 @@ export enum DebugConfigurationProviderTriggerKind { Dynamic = 2 } +export interface DebugConfigurationProvider { + readonly handle: number; + readonly type: string; + readonly triggerKind: DebugConfigurationProviderTriggerKind; + provideDebugConfigurations?(folder: string | undefined): Promise; + resolveDebugConfiguration?(folder: string | undefined, debugConfiguration: theia.DebugConfiguration): Promise; + resolveDebugConfigurationWithSubstitutedVariables?(folder: string | undefined, debugConfiguration: theia.DebugConfiguration): Promise; +} + +export interface DebugConfigurationProviderDescriptor { + readonly handle: number, + readonly type: string, + readonly trigger: DebugConfigurationProviderTriggerKind, + readonly provideDebugConfiguration: boolean, + readonly resolveDebugConfigurations: boolean, + readonly resolveDebugConfigurationWithSubstitutedVariables: boolean +} + export interface DebugExt { $onSessionCustomEvent(sessionId: string, event: string, body?: any): void; $breakpointsDidChange(added: Breakpoint[], removed: string[], changed: Breakpoint[]): void; $sessionDidCreate(sessionId: string): void; $sessionDidDestroy(sessionId: string): void; $sessionDidChange(sessionId: string | undefined): void; + /** + * @deprecated since 1.24.0. Use $registerDebugConfigurationProvider with $provideDebugConfigurationsByHandle instead. + */ $provideDebugConfigurations(debugType: string, workspaceFolder: string | undefined, dynamic?: boolean): Promise; + + /** + * @deprecated since 1.24.0. Use $registerDebugConfigurationProvider with $resolveDebugConfigurationByHandle instead. + */ $resolveDebugConfigurations(debugConfiguration: theia.DebugConfiguration, workspaceFolder: string | undefined): Promise; + + /** + * @deprecated since 1.24.0. Use $registerDebugConfigurationProvider with $resolveDebugConfigurationWithSubstitutedVariablesByHandle instead. + */ $resolveDebugConfigurationWithSubstitutedVariables(debugConfiguration: theia.DebugConfiguration, workspaceFolder: string | undefined): Promise; + + $provideDebugConfigurationsByHandle(handle: number, workspaceFolder: string | undefined): Promise; + $resolveDebugConfigurationByHandle(handle: number, workspaceFolder: string | undefined, debugConfiguration: theia.DebugConfiguration): Promise; + $resolveDebugConfigurationWithSubstitutedVariablesByHandle(handle: number, workspaceFolder: string | undefined, debugConfiguration: theia.DebugConfiguration): Promise; + $createDebugSession(debugConfiguration: theia.DebugConfiguration): Promise; $terminateDebugSession(sessionId: string): Promise; $getTerminalCreationOptions(debugType: string): Promise; @@ -1671,6 +1705,8 @@ export interface DebugMain { $appendLineToDebugConsole(value: string): Promise; $registerDebuggerContribution(description: DebuggerDescription): Promise; $unregisterDebuggerConfiguration(debugType: string): Promise; + $registerDebugConfigurationProvider(description: DebugConfigurationProviderDescriptor): void; + $unregisterDebugConfigurationProvider(handle: number): Promise; $addBreakpoints(breakpoints: Breakpoint[]): Promise; $removeBreakpoints(breakpoints: string[]): Promise; $startDebugging(folder: theia.WorkspaceFolder | undefined, nameOrConfiguration: string | theia.DebugConfiguration, options: theia.DebugSessionOptions): Promise; diff --git a/packages/plugin-ext/src/main/browser/debug/debug-main.ts b/packages/plugin-ext/src/main/browser/debug/debug-main.ts index 717d9ff5dd0d6..b2ef39bccdefe 100644 --- a/packages/plugin-ext/src/main/browser/debug/debug-main.ts +++ b/packages/plugin-ext/src/main/browser/debug/debug-main.ts @@ -19,6 +19,7 @@ import { interfaces } from '@theia/core/shared/inversify'; import { RPCProtocol } from '../../../common/rpc-protocol'; import { + DebugConfigurationProviderDescriptor, DebugMain, DebugExt, MAIN_RPC_CONTEXT @@ -40,10 +41,11 @@ import { MessageClient } from '@theia/core/lib/common/message-service-protocol'; import { OutputChannelManager } from '@theia/output/lib/browser/output-channel'; import { DebugPreferences } from '@theia/debug/lib/browser/debug-preferences'; import { PluginDebugAdapterContribution } from './plugin-debug-adapter-contribution'; +import { PluginDebugConfigurationProvider } from './plugin-debug-configuration-provider'; import { PluginDebugSessionContributionRegistrator, PluginDebugSessionContributionRegistry } from './plugin-debug-session-contribution-registry'; import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; import { PluginDebugSessionFactory } from './plugin-debug-session-factory'; -import { PluginDebugAdapterContributionRegistrator, PluginDebugService } from './plugin-debug-service'; +import { PluginDebugService } from './plugin-debug-service'; import { HostedPluginSupport } from '../../../hosted/browser/hosted-plugin'; import { DebugFunctionBreakpoint } from '@theia/debug/lib/browser/model/debug-function-breakpoint'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; @@ -67,12 +69,13 @@ export class DebugMainImpl implements DebugMain, Disposable { private readonly outputChannelManager: OutputChannelManager; private readonly debugPreferences: DebugPreferences; private readonly sessionContributionRegistrator: PluginDebugSessionContributionRegistrator; - private readonly adapterContributionRegistrator: PluginDebugAdapterContributionRegistrator; + private readonly pluginDebugService: PluginDebugService; private readonly fileService: FileService; private readonly pluginService: HostedPluginSupport; private readonly debugContributionProvider: ContributionProvider; private readonly debuggerContributions = new Map(); + private readonly configurationProviders = new Map(); private readonly toDispose = new DisposableCollection(); constructor(rpc: RPCProtocol, readonly connectionMain: ConnectionImpl, container: interfaces.Container) { @@ -87,7 +90,7 @@ export class DebugMainImpl implements DebugMain, Disposable { this.messages = container.get(MessageClient); this.outputChannelManager = container.get(OutputChannelManager); this.debugPreferences = container.get(DebugPreferences); - this.adapterContributionRegistrator = container.get(PluginDebugService); + this.pluginDebugService = container.get(PluginDebugService); this.sessionContributionRegistrator = container.get(PluginDebugSessionContributionRegistry); this.debugContributionProvider = container.getNamed(ContributionProvider, DebugContribution); this.fileService = container.get(FileService); @@ -160,7 +163,7 @@ export class DebugMainImpl implements DebugMain, Disposable { ); this.debuggerContributions.set(debugType, toDispose); toDispose.pushAll([ - this.adapterContributionRegistrator.registerDebugAdapterContribution( + this.pluginDebugService.registerDebugAdapterContribution( new PluginDebugAdapterContribution(description, this.debugExt, this.pluginService) ), this.sessionContributionRegistrator.registerDebugSessionContribution({ @@ -178,6 +181,27 @@ export class DebugMainImpl implements DebugMain, Disposable { } } + $registerDebugConfigurationProvider(description: DebugConfigurationProviderDescriptor): void { + const handle = description.handle; + const toDispose = new DisposableCollection( + Disposable.create(() => this.configurationProviders.delete(handle)) + ); + this.configurationProviders.set(handle, toDispose); + + toDispose.push( + this.pluginDebugService.registerDebugConfigurationProvider(new PluginDebugConfigurationProvider(description, this.debugExt)) + ); + + this.toDispose.push(Disposable.create(() => this.$unregisterDebugConfigurationProvider(handle))); + } + + async $unregisterDebugConfigurationProvider(handle: number): Promise { + const disposable = this.configurationProviders.get(handle); + if (disposable) { + disposable.dispose(); + } + } + async $addBreakpoints(breakpoints: Breakpoint[]): Promise { const newBreakpoints = new Map(); breakpoints.forEach(b => newBreakpoints.set(b.id, b)); diff --git a/packages/plugin-ext/src/main/browser/debug/plugin-debug-adapter-contribution.ts b/packages/plugin-ext/src/main/browser/debug/plugin-debug-adapter-contribution.ts index 8724c107a3f99..60808aa0b77c0 100644 --- a/packages/plugin-ext/src/main/browser/debug/plugin-debug-adapter-contribution.ts +++ b/packages/plugin-ext/src/main/browser/debug/plugin-debug-adapter-contribution.ts @@ -37,14 +37,23 @@ export class PluginDebugAdapterContribution { return this.description.label; } + /** + * @deprecated since 1.24.0, Use [PluginDebugConfigurationProvider](plugin-debug-configuration-provider.ts) + */ async provideDebugConfigurations(workspaceFolderUri: string | undefined, dynamic: boolean = false): Promise { return this.debugExt.$provideDebugConfigurations(this.type, workspaceFolderUri, dynamic); } + /** + * @deprecated since 1.24.0, Use [PluginDebugConfigurationProvider](plugin-debug-configuration-provider.ts) + */ async resolveDebugConfiguration(config: DebugConfiguration, workspaceFolderUri: string | undefined): Promise { return this.debugExt.$resolveDebugConfigurations(config, workspaceFolderUri); } + /** + * @deprecated since 1.24.0, Use [PluginDebugConfigurationProvider](plugin-debug-configuration-provider.ts) + */ async resolveDebugConfigurationWithSubstitutedVariables(config: DebugConfiguration, workspaceFolderUri: string | undefined): Promise { return this.debugExt.$resolveDebugConfigurationWithSubstitutedVariables(config, workspaceFolderUri); } diff --git a/packages/plugin-ext/src/main/browser/debug/plugin-debug-configuration-provider.ts b/packages/plugin-ext/src/main/browser/debug/plugin-debug-configuration-provider.ts new file mode 100644 index 0000000000000..ba2bb20990cdf --- /dev/null +++ b/packages/plugin-ext/src/main/browser/debug/plugin-debug-configuration-provider.ts @@ -0,0 +1,57 @@ +// ***************************************************************************** +// Copyright (C) 2022 Ericsson and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { + DebugConfigurationProvider, + DebugConfigurationProviderDescriptor, + DebugConfigurationProviderTriggerKind, + DebugExt +} from '../../../common/plugin-api-rpc'; +import { DebugConfiguration } from '@theia/debug/lib/common/debug-configuration'; + +export class PluginDebugConfigurationProvider implements DebugConfigurationProvider { + public handle: number; + public type: string; + public triggerKind: DebugConfigurationProviderTriggerKind; + provideDebugConfigurations: (folder: string | undefined) => Promise; + resolveDebugConfiguration: (folder: string | undefined, debugConfiguration: DebugConfiguration) => Promise; + resolveDebugConfigurationWithSubstitutedVariables: (folder: string | undefined, debugConfiguration: DebugConfiguration) => Promise; + + constructor( + description: DebugConfigurationProviderDescriptor, + protected readonly debugExt: DebugExt + ) { + this.handle = description.handle; + this.type = description.type; + this.triggerKind = description.trigger; + + if (description.provideDebugConfiguration) { + this.provideDebugConfigurations = async (folder: string | undefined) => this.debugExt.$provideDebugConfigurationsByHandle(this.handle, folder); + } + + if (description.resolveDebugConfigurations) { + this.resolveDebugConfiguration = + async (folder: string | undefined, debugConfiguration: DebugConfiguration) => + this.debugExt.$resolveDebugConfigurationByHandle(this.handle, folder, debugConfiguration); + } + + if (description.resolveDebugConfigurationWithSubstitutedVariables) { + this.resolveDebugConfigurationWithSubstitutedVariables = + async (folder: string | undefined, debugConfiguration: DebugConfiguration) => + this.debugExt.$resolveDebugConfigurationWithSubstitutedVariablesByHandle(this.handle, folder, debugConfiguration); + } + } +} diff --git a/packages/plugin-ext/src/main/browser/debug/plugin-debug-service.ts b/packages/plugin-ext/src/main/browser/debug/plugin-debug-service.ts index cbd89ed3cdf7e..05c2bcda856c6 100644 --- a/packages/plugin-ext/src/main/browser/debug/plugin-debug-service.ts +++ b/packages/plugin-ext/src/main/browser/debug/plugin-debug-service.ts @@ -14,43 +14,29 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** -import { DebugService, DebuggerDescription, DebugPath } from '@theia/debug/lib/common/debug-service'; +import { DebuggerDescription, DebugPath, DebugService } from '@theia/debug/lib/common/debug-service'; import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; import { DebugConfiguration } from '@theia/debug/lib/common/debug-configuration'; import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema'; import { PluginDebugAdapterContribution } from './plugin-debug-adapter-contribution'; +import { PluginDebugConfigurationProvider } from './plugin-debug-configuration-provider'; import { injectable, inject, postConstruct } from '@theia/core/shared/inversify'; import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider'; import { WorkspaceService } from '@theia/workspace/lib/browser'; +import { DebugConfigurationProviderTriggerKind } from '../../../common/plugin-api-rpc'; import { DebuggerContribution } from '../../../common/plugin-protocol'; import { DebugRequestTypes } from '@theia/debug/lib/browser/debug-session-connection'; import * as theia from '@theia/plugin'; -/** - * Debug adapter contribution registrator. - */ -export interface PluginDebugAdapterContributionRegistrator { - /** - * Registers [PluginDebugAdapterContribution](#PluginDebugAdapterContribution). - * @param contrib contribution - */ - registerDebugAdapterContribution(contrib: PluginDebugAdapterContribution): Disposable; - - /** - * Unregisters [PluginDebugAdapterContribution](#PluginDebugAdapterContribution). - * @param debugType the debug type - */ - unregisterDebugAdapterContribution(debugType: string): void; -} - /** * Debug service to work with plugin and extension contributions. */ @injectable() -export class PluginDebugService implements DebugService, PluginDebugAdapterContributionRegistrator { +export class PluginDebugService implements DebugService { protected readonly debuggers: DebuggerContribution[] = []; protected readonly contributors = new Map(); + protected readonly configurationProviders = new Map(); protected readonly toDispose = new DisposableCollection(); // maps session and contribution @@ -92,6 +78,16 @@ export class PluginDebugService implements DebugService, PluginDebugAdapterContr this.contributors.delete(debugType); } + registerDebugConfigurationProvider(provider: PluginDebugConfigurationProvider): Disposable { + const handle = provider.handle; + this.configurationProviders.set(handle, provider); + return Disposable.create(() => this.unregisterDebugConfigurationProvider(handle)); + } + + unregisterDebugConfigurationProvider(handle: number): void { + this.configurationProviders.delete(handle); + } + async debugTypes(): Promise { const debugTypes = new Set(await this.delegated.debugTypes()); for (const contribution of this.debuggers) { @@ -104,52 +100,71 @@ export class PluginDebugService implements DebugService, PluginDebugAdapterContr } async provideDebugConfigurations(debugType: keyof DebugRequestTypes, workspaceFolderUri: string | undefined): Promise { - const contributor = this.contributors.get(debugType); - if (contributor) { - return contributor.provideDebugConfigurations && contributor.provideDebugConfigurations(workspaceFolderUri) || []; - } else { + const pluginProviders = + Array.from(this.configurationProviders.values()).filter(p => ( + p.triggerKind === DebugConfigurationProviderTriggerKind.Initial && + (p.type === debugType || p.type === '*') && + p.provideDebugConfigurations + )); + + if (pluginProviders.length === 0) { return this.delegated.provideDebugConfigurations(debugType, workspaceFolderUri); } + + const results: DebugConfiguration[] = []; + await Promise.all(pluginProviders.map(async p => { + const result = await p.provideDebugConfigurations(workspaceFolderUri); + if (result) { + results.push(...result); + } + })); + + return results; } - async provideDynamicDebugConfigurations(): Promise<{ type: string, configurations: DebugConfiguration[] }[]> { - const result: Promise<{ type: string, configurations: theia.DebugConfiguration[] }>[] = []; + async provideDynamicDebugConfigurations(): Promise> { + const pluginProviders = + Array.from(this.configurationProviders.values()).filter(p => ( + p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && + p.provideDebugConfigurations + )); - for (const [type, contributor] of this.contributors.entries()) { - const typeConfigurations = this.resolveDynamicConfigurationsForType(type, contributor); - result.push(typeConfigurations); - } + const configurationsRecord: Record = {}; - return Promise.all(result); - } + await Promise.all(pluginProviders.map(async provider => { + const configurations = await provider.provideDebugConfigurations(undefined); + for (const configuration of configurations) { + configuration.dynamic = true; + } + let configurationsPerType = configurationsRecord[provider.type]; + configurationsPerType = configurationsPerType ? configurationsPerType.concat(configurations) : configurations; - protected async resolveDynamicConfigurationsForType( - type: string, - contributor: PluginDebugAdapterContribution): Promise<{ type: string, configurations: DebugConfiguration[] }> { + if (configurationsPerType.length > 0) { + configurationsRecord[provider.type] = configurationsPerType; + } + })); - const configurations = await contributor.provideDebugConfigurations(undefined, true); - for (const configuration of configurations) { - configuration.dynamic = true; - } - return { type, configurations }; + return configurationsRecord; } async resolveDebugConfiguration(config: DebugConfiguration, workspaceFolderUri: string | undefined): Promise { let resolved = config; - // we should iterate over all to handle configuration providers for `*` - for (const contributor of this.contributors.values()) { - if (contributor) { - try { - const next = await contributor.resolveDebugConfiguration(resolved, workspaceFolderUri); - if (next) { - resolved = next; - } else { - return resolved; - } - } catch (e) { - console.error(e); + const allProviders = Array.from(this.configurationProviders.values()); + // Append debug type '*' at the end + const pluginProviders = allProviders.filter(p => p.type === config.type && !!p.resolveDebugConfiguration); + pluginProviders.push(...allProviders.filter(p => p.type === '*' && !!p.resolveDebugConfiguration)); + + for (const provider of pluginProviders) { + try { + const next = await provider.resolveDebugConfiguration(workspaceFolderUri, resolved); + if (next) { + resolved = next; + } else { + return resolved; } + } catch (e) { + console.error(e); } } @@ -159,19 +174,21 @@ export class PluginDebugService implements DebugService, PluginDebugAdapterContr async resolveDebugConfigurationWithSubstitutedVariables(config: DebugConfiguration, workspaceFolderUri: string | undefined): Promise { let resolved = config; - // we should iterate over all to handle configuration providers for `*` - for (const contributor of this.contributors.values()) { - if (contributor) { - try { - const next = await contributor.resolveDebugConfigurationWithSubstitutedVariables(resolved, workspaceFolderUri); - if (next) { - resolved = next; - } else { - return resolved; - } - } catch (e) { - console.error(e); + const allProviders = Array.from(this.configurationProviders.values()); + // Append debug type '*' at the end + const pluginProviders = allProviders.filter(p => p.type === config.type && !!p.resolveDebugConfigurationWithSubstitutedVariables); + pluginProviders.push(...allProviders.filter(p => p.type === '*' && !!p.resolveDebugConfigurationWithSubstitutedVariables)); + + for (const provider of pluginProviders) { + try { + const next = await provider.resolveDebugConfigurationWithSubstitutedVariables(workspaceFolderUri, resolved); + if (next) { + resolved = next; + } else { + return resolved; } + } catch (e) { + console.error(e); } } diff --git a/packages/plugin-ext/src/plugin/node/debug/debug.ts b/packages/plugin-ext/src/plugin/node/debug/debug.ts index b1614ce5205bb..152e6f4bca588 100644 --- a/packages/plugin-ext/src/plugin/node/debug/debug.ts +++ b/packages/plugin-ext/src/plugin/node/debug/debug.ts @@ -34,21 +34,23 @@ import { PluginDebugAdapterTracker } from './plugin-debug-adapter-tracker'; import uuid = require('uuid'); import { DebugAdapter } from '@theia/debug/lib/node/debug-model'; -/* eslint-disable @typescript-eslint/no-explicit-any */ +interface ConfigurationProviderRecord { + handle: number; + type: string; + trigger: DebugConfigurationProviderTriggerKind, + provider: theia.DebugConfigurationProvider; +} +/* eslint-disable @typescript-eslint/no-explicit-any */ // TODO: rename file to `debug-ext.ts` - /** * It is supposed to work at node only. */ export class DebugExtImpl implements DebugExt { // debug sessions by sessionId private sessions = new Map(); - - // providers by type (initial) - private configurationProviders = new Map>(); - // providers by type (dynamic) - private dynamicConfigurationProviders = new Map>(); + private configurationProviderHandleGenerator: number; + private configurationProviders: ConfigurationProviderRecord[]; /** * Only use internally, don't send it to the frontend. It's expensive! @@ -85,6 +87,8 @@ export class DebugExtImpl implements DebugExt { append: (value: string) => this.proxy.$appendToDebugConsole(value), appendLine: (value: string) => this.proxy.$appendLineToDebugConsole(value) }; + this.configurationProviderHandleGenerator = 0; + this.configurationProviders = []; } /** @@ -191,24 +195,23 @@ export class DebugExtImpl implements DebugExt { }); } - registerDebugConfigurationProvider(debugType: string, provider: theia.DebugConfigurationProvider, trigger: theia.DebugConfigurationProviderTriggerKind): Disposable { + registerDebugConfigurationProvider(debugType: string, provider: theia.DebugConfigurationProvider, trigger: DebugConfigurationProviderTriggerKind): Disposable { console.log(`Debug configuration provider has been registered: ${debugType}, trigger: ${trigger}`); - const providersByTriggerKind = trigger === DebugConfigurationProviderTriggerKind.Initial ? this.configurationProviders : this.dynamicConfigurationProviders; - let providers = providersByTriggerKind.get(debugType); - if (!providers) { - providersByTriggerKind.set(debugType, providers = new Set()); - } - providers.add(provider); + const handle = this.configurationProviderHandleGenerator++; + this.configurationProviders.push({ handle, type: debugType, trigger, provider }); + const descriptor = { + handle, + type: debugType, + trigger, + provideDebugConfiguration: !!provider.provideDebugConfigurations, + resolveDebugConfigurations: !!provider.resolveDebugConfiguration, + resolveDebugConfigurationWithSubstitutedVariables: !!provider.resolveDebugConfigurationWithSubstitutedVariables + }; + this.proxy.$registerDebugConfigurationProvider(descriptor); return Disposable.create(() => { - // eslint-disable-next-line @typescript-eslint/no-shadow - const providers = providersByTriggerKind.get(debugType); - if (providers) { - providers.delete(provider); - if (providers.size === 0) { - providersByTriggerKind.delete(debugType); - } - } + this.configurationProviders = this.configurationProviders.filter(p => (p.handle !== handle)); + this.proxy.$unregisterDebugConfigurationProvider(handle); }); } @@ -331,15 +334,60 @@ export class DebugExtImpl implements DebugExt { return undefined; } + private getConfigurationProviderRecord(handle: number): { + provider: theia.DebugConfigurationProvider + type: string + } { + const record = this.configurationProviders.find(p => p.handle === handle); + if (!record) { + throw new Error('No Debug configuration provider found with given handle number: ' + handle); + } + const { provider, type } = record; + return { provider, type }; + } + + async $provideDebugConfigurationsByHandle(handle: number, workspaceFolderUri: string | undefined): Promise { + const { provider, type } = this.getConfigurationProviderRecord(handle); + + const configurations = await provider.provideDebugConfigurations?.(this.toWorkspaceFolder(workspaceFolderUri)); + if (!configurations) { + throw new Error('nothing returned from DebugConfigurationProvider.provideDebugConfigurations, type: ' + type); + } + + return configurations; + } + + async $resolveDebugConfigurationByHandle( + handle: number, + workspaceFolderUri: string | undefined, + debugConfiguration: theia.DebugConfiguration + ): Promise { + + const { provider } = this.getConfigurationProviderRecord(handle); + return provider.resolveDebugConfiguration?.(this.toWorkspaceFolder(workspaceFolderUri), debugConfiguration); + } + + async $resolveDebugConfigurationWithSubstitutedVariablesByHandle( + handle: number, + workspaceFolderUri: string | undefined, + debugConfiguration: theia.DebugConfiguration + ): Promise { + + const { provider } = this.getConfigurationProviderRecord(handle); + return provider.resolveDebugConfigurationWithSubstitutedVariables?.(this.toWorkspaceFolder(workspaceFolderUri), debugConfiguration); + } + async $provideDebugConfigurations(debugType: string, workspaceFolderUri: string | undefined, dynamic: boolean = false): Promise { let result: theia.DebugConfiguration[] = []; - const providers = dynamic ? this.dynamicConfigurationProviders.get(debugType) : this.configurationProviders.get(debugType); - if (providers) { - for (const provider of providers) { - if (provider.provideDebugConfigurations) { - result = result.concat(await provider.provideDebugConfigurations(this.toWorkspaceFolder(workspaceFolderUri)) || []); - } + const trigger = dynamic ? DebugConfigurationProviderTriggerKind.Dynamic : DebugConfigurationProviderTriggerKind.Initial; + const providers = this.configurationProviders + .filter(p => p.type === debugType && p.trigger === trigger) + .map(p => p.provider); + + for (const provider of providers) { + if (provider.provideDebugConfigurations) { + result = result.concat(await provider.provideDebugConfigurations(this.toWorkspaceFolder(workspaceFolderUri)) || []); } } @@ -349,25 +397,21 @@ export class DebugExtImpl implements DebugExt { async $resolveDebugConfigurations(debugConfiguration: theia.DebugConfiguration, workspaceFolderUri: string | undefined): Promise { let current = debugConfiguration; - for (const providers of [ - this.configurationProviders.get(debugConfiguration.type), - this.dynamicConfigurationProviders.get(debugConfiguration.type), - this.configurationProviders.get('*') - ]) { - if (providers) { - for (const provider of providers) { - if (provider.resolveDebugConfiguration) { - try { - const next = await provider.resolveDebugConfiguration(this.toWorkspaceFolder(workspaceFolderUri), current); - if (next) { - current = next; - } else { - return current; - } - } catch (e) { - console.error(e); - } + const providers = this.configurationProviders + .filter(p => p.type === debugConfiguration.type || p.type === '*') + .map(p => p.provider); + + for (const provider of providers) { + if (provider.resolveDebugConfiguration) { + try { + const next = await provider.resolveDebugConfiguration(this.toWorkspaceFolder(workspaceFolderUri), current); + if (next) { + current = next; + } else { + return current; } + } catch (e) { + console.error(e); } } } @@ -379,25 +423,21 @@ export class DebugExtImpl implements DebugExt { Promise { let current = debugConfiguration; - for (const providers of [ - this.configurationProviders.get(debugConfiguration.type), - this.dynamicConfigurationProviders.get(debugConfiguration.type), - this.configurationProviders.get('*') - ]) { - if (providers) { - for (const provider of providers) { - if (provider.resolveDebugConfigurationWithSubstitutedVariables) { - try { - const next = await provider.resolveDebugConfigurationWithSubstitutedVariables(this.toWorkspaceFolder(workspaceFolderUri), current); - if (next) { - current = next; - } else { - return current; - } - } catch (e) { - console.error(e); - } + const providers = this.configurationProviders + .filter(p => p.type === debugConfiguration.type || p.type === '*') + .map(p => p.provider); + + for (const provider of providers) { + if (provider.resolveDebugConfigurationWithSubstitutedVariables) { + try { + const next = await provider.resolveDebugConfigurationWithSubstitutedVariables(this.toWorkspaceFolder(workspaceFolderUri), current); + if (next) { + current = next; + } else { + return current; } + } catch (e) { + console.error(e); } } }