Skip to content

Commit fe66be1

Browse files
authored
Merge pull request microsoft#100794 from microsoft/connor4312/allow-multiple-dynamic
debug: allow a single debug extension to provide multiple configs
2 parents 48ce8ec + a1f0d54 commit fe66be1

1 file changed

Lines changed: 68 additions & 24 deletions

File tree

src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as nls from 'vs/nls';
7-
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
7+
import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
88
import { Event, Emitter } from 'vs/base/common/event';
99
import * as strings from 'vs/base/common/strings';
1010
import * as objects from 'vs/base/common/objects';
@@ -37,7 +37,7 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance
3737
import { withUndefinedAsNull } from 'vs/base/common/types';
3838
import { sequence } from 'vs/base/common/async';
3939
import { IHistoryService } from 'vs/workbench/services/history/common/history';
40-
import { first } from 'vs/base/common/arrays';
40+
import { first, flatten } from 'vs/base/common/arrays';
4141
import { getVisibleAndSorted } from 'vs/workbench/contrib/debug/common/debugUtils';
4242
import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes';
4343

@@ -47,6 +47,8 @@ jsonRegistry.registerSchema(launchSchemaId, launchSchema);
4747
const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname';
4848
const DEBUG_SELECTED_ROOT = 'debug.selectedroot';
4949

50+
interface IDynamicPickItem { label: string, launch: ILaunch, config: IConfig }
51+
5052
export class ConfigurationManager implements IConfigurationManager {
5153
private debuggers: Debugger[];
5254
private breakpointModeIdsSet = new Set<string>();
@@ -251,23 +253,60 @@ export class ConfigurationManager implements IConfigurationManager {
251253
async getDynamicProviders(): Promise<{ label: string, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[]> {
252254
const extensions = await this.extensionService.getExtensions();
253255
const onDebugDynamicConfigurationsName = 'onDebugDynamicConfigurations';
254-
const debugDynamicExtensionsTypes = extensions.map(e => {
255-
const activationEvent = e.activationEvents && e.activationEvents.find(e => e.includes(onDebugDynamicConfigurationsName));
256-
if (activationEvent) {
257-
const type = activationEvent.substr(onDebugDynamicConfigurationsName.length + 1);
258-
return type || (e.contributes && e.contributes.debuggers && e.contributes.debuggers.length ? e.contributes.debuggers[0].type : undefined);
256+
const debugDynamicExtensionsTypes = extensions.reduce((acc, e) => {
257+
if (!e.activationEvents) {
258+
return acc;
259259
}
260260

261-
return undefined;
262-
}).filter(type => typeof type === 'string' && !!this.getDebuggerLabel(type)) as string[];
261+
const explicitTypes: string[] = [];
262+
let hasGenericEvent = false;
263+
for (const event of e.activationEvents) {
264+
if (event === onDebugDynamicConfigurationsName) {
265+
hasGenericEvent = true;
266+
} else if (event.startsWith(`${onDebugDynamicConfigurationsName}:`)) {
267+
explicitTypes.push(event.slice(onDebugDynamicConfigurationsName.length + 1));
268+
}
269+
}
270+
271+
if (explicitTypes.length) {
272+
return acc.concat(explicitTypes);
273+
}
274+
275+
if (hasGenericEvent) {
276+
const debuggerType = e.contributes?.debuggers?.[0].type;
277+
return debuggerType ? acc.concat(debuggerType) : acc;
278+
}
279+
280+
return acc;
281+
}, [] as string[]);
263282

264283
return debugDynamicExtensionsTypes.map(type => {
265284
return {
266285
label: this.getDebuggerLabel(type)!,
267286
pick: async () => {
287+
const disposables = new DisposableStore();
288+
const input = disposables.add(this.quickInputService.createQuickPick<IDynamicPickItem>());
289+
input.busy = true;
290+
input.placeholder = nls.localize('selectConfiguration', "Select Launch Configuration");
291+
input.show();
292+
293+
let chosenDidCancel = false;
294+
const chosenPromise = new Promise<IDynamicPickItem | undefined>(resolve => {
295+
disposables.add(input.onDidAccept(() => resolve(input.activeItems[0])));
296+
disposables.add(input.onDidTriggerItemButton(async (context) => {
297+
resolve(undefined);
298+
const { launch, config } = context.item;
299+
await launch.openConfigFile(false, config.type);
300+
// Only Launch have a pin trigger button
301+
await (launch as Launch).writeConfiguration(config);
302+
this.selectConfiguration(launch, config.name);
303+
}));
304+
disposables.add(input.onDidHide(() => { chosenDidCancel = true; resolve(); }));
305+
});
306+
268307
await this.activateDebuggers(onDebugDynamicConfigurationsName, type);
269308
const token = new CancellationTokenSource();
270-
const picks: Promise<{ label: string, launch: ILaunch, config: IConfig }[]>[] = [];
309+
const picks: Promise<IDynamicPickItem[]>[] = [];
271310
const provider = this.configProviders.filter(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations)[0];
272311
this.getLaunches().forEach(launch => {
273312
if (launch.workspace && provider) {
@@ -282,25 +321,30 @@ export class ConfigurationManager implements IConfigurationManager {
282321
}))));
283322
}
284323
});
285-
const promiseOfPicks = Promise.all(picks).then(result => result.reduce((first, second) => first.concat(second), []));
286324

287-
const result = await this.quickInputService.pick<{ label: string, launch: ILaunch, config: IConfig }>(promiseOfPicks, {
288-
placeHolder: nls.localize('selectConfiguration', "Select Launch Configuration"),
289-
onDidTriggerItemButton: async (context) => {
290-
await this.quickInputService.cancel();
291-
const { launch, config } = context.item;
292-
await launch.openConfigFile(false, config.type);
293-
// Only Launch have a pin trigger button
294-
await (launch as Launch).writeConfiguration(config);
295-
this.selectConfiguration(launch, config.name);
296-
}
297-
});
298-
if (!result) {
325+
const nestedPicks = await Promise.all(picks);
326+
const items = flatten(nestedPicks);
327+
328+
let chosen: IDynamicPickItem | undefined;
329+
330+
// If there's exactly one item to choose from, pick it automatically
331+
if (items.length === 1 && !chosenDidCancel) {
332+
chosen = items[0];
333+
} else {
334+
input.items = items;
335+
input.busy = false;
336+
chosen = await chosenPromise;
337+
}
338+
339+
disposables.dispose();
340+
341+
if (!chosen) {
299342
// User canceled quick input we should notify the provider to cancel computing configurations
300343
token.cancel();
344+
return;
301345
}
302346

303-
return result;
347+
return chosen;
304348
}
305349
};
306350
});

0 commit comments

Comments
 (0)