Skip to content
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

Rob's comments and update variable view when done debugging #7527

Merged
merged 10 commits into from
Sep 15, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 62 additions & 40 deletions src/client/datascience/jupyter/debuggerVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class DebuggerVariables extends DebugLocationTracker
@inject(IVSCodeNotebook) private readonly vscNotebook: IVSCodeNotebook
) {
super(undefined);
this.debuggingManager.onDoneDebugging(() => this.refreshEventEmitter.fire(), this);
}

public get refreshRequired(): Event<void> {
Expand Down Expand Up @@ -236,32 +237,8 @@ export class DebuggerVariables extends DebugLocationTracker
// When the initialize response comes back, indicate we have started.
if (message.type === 'response' && message.command === 'initialize') {
this.debuggingStarted = true;
} else if (
(message as DebugProtocol.StackTraceResponse).command === 'stackTrace' &&
this.activeNotebookIsDebugging()
) {
const sf = (message as DebugProtocol.StackTraceResponse).body.stackFrames[0];
const callScopes = async () => {
const doc = this.vscNotebook.activeNotebookEditor?.document;
if (doc) {
const session = await this.debuggingManager.getDebugSession(doc);
const mode = this.debuggingManager.getDebugMode(doc);
if (mode === KernelDebugMode.RunByLine) {
const cell = this.debuggingManager.getDebugCell(doc);
// Only call scopes (and variables) if we are stopped on the cell we are executing
if (sf.source && cell && sf.source.path === cell.document.uri.toString()) {
void session?.customRequest('scopes', { frameId: sf.id });
}
} else {
// Only call scopes (and variables) if we are stopped on the notebook we are executing
const docURI = path.basename(doc.uri.toString());
if (sf.source && sf.source.path && sf.source.path.includes(docURI)) {
void session?.customRequest('scopes', { frameId: sf.id });
}
}
}
};
void callScopes();
} else if (message.type === 'event' && message.event === 'stopped' && this.activeNotebookIsDebugging()) {
void this.handleNotebookVariables(message as DebugProtocol.StoppedEvent);
} else if (message.type === 'response' && message.command === 'scopes' && message.body && message.body.scopes) {
const response = message as DebugProtocol.ScopesResponse;

Expand All @@ -271,16 +248,6 @@ export class DebuggerVariables extends DebugLocationTracker
this.currentVariablesReference = newVariablesReference;
this.currentSeqNumsForVariables.clear();
}

// Catch the scopes response and use its variablesReference to send a variables message
const doc = this.vscNotebook.activeNotebookEditor?.document;
if (this.activeNotebookIsDebugging() && doc) {
const callVariables = async () => {
const session = await this.debuggingManager.getDebugSession(doc);
void session?.customRequest('variables', { variablesReference: newVariablesReference });
};
void callVariables();
}
} else if (
message.type === 'response' &&
message.command === 'variables' &&
Expand All @@ -300,10 +267,6 @@ export class DebuggerVariables extends DebugLocationTracker

this.updateVariables(undefined, message as DebugProtocol.VariablesResponse);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this stuff be deleted, it seems like you copied the code to line 469

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it was needed, but after testing, its not, I'll remove it.

this.monkeyPatchDataViewableVariables(message);

if (this.activeNotebookIsDebugging()) {
this.refreshEventEmitter.fire();
}
} else if (message.type === 'event' && message.event === 'terminated') {
// When the debugger exits, make sure the variables are cleared
this.lastKnownVariables = [];
Expand Down Expand Up @@ -452,6 +415,65 @@ export class DebuggerVariables extends DebugLocationTracker
const activeNotebook = this.vscNotebook.activeNotebookEditor;
return !!activeNotebook && this.debuggingManager.isDebugging(activeNotebook.document);
}

// This handles all the debug session calls, variable handling, and refresh calls needed for notebook debugging
private async handleNotebookVariables(stoppedMessage: DebugProtocol.StoppedEvent): Promise<void> {
const doc = this.vscNotebook.activeNotebookEditor?.document;
const threadId = stoppedMessage.body.threadId;

if (doc) {
const session = await this.debuggingManager.getDebugSession(doc);
if (session) {
// Call stack trace
const stResponse = await session.customRequest('stackTrace', {
threadId,
startFrame: 0,
levels: 1
});

// Call scopes
const sf = stResponse.stackFrames[0];
const mode = this.debuggingManager.getDebugMode(doc);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let scopesResponse: any | undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why any?


if (mode === KernelDebugMode.RunByLine) {
// Only call scopes (and variables) if we are stopped on the cell we are executing
const cell = this.debuggingManager.getDebugCell(doc);
if (sf.source && cell && sf.source.path === cell.document.uri.toString()) {
scopesResponse = await session.customRequest('scopes', { frameId: sf.id });
}
} else {
// Only call scopes (and variables) if we are stopped on the notebook we are executing
const docURI = path.basename(doc.uri.toString());
if (sf.source && sf.source.path && sf.source.path.includes(docURI)) {
scopesResponse = await session.customRequest('scopes', { frameId: sf.id });
}
}

// Call variables
if (scopesResponse) {
// Keep track of variablesReference because "hover" requests also try to update variables
const newVariablesReference = scopesResponse.scopes[0].variablesReference;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you only want to update the first scope?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no hahah

if (newVariablesReference !== this.currentVariablesReference) {
this.currentVariablesReference = newVariablesReference;
this.currentSeqNumsForVariables.clear();
}

// Catch the scopes response and use its variablesReference to send a variables message
const varResponse: DebugProtocol.VariablesResponse = await session.customRequest('variables', {
variablesReference: newVariablesReference
});

// Refresh variable view
this.updateVariables(undefined, varResponse as DebugProtocol.VariablesResponse);
this.monkeyPatchDataViewableVariables(varResponse);

this.refreshEventEmitter.fire();
}
}
}
}
}

