Skip to content

Commit

Permalink
debug: allow a single debug extension to provide multiple configs
Browse files Browse the repository at this point in the history
Previously if a debug extension provided multiple dynamic
configurations, we would just use the first debugger -- whatever that
was. This change now shows all configurations for which dynamic configs
are registered.

I also adjusted the picker to automatically select the first item if
there's only a single configuration provided. This works well for the
debug terminal, but also means that the user doesn't see the name of
the selected item, which might not be desirable. Open to pushback.

Together these finish the request for a separate top-level contribution
for the terminal in  #98054

Finally, with that adjustment I made a tweak so that the picker shows
up in a `busy` state while extensions are activating. Previously you
would select a dynamic configuration title and could have a few seconds
of delay before the picker came up, which is probably not desirable.
  • Loading branch information
connor4312 committed Jun 22, 2020
1 parent f2a654c commit 3a9d44e
Showing 1 changed file with 51 additions and 22 deletions.
73 changes: 51 additions & 22 deletions src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ jsonRegistry.registerSchema(launchSchemaId, launchSchema);
const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname';
const DEBUG_SELECTED_ROOT = 'debug.selectedroot';

interface IDynamicPickItem { label: string, launch: ILaunch, config: IConfig }

export class ConfigurationManager implements IConfigurationManager {
private debuggers: Debugger[];
private breakpointModeIdsSet = new Set<string>();
Expand Down Expand Up @@ -251,23 +253,50 @@ export class ConfigurationManager implements IConfigurationManager {
async getDynamicProviders(): Promise<{ label: string, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[]> {
const extensions = await this.extensionService.getExtensions();
const onDebugDynamicConfigurationsName = 'onDebugDynamicConfigurations';
const debugDynamicExtensionsTypes = extensions.map(e => {
const activationEvent = e.activationEvents && e.activationEvents.find(e => e.includes(onDebugDynamicConfigurationsName));
if (activationEvent) {
const type = activationEvent.substr(onDebugDynamicConfigurationsName.length + 1);
return type || (e.contributes && e.contributes.debuggers && e.contributes.debuggers.length ? e.contributes.debuggers[0].type : undefined);
const debugDynamicExtensionsTypes = extensions.reduce((acc, e) => {
if (!e.activationEvents) {
return acc;
}

return undefined;
}).filter(type => typeof type === 'string' && !!this.getDebuggerLabel(type)) as string[];
const explicitTypes = e.activationEvents.filter(e => e.includes(`${onDebugDynamicConfigurationsName}:`)).map(type => type.slice(onDebugDynamicConfigurationsName.length + 1));
if (explicitTypes.length) {
return [...acc, ...explicitTypes];
}

if (e.activationEvents.includes(onDebugDynamicConfigurationsName)) {
const debuggerType = e.contributes?.debuggers?.[0].type;
return debuggerType ? [...acc, debuggerType] : acc;
}

return acc;
}, [] as string[]);

return debugDynamicExtensionsTypes.map(type => {
return {
label: this.getDebuggerLabel(type)!,
pick: async () => {
const input = this.quickInputService.createQuickPick<IDynamicPickItem>();
input.busy = true;
input.placeholder = nls.localize('selectConfiguration', "Select Launch Configuration");
input.show();

let chosenDidCancel = false;
const chosenPromise = new Promise<IDynamicPickItem | undefined>(resolve => {
input.onDidAccept(() => resolve(input.activeItems[0]));
input.onDidTriggerItemButton(async (context) => {
resolve(undefined);
const { launch, config } = context.item;
await launch.openConfigFile(false, config.type);
// Only Launch have a pin trigger button
await (launch as Launch).writeConfiguration(config);
this.selectConfiguration(launch, config.name);
});
input.onDidHide(() => { chosenDidCancel = true; resolve(); });
});

await this.activateDebuggers(onDebugDynamicConfigurationsName, type);
const token = new CancellationTokenSource();
const picks: Promise<{ label: string, launch: ILaunch, config: IConfig }[]>[] = [];
const picks: Promise<IDynamicPickItem[]>[] = [];
const provider = this.configProviders.filter(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations)[0];
this.getLaunches().forEach(launch => {
if (launch.workspace && provider) {
Expand All @@ -282,25 +311,25 @@ export class ConfigurationManager implements IConfigurationManager {
}))));
}
});
const promiseOfPicks = Promise.all(picks).then(result => result.reduce((first, second) => first.concat(second), []));

const result = await this.quickInputService.pick<{ label: string, launch: ILaunch, config: IConfig }>(promiseOfPicks, {
placeHolder: nls.localize('selectConfiguration', "Select Launch Configuration"),
onDidTriggerItemButton: async (context) => {
await this.quickInputService.cancel();
const { launch, config } = context.item;
await launch.openConfigFile(false, config.type);
// Only Launch have a pin trigger button
await (launch as Launch).writeConfiguration(config);
this.selectConfiguration(launch, config.name);
}
});
if (!result) {
const items = await Promise.all(picks).then(result => result.reduce((first, second) => first.concat(second), []));
let chosen: IDynamicPickItem | undefined;
if (items.length === 1 && !chosenDidCancel) {
chosen = items[0];
} else {
input.items = items;
input.busy = false;
chosen = await chosenPromise;
}

input.dispose();
if (!chosen) {
// User canceled quick input we should notify the provider to cancel computing configurations
token.cancel();
return;
}

return result;
return chosen;
}
};
});
Expand Down

0 comments on commit 3a9d44e

Please sign in to comment.