From cd68ca16e432fc9529be189209d7453ee0b5a2c5 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 22 Nov 2024 11:32:03 -0800 Subject: [PATCH] debug: cleanup welcome view actions Some consolidation especially now that copilot is also going to be having a welcome view contribution: - Show dynamic configurations in "Run and Debug" as "More X options..." - Remove the separate action for "Show all automatic debug configurations" - Make the "create a launch.json file" start adding a configuration as well (positioning the cursor and triggering completion) - Make "Run and Debug"'s memoization be able to remember dynamic configs ![](https://memes.peet.io/img/24-11-6171dc57-fd60-4165-bcb6-d156bb0517cc.png) fyi @roblourens --- .../debug/browser/debugAdapterManager.ts | 66 +++++++++----- .../browser/debugConfigurationManager.ts | 89 ++++++++++--------- .../contrib/debug/browser/debugService.ts | 74 +++++++++------ .../contrib/debug/browser/debugViewlet.ts | 13 ++- .../contrib/debug/browser/welcomeView.ts | 47 +++++----- .../workbench/contrib/debug/common/debug.ts | 14 ++- .../contrib/debug/common/debugStorage.ts | 14 ++- 7 files changed, 191 insertions(+), 126 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts index 20f77ec9f17af..0a130630a4000 100644 --- a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -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'; @@ -39,6 +39,7 @@ const jsonRegistry = Registry.as(JSONExtensions.JSONC export interface IAdapterManagerDelegate { onDidNewSession: Event; + configurationManager(): IConfigurationManager; } export class AdapterManager extends Disposable implements IAdapterManager { @@ -60,7 +61,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { private usedDebugTypes = new Set(); constructor( - delegate: IAdapterManagerDelegate, + private readonly delegate: IAdapterManagerDelegate, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @@ -340,7 +341,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { .find(a => a.interestedInLanguage(languageId)); } - async guessDebugger(gettingConfigurations: boolean): Promise { + async guessDebugger(gettingConfigurations: boolean): Promise { const activeTextEditorControl = this.editorService.activeTextEditorControl; let candidates: Debugger[] = []; let languageLabel: string | null = null; @@ -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; @@ -407,11 +408,12 @@ export class AdapterManager extends Disposable implements IAdapterManager { } }); - const picks: ({ label: string; debugger?: Debugger; type?: string } | MenuItemAction)[] = []; + const picks: ({ label: string; pick?: () => IGuessedDebugger | Promise; 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) { @@ -419,12 +421,30 @@ export class AdapterManager extends Disposable implements IAdapterManager { 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 => { + 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) { @@ -432,20 +452,24 @@ export class AdapterManager extends Disposable implements IAdapterManager { 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 { diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 26d6f90be38bc..0cc18f401f1a3 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -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'; @@ -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 } @@ -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; pick: () => Promise<{ launch: ILaunch; config: IConfig } | undefined> }[]> { + async getDynamicProviders(): Promise<{ label: string; type: string; getProvider: () => Promise; 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; @@ -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)); } } @@ -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[] = []; - 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()); input.busy = true; input.placeholder = nls.localize('selectConfiguration', "Select Launch Configuration"); @@ -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 { + // 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[] = []; + 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) { @@ -560,13 +559,19 @@ abstract class AbstractLaunch implements ILaunch { async getInitialConfigurationContent(folderUri?: uri, type?: string, useInitialConfigs?: boolean, token?: CancellationToken): Promise { let content = ''; - const adapter = type ? this.adapterManager.getEnabledDebugger(type) : await this.adapterManager.guessDebugger(true); - if (adapter) { + const adapter: Partial | 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; } diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 44f17e95d19aa..7019624359ca2 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -34,24 +34,6 @@ import { IWorkspaceTrustRequestService } from '../../../../platform/workspace/co import { EditorsOrder } from '../../../common/editor.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js'; -import { AdapterManager } from './debugAdapterManager.js'; -import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL } from './debugCommands.js'; -import { ConfigurationManager } from './debugConfigurationManager.js'; -import { DebugMemoryFileSystemProvider } from './debugMemory.js'; -import { DebugSession } from './debugSession.js'; -import { DebugTaskRunner, TaskRunResult } from './debugTaskRunner.js'; -import { CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_UX, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_HAS_DEBUGGED, CONTEXT_IN_DEBUG_MODE, DEBUG_MEMORY_SCHEME, DEBUG_SCHEME, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, ICompound, IConfig, IConfigurationManager, IDebugConfiguration, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IEnablement, IExceptionBreakpoint, IGlobalConfig, ILaunch, IStackFrame, IThread, IViewModel, REPL_VIEW_ID, State, VIEWLET_ID, debuggerDisabledMessage, getStateLabel } from '../common/debug.js'; -import { DebugCompoundRoot } from '../common/debugCompoundRoot.js'; -import { Breakpoint, DataBreakpoint, DebugModel, FunctionBreakpoint, IDataBreakpointOptions, IFunctionBreakpointOptions, IInstructionBreakpointOptions, InstructionBreakpoint } from '../common/debugModel.js'; -import { Source } from '../common/debugSource.js'; -import { DebugStorage } from '../common/debugStorage.js'; -import { DebugTelemetry } from '../common/debugTelemetry.js'; -import { getExtensionHostDebugSession, saveAllBeforeDebugStart } from '../common/debugUtils.js'; -import { ViewModel } from '../common/debugViewModel.js'; -import { Debugger } from '../common/debugger.js'; -import { DisassemblyViewInput } from '../common/disassemblyViewInput.js'; -import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from '../../files/common/files.js'; -import { ITestService } from '../../testing/common/testService.js'; import { IActivityService, NumberBadge } from '../../../services/activity/common/activity.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; @@ -59,6 +41,23 @@ import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js'; import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from '../../files/common/files.js'; +import { ITestService } from '../../testing/common/testService.js'; +import { CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_UX, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_HAS_DEBUGGED, CONTEXT_IN_DEBUG_MODE, DEBUG_MEMORY_SCHEME, DEBUG_SCHEME, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, ICompound, IConfig, IConfigurationManager, IDebugConfiguration, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IEnablement, IExceptionBreakpoint, IGlobalConfig, IGuessedDebugger, ILaunch, IStackFrame, IThread, IViewModel, REPL_VIEW_ID, State, VIEWLET_ID, debuggerDisabledMessage, getStateLabel } from '../common/debug.js'; +import { DebugCompoundRoot } from '../common/debugCompoundRoot.js'; +import { Breakpoint, DataBreakpoint, DebugModel, FunctionBreakpoint, IDataBreakpointOptions, IFunctionBreakpointOptions, IInstructionBreakpointOptions, InstructionBreakpoint } from '../common/debugModel.js'; +import { Source } from '../common/debugSource.js'; +import { DebugStorage, IChosenEnvironment } from '../common/debugStorage.js'; +import { DebugTelemetry } from '../common/debugTelemetry.js'; +import { getExtensionHostDebugSession, saveAllBeforeDebugStart } from '../common/debugUtils.js'; +import { ViewModel } from '../common/debugViewModel.js'; +import { DisassemblyViewInput } from '../common/disassemblyViewInput.js'; +import { AdapterManager } from './debugAdapterManager.js'; +import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL } from './debugCommands.js'; +import { ConfigurationManager } from './debugConfigurationManager.js'; +import { DebugMemoryFileSystemProvider } from './debugMemory.js'; +import { DebugSession } from './debugSession.js'; +import { DebugTaskRunner, TaskRunResult } from './debugTaskRunner.js'; export class DebugService implements IDebugService { declare readonly _serviceBrand: undefined; @@ -89,7 +88,7 @@ export class DebugService implements IDebugService { private previousState: State | undefined; private sessionCancellationTokens = new Map(); private activity: IDisposable | undefined; - private chosenEnvironments: { [key: string]: string }; + private chosenEnvironments: Record; private haveDoneLazySetup = false; constructor( @@ -122,7 +121,10 @@ export class DebugService implements IDebugService { this._onWillNewSession = new Emitter(); this._onDidEndSession = new Emitter(); - this.adapterManager = this.instantiationService.createInstance(AdapterManager, { onDidNewSession: this.onDidNewSession }); + this.adapterManager = this.instantiationService.createInstance(AdapterManager, { + onDidNewSession: this.onDidNewSession, + configurationManager: () => this.configurationManager, + }); this.disposables.add(this.adapterManager); this.configurationManager = this.instantiationService.createInstance(ConfigurationManager, this.adapterManager); this.disposables.add(this.configurationManager); @@ -457,26 +459,42 @@ export class DebugService implements IDebugService { type = config.type; } else { // a no-folder workspace has no launch.config - config = Object.create(null); + config = Object.create(null) as IConfig; } if (options && options.noDebug) { - config!.noDebug = true; + config.noDebug = true; } else if (options && typeof options.noDebug === 'undefined' && options.parentSession && options.parentSession.configuration.noDebug) { - config!.noDebug = true; + config.noDebug = true; } const unresolvedConfig = deepClone(config); - let guess: Debugger | undefined; + let guess: IGuessedDebugger | undefined; let activeEditor: EditorInput | undefined; if (!type) { activeEditor = this.editorService.activeEditor; if (activeEditor && activeEditor.resource) { - type = this.chosenEnvironments[activeEditor.resource.toString()]; + const chosen = this.chosenEnvironments[activeEditor.resource.toString()]; + if (chosen) { + type = chosen.type; + if (chosen.dynamicLabel) { + const dyn = await this.configurationManager.getDynamicConfigurationsByType(chosen.type); + const found = dyn.find(d => d.label === chosen.dynamicLabel); + if (found) { + launch = found.launch; + Object.assign(config, found.config); + } + } + } } + if (!type) { guess = await this.adapterManager.guessDebugger(false); if (guess) { - type = guess.type; + type = guess.debugger.type; + if (guess.withConfig) { + launch = guess.withConfig.launch; + Object.assign(config, guess.withConfig.config); + } } } } @@ -485,7 +503,7 @@ export class DebugService implements IDebugService { const sessionId = generateUuid(); this.sessionCancellationTokens.set(sessionId, initCancellationToken); - const configByProviders = await this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, initCancellationToken.token); + const configByProviders = await this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config, initCancellationToken.token); // a falsy config indicates an aborted launch if (configByProviders && configByProviders.type) { try { @@ -550,7 +568,7 @@ export class DebugService implements IDebugService { const result = await this.doCreateSession(sessionId, launch?.workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options); if (result && guess && activeEditor && activeEditor.resource) { // Remeber user choice of environment per active editor to make starting debugging smoother #124770 - this.chosenEnvironments[activeEditor.resource.toString()] = guess.type; + this.chosenEnvironments[activeEditor.resource.toString()] = { type: guess.debugger.type, dynamicLabel: guess.withConfig?.label }; this.debugStorage.storeChosenEnvironments(this.chosenEnvironments); } return result; diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index dc205a0093ba2..6db69ee2dfba9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -30,10 +30,11 @@ import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL, DEBUG_START_COMMAND_ import { debugConfigure } from './debugIcons.js'; import { createDisconnectMenuItemAction } from './debugToolBar.js'; import { WelcomeView } from './welcomeView.js'; -import { BREAKPOINTS_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY, getStateLabel, IDebugService, ILaunch, REPL_VIEW_ID, State, VIEWLET_ID } from '../common/debug.js'; +import { BREAKPOINTS_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY, getStateLabel, IDebugService, ILaunch, REPL_VIEW_ID, State, VIEWLET_ID, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution } from '../common/debug.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; import { IBaseActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; +import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; export class DebugViewPaneContainer extends ViewPaneContainer { @@ -223,7 +224,7 @@ registerAction2(class extends Action2 { }); } - async run(accessor: ServicesAccessor): Promise { + async run(accessor: ServicesAccessor, opts?: { addNew?: boolean }): Promise { const debugService = accessor.get(IDebugService); const quickInputService = accessor.get(IQuickInputService); const configurationManager = debugService.getConfigurationManager(); @@ -247,7 +248,13 @@ registerAction2(class extends Action2 { } if (launch) { - await launch.openConfigFile({ preserveFocus: false }); + const { editor } = await launch.openConfigFile({ preserveFocus: false }); + if (editor && opts?.addNew) { + const codeEditor = editor.getControl(); + if (codeEditor) { + await codeEditor.getContribution(EDITOR_CONTRIBUTION_ID)?.addLaunchConfiguration(); + } + } } } }); diff --git a/src/vs/workbench/contrib/debug/browser/welcomeView.ts b/src/vs/workbench/contrib/debug/browser/welcomeView.ts index 7dd6a939bb3fc..f1ca97ff5e6d5 100644 --- a/src/vs/workbench/contrib/debug/browser/welcomeView.ts +++ b/src/vs/workbench/contrib/debug/browser/welcomeView.ts @@ -3,30 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js'; -import { IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; -import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { IContextKeyService, RawContextKey, IContextKey, ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; +import { DisposableStore } from '../../../../base/common/lifecycle.js'; +import { isMacintosh, isWeb } from '../../../../base/common/platform.js'; +import { isCodeEditor, isDiffEditor } from '../../../../editor/browser/editorBrowser.js'; import { localize, localize2 } from '../../../../nls.js'; -import { IDebugService, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_EXTENSION_AVAILABLE } from '../common/debug.js'; -import { IEditorService } from '../../../services/editor/common/editorService.js'; -import { ViewPane } from '../../../browser/parts/views/viewPane.js'; +import { ILocalizedString } from '../../../../platform/action/common/action.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { IViewDescriptorService, IViewsRegistry, Extensions, ViewContentGroups } from '../../../common/views.js'; -import { Registry } from '../../../../platform/registry/common/platform.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; -import { WorkbenchStateContext } from '../../../common/contextkeys.js'; -import { OpenFolderAction, OpenFileAction, OpenFileFolderAction } from '../../../browser/actions/workspaceActions.js'; -import { isMacintosh, isWeb } from '../../../../base/common/platform.js'; -import { isCodeEditor, isDiffEditor } from '../../../../editor/browser/editorBrowser.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; -import { DisposableStore } from '../../../../base/common/lifecycle.js'; -import { SELECT_AND_START_ID, DEBUG_CONFIGURE_COMMAND_ID, DEBUG_START_COMMAND_ID } from './debugCommands.js'; -import { ILocalizedString } from '../../../../platform/action/common/action.js'; -import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { OpenFileAction, OpenFileFolderAction, OpenFolderAction } from '../../../browser/actions/workspaceActions.js'; +import { ViewPane } from '../../../browser/parts/views/viewPane.js'; +import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js'; +import { WorkbenchStateContext } from '../../../common/contextkeys.js'; +import { Extensions, IViewDescriptorService, IViewsRegistry, ViewContentGroups } from '../../../common/views.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_EXTENSION_AVAILABLE, IDebugService } from '../common/debug.js'; +import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_START_COMMAND_ID } from './debugCommands.js'; const debugStartLanguageKey = 'debugStartLanguage'; const CONTEXT_DEBUG_START_LANGUAGE = new RawContextKey(debugStartLanguageKey, undefined); @@ -141,13 +141,6 @@ viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { order: 1 }); -viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { - content: `[${localize('detectThenRunAndDebug', "Show all automatic debug configurations")}](command:${SELECT_AND_START_ID}).`, - when: CONTEXT_DEBUGGERS_AVAILABLE, - group: ViewContentGroups.Debug, - order: 10 -}); - viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { content: localize( { @@ -157,7 +150,7 @@ viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { '{Locked="](command:{0})"}' ] }, - "To customize Run and Debug [create a launch.json file](command:{0}).", DEBUG_CONFIGURE_COMMAND_ID), + "To customize Run and Debug [create a launch.json file](command:{0}).", `${DEBUG_CONFIGURE_COMMAND_ID}?${encodeURIComponent(JSON.stringify([{ addNew: true }]))}`), when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, WorkbenchStateContext.notEqualsTo('empty')), group: ViewContentGroups.Debug }); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 72edcb354cfd9..9d26e02c3f445 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -1018,7 +1018,8 @@ export interface IConfigurationManager { onDidChangeConfigurationProviders: Event; hasDebugConfigurationProvider(debugType: string, triggerKind?: DebugConfigurationProviderTriggerKind): boolean; - getDynamicProviders(): Promise<{ label: string; type: string; pick: () => Promise<{ launch: ILaunch; config: IConfig } | undefined> }[]>; + getDynamicProviders(): Promise<{ label: string; type: string; pick: () => Promise<{ launch: ILaunch; config: IConfig; label: string } | undefined> }[]>; + getDynamicConfigurationsByType(type: string, token?: CancellationToken): Promise<{ launch: ILaunch; config: IConfig; label: string }[]>; registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable; unregisterDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): void; @@ -1049,11 +1050,20 @@ export interface IAdapterManager { substituteVariables(debugType: string, folder: IWorkspaceFolder | undefined, config: IConfig): Promise; runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, sessionId: string): Promise; getEnabledDebugger(type: string): (IDebugger & IDebuggerMetadata) | undefined; - guessDebugger(gettingConfigurations: boolean): Promise<(IDebugger & IDebuggerMetadata) | undefined>; + guessDebugger(gettingConfigurations: boolean): Promise; get onDidDebuggersExtPointRead(): Event; } +export interface IGuessedDebugger { + debugger: IDebugger; + withConfig?: { + label: string; + launch: ILaunch; + config: IConfig; + }; +} + export interface ILaunch { /** diff --git a/src/vs/workbench/contrib/debug/common/debugStorage.ts b/src/vs/workbench/contrib/debug/common/debugStorage.ts index f4b4dd6a7a6a7..5c2a8f485bab4 100644 --- a/src/vs/workbench/contrib/debug/common/debugStorage.ts +++ b/src/vs/workbench/contrib/debug/common/debugStorage.ts @@ -12,6 +12,7 @@ import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uri import { IDebugModel, IEvaluate, IExpression } from './debug.js'; import { Breakpoint, DataBreakpoint, ExceptionBreakpoint, Expression, FunctionBreakpoint } from './debugModel.js'; import { ITextFileService } from '../../../services/textfile/common/textfiles.js'; +import { mapValues } from '../../../../base/common/objects.js'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint'; @@ -21,6 +22,11 @@ const DEBUG_WATCH_EXPRESSIONS_KEY = 'debug.watchexpressions'; const DEBUG_CHOSEN_ENVIRONMENTS_KEY = 'debug.chosenenvironment'; const DEBUG_UX_STATE_KEY = 'debug.uxstate'; +export interface IChosenEnvironment { + type: string; + dynamicLabel?: string; +} + export class DebugStorage extends Disposable { public readonly breakpoints = observableValue(this, this.loadBreakpoints()); public readonly functionBreakpoints = observableValue(this, this.loadFunctionBreakpoints()); @@ -118,11 +124,13 @@ export class DebugStorage extends Disposable { return result || []; } - loadChosenEnvironments(): { [key: string]: string } { - return JSON.parse(this.storageService.get(DEBUG_CHOSEN_ENVIRONMENTS_KEY, StorageScope.WORKSPACE, '{}')); + loadChosenEnvironments(): Record { + const obj = JSON.parse(this.storageService.get(DEBUG_CHOSEN_ENVIRONMENTS_KEY, StorageScope.WORKSPACE, '{}')); + // back compat from when this was a string map: + return mapValues(obj, (value): IChosenEnvironment => typeof value === 'string' ? { type: value } : value); } - storeChosenEnvironments(environments: { [key: string]: string }): void { + storeChosenEnvironments(environments: Record): void { this.storageService.store(DEBUG_CHOSEN_ENVIRONMENTS_KEY, JSON.stringify(environments), StorageScope.WORKSPACE, StorageTarget.MACHINE); }