diff --git a/src/interactive-window/debugger/jupyter/debuggingManager.ts b/src/interactive-window/debugger/jupyter/debuggingManager.ts index b30ca7a08f9..efe1404f15d 100644 --- a/src/interactive-window/debugger/jupyter/debuggingManager.ts +++ b/src/interactive-window/debugger/jupyter/debuggingManager.ts @@ -148,12 +148,10 @@ export class InteractiveWindowDebuggingManager __cellIndex: cell.index }; const opts: DebugSessionOptions = { suppressSaveBeforeStart: true }; - return this.startDebuggingConfig(doc, config, opts); + return this.startDebuggingConfig(config, opts); } - protected override async createDebugAdapterDescriptor( - session: DebugSession - ): Promise { + protected async createDebugAdapterDescriptor(session: DebugSession): Promise { const config = session.configuration; assertIsDebugConfig(config); @@ -194,9 +192,7 @@ export class InteractiveWindowDebuggingManager const cell = activeDoc.cellAt(config.__cellIndex); const controller = new DebugCellController(adapter, cell, kernel!); adapter.setDebuggingDelegate(controller); - controller.ready - .then(() => debug.resolve(session)) - .catch((ex) => console.error('Failed waiting for controller to be ready', ex)); + controller.ready.catch((ex) => console.error('Failed waiting for controller to be ready', ex)); // ?? TODO this.trackDebugAdapter(activeDoc, adapter); return new DebugAdapterInlineImplementation(adapter); @@ -205,7 +201,7 @@ export class InteractiveWindowDebuggingManager // TODO: This will likely be needed for mapping breakpoints and such public async updateSourceMaps(notebookEditor: NotebookEditor, hashes: IFileGeneratedCodes[]): Promise { // Make sure that we have an active debugging session at this point - let debugSession = await this.getDebugSession(notebookEditor.notebook); + let debugSession = this.getDebugSession(notebookEditor.notebook); if (debugSession) { traceInfoIfCI(`Sending debug request for source map`); await Promise.all( diff --git a/src/notebooks/debugger/debugCellControllers.ts b/src/notebooks/debugger/debugCellControllers.ts index 6218440fb6d..14396012554 100644 --- a/src/notebooks/debugger/debugCellControllers.ts +++ b/src/notebooks/debugger/debugCellControllers.ts @@ -51,7 +51,7 @@ export class DebugCellController implements IDebuggingDelegate { this.commandManager .executeCommand('notebook.cell.execute', { ranges: [{ start: this.debugCell.index, end: this.debugCell.index + 1 }], - document: this.debugCell.document.uri + document: this.debugCell.notebook.uri }) .then(noop, noop); } diff --git a/src/notebooks/debugger/debugger.ts b/src/notebooks/debugger/debugger.ts index 158905281e9..f834bfef0ae 100644 --- a/src/notebooks/debugger/debugger.ts +++ b/src/notebooks/debugger/debugger.ts @@ -2,44 +2,15 @@ // Licensed under the MIT License. 'use strict'; -import { debug, NotebookDocument, DebugSession, DebugSessionOptions, DebugConfiguration } from 'vscode'; -import { noop } from '../../platform/common/utils/misc'; +import { DebugConfiguration, DebugSession, NotebookDocument } from 'vscode'; /** * Wraps debug start in a promise */ export class Debugger { - private resolveFunc?: (value: DebugSession) => void; - private rejectFunc?: (reason?: Error) => void; - - readonly session: Promise; - constructor( public readonly document: NotebookDocument, public readonly config: DebugConfiguration, - options?: DebugSessionOptions - ) { - this.session = new Promise((resolve, reject) => { - this.resolveFunc = resolve; - this.rejectFunc = reject; - - debug.startDebugging(undefined, config, options).then(undefined, reject); - }); - } - - resolve(session: DebugSession) { - if (this.resolveFunc) { - this.resolveFunc(session); - } - } - - reject(reason: Error) { - if (this.rejectFunc) { - this.rejectFunc(reason); - } - } - - async stop() { - void debug.stopDebugging(await this.session).then(noop, noop); - } + public readonly session: DebugSession + ) {} } diff --git a/src/notebooks/debugger/debuggingManager.ts b/src/notebooks/debugger/debuggingManager.ts index ba8def1dee6..4edff80fe40 100644 --- a/src/notebooks/debugger/debuggingManager.ts +++ b/src/notebooks/debugger/debuggingManager.ts @@ -30,18 +30,18 @@ import { IConfigurationService } from '../../platform/common/types'; import { DataScience } from '../../platform/common/utils/localize'; import { noop } from '../../platform/common/utils/misc'; import { IServiceContainer } from '../../platform/ioc/types'; -import { traceError, traceInfo, traceInfoIfCI } from '../../platform/logging'; +import { traceInfo, traceInfoIfCI } from '../../platform/logging'; import { ResourceSet } from '../../platform/vscode-path/map'; import * as path from '../../platform/vscode-path/path'; import { sendTelemetryEvent } from '../../telemetry'; import { IControllerLoader, IControllerSelection } from '../controllers/types'; import { DebuggingTelemetry, pythonKernelDebugAdapter } from './constants'; import { DebugCellController } from './debugCellControllers'; +import { Debugger } from './debugger'; import { DebuggingManagerBase } from './debuggingManagerBase'; import { IDebuggingManager, IKernelDebugAdapterConfig, KernelDebugMode } from './debuggingTypes'; import { assertIsDebugConfig, IpykernelCheckResult } from './helper'; import { KernelDebugAdapter } from './kernelDebugAdapter'; -import { KernelDebugAdapterBase } from './kernelDebugAdapterBase'; import { RunByLineController } from './runByLineController'; /** @@ -202,67 +202,34 @@ export class DebuggingManager return; } - if (this.notebookInProgress.has(editor.notebook)) { - traceInfo(`Cannot start debugging. Already debugging this notebook`); - return; - } - - if (this.isDebugging(editor.notebook)) { - traceInfo(`Cannot start debugging. Already debugging this notebook document.`); - return; + await this.checkIpykernel(editor); + if (mode === KernelDebugMode.RunByLine || mode === KernelDebugMode.Cell) { + await this.startDebuggingCell(editor.notebook, mode, cell!); + } else { + await this.startDebugging(editor.notebook); } + } - const checkIpykernelAndStart = async (allowSelectKernel = true): Promise => { - const ipykernelResult = await this.checkForIpykernel6(editor.notebook); - switch (ipykernelResult) { - case IpykernelCheckResult.NotInstalled: - // User would have been notified about this, nothing more to do. - return; - case IpykernelCheckResult.Outdated: - case IpykernelCheckResult.Unknown: { - this.promptInstallIpykernel6().then(noop, noop); - return; - } - case IpykernelCheckResult.Ok: { - switch (mode) { - case KernelDebugMode.Everything: { - await this.startDebugging(editor.notebook); - return; - } - case KernelDebugMode.Cell: - if (cell) { - await this.startDebuggingCell(editor.notebook, KernelDebugMode.Cell, cell); - } - return; - case KernelDebugMode.RunByLine: - if (cell) { - await this.startDebuggingCell(editor.notebook, KernelDebugMode.RunByLine, cell); - } - return; - default: - return; - } - } - case IpykernelCheckResult.ControllerNotSelected: { - if (allowSelectKernel) { - await this.commandManager.executeCommand('notebook.selectKernel', { notebookEditor: editor }); - await checkIpykernelAndStart(false); - } + protected async checkIpykernel(editor: NotebookEditor, allowSelectKernel: boolean = true): Promise { + const ipykernelResult = await this.checkForIpykernel6(editor.notebook); + switch (ipykernelResult) { + case IpykernelCheckResult.NotInstalled: + // User would have been notified about this, nothing more to do. + return; + case IpykernelCheckResult.Outdated: + case IpykernelCheckResult.Unknown: { + this.promptInstallIpykernel6().then(noop, noop); + return; + } + case IpykernelCheckResult.ControllerNotSelected: { + if (allowSelectKernel) { + await this.commandManager.executeCommand('notebook.selectKernel', { notebookEditor: editor }); + await this.checkIpykernel(editor, false); } } - }; - - try { - this.notebookInProgress.add(editor.notebook); - this.updateDebugContextKey(); - await checkIpykernelAndStart(); - } catch (e) { - traceInfo(`Error starting debugging: ${e}`); - } finally { - this.notebookInProgress.delete(editor.notebook); - this.updateDebugContextKey(); } } + private async startDebuggingCell( doc: NotebookDocument, mode: KernelDebugMode.Cell | KernelDebugMode.RunByLine, @@ -275,7 +242,8 @@ export class DebuggingManager justMyCode: true, // add a property to the config to know if the session is runByLine __mode: mode, - __cellIndex: cell.index + __cellIndex: cell.index, + __notebookUri: doc.uri.toString() }; const opts: DebugSessionOptions | undefined = mode === KernelDebugMode.RunByLine @@ -286,7 +254,7 @@ export class DebuggingManager suppressSaveBeforeStart: true } : { suppressSaveBeforeStart: true }; - return this.startDebuggingConfig(doc, config, opts); + return this.startDebuggingConfig(config, opts); } private async startDebugging(doc: NotebookDocument) { @@ -296,71 +264,81 @@ export class DebuggingManager request: 'attach', internalConsoleOptions: 'neverOpen', justMyCode: false, - __mode: KernelDebugMode.Everything + __mode: KernelDebugMode.Everything, + __notebookUri: doc.uri.toString() }; - return this.startDebuggingConfig(doc, config); - } - - protected override trackDebugAdapter(notebook: NotebookDocument, adapter: KernelDebugAdapterBase): void { - super.trackDebugAdapter(notebook, adapter); - this.updateDebugContextKey(); + return this.startDebuggingConfig(config); } - protected override async createDebugAdapterDescriptor( - session: DebugSession - ): Promise { + protected async createDebugAdapterDescriptor(session: DebugSession): Promise { const config = session.configuration; assertIsDebugConfig(config); - const activeDoc = config.__interactiveWindowNotebookUri - ? this.vscNotebook.notebookDocuments.find( - (doc) => doc.uri.toString() === config.__interactiveWindowNotebookUri - ) - : this.vscNotebook.activeNotebookEditor?.notebook; - if (activeDoc) { - // TODO we apparently always have a kernel here, clean up typings - const kernel = await this.ensureKernelIsRunning(activeDoc); - const debug = this.getDebuggerByUri(activeDoc); - - if (debug) { - if (kernel?.session) { - const adapter = new KernelDebugAdapter( - session, - debug.document, - kernel.session, - kernel, - this.platform, - this.debugService - ); - - if (config.__mode === KernelDebugMode.RunByLine && typeof config.__cellIndex === 'number') { - const cell = activeDoc.cellAt(config.__cellIndex); - const controller = new RunByLineController( - adapter, - cell, - this.commandManager, - kernel!, - this.settings - ); - adapter.setDebuggingDelegate(controller); - this.notebookToRunByLineController.set(debug.document, controller); - this.updateRunByLineContextKeys(); - } else if (config.__mode === KernelDebugMode.Cell && typeof config.__cellIndex === 'number') { - const cell = activeDoc.cellAt(config.__cellIndex); - const controller = new DebugCellController(adapter, cell, kernel!, this.commandManager); - adapter.setDebuggingDelegate(controller); - } - this.trackDebugAdapter(debug.document, adapter); + const notebookUri = config.__interactiveWindowNotebookUri ?? config.__notebookUri; + const notebook = this.vscNotebook.notebookDocuments.find((doc) => doc.uri.toString() === notebookUri); - // Wait till we're attached before resolving the session - debug.resolve(session); - return new DebugAdapterInlineImplementation(adapter); - } else { - this.appShell.showInformationMessage(DataScience.kernelWasNotStarted()).then(noop, noop); - } + if (!notebook) { + traceInfo(`Cannot start debugging. Notebook ${notebookUri} not found.`); + return; + } + + if (this.notebookInProgress.has(notebook)) { + traceInfo(`Cannot start debugging. Already debugging this notebook`); + return; + } + + if (this.isDebugging(notebook)) { + traceInfo(`Cannot start debugging. Already debugging this notebook document.`); + return; + } + + this.notebookToDebugger.set(notebook, new Debugger(notebook, config, session)); + try { + this.notebookInProgress.add(notebook); + this.updateDebugContextKey(); + return await this.doCreateDebugAdapterDescriptor(config, session, notebook); + } finally { + this.notebookInProgress.delete(notebook); + this.updateDebugContextKey(); + } + } + + private async doCreateDebugAdapterDescriptor( + config: IKernelDebugAdapterConfig, + session: DebugSession, + notebook: NotebookDocument + ): Promise { + const kernel = await this.ensureKernelIsRunning(notebook); + if (kernel?.session) { + const adapter = new KernelDebugAdapter( + session, + notebook, + kernel.session, + kernel, + this.platform, + this.debugService + ); + + if (config.__mode === KernelDebugMode.RunByLine && typeof config.__cellIndex === 'number') { + const cell = notebook.cellAt(config.__cellIndex); + const controller = new RunByLineController(adapter, cell, this.commandManager, kernel!, this.settings); + adapter.setDebuggingDelegate(controller); + this.notebookToRunByLineController.set(notebook, controller); + this.updateRunByLineContextKeys(); + } else if (config.__mode === KernelDebugMode.Cell && typeof config.__cellIndex === 'number') { + const cell = notebook.cellAt(config.__cellIndex); + const controller = new DebugCellController(adapter, cell, kernel!, this.commandManager); + adapter.setDebuggingDelegate(controller); } + + this.trackDebugAdapter(notebook, adapter); + this.updateDebugContextKey(); + + return new DebugAdapterInlineImplementation(adapter); + } else { + this.appShell.showInformationMessage(DataScience.kernelWasNotStarted()).then(noop, noop); } - traceError('Debug sessions should start only from the cell toolbar command'); + return; } } diff --git a/src/notebooks/debugger/debuggingManagerBase.ts b/src/notebooks/debugger/debuggingManagerBase.ts index 8dac8b6708e..3c3534ad83e 100644 --- a/src/notebooks/debugger/debuggingManagerBase.ts +++ b/src/notebooks/debugger/debuggingManagerBase.ts @@ -5,28 +5,27 @@ import { debug, - NotebookDocument, - workspace, DebugSession, DebugSessionOptions, - DebugAdapterDescriptor, Event, EventEmitter, - NotebookCell + NotebookCell, + NotebookDocument, + workspace } from 'vscode'; import { IKernel, IKernelProvider } from '../../kernels/types'; -import { IDisposable } from '../../platform/common/types'; import { IApplicationShell, ICommandManager, IVSCodeNotebook } from '../../platform/common/application/types'; -import { DebuggingTelemetry } from './constants'; -import { sendTelemetryEvent } from '../../telemetry'; -import { traceError, traceInfoIfCI } from '../../platform/logging'; +import { IDisposable } from '../../platform/common/types'; import { DataScience } from '../../platform/common/utils/localize'; -import { IKernelDebugAdapterConfig } from './debuggingTypes'; -import { Debugger } from './debugger'; -import { KernelDebugAdapterBase } from './kernelDebugAdapterBase'; -import { IpykernelCheckResult, isUsingIpykernel6OrLater } from './helper'; import { noop } from '../../platform/common/utils/misc'; +import { traceError, traceInfoIfCI } from '../../platform/logging'; +import { sendTelemetryEvent } from '../../telemetry'; import { IControllerLoader, IControllerSelection } from '../controllers/types'; +import { DebuggingTelemetry } from './constants'; +import { Debugger } from './debugger'; +import { IKernelDebugAdapterConfig } from './debuggingTypes'; +import { IpykernelCheckResult, isUsingIpykernel6OrLater } from './helper'; +import { KernelDebugAdapterBase } from './kernelDebugAdapterBase'; import { KernelConnector } from '../controllers/kernelConnector'; import { IServiceContainer } from '../../platform/ioc/types'; import { DisplayOptions } from '../../kernels/displayOptions'; @@ -35,7 +34,7 @@ import { DisplayOptions } from '../../kernels/displayOptions'; * The DebuggingManager maintains the mapping between notebook documents and debug sessions. */ export abstract class DebuggingManagerBase implements IDisposable { - private notebookToDebugger = new Map(); + protected notebookToDebugger = new Map(); protected notebookToDebugAdapter = new Map(); protected notebookInProgress = new Set(); protected readonly disposables: IDisposable[] = []; @@ -60,7 +59,7 @@ export abstract class DebuggingManagerBase implements IDisposable { workspace.onDidCloseNotebookDocument(async (document) => { const dbg = this.notebookToDebugger.get(document); if (dbg) { - await dbg.stop(); + await debug.stopDebugging(dbg.session); this.onDidStopDebugging(document); } }) @@ -82,12 +81,13 @@ export abstract class DebuggingManagerBase implements IDisposable { return this.notebookToDebugger.has(notebook); } - public getDebugSession(notebook: NotebookDocument): Promise | undefined { + public getDebugSession(notebook: NotebookDocument): DebugSession | undefined { const dbg = this.notebookToDebugger.get(notebook); if (dbg) { return dbg.session; } } + public getDebugAdapter(notebook: NotebookDocument): KernelDebugAdapterBase | undefined { return this.notebookToDebugAdapter.get(notebook); } @@ -96,26 +96,14 @@ export abstract class DebuggingManagerBase implements IDisposable { // } - protected async startDebuggingConfig( - doc: NotebookDocument, - config: IKernelDebugAdapterConfig, - options?: DebugSessionOptions - ) { + protected async startDebuggingConfig(config: IKernelDebugAdapterConfig, options?: DebugSessionOptions) { traceInfoIfCI(`Attempting to start debugging with config ${JSON.stringify(config)}`); - let dbg = this.notebookToDebugger.get(doc); - if (!dbg) { - dbg = new Debugger(doc, config, options); - this.notebookToDebugger.set(doc, dbg); - - try { - const session = await dbg.session; - traceInfoIfCI(`Debugger session is ready. Should be debugging ${session.id} now`); - } catch (err) { - traceError(`Can't start debugging (${err})`); - this.appShell.showErrorMessage(DataScience.cantStartDebugging()).then(noop, noop); - } - } else { - traceInfoIfCI(`Not starting debugging because already debugging in this notebook`); + + try { + await debug.startDebugging(undefined, config, options); + } catch (err) { + traceError(`Can't start debugging (${err})`); + this.appShell.showErrorMessage(DataScience.cantStartDebugging()).then(noop, noop); } } @@ -123,6 +111,7 @@ export abstract class DebuggingManagerBase implements IDisposable { this.notebookToDebugAdapter.set(notebook, adapter); this.disposables.push(adapter.onDidEndSession(this.endSession.bind(this))); } + protected async endSession(session: DebugSession) { traceInfoIfCI(`Ending debug session ${session.id}`); this._doneDebugging.fire(); @@ -136,8 +125,6 @@ export abstract class DebuggingManagerBase implements IDisposable { } } - protected abstract createDebugAdapterDescriptor(session: DebugSession): Promise; - protected getDebuggerByUri(document: NotebookDocument): Debugger | undefined { for (const [doc, dbg] of this.notebookToDebugger.entries()) { if (document === doc) { diff --git a/src/notebooks/debugger/debuggingTypes.ts b/src/notebooks/debugger/debuggingTypes.ts index 7d8831ccc1e..83278661aaa 100644 --- a/src/notebooks/debugger/debuggingTypes.ts +++ b/src/notebooks/debugger/debuggingTypes.ts @@ -76,7 +76,7 @@ export interface IDebuggingManager { readonly onDoneDebugging: Event; isDebugging(notebook: NotebookDocument): boolean; getDebugMode(notebook: NotebookDocument): KernelDebugMode | undefined; - getDebugSession(notebook: NotebookDocument): Promise | undefined; + getDebugSession(notebook: NotebookDocument): DebugSession | undefined; getDebugCell(notebook: NotebookDocument): NotebookCell | undefined; getDebugAdapter(notebook: NotebookDocument): IKernelDebugAdapter | undefined; } @@ -122,6 +122,9 @@ export enum KernelDebugMode { export interface IKernelDebugAdapterConfig extends DebugConfiguration { __mode: KernelDebugMode; __cellIndex?: number; + + // TODO + __notebookUri?: string; __interactiveWindowNotebookUri?: string; } diff --git a/src/notebooks/debugger/runByLineController.ts b/src/notebooks/debugger/runByLineController.ts index 20464c2d953..4635e777f2f 100644 --- a/src/notebooks/debugger/runByLineController.ts +++ b/src/notebooks/debugger/runByLineController.ts @@ -150,7 +150,7 @@ export class RunByLineController implements IDebuggingDelegate { this.commandManager .executeCommand('notebook.cell.execute', { ranges: [{ start: this.debugCell.index, end: this.debugCell.index + 1 }], - document: this.debugCell.document.uri + document: this.debugCell.notebook.uri }) .then(noop, noop); }