diff --git a/packages/debug/src/browser/debug-session-manager.ts b/packages/debug/src/browser/debug-session-manager.ts index 47c0ec46eaf04..54da78df11546 100644 --- a/packages/debug/src/browser/debug-session-manager.ts +++ b/packages/debug/src/browser/debug-session-manager.ts @@ -206,11 +206,12 @@ export class DebugSessionManager { return options; } const { workspaceFolderUri } = options; - const resolvedConfiguration = await this.resolveDebugConfiguration(options.configuration, workspaceFolderUri); - const configuration = await this.variableResolver.resolve(resolvedConfiguration, { + let configuration = await this.resolveDebugConfiguration(options.configuration, workspaceFolderUri); + configuration = await this.variableResolver.resolve(configuration, { context: options.workspaceFolderUri ? new URI(options.workspaceFolderUri) : undefined, configurationSection: 'launch' }); + configuration = await this.resolveDebugConfigurationWithSubstitutedVariables(configuration, workspaceFolderUri); const key = configuration.name + workspaceFolderUri; const id = this.configurationIds.has(key) ? this.configurationIds.get(key)! + 1 : 0; this.configurationIds.set(key, id); @@ -229,6 +230,10 @@ export class DebugSessionManager { await WaitUntilEvent.fire(this.onWillResolveDebugConfigurationEmitter, { debugType }); } + protected async resolveDebugConfigurationWithSubstitutedVariables(configuration: DebugConfiguration, workspaceFolderUri: string | undefined): Promise { + return this.debug.resolveDebugConfigurationWithSubstitutedVariables(configuration, workspaceFolderUri); + } + protected async doStart(sessionId: string, options: DebugSessionOptions): Promise { const contrib = this.sessionContributionRegistry.get(options.configuration.type); const sessionFactory = contrib ? contrib.debugSessionFactory() : this.debugSessionFactory; diff --git a/packages/debug/src/common/debug-model.ts b/packages/debug/src/common/debug-model.ts index 42773917ef96f..ad2880f07f00e 100644 --- a/packages/debug/src/common/debug-model.ts +++ b/packages/debug/src/common/debug-model.ts @@ -163,9 +163,17 @@ export interface DebugAdapterContribution { /** * Resolves a [debug configuration](#DebugConfiguration) by filling in missing values - * or by adding/changing/removing attributes. + * or by adding/changing/removing attributes before variable substitution. * @param config The [debug configuration](#DebugConfiguration) to resolve. * @returns The resolved debug configuration. */ resolveDebugConfiguration?(config: DebugConfiguration, workspaceFolderUri?: string): MaybePromise; + + /** + * Resolves a [debug configuration](#DebugConfiguration) by filling in missing values + * or by adding/changing/removing attributes with substituted variables. + * @param config The [debug configuration](#DebugConfiguration) to resolve. + * @returns The resolved debug configuration. + */ + resolveDebugConfigurationWithSubstitutedVariables?(config: DebugConfiguration, workspaceFolderUri?: string): MaybePromise; } diff --git a/packages/debug/src/common/debug-service.ts b/packages/debug/src/common/debug-service.ts index c993dc4fb826e..6f51f3a4b8984 100644 --- a/packages/debug/src/common/debug-service.ts +++ b/packages/debug/src/common/debug-service.ts @@ -72,12 +72,20 @@ export interface DebugService extends Disposable { /** * Resolves a [debug configuration](#DebugConfiguration) by filling in missing values - * or by adding/changing/removing attributes. + * or by adding/changing/removing attributes before variable substitution. * @param debugConfiguration The [debug configuration](#DebugConfiguration) to resolve. * @returns The resolved debug configuration. */ resolveDebugConfiguration(config: DebugConfiguration, workspaceFolderUri: string | undefined): Promise; + /** + * Resolves a [debug configuration](#DebugConfiguration) by filling in missing values + * or by adding/changing/removing attributes with substituted variables. + * @param debugConfiguration The [debug configuration](#DebugConfiguration) to resolve. + * @returns The resolved debug configuration. + */ + resolveDebugConfigurationWithSubstitutedVariables(config: DebugConfiguration, workspaceFolderUri: string | undefined): Promise; + /** * Creates a new [debug adapter session](#DebugAdapterSession). * @param config The resolved [debug configuration](#DebugConfiguration). diff --git a/packages/debug/src/node/debug-adapter-contribution-registry.ts b/packages/debug/src/node/debug-adapter-contribution-registry.ts index bd9a30538e667..21f6cda3a59e4 100644 --- a/packages/debug/src/node/debug-adapter-contribution-registry.ts +++ b/packages/debug/src/node/debug-adapter-contribution-registry.ts @@ -92,7 +92,7 @@ export class DebugAdapterContributionRegistry { /** * Resolves a [debug configuration](#DebugConfiguration) by filling in missing values - * or by adding/changing/removing attributes. + * or by adding/changing/removing attributes before variable substitution. * @param debugConfiguration The [debug configuration](#DebugConfiguration) to resolve. * @returns The resolved debug configuration. */ @@ -115,6 +115,31 @@ export class DebugAdapterContributionRegistry { return current; } + /** + * Resolves a [debug configuration](#DebugConfiguration) by filling in missing values + * or by adding/changing/removing attributes with substituted variables. + * @param debugConfiguration The [debug configuration](#DebugConfiguration) to resolve. + * @returns The resolved debug configuration. + */ + async resolveDebugConfigurationWithSubstitutedVariables(config: DebugConfiguration, workspaceFolderUri?: string): Promise { + let current = config; + for (const contribution of this.getContributions(config.type)) { + if (contribution.resolveDebugConfigurationWithSubstitutedVariables) { + try { + const next = await contribution.resolveDebugConfigurationWithSubstitutedVariables(config, workspaceFolderUri); + if (next) { + current = next; + } else { + return current; + } + } catch (e) { + console.error(e); + } + } + } + return current; + } + /** * Provides schema attributes. * @param debugType The registered debug type diff --git a/packages/debug/src/node/debug-service-impl.ts b/packages/debug/src/node/debug-service-impl.ts index 001b9e9bfb723..dcaf0fd3e7918 100644 --- a/packages/debug/src/node/debug-service-impl.ts +++ b/packages/debug/src/node/debug-service-impl.ts @@ -60,6 +60,9 @@ export class DebugServiceImpl implements DebugService { async resolveDebugConfiguration(config: DebugConfiguration, workspaceFolderUri?: string): Promise { return this.registry.resolveDebugConfiguration(config, workspaceFolderUri); } + async resolveDebugConfigurationWithSubstitutedVariables(config: DebugConfiguration, workspaceFolderUri?: string): Promise { + return this.registry.resolveDebugConfigurationWithSubstitutedVariables(config, workspaceFolderUri); + } protected readonly sessions = new Set(); async createDebugSession(config: DebugConfiguration): Promise { diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index fcfdaeec87f66..350be201ce1b5 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -361,8 +361,8 @@ export interface StatusBarMessageRegistryMain { alignment: theia.StatusBarAlignment, color: string | undefined, tooltip: string | undefined, - command: string | undefined): PromiseLike; - $update(id: string, message: string): void; + command: string | undefined, + args: any[] | undefined): PromiseLike; $dispose(id: string): void; } @@ -1366,6 +1366,8 @@ export interface DebugExt { $sessionDidChange(sessionId: string | undefined): void; $provideDebugConfigurations(debugType: string, workspaceFolder: string | undefined): Promise; $resolveDebugConfigurations(debugConfiguration: theia.DebugConfiguration, workspaceFolder: string | undefined): Promise; + $resolveDebugConfigurationWithSubstitutedVariables(debugConfiguration: theia.DebugConfiguration, workspaceFolder: string | undefined): + Promise; $createDebugSession(debugConfiguration: theia.DebugConfiguration): Promise; $terminateDebugSession(sessionId: string): Promise; $getTerminalCreationOptions(debugType: string): Promise; 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 98fbe0ce8c2ea..a7a866fc88761 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 @@ -45,6 +45,10 @@ export class PluginDebugAdapterContribution { return this.debugExt.$resolveDebugConfigurations(config, workspaceFolderUri); } + async resolveDebugConfigurationWithSubstitutedVariables(config: DebugConfiguration, workspaceFolderUri: string | undefined): Promise { + return this.debugExt.$resolveDebugConfigurationWithSubstitutedVariables(config, workspaceFolderUri); + } + async createDebugSession(config: DebugConfiguration): Promise { await this.pluginService.activateByDebug('onDebugAdapterProtocolTracker', config.type); return this.debugExt.$createDebugSession(config); 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 6cabe2a706d6c..5c989a97e3ff8 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 @@ -132,6 +132,28 @@ export class PluginDebugService implements DebugService, PluginDebugAdapterContr return this.delegated.resolveDebugConfiguration(resolved, workspaceFolderUri); } + 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); + } + } + } + + return this.delegated.resolveDebugConfigurationWithSubstitutedVariables(resolved, workspaceFolderUri); + } + registerDebugger(contribution: DebuggerContribution): Disposable { this.debuggers.push(contribution); return Disposable.create(() => { diff --git a/packages/plugin-ext/src/main/browser/status-bar-message-registry-main.ts b/packages/plugin-ext/src/main/browser/status-bar-message-registry-main.ts index 1c7509ddde1fd..634e5887c591c 100644 --- a/packages/plugin-ext/src/main/browser/status-bar-message-registry-main.ts +++ b/packages/plugin-ext/src/main/browser/status-bar-message-registry-main.ts @@ -44,14 +44,17 @@ export class StatusBarMessageRegistryMainImpl implements StatusBarMessageRegistr alignment: number, color: string | undefined, tooltip: string | undefined, - command: string | undefined): Promise { + command: string | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + args: any[] | undefined): Promise { const entry = { text: text || '', priority, alignment: alignment === types.StatusBarAlignment.Left ? StatusBarAlignment.LEFT : StatusBarAlignment.RIGHT, color: color && (this.colorRegistry.getCurrentColor(color) || color), tooltip, - command + command, + args }; this.entries.set(id, entry); @@ -63,14 +66,6 @@ export class StatusBarMessageRegistryMainImpl implements StatusBarMessageRegistr } } - $update(id: string, message: string): void { - const entry = this.entries.get(id); - if (entry) { - entry.text = message; - this.delegate.setElement(id, entry); - } - } - $dispose(id: string): void { const entry = this.entries.get(id); if (entry) { diff --git a/packages/plugin-ext/src/plugin/node/debug/debug.ts b/packages/plugin-ext/src/plugin/node/debug/debug.ts index 72ba42f6a9de7..00bb2c9b12b96 100644 --- a/packages/plugin-ext/src/plugin/node/debug/debug.ts +++ b/packages/plugin-ext/src/plugin/node/debug/debug.ts @@ -353,6 +353,32 @@ export class DebugExtImpl implements DebugExt { return current; } + async $resolveDebugConfigurationWithSubstitutedVariables(debugConfiguration: theia.DebugConfiguration, workspaceFolderUri: string | undefined): + Promise { + let current = debugConfiguration; + + for (const providers of [this.configurationProviders.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); + } + } + } + } + } + + return current; + } + protected async createDebugAdapterTracker(session: theia.DebugSession): Promise { return PluginDebugAdapterTracker.create(session, this.trackerFactories); } diff --git a/packages/plugin-ext/src/plugin/status-bar/status-bar-item.ts b/packages/plugin-ext/src/plugin/status-bar/status-bar-item.ts index c1a581fd95c32..3ef9cfa71fea2 100644 --- a/packages/plugin-ext/src/plugin/status-bar/status-bar-item.ts +++ b/packages/plugin-ext/src/plugin/status-bar/status-bar-item.ts @@ -28,7 +28,7 @@ export class StatusBarItemImpl implements theia.StatusBarItem { private _text: string; private _tooltip: string; private _color: string | ThemeColor; - private _command: string; + private _command: string | theia.Command; private _isVisible: boolean; private _timeoutHandle: NodeJS.Timer | undefined; @@ -63,7 +63,7 @@ export class StatusBarItemImpl implements theia.StatusBarItem { return this._color; } - public get command(): string { + public get command(): string | theia.Command { return this._command; } @@ -82,7 +82,7 @@ export class StatusBarItemImpl implements theia.StatusBarItem { this.update(); } - public set command(command: string) { + public set command(command: string | theia.Command) { this._command = command; this.update(); } @@ -111,13 +111,16 @@ export class StatusBarItemImpl implements theia.StatusBarItem { this._timeoutHandle = setTimeout(() => { this._timeoutHandle = undefined; + const commandId = typeof this.command === 'object' ? this.command.command : this.command; + const args = typeof this.command === 'object' ? this.command.arguments : undefined; // Set to status bar this._proxy.$setMessage(this.id, this.text, this.priority, this.alignment, typeof this.color === 'string' ? this.color : this.color && this.color.id, this.tooltip, - this.command); + commandId, + args); }, 0); } diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index 1cf32c334dbcf..a59f916945cfc 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -2366,7 +2366,7 @@ declare module '@theia/plugin' { /** * The identifier of a command to run on click. */ - command: string | undefined; + command: string | Command | undefined; /** * Shows the entry in the status bar. @@ -8183,6 +8183,21 @@ declare module '@theia/plugin' { * @return The resolved debug configuration or undefined or null. */ resolveDebugConfiguration?(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult; + + /** + * This hook is directly called after 'resolveDebugConfiguration' but with all variables substituted. + * It can be used to resolve or verify a [debug configuration](#DebugConfiguration) by filling in missing values or by adding/changing/removing attributes. + * If more than one debug configuration provider is registered for the same type, the 'resolveDebugConfigurationWithSubstitutedVariables' calls are chained + * in arbitrary order and the initial debug configuration is piped through the chain. + * Returning the value 'undefined' prevents the debug session from starting. + * Returning the value 'null' prevents the debug session from starting and opens the underlying debug configuration instead. + * + * @param folder The workspace folder from which the configuration originates from or `undefined` for a folderless setup. + * @param debugConfiguration The [debug configuration](#DebugConfiguration) to resolve. + * @param token A cancellation token. + * @return The resolved debug configuration or undefined or null. + */ + resolveDebugConfigurationWithSubstitutedVariables?(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult; } /**