Skip to content

Commit

Permalink
RBL code cleanup (#7416)
Browse files Browse the repository at this point in the history
* Refactor KernelDebugAdapter and add RunByLineController
#7346

* Move initializeExecute into RunByLineController

* Add cell debug controller

Co-authored-by: David <dakutuga@microsoft.com>
  • Loading branch information
roblourens and David authored Sep 13, 2021
1 parent d96d21a commit a591f95
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 219 deletions.
10 changes: 4 additions & 6 deletions src/client/datascience/jupyter/debuggerVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { inject, injectable, named } from 'inversify';
import * as path from 'path';

import { DebugAdapterTracker, DebugSession, Disposable, Event, EventEmitter } from 'vscode';
import { DebugAdapterTracker, Disposable, Event, EventEmitter } from 'vscode';
import { DebugProtocol } from 'vscode-debugprotocol';
import { IDebugService, IVSCodeNotebook } from '../../common/application/types';
import { traceError } from '../../common/logger';
Expand Down Expand Up @@ -62,7 +62,7 @@ export class DebuggerVariables extends DebugLocationTracker

public get active(): boolean {
return (
(this.debugService.activeDebugSession !== undefined || this.getDebuggingManagerSession() !== undefined) &&
(this.debugService.activeDebugSession !== undefined || this.activeNotebookIsDebugging()) &&
this.debuggingStarted
);
}
Expand Down Expand Up @@ -408,11 +408,9 @@ export class DebuggerVariables extends DebugLocationTracker
this.refreshEventEmitter.fire();
}

private getDebuggingManagerSession(): DebugSession | undefined {
private activeNotebookIsDebugging(): boolean {
const activeNotebook = this.vscNotebook.activeNotebookEditor;
if (activeNotebook) {
return this.debuggingManager.getDebugSession(activeNotebook.document);
}
return !!activeNotebook && this.debuggingManager.isDebugging(activeNotebook.document);
}
}

Expand Down
173 changes: 173 additions & 0 deletions src/client/debugger/jupyter/debugControllers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import * as path from 'path';
import { DebugProtocolMessage, NotebookCell } from 'vscode';
import { DebugProtocol } from 'vscode-debugprotocol';
import { parseForComments } from '../../../datascience-ui/common';
import { ICommandManager } from '../../common/application/types';
import { traceVerbose } from '../../common/logger';
import { IConfigurationService } from '../../common/types';
import { noop } from '../../common/utils/misc';
import { Commands } from '../../datascience/constants';
import { IKernel } from '../../datascience/jupyter/kernels/types';
import { sendTelemetryEvent } from '../../telemetry';
import { DebuggingTelemetry } from '../constants';
import { DebuggingDelegate, IKernelDebugAdapter } from '../types';

export class DebugCellController implements DebuggingDelegate {
constructor(
private readonly debugAdapter: IKernelDebugAdapter,
public readonly debugCell: NotebookCell,
private readonly kernel: IKernel,
private readonly commandManager: ICommandManager
) {
sendTelemetryEvent(DebuggingTelemetry.successfullyStartedRunAndDebugCell);
}

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

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

void this.commandManager.executeCommand('notebook.cell.execute', {
ranges: [{ start: this.debugCell.index, end: this.debugCell.index + 1 }],
document: this.debugCell.document.uri
});
}
}
}

export class RunByLineController implements DebuggingDelegate {
private lastPausedThreadId: number | undefined;

constructor(
private readonly debugAdapter: IKernelDebugAdapter,
public readonly debugCell: NotebookCell,
private readonly commandManager: ICommandManager,
private readonly kernel: IKernel,
private readonly settings: IConfigurationService
) {
sendTelemetryEvent(DebuggingTelemetry.successfullyStartedRunByLine);
}

public continue(): void {
if (typeof this.lastPausedThreadId !== 'number') {
traceVerbose(`No paused thread, can't do RBL`);
return;
}

void this.debugAdapter.stepIn(this.lastPausedThreadId);
}

public stop(): void {
this.debugAdapter.disconnect();
}

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

if (anyMsg.content.event === 'stopped') {
this.lastPausedThreadId = anyMsg.content.body.threadId;
if (await this.handleStoppedEvent(this.lastPausedThreadId!)) {
this.trace('intercepted', JSON.stringify(anyMsg.content));
return true;
}
}

return false;
}

public async willSendRequest(request: DebugProtocol.Request): Promise<void> {
if (request.command === 'configurationDone') {
await this.initializeExecute();
}
}

private async handleStoppedEvent(threadId: number): Promise<boolean> {
if (await this.shouldStepIn(threadId)) {
void this.debugAdapter.stepIn(threadId);
return true;
}

return false;
}

private async shouldStepIn(threadId: number): Promise<boolean> {
// Call stackTrace to determine whether to forward the stop event to the client, and also to
// start the process of updating the variables view.
const stResponse = await this.debugAdapter.stackTrace({ threadId, startFrame: 0, levels: 1 });

const sf = stResponse.stackFrames[0];
return !!sf.source && sf.source.path !== this.debugCell.document.uri.toString();
}

private trace(tag: string, msg: string) {
traceVerbose(`[Debug-RBL] ${tag}: ${msg}`);
}

private async initializeExecute() {
await cellDebugSetup(this.kernel, this.debugAdapter, this.debugCell);

// This will save the code lines of the cell in lineList (so ignore comments and emtpy lines)
// Its done to set the Run by Line breakpoint on the first code line
const textLines = this.debugCell.document.getText().splitLines({ trim: false, removeEmptyEntries: false });
const lineList: number[] = [];
parseForComments(
textLines,
() => noop(),
(s, i) => {
if (s.trim().length !== 0) {
lineList.push(i);
}
}
);
lineList.sort();

// Don't send the SetBreakpointsRequest or open the variable view if there are no code lines
if (lineList.length !== 0) {
const initialBreakpoint: DebugProtocol.SourceBreakpoint = {
line: lineList[0] + 1
};
await this.debugAdapter.setBreakpoints({
source: {
name: path.basename(this.debugCell.notebook.uri.path),
path: this.debugCell.document.uri.toString()
},
breakpoints: [initialBreakpoint],
sourceModified: false
});

// Open variable view
const settings = this.settings.getSettings();
if (settings.showVariableViewWhenDebugging) {
void this.commandManager.executeCommand(Commands.OpenVariableView);
}
}

// Run cell
void this.commandManager.executeCommand('notebook.cell.execute', {
ranges: [{ start: this.debugCell.index, end: this.debugCell.index + 1 }],
document: this.debugCell.document.uri
});
}
}

async function cellDebugSetup(
kernel: IKernel,
debugAdapter: IKernelDebugAdapter,
debugCell: NotebookCell
): Promise<void> {
// remove this if when https://github.com/microsoft/debugpy/issues/706 is fixed and ipykernel ships it
// executing this code restarts debugpy and fixes https://github.com/microsoft/vscode-jupyter/issues/7251
if (kernel) {
const code = 'import debugpy\ndebugpy.debug_this_thread()';
await kernel.executeHidden(code, debugCell.notebook);
}

await debugAdapter.dumpCell(debugCell.index);
}
Loading

0 comments on commit a591f95

Please sign in to comment.