-
Notifications
You must be signed in to change notification settings - Fork 300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support debugging IW using Jupyter protocol #10105
Merged
Merged
Changes from 5 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
71e0643
Move debugger files from platform folder
DonJayamanne 423cab9
Oops
DonJayamanne b3dda61
Support debugging of IW using Jupyter protocol
DonJayamanne 1df9d10
Updates to metadata
DonJayamanne dd05b78
Fixes
DonJayamanne 9f541e8
Oops
DonJayamanne 2d7f073
Misc
DonJayamanne 25edd11
Oops
DonJayamanne f45be77
Misc
DonJayamanne 392f8e7
Misc
DonJayamanne 6e9e71a
Misc
DonJayamanne b7ccf85
oops
DonJayamanne 4777b64
MIsc
DonJayamanne 81797d8
oops
DonJayamanne 6ccc99d
oops
DonJayamanne File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
src/interactive-window/debugger/jupyter/debugCellControllers.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import * as path from '../../../platform/vscode-path/path'; | ||
import { DebugProtocolMessage, NotebookCell, Uri } from 'vscode'; | ||
import { DebugProtocol } from 'vscode-debugprotocol'; | ||
import { IDebuggingDelegate, IKernelDebugAdapter } from '../../../kernels/debugger/types'; | ||
import { DebuggingTelemetry } from '../../../kernels/debugger/constants'; | ||
import { IKernel } from '../../../kernels/types'; | ||
import { cellDebugSetup } from '../../../notebooks/debugger/helper'; | ||
import { createDeferred } from '../../../platform/common/utils/async'; | ||
import { sendTelemetryEvent } from '../../../telemetry'; | ||
import { getInteractiveCellMetadata } from '../../helpers'; | ||
|
||
export class DebugCellController implements IDebuggingDelegate { | ||
private readonly _ready = createDeferred<void>(); | ||
public readonly ready = this._ready.promise; | ||
constructor( | ||
private readonly debugAdapter: IKernelDebugAdapter, | ||
public readonly debugCell: NotebookCell, | ||
private readonly kernel: IKernel | ||
) { | ||
sendTelemetryEvent(DebuggingTelemetry.successfullyStartedRunAndDebugCell); | ||
} | ||
|
||
public async willSendEvent(_msg: DebugProtocolMessage): Promise<boolean> { | ||
return false; | ||
} | ||
|
||
public async willSendRequest(request: DebugProtocol.Request): Promise<void> { | ||
const metadata = getInteractiveCellMetadata(this.debugCell); | ||
if (request.command === 'configurationDone' && metadata && metadata.generatedCode) { | ||
await cellDebugSetup(this.kernel, this.debugAdapter); | ||
|
||
const realPath = this.debugAdapter.getSourcePath(metadata.interactive.uristring); | ||
if (realPath) { | ||
const initialBreakpoint: DebugProtocol.SourceBreakpoint = { | ||
line: metadata.generatedCode.firstExecutableLineIndex - metadata.interactive.line | ||
}; | ||
const uri = Uri.parse(metadata.interactive.uristring); | ||
await this.debugAdapter.setBreakpoints({ | ||
source: { | ||
name: path.basename(uri.path), | ||
path: realPath | ||
}, | ||
breakpoints: [initialBreakpoint], | ||
sourceModified: false | ||
}); | ||
} | ||
this._ready.resolve(); | ||
} | ||
} | ||
} |
184 changes: 184 additions & 0 deletions
184
src/interactive-window/debugger/jupyter/debuggingManager.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import { inject, injectable } from 'inversify'; | ||
import { | ||
NotebookDocument, | ||
DebugAdapterInlineImplementation, | ||
DebugSession, | ||
NotebookCell, | ||
DebugSessionOptions, | ||
DebugAdapterDescriptor, | ||
NotebookEditor, | ||
debug | ||
} from 'vscode'; | ||
import { pythonIWKernelDebugAdapter } from '../../../kernels/debugger/constants'; | ||
import { | ||
IDebuggingManager, | ||
IInteractiveWindowDebuggingManager, | ||
KernelDebugMode, | ||
IKernelDebugAdapterConfig, | ||
IDebugLocationTrackerFactory | ||
} from '../../../kernels/debugger/types'; | ||
import { IKernelProvider } from '../../../kernels/types'; | ||
import { IpykernelCheckResult, assertIsDebugConfig } from '../../../notebooks/debugger/helper'; | ||
import { KernelDebugAdapter } from './kernelDebugAdapter'; | ||
import { INotebookControllerManager } from '../../../notebooks/types'; | ||
import { IExtensionSingleActivationService } from '../../../platform/activation/types'; | ||
import { ICommandManager, IApplicationShell, IVSCodeNotebook } from '../../../platform/common/application/types'; | ||
import { IPlatformService } from '../../../platform/common/platform/types'; | ||
import { DataScience } from '../../../platform/common/utils/localize'; | ||
import { traceInfoIfCI, traceInfo, traceError } from '../../../platform/logging'; | ||
import * as path from '../../../platform/vscode-path/path'; | ||
import { DebugCellController } from './debugCellControllers'; | ||
import { DebuggingManagerBase } from '../../../kernels/debugger/debuggingManagerBase'; | ||
import { IConfigurationService } from '../../../platform/common/types'; | ||
|
||
/** | ||
* The DebuggingManager maintains the mapping between notebook documents and debug sessions. | ||
*/ | ||
@injectable() | ||
export class InteractiveWindowDebuggingManager | ||
extends DebuggingManagerBase | ||
implements IExtensionSingleActivationService, IDebuggingManager, IInteractiveWindowDebuggingManager | ||
{ | ||
public constructor( | ||
@inject(IKernelProvider) kernelProvider: IKernelProvider, | ||
@inject(INotebookControllerManager) notebookControllerManager: INotebookControllerManager, | ||
@inject(ICommandManager) commandManager: ICommandManager, | ||
@inject(IApplicationShell) appShell: IApplicationShell, | ||
@inject(IVSCodeNotebook) vscNotebook: IVSCodeNotebook, | ||
@inject(IPlatformService) private readonly platform: IPlatformService, | ||
@inject(IDebugLocationTrackerFactory) | ||
private readonly debugLocationTrackerFactory: IDebugLocationTrackerFactory, | ||
@inject(IConfigurationService) private readonly configService: IConfigurationService | ||
) { | ||
super(kernelProvider, notebookControllerManager, commandManager, appShell, vscNotebook); | ||
} | ||
|
||
public override async activate(): Promise<void> { | ||
await super.activate(); | ||
// factory for kernel debug adapters | ||
this.disposables.push( | ||
debug.registerDebugAdapterDescriptorFactory(pythonIWKernelDebugAdapter, { | ||
createDebugAdapterDescriptor: async (session) => this.createDebugAdapterDescriptor(session) | ||
}) | ||
); | ||
} | ||
public getDebugMode(_notebook: NotebookDocument): KernelDebugMode | undefined { | ||
return KernelDebugMode.InteractiveWindow; | ||
} | ||
public async start(editor: NotebookEditor, cell: NotebookCell) { | ||
traceInfoIfCI(`Starting debugging IW`); | ||
|
||
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. Toolbar should update`); | ||
return; | ||
} | ||
|
||
const checkIpykernelAndStart = async (allowSelectKernel = true): Promise<void> => { | ||
const ipykernelResult = await this.checkForIpykernel6(editor.document); | ||
switch (ipykernelResult) { | ||
case IpykernelCheckResult.NotInstalled: | ||
// User would have been notified about this, nothing more to do. | ||
return; | ||
case IpykernelCheckResult.Outdated: | ||
case IpykernelCheckResult.Unknown: { | ||
void this.promptInstallIpykernel6(); | ||
return; | ||
} | ||
case IpykernelCheckResult.Ok: { | ||
await this.startDebuggingCell(editor.notebook, cell); | ||
return; | ||
} | ||
case IpykernelCheckResult.ControllerNotSelected: { | ||
if (allowSelectKernel) { | ||
await this.commandManager.executeCommand('notebook.selectKernel', { notebookEditor: editor }); | ||
await checkIpykernelAndStart(false); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
try { | ||
this.notebookInProgress.add(editor.notebook); | ||
await checkIpykernelAndStart(); | ||
} catch (e) { | ||
traceInfo(`Error starting debugging: ${e}`); | ||
} finally { | ||
this.notebookInProgress.delete(editor.notebook); | ||
} | ||
} | ||
|
||
private async startDebuggingCell(doc: NotebookDocument, cell: NotebookCell) { | ||
const settings = this.configService.getSettings(doc.uri); | ||
const config: IKernelDebugAdapterConfig = { | ||
type: pythonIWKernelDebugAdapter, | ||
name: path.basename(doc.uri.toString()), | ||
request: 'attach', | ||
justMyCode: settings.debugJustMyCode, | ||
__interactiveWindowNotebookUri: doc.uri.toString(), | ||
// add a property to the config to know if the session is runByLine | ||
__mode: KernelDebugMode.InteractiveWindow, | ||
__cellIndex: cell.index | ||
}; | ||
const opts: DebugSessionOptions = { suppressSaveBeforeStart: true }; | ||
return this.startDebuggingConfig(doc, config, opts); | ||
} | ||
|
||
protected override async createDebugAdapterDescriptor( | ||
session: DebugSession | ||
): Promise<DebugAdapterDescriptor | undefined> { | ||
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 || typeof config.__cellIndex !== 'number') { | ||
// This cannot happen. | ||
traceError('Invalid debug session for debugging of IW using Jupyter Protocol'); | ||
return; | ||
} | ||
|
||
// TODO we apparently always have a kernel here, clean up typings | ||
const kernel = await this.ensureKernelIsRunning(activeDoc); | ||
const debug = this.getDebuggerByUri(activeDoc); | ||
if (!debug) { | ||
return; | ||
} | ||
if (!kernel?.session) { | ||
void this.appShell.showInformationMessage(DataScience.kernelWasNotStarted()); | ||
return; | ||
} | ||
const adapter = new KernelDebugAdapter( | ||
session, | ||
debug.document, | ||
kernel.session, | ||
kernel, | ||
this.platform, | ||
this.debugLocationTrackerFactory | ||
); | ||
|
||
this.notebookToDebugAdapter.set(debug.document, adapter); | ||
this.disposables.push(adapter.onDidEndSession(this.endSession.bind(this))); | ||
|
||
// Wait till we're attached before resolving the session | ||
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)); | ||
|
||
this.trackDebugAdapter(activeDoc, adapter); | ||
return new DebugAdapterInlineImplementation(adapter); | ||
} | ||
} |
100 changes: 100 additions & 0 deletions
100
src/interactive-window/debugger/jupyter/kernelDebugAdapter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
'use strict'; | ||
|
||
import { KernelMessage } from '@jupyterlab/services'; | ||
import * as path from '../../../platform/vscode-path/path'; | ||
import { DebugAdapterTracker, DebugSession, NotebookCellKind, NotebookDocument, Uri } from 'vscode'; | ||
import { DebugProtocol } from 'vscode-debugprotocol'; | ||
import { IJupyterSession, IKernel } from '../../../kernels/types'; | ||
import { IPlatformService } from '../../../platform/common/platform/types'; | ||
import { IDumpCellResponse, IDebugLocationTrackerFactory } from '../../../kernels/debugger/types'; | ||
import { traceError, traceInfoIfCI } from '../../../platform/logging'; | ||
import { getInteractiveCellMetadata } from '../../../interactive-window/helpers'; | ||
import { KernelDebugAdapterBase } from '../../../kernels/debugger/kernelDebugAdapterBase'; | ||
|
||
export class KernelDebugAdapter extends KernelDebugAdapterBase { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved a lot of stuff into a base class |
||
private readonly debugLocationTracker?: DebugAdapterTracker; | ||
constructor( | ||
session: DebugSession, | ||
notebookDocument: NotebookDocument, | ||
jupyterSession: IJupyterSession, | ||
kernel: IKernel | undefined, | ||
platformService: IPlatformService, | ||
debugLocationTrackerFactory?: IDebugLocationTrackerFactory | ||
) { | ||
super(session, notebookDocument, jupyterSession, kernel, platformService); | ||
if (debugLocationTrackerFactory) { | ||
this.debugLocationTracker = debugLocationTrackerFactory.createDebugAdapterTracker( | ||
session | ||
) as DebugAdapterTracker; | ||
if (this.debugLocationTracker.onWillStartSession) { | ||
this.debugLocationTracker.onWillStartSession(); | ||
} | ||
this.onDidSendMessage( | ||
(msg) => { | ||
if (this.debugLocationTracker?.onDidSendMessage) { | ||
this.debugLocationTracker.onDidSendMessage(msg); | ||
} | ||
}, | ||
this, | ||
this.disposables | ||
); | ||
this.onDidEndSession( | ||
() => { | ||
if (this.debugLocationTracker?.onWillStopSession) { | ||
this.debugLocationTracker.onWillStopSession(); | ||
} | ||
}, | ||
this, | ||
this.disposables | ||
); | ||
} | ||
} | ||
|
||
override handleMessage(message: DebugProtocol.ProtocolMessage): Promise<KernelMessage.IDebugReplyMsg | undefined> { | ||
traceInfoIfCI(`KernelDebugAdapter::handleMessage ${JSON.stringify(message, undefined, ' ')}`); | ||
if (message.type === 'request' && this.debugLocationTracker?.onWillReceiveMessage) { | ||
this.debugLocationTracker.onWillReceiveMessage(message); | ||
} | ||
if (message.type === 'response' && this.debugLocationTracker?.onDidSendMessage) { | ||
this.debugLocationTracker.onDidSendMessage(message); | ||
} | ||
return super.handleMessage(message); | ||
} | ||
|
||
public override async dumpAllCells() { | ||
DonJayamanne marked this conversation as resolved.
Show resolved
Hide resolved
|
||
await Promise.all( | ||
this.notebookDocument.getCells().map(async (cell) => { | ||
if (cell.kind === NotebookCellKind.Code) { | ||
await this.dumpCell(cell.index); | ||
} | ||
}) | ||
); | ||
} | ||
// Dump content of given cell into a tmp file and return path to file. | ||
protected async dumpCell(index: number): Promise<void> { | ||
const cell = this.notebookDocument.cellAt(index); | ||
const metadata = getInteractiveCellMetadata(cell); | ||
if (!metadata) { | ||
throw new Error('Not an interactive window cell'); | ||
} | ||
try { | ||
const response = await this.session.customRequest('dumpCell', { | ||
code: (metadata.generatedCode?.code || cell.document.getText()).replace(/\r\n/g, '\n') | ||
}); | ||
const norm = path.normalize((response as IDumpCellResponse).sourcePath); | ||
this.fileToCell.set(norm, { | ||
uri: Uri.parse(metadata.interactive.uristring), | ||
lineOffset: metadata.interactive.line + 1 // Add an extra 1 for the cell marker. | ||
}); | ||
this.cellToFile.set(metadata.interactive.uristring, { | ||
path: norm, | ||
lineOffset: metadata.interactive.line + 1 // Add an extra 1 for the cell marker. | ||
}); | ||
} catch (err) { | ||
traceError(`Failed to dump cell for ${cell.index} with code ${metadata.interactive.originalSource}`, err); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved a lot of stuff into a base class