Support taskVar variables from problem matcher named capture groups

Fixes #303361

Introduces \\\ variables that extract named capture groups
from a task problem matcher's endsPattern and make them available for
substitution in launch configurations and other variable-resolved contexts.

Changes:
- WatchingProblemCollector.tryFinish() extracts matches.groups from endsPattern
- IProblemCollectorEvent carries capturedVariables in BackgroundProcessingEnds
- TerminalTaskSystem registers captured variables via contributeVariable()
- VariableKind.TaskVar added to the enum
- Test added for taskVar variable resolution
- .vscode/launch.json updated to use \
This commit is contained in:
Henning Dieterichs
2026-03-20 01:56:36 +01:00
committed by Henning Dieterichs
parent e898258da3
commit 5e748c905d
6 changed files with 36 additions and 6 deletions

4
.vscode/launch.json vendored
View File

@@ -660,7 +660,7 @@
"name": "Component Explorer (Edge)",
"type": "msedge",
"request": "launch",
"url": "http://localhost:5337/___explorer",
"url": "${taskVar:componentExplorerUrl}",
"preLaunchTask": "Launch Component Explorer",
"presentation": {
"group": "1_component_explorer",
@@ -671,7 +671,7 @@
"name": "Component Explorer (Chrome)",
"type": "chrome",
"request": "launch",
"url": "http://localhost:5337/___explorer",
"url": "${taskVar:componentExplorerUrl}",
"preLaunchTask": "Launch Component Explorer",
"presentation": {
"group": "1_component_explorer",

2
.vscode/tasks.json vendored
View File

@@ -410,7 +410,7 @@
"background": {
"activeOnStart": true,
"beginsPattern": ".*Setting up sessions.*",
"endsPattern": "Redirection server listening on.*"
"endsPattern": " current: (?<componentExplorerUrl>.*) \\(current\\)"
}
}
},

View File

@@ -177,6 +177,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
private _terminalTabActions = [{ id: RerunForActiveTerminalCommandId, label: nls.localize('rerunTask', 'Rerun Task'), icon: rerunTaskIcon }];
private _taskTerminalActive: IContextKey<boolean>;
private readonly _taskStartTimes = new Map<number, number>();
private readonly _capturedTaskVariables = new Map<string, string>();
taskShellIntegrationStartSequence(cwd: string | URI | undefined): string {
return (
@@ -898,6 +899,9 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
if (this._busyTasks[mapKey]) {
delete this._busyTasks[mapKey];
}
if (event.capturedVariables) {
this._registerCapturedVariables(event.capturedVariables);
}
this._fireTaskEvent(TaskEvent.inactive(task, terminal?.instanceId, this._takeTaskDuration(terminal?.instanceId)));
if (eventCounter === 0) {
if ((watchingProblemMatcher.numberOfMatches > 0) && watchingProblemMatcher.maxMarkerSeverity &&
@@ -1170,6 +1174,15 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
return Date.now() - startTime;
}
private _registerCapturedVariables(capturedVariables: ReadonlyMap<string, string>): void {
for (const [name, value] of capturedVariables) {
this._capturedTaskVariables.set(name, value);
if (!this._configurationResolverService.resolvableVariables.has(`taskVar:${name}`)) {
this._configurationResolverService.contributeVariable(`taskVar:${name}`, async () => this._capturedTaskVariables.get(name));
}
}
}
private _createTerminalName(task: CustomTask | ContributedTask): string {
const needsFolderQualification = this._contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;
return needsFolderQualification ? task.getQualifiedLabel() : (task.configurationProperties.name || '');

View File

@@ -24,11 +24,12 @@ export const enum ProblemCollectorEventKind {
export interface IProblemCollectorEvent {
kind: ProblemCollectorEventKind;
capturedVariables?: ReadonlyMap<string, string>;
}
namespace IProblemCollectorEvent {
export function create(kind: ProblemCollectorEventKind) {
return Object.freeze({ kind });
export function create(kind: ProblemCollectorEventKind, capturedVariables?: ReadonlyMap<string, string>) {
return Object.freeze({ kind, capturedVariables });
}
}
@@ -563,7 +564,8 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement
}
if (this._activeBackgroundMatchers.delete(background.key)) {
this.resetCurrentResource();
this._onDidStateChange.fire(IProblemCollectorEvent.create(ProblemCollectorEventKind.BackgroundProcessingEnds));
const capturedVariables = matches.groups ? new Map(Object.entries(matches.groups)) : undefined;
this._onDidStateChange.fire(IProblemCollectorEvent.create(ProblemCollectorEventKind.BackgroundProcessingEnds, capturedVariables));
result = true;
this.lines.push(line);
const owner = background.matcher.owner;

View File

@@ -82,6 +82,7 @@ export enum VariableKind {
Command = 'command',
Input = 'input',
ExtensionInstallFolder = 'extensionInstallFolder',
TaskVar = 'taskVar',
WorkspaceFolder = 'workspaceFolder',
Cwd = 'cwd',

View File

@@ -706,6 +706,20 @@ suite('Configuration Resolver Service', () => {
});
});
test('contributed taskVar variable', () => {
const url = 'http://localhost:5678';
const variable = 'taskVar:componentExplorerUrl';
const configuration = {
'url': '${taskVar:componentExplorerUrl}/___explorer',
};
configurationResolverService!.contributeVariable(variable, async () => { return url; });
return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration).then(result => {
assert.deepStrictEqual({ ...result }, {
'url': `${url}/___explorer`
});
});
});
test('resolveWithEnvironment', async () => {
const env = {
'VAR_1': 'VAL_1',