export function convertDebugProtocolVariableToIJupyterVariable(variable: DebugProtocol.Variable) {
Expand Down
14 changes: 7 additions & 7 deletions src/client/debugger/jupyter/debugControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import { Commands } from '../../datascience/constants';
import { IKernel } from '../../datascience/jupyter/kernels/types';
import { sendTelemetryEvent } from '../../telemetry';
import { DebuggingTelemetry } from '../constants';
import { DebuggingDelegate, IKernelDebugAdapter, KernelDebugMode } from '../types';
import { IDebuggingDelegate, IKernelDebugAdapter, KernelDebugMode } from '../types';

export class DebugCellController implements DebuggingDelegate {
export class DebugCellController implements IDebuggingDelegate {
constructor(
private readonly debugAdapter: IKernelDebugAdapter,
public readonly debugCell: NotebookCell,
Expand All @@ -25,11 +25,11 @@ export class DebugCellController implements DebuggingDelegate {
sendTelemetryEvent(DebuggingTelemetry.successfullyStartedRunAndDebugCell);
}

public async willSendEvent(_msg: DebugProtocolMessage): Promise<boolean> {
public async onWillSendEvent(_msg: DebugProtocolMessage): Promise<boolean> {
return false;
}

public async willSendRequest(request: DebugProtocol.Request): Promise<void> {
public async onWillSendRequest(request: DebugProtocol.Request): Promise<void> {
if (request.command === 'configurationDone') {
await cellDebugSetup(this.kernel, this.debugAdapter, this.debugCell);

Expand All @@ -41,7 +41,7 @@ export class DebugCellController implements DebuggingDelegate {
}
}

export class RunByLineController implements DebuggingDelegate {
export class RunByLineController implements IDebuggingDelegate {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious, do we need all of these interfaces? Why can't we just use classes

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We were following the extension patterns.

private lastPausedThreadId: number | undefined;

constructor(
Expand Down Expand Up @@ -72,7 +72,7 @@ export class RunByLineController implements DebuggingDelegate {
return config.__mode;
}

public async willSendEvent(msg: DebugProtocolMessage): Promise<boolean> {
public async onWillSendEvent(msg: DebugProtocolMessage): Promise<boolean> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const anyMsg = msg as any;

Expand All @@ -87,7 +87,7 @@ export class RunByLineController implements DebuggingDelegate {
return false;
}

public async willSendRequest(request: DebugProtocol.Request): Promise<void> {
public async onWillSendRequest(request: DebugProtocol.Request): Promise<void> {
if (request.command === 'configurationDone') {
await this.initializeExecute();
}
Expand Down
41 changes: 41 additions & 0 deletions src/client/debugger/jupyter/debugger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';
import { debug, NotebookDocument, DebugSession, DebugSessionOptions, DebugConfiguration } from 'vscode';

export class Debugger {
private resolveFunc?: (value: DebugSession) => void;
private rejectFunc?: (reason?: Error) => void;

readonly session: Promise<DebugSession>;

constructor(
public readonly document: NotebookDocument,
public readonly config: DebugConfiguration,
options?: DebugSessionOptions
) {
this.session = new Promise<DebugSession>((resolve, reject) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might try using a deferred object for this idea of remembering the promise resolve/reject.

If you create a deferred object, you can implement resolve/reject with it.

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this async if we're ignoring the promise returned by debug.stopDebuggin

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because we have to wait to get the session

}
}
48 changes: 10 additions & 38 deletions src/client/debugger/jupyter/debuggingManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import {
DebugSession,
NotebookCell,
DebugSessionOptions,
DebugConfiguration,
ProgressLocation,
DebugAdapterDescriptor
DebugAdapterDescriptor,
Event,
EventEmitter
} from 'vscode';
import * as path from 'path';
import { IKernel, IKernelProvider } from '../../datascience/jupyter/kernels/types';
Expand All @@ -38,42 +39,7 @@ import { sendTelemetryEvent } from '../../telemetry';
import { PythonEnvironment } from '../../pythonEnvironments/info';
import { DebugCellController, RunByLineController } from './debugControllers';
import { assertIsDebugConfig } from './helper';

class Debugger {
private resolveFunc?: (value: DebugSession) => void;
private rejectFunc?: (reason?: Error) => void;

readonly session: Promise<DebugSession>;

constructor(
public readonly document: NotebookDocument,
public readonly config: DebugConfiguration,
options?: DebugSessionOptions
) {
this.session = new Promise<DebugSession>((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);
}
}
import { Debugger } from './debugger';

/**
* The DebuggingManager maintains the mapping between notebook documents and debug sessions.
Expand All @@ -86,6 +52,7 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb
private notebookToRunByLineController = new Map<NotebookDocument, RunByLineController>();
private cache = new Map<PythonEnvironment, boolean>();
private readonly disposables: IDisposable[] = [];
private _doneDebugging = new EventEmitter<void>();

public constructor(
@inject(IKernelProvider) private kernelProvider: IKernelProvider,
Expand Down Expand Up @@ -223,6 +190,10 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb
);
}

public get onDoneDebugging(): Event<void> {
return this._doneDebugging.event;
}

public dispose() {
this.disposables.forEach((d) => d.dispose());
}
Expand Down Expand Up @@ -312,6 +283,7 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb
private async endSession(session: DebugSession) {
void this.updateToolbar(false);
void this.updateCellToolbar(false);
this._doneDebugging.fire();
for (const [doc, dbg] of this.notebookToDebugger.entries()) {
if (dbg && session.id === (await dbg.session).id) {
this.notebookToDebugger.delete(doc);
Expand Down
18 changes: 9 additions & 9 deletions src/client/debugger/jupyter/kernelDebugAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ import { IJupyterSession } from '../../datascience/types';
import { sendTelemetryEvent } from '../../telemetry';
import { DebuggingTelemetry } from '../constants';
import {
DebuggingDelegate,
debugInfoResponse,
dumpCellResponse,
IDebuggingDelegate,
IDebugInfoResponse,
IDumpCellResponse,
IKernelDebugAdapter,
IKernelDebugAdapterConfig,
KernelDebugMode
Expand All @@ -48,7 +48,7 @@ export class KernelDebugAdapter implements DebugAdapter, IKernelDebugAdapter, ID
private readonly endSession = new EventEmitter<DebugSession>();
private readonly configuration: IKernelDebugAdapterConfig;
private readonly disposables: IDisposable[] = [];
private delegate: DebuggingDelegate | undefined;
private delegate: IDebuggingDelegate | undefined;
onDidSendMessage: Event<DebugProtocolMessage> = this.sendMessage.event;
onDidEndSession: Event<DebugSession> = this.endSession.event;
public readonly debugCellUri: Uri | undefined;
Expand Down Expand Up @@ -77,7 +77,7 @@ export class KernelDebugAdapter implements DebugAdapter, IKernelDebugAdapter, ID

if (anyMsg.header.msg_type === 'debug_event') {
this.trace('event', JSON.stringify(msg));
if (!(await this.delegate?.willSendEvent(anyMsg))) {
if (!(await this.delegate?.onWillSendEvent(anyMsg))) {
this.sendMessage.fire(msg.content);
}
}
Expand Down Expand Up @@ -124,7 +124,7 @@ export class KernelDebugAdapter implements DebugAdapter, IKernelDebugAdapter, ID
);
}

public setDebuggingDelegate(delegate: DebuggingDelegate) {
public setDebuggingDelegate(delegate: IDebuggingDelegate) {
this.delegate = delegate;
}

Expand Down Expand Up @@ -155,7 +155,7 @@ export class KernelDebugAdapter implements DebugAdapter, IKernelDebugAdapter, ID
}

if (message.type === 'request') {
await this.delegate?.willSendRequest(message as DebugProtocol.Request);
await this.delegate?.onWillSendRequest(message as DebugProtocol.Request);
}

this.sendRequestToJupyterSession(message);
Expand Down Expand Up @@ -211,7 +211,7 @@ export class KernelDebugAdapter implements DebugAdapter, IKernelDebugAdapter, ID
if (cell) {
try {
const response = await this.session.customRequest('dumpCell', { code: cell.document.getText() });
const norm = path.normalize((response as dumpCellResponse).sourcePath);
const norm = path.normalize((response as IDumpCellResponse).sourcePath);
this.fileToCell.set(norm, cell);
this.cellToFile.set(cell.document.uri.toString(), norm);
} catch (err) {
Expand All @@ -224,7 +224,7 @@ export class KernelDebugAdapter implements DebugAdapter, IKernelDebugAdapter, ID
const response = await this.session.customRequest('debugInfo');

// If there's stopped threads at this point, continue them all
(response as debugInfoResponse).stoppedThreads.forEach((thread: number) => {
(response as IDebugInfoResponse).stoppedThreads.forEach((thread: number) => {
this.jupyterSession.requestDebug({
seq: 0,
type: 'request',
Expand Down
Loading