Skip to content
Merged
1 change: 1 addition & 0 deletions news/1 Enhancements/6672.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add debug command code lenses when in debug mode
26 changes: 26 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,26 @@
"title": "%python.command.python.datascience.runcurrentcell.title%",
"category": "Python"
},
{
"command": "python.datascience.debugcell",
"title": "%python.command.python.datascience.debugcell.title%",
"category": "Python"
},
{
"command": "python.datascience.debugstepover",
"title": "%python.command.python.datascience.debugstepover.title%",
"category": "Python"
},
{
"command": "python.datascience.debugstop",
"title": "%python.command.python.datascience.debugstop.title%",
"category": "Python"
},
{
"command": "python.datascience.debugcontinue",
"title": "%python.command.python.datascience.debugcontinue.title%",
"category": "Python"
},
{
"command": "python.datascience.runcurrentcelladvance",
"title": "%python.command.python.datascience.runcurrentcelladvance.title%",
Expand Down Expand Up @@ -1430,6 +1450,12 @@
"description": "Set of commands to put as code lens above a cell. Defaults to 'python.datascience.runcell, python.datascience.runallcellsabove, python.datascience.debugcell'",
"scope": "resource"
},
"python.dataScience.debugCodeLenses": {
"type": "string",
"default": "python.datascience.debugcontinue, python.datascience.debugstop, python.datascience.debugstepover",
"description": "Set of debug commands to put as code lens above a cell while debugging.",
"scope": "resource"
},
"python.dataScience.ptvsdDistPath": {
"type": "string",
"default": "",
Expand Down
6 changes: 6 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
"python.command.python.datascience.runcurrentcellandallbelow.palette.title": "Run Current Cell and Below",
"python.command.python.datascience.debugcurrentcell.palette.title": "Debug Current Cell",
"python.command.python.datascience.debugcell.title": "Debug Cell",
"python.command.python.datascience.debugstepover.title": "Step Over",
"python.command.python.datascience.debugcontinue.title": "Continue",
"python.command.python.datascience.debugstop.title": "Stop",
"python.command.python.datascience.runtoline.title": "Run To Line in Python Interactive Window",
"python.command.python.datascience.runfromline.title": "Run From Line in Python Interactive Window",
"python.command.python.datascience.runcurrentcell.title": "Run Current Cell",
Expand Down Expand Up @@ -305,6 +308,9 @@
"DataScience.jupyterDataRateExceeded": "Cannot view variable because data rate exceeded. Please restart your server with a higher data rate limit. For example, --NotebookApp.iopub_data_rate_limit=10000000000.0",
"DataScience.addCellBelowCommandTitle": "Add cell",
"DataScience.debugCellCommandTitle": "Debug cell",
"DataScience.debugStepOverCommandTitle": "Step over",
"DataScience.debugContinueCommandTitle": "Continue",
"DataScience.debugStopCommandTitle": "Stop",
"DataScience.runCurrentCellAndAddBelow": "Run current and add cell below",
"DataScience.variableExplorerDisabledDuringDebugging": "Please see the Debug Side Bar's VARIABLES section.",
"DataScience.jupyterDebuggerNotInstalledError": "Pip module ptvsd is required for debugging cells. You will need to install it to debug cells.",
Expand Down
5 changes: 5 additions & 0 deletions src/client/common/application/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ interface ICommandNameWithoutArgumentTypeMapping {
[Commands.Set_ShebangInterpreter]: [];
[Commands.Run_Linter]: [];
[Commands.Enable_Linter]: [];
['workbench.action.debug.continue']: [];
['workbench.action.debug.stepOver']: [];
['workbench.action.debug.stop']: [];
['workbench.action.reloadWindow']: [];
['editor.action.formatDocument']: [];
Expand Down Expand Up @@ -115,6 +117,9 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu
[DSCommands.RunFileInInteractiveWindows]: [string];
[DSCommands.DebugFileInInteractiveWindows]: [string];
[DSCommands.DebugCell]: [string, number, number, number, number];
[DSCommands.DebugStepOver]: [];
[DSCommands.DebugStop]: [];
[DSCommands.DebugContinue]: [];
[DSCommands.RunCurrentCellAndAddBelow]: [string];
[DSCommands.ScrollToCell]: [string, string];
}
4 changes: 4 additions & 0 deletions src/client/common/application/debugService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export class DebugService implements IDebugService {
public registerDebugConfigurationProvider(debugType: string, provider: any): Disposable {
return debug.registerDebugConfigurationProvider(debugType, provider);
}
// tslint:disable-next-line:no-any
public registerDebugAdapterTrackerFactory(debugType: string, provider: any): Disposable {
return debug.registerDebugAdapterTrackerFactory(debugType, provider);
}
public startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, parentSession?: DebugSession): Thenable<boolean> {
return debug.startDebugging(folder, nameOrConfiguration, parentSession);
}
Expand Down
10 changes: 10 additions & 0 deletions src/client/common/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CancellationToken,
CompletionItemProvider,
ConfigurationChangeEvent,
DebugAdapterTrackerFactory,
DebugConfiguration,
DebugConfigurationProvider,
DebugConsole,
Expand Down Expand Up @@ -764,6 +765,15 @@ export interface IDebugService {
*/
registerDebugConfigurationProvider(debugType: string, provider: DebugConfigurationProvider): Disposable;

/**
* Register a debug adapter tracker factory for the given debug type.
*
* @param debugType The debug type for which the factory is registered or '*' for matching all debug types.
* @param factory The [debug adapter tracker factory](#DebugAdapterTrackerFactory) to register.
* @return A [disposable](#Disposable) that unregisters this factory when being disposed.
*/
registerDebugAdapterTrackerFactory(debugType: string, factory: DebugAdapterTrackerFactory): Disposable;

/**
* Start debugging by using either a named launch or named compound configuration,
* or by directly passing a [DebugConfiguration](#DebugConfiguration).
Expand Down
1 change: 1 addition & 0 deletions src/client/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ export interface IDataScienceSettings {
askForKernelRestart?: boolean;
enablePlotViewer?: boolean;
codeLenses?: string;
debugCodeLenses?: string;
ptvsdDistPath?: string;
stopOnFirstLineWhileDebugging?: boolean;
textOutputLimit?: number;
Expand Down
3 changes: 3 additions & 0 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ export namespace DataScience {
export const jupyterDataRateExceeded = localize('DataScience.jupyterDataRateExceeded', 'Cannot view variable because data rate exceeded. Please restart your server with a higher data rate limit. For example, --NotebookApp.iopub_data_rate_limit=10000000000.0');
export const addCellBelowCommandTitle = localize('DataScience.addCellBelowCommandTitle', 'Add cell');
export const debugCellCommandTitle = localize('DataScience.debugCellCommandTitle', 'Debug cell');
export const debugStepOverCommandTitle = localize('DataScience.debugStepOverCommandTitle', 'Step over');
export const debugContinueCommandTitle = localize('DataScience.debugContinueCommandTitle', 'Continue');
export const debugStopCommandTitle = localize('DataScience.debugStopCommandTitle', 'Stop');
export const runCurrentCellAndAddBelow = localize('DataScience.runCurrentCellAndAddBelow', 'Run current and add cell below');
export const variableExplorerDisabledDuringDebugging = localize('DataScience.variableExplorerDisabledDuringDebugging', 'Please see the Debug Side Bar\'s VARIABLES section.');
export const jupyterDebuggerNotInstalledError = localize('DataScience.jupyterDebuggerNotInstalledError', 'Pip module ptvsd is required for debugging cells. You will need to install it to debug cells.');
Expand Down
17 changes: 16 additions & 1 deletion src/client/datascience/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,22 @@ export namespace Commands {
export const AddCellBelow = 'python.datascience.addcellbelow';
export const DebugCurrentCellPalette = 'python.datascience.debugcurrentcell.palette';
export const DebugCell = 'python.datascience.debugcell';
export const DebugStepOver = 'python.datascience.debugstepover';
export const DebugContinue = 'python.datascience.debugcontinue';
export const DebugStop = 'python.datascience.debugstop';
export const RunCurrentCellAndAddBelow = 'python.datascience.runcurrentcellandaddbelow';
export const ScrollToCell = 'python.datascience.scrolltocell';
}

export namespace CodeLensCommands {
// If not specified in the options this is the default set of commands in our design time code lenses
export const DefaultDesignLenses = [Commands.RunCurrentCell, Commands.RunAllCellsAbove, Commands.DebugCell];
// If not specified in the options this is the default set of commands in our debug time code lenses
export const DefaultDebuggingLenses = [Commands.DebugContinue, Commands.DebugStop, Commands.DebugStepOver];
// These are the commands that are allowed at debug time
export const DebuggerCommands = [Commands.DebugContinue, Commands.DebugStop, Commands.DebugStepOver];
}

export namespace EditorContexts {
export const HasCodeCells = 'python.datascience.hascodecells';
export const DataScienceEnabled = 'python.datascience.featureenabled';
Expand Down Expand Up @@ -152,7 +164,10 @@ export enum Telemetry {
PtvsdPromptToInstall = 'DATASCIENCE.PTVSD_PROMPT_TO_INSTALL',
PtvsdSuccessfullyInstalled = 'DATASCIENCE.PTVSD_SUCCESSFULLY_INSTALLED',
PtvsdInstallFailed = 'DATASCIENCE.PTVSD_INSTALL_FAILED',
ScrolledToCell = 'DATASCIENCE.SCROLLED_TO_CELL'
ScrolledToCell = 'DATASCIENCE.SCROLLED_TO_CELL',
DebugStepOver = 'DATASCIENCE.DEBUG_STEP_OVER',
DebugContinue = 'DATASCIENCE.DEBUG_CONTINUE',
DebugStop = 'DATASCIENCE.DEBUG_STOP'
}

export namespace HelpLinks {
Expand Down
39 changes: 38 additions & 1 deletion src/client/datascience/datascience.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { inject, injectable } from 'inversify';
import { URL } from 'url';
import * as vscode from 'vscode';

import { IApplicationShell, ICommandManager, IDocumentManager, IWorkspaceService } from '../common/application/types';
import { IApplicationShell, ICommandManager, IDebugService, IDocumentManager, IWorkspaceService } from '../common/application/types';
import { PYTHON_ALLFILES, PYTHON_LANGUAGE } from '../common/constants';
import { ContextKey } from '../common/contextKey';
import { traceError } from '../common/logger';
Expand Down Expand Up @@ -43,6 +43,7 @@ export class DataScience implements IDataScience {
@inject(IConfigurationService) private configuration: IConfigurationService,
@inject(IDocumentManager) private documentManager: IDocumentManager,
@inject(IApplicationShell) private appShell: IApplicationShell,
@inject(IDebugService) private debugService: IDebugService,
@inject(IWorkspaceService) private workspace: IWorkspaceService
) {
this.commandListeners = this.serviceContainer.getAll<IDataScienceCommandListener>(IDataScienceCommandListener);
Expand Down Expand Up @@ -243,6 +244,36 @@ export class DataScience implements IDataScience {
}
}

@captureTelemetry(Telemetry.DebugStepOver)
public async debugStepOver(): Promise<void> {
this.dataScienceSurveyBanner.showBanner().ignoreErrors();

// Make sure that we are in debug mode
if (this.debugService.activeDebugSession) {
this.commandManager.executeCommand('workbench.action.debug.stepOver');
}
}

@captureTelemetry(Telemetry.DebugStop)
public async debugStop(): Promise<void> {
this.dataScienceSurveyBanner.showBanner().ignoreErrors();

// Make sure that we are in debug mode
if (this.debugService.activeDebugSession) {
this.commandManager.executeCommand('workbench.action.debug.stop');
}
}

@captureTelemetry(Telemetry.DebugContinue)
public async debugContinue(): Promise<void> {
this.dataScienceSurveyBanner.showBanner().ignoreErrors();

// Make sure that we are in debug mode
if (this.debugService.activeDebugSession) {
this.commandManager.executeCommand('workbench.action.debug.continue');
}
}

@captureTelemetry(Telemetry.SetJupyterURIToLocal)
private async setJupyterURIToLocal(): Promise<void> {
await this.configuration.updateSetting('dataScience.jupyterServerURI', Settings.JupyterServerLocalLaunch, undefined, vscode.ConfigurationTarget.Workspace);
Expand Down Expand Up @@ -417,6 +448,12 @@ export class DataScience implements IDataScience {
this.disposableRegistry.push(disposable);
disposable = this.commandManager.registerCommand(Commands.DebugCell, this.debugCell, this);
this.disposableRegistry.push(disposable);
disposable = this.commandManager.registerCommand(Commands.DebugStepOver, this.debugStepOver, this);
this.disposableRegistry.push(disposable);
disposable = this.commandManager.registerCommand(Commands.DebugContinue, this.debugContinue, this);
this.disposableRegistry.push(disposable);
disposable = this.commandManager.registerCommand(Commands.DebugStop, this.debugStop, this);
this.disposableRegistry.push(disposable);
disposable = this.commandManager.registerCommand(Commands.DebugCurrentCellPalette, this.debugCurrentCellFromCursor, this);
this.disposableRegistry.push(disposable);
this.commandListeners.forEach((listener: IDataScienceCommandListener) => {
Expand Down
110 changes: 110 additions & 0 deletions src/client/datascience/debugLocationTracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
import { injectable } from 'inversify';
import { DebugSession, Event, EventEmitter } from 'vscode';
import { DebugProtocol } from 'vscode-debugprotocol';

import { IDebugLocation, IDebugLocationTracker } from './types';

// When a python debugging session is active keep track of the current debug location
@injectable()
export class DebugLocationTracker implements IDebugLocationTracker {
private waitingForStackTrace: boolean = false;
private _debugLocation: IDebugLocation | undefined;
private debugLocationUpdatedEvent: EventEmitter<void> = new EventEmitter<void>();

public setDebugSession(_targetSession: DebugSession) {
this.DebugLocation = undefined;
this.waitingForStackTrace = false;
}

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

public get debugLocation(): IDebugLocation | undefined {
return this._debugLocation;
}

// tslint:disable-next-line:no-any
public onDidSendMessage(message: DebugProtocol.ProtocolMessage) {
if (this.isStopEvent(message)) {
// Some type of stop, wait to see our next stack trace to find our location
this.waitingForStackTrace = true;
}

if (this.isContinueEvent(message)) {
// Running, clear the location
this.DebugLocation = undefined;
this.waitingForStackTrace = false;
}

if (this.waitingForStackTrace) {
// If we are waiting for a stack track, check our messages for one
const debugLoc = this.getStackTrace(message);
if (debugLoc) {
this.DebugLocation = debugLoc;
this.waitingForStackTrace = false;
}
}

}

// Set our new location and fire our debug event
private set DebugLocation(newLocation: IDebugLocation | undefined) {
const oldLocation = this._debugLocation;
this._debugLocation = newLocation;

if (this._debugLocation !== oldLocation) {
this.debugLocationUpdatedEvent.fire();
}
}

// tslint:disable-next-line:no-any
private isStopEvent(message: DebugProtocol.ProtocolMessage) {
if (message.type === 'event') {
const eventMessage = message as DebugProtocol.Event;
if (eventMessage.event === 'stopped') {
return true;
}
}

return false;
}

// tslint:disable-next-line:no-any
private getStackTrace(message: DebugProtocol.ProtocolMessage): IDebugLocation | undefined {
if (message.type === 'response') {
const responseMessage = message as DebugProtocol.Response;
if (responseMessage.command === 'stackTrace') {
const messageBody = responseMessage.body;
if (messageBody.stackFrames.length > 0) {
const lineNumber = messageBody.stackFrames[0].line;
const fileName = messageBody.stackFrames[0].source.path;
const column = messageBody.stackFrames[0].column;
return { lineNumber, fileName, column };
}
}
}

return undefined;
}

// tslint:disable-next-line:no-any
private isContinueEvent(message: DebugProtocol.ProtocolMessage): boolean {
if (message.type === 'event') {
const eventMessage = message as DebugProtocol.Event;
if (eventMessage.event === 'continue') {
return true;
}
} else if (message.type === 'response') {
const responseMessage = message as DebugProtocol.Response;
if (responseMessage.command === 'continue') {
return true;
}
}

return false;
}
}
26 changes: 26 additions & 0 deletions src/client/datascience/debugLocationTrackerFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
import { inject, injectable } from 'inversify';
import { DebugAdapterTracker, DebugSession, ProviderResult } from 'vscode';

import { IDebugService } from '../common/application/types';
import { IDisposableRegistry } from '../common/types';
import { IDebugLocationTracker, IDebugLocationTrackerFactory } from './types';

// Hook up our IDebugLocationTracker to python debugging sessions
@injectable()
export class DebugLocationTrackerFactory implements IDebugLocationTrackerFactory {
constructor(
@inject(IDebugLocationTracker) private locationTracker: IDebugLocationTracker,
@inject(IDebugService) debugService: IDebugService,
@inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry
) {
disposableRegistry.push(debugService.registerDebugAdapterTrackerFactory('python', this));
}

public createDebugAdapterTracker(session: DebugSession): ProviderResult<DebugAdapterTracker> {
this.locationTracker.setDebugSession(session);
return this.locationTracker;
}
}
Loading