Skip to content

Commit 52dcd3f

Browse files
Merge pull request #6807 from IanMatthewHuff/release
Debug Mode Code lenses (#6792)
2 parents 2b774ab + 0961be7 commit 52dcd3f

24 files changed

+525
-32
lines changed

news/1 Enhancements/6672.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add debug command code lenses when in debug mode

package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,26 @@
351351
"title": "%python.command.python.datascience.runcurrentcell.title%",
352352
"category": "Python"
353353
},
354+
{
355+
"command": "python.datascience.debugcell",
356+
"title": "%python.command.python.datascience.debugcell.title%",
357+
"category": "Python"
358+
},
359+
{
360+
"command": "python.datascience.debugstepover",
361+
"title": "%python.command.python.datascience.debugstepover.title%",
362+
"category": "Python"
363+
},
364+
{
365+
"command": "python.datascience.debugstop",
366+
"title": "%python.command.python.datascience.debugstop.title%",
367+
"category": "Python"
368+
},
369+
{
370+
"command": "python.datascience.debugcontinue",
371+
"title": "%python.command.python.datascience.debugcontinue.title%",
372+
"category": "Python"
373+
},
354374
{
355375
"command": "python.datascience.runcurrentcelladvance",
356376
"title": "%python.command.python.datascience.runcurrentcelladvance.title%",
@@ -1430,6 +1450,12 @@
14301450
"description": "Set of commands to put as code lens above a cell. Defaults to 'python.datascience.runcell, python.datascience.runallcellsabove, python.datascience.debugcell'",
14311451
"scope": "resource"
14321452
},
1453+
"python.dataScience.debugCodeLenses": {
1454+
"type": "string",
1455+
"default": "python.datascience.debugcontinue, python.datascience.debugstop, python.datascience.debugstepover",
1456+
"description": "Set of debug commands to put as code lens above a cell while debugging.",
1457+
"scope": "resource"
1458+
},
14331459
"python.dataScience.ptvsdDistPath": {
14341460
"type": "string",
14351461
"default": "",

package.nls.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
"python.command.python.datascience.runcurrentcellandallbelow.palette.title": "Run Current Cell and Below",
4040
"python.command.python.datascience.debugcurrentcell.palette.title": "Debug Current Cell",
4141
"python.command.python.datascience.debugcell.title": "Debug Cell",
42+
"python.command.python.datascience.debugstepover.title": "Step Over",
43+
"python.command.python.datascience.debugcontinue.title": "Continue",
44+
"python.command.python.datascience.debugstop.title": "Stop",
4245
"python.command.python.datascience.runtoline.title": "Run To Line in Python Interactive Window",
4346
"python.command.python.datascience.runfromline.title": "Run From Line in Python Interactive Window",
4447
"python.command.python.datascience.runcurrentcell.title": "Run Current Cell",
@@ -305,6 +308,9 @@
305308
"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",
306309
"DataScience.addCellBelowCommandTitle": "Add cell",
307310
"DataScience.debugCellCommandTitle": "Debug cell",
311+
"DataScience.debugStepOverCommandTitle": "Step over",
312+
"DataScience.debugContinueCommandTitle": "Continue",
313+
"DataScience.debugStopCommandTitle": "Stop",
308314
"DataScience.runCurrentCellAndAddBelow": "Run current and add cell below",
309315
"DataScience.variableExplorerDisabledDuringDebugging": "Please see the Debug Side Bar's VARIABLES section.",
310316
"DataScience.jupyterDebuggerNotInstalledError": "Pip module ptvsd is required for debugging cells. You will need to install it to debug cells.",

src/client/common/application/commands.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ interface ICommandNameWithoutArgumentTypeMapping {
2525
[Commands.Set_ShebangInterpreter]: [];
2626
[Commands.Run_Linter]: [];
2727
[Commands.Enable_Linter]: [];
28+
['workbench.action.debug.continue']: [];
29+
['workbench.action.debug.stepOver']: [];
2830
['workbench.action.debug.stop']: [];
2931
['workbench.action.reloadWindow']: [];
3032
['editor.action.formatDocument']: [];
@@ -115,6 +117,9 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu
115117
[DSCommands.RunFileInInteractiveWindows]: [string];
116118
[DSCommands.DebugFileInInteractiveWindows]: [string];
117119
[DSCommands.DebugCell]: [string, number, number, number, number];
120+
[DSCommands.DebugStepOver]: [];
121+
[DSCommands.DebugStop]: [];
122+
[DSCommands.DebugContinue]: [];
118123
[DSCommands.RunCurrentCellAndAddBelow]: [string];
119124
[DSCommands.ScrollToCell]: [string, string];
120125
}

src/client/common/application/debugService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ export class DebugService implements IDebugService {
3838
public registerDebugConfigurationProvider(debugType: string, provider: any): Disposable {
3939
return debug.registerDebugConfigurationProvider(debugType, provider);
4040
}
41+
// tslint:disable-next-line:no-any
42+
public registerDebugAdapterTrackerFactory(debugType: string, provider: any): Disposable {
43+
return debug.registerDebugAdapterTrackerFactory(debugType, provider);
44+
}
4145
public startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, parentSession?: DebugSession): Thenable<boolean> {
4246
return debug.startDebugging(folder, nameOrConfiguration, parentSession);
4347
}

src/client/common/application/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
CancellationToken,
88
CompletionItemProvider,
99
ConfigurationChangeEvent,
10+
DebugAdapterTrackerFactory,
1011
DebugConfiguration,
1112
DebugConfigurationProvider,
1213
DebugConsole,
@@ -764,6 +765,15 @@ export interface IDebugService {
764765
*/
765766
registerDebugConfigurationProvider(debugType: string, provider: DebugConfigurationProvider): Disposable;
766767

768+
/**
769+
* Register a debug adapter tracker factory for the given debug type.
770+
*
771+
* @param debugType The debug type for which the factory is registered or '*' for matching all debug types.
772+
* @param factory The [debug adapter tracker factory](#DebugAdapterTrackerFactory) to register.
773+
* @return A [disposable](#Disposable) that unregisters this factory when being disposed.
774+
*/
775+
registerDebugAdapterTrackerFactory(debugType: string, factory: DebugAdapterTrackerFactory): Disposable;
776+
767777
/**
768778
* Start debugging by using either a named launch or named compound configuration,
769779
* or by directly passing a [DebugConfiguration](#DebugConfiguration).

src/client/common/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ export interface IDataScienceSettings {
324324
askForKernelRestart?: boolean;
325325
enablePlotViewer?: boolean;
326326
codeLenses?: string;
327+
debugCodeLenses?: string;
327328
ptvsdDistPath?: string;
328329
stopOnFirstLineWhileDebugging?: boolean;
329330
textOutputLimit?: number;

src/client/common/utils/localize.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ export namespace DataScience {
229229
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');
230230
export const addCellBelowCommandTitle = localize('DataScience.addCellBelowCommandTitle', 'Add cell');
231231
export const debugCellCommandTitle = localize('DataScience.debugCellCommandTitle', 'Debug cell');
232+
export const debugStepOverCommandTitle = localize('DataScience.debugStepOverCommandTitle', 'Step over');
233+
export const debugContinueCommandTitle = localize('DataScience.debugContinueCommandTitle', 'Continue');
234+
export const debugStopCommandTitle = localize('DataScience.debugStopCommandTitle', 'Stop');
232235
export const runCurrentCellAndAddBelow = localize('DataScience.runCurrentCellAndAddBelow', 'Run current and add cell below');
233236
export const variableExplorerDisabledDuringDebugging = localize('DataScience.variableExplorerDisabledDuringDebugging', 'Please see the Debug Side Bar\'s VARIABLES section.');
234237
export const jupyterDebuggerNotInstalledError = localize('DataScience.jupyterDebuggerNotInstalledError', 'Pip module ptvsd is required for debugging cells. You will need to install it to debug cells.');

src/client/datascience/constants.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,22 @@ export namespace Commands {
3636
export const AddCellBelow = 'python.datascience.addcellbelow';
3737
export const DebugCurrentCellPalette = 'python.datascience.debugcurrentcell.palette';
3838
export const DebugCell = 'python.datascience.debugcell';
39+
export const DebugStepOver = 'python.datascience.debugstepover';
40+
export const DebugContinue = 'python.datascience.debugcontinue';
41+
export const DebugStop = 'python.datascience.debugstop';
3942
export const RunCurrentCellAndAddBelow = 'python.datascience.runcurrentcellandaddbelow';
4043
export const ScrollToCell = 'python.datascience.scrolltocell';
4144
}
4245

46+
export namespace CodeLensCommands {
47+
// If not specified in the options this is the default set of commands in our design time code lenses
48+
export const DefaultDesignLenses = [Commands.RunCurrentCell, Commands.RunAllCellsAbove, Commands.DebugCell];
49+
// If not specified in the options this is the default set of commands in our debug time code lenses
50+
export const DefaultDebuggingLenses = [Commands.DebugContinue, Commands.DebugStop, Commands.DebugStepOver];
51+
// These are the commands that are allowed at debug time
52+
export const DebuggerCommands = [Commands.DebugContinue, Commands.DebugStop, Commands.DebugStepOver];
53+
}
54+
4355
export namespace EditorContexts {
4456
export const HasCodeCells = 'python.datascience.hascodecells';
4557
export const DataScienceEnabled = 'python.datascience.featureenabled';
@@ -152,7 +164,10 @@ export enum Telemetry {
152164
PtvsdPromptToInstall = 'DATASCIENCE.PTVSD_PROMPT_TO_INSTALL',
153165
PtvsdSuccessfullyInstalled = 'DATASCIENCE.PTVSD_SUCCESSFULLY_INSTALLED',
154166
PtvsdInstallFailed = 'DATASCIENCE.PTVSD_INSTALL_FAILED',
155-
ScrolledToCell = 'DATASCIENCE.SCROLLED_TO_CELL'
167+
ScrolledToCell = 'DATASCIENCE.SCROLLED_TO_CELL',
168+
DebugStepOver = 'DATASCIENCE.DEBUG_STEP_OVER',
169+
DebugContinue = 'DATASCIENCE.DEBUG_CONTINUE',
170+
DebugStop = 'DATASCIENCE.DEBUG_STOP'
156171
}
157172

158173
export namespace HelpLinks {

src/client/datascience/datascience.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { inject, injectable } from 'inversify';
88
import { URL } from 'url';
99
import * as vscode from 'vscode';
1010

11-
import { IApplicationShell, ICommandManager, IDocumentManager, IWorkspaceService } from '../common/application/types';
11+
import { IApplicationShell, ICommandManager, IDebugService, IDocumentManager, IWorkspaceService } from '../common/application/types';
1212
import { PYTHON_ALLFILES, PYTHON_LANGUAGE } from '../common/constants';
1313
import { ContextKey } from '../common/contextKey';
1414
import { traceError } from '../common/logger';
@@ -43,6 +43,7 @@ export class DataScience implements IDataScience {
4343
@inject(IConfigurationService) private configuration: IConfigurationService,
4444
@inject(IDocumentManager) private documentManager: IDocumentManager,
4545
@inject(IApplicationShell) private appShell: IApplicationShell,
46+
@inject(IDebugService) private debugService: IDebugService,
4647
@inject(IWorkspaceService) private workspace: IWorkspaceService
4748
) {
4849
this.commandListeners = this.serviceContainer.getAll<IDataScienceCommandListener>(IDataScienceCommandListener);
@@ -243,6 +244,36 @@ export class DataScience implements IDataScience {
243244
}
244245
}
245246

247+
@captureTelemetry(Telemetry.DebugStepOver)
248+
public async debugStepOver(): Promise<void> {
249+
this.dataScienceSurveyBanner.showBanner().ignoreErrors();
250+
251+
// Make sure that we are in debug mode
252+
if (this.debugService.activeDebugSession) {
253+
this.commandManager.executeCommand('workbench.action.debug.stepOver');
254+
}
255+
}
256+
257+
@captureTelemetry(Telemetry.DebugStop)
258+
public async debugStop(): Promise<void> {
259+
this.dataScienceSurveyBanner.showBanner().ignoreErrors();
260+
261+
// Make sure that we are in debug mode
262+
if (this.debugService.activeDebugSession) {
263+
this.commandManager.executeCommand('workbench.action.debug.stop');
264+
}
265+
}
266+
267+
@captureTelemetry(Telemetry.DebugContinue)
268+
public async debugContinue(): Promise<void> {
269+
this.dataScienceSurveyBanner.showBanner().ignoreErrors();
270+
271+
// Make sure that we are in debug mode
272+
if (this.debugService.activeDebugSession) {
273+
this.commandManager.executeCommand('workbench.action.debug.continue');
274+
}
275+
}
276+
246277
@captureTelemetry(Telemetry.SetJupyterURIToLocal)
247278
private async setJupyterURIToLocal(): Promise<void> {
248279
await this.configuration.updateSetting('dataScience.jupyterServerURI', Settings.JupyterServerLocalLaunch, undefined, vscode.ConfigurationTarget.Workspace);
@@ -417,6 +448,12 @@ export class DataScience implements IDataScience {
417448
this.disposableRegistry.push(disposable);
418449
disposable = this.commandManager.registerCommand(Commands.DebugCell, this.debugCell, this);
419450
this.disposableRegistry.push(disposable);
451+
disposable = this.commandManager.registerCommand(Commands.DebugStepOver, this.debugStepOver, this);
452+
this.disposableRegistry.push(disposable);
453+
disposable = this.commandManager.registerCommand(Commands.DebugContinue, this.debugContinue, this);
454+
this.disposableRegistry.push(disposable);
455+
disposable = this.commandManager.registerCommand(Commands.DebugStop, this.debugStop, this);
456+
this.disposableRegistry.push(disposable);
420457
disposable = this.commandManager.registerCommand(Commands.DebugCurrentCellPalette, this.debugCurrentCellFromCursor, this);
421458
this.disposableRegistry.push(disposable);
422459
this.commandListeners.forEach((listener: IDataScienceCommandListener) => {
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
'use strict';
4+
import { injectable } from 'inversify';
5+
import { DebugSession, Event, EventEmitter } from 'vscode';
6+
import { DebugProtocol } from 'vscode-debugprotocol';
7+
8+
import { IDebugLocation, IDebugLocationTracker } from './types';
9+
10+
// When a python debugging session is active keep track of the current debug location
11+
@injectable()
12+
export class DebugLocationTracker implements IDebugLocationTracker {
13+
private waitingForStackTrace: boolean = false;
14+
private _debugLocation: IDebugLocation | undefined;
15+
private debugLocationUpdatedEvent: EventEmitter<void> = new EventEmitter<void>();
16+
17+
public setDebugSession(_targetSession: DebugSession) {
18+
this.DebugLocation = undefined;
19+
this.waitingForStackTrace = false;
20+
}
21+
22+
public get debugLocationUpdated(): Event<void> {
23+
return this.debugLocationUpdatedEvent.event;
24+
}
25+
26+
public get debugLocation(): IDebugLocation | undefined {
27+
return this._debugLocation;
28+
}
29+
30+
// tslint:disable-next-line:no-any
31+
public onDidSendMessage(message: DebugProtocol.ProtocolMessage) {
32+
if (this.isStopEvent(message)) {
33+
// Some type of stop, wait to see our next stack trace to find our location
34+
this.waitingForStackTrace = true;
35+
}
36+
37+
if (this.isContinueEvent(message)) {
38+
// Running, clear the location
39+
this.DebugLocation = undefined;
40+
this.waitingForStackTrace = false;
41+
}
42+
43+
if (this.waitingForStackTrace) {
44+
// If we are waiting for a stack track, check our messages for one
45+
const debugLoc = this.getStackTrace(message);
46+
if (debugLoc) {
47+
this.DebugLocation = debugLoc;
48+
this.waitingForStackTrace = false;
49+
}
50+
}
51+
52+
}
53+
54+
// Set our new location and fire our debug event
55+
private set DebugLocation(newLocation: IDebugLocation | undefined) {
56+
const oldLocation = this._debugLocation;
57+
this._debugLocation = newLocation;
58+
59+
if (this._debugLocation !== oldLocation) {
60+
this.debugLocationUpdatedEvent.fire();
61+
}
62+
}
63+
64+
// tslint:disable-next-line:no-any
65+
private isStopEvent(message: DebugProtocol.ProtocolMessage) {
66+
if (message.type === 'event') {
67+
const eventMessage = message as DebugProtocol.Event;
68+
if (eventMessage.event === 'stopped') {
69+
return true;
70+
}
71+
}
72+
73+
return false;
74+
}
75+
76+
// tslint:disable-next-line:no-any
77+
private getStackTrace(message: DebugProtocol.ProtocolMessage): IDebugLocation | undefined {
78+
if (message.type === 'response') {
79+
const responseMessage = message as DebugProtocol.Response;
80+
if (responseMessage.command === 'stackTrace') {
81+
const messageBody = responseMessage.body;
82+
if (messageBody.stackFrames.length > 0) {
83+
const lineNumber = messageBody.stackFrames[0].line;
84+
const fileName = messageBody.stackFrames[0].source.path;
85+
const column = messageBody.stackFrames[0].column;
86+
return { lineNumber, fileName, column };
87+
}
88+
}
89+
}
90+
91+
return undefined;
92+
}
93+
94+
// tslint:disable-next-line:no-any
95+
private isContinueEvent(message: DebugProtocol.ProtocolMessage): boolean {
96+
if (message.type === 'event') {
97+
const eventMessage = message as DebugProtocol.Event;
98+
if (eventMessage.event === 'continue') {
99+
return true;
100+
}
101+
} else if (message.type === 'response') {
102+
const responseMessage = message as DebugProtocol.Response;
103+
if (responseMessage.command === 'continue') {
104+
return true;
105+
}
106+
}
107+
108+
return false;
109+
}
110+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
'use strict';
4+
import { inject, injectable } from 'inversify';
5+
import { DebugAdapterTracker, DebugSession, ProviderResult } from 'vscode';
6+
7+
import { IDebugService } from '../common/application/types';
8+
import { IDisposableRegistry } from '../common/types';
9+
import { IDebugLocationTracker, IDebugLocationTrackerFactory } from './types';
10+
11+
// Hook up our IDebugLocationTracker to python debugging sessions
12+
@injectable()
13+
export class DebugLocationTrackerFactory implements IDebugLocationTrackerFactory {
14+
constructor(
15+
@inject(IDebugLocationTracker) private locationTracker: IDebugLocationTracker,
16+
@inject(IDebugService) debugService: IDebugService,
17+
@inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry
18+
) {
19+
disposableRegistry.push(debugService.registerDebugAdapterTrackerFactory('python', this));
20+
}
21+
22+
public createDebugAdapterTracker(session: DebugSession): ProviderResult<DebugAdapterTracker> {
23+
this.locationTracker.setDebugSession(session);
24+
return this.locationTracker;
25+
}
26+
}

0 commit comments

Comments
 (0)