Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

debug: cleanup welcome view actions #234446

Merged
merged 1 commit into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 45 additions & 21 deletions src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { IQuickInputService, IQuickPickItem } from '../../../../platform/quickin
import { Registry } from '../../../../platform/registry/common/platform.js';
import { IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js';
import { Breakpoints } from '../common/breakpoints.js';
import { CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_EXTENSION_AVAILABLE, IAdapterDescriptor, IAdapterManager, IConfig, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugAdapterFactory, IDebugConfiguration, IDebugSession, INTERNAL_CONSOLE_OPTIONS_SCHEMA } from '../common/debug.js';
import { CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_EXTENSION_AVAILABLE, IAdapterDescriptor, IAdapterManager, IConfig, IConfigurationManager, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugAdapterFactory, IDebugConfiguration, IDebugSession, IGuessedDebugger, INTERNAL_CONSOLE_OPTIONS_SCHEMA } from '../common/debug.js';
import { Debugger } from '../common/debugger.js';
import { breakpointsExtPoint, debuggersExtPoint, launchSchema, presentationSchema } from '../common/debugSchemas.js';
import { TaskDefinitionRegistry } from '../../tasks/common/taskDefinitionRegistry.js';
Expand All @@ -39,6 +39,7 @@ const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONC

export interface IAdapterManagerDelegate {
onDidNewSession: Event<IDebugSession>;
configurationManager(): IConfigurationManager;
}

export class AdapterManager extends Disposable implements IAdapterManager {
Expand All @@ -60,7 +61,7 @@ export class AdapterManager extends Disposable implements IAdapterManager {
private usedDebugTypes = new Set<string>();

constructor(
delegate: IAdapterManagerDelegate,
private readonly delegate: IAdapterManagerDelegate,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
Expand Down Expand Up @@ -340,7 +341,7 @@ export class AdapterManager extends Disposable implements IAdapterManager {
.find(a => a.interestedInLanguage(languageId));
}

async guessDebugger(gettingConfigurations: boolean): Promise<Debugger | undefined> {
async guessDebugger(gettingConfigurations: boolean): Promise<IGuessedDebugger | undefined> {
const activeTextEditorControl = this.editorService.activeTextEditorControl;
let candidates: Debugger[] = [];
let languageLabel: string | null = null;
Expand All @@ -355,7 +356,7 @@ export class AdapterManager extends Disposable implements IAdapterManager {
.filter(a => a.enabled)
.filter(a => language && a.interestedInLanguage(language));
if (adapters.length === 1) {
return adapters[0];
return { debugger: adapters[0] };
}
if (adapters.length > 1) {
candidates = adapters;
Expand Down Expand Up @@ -407,45 +408,68 @@ export class AdapterManager extends Disposable implements IAdapterManager {
}
});

const picks: ({ label: string; debugger?: Debugger; type?: string } | MenuItemAction)[] = [];
const picks: ({ label: string; pick?: () => IGuessedDebugger | Promise<IGuessedDebugger | undefined>; type?: string } | MenuItemAction)[] = [];
const dynamic = await this.delegate.configurationManager().getDynamicProviders();
if (suggestedCandidates.length > 0) {
picks.push(
{ type: 'separator', label: nls.localize('suggestedDebuggers', "Suggested") },
...suggestedCandidates.map(c => ({ label: c.label, debugger: c })));
...suggestedCandidates.map(c => ({ label: c.label, pick: () => ({ debugger: c }) })));
}

if (otherCandidates.length > 0) {
if (picks.length > 0) {
picks.push({ type: 'separator', label: '' });
}

picks.push(...otherCandidates.map(c => ({ label: c.label, debugger: c })));
picks.push(...otherCandidates.map(c => ({ label: c.label, pick: () => ({ debugger: c }) })));
}

if (dynamic.length) {
if (picks.length) {
picks.push({ type: 'separator', label: '' });
}

for (const d of dynamic) {
picks.push({
label: nls.localize('moreOptionsForDebugType', "More {0} options...", d.label),
pick: async (): Promise<IGuessedDebugger | undefined> => {
const cfg = await d.pick();
if (!cfg) { return undefined; }
return cfg && { debugger: this.getDebugger(d.type)!, withConfig: cfg };
},
});
}
}

picks.push(
{ type: 'separator', label: '' },
{ label: languageLabel ? nls.localize('installLanguage', "Install an extension for {0}...", languageLabel) : nls.localize('installExt', "Install extension...") });
{ label: languageLabel ? nls.localize('installLanguage', "Install an extension for {0}...", languageLabel) : nls.localize('installExt', "Install extension...") }
);

const contributed = this.menuService.getMenuActions(MenuId.DebugCreateConfiguration, this.contextKeyService);
for (const [, action] of contributed) {
for (const item of action) {
picks.push(item);
}
}

const placeHolder = nls.localize('selectDebug', "Select debugger");
return this.quickInputService.pick<{ label: string; debugger?: Debugger } | IQuickPickItem>(picks, { activeItem: picks[0], placeHolder })
.then(async picked => {
if (picked && 'debugger' in picked && picked.debugger) {
return picked.debugger;
} else if (picked instanceof MenuItemAction) {
picked.run();
return;
}
if (picked) {
this.commandService.executeCommand('debug.installAdditionalDebuggers', languageLabel);
}
return undefined;
});
return this.quickInputService.pick<{ label: string; debugger?: Debugger } | IQuickPickItem>(picks, { activeItem: picks[0], placeHolder }).then(async picked => {
if (picked && 'pick' in picked && typeof picked.pick === 'function') {
return await picked.pick();
}

if (picked instanceof MenuItemAction) {
picked.run();
return;
}

if (picked) {
this.commandService.executeCommand('debug.installAdditionalDebuggers', languageLabel);
}

return undefined;
});
}

private initExtensionActivationsIfNeeded(): void {
Expand Down
89 changes: 47 additions & 42 deletions src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uri
import { IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
import { IEditorPane } from '../../../common/editor.js';
import { debugConfigure } from './debugIcons.js';
import { CONTEXT_DEBUG_CONFIGURATION_TYPE, DebugConfigurationProviderTriggerKind, IAdapterManager, ICompound, IConfig, IConfigPresentation, IConfigurationManager, IDebugConfigurationProvider, IGlobalConfig, ILaunch } from '../common/debug.js';
import { CONTEXT_DEBUG_CONFIGURATION_TYPE, DebugConfigurationProviderTriggerKind, IAdapterManager, ICompound, IConfig, IConfigPresentation, IConfigurationManager, IDebugConfigurationProvider, IGlobalConfig, IGuessedDebugger, ILaunch } from '../common/debug.js';
import { launchSchema } from '../common/debugSchemas.js';
import { getVisibleAndSorted } from '../common/debugUtils.js';
import { launchSchemaId } from '../../../services/configuration/common/configuration.js';
Expand All @@ -46,6 +46,7 @@ const DEBUG_SELECTED_ROOT = 'debug.selectedroot';
// Debug type is only stored if a dynamic configuration is used for better restore
const DEBUG_SELECTED_TYPE = 'debug.selectedtype';
const DEBUG_RECENT_DYNAMIC_CONFIGURATIONS = 'debug.recentdynamicconfigurations';
const ON_DEBUG_DYNAMIC_CONFIGURATIONS_NAME = 'onDebugDynamicConfigurations';

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

Expand Down Expand Up @@ -174,9 +175,8 @@ export class ConfigurationManager implements IConfigurationManager {
return results.reduce((first, second) => first.concat(second), []);
}

async getDynamicProviders(): Promise<{ label: string; type: string; getProvider: () => Promise<IDebugConfigurationProvider | undefined>; pick: () => Promise<{ launch: ILaunch; config: IConfig } | undefined> }[]> {
async getDynamicProviders(): Promise<{ label: string; type: string; getProvider: () => Promise<IDebugConfigurationProvider | undefined>; pick: () => Promise<{ launch: ILaunch; config: IConfig; label: string } | undefined> }[]> {
await this.extensionService.whenInstalledExtensionsRegistered();
const onDebugDynamicConfigurationsName = 'onDebugDynamicConfigurations';
const debugDynamicExtensionsTypes = this.extensionService.extensions.reduce((acc, e) => {
if (!e.activationEvents) {
return acc;
Expand All @@ -185,10 +185,10 @@ export class ConfigurationManager implements IConfigurationManager {
const explicitTypes: string[] = [];
let hasGenericEvent = false;
for (const event of e.activationEvents) {
if (event === onDebugDynamicConfigurationsName) {
if (event === ON_DEBUG_DYNAMIC_CONFIGURATIONS_NAME) {
hasGenericEvent = true;
} else if (event.startsWith(`${onDebugDynamicConfigurationsName}:`)) {
explicitTypes.push(event.slice(onDebugDynamicConfigurationsName.length + 1));
} else if (event.startsWith(`${ON_DEBUG_DYNAMIC_CONFIGURATIONS_NAME}:`)) {
explicitTypes.push(event.slice(ON_DEBUG_DYNAMIC_CONFIGURATIONS_NAME.length + 1));
}
}

Expand All @@ -214,33 +214,17 @@ export class ConfigurationManager implements IConfigurationManager {
return {
label: this.adapterManager.getDebuggerLabel(type)!,
getProvider: async () => {
await this.adapterManager.activateDebuggers(onDebugDynamicConfigurationsName, type);
await this.adapterManager.activateDebuggers(ON_DEBUG_DYNAMIC_CONFIGURATIONS_NAME, type);
return this.configProviders.find(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations);
},
type,
pick: async () => {
// Do a late 'onDebugDynamicConfigurationsName' activation so extensions are not activated too early #108578
await this.adapterManager.activateDebuggers(onDebugDynamicConfigurationsName, type);

const token = new CancellationTokenSource();
const picks: Promise<IDynamicPickItem[]>[] = [];
const provider = this.configProviders.find(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations);
this.getLaunches().forEach(launch => {
if (provider) {
picks.push(provider.provideDebugConfigurations!(launch.workspace?.uri, token.token).then(configurations => configurations.map(config => ({
label: config.name,
description: launch.name,
config,
buttons: [{
iconClass: ThemeIcon.asClassName(debugConfigure),
tooltip: nls.localize('editLaunchConfig', "Edit Debug Configuration in launch.json")
}],
launch
}))));
}
});
await this.adapterManager.activateDebuggers(ON_DEBUG_DYNAMIC_CONFIGURATIONS_NAME, type);

const disposables = new DisposableStore();
const token = new CancellationTokenSource();
disposables.add(token);
const input = disposables.add(this.quickInputService.createQuickPick<IDynamicPickItem>());
input.busy = true;
input.placeholder = nls.localize('selectConfiguration', "Select Launch Configuration");
Expand All @@ -257,41 +241,56 @@ export class ConfigurationManager implements IConfigurationManager {
this.removeRecentDynamicConfigurations(config.name, config.type);
}));
disposables.add(input.onDidHide(() => resolve(undefined)));
});
}).finally(() => token.cancel());

let nestedPicks: IDynamicPickItem[][];
let items: IDynamicPickItem[];
try {
// This await invokes the extension providers, which might fail due to several reasons,
// therefore we gate this logic under a try/catch to prevent leaving the Debug Tab
// selector in a borked state.
nestedPicks = await Promise.all(picks);
items = await this.getDynamicConfigurationsByType(type, token.token);
} catch (err) {
this.logService.error(err);
disposables.dispose();
return;
}

const items = nestedPicks.flat();

input.items = items;
input.busy = false;
input.show();
const chosen = await chosenPromise;

disposables.dispose();

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

return chosen;
}
};
});
}

async getDynamicConfigurationsByType(type: string, token: CancellationToken = CancellationToken.None): Promise<IDynamicPickItem[]> {
// Do a late 'onDebugDynamicConfigurationsName' activation so extensions are not activated too early #108578
await this.adapterManager.activateDebuggers(ON_DEBUG_DYNAMIC_CONFIGURATIONS_NAME, type);

const picks: Promise<IDynamicPickItem[]>[] = [];
const provider = this.configProviders.find(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations);
this.getLaunches().forEach(launch => {
if (provider) {
picks.push(provider.provideDebugConfigurations!(launch.workspace?.uri, token).then(configurations => configurations.map(config => ({
label: config.name,
description: launch.name,
config,
buttons: [{
iconClass: ThemeIcon.asClassName(debugConfigure),
tooltip: nls.localize('editLaunchConfig', "Edit Debug Configuration in launch.json")
}],
launch
}))));
}
});

return (await Promise.all(picks)).flat();
}

getAllConfigurations(): { launch: ILaunch; name: string; presentation?: IConfigPresentation }[] {
const all: { launch: ILaunch; name: string; presentation?: IConfigPresentation }[] = [];
for (const l of this.launches) {
Expand Down Expand Up @@ -560,13 +559,19 @@ abstract class AbstractLaunch implements ILaunch {

async getInitialConfigurationContent(folderUri?: uri, type?: string, useInitialConfigs?: boolean, token?: CancellationToken): Promise<string> {
let content = '';
const adapter = type ? this.adapterManager.getEnabledDebugger(type) : await this.adapterManager.guessDebugger(true);
if (adapter) {
const adapter: Partial<IGuessedDebugger> | undefined = type
? { debugger: this.adapterManager.getEnabledDebugger(type) }
: await this.adapterManager.guessDebugger(true);

if (adapter?.withConfig && adapter.debugger) {
content = await adapter.debugger.getInitialConfigurationContent([adapter.withConfig.config]);
} else if (adapter?.debugger) {
const initialConfigs = useInitialConfigs ?
await this.configurationManager.provideDebugConfigurations(folderUri, adapter.type, token || CancellationToken.None) :
await this.configurationManager.provideDebugConfigurations(folderUri, adapter.debugger.type, token || CancellationToken.None) :
[];
content = await adapter.getInitialConfigurationContent(initialConfigs);
content = await adapter.debugger.getInitialConfigurationContent(initialConfigs);
}

return content;
}

Expand Down
Loading
Loading