Path: blob/main/src/vs/workbench/contrib/debug/browser/debugService.ts
5242 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 { IAction, toAction } 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 = this.disposables.add(new Emitter<State>());120this._onDidNewSession = this.disposables.add(new Emitter<IDebugSession>());121this._onWillNewSession = this.disposables.add(new Emitter<IDebugSession>());122this._onDidEndSession = this.disposables.add(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(toAction({575id: 'installAdditionalDebuggers',576label: nls.localize({ key: 'installAdditionalDebuggers', comment: ['Placeholder is the debug type, so for example "node", "python"'] }, "Install {0} Extension", resolvedConfig.type),577enabled: true,578run: async () => this.commandService.executeCommand('debug.installAdditionalDebuggers', resolvedConfig?.type)579}));580581await this.showError(message, actionList); return false;582}583584if (!dbg.enabled) {585await this.showError(debuggerDisabledMessage(dbg.type), []);586return false;587}588589const result = await this.doCreateSession(sessionId, launch?.workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options, userConfirmedConcurrentSession);590if (result && guess && activeEditor && activeEditor.resource) {591// Remeber user choice of environment per active editor to make starting debugging smoother #124770592this.chosenEnvironments[activeEditor.resource.toString()] = { type: guess.debugger.type, dynamicLabel: guess.withConfig?.label };593this.debugStorage.storeChosenEnvironments(this.chosenEnvironments);594}595return result;596} catch (err) {597if (err && err.message) {598await this.showError(err.message);599} else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {600await 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."));601}602if (launch && !initCancellationToken.token.isCancellationRequested) {603await launch.openConfigFile({ preserveFocus: true }, initCancellationToken.token);604}605606return false;607}608}609610if (launch && type && configByProviders === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null".611await launch.openConfigFile({ preserveFocus: true, type }, initCancellationToken.token);612}613614return false;615}616617/**618* instantiates the new session, initializes the session, registers session listeners and reports telemetry619*/620private async doCreateSession(sessionId: string, root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig; unresolved: IConfig | undefined }, options?: IDebugSessionOptions, userConfirmedConcurrentSession = false): Promise<boolean> {621622const session = this.instantiationService.createInstance(DebugSession, sessionId, configuration, root, this.model, options);623if (!userConfirmedConcurrentSession && options?.startedByUser && this.model.getSessions().some(s =>624s.configuration.name === configuration.resolved.name &&625s.configuration.type === configuration.resolved.type &&626s.configuration.request === configuration.resolved.request &&627s.root === root628) && configuration.resolved.suppressMultipleSessionWarning !== true) {629// There is already a session with the same configuration, prompt user #127721630const confirmed = await this.confirmConcurrentSession(session.getLabel());631if (!confirmed) {632return false;633}634}635636this.model.addSession(session);637638// since the Session is now properly registered under its ID and hooked, we can announce it639// this event doesn't go to extensions640this._onWillNewSession.fire(session);641642const openDebug = this.configurationService.getValue<IDebugConfiguration>('debug').openDebug;643// Open debug viewlet based on the visibility of the side bar and openDebug setting. Do not open for 'run without debug'644if (!configuration.resolved.noDebug && (openDebug === 'openOnSessionStart' || (openDebug !== 'neverOpen' && this.viewModel.firstSessionStart)) && !session.suppressDebugView) {645await this.paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar);646}647648try {649await this.launchOrAttachToSession(session);650651const internalConsoleOptions = session.configuration.internalConsoleOptions || this.configurationService.getValue<IDebugConfiguration>('debug').internalConsoleOptions;652if (internalConsoleOptions === 'openOnSessionStart' || (this.viewModel.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) {653this.viewsService.openView(REPL_VIEW_ID, false);654}655656this.viewModel.firstSessionStart = false;657const showSubSessions = this.configurationService.getValue<IDebugConfiguration>('debug').showSubSessionsInToolBar;658const sessions = this.model.getSessions();659const shownSessions = showSubSessions ? sessions : sessions.filter(s => !s.parentSession);660if (shownSessions.length > 1) {661this.viewModel.setMultiSessionView(true);662}663664// since the initialized response has arrived announce the new Session (including extensions)665this._onDidNewSession.fire(session);666667return true;668} catch (error) {669670if (errors.isCancellationError(error)) {671// don't show 'canceled' error messages to the user #7906672return false;673}674675// Show the repl if some error got logged there #5870676if (session && session.getReplElements().length > 0) {677this.viewsService.openView(REPL_VIEW_ID, false);678}679680if (session.configuration && session.configuration.request === 'attach' && session.configuration.__autoAttach) {681// ignore attach timeouts in auto attach mode682return false;683}684685const errorMessage = error instanceof Error ? error.message : error;686if (error.showUser !== false) {687// Only show the error when showUser is either not defined, or is true #128484688await this.showError(errorMessage, isErrorWithActions(error) ? error.actions : []);689}690return false;691}692}693694private async confirmConcurrentSession(sessionLabel: string): Promise<boolean> {695const result = await this.dialogService.confirm({696message: nls.localize('multipleSession', "'{0}' is already running. Do you want to start another instance?", sessionLabel)697});698return result.confirmed;699}700701private async launchOrAttachToSession(session: IDebugSession, forceFocus = false): Promise<void> {702// register listeners as the very first thing!703this.registerSessionListeners(session);704705const dbgr = this.adapterManager.getDebugger(session.configuration.type);706try {707await session.initialize(dbgr!);708await session.launchOrAttach(session.configuration);709const launchJsonExists = !!session.root && !!this.configurationService.getValue<IGlobalConfig>('launch', { resource: session.root.uri });710await this.telemetry.logDebugSessionStart(dbgr!, launchJsonExists);711712if (forceFocus || !this.viewModel.focusedSession || (session.parentSession === this.viewModel.focusedSession && session.compact)) {713await this.focusStackFrame(undefined, undefined, session);714}715} catch (err) {716if (this.viewModel.focusedSession === session) {717await this.focusStackFrame(undefined);718}719return Promise.reject(err);720}721}722723private registerSessionListeners(session: IDebugSession): void {724const listenerDisposables = new DisposableStore();725this.disposables.add(listenerDisposables);726727const sessionRunningScheduler = listenerDisposables.add(new RunOnceScheduler(() => {728// Do not immediatly defocus the stack frame if the session is running729if (session.state === State.Running && this.viewModel.focusedSession === session) {730this.viewModel.setFocus(undefined, this.viewModel.focusedThread, session, false);731}732}, 200));733listenerDisposables.add(session.onDidChangeState(() => {734if (session.state === State.Running && this.viewModel.focusedSession === session) {735sessionRunningScheduler.schedule();736}737if (session === this.viewModel.focusedSession) {738this.onStateChange();739}740}));741listenerDisposables.add(this.onDidEndSession(e => {742if (e.session === session) {743this.disposables.delete(listenerDisposables);744}745}));746listenerDisposables.add(session.onDidEndAdapter(async adapterExitEvent => {747748if (adapterExitEvent) {749if (adapterExitEvent.error) {750this.notificationService.error(nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly ({0})", adapterExitEvent.error.message || adapterExitEvent.error.toString()));751}752this.telemetry.logDebugSessionStop(session, adapterExitEvent);753}754755// 'Run without debugging' mode VSCode must terminate the extension host. More details: #3905756const extensionDebugSession = getExtensionHostDebugSession(session);757if (extensionDebugSession && extensionDebugSession.state === State.Running && extensionDebugSession.configuration.noDebug) {758this.extensionHostDebugService.close(extensionDebugSession.getId());759}760761if (session.configuration.postDebugTask) {762const root = session.root ?? this.contextService.getWorkspace();763try {764await this.taskRunner.runTask(root, session.configuration.postDebugTask);765} catch (err) {766this.notificationService.error(err);767}768}769this.endInitializingState();770this.cancelTokens(session.getId());771772if (this.configurationService.getValue<IDebugConfiguration>('debug').closeReadonlyTabsOnEnd) {773const editorsToClose = this.editorService.getEditors(EditorsOrder.SEQUENTIAL).filter(({ editor }) => {774return editor.resource?.scheme === DEBUG_SCHEME && session.getId() === Source.getEncodedDebugData(editor.resource).sessionId;775});776this.editorService.closeEditors(editorsToClose);777}778this._onDidEndSession.fire({ session, restart: this.restartingSessions.has(session) });779780const focusedSession = this.viewModel.focusedSession;781if (focusedSession && focusedSession.getId() === session.getId()) {782const { session, thread, stackFrame } = getStackFrameThreadAndSessionToFocus(this.model, undefined, undefined, undefined, focusedSession);783this.viewModel.setFocus(stackFrame, thread, session, false);784}785786if (this.model.getSessions().length === 0) {787this.viewModel.setMultiSessionView(false);788789if (this.layoutService.isVisible(Parts.SIDEBAR_PART) && this.configurationService.getValue<IDebugConfiguration>('debug').openExplorerOnEnd) {790this.paneCompositeService.openPaneComposite(EXPLORER_VIEWLET_ID, ViewContainerLocation.Sidebar);791}792793// Data breakpoints that can not be persisted should be cleared when a session ends794const dataBreakpoints = this.model.getDataBreakpoints().filter(dbp => !dbp.canPersist);795dataBreakpoints.forEach(dbp => this.model.removeDataBreakpoints(dbp.getId()));796797if (this.configurationService.getValue<IDebugConfiguration>('debug').console.closeOnEnd) {798const debugConsoleContainer = this.viewDescriptorService.getViewContainerByViewId(REPL_VIEW_ID);799if (debugConsoleContainer && this.viewsService.isViewContainerVisible(debugConsoleContainer.id)) {800this.viewsService.closeViewContainer(debugConsoleContainer.id);801}802}803}804805this.model.removeExceptionBreakpointsForSession(session.getId());806// session.dispose(); TODO@roblourens807}));808}809810async restartSession(session: IDebugSession, restartData?: any): Promise<void> {811if (session.saveBeforeRestart) {812await saveAllBeforeDebugStart(this.configurationService, this.editorService);813}814815const isAutoRestart = !!restartData;816817const runTasks: () => Promise<TaskRunResult> = async () => {818if (isAutoRestart) {819// Do not run preLaunch and postDebug tasks for automatic restarts820return Promise.resolve(TaskRunResult.Success);821}822823const root = session.root || this.contextService.getWorkspace();824await this.taskRunner.runTask(root, session.configuration.preRestartTask);825await this.taskRunner.runTask(root, session.configuration.postDebugTask);826827const taskResult1 = await this.taskRunner.runTaskAndCheckErrors(root, session.configuration.preLaunchTask);828if (taskResult1 !== TaskRunResult.Success) {829return taskResult1;830}831832return this.taskRunner.runTaskAndCheckErrors(root, session.configuration.postRestartTask);833};834835const extensionDebugSession = getExtensionHostDebugSession(session);836if (extensionDebugSession) {837const taskResult = await runTasks();838if (taskResult === TaskRunResult.Success) {839this.extensionHostDebugService.reload(extensionDebugSession.getId());840}841842return;843}844845// Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration846let needsToSubstitute = false;847let unresolved: IConfig | undefined;848const launch = session.root ? this.configurationManager.getLaunch(session.root.uri) : undefined;849if (launch) {850unresolved = launch.getConfiguration(session.configuration.name);851if (unresolved && !equals(unresolved, session.unresolvedConfiguration)) {852unresolved.noDebug = session.configuration.noDebug;853needsToSubstitute = true;854}855}856857let resolved: IConfig | undefined | null = session.configuration;858if (launch && needsToSubstitute && unresolved) {859const initCancellationToken = new CancellationTokenSource();860this.sessionCancellationTokens.set(session.getId(), initCancellationToken);861const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, initCancellationToken.token);862if (resolvedByProviders) {863resolved = await this.substituteVariables(launch, resolvedByProviders);864if (resolved && !initCancellationToken.token.isCancellationRequested) {865resolved = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, resolved.type, resolved, initCancellationToken.token);866}867} else {868resolved = resolvedByProviders;869}870}871if (resolved) {872session.setConfiguration({ resolved, unresolved });873}874session.configuration.__restart = restartData;875876const doRestart = async (fn: () => Promise<boolean | undefined>) => {877this.restartingSessions.add(session);878let didRestart = false;879try {880didRestart = (await fn()) !== false;881} catch (e) {882didRestart = false;883throw e;884} finally {885this.restartingSessions.delete(session);886// we previously may have issued an onDidEndSession with restart: true,887// assuming the adapter exited (in `registerSessionListeners`). But the888// restart failed, so emit the final termination now.889if (!didRestart) {890this._onDidEndSession.fire({ session, restart: false });891}892}893};894895for (const breakpoint of this.model.getBreakpoints({ triggeredOnly: true })) {896breakpoint.setSessionDidTrigger(session.getId(), false);897}898899// For debug sessions spawned by test runs, cancel the test run and stop900// the session, then start the test run again; tests have no notion of restarts.901if (session.correlatedTestRun) {902if (!session.correlatedTestRun.completedAt) {903session.cancelCorrelatedTestRun();904await Event.toPromise(session.correlatedTestRun.onComplete);905// todo@connor4312 is there any reason to wait for the debug session to906// terminate? I don't think so, test extension should already handle any907// state conflicts...908}909910this.testService.runResolvedTests(session.correlatedTestRun.request);911return;912}913914if (session.capabilities.supportsRestartRequest) {915const taskResult = await runTasks();916if (taskResult === TaskRunResult.Success) {917await doRestart(async () => {918await session.restart();919return true;920});921}922923return;924}925926const shouldFocus = !!this.viewModel.focusedSession && session.getId() === this.viewModel.focusedSession.getId();927return doRestart(async () => {928// If the restart is automatic -> disconnect, otherwise -> terminate #55064929if (isAutoRestart) {930await session.disconnect(true);931} else {932await session.terminate(true);933}934935return new Promise<boolean>((c, e) => {936setTimeout(async () => {937const taskResult = await runTasks();938if (taskResult !== TaskRunResult.Success) {939return c(false);940}941942if (!resolved) {943return c(false);944}945946try {947await this.launchOrAttachToSession(session, shouldFocus);948this._onDidNewSession.fire(session);949c(true);950} catch (error) {951e(error);952}953}, 300);954});955});956}957958async stopSession(session: IDebugSession | undefined, disconnect = false, suspend = false): Promise<any> {959if (session) {960return disconnect ? session.disconnect(undefined, suspend) : session.terminate();961}962963const sessions = this.model.getSessions();964if (sessions.length === 0) {965this.taskRunner.cancel();966// User might have cancelled starting of a debug session, and in some cases the quick pick is left open967await this.quickInputService.cancel();968this.endInitializingState();969this.cancelTokens(undefined);970}971972return Promise.all(sessions.map(s => disconnect ? s.disconnect(undefined, suspend) : s.terminate()));973}974975private async substituteVariables(launch: ILaunch | undefined, config: IConfig): Promise<IConfig | undefined> {976const dbg = this.adapterManager.getDebugger(config.type);977if (dbg) {978let folder: IWorkspaceFolder | undefined = undefined;979if (launch && launch.workspace) {980folder = launch.workspace;981} else {982const folders = this.contextService.getWorkspace().folders;983if (folders.length === 1) {984folder = folders[0];985}986}987try {988return await dbg.substituteVariables(folder, config);989} catch (err) {990if (err.message !== errors.canceledName) {991this.showError(err.message, undefined, !!launch?.getConfiguration(config.name));992}993return undefined; // bail out994}995}996return Promise.resolve(config);997}998999private async showError(message: string, errorActions: ReadonlyArray<IAction> = [], promptLaunchJson = true): Promise<void> {1000const configureAction = toAction({ id: DEBUG_CONFIGURE_COMMAND_ID, label: DEBUG_CONFIGURE_LABEL, enabled: true, run: () => this.commandService.executeCommand(DEBUG_CONFIGURE_COMMAND_ID) });1001// Don't append the standard command if id of any provided action indicates it is a command1002const actions = errorActions.filter((action) => action.id.endsWith('.command')).length > 0 ?1003errorActions :1004[...errorActions, ...(promptLaunchJson ? [configureAction] : [])];1005await this.dialogService.prompt({1006type: severity.Error,1007message,1008buttons: actions.map(action => ({1009label: action.label,1010run: () => action.run()1011})),1012cancelButton: true1013});1014}10151016//---- focus management10171018async focusStackFrame(_stackFrame: IStackFrame | undefined, _thread?: IThread, _session?: IDebugSession, options?: { explicit?: boolean; preserveFocus?: boolean; sideBySide?: boolean; pinned?: boolean }): Promise<void> {1019const { stackFrame, thread, session } = getStackFrameThreadAndSessionToFocus(this.model, _stackFrame, _thread, _session);10201021if (stackFrame) {1022const editor = await stackFrame.openInEditor(this.editorService, options?.preserveFocus ?? true, options?.sideBySide, options?.pinned);1023if (editor) {1024if (editor.input === DisassemblyViewInput.instance) {1025// Go to address is invoked via setFocus1026} else {1027const control = editor.getControl();1028if (stackFrame && isCodeEditor(control) && control.hasModel()) {1029const model = control.getModel();1030const lineNumber = stackFrame.range.startLineNumber;1031if (lineNumber >= 1 && lineNumber <= model.getLineCount()) {1032const lineContent = control.getModel().getLineContent(lineNumber);1033aria.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.'] },1034"{0}, debugging paused {1}, {2}:{3}", lineContent, thread && thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber));1035}1036}1037}1038}1039}1040if (session) {1041this.debugType.set(session.configuration.type);1042} else {1043this.debugType.reset();1044}10451046this.viewModel.setFocus(stackFrame, thread, session, !!options?.explicit);1047}10481049//---- watches10501051addWatchExpression(name?: string): void {1052const we = this.model.addWatchExpression(name);1053if (!name) {1054this.viewModel.setSelectedExpression(we, false);1055}1056this.debugStorage.storeWatchExpressions(this.model.getWatchExpressions());1057}10581059renameWatchExpression(id: string, newName: string): void {1060this.model.renameWatchExpression(id, newName);1061this.debugStorage.storeWatchExpressions(this.model.getWatchExpressions());1062}10631064moveWatchExpression(id: string, position: number): void {1065this.model.moveWatchExpression(id, position);1066this.debugStorage.storeWatchExpressions(this.model.getWatchExpressions());1067}10681069removeWatchExpressions(id?: string): void {1070this.model.removeWatchExpressions(id);1071this.debugStorage.storeWatchExpressions(this.model.getWatchExpressions());1072}10731074//---- breakpoints10751076canSetBreakpointsIn(model: ITextModel): boolean {1077return this.adapterManager.canSetBreakpointsIn(model);1078}10791080async enableOrDisableBreakpoints(enable: boolean, breakpoint?: IEnablement): Promise<void> {1081if (breakpoint) {1082this.model.setEnablement(breakpoint, enable);1083this.debugStorage.storeBreakpoints(this.model);1084if (breakpoint instanceof Breakpoint) {1085await this.makeTriggeredBreakpointsMatchEnablement(enable, breakpoint);1086await this.sendBreakpoints(breakpoint.originalUri);1087} else if (breakpoint instanceof FunctionBreakpoint) {1088await this.sendFunctionBreakpoints();1089} else if (breakpoint instanceof DataBreakpoint) {1090await this.sendDataBreakpoints();1091} else if (breakpoint instanceof InstructionBreakpoint) {1092await this.sendInstructionBreakpoints();1093} else {1094await this.sendExceptionBreakpoints();1095}1096} else {1097this.model.enableOrDisableAllBreakpoints(enable);1098this.debugStorage.storeBreakpoints(this.model);1099await this.sendAllBreakpoints();1100}1101this.debugStorage.storeBreakpoints(this.model);1102}11031104async addBreakpoints(uri: uri, rawBreakpoints: IBreakpointData[], ariaAnnounce = true): Promise<IBreakpoint[]> {1105const breakpoints = this.model.addBreakpoints(uri, rawBreakpoints);1106if (ariaAnnounce) {1107breakpoints.forEach(bp => aria.status(nls.localize('breakpointAdded', "Added breakpoint, line {0}, file {1}", bp.lineNumber, uri.fsPath)));1108}11091110// In some cases we need to store breakpoints before we send them because sending them can take a long time1111// And after sending them because the debug adapter can attach adapter data to a breakpoint1112this.debugStorage.storeBreakpoints(this.model);1113await this.sendBreakpoints(uri);1114this.debugStorage.storeBreakpoints(this.model);1115return breakpoints;1116}11171118async updateBreakpoints(uri: uri, data: Map<string, IBreakpointUpdateData>, sendOnResourceSaved: boolean): Promise<void> {1119this.model.updateBreakpoints(data);1120this.debugStorage.storeBreakpoints(this.model);1121if (sendOnResourceSaved) {1122this.breakpointsToSendOnResourceSaved.add(uri);1123} else {1124await this.sendBreakpoints(uri);1125this.debugStorage.storeBreakpoints(this.model);1126}1127}11281129async removeBreakpoints(id?: string | string[]): Promise<void> {1130const breakpoints = this.model.getBreakpoints();1131const toRemove = id === undefined1132? breakpoints1133: id instanceof Array1134? breakpoints.filter(bp => id.includes(bp.getId()))1135: breakpoints.filter(bp => bp.getId() === id);1136// note: using the debugger-resolved uri for aria to reflect UI state1137toRemove.forEach(bp => aria.status(nls.localize('breakpointRemoved', "Removed breakpoint, line {0}, file {1}", bp.lineNumber, bp.uri.fsPath)));1138const urisToClear = new Set(toRemove.map(bp => bp.originalUri.toString()));11391140this.model.removeBreakpoints(toRemove);1141this.unlinkTriggeredBreakpoints(breakpoints, toRemove).forEach(uri => urisToClear.add(uri.toString()));11421143this.debugStorage.storeBreakpoints(this.model);1144await Promise.all([...urisToClear].map(uri => this.sendBreakpoints(URI.parse(uri))));1145}11461147setBreakpointsActivated(activated: boolean): Promise<void> {1148this.model.setBreakpointsActivated(activated);1149return this.sendAllBreakpoints();1150}11511152async addFunctionBreakpoint(opts?: IFunctionBreakpointOptions, id?: string): Promise<void> {1153this.model.addFunctionBreakpoint(opts ?? { name: '' }, id);1154// If opts not provided, sending the breakpoint is handled by a later to call to `updateFunctionBreakpoint`1155if (opts) {1156this.debugStorage.storeBreakpoints(this.model);1157await this.sendFunctionBreakpoints();1158this.debugStorage.storeBreakpoints(this.model);1159}1160}11611162async updateFunctionBreakpoint(id: string, update: { name?: string; hitCondition?: string; condition?: string }): Promise<void> {1163this.model.updateFunctionBreakpoint(id, update);1164this.debugStorage.storeBreakpoints(this.model);1165await this.sendFunctionBreakpoints();1166}11671168async removeFunctionBreakpoints(id?: string): Promise<void> {1169this.model.removeFunctionBreakpoints(id);1170this.debugStorage.storeBreakpoints(this.model);1171await this.sendFunctionBreakpoints();1172}11731174async addDataBreakpoint(opts: IDataBreakpointOptions): Promise<void> {1175this.model.addDataBreakpoint(opts);1176this.debugStorage.storeBreakpoints(this.model);1177await this.sendDataBreakpoints();1178this.debugStorage.storeBreakpoints(this.model);1179}11801181async updateDataBreakpoint(id: string, update: { hitCondition?: string; condition?: string }): Promise<void> {1182this.model.updateDataBreakpoint(id, update);1183this.debugStorage.storeBreakpoints(this.model);1184await this.sendDataBreakpoints();1185}11861187async removeDataBreakpoints(id?: string): Promise<void> {1188this.model.removeDataBreakpoints(id);1189this.debugStorage.storeBreakpoints(this.model);1190await this.sendDataBreakpoints();1191}11921193async addInstructionBreakpoint(opts: IInstructionBreakpointOptions): Promise<void> {1194this.model.addInstructionBreakpoint(opts);1195this.debugStorage.storeBreakpoints(this.model);1196await this.sendInstructionBreakpoints();1197this.debugStorage.storeBreakpoints(this.model);1198}11991200async removeInstructionBreakpoints(instructionReference?: string, offset?: number): Promise<void> {1201this.model.removeInstructionBreakpoints(instructionReference, offset);1202this.debugStorage.storeBreakpoints(this.model);1203await this.sendInstructionBreakpoints();1204}12051206setExceptionBreakpointFallbackSession(sessionId: string) {1207this.model.setExceptionBreakpointFallbackSession(sessionId);1208this.debugStorage.storeBreakpoints(this.model);1209}12101211setExceptionBreakpointsForSession(session: IDebugSession, filters: DebugProtocol.ExceptionBreakpointsFilter[]): void {1212this.model.setExceptionBreakpointsForSession(session.getId(), filters);1213this.debugStorage.storeBreakpoints(this.model);1214}12151216async setExceptionBreakpointCondition(exceptionBreakpoint: IExceptionBreakpoint, condition: string | undefined): Promise<void> {1217this.model.setExceptionBreakpointCondition(exceptionBreakpoint, condition);1218this.debugStorage.storeBreakpoints(this.model);1219await this.sendExceptionBreakpoints();1220}12211222async sendAllBreakpoints(session?: IDebugSession): Promise<void> {1223const setBreakpointsPromises = distinct(this.model.getBreakpoints(), bp => bp.originalUri.toString())1224.map(bp => this.sendBreakpoints(bp.originalUri, false, session));12251226// If sending breakpoints to one session which we know supports the configurationDone request, can make all requests in parallel1227if (session?.capabilities.supportsConfigurationDoneRequest) {1228await Promise.all([1229...setBreakpointsPromises,1230this.sendFunctionBreakpoints(session),1231this.sendDataBreakpoints(session),1232this.sendInstructionBreakpoints(session),1233this.sendExceptionBreakpoints(session),1234]);1235} else {1236await Promise.all(setBreakpointsPromises);1237await this.sendFunctionBreakpoints(session);1238await this.sendDataBreakpoints(session);1239await this.sendInstructionBreakpoints(session);1240// send exception breakpoints at the end since some debug adapters may rely on the order - this was the case before1241// the configurationDone request was introduced.1242await this.sendExceptionBreakpoints(session);1243}1244}12451246/**1247* Removes the condition of triggered breakpoints that depended on1248* breakpoints in `removedBreakpoints`. Returns the URIs of resources that1249* had their breakpoints changed in this way.1250*/1251private unlinkTriggeredBreakpoints(allBreakpoints: readonly IBreakpoint[], removedBreakpoints: readonly IBreakpoint[]): uri[] {1252const affectedUris: uri[] = [];1253for (const removed of removedBreakpoints) {1254for (const existing of allBreakpoints) {1255if (!removedBreakpoints.includes(existing) && existing.triggeredBy === removed.getId()) {1256this.model.updateBreakpoints(new Map([[existing.getId(), { triggeredBy: undefined }]]));1257affectedUris.push(existing.originalUri);1258}1259}1260}12611262return affectedUris;1263}12641265private async makeTriggeredBreakpointsMatchEnablement(enable: boolean, breakpoint: Breakpoint) {1266if (enable) {1267/** If the breakpoint is being enabled, also ensure its triggerer is enabled */1268if (breakpoint.triggeredBy) {1269const trigger = this.model.getBreakpoints().find(bp => breakpoint.triggeredBy === bp.getId());1270if (trigger && !trigger.enabled) {1271await this.enableOrDisableBreakpoints(enable, trigger);1272}1273}1274}127512761277/** Makes its triggeree states match the state of this breakpoint */1278await Promise.all(this.model.getBreakpoints()1279.filter(bp => bp.triggeredBy === breakpoint.getId() && bp.enabled !== enable)1280.map(bp => this.enableOrDisableBreakpoints(enable, bp))1281);1282}12831284public async sendBreakpoints(modelUri: uri, sourceModified = false, session?: IDebugSession): Promise<void> {1285const breakpointsToSend = this.model.getBreakpoints({ originalUri: modelUri, enabledOnly: true });1286await sendToOneOrAllSessions(this.model, session, async s => {1287if (!s.configuration.noDebug) {1288const sessionBps = breakpointsToSend.filter(bp => !bp.triggeredBy || bp.getSessionDidTrigger(s.getId()));1289await s.sendBreakpoints(modelUri, sessionBps, sourceModified);1290}1291});1292}12931294private async sendFunctionBreakpoints(session?: IDebugSession): Promise<void> {1295const breakpointsToSend = this.model.getFunctionBreakpoints().filter(fbp => fbp.enabled && this.model.areBreakpointsActivated());12961297await sendToOneOrAllSessions(this.model, session, async s => {1298if (s.capabilities.supportsFunctionBreakpoints && !s.configuration.noDebug) {1299await s.sendFunctionBreakpoints(breakpointsToSend);1300}1301});1302}13031304private async sendDataBreakpoints(session?: IDebugSession): Promise<void> {1305const breakpointsToSend = this.model.getDataBreakpoints().filter(fbp => fbp.enabled && this.model.areBreakpointsActivated());13061307await sendToOneOrAllSessions(this.model, session, async s => {1308if (s.capabilities.supportsDataBreakpoints && !s.configuration.noDebug) {1309await s.sendDataBreakpoints(breakpointsToSend);1310}1311});1312}13131314private async sendInstructionBreakpoints(session?: IDebugSession): Promise<void> {1315const breakpointsToSend = this.model.getInstructionBreakpoints().filter(fbp => fbp.enabled && this.model.areBreakpointsActivated());13161317await sendToOneOrAllSessions(this.model, session, async s => {1318if (s.capabilities.supportsInstructionBreakpoints && !s.configuration.noDebug) {1319await s.sendInstructionBreakpoints(breakpointsToSend);1320}1321});1322}13231324private sendExceptionBreakpoints(session?: IDebugSession): Promise<void> {1325return sendToOneOrAllSessions(this.model, session, async s => {1326const enabledExceptionBps = this.model.getExceptionBreakpointsForSession(s.getId()).filter(exb => exb.enabled);1327if (s.capabilities.supportsConfigurationDoneRequest && (!s.capabilities.exceptionBreakpointFilters || s.capabilities.exceptionBreakpointFilters.length === 0)) {1328// Only call `setExceptionBreakpoints` as specified in dap protocol #900011329return;1330}1331if (!s.configuration.noDebug) {1332await s.sendExceptionBreakpoints(enabledExceptionBps);1333}1334});1335}13361337private onFileChanges(fileChangesEvent: FileChangesEvent): void {1338const toRemove = this.model.getBreakpoints().filter(bp =>1339fileChangesEvent.contains(bp.originalUri, FileChangeType.DELETED));1340if (toRemove.length) {1341this.model.removeBreakpoints(toRemove);1342}13431344const toSend: URI[] = [];1345for (const uri of this.breakpointsToSendOnResourceSaved) {1346if (fileChangesEvent.contains(uri, FileChangeType.UPDATED)) {1347toSend.push(uri);1348}1349}13501351for (const uri of toSend) {1352this.breakpointsToSendOnResourceSaved.delete(uri);1353this.sendBreakpoints(uri, true);1354}1355}13561357async runTo(uri: uri, lineNumber: number, column?: number): Promise<void> {1358let breakpointToRemove: IBreakpoint | undefined;1359let threadToContinue = this.getViewModel().focusedThread;1360const addTempBreakPoint = async () => {1361const bpExists = !!(this.getModel().getBreakpoints({ column, lineNumber, uri }).length);13621363if (!bpExists) {1364const addResult = await this.addAndValidateBreakpoints(uri, lineNumber, column);1365if (addResult.thread) {1366threadToContinue = addResult.thread;1367}13681369if (addResult.breakpoint) {1370breakpointToRemove = addResult.breakpoint;1371}1372}1373return { threadToContinue, breakpointToRemove };1374};1375const removeTempBreakPoint = (state: State): boolean => {1376if (state === State.Stopped || state === State.Inactive) {1377if (breakpointToRemove) {1378this.removeBreakpoints(breakpointToRemove.getId());1379}1380return true;1381}1382return false;1383};13841385await addTempBreakPoint();1386if (this.state === State.Inactive) {1387// If no session exists start the debugger1388const { launch, name, getConfig } = this.getConfigurationManager().selectedConfiguration;1389const config = await getConfig();1390const configOrName = config ? Object.assign(deepClone(config), {}) : name;1391const listener = this.onDidChangeState(state => {1392if (removeTempBreakPoint(state)) {1393listener.dispose();1394}1395});1396await this.startDebugging(launch, configOrName, undefined, true);1397}1398if (this.state === State.Stopped) {1399const focusedSession = this.getViewModel().focusedSession;1400if (!focusedSession || !threadToContinue) {1401return;1402}14031404const listener = threadToContinue.session.onDidChangeState(() => {1405if (removeTempBreakPoint(focusedSession.state)) {1406listener.dispose();1407}1408});1409await threadToContinue.continue();1410}1411}14121413private async addAndValidateBreakpoints(uri: URI, lineNumber: number, column?: number) {1414const debugModel = this.getModel();1415const viewModel = this.getViewModel();14161417const breakpoints = await this.addBreakpoints(uri, [{ lineNumber, column }], false);1418const breakpoint = breakpoints?.[0];1419if (!breakpoint) {1420return { breakpoint: undefined, thread: viewModel.focusedThread };1421}14221423// If the breakpoint was not initially verified, wait up to 2s for it to become so.1424// Inherently racey if multiple sessions can verify async, but not solvable...1425if (!breakpoint.verified) {1426let listener: IDisposable;1427await raceTimeout(new Promise<void>(resolve => {1428listener = debugModel.onDidChangeBreakpoints(() => {1429if (breakpoint.verified) {1430resolve();1431}1432});1433}), 2000);1434listener!.dispose();1435}14361437// Look at paused threads for sessions that verified this bp. Prefer, in order:1438const enum Score {1439/** The focused thread */1440Focused,1441/** Any other stopped thread of a session that verified the bp */1442Verified,1443/** Any thread that verified and paused in the same file */1444VerifiedAndPausedInFile,1445/** The focused thread if it verified the breakpoint */1446VerifiedAndFocused,1447}14481449let bestThread = viewModel.focusedThread;1450let bestScore = Score.Focused;1451for (const sessionId of breakpoint.sessionsThatVerified) {1452const session = debugModel.getSession(sessionId);1453if (!session) {1454continue;1455}14561457const threads = session.getAllThreads().filter(t => t.stopped);1458if (bestScore < Score.VerifiedAndFocused) {1459if (viewModel.focusedThread && threads.includes(viewModel.focusedThread)) {1460bestThread = viewModel.focusedThread;1461bestScore = Score.VerifiedAndFocused;1462}1463}14641465if (bestScore < Score.VerifiedAndPausedInFile) {1466const pausedInThisFile = threads.find(t => {1467const top = t.getTopStackFrame();1468return top && this.uriIdentityService.extUri.isEqual(top.source.uri, uri);1469});14701471if (pausedInThisFile) {1472bestThread = pausedInThisFile;1473bestScore = Score.VerifiedAndPausedInFile;1474}1475}14761477if (bestScore < Score.Verified) {1478bestThread = threads[0];1479bestScore = Score.VerifiedAndPausedInFile;1480}1481}14821483return { thread: bestThread, breakpoint };1484}1485}14861487export function getStackFrameThreadAndSessionToFocus(model: IDebugModel, stackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, avoidSession?: IDebugSession): { stackFrame: IStackFrame | undefined; thread: IThread | undefined; session: IDebugSession | undefined } {1488if (!session) {1489if (stackFrame || thread) {1490session = stackFrame ? stackFrame.thread.session : thread!.session;1491} else {1492const sessions = model.getSessions();1493const stoppedSession = sessions.find(s => s.state === State.Stopped);1494// Make sure to not focus session that is going down1495session = stoppedSession || sessions.find(s => s !== avoidSession && s !== avoidSession?.parentSession) || (sessions.length ? sessions[0] : undefined);1496}1497}14981499if (!thread) {1500if (stackFrame) {1501thread = stackFrame.thread;1502} else {1503const threads = session ? session.getAllThreads() : undefined;1504const stoppedThread = threads && threads.find(t => t.stopped);1505thread = stoppedThread || (threads && threads.length ? threads[0] : undefined);1506}1507}15081509if (!stackFrame && thread) {1510stackFrame = thread.getTopStackFrame();1511}15121513return { session, thread, stackFrame };1514}15151516async function sendToOneOrAllSessions(model: DebugModel, session: IDebugSession | undefined, send: (session: IDebugSession) => Promise<void>): Promise<void> {1517if (session) {1518await send(session);1519} else {1520await Promise.all(model.getSessions().map(s => send(s)));1521}1522}152315241525