Path: blob/main/src/vs/workbench/contrib/debug/browser/debugService.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as aria from '../../../../base/browser/ui/aria/aria.js';6import { Action, IAction } from '../../../../base/common/actions.js';7import { distinct } from '../../../../base/common/arrays.js';8import { RunOnceScheduler, raceTimeout } from '../../../../base/common/async.js';9import { CancellationTokenSource } from '../../../../base/common/cancellation.js';10import { isErrorWithActions } from '../../../../base/common/errorMessage.js';11import * as errors from '../../../../base/common/errors.js';12import { Emitter, Event } from '../../../../base/common/event.js';13import { DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';14import { deepClone, equals } from '../../../../base/common/objects.js';1516import severity from '../../../../base/common/severity.js';17import { URI, URI as uri } from '../../../../base/common/uri.js';18import { generateUuid } from '../../../../base/common/uuid.js';19import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js';20import { ITextModel } from '../../../../editor/common/model.js';21import * as nls from '../../../../nls.js';22import { ICommandService } from '../../../../platform/commands/common/commands.js';23import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';24import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';25import { IExtensionHostDebugService } from '../../../../platform/debug/common/extensionHostDebug.js';26import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';27import { FileChangeType, FileChangesEvent, IFileService } from '../../../../platform/files/common/files.js';28import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';29import { INotificationService } from '../../../../platform/notification/common/notification.js';30import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';31import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';32import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';33import { IWorkspaceTrustRequestService } from '../../../../platform/workspace/common/workspaceTrust.js';34import { EditorsOrder } from '../../../common/editor.js';35import { EditorInput } from '../../../common/editor/editorInput.js';36import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js';37import { IActivityService, NumberBadge } from '../../../services/activity/common/activity.js';38import { IEditorService } from '../../../services/editor/common/editorService.js';39import { IExtensionService } from '../../../services/extensions/common/extensions.js';40import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js';41import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js';42import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js';43import { IViewsService } from '../../../services/views/common/viewsService.js';44import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from '../../files/common/files.js';45import { ITestService } from '../../testing/common/testService.js';46import { 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';47import { DebugCompoundRoot } from '../common/debugCompoundRoot.js';48import { Breakpoint, DataBreakpoint, DebugModel, FunctionBreakpoint, IDataBreakpointOptions, IFunctionBreakpointOptions, IInstructionBreakpointOptions, InstructionBreakpoint } from '../common/debugModel.js';49import { Source } from '../common/debugSource.js';50import { DebugStorage, IChosenEnvironment } from '../common/debugStorage.js';51import { DebugTelemetry } from '../common/debugTelemetry.js';52import { getExtensionHostDebugSession, saveAllBeforeDebugStart } from '../common/debugUtils.js';53import { ViewModel } from '../common/debugViewModel.js';54import { DisassemblyViewInput } from '../common/disassemblyViewInput.js';55import { AdapterManager } from './debugAdapterManager.js';56import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL } from './debugCommands.js';57import { ConfigurationManager } from './debugConfigurationManager.js';58import { DebugMemoryFileSystemProvider } from './debugMemory.js';59import { DebugSession } from './debugSession.js';60import { DebugTaskRunner, TaskRunResult } from './debugTaskRunner.js';6162export class DebugService implements IDebugService {63declare readonly _serviceBrand: undefined;6465private readonly _onDidChangeState: Emitter<State>;66private readonly _onDidNewSession: Emitter<IDebugSession>;67private readonly _onWillNewSession: Emitter<IDebugSession>;68private readonly _onDidEndSession: Emitter<{ session: IDebugSession; restart: boolean }>;69private readonly restartingSessions = new Set<IDebugSession>();70private debugStorage: DebugStorage;71private model: DebugModel;72private viewModel: ViewModel;73private telemetry: DebugTelemetry;74private taskRunner: DebugTaskRunner;75private configurationManager: ConfigurationManager;76private adapterManager: AdapterManager;77private readonly disposables = new DisposableStore();78private debugType!: IContextKey<string>;79private debugState!: IContextKey<string>;80private inDebugMode!: IContextKey<boolean>;81private debugUx!: IContextKey<string>;82private hasDebugged!: IContextKey<boolean>;83private breakpointsExist!: IContextKey<boolean>;84private disassemblyViewFocus!: IContextKey<boolean>;85private breakpointsToSendOnResourceSaved: Set<URI>;86private initializing = false;87private _initializingOptions: IDebugSessionOptions | undefined;88private previousState: State | undefined;89private sessionCancellationTokens = new Map<string, CancellationTokenSource>();90private activity: IDisposable | undefined;91private chosenEnvironments: Record<string, IChosenEnvironment>;92private haveDoneLazySetup = false;9394constructor(95@IEditorService private readonly editorService: IEditorService,96@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService,97@IViewsService private readonly viewsService: IViewsService,98@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,99@INotificationService private readonly notificationService: INotificationService,100@IDialogService private readonly dialogService: IDialogService,101@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,102@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,103@IContextKeyService private readonly contextKeyService: IContextKeyService,104@ILifecycleService private readonly lifecycleService: ILifecycleService,105@IInstantiationService private readonly instantiationService: IInstantiationService,106@IExtensionService private readonly extensionService: IExtensionService,107@IFileService private readonly fileService: IFileService,108@IConfigurationService private readonly configurationService: IConfigurationService,109@IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService,110@IActivityService private readonly activityService: IActivityService,111@ICommandService private readonly commandService: ICommandService,112@IQuickInputService private readonly quickInputService: IQuickInputService,113@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,114@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,115@ITestService private readonly testService: ITestService,116) {117this.breakpointsToSendOnResourceSaved = new Set<URI>();118119this._onDidChangeState = new Emitter<State>();120this._onDidNewSession = new Emitter<IDebugSession>();121this._onWillNewSession = new Emitter<IDebugSession>();122this._onDidEndSession = new Emitter();123124this.adapterManager = this.instantiationService.createInstance(AdapterManager, {125onDidNewSession: this.onDidNewSession,126configurationManager: () => this.configurationManager,127});128this.disposables.add(this.adapterManager);129this.configurationManager = this.instantiationService.createInstance(ConfigurationManager, this.adapterManager);130this.disposables.add(this.configurationManager);131this.debugStorage = this.disposables.add(this.instantiationService.createInstance(DebugStorage));132133this.chosenEnvironments = this.debugStorage.loadChosenEnvironments();134135this.model = this.instantiationService.createInstance(DebugModel, this.debugStorage);136this.telemetry = this.instantiationService.createInstance(DebugTelemetry, this.model);137138this.viewModel = new ViewModel(contextKeyService);139this.taskRunner = this.instantiationService.createInstance(DebugTaskRunner);140141this.disposables.add(this.fileService.onDidFilesChange(e => this.onFileChanges(e)));142this.disposables.add(this.lifecycleService.onWillShutdown(this.dispose, this));143144this.disposables.add(this.extensionHostDebugService.onAttachSession(event => {145const session = this.model.getSession(event.sessionId, true);146if (session) {147// EH was started in debug mode -> attach to it148session.configuration.request = 'attach';149session.configuration.port = event.port;150session.setSubId(event.subId);151this.launchOrAttachToSession(session);152}153}));154this.disposables.add(this.extensionHostDebugService.onTerminateSession(event => {155const session = this.model.getSession(event.sessionId);156if (session && session.subId === event.subId) {157session.disconnect();158}159}));160161this.disposables.add(this.viewModel.onDidFocusStackFrame(() => {162this.onStateChange();163}));164this.disposables.add(this.viewModel.onDidFocusSession((session: IDebugSession | undefined) => {165this.onStateChange();166167if (session) {168this.setExceptionBreakpointFallbackSession(session.getId());169}170}));171this.disposables.add(Event.any(this.adapterManager.onDidRegisterDebugger, this.configurationManager.onDidSelectConfiguration)(() => {172const debugUxValue = (this.state !== State.Inactive || (this.configurationManager.getAllConfigurations().length > 0 && this.adapterManager.hasEnabledDebuggers())) ? 'default' : 'simple';173this.debugUx.set(debugUxValue);174this.debugStorage.storeDebugUxState(debugUxValue);175}));176this.disposables.add(this.model.onDidChangeCallStack(() => {177const numberOfSessions = this.model.getSessions().filter(s => !s.parentSession).length;178this.activity?.dispose();179if (numberOfSessions > 0) {180const viewContainer = this.viewDescriptorService.getViewContainerByViewId(CALLSTACK_VIEW_ID);181if (viewContainer) {182this.activity = this.activityService.showViewContainerActivity(viewContainer.id, { badge: new NumberBadge(numberOfSessions, n => n === 1 ? nls.localize('1activeSession', "1 active session") : nls.localize('nActiveSessions', "{0} active sessions", n)) });183}184}185}));186187this.disposables.add(editorService.onDidActiveEditorChange(() => {188this.contextKeyService.bufferChangeEvents(() => {189if (editorService.activeEditor === DisassemblyViewInput.instance) {190this.disassemblyViewFocus.set(true);191} else {192// This key can be initialized a tick after this event is fired193this.disassemblyViewFocus?.reset();194}195});196}));197198this.disposables.add(this.lifecycleService.onBeforeShutdown(() => {199for (const editor of editorService.editors) {200// Editors will not be valid on window reload, so close them.201if (editor.resource?.scheme === DEBUG_MEMORY_SCHEME) {202editor.dispose();203}204}205}));206207this.disposables.add(extensionService.onWillStop(evt => {208evt.veto(209this.model.getSessions().length > 0,210nls.localize('active debug session', 'A debug session is still running that would terminate.'),211);212}));213214this.initContextKeys(contextKeyService);215}216217private initContextKeys(contextKeyService: IContextKeyService): void {218queueMicrotask(() => {219contextKeyService.bufferChangeEvents(() => {220this.debugType = CONTEXT_DEBUG_TYPE.bindTo(contextKeyService);221this.debugState = CONTEXT_DEBUG_STATE.bindTo(contextKeyService);222this.hasDebugged = CONTEXT_HAS_DEBUGGED.bindTo(contextKeyService);223this.inDebugMode = CONTEXT_IN_DEBUG_MODE.bindTo(contextKeyService);224this.debugUx = CONTEXT_DEBUG_UX.bindTo(contextKeyService);225this.debugUx.set(this.debugStorage.loadDebugUxState());226this.breakpointsExist = CONTEXT_BREAKPOINTS_EXIST.bindTo(contextKeyService);227// Need to set disassemblyViewFocus here to make it in the same context as the debug event handlers228this.disassemblyViewFocus = CONTEXT_DISASSEMBLY_VIEW_FOCUS.bindTo(contextKeyService);229});230231const setBreakpointsExistContext = () => this.breakpointsExist.set(!!(this.model.getBreakpoints().length || this.model.getDataBreakpoints().length || this.model.getFunctionBreakpoints().length));232setBreakpointsExistContext();233this.disposables.add(this.model.onDidChangeBreakpoints(() => setBreakpointsExistContext()));234});235}236237getModel(): IDebugModel {238return this.model;239}240241getViewModel(): IViewModel {242return this.viewModel;243}244245getConfigurationManager(): IConfigurationManager {246return this.configurationManager;247}248249getAdapterManager(): IAdapterManager {250return this.adapterManager;251}252253sourceIsNotAvailable(uri: uri): void {254this.model.sourceIsNotAvailable(uri);255}256257dispose(): void {258this.disposables.dispose();259}260261//---- state management262263get state(): State {264const focusedSession = this.viewModel.focusedSession;265if (focusedSession) {266return focusedSession.state;267}268269return this.initializing ? State.Initializing : State.Inactive;270}271272get initializingOptions(): IDebugSessionOptions | undefined {273return this._initializingOptions;274}275276private startInitializingState(options?: IDebugSessionOptions): void {277if (!this.initializing) {278this.initializing = true;279this._initializingOptions = options;280this.onStateChange();281}282}283284private endInitializingState(): void {285if (this.initializing) {286this.initializing = false;287this._initializingOptions = undefined;288this.onStateChange();289}290}291292private cancelTokens(id: string | undefined): void {293if (id) {294const token = this.sessionCancellationTokens.get(id);295if (token) {296token.cancel();297this.sessionCancellationTokens.delete(id);298}299} else {300this.sessionCancellationTokens.forEach(t => t.cancel());301this.sessionCancellationTokens.clear();302}303}304305private onStateChange(): void {306const state = this.state;307if (this.previousState !== state) {308this.contextKeyService.bufferChangeEvents(() => {309this.debugState.set(getStateLabel(state));310this.inDebugMode.set(state !== State.Inactive);311// Only show the simple ux if debug is not yet started and if no launch.json exists312const debugUxValue = ((state !== State.Inactive && state !== State.Initializing) || (this.adapterManager.hasEnabledDebuggers() && this.configurationManager.selectedConfiguration.name)) ? 'default' : 'simple';313this.debugUx.set(debugUxValue);314this.debugStorage.storeDebugUxState(debugUxValue);315});316this.previousState = state;317this._onDidChangeState.fire(state);318}319}320321get onDidChangeState(): Event<State> {322return this._onDidChangeState.event;323}324325get onDidNewSession(): Event<IDebugSession> {326return this._onDidNewSession.event;327}328329get onWillNewSession(): Event<IDebugSession> {330return this._onWillNewSession.event;331}332333get onDidEndSession(): Event<{ session: IDebugSession; restart: boolean }> {334return this._onDidEndSession.event;335}336337private lazySetup() {338if (!this.haveDoneLazySetup) {339// Registering fs providers is slow340// https://github.com/microsoft/vscode/issues/159886341this.disposables.add(this.fileService.registerProvider(DEBUG_MEMORY_SCHEME, this.disposables.add(new DebugMemoryFileSystemProvider(this))));342this.haveDoneLazySetup = true;343}344}345346//---- life cycle management347348/**349* main entry point350* properly manages compounds, checks for errors and handles the initializing state.351*/352async startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions, saveBeforeStart = !options?.parentSession): Promise<boolean> {353const message = options && options.noDebug ? nls.localize('runTrust', "Running executes build tasks and program code from your workspace.") : nls.localize('debugTrust', "Debugging executes build tasks and program code from your workspace.");354const trust = await this.workspaceTrustRequestService.requestWorkspaceTrust({ message });355if (!trust) {356return false;357}358359this.lazySetup();360this.startInitializingState(options);361this.hasDebugged.set(true);362try {363// make sure to save all files and that the configuration is up to date364await this.extensionService.activateByEvent('onDebug');365if (saveBeforeStart) {366await saveAllBeforeDebugStart(this.configurationService, this.editorService);367}368await this.extensionService.whenInstalledExtensionsRegistered();369370let config: IConfig | undefined;371let compound: ICompound | undefined;372if (!configOrName) {373configOrName = this.configurationManager.selectedConfiguration.name;374}375if (typeof configOrName === 'string' && launch) {376config = launch.getConfiguration(configOrName);377compound = launch.getCompound(configOrName);378} else if (typeof configOrName !== 'string') {379config = configOrName;380}381382if (compound) {383// we are starting a compound debug, first do some error checking and than start each configuration in the compound384if (!compound.configurations) {385throw new Error(nls.localize({ key: 'compoundMustHaveConfigurations', comment: ['compound indicates a "compounds" configuration item', '"configurations" is an attribute and should not be localized'] },386"Compound must have \"configurations\" attribute set in order to start multiple configurations."));387}388if (compound.preLaunchTask) {389const taskResult = await this.taskRunner.runTaskAndCheckErrors(launch?.workspace || this.contextService.getWorkspace(), compound.preLaunchTask);390if (taskResult === TaskRunResult.Failure) {391this.endInitializingState();392return false;393}394}395if (compound.stopAll) {396options = { ...options, compoundRoot: new DebugCompoundRoot() };397}398399const values = await Promise.all(compound.configurations.map(configData => {400const name = typeof configData === 'string' ? configData : configData.name;401if (name === compound.name) {402return Promise.resolve(false);403}404405let launchForName: ILaunch | undefined;406if (typeof configData === 'string') {407const launchesContainingName = this.configurationManager.getLaunches().filter(l => !!l.getConfiguration(name));408if (launchesContainingName.length === 1) {409launchForName = launchesContainingName[0];410} else if (launch && launchesContainingName.length > 1 && launchesContainingName.indexOf(launch) >= 0) {411// If there are multiple launches containing the configuration give priority to the configuration in the current launch412launchForName = launch;413} else {414throw new Error(launchesContainingName.length === 0 ? nls.localize('noConfigurationNameInWorkspace', "Could not find launch configuration '{0}' in the workspace.", name)415: nls.localize('multipleConfigurationNamesInWorkspace', "There are multiple launch configurations '{0}' in the workspace. Use folder name to qualify the configuration.", name));416}417} else if (configData.folder) {418const launchesMatchingConfigData = this.configurationManager.getLaunches().filter(l => l.workspace && l.workspace.name === configData.folder && !!l.getConfiguration(configData.name));419if (launchesMatchingConfigData.length === 1) {420launchForName = launchesMatchingConfigData[0];421} else {422throw new Error(nls.localize('noFolderWithName', "Can not find folder with name '{0}' for configuration '{1}' in compound '{2}'.", configData.folder, configData.name, compound.name));423}424}425426return this.createSession(launchForName, launchForName!.getConfiguration(name), options);427}));428429const result = values.every(success => !!success); // Compound launch is a success only if each configuration launched successfully430this.endInitializingState();431return result;432}433434if (configOrName && !config) {435const message = !!launch ? nls.localize('configMissing', "Configuration '{0}' is missing in 'launch.json'.", typeof configOrName === 'string' ? configOrName : configOrName.name) :436nls.localize('launchJsonDoesNotExist', "'launch.json' does not exist for passed workspace folder.");437throw new Error(message);438}439440const result = await this.createSession(launch, config, options);441this.endInitializingState();442return result;443} catch (err) {444// make sure to get out of initializing state, and propagate the result445this.notificationService.error(err);446this.endInitializingState();447return Promise.reject(err);448}449}450451/**452* gets the debugger for the type, resolves configurations by providers, substitutes variables and runs prelaunch tasks453*/454private async createSession(launch: ILaunch | undefined, config: IConfig | undefined, options?: IDebugSessionOptions): Promise<boolean> {455// We keep the debug type in a separate variable 'type' so that a no-folder config has no attributes.456// Storing the type in the config would break extensions that assume that the no-folder case is indicated by an empty config.457let type: string | undefined;458if (config) {459type = config.type;460} else {461// a no-folder workspace has no launch.config462config = Object.create(null) as IConfig;463}464if (options && options.noDebug) {465config.noDebug = true;466} else if (options && typeof options.noDebug === 'undefined' && options.parentSession && options.parentSession.configuration.noDebug) {467config.noDebug = true;468}469const unresolvedConfig = deepClone(config);470471let guess: IGuessedDebugger | undefined;472let activeEditor: EditorInput | undefined;473if (!type) {474activeEditor = this.editorService.activeEditor;475if (activeEditor && activeEditor.resource) {476const chosen = this.chosenEnvironments[activeEditor.resource.toString()];477if (chosen) {478type = chosen.type;479if (chosen.dynamicLabel) {480const dyn = await this.configurationManager.getDynamicConfigurationsByType(chosen.type);481const found = dyn.find(d => d.label === chosen.dynamicLabel);482if (found) {483launch = found.launch;484Object.assign(config, found.config);485}486}487}488}489490if (!type) {491guess = await this.adapterManager.guessDebugger(false);492if (guess) {493type = guess.debugger.type;494if (guess.withConfig) {495launch = guess.withConfig.launch;496Object.assign(config, guess.withConfig.config);497}498}499}500}501502const initCancellationToken = new CancellationTokenSource();503const sessionId = generateUuid();504this.sessionCancellationTokens.set(sessionId, initCancellationToken);505506const configByProviders = await this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config, initCancellationToken.token);507// a falsy config indicates an aborted launch508if (configByProviders && configByProviders.type) {509try {510let resolvedConfig = await this.substituteVariables(launch, configByProviders);511if (!resolvedConfig) {512// User cancelled resolving of interactive variables, silently return513return false;514}515516if (initCancellationToken.token.isCancellationRequested) {517// User cancelled, silently return518return false;519}520521// Check for concurrent sessions before running preLaunchTask to avoid running the task if user cancels522let userConfirmedConcurrentSession = false;523if (options?.startedByUser && resolvedConfig && resolvedConfig.suppressMultipleSessionWarning !== true) {524// Check if there's already a session with the same launch configuration525const existingSessions = this.model.getSessions();526const workspace = launch?.workspace;527528const existingSession = existingSessions.find(s =>529s.configuration.name === resolvedConfig!.name &&530s.configuration.type === resolvedConfig!.type &&531s.configuration.request === resolvedConfig!.request &&532s.root === workspace533);534535if (existingSession) {536// There is already a session with the same configuration, prompt user before running preLaunchTask537const confirmed = await this.confirmConcurrentSession(existingSession.getLabel());538if (!confirmed) {539return false;540}541userConfirmedConcurrentSession = true;542}543}544545const workspace = launch?.workspace || this.contextService.getWorkspace();546const taskResult = await this.taskRunner.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask);547if (taskResult === TaskRunResult.Failure) {548return false;549}550551const cfg = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, resolvedConfig.type, resolvedConfig, initCancellationToken.token);552if (!cfg) {553if (launch && type && cfg === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null".554await launch.openConfigFile({ preserveFocus: true, type }, initCancellationToken.token);555}556return false;557}558resolvedConfig = cfg;559560const dbg = this.adapterManager.getDebugger(resolvedConfig.type);561if (!dbg || (configByProviders.request !== 'attach' && configByProviders.request !== 'launch')) {562let message: string;563if (configByProviders.request !== 'attach' && configByProviders.request !== 'launch') {564message = configByProviders.request ? nls.localize('debugRequestNotSupported', "Attribute '{0}' has an unsupported value '{1}' in the chosen debug configuration.", 'request', configByProviders.request)565: nls.localize('debugRequesMissing', "Attribute '{0}' is missing from the chosen debug configuration.", 'request');566567} else {568message = resolvedConfig.type ? nls.localize('debugTypeNotSupported', "Configured debug type '{0}' is not supported.", resolvedConfig.type) :569nls.localize('debugTypeMissing', "Missing property 'type' for the chosen launch configuration.");570}571572const actionList: IAction[] = [];573574actionList.push(new Action(575'installAdditionalDebuggers',576nls.localize({ key: 'installAdditionalDebuggers', comment: ['Placeholder is the debug type, so for example "node", "python"'] }, "Install {0} Extension", resolvedConfig.type),577undefined,578true,579async () => this.commandService.executeCommand('debug.installAdditionalDebuggers', resolvedConfig?.type)580));581582await this.showError(message, actionList);583584return false;585}586587if (!dbg.enabled) {588await this.showError(debuggerDisabledMessage(dbg.type), []);589return false;590}591592const result = await this.doCreateSession(sessionId, launch?.workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options, userConfirmedConcurrentSession);593if (result && guess && activeEditor && activeEditor.resource) {594// Remeber user choice of environment per active editor to make starting debugging smoother #124770595this.chosenEnvironments[activeEditor.resource.toString()] = { type: guess.debugger.type, dynamicLabel: guess.withConfig?.label };596this.debugStorage.storeChosenEnvironments(this.chosenEnvironments);597}598return result;599} catch (err) {600if (err && err.message) {601await this.showError(err.message);602} else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {603await this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved and that you have a debug extension installed for that file type."));604}605if (launch && !initCancellationToken.token.isCancellationRequested) {606await launch.openConfigFile({ preserveFocus: true }, initCancellationToken.token);607}608609return false;610}611}612613if (launch && type && configByProviders === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null".614await launch.openConfigFile({ preserveFocus: true, type }, initCancellationToken.token);615}616617return false;618}619620/**621* instantiates the new session, initializes the session, registers session listeners and reports telemetry622*/623private async doCreateSession(sessionId: string, root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig; unresolved: IConfig | undefined }, options?: IDebugSessionOptions, userConfirmedConcurrentSession = false): Promise<boolean> {624625const session = this.instantiationService.createInstance(DebugSession, sessionId, configuration, root, this.model, options);626if (!userConfirmedConcurrentSession && options?.startedByUser && this.model.getSessions().some(s =>627s.configuration.name === configuration.resolved.name &&628s.configuration.type === configuration.resolved.type &&629s.configuration.request === configuration.resolved.request &&630s.root === root631) && configuration.resolved.suppressMultipleSessionWarning !== true) {632// There is already a session with the same configuration, prompt user #127721633const confirmed = await this.confirmConcurrentSession(session.getLabel());634if (!confirmed) {635return false;636}637}638639this.model.addSession(session);640641// since the Session is now properly registered under its ID and hooked, we can announce it642// this event doesn't go to extensions643this._onWillNewSession.fire(session);644645const openDebug = this.configurationService.getValue<IDebugConfiguration>('debug').openDebug;646// Open debug viewlet based on the visibility of the side bar and openDebug setting. Do not open for 'run without debug'647if (!configuration.resolved.noDebug && (openDebug === 'openOnSessionStart' || (openDebug !== 'neverOpen' && this.viewModel.firstSessionStart)) && !session.suppressDebugView) {648await this.paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar);649}650651try {652await this.launchOrAttachToSession(session);653654const internalConsoleOptions = session.configuration.internalConsoleOptions || this.configurationService.getValue<IDebugConfiguration>('debug').internalConsoleOptions;655if (internalConsoleOptions === 'openOnSessionStart' || (this.viewModel.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) {656this.viewsService.openView(REPL_VIEW_ID, false);657}658659this.viewModel.firstSessionStart = false;660const showSubSessions = this.configurationService.getValue<IDebugConfiguration>('debug').showSubSessionsInToolBar;661const sessions = this.model.getSessions();662const shownSessions = showSubSessions ? sessions : sessions.filter(s => !s.parentSession);663if (shownSessions.length > 1) {664this.viewModel.setMultiSessionView(true);665}666667// since the initialized response has arrived announce the new Session (including extensions)668this._onDidNewSession.fire(session);669670return true;671} catch (error) {672673if (errors.isCancellationError(error)) {674// don't show 'canceled' error messages to the user #7906675return false;676}677678// Show the repl if some error got logged there #5870679if (session && session.getReplElements().length > 0) {680this.viewsService.openView(REPL_VIEW_ID, false);681}682683if (session.configuration && session.configuration.request === 'attach' && session.configuration.__autoAttach) {684// ignore attach timeouts in auto attach mode685return false;686}687688const errorMessage = error instanceof Error ? error.message : error;689if (error.showUser !== false) {690// Only show the error when showUser is either not defined, or is true #128484691await this.showError(errorMessage, isErrorWithActions(error) ? error.actions : []);692}693return false;694}695}696697private async confirmConcurrentSession(sessionLabel: string): Promise<boolean> {698const result = await this.dialogService.confirm({699message: nls.localize('multipleSession', "'{0}' is already running. Do you want to start another instance?", sessionLabel)700});701return result.confirmed;702}703704private async launchOrAttachToSession(session: IDebugSession, forceFocus = false): Promise<void> {705// register listeners as the very first thing!706this.registerSessionListeners(session);707708const dbgr = this.adapterManager.getDebugger(session.configuration.type);709try {710await session.initialize(dbgr!);711await session.launchOrAttach(session.configuration);712const launchJsonExists = !!session.root && !!this.configurationService.getValue<IGlobalConfig>('launch', { resource: session.root.uri });713await this.telemetry.logDebugSessionStart(dbgr!, launchJsonExists);714715if (forceFocus || !this.viewModel.focusedSession || (session.parentSession === this.viewModel.focusedSession && session.compact)) {716await this.focusStackFrame(undefined, undefined, session);717}718} catch (err) {719if (this.viewModel.focusedSession === session) {720await this.focusStackFrame(undefined);721}722return Promise.reject(err);723}724}725726private registerSessionListeners(session: IDebugSession): void {727const listenerDisposables = new DisposableStore();728this.disposables.add(listenerDisposables);729730const sessionRunningScheduler = listenerDisposables.add(new RunOnceScheduler(() => {731// Do not immediatly defocus the stack frame if the session is running732if (session.state === State.Running && this.viewModel.focusedSession === session) {733this.viewModel.setFocus(undefined, this.viewModel.focusedThread, session, false);734}735}, 200));736listenerDisposables.add(session.onDidChangeState(() => {737if (session.state === State.Running && this.viewModel.focusedSession === session) {738sessionRunningScheduler.schedule();739}740if (session === this.viewModel.focusedSession) {741this.onStateChange();742}743}));744listenerDisposables.add(this.onDidEndSession(e => {745if (e.session === session) {746this.disposables.delete(listenerDisposables);747}748}));749listenerDisposables.add(session.onDidEndAdapter(async adapterExitEvent => {750751if (adapterExitEvent) {752if (adapterExitEvent.error) {753this.notificationService.error(nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly ({0})", adapterExitEvent.error.message || adapterExitEvent.error.toString()));754}755this.telemetry.logDebugSessionStop(session, adapterExitEvent);756}757758// 'Run without debugging' mode VSCode must terminate the extension host. More details: #3905759const extensionDebugSession = getExtensionHostDebugSession(session);760if (extensionDebugSession && extensionDebugSession.state === State.Running && extensionDebugSession.configuration.noDebug) {761this.extensionHostDebugService.close(extensionDebugSession.getId());762}763764if (session.configuration.postDebugTask) {765const root = session.root ?? this.contextService.getWorkspace();766try {767await this.taskRunner.runTask(root, session.configuration.postDebugTask);768} catch (err) {769this.notificationService.error(err);770}771}772this.endInitializingState();773this.cancelTokens(session.getId());774775if (this.configurationService.getValue<IDebugConfiguration>('debug').closeReadonlyTabsOnEnd) {776const editorsToClose = this.editorService.getEditors(EditorsOrder.SEQUENTIAL).filter(({ editor }) => {777return editor.resource?.scheme === DEBUG_SCHEME && session.getId() === Source.getEncodedDebugData(editor.resource).sessionId;778});779this.editorService.closeEditors(editorsToClose);780}781this._onDidEndSession.fire({ session, restart: this.restartingSessions.has(session) });782783const focusedSession = this.viewModel.focusedSession;784if (focusedSession && focusedSession.getId() === session.getId()) {785const { session, thread, stackFrame } = getStackFrameThreadAndSessionToFocus(this.model, undefined, undefined, undefined, focusedSession);786this.viewModel.setFocus(stackFrame, thread, session, false);787}788789if (this.model.getSessions().length === 0) {790this.viewModel.setMultiSessionView(false);791792if (this.layoutService.isVisible(Parts.SIDEBAR_PART) && this.configurationService.getValue<IDebugConfiguration>('debug').openExplorerOnEnd) {793this.paneCompositeService.openPaneComposite(EXPLORER_VIEWLET_ID, ViewContainerLocation.Sidebar);794}795796// Data breakpoints that can not be persisted should be cleared when a session ends797const dataBreakpoints = this.model.getDataBreakpoints().filter(dbp => !dbp.canPersist);798dataBreakpoints.forEach(dbp => this.model.removeDataBreakpoints(dbp.getId()));799800if (this.configurationService.getValue<IDebugConfiguration>('debug').console.closeOnEnd) {801const debugConsoleContainer = this.viewDescriptorService.getViewContainerByViewId(REPL_VIEW_ID);802if (debugConsoleContainer && this.viewsService.isViewContainerVisible(debugConsoleContainer.id)) {803this.viewsService.closeViewContainer(debugConsoleContainer.id);804}805}806}807808this.model.removeExceptionBreakpointsForSession(session.getId());809// session.dispose(); TODO@roblourens810}));811}812813async restartSession(session: IDebugSession, restartData?: any): Promise<void> {814if (session.saveBeforeRestart) {815await saveAllBeforeDebugStart(this.configurationService, this.editorService);816}817818const isAutoRestart = !!restartData;819820const runTasks: () => Promise<TaskRunResult> = async () => {821if (isAutoRestart) {822// Do not run preLaunch and postDebug tasks for automatic restarts823return Promise.resolve(TaskRunResult.Success);824}825826const root = session.root || this.contextService.getWorkspace();827await this.taskRunner.runTask(root, session.configuration.preRestartTask);828await this.taskRunner.runTask(root, session.configuration.postDebugTask);829830const taskResult1 = await this.taskRunner.runTaskAndCheckErrors(root, session.configuration.preLaunchTask);831if (taskResult1 !== TaskRunResult.Success) {832return taskResult1;833}834835return this.taskRunner.runTaskAndCheckErrors(root, session.configuration.postRestartTask);836};837838const extensionDebugSession = getExtensionHostDebugSession(session);839if (extensionDebugSession) {840const taskResult = await runTasks();841if (taskResult === TaskRunResult.Success) {842this.extensionHostDebugService.reload(extensionDebugSession.getId());843}844845return;846}847848// Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration849let needsToSubstitute = false;850let unresolved: IConfig | undefined;851const launch = session.root ? this.configurationManager.getLaunch(session.root.uri) : undefined;852if (launch) {853unresolved = launch.getConfiguration(session.configuration.name);854if (unresolved && !equals(unresolved, session.unresolvedConfiguration)) {855unresolved.noDebug = session.configuration.noDebug;856needsToSubstitute = true;857}858}859860let resolved: IConfig | undefined | null = session.configuration;861if (launch && needsToSubstitute && unresolved) {862const initCancellationToken = new CancellationTokenSource();863this.sessionCancellationTokens.set(session.getId(), initCancellationToken);864const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, initCancellationToken.token);865if (resolvedByProviders) {866resolved = await this.substituteVariables(launch, resolvedByProviders);867if (resolved && !initCancellationToken.token.isCancellationRequested) {868resolved = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, resolved.type, resolved, initCancellationToken.token);869}870} else {871resolved = resolvedByProviders;872}873}874if (resolved) {875session.setConfiguration({ resolved, unresolved });876}877session.configuration.__restart = restartData;878879const doRestart = async (fn: () => Promise<boolean | undefined>) => {880this.restartingSessions.add(session);881let didRestart = false;882try {883didRestart = (await fn()) !== false;884} catch (e) {885didRestart = false;886throw e;887} finally {888this.restartingSessions.delete(session);889// we previously may have issued an onDidEndSession with restart: true,890// assuming the adapter exited (in `registerSessionListeners`). But the891// restart failed, so emit the final termination now.892if (!didRestart) {893this._onDidEndSession.fire({ session, restart: false });894}895}896};897898for (const breakpoint of this.model.getBreakpoints({ triggeredOnly: true })) {899breakpoint.setSessionDidTrigger(session.getId(), false);900}901902// For debug sessions spawned by test runs, cancel the test run and stop903// the session, then start the test run again; tests have no notion of restarts.904if (session.correlatedTestRun) {905if (!session.correlatedTestRun.completedAt) {906session.cancelCorrelatedTestRun();907await Event.toPromise(session.correlatedTestRun.onComplete);908// todo@connor4312 is there any reason to wait for the debug session to909// terminate? I don't think so, test extension should already handle any910// state conflicts...911}912913this.testService.runResolvedTests(session.correlatedTestRun.request);914return;915}916917if (session.capabilities.supportsRestartRequest) {918const taskResult = await runTasks();919if (taskResult === TaskRunResult.Success) {920await doRestart(async () => {921await session.restart();922return true;923});924}925926return;927}928929const shouldFocus = !!this.viewModel.focusedSession && session.getId() === this.viewModel.focusedSession.getId();930return doRestart(async () => {931// If the restart is automatic -> disconnect, otherwise -> terminate #55064932if (isAutoRestart) {933await session.disconnect(true);934} else {935await session.terminate(true);936}937938return new Promise<boolean>((c, e) => {939setTimeout(async () => {940const taskResult = await runTasks();941if (taskResult !== TaskRunResult.Success) {942return c(false);943}944945if (!resolved) {946return c(false);947}948949try {950await this.launchOrAttachToSession(session, shouldFocus);951this._onDidNewSession.fire(session);952c(true);953} catch (error) {954e(error);955}956}, 300);957});958});959}960961async stopSession(session: IDebugSession | undefined, disconnect = false, suspend = false): Promise<any> {962if (session) {963return disconnect ? session.disconnect(undefined, suspend) : session.terminate();964}965966const sessions = this.model.getSessions();967if (sessions.length === 0) {968this.taskRunner.cancel();969// User might have cancelled starting of a debug session, and in some cases the quick pick is left open970await this.quickInputService.cancel();971this.endInitializingState();972this.cancelTokens(undefined);973}974975return Promise.all(sessions.map(s => disconnect ? s.disconnect(undefined, suspend) : s.terminate()));976}977978private async substituteVariables(launch: ILaunch | undefined, config: IConfig): Promise<IConfig | undefined> {979const dbg = this.adapterManager.getDebugger(config.type);980if (dbg) {981let folder: IWorkspaceFolder | undefined = undefined;982if (launch && launch.workspace) {983folder = launch.workspace;984} else {985const folders = this.contextService.getWorkspace().folders;986if (folders.length === 1) {987folder = folders[0];988}989}990try {991return await dbg.substituteVariables(folder, config);992} catch (err) {993if (err.message !== errors.canceledName) {994this.showError(err.message, undefined, !!launch?.getConfiguration(config.name));995}996return undefined; // bail out997}998}999return Promise.resolve(config);1000}10011002private async showError(message: string, errorActions: ReadonlyArray<IAction> = [], promptLaunchJson = true): Promise<void> {1003const configureAction = new Action(DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL, undefined, true, () => this.commandService.executeCommand(DEBUG_CONFIGURE_COMMAND_ID));1004// Don't append the standard command if id of any provided action indicates it is a command1005const actions = errorActions.filter((action) => action.id.endsWith('.command')).length > 0 ?1006errorActions :1007[...errorActions, ...(promptLaunchJson ? [configureAction] : [])];1008await this.dialogService.prompt({1009type: severity.Error,1010message,1011buttons: actions.map(action => ({1012label: action.label,1013run: () => action.run()1014})),1015cancelButton: true1016});1017}10181019//---- focus management10201021async focusStackFrame(_stackFrame: IStackFrame | undefined, _thread?: IThread, _session?: IDebugSession, options?: { explicit?: boolean; preserveFocus?: boolean; sideBySide?: boolean; pinned?: boolean }): Promise<void> {1022const { stackFrame, thread, session } = getStackFrameThreadAndSessionToFocus(this.model, _stackFrame, _thread, _session);10231024if (stackFrame) {1025const editor = await stackFrame.openInEditor(this.editorService, options?.preserveFocus ?? true, options?.sideBySide, options?.pinned);1026if (editor) {1027if (editor.input === DisassemblyViewInput.instance) {1028// Go to address is invoked via setFocus1029} else {1030const control = editor.getControl();1031if (stackFrame && isCodeEditor(control) && control.hasModel()) {1032const model = control.getModel();1033const lineNumber = stackFrame.range.startLineNumber;1034if (lineNumber >= 1 && lineNumber <= model.getLineCount()) {1035const lineContent = control.getModel().getLineContent(lineNumber);1036aria.alert(nls.localize({ key: 'debuggingPaused', comment: ['First placeholder is the file line content, second placeholder is the reason why debugging is stopped, for example "breakpoint", third is the stack frame name, and last is the line number.'] },1037"{0}, debugging paused {1}, {2}:{3}", lineContent, thread && thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber));1038}1039}1040}1041}1042}1043if (session) {1044this.debugType.set(session.configuration.type);1045} else {1046this.debugType.reset();1047}10481049this.viewModel.setFocus(stackFrame, thread, session, !!options?.explicit);1050}10511052//---- watches10531054addWatchExpression(name?: string): void {1055const we = this.model.addWatchExpression(name);1056if (!name) {1057this.viewModel.setSelectedExpression(we, false);1058}1059this.debugStorage.storeWatchExpressions(this.model.getWatchExpressions());1060}10611062renameWatchExpression(id: string, newName: string): void {1063this.model.renameWatchExpression(id, newName);1064this.debugStorage.storeWatchExpressions(this.model.getWatchExpressions());1065}10661067moveWatchExpression(id: string, position: number): void {1068this.model.moveWatchExpression(id, position);1069this.debugStorage.storeWatchExpressions(this.model.getWatchExpressions());1070}10711072removeWatchExpressions(id?: string): void {1073this.model.removeWatchExpressions(id);1074this.debugStorage.storeWatchExpressions(this.model.getWatchExpressions());1075}10761077//---- breakpoints10781079canSetBreakpointsIn(model: ITextModel): boolean {1080return this.adapterManager.canSetBreakpointsIn(model);1081}10821083async enableOrDisableBreakpoints(enable: boolean, breakpoint?: IEnablement): Promise<void> {1084if (breakpoint) {1085this.model.setEnablement(breakpoint, enable);1086this.debugStorage.storeBreakpoints(this.model);1087if (breakpoint instanceof Breakpoint) {1088await this.makeTriggeredBreakpointsMatchEnablement(enable, breakpoint);1089await this.sendBreakpoints(breakpoint.originalUri);1090} else if (breakpoint instanceof FunctionBreakpoint) {1091await this.sendFunctionBreakpoints();1092} else if (breakpoint instanceof DataBreakpoint) {1093await this.sendDataBreakpoints();1094} else if (breakpoint instanceof InstructionBreakpoint) {1095await this.sendInstructionBreakpoints();1096} else {1097await this.sendExceptionBreakpoints();1098}1099} else {1100this.model.enableOrDisableAllBreakpoints(enable);1101this.debugStorage.storeBreakpoints(this.model);1102await this.sendAllBreakpoints();1103}1104this.debugStorage.storeBreakpoints(this.model);1105}11061107async addBreakpoints(uri: uri, rawBreakpoints: IBreakpointData[], ariaAnnounce = true): Promise<IBreakpoint[]> {1108const breakpoints = this.model.addBreakpoints(uri, rawBreakpoints);1109if (ariaAnnounce) {1110breakpoints.forEach(bp => aria.status(nls.localize('breakpointAdded', "Added breakpoint, line {0}, file {1}", bp.lineNumber, uri.fsPath)));1111}11121113// In some cases we need to store breakpoints before we send them because sending them can take a long time1114// And after sending them because the debug adapter can attach adapter data to a breakpoint1115this.debugStorage.storeBreakpoints(this.model);1116await this.sendBreakpoints(uri);1117this.debugStorage.storeBreakpoints(this.model);1118return breakpoints;1119}11201121async updateBreakpoints(uri: uri, data: Map<string, IBreakpointUpdateData>, sendOnResourceSaved: boolean): Promise<void> {1122this.model.updateBreakpoints(data);1123this.debugStorage.storeBreakpoints(this.model);1124if (sendOnResourceSaved) {1125this.breakpointsToSendOnResourceSaved.add(uri);1126} else {1127await this.sendBreakpoints(uri);1128this.debugStorage.storeBreakpoints(this.model);1129}1130}11311132async removeBreakpoints(id?: string): Promise<void> {1133const breakpoints = this.model.getBreakpoints();1134const toRemove = breakpoints.filter(bp => !id || bp.getId() === id);1135// note: using the debugger-resolved uri for aria to reflect UI state1136toRemove.forEach(bp => aria.status(nls.localize('breakpointRemoved', "Removed breakpoint, line {0}, file {1}", bp.lineNumber, bp.uri.fsPath)));1137const urisToClear = new Set(toRemove.map(bp => bp.originalUri.toString()));11381139this.model.removeBreakpoints(toRemove);1140this.unlinkTriggeredBreakpoints(breakpoints, toRemove).forEach(uri => urisToClear.add(uri.toString()));11411142this.debugStorage.storeBreakpoints(this.model);1143await Promise.all([...urisToClear].map(uri => this.sendBreakpoints(URI.parse(uri))));1144}11451146setBreakpointsActivated(activated: boolean): Promise<void> {1147this.model.setBreakpointsActivated(activated);1148return this.sendAllBreakpoints();1149}11501151async addFunctionBreakpoint(opts?: IFunctionBreakpointOptions, id?: string): Promise<void> {1152this.model.addFunctionBreakpoint(opts ?? { name: '' }, id);1153// If opts not provided, sending the breakpoint is handled by a later to call to `updateFunctionBreakpoint`1154if (opts) {1155this.debugStorage.storeBreakpoints(this.model);1156await this.sendFunctionBreakpoints();1157this.debugStorage.storeBreakpoints(this.model);1158}1159}11601161async updateFunctionBreakpoint(id: string, update: { name?: string; hitCondition?: string; condition?: string }): Promise<void> {1162this.model.updateFunctionBreakpoint(id, update);1163this.debugStorage.storeBreakpoints(this.model);1164await this.sendFunctionBreakpoints();1165}11661167async removeFunctionBreakpoints(id?: string): Promise<void> {1168this.model.removeFunctionBreakpoints(id);1169this.debugStorage.storeBreakpoints(this.model);1170await this.sendFunctionBreakpoints();1171}11721173async addDataBreakpoint(opts: IDataBreakpointOptions): Promise<void> {1174this.model.addDataBreakpoint(opts);1175this.debugStorage.storeBreakpoints(this.model);1176await this.sendDataBreakpoints();1177this.debugStorage.storeBreakpoints(this.model);1178}11791180async updateDataBreakpoint(id: string, update: { hitCondition?: string; condition?: string }): Promise<void> {1181this.model.updateDataBreakpoint(id, update);1182this.debugStorage.storeBreakpoints(this.model);1183await this.sendDataBreakpoints();1184}11851186async removeDataBreakpoints(id?: string): Promise<void> {1187this.model.removeDataBreakpoints(id);1188this.debugStorage.storeBreakpoints(this.model);1189await this.sendDataBreakpoints();1190}11911192async addInstructionBreakpoint(opts: IInstructionBreakpointOptions): Promise<void> {1193this.model.addInstructionBreakpoint(opts);1194this.debugStorage.storeBreakpoints(this.model);1195await this.sendInstructionBreakpoints();1196this.debugStorage.storeBreakpoints(this.model);1197}11981199async removeInstructionBreakpoints(instructionReference?: string, offset?: number): Promise<void> {1200this.model.removeInstructionBreakpoints(instructionReference, offset);1201this.debugStorage.storeBreakpoints(this.model);1202await this.sendInstructionBreakpoints();1203}12041205setExceptionBreakpointFallbackSession(sessionId: string) {1206this.model.setExceptionBreakpointFallbackSession(sessionId);1207this.debugStorage.storeBreakpoints(this.model);1208}12091210setExceptionBreakpointsForSession(session: IDebugSession, filters: DebugProtocol.ExceptionBreakpointsFilter[]): void {1211this.model.setExceptionBreakpointsForSession(session.getId(), filters);1212this.debugStorage.storeBreakpoints(this.model);1213}12141215async setExceptionBreakpointCondition(exceptionBreakpoint: IExceptionBreakpoint, condition: string | undefined): Promise<void> {1216this.model.setExceptionBreakpointCondition(exceptionBreakpoint, condition);1217this.debugStorage.storeBreakpoints(this.model);1218await this.sendExceptionBreakpoints();1219}12201221async sendAllBreakpoints(session?: IDebugSession): Promise<void> {1222const setBreakpointsPromises = distinct(this.model.getBreakpoints(), bp => bp.originalUri.toString())1223.map(bp => this.sendBreakpoints(bp.originalUri, false, session));12241225// If sending breakpoints to one session which we know supports the configurationDone request, can make all requests in parallel1226if (session?.capabilities.supportsConfigurationDoneRequest) {1227await Promise.all([1228...setBreakpointsPromises,1229this.sendFunctionBreakpoints(session),1230this.sendDataBreakpoints(session),1231this.sendInstructionBreakpoints(session),1232this.sendExceptionBreakpoints(session),1233]);1234} else {1235await Promise.all(setBreakpointsPromises);1236await this.sendFunctionBreakpoints(session);1237await this.sendDataBreakpoints(session);1238await this.sendInstructionBreakpoints(session);1239// send exception breakpoints at the end since some debug adapters may rely on the order - this was the case before1240// the configurationDone request was introduced.1241await this.sendExceptionBreakpoints(session);1242}1243}12441245/**1246* Removes the condition of triggered breakpoints that depended on1247* breakpoints in `removedBreakpoints`. Returns the URIs of resources that1248* had their breakpoints changed in this way.1249*/1250private unlinkTriggeredBreakpoints(allBreakpoints: readonly IBreakpoint[], removedBreakpoints: readonly IBreakpoint[]): uri[] {1251const affectedUris: uri[] = [];1252for (const removed of removedBreakpoints) {1253for (const existing of allBreakpoints) {1254if (!removedBreakpoints.includes(existing) && existing.triggeredBy === removed.getId()) {1255this.model.updateBreakpoints(new Map([[existing.getId(), { triggeredBy: undefined }]]));1256affectedUris.push(existing.originalUri);1257}1258}1259}12601261return affectedUris;1262}12631264private async makeTriggeredBreakpointsMatchEnablement(enable: boolean, breakpoint: Breakpoint) {1265if (enable) {1266/** If the breakpoint is being enabled, also ensure its triggerer is enabled */1267if (breakpoint.triggeredBy) {1268const trigger = this.model.getBreakpoints().find(bp => breakpoint.triggeredBy === bp.getId());1269if (trigger && !trigger.enabled) {1270await this.enableOrDisableBreakpoints(enable, trigger);1271}1272}1273}127412751276/** Makes its triggeree states match the state of this breakpoint */1277await Promise.all(this.model.getBreakpoints()1278.filter(bp => bp.triggeredBy === breakpoint.getId() && bp.enabled !== enable)1279.map(bp => this.enableOrDisableBreakpoints(enable, bp))1280);1281}12821283public async sendBreakpoints(modelUri: uri, sourceModified = false, session?: IDebugSession): Promise<void> {1284const breakpointsToSend = this.model.getBreakpoints({ originalUri: modelUri, enabledOnly: true });1285await sendToOneOrAllSessions(this.model, session, async s => {1286if (!s.configuration.noDebug) {1287const sessionBps = breakpointsToSend.filter(bp => !bp.triggeredBy || bp.getSessionDidTrigger(s.getId()));1288await s.sendBreakpoints(modelUri, sessionBps, sourceModified);1289}1290});1291}12921293private async sendFunctionBreakpoints(session?: IDebugSession): Promise<void> {1294const breakpointsToSend = this.model.getFunctionBreakpoints().filter(fbp => fbp.enabled && this.model.areBreakpointsActivated());12951296await sendToOneOrAllSessions(this.model, session, async s => {1297if (s.capabilities.supportsFunctionBreakpoints && !s.configuration.noDebug) {1298await s.sendFunctionBreakpoints(breakpointsToSend);1299}1300});1301}13021303private async sendDataBreakpoints(session?: IDebugSession): Promise<void> {1304const breakpointsToSend = this.model.getDataBreakpoints().filter(fbp => fbp.enabled && this.model.areBreakpointsActivated());13051306await sendToOneOrAllSessions(this.model, session, async s => {1307if (s.capabilities.supportsDataBreakpoints && !s.configuration.noDebug) {1308await s.sendDataBreakpoints(breakpointsToSend);1309}1310});1311}13121313private async sendInstructionBreakpoints(session?: IDebugSession): Promise<void> {1314const breakpointsToSend = this.model.getInstructionBreakpoints().filter(fbp => fbp.enabled && this.model.areBreakpointsActivated());13151316await sendToOneOrAllSessions(this.model, session, async s => {1317if (s.capabilities.supportsInstructionBreakpoints && !s.configuration.noDebug) {1318await s.sendInstructionBreakpoints(breakpointsToSend);1319}1320});1321}13221323private sendExceptionBreakpoints(session?: IDebugSession): Promise<void> {1324return sendToOneOrAllSessions(this.model, session, async s => {1325const enabledExceptionBps = this.model.getExceptionBreakpointsForSession(s.getId()).filter(exb => exb.enabled);1326if (s.capabilities.supportsConfigurationDoneRequest && (!s.capabilities.exceptionBreakpointFilters || s.capabilities.exceptionBreakpointFilters.length === 0)) {1327// Only call `setExceptionBreakpoints` as specified in dap protocol #900011328return;1329}1330if (!s.configuration.noDebug) {1331await s.sendExceptionBreakpoints(enabledExceptionBps);1332}1333});1334}13351336private onFileChanges(fileChangesEvent: FileChangesEvent): void {1337const toRemove = this.model.getBreakpoints().filter(bp =>1338fileChangesEvent.contains(bp.originalUri, FileChangeType.DELETED));1339if (toRemove.length) {1340this.model.removeBreakpoints(toRemove);1341}13421343const toSend: URI[] = [];1344for (const uri of this.breakpointsToSendOnResourceSaved) {1345if (fileChangesEvent.contains(uri, FileChangeType.UPDATED)) {1346toSend.push(uri);1347}1348}13491350for (const uri of toSend) {1351this.breakpointsToSendOnResourceSaved.delete(uri);1352this.sendBreakpoints(uri, true);1353}1354}13551356async runTo(uri: uri, lineNumber: number, column?: number): Promise<void> {1357let breakpointToRemove: IBreakpoint | undefined;1358let threadToContinue = this.getViewModel().focusedThread;1359const addTempBreakPoint = async () => {1360const bpExists = !!(this.getModel().getBreakpoints({ column, lineNumber, uri }).length);13611362if (!bpExists) {1363const addResult = await this.addAndValidateBreakpoints(uri, lineNumber, column);1364if (addResult.thread) {1365threadToContinue = addResult.thread;1366}13671368if (addResult.breakpoint) {1369breakpointToRemove = addResult.breakpoint;1370}1371}1372return { threadToContinue, breakpointToRemove };1373};1374const removeTempBreakPoint = (state: State): boolean => {1375if (state === State.Stopped || state === State.Inactive) {1376if (breakpointToRemove) {1377this.removeBreakpoints(breakpointToRemove.getId());1378}1379return true;1380}1381return false;1382};13831384await addTempBreakPoint();1385if (this.state === State.Inactive) {1386// If no session exists start the debugger1387const { launch, name, getConfig } = this.getConfigurationManager().selectedConfiguration;1388const config = await getConfig();1389const configOrName = config ? Object.assign(deepClone(config), {}) : name;1390const listener = this.onDidChangeState(state => {1391if (removeTempBreakPoint(state)) {1392listener.dispose();1393}1394});1395await this.startDebugging(launch, configOrName, undefined, true);1396}1397if (this.state === State.Stopped) {1398const focusedSession = this.getViewModel().focusedSession;1399if (!focusedSession || !threadToContinue) {1400return;1401}14021403const listener = threadToContinue.session.onDidChangeState(() => {1404if (removeTempBreakPoint(focusedSession.state)) {1405listener.dispose();1406}1407});1408await threadToContinue.continue();1409}1410}14111412private async addAndValidateBreakpoints(uri: URI, lineNumber: number, column?: number) {1413const debugModel = this.getModel();1414const viewModel = this.getViewModel();14151416const breakpoints = await this.addBreakpoints(uri, [{ lineNumber, column }], false);1417const breakpoint = breakpoints?.[0];1418if (!breakpoint) {1419return { breakpoint: undefined, thread: viewModel.focusedThread };1420}14211422// If the breakpoint was not initially verified, wait up to 2s for it to become so.1423// Inherently racey if multiple sessions can verify async, but not solvable...1424if (!breakpoint.verified) {1425let listener: IDisposable;1426await raceTimeout(new Promise<void>(resolve => {1427listener = debugModel.onDidChangeBreakpoints(() => {1428if (breakpoint.verified) {1429resolve();1430}1431});1432}), 2000);1433listener!.dispose();1434}14351436// Look at paused threads for sessions that verified this bp. Prefer, in order:1437const enum Score {1438/** The focused thread */1439Focused,1440/** Any other stopped thread of a session that verified the bp */1441Verified,1442/** Any thread that verified and paused in the same file */1443VerifiedAndPausedInFile,1444/** The focused thread if it verified the breakpoint */1445VerifiedAndFocused,1446}14471448let bestThread = viewModel.focusedThread;1449let bestScore = Score.Focused;1450for (const sessionId of breakpoint.sessionsThatVerified) {1451const session = debugModel.getSession(sessionId);1452if (!session) {1453continue;1454}14551456const threads = session.getAllThreads().filter(t => t.stopped);1457if (bestScore < Score.VerifiedAndFocused) {1458if (viewModel.focusedThread && threads.includes(viewModel.focusedThread)) {1459bestThread = viewModel.focusedThread;1460bestScore = Score.VerifiedAndFocused;1461}1462}14631464if (bestScore < Score.VerifiedAndPausedInFile) {1465const pausedInThisFile = threads.find(t => {1466const top = t.getTopStackFrame();1467return top && this.uriIdentityService.extUri.isEqual(top.source.uri, uri);1468});14691470if (pausedInThisFile) {1471bestThread = pausedInThisFile;1472bestScore = Score.VerifiedAndPausedInFile;1473}1474}14751476if (bestScore < Score.Verified) {1477bestThread = threads[0];1478bestScore = Score.VerifiedAndPausedInFile;1479}1480}14811482return { thread: bestThread, breakpoint };1483}1484}14851486export function getStackFrameThreadAndSessionToFocus(model: IDebugModel, stackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, avoidSession?: IDebugSession): { stackFrame: IStackFrame | undefined; thread: IThread | undefined; session: IDebugSession | undefined } {1487if (!session) {1488if (stackFrame || thread) {1489session = stackFrame ? stackFrame.thread.session : thread!.session;1490} else {1491const sessions = model.getSessions();1492const stoppedSession = sessions.find(s => s.state === State.Stopped);1493// Make sure to not focus session that is going down1494session = stoppedSession || sessions.find(s => s !== avoidSession && s !== avoidSession?.parentSession) || (sessions.length ? sessions[0] : undefined);1495}1496}14971498if (!thread) {1499if (stackFrame) {1500thread = stackFrame.thread;1501} else {1502const threads = session ? session.getAllThreads() : undefined;1503const stoppedThread = threads && threads.find(t => t.stopped);1504thread = stoppedThread || (threads && threads.length ? threads[0] : undefined);1505}1506}15071508if (!stackFrame && thread) {1509stackFrame = thread.getTopStackFrame();1510}15111512return { session, thread, stackFrame };1513}15141515async function sendToOneOrAllSessions(model: DebugModel, session: IDebugSession | undefined, send: (session: IDebugSession) => Promise<void>): Promise<void> {1516if (session) {1517await send(session);1518} else {1519await Promise.all(model.getSessions().map(s => send(s)));1520}1521}152215231524