From 2492c415c9c99d4100fec4c68252cc20f008e608 Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Mon, 29 Jul 2019 15:23:46 -0700 Subject: [PATCH 01/12] working, but on all lenses --- package.json | 21 ++++++++++ package.nls.json | 6 +++ src/client/common/application/commands.ts | 5 +++ src/client/common/types.ts | 1 + src/client/common/utils/localize.ts | 3 ++ src/client/datascience/constants.ts | 12 ++++++ src/client/datascience/datascience.ts | 37 ++++++++++++++++- .../editor-integration/codeLensFactory.ts | 40 +++++++++++++++++-- .../editor-integration/codelensprovider.ts | 28 +++++++++++-- 9 files changed, 146 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index c40a2a64fcb7..49ebfe8f14f3 100644 --- a/package.json +++ b/package.json @@ -338,6 +338,21 @@ "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%", @@ -1398,6 +1413,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": "", diff --git a/package.nls.json b/package.nls.json index 36d58c8fff0c..465e85ed8494 100644 --- a/package.nls.json +++ b/package.nls.json @@ -38,6 +38,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", @@ -303,6 +306,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": "Variables are not available while debugging.", "DataScience.jupyterDebuggerNotInstalledError" : "Pip module ptvsd is required for debugging cells. You will need to install it to debug cells.", diff --git a/src/client/common/application/commands.ts b/src/client/common/application/commands.ts index b756b22db06b..388bc9059c5b 100644 --- a/src/client/common/application/commands.ts +++ b/src/client/common/application/commands.ts @@ -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']: []; @@ -112,6 +114,9 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu [DSCommands.ExportFileAsNotebook]: [undefined | Uri, undefined | CommandSource]; [DSCommands.RunFileInInteractiveWindows]: [string]; [DSCommands.DebugCell]: [string, number, number, number, number]; + [DSCommands.DebugStepOver]: []; + [DSCommands.DebugStop]: []; + [DSCommands.DebugContinue]: []; [DSCommands.RunCurrentCellAndAddBelow]: [string]; [DSCommands.ScrollToCell]: [string, string]; } diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 60266c259ac1..fc525b2dedc3 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -324,6 +324,7 @@ export interface IDataScienceSettings { askForKernelRestart?: boolean; enablePlotViewer?: boolean; codeLenses?: string; + debugCodeLenses?: string; ptvsdDistPath?: string; stopOnFirstLineWhileDebugging?: boolean; textOutputLimit?: number; diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 0bf97e665875..40baf3305ba0 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -228,6 +228,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', 'Variables are not available while debugging.'); export const jupyterDebuggerNotInstalledError = localize('DataScience.jupyterDebuggerNotInstalledError', 'Pip module ptvsd is required for debugging cells. You will need to install it to debug cells.'); diff --git a/src/client/datascience/constants.ts b/src/client/datascience/constants.ts index e2be6612c043..f8f10b9f7936 100644 --- a/src/client/datascience/constants.ts +++ b/src/client/datascience/constants.ts @@ -35,10 +35,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'; diff --git a/src/client/datascience/datascience.ts b/src/client/datascience/datascience.ts index f3182ea92446..424987103359 100644 --- a/src/client/datascience/datascience.ts +++ b/src/client/datascience/datascience.ts @@ -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'; @@ -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); @@ -229,6 +230,34 @@ export class DataScience implements IDataScience { } } + // IANHU: Telemetry? + public async debugStepOver(): Promise { + this.dataScienceSurveyBanner.showBanner().ignoreErrors(); + + // Make sure that we are in debug mode + if (this.debugService.activeDebugSession) { + this.commandManager.executeCommand('workbench.action.debug.stepOver'); + } + } + + public async debugStop(): Promise { + this.dataScienceSurveyBanner.showBanner().ignoreErrors(); + + // Make sure that we are in debug mode + if (this.debugService.activeDebugSession) { + this.commandManager.executeCommand('workbench.action.debug.stop'); + } + } + + public async debugContinue(): Promise { + 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 { await this.configuration.updateSetting('dataScience.jupyterServerURI', Settings.JupyterServerLocalLaunch, undefined, vscode.ConfigurationTarget.Workspace); @@ -401,6 +430,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) => { diff --git a/src/client/datascience/editor-integration/codeLensFactory.ts b/src/client/datascience/editor-integration/codeLensFactory.ts index c183119b898f..da1447814dc3 100644 --- a/src/client/datascience/editor-integration/codeLensFactory.ts +++ b/src/client/datascience/editor-integration/codeLensFactory.ts @@ -9,7 +9,7 @@ import { IConfigurationService } from '../../common/types'; import * as localize from '../../common/utils/localize'; import { noop } from '../../common/utils/misc'; import { generateCellRanges } from '../cellFactory'; -import { Commands } from '../constants'; +import { CodeLensCommands, Commands } from '../constants'; import { InteractiveWindowMessages } from '../interactive-window/interactiveWindowTypes'; import { ICell, ICellHashProvider, ICodeLensFactory, IFileHashes, IInteractiveWindowListener } from '../types'; @@ -79,11 +79,21 @@ export class CodeLensFactory implements ICodeLensFactory, IInteractiveWindowList } private enumerateCommands(): string[] { + let fullCommandList: string[]; const commands = this.configService.getSettings().datascience.codeLenses; if (commands) { - return commands.split(',').map(s => s.trim()); + fullCommandList = commands.split(',').map(s => s.trim()); + } else { + fullCommandList = CodeLensCommands.DefaultDesignLenses; } - return [Commands.RunCurrentCell, Commands.RunAllCellsAbove, Commands.DebugCell]; + const debugCommands = this.configService.getSettings().datascience.debugCodeLenses; + if (debugCommands) { + fullCommandList = fullCommandList.concat(debugCommands.split(',').map(s => s.trim())); + } else { + fullCommandList = fullCommandList.concat(CodeLensCommands.DefaultDebuggingLenses); + } + + return fullCommandList; } private createCodeLens(document: TextDocument, cellRange: { range: Range; cell_type: string }, commandName: string, isFirst: boolean): CodeLens | undefined { @@ -118,6 +128,30 @@ export class CodeLensFactory implements ICodeLensFactory, IInteractiveWindowList localize.DataScience.debugCellCommandTitle(), [document.fileName, range.start.line, range.start.character, range.end.line, range.end.character]); + case Commands.DebugStepOver: + // Only code cells get debug actions + if (cell_type !== 'code') { break; } + return this.generateCodeLens( + range, + Commands.DebugStepOver, + localize.DataScience.debugStepOverCommandTitle()); + + case Commands.DebugContinue: + // Only code cells get debug actions + if (cell_type !== 'code') { break; } + return this.generateCodeLens( + range, + Commands.DebugContinue, + localize.DataScience.debugContinueCommandTitle()); + + case Commands.DebugStop: + // Only code cells get debug actions + if (cell_type !== 'code') { break; } + return this.generateCodeLens( + range, + Commands.DebugStop, + localize.DataScience.debugStopCommandTitle()); + case Commands.RunCurrentCell: case Commands.RunCell: return this.generateCodeLens( diff --git a/src/client/datascience/editor-integration/codelensprovider.ts b/src/client/datascience/editor-integration/codelensprovider.ts index 725962b3fb62..d8f9079c8fd9 100644 --- a/src/client/datascience/editor-integration/codelensprovider.ts +++ b/src/client/datascience/editor-integration/codelensprovider.ts @@ -10,7 +10,7 @@ import { IConfigurationService, IDataScienceSettings, IDisposable, IDisposableRe import { StopWatch } from '../../common/utils/stopWatch'; import { IServiceContainer } from '../../ioc/types'; import { sendTelemetryEvent } from '../../telemetry'; -import { EditorContexts, Telemetry } from '../constants'; +import { CodeLensCommands, Commands, EditorContexts, Telemetry } from '../constants'; import { ICodeWatcher, IDataScienceCodeLensProvider } from '../types'; @injectable() @@ -80,7 +80,7 @@ export class DataScienceCodeLensProvider implements IDataScienceCodeLensProvider // Don't provide any code lenses if we have not enabled data science const settings = this.configuration.getSettings(); - if (!settings.datascience.enabled || !settings.datascience.enableCellCodeLens || this.debugService.activeDebugSession) { + if (!settings.datascience.enabled || !settings.datascience.enableCellCodeLens) { // Clear out any existing code watchers, providecodelenses is called on settings change // so we don't need to watch the settings change specifically here if (this.activeCodeWatchers.length > 0) { @@ -89,7 +89,29 @@ export class DataScienceCodeLensProvider implements IDataScienceCodeLensProvider return []; } - return result; + return this.adjustDebuggingLenses(result); + } + + private adjustDebuggingLenses(lenses: vscode.CodeLens[]): vscode.CodeLens[] { + const debugCellList = CodeLensCommands.DebuggerCommands; + + if (this.debugService.activeDebugSession) { + return lenses.filter(lens => { + if (lens.command) { + return debugCellList.includes(lens.command.command); + } + + return false; + }); + } else { + return lenses.filter(lens => { + if (lens.command) { + return !(debugCellList.includes(lens.command.command)); + } + + return false; + }); + } } private getCodeLens(document: vscode.TextDocument): vscode.CodeLens[] { From 8a28a3339f409eafd2e73e8d4f3cea1ba90aa6ff Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Tue, 30 Jul 2019 09:22:53 -0700 Subject: [PATCH 02/12] first hookup of location tracker --- src/client/common/application/debugService.ts | 4 +++ src/client/common/application/types.ts | 4 +++ .../datascience/debugLocationTracker.ts | 19 ++++++++++++++ .../debugLocationTrackerFactory.ts | 25 +++++++++++++++++++ .../editor-integration/codelensprovider.ts | 2 +- src/client/datascience/serviceRegistry.ts | 6 +++++ src/client/datascience/types.ts | 12 ++++++++- src/client/extension.ts | 5 +++- 8 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 src/client/datascience/debugLocationTracker.ts create mode 100644 src/client/datascience/debugLocationTrackerFactory.ts diff --git a/src/client/common/application/debugService.ts b/src/client/common/application/debugService.ts index 20c4fe4cb05f..ab19abd01f04 100644 --- a/src/client/common/application/debugService.ts +++ b/src/client/common/application/debugService.ts @@ -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 { return debug.startDebugging(folder, nameOrConfiguration, parentSession); } diff --git a/src/client/common/application/types.ts b/src/client/common/application/types.ts index e420b354e8c4..eb87f6a08662 100644 --- a/src/client/common/application/types.ts +++ b/src/client/common/application/types.ts @@ -7,6 +7,7 @@ import { CancellationToken, CompletionItemProvider, ConfigurationChangeEvent, + DebugAdapterTrackerFactory, DebugConfiguration, DebugConfigurationProvider, DebugConsole, @@ -764,6 +765,9 @@ export interface IDebugService { */ registerDebugConfigurationProvider(debugType: string, provider: DebugConfigurationProvider): Disposable; + // IANHU: Fill in the comment + 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). diff --git a/src/client/datascience/debugLocationTracker.ts b/src/client/datascience/debugLocationTracker.ts new file mode 100644 index 000000000000..a55dcf583e6c --- /dev/null +++ b/src/client/datascience/debugLocationTracker.ts @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import { injectable } from 'inversify'; +import { DebugSession } from 'vscode'; + +import { IDebugLocationTracker } from './types'; + +@injectable() +export class DebugLocationTracker implements IDebugLocationTracker { + public setDebugSession(targetSession: DebugSession) { + let testing = 1; + testing = 2; + } + + public getDebugLocation(): number { + return 1; + } +} diff --git a/src/client/datascience/debugLocationTrackerFactory.ts b/src/client/datascience/debugLocationTrackerFactory.ts new file mode 100644 index 000000000000..de86399ef7f6 --- /dev/null +++ b/src/client/datascience/debugLocationTrackerFactory.ts @@ -0,0 +1,25 @@ +// 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'; + +@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 { + this.locationTracker.setDebugSession(session); + return this.locationTracker; + } +} diff --git a/src/client/datascience/editor-integration/codelensprovider.ts b/src/client/datascience/editor-integration/codelensprovider.ts index d8f9079c8fd9..56982c58b76d 100644 --- a/src/client/datascience/editor-integration/codelensprovider.ts +++ b/src/client/datascience/editor-integration/codelensprovider.ts @@ -10,7 +10,7 @@ import { IConfigurationService, IDataScienceSettings, IDisposable, IDisposableRe import { StopWatch } from '../../common/utils/stopWatch'; import { IServiceContainer } from '../../ioc/types'; import { sendTelemetryEvent } from '../../telemetry'; -import { CodeLensCommands, Commands, EditorContexts, Telemetry } from '../constants'; +import { CodeLensCommands, EditorContexts, Telemetry } from '../constants'; import { ICodeWatcher, IDataScienceCodeLensProvider } from '../types'; @injectable() diff --git a/src/client/datascience/serviceRegistry.ts b/src/client/datascience/serviceRegistry.ts index c151c337647b..c64b6d78ba4b 100644 --- a/src/client/datascience/serviceRegistry.ts +++ b/src/client/datascience/serviceRegistry.ts @@ -11,6 +11,8 @@ import { Telemetry } from './constants'; import { DataViewer } from './data-viewing/dataViewer'; import { DataViewerProvider } from './data-viewing/dataViewerProvider'; import { DataScience } from './datascience'; +import { DebugLocationTracker } from './debugLocationTracker'; +import { DebugLocationTrackerFactory } from './debugLocationTrackerFactory'; import { CellHashProvider } from './editor-integration/cellhashprovider'; import { CodeLensFactory } from './editor-integration/codeLensFactory'; import { DataScienceCodeLensProvider } from './editor-integration/codelensprovider'; @@ -50,6 +52,8 @@ import { IDataScienceErrorHandler, IDataViewer, IDataViewerProvider, + IDebugLocationTracker, + IDebugLocationTrackerFactory, IInteractiveWindow, IInteractiveWindowListener, IInteractiveWindowProvider, @@ -122,4 +126,6 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addBinding(ICellHashProvider, INotebookExecutionLogger); serviceManager.addBinding(IJupyterDebugger, ICellHashListener); serviceManager.addBinding(ICodeLensFactory, IInteractiveWindowListener); + serviceManager.addSingleton(IDebugLocationTrackerFactory, wrapType(DebugLocationTrackerFactory)); + serviceManager.addSingleton(IDebugLocationTracker, wrapType(DebugLocationTracker)); } diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index 190528c76017..4f190c7f0957 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -5,7 +5,7 @@ import { nbformat } from '@jupyterlab/coreutils'; import { Kernel, KernelMessage } from '@jupyterlab/services/lib/kernel'; import { JSONObject } from '@phosphor/coreutils'; import { Observable } from 'rxjs/Observable'; -import { CancellationToken, CodeLens, CodeLensProvider, Disposable, Event, Range, TextDocument, TextEditor } from 'vscode'; +import { CancellationToken, CodeLens, CodeLensProvider, DebugAdapterTracker, DebugAdapterTrackerFactory, DebugSession, Disposable, Event, Range, TextDocument, TextEditor } from 'vscode'; import { ICommandManager } from '../common/application/types'; import { ExecutionResult, ObservableExecutionResult, SpawnOptions } from '../common/process/types'; @@ -444,3 +444,13 @@ export interface ICellHashProvider { updated: Event; getHashes(): IFileHashes[]; } + +export const IDebugLocationTrackerFactory = Symbol('IDebugLocationTrackerFactory'); +export interface IDebugLocationTrackerFactory extends DebugAdapterTrackerFactory { +} + +export const IDebugLocationTracker = Symbol('IDebugLocationTracker'); +export interface IDebugLocationTracker extends DebugAdapterTracker { + setDebugSession(targetSession: DebugSession): void; + getDebugLocation(): number; +} diff --git a/src/client/extension.ts b/src/client/extension.ts index adf1dbfed115..09cc335531b1 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -65,7 +65,7 @@ import { createDeferred } from './common/utils/async'; import { Common } from './common/utils/localize'; import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; import { registerTypes as dataScienceRegisterTypes } from './datascience/serviceRegistry'; -import { IDataScience } from './datascience/types'; +import { IDataScience, IDebugLocationTrackerFactory } from './datascience/types'; import { DebuggerTypeName } from './debugger/constants'; import { DebugSessionEventDispatcher } from './debugger/extension/hooks/eventHandlerDispatcher'; import { IDebugSessionEventHandlers } from './debugger/extension/hooks/types'; @@ -161,6 +161,9 @@ async function activateUnsafe(context: ExtensionContext): Promise const lintingEngine = serviceManager.get(ILintingEngine); lintingEngine.linkJupyterExtension(jupyterExtension).ignoreErrors(); + // Activate debug location tracker + serviceManager.get(IDebugLocationTrackerFactory); + // Activate data science features const dataScience = serviceManager.get(IDataScience); dataScience.activate().ignoreErrors(); From a3f59091d2d8fa701bc644934208f9ddb1ab2462 Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Tue, 30 Jul 2019 13:05:26 -0700 Subject: [PATCH 03/12] basic tracker working --- .../datascience/debugLocationTracker.ts | 75 +++++++++++++++++-- .../datascience/jupyter/jupyterDebugger.ts | 3 +- src/client/datascience/types.ts | 7 +- 3 files changed, 77 insertions(+), 8 deletions(-) diff --git a/src/client/datascience/debugLocationTracker.ts b/src/client/datascience/debugLocationTracker.ts index a55dcf583e6c..c1c961222746 100644 --- a/src/client/datascience/debugLocationTracker.ts +++ b/src/client/datascience/debugLocationTracker.ts @@ -4,16 +4,79 @@ import { injectable } from 'inversify'; import { DebugSession } from 'vscode'; -import { IDebugLocationTracker } from './types'; +import { traceInfo } from '../common/logger'; +import { IDebugLocation, IDebugLocationTracker } from './types'; @injectable() export class DebugLocationTracker implements IDebugLocationTracker { - public setDebugSession(targetSession: DebugSession) { - let testing = 1; - testing = 2; + private waitingForStackTrace: boolean = false; + private debugLocation: IDebugLocation | undefined; + + public setDebugSession(_targetSession: DebugSession) { + this.debugLocation = undefined; + this.waitingForStackTrace = false; + } + + public getDebugLocation(): IDebugLocation | undefined { + return this.debugLocation; + } + + // tslint:disable-next-line:no-any + public onDidSendMessage(message: any) { + traceInfo('******** Debugger Message'); + traceInfo(message.command); + traceInfo(message.type); + traceInfo(JSON.stringify(message.body)); + + if (this.isStopEvent(message)) { + this.waitingForStackTrace = true; + } + + // IANHU: Else if? + if (this.waitingForStackTrace) { + const debugLoc = this.getStackTrace(message); + if (debugLoc) { + this.debugLocation = debugLoc; + this.waitingForStackTrace = false; + } + } + + if (this.isContinueEvent(message)) { + this.debugLocation = undefined; + this.waitingForStackTrace = false; + } } - public getDebugLocation(): number { - return 1; + // tslint:disable-next-line:no-any + private isStopEvent(message: any) { + if (message.type === 'event') { + if (message.event === 'stop' || message.event === 'stopped') { + return true; + } + } + + return false; + } + + // tslint:disable-next-line:no-any + private getStackTrace(message: any): IDebugLocation | undefined { + if (message.command === 'stackTrace') { + if (message.body.stackFrames.length > 0) { + const lineNumber = message.body.stackFrames[0].line; + const fileName = message.body.stackFrames[0].source.path; + return { lineNumber, fileName }; + } + } + + return undefined; + } + + // tslint:disable-next-line:no-any + private isContinueEvent(message: any): boolean { + if ((message.type === 'event' && message.event === 'continue') || (message.command === 'continue' && message.type === 'response')) { + return true; + } + + return false; } } diff --git a/src/client/datascience/jupyter/jupyterDebugger.ts b/src/client/datascience/jupyter/jupyterDebugger.ts index 2f8a51c4e575..edb23975c505 100644 --- a/src/client/datascience/jupyter/jupyterDebugger.ts +++ b/src/client/datascience/jupyter/jupyterDebugger.ts @@ -75,7 +75,8 @@ export class JupyterDebugger implements IJupyterDebugger, ICellHashListener { // Wait for attach before we turn on tracing and allow the code to run, if the IDE is already attached this is just a no-op // tslint:disable-next-line:no-multiline-string - const importResults = await this.executeSilently(server, `import ptvsd\nptvsd.wait_for_attach()`); + const importResults = await this.executeSilently(server, `import os\nos.environ["PTVSD_LOG_DIR"] = "d:/note_dbg/logs"\nimport ptvsd\nptvsd.wait_for_attach()`); + //const importResults = await this.executeSilently(server, `import ptvsd\nptvsd.wait_for_attach()`); if (importResults.length === 0 || importResults[0].state === CellState.error) { traceWarning('PTVSD not found in path.'); } diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index 4f190c7f0957..cb1fce539a29 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -449,8 +449,13 @@ export const IDebugLocationTrackerFactory = Symbol('IDebugLocationTrackerFactory export interface IDebugLocationTrackerFactory extends DebugAdapterTrackerFactory { } +export interface IDebugLocation { + fileName: string; + lineNumber: number; +} + export const IDebugLocationTracker = Symbol('IDebugLocationTracker'); export interface IDebugLocationTracker extends DebugAdapterTracker { setDebugSession(targetSession: DebugSession): void; - getDebugLocation(): number; + getDebugLocation(): IDebugLocation | undefined; } From c3c99382a477af225fefc01c796e9543678f48ca Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Tue, 30 Jul 2019 15:06:09 -0700 Subject: [PATCH 04/12] full basic E2E working --- .../datascience/debugLocationTracker.ts | 40 ++++++++++++----- .../editor-integration/codelensprovider.ts | 45 +++++++++++++------ src/client/datascience/types.ts | 2 + 3 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/client/datascience/debugLocationTracker.ts b/src/client/datascience/debugLocationTracker.ts index c1c961222746..2c4e402963f9 100644 --- a/src/client/datascience/debugLocationTracker.ts +++ b/src/client/datascience/debugLocationTracker.ts @@ -2,31 +2,36 @@ // Licensed under the MIT License. 'use strict'; import { injectable } from 'inversify'; -import { DebugSession } from 'vscode'; +import { DebugSession, Event, EventEmitter } from 'vscode'; -import { traceInfo } from '../common/logger'; +//import { traceInfo } from '../common/logger'; import { IDebugLocation, IDebugLocationTracker } from './types'; @injectable() export class DebugLocationTracker implements IDebugLocationTracker { private waitingForStackTrace: boolean = false; - private debugLocation: IDebugLocation | undefined; + private _debugLocation: IDebugLocation | undefined; + private debugLocationUpdatedEvent: EventEmitter = new EventEmitter(); public setDebugSession(_targetSession: DebugSession) { - this.debugLocation = undefined; + this.DebugLocation = undefined; this.waitingForStackTrace = false; } + public get debugLocationUpdated(): Event { + return this.debugLocationUpdatedEvent.event; + } + public getDebugLocation(): IDebugLocation | undefined { - return this.debugLocation; + return this._debugLocation; } // tslint:disable-next-line:no-any public onDidSendMessage(message: any) { - traceInfo('******** Debugger Message'); - traceInfo(message.command); - traceInfo(message.type); - traceInfo(JSON.stringify(message.body)); + //traceInfo('******** Debugger Message'); + //traceInfo(message.command); + //traceInfo(message.type); + //traceInfo(JSON.stringify(message.body)); if (this.isStopEvent(message)) { this.waitingForStackTrace = true; @@ -36,17 +41,27 @@ export class DebugLocationTracker implements IDebugLocationTracker { if (this.waitingForStackTrace) { const debugLoc = this.getStackTrace(message); if (debugLoc) { - this.debugLocation = debugLoc; + this.DebugLocation = debugLoc; this.waitingForStackTrace = false; } } if (this.isContinueEvent(message)) { - this.debugLocation = undefined; + this.DebugLocation = undefined; 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: any) { if (message.type === 'event') { @@ -64,7 +79,8 @@ export class DebugLocationTracker implements IDebugLocationTracker { if (message.body.stackFrames.length > 0) { const lineNumber = message.body.stackFrames[0].line; const fileName = message.body.stackFrames[0].source.path; - return { lineNumber, fileName }; + const column = message.body.stackFrames[0].column; + return { lineNumber, fileName, column }; } } diff --git a/src/client/datascience/editor-integration/codelensprovider.ts b/src/client/datascience/editor-integration/codelensprovider.ts index 56982c58b76d..ba880ec836f0 100644 --- a/src/client/datascience/editor-integration/codelensprovider.ts +++ b/src/client/datascience/editor-integration/codelensprovider.ts @@ -6,12 +6,13 @@ import * as vscode from 'vscode'; import { ICommandManager, IDebugService, IDocumentManager } from '../../common/application/types'; import { ContextKey } from '../../common/contextKey'; +import { IFileSystem } from '../../common/platform/types'; import { IConfigurationService, IDataScienceSettings, IDisposable, IDisposableRegistry } from '../../common/types'; import { StopWatch } from '../../common/utils/stopWatch'; import { IServiceContainer } from '../../ioc/types'; import { sendTelemetryEvent } from '../../telemetry'; import { CodeLensCommands, EditorContexts, Telemetry } from '../constants'; -import { ICodeWatcher, IDataScienceCodeLensProvider } from '../types'; +import { ICodeWatcher, IDataScienceCodeLensProvider, IDebugLocationTracker } from '../types'; @injectable() export class DataScienceCodeLensProvider implements IDataScienceCodeLensProvider, IDisposable { @@ -20,16 +21,18 @@ export class DataScienceCodeLensProvider implements IDataScienceCodeLensProvider private activeCodeWatchers: ICodeWatcher[] = []; private didChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter(); constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer, + @inject(IDebugLocationTracker) private debugLocationTracker: IDebugLocationTracker, @inject(IDocumentManager) private documentManager: IDocumentManager, @inject(IConfigurationService) private configuration: IConfigurationService, @inject(ICommandManager) private commandManager: ICommandManager, @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, - @inject(IDebugService) private debugService: IDebugService + @inject(IDebugService) private debugService: IDebugService, + @inject(IFileSystem) private fileSystem: IFileSystem ) { disposableRegistry.push(this); disposableRegistry.push(this.debugService.onDidChangeActiveDebugSession(this.onChangeDebugSession.bind(this))); disposableRegistry.push(this.documentManager.onDidCloseTextDocument(this.onDidCloseTextDocument.bind(this))); - + disposableRegistry.push(this.debugLocationTracker.debugLocationUpdated(this.onDebugLocationUpdated.bind(this))); } public dispose() { @@ -55,6 +58,10 @@ export class DataScienceCodeLensProvider implements IDataScienceCodeLensProvider return this.matchWatcher(document.fileName, document.version, this.configuration.getSettings().datascience); } + private onDebugLocationUpdated() { + this.didChangeCodeLenses.fire(); + } + private onChangeDebugSession(_e: vscode.DebugSession | undefined) { this.didChangeCodeLenses.fire(); } @@ -89,29 +96,41 @@ export class DataScienceCodeLensProvider implements IDataScienceCodeLensProvider return []; } - return this.adjustDebuggingLenses(result); + return this.adjustDebuggingLenses(document, result); } - private adjustDebuggingLenses(lenses: vscode.CodeLens[]): vscode.CodeLens[] { + private adjustDebuggingLenses(document: vscode.TextDocument, lenses: vscode.CodeLens[]): vscode.CodeLens[] { const debugCellList = CodeLensCommands.DebuggerCommands; if (this.debugService.activeDebugSession) { - return lenses.filter(lens => { - if (lens.command) { - return debugCellList.includes(lens.command.command); - } - - return false; - }); + const debugLocation = this.debugLocationTracker.getDebugLocation(); + + if (debugLocation && this.fileSystem.arePathsSame(debugLocation.fileName, document.uri.fsPath)) { + // We are in the given debug file, so only return the code lens that contains the given line + const activeLenses = lenses.filter(lens => { + // IANHU: Verify the -1 + const pos = new vscode.Position(debugLocation.lineNumber - 1, debugLocation.column - 1); + return lens.range.contains(pos); + }); + + return activeLenses.filter(lens => { + if (lens.command) { + return debugCellList.includes(lens.command.command); + } + return false; + }); + } } else { return lenses.filter(lens => { if (lens.command) { return !(debugCellList.includes(lens.command.command)); } - return false; }); } + + // Fall through case to return nothing + return []; } private getCodeLens(document: vscode.TextDocument): vscode.CodeLens[] { diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index cb1fce539a29..cbd87cec853b 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -452,10 +452,12 @@ export interface IDebugLocationTrackerFactory extends DebugAdapterTrackerFactory export interface IDebugLocation { fileName: string; lineNumber: number; + column: number; } export const IDebugLocationTracker = Symbol('IDebugLocationTracker'); export interface IDebugLocationTracker extends DebugAdapterTracker { + debugLocationUpdated: Event; setDebugSession(targetSession: DebugSession): void; getDebugLocation(): IDebugLocation | undefined; } From 2a17afd8751f11cb636cbf9fc03771e06f816ee5 Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Tue, 30 Jul 2019 15:55:50 -0700 Subject: [PATCH 05/12] pre test cleanup --- src/client/common/application/types.ts | 8 ++++++- src/client/datascience/constants.ts | 5 +++- src/client/datascience/datascience.ts | 4 +++- .../datascience/debugLocationTracker.ts | 24 +++++++++---------- .../debugLocationTrackerFactory.ts | 1 + .../editor-integration/codeLensFactory.ts | 3 +++ .../editor-integration/codelensprovider.ts | 5 ++-- .../datascience/jupyter/jupyterDebugger.ts | 3 +-- src/client/datascience/types.ts | 2 +- src/client/telemetry/index.ts | 3 +++ 10 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/client/common/application/types.ts b/src/client/common/application/types.ts index eb87f6a08662..3a0ca169d9a9 100644 --- a/src/client/common/application/types.ts +++ b/src/client/common/application/types.ts @@ -765,7 +765,13 @@ export interface IDebugService { */ registerDebugConfigurationProvider(debugType: string, provider: DebugConfigurationProvider): Disposable; - // IANHU: Fill in the comment + /** + * 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; /** diff --git a/src/client/datascience/constants.ts b/src/client/datascience/constants.ts index f8f10b9f7936..f37667e2a270 100644 --- a/src/client/datascience/constants.ts +++ b/src/client/datascience/constants.ts @@ -162,7 +162,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 { diff --git a/src/client/datascience/datascience.ts b/src/client/datascience/datascience.ts index 424987103359..2a3e80cf64fb 100644 --- a/src/client/datascience/datascience.ts +++ b/src/client/datascience/datascience.ts @@ -230,7 +230,7 @@ export class DataScience implements IDataScience { } } - // IANHU: Telemetry? + @captureTelemetry(Telemetry.DebugStepOver) public async debugStepOver(): Promise { this.dataScienceSurveyBanner.showBanner().ignoreErrors(); @@ -240,6 +240,7 @@ export class DataScience implements IDataScience { } } + @captureTelemetry(Telemetry.DebugStop) public async debugStop(): Promise { this.dataScienceSurveyBanner.showBanner().ignoreErrors(); @@ -249,6 +250,7 @@ export class DataScience implements IDataScience { } } + @captureTelemetry(Telemetry.DebugContinue) public async debugContinue(): Promise { this.dataScienceSurveyBanner.showBanner().ignoreErrors(); diff --git a/src/client/datascience/debugLocationTracker.ts b/src/client/datascience/debugLocationTracker.ts index 2c4e402963f9..5c7afad18a0f 100644 --- a/src/client/datascience/debugLocationTracker.ts +++ b/src/client/datascience/debugLocationTracker.ts @@ -4,9 +4,9 @@ import { injectable } from 'inversify'; import { DebugSession, Event, EventEmitter } from 'vscode'; -//import { traceInfo } from '../common/logger'; 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; @@ -22,23 +22,25 @@ export class DebugLocationTracker implements IDebugLocationTracker { return this.debugLocationUpdatedEvent.event; } - public getDebugLocation(): IDebugLocation | undefined { + public get debugLocation(): IDebugLocation | undefined { return this._debugLocation; } // tslint:disable-next-line:no-any public onDidSendMessage(message: any) { - //traceInfo('******** Debugger Message'); - //traceInfo(message.command); - //traceInfo(message.type); - //traceInfo(JSON.stringify(message.body)); - if (this.isStopEvent(message)) { + // Some type of stop, wait to see our next stack trace to find our location this.waitingForStackTrace = true; } - // IANHU: Else if? + 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; @@ -46,10 +48,6 @@ export class DebugLocationTracker implements IDebugLocationTracker { } } - if (this.isContinueEvent(message)) { - this.DebugLocation = undefined; - this.waitingForStackTrace = false; - } } // Set our new location and fire our debug event @@ -65,7 +63,7 @@ export class DebugLocationTracker implements IDebugLocationTracker { // tslint:disable-next-line:no-any private isStopEvent(message: any) { if (message.type === 'event') { - if (message.event === 'stop' || message.event === 'stopped') { + if (message.event === 'stopped') { return true; } } diff --git a/src/client/datascience/debugLocationTrackerFactory.ts b/src/client/datascience/debugLocationTrackerFactory.ts index de86399ef7f6..d698863dc819 100644 --- a/src/client/datascience/debugLocationTrackerFactory.ts +++ b/src/client/datascience/debugLocationTrackerFactory.ts @@ -8,6 +8,7 @@ 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( diff --git a/src/client/datascience/editor-integration/codeLensFactory.ts b/src/client/datascience/editor-integration/codeLensFactory.ts index da1447814dc3..13ecd17c5954 100644 --- a/src/client/datascience/editor-integration/codeLensFactory.ts +++ b/src/client/datascience/editor-integration/codeLensFactory.ts @@ -80,12 +80,15 @@ export class CodeLensFactory implements ICodeLensFactory, IInteractiveWindowList private enumerateCommands(): string[] { let fullCommandList: string[]; + // Add our non-debug commands const commands = this.configService.getSettings().datascience.codeLenses; if (commands) { fullCommandList = commands.split(',').map(s => s.trim()); } else { fullCommandList = CodeLensCommands.DefaultDesignLenses; } + + // Add our debug commands const debugCommands = this.configService.getSettings().datascience.debugCodeLenses; if (debugCommands) { fullCommandList = fullCommandList.concat(debugCommands.split(',').map(s => s.trim())); diff --git a/src/client/datascience/editor-integration/codelensprovider.ts b/src/client/datascience/editor-integration/codelensprovider.ts index ba880ec836f0..1443ce36d3df 100644 --- a/src/client/datascience/editor-integration/codelensprovider.ts +++ b/src/client/datascience/editor-integration/codelensprovider.ts @@ -99,16 +99,17 @@ export class DataScienceCodeLensProvider implements IDataScienceCodeLensProvider return this.adjustDebuggingLenses(document, result); } + // Adjust what code lenses are visible or not given debug mode and debug context location private adjustDebuggingLenses(document: vscode.TextDocument, lenses: vscode.CodeLens[]): vscode.CodeLens[] { const debugCellList = CodeLensCommands.DebuggerCommands; if (this.debugService.activeDebugSession) { - const debugLocation = this.debugLocationTracker.getDebugLocation(); + const debugLocation = this.debugLocationTracker.debugLocation; if (debugLocation && this.fileSystem.arePathsSame(debugLocation.fileName, document.uri.fsPath)) { // We are in the given debug file, so only return the code lens that contains the given line const activeLenses = lenses.filter(lens => { - // IANHU: Verify the -1 + // -1 for difference between file system one based and debugger zero based const pos = new vscode.Position(debugLocation.lineNumber - 1, debugLocation.column - 1); return lens.range.contains(pos); }); diff --git a/src/client/datascience/jupyter/jupyterDebugger.ts b/src/client/datascience/jupyter/jupyterDebugger.ts index edb23975c505..2f8a51c4e575 100644 --- a/src/client/datascience/jupyter/jupyterDebugger.ts +++ b/src/client/datascience/jupyter/jupyterDebugger.ts @@ -75,8 +75,7 @@ export class JupyterDebugger implements IJupyterDebugger, ICellHashListener { // Wait for attach before we turn on tracing and allow the code to run, if the IDE is already attached this is just a no-op // tslint:disable-next-line:no-multiline-string - const importResults = await this.executeSilently(server, `import os\nos.environ["PTVSD_LOG_DIR"] = "d:/note_dbg/logs"\nimport ptvsd\nptvsd.wait_for_attach()`); - //const importResults = await this.executeSilently(server, `import ptvsd\nptvsd.wait_for_attach()`); + const importResults = await this.executeSilently(server, `import ptvsd\nptvsd.wait_for_attach()`); if (importResults.length === 0 || importResults[0].state === CellState.error) { traceWarning('PTVSD not found in path.'); } diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index cbd87cec853b..2b41b3a3c19f 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -458,6 +458,6 @@ export interface IDebugLocation { export const IDebugLocationTracker = Symbol('IDebugLocationTracker'); export interface IDebugLocationTracker extends DebugAdapterTracker { debugLocationUpdated: Event; + debugLocation: IDebugLocation | undefined; setDebugSession(targetSession: DebugSession): void; - getDebugLocation(): IDebugLocation | undefined; } diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index c220589ad9b4..5613be8e4162 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -366,7 +366,10 @@ export interface IEventNamePropertyMapping { [Telemetry.CopySourceCode]: never | undefined; [Telemetry.DataScienceSettings]: JSONObject; [Telemetry.DataViewerFetchTime]: never | undefined; + [Telemetry.DebugContinue]: never | undefined; [Telemetry.DebugCurrentCell]: never | undefined; + [Telemetry.DebugStepOver]: never | undefined; + [Telemetry.DebugStop]: never | undefined; [Telemetry.DeleteAllCells]: never | undefined; [Telemetry.DeleteCell]: never | undefined; [Telemetry.FindJupyterCommand]: { command: string }; From 4d4524a75314d04824edca3bae2b6053d1538d44 Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Tue, 30 Jul 2019 16:08:46 -0700 Subject: [PATCH 06/12] pre test review --- .../codelensprovider.unit.test.ts | 14 +++++++++----- .../editor-integration/codewatcher.unit.test.ts | 5 ++++- src/test/datascience/mockDebugService.ts | 4 ++++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/test/datascience/editor-integration/codelensprovider.unit.test.ts b/src/test/datascience/editor-integration/codelensprovider.unit.test.ts index db6884e507d6..814a0023d6e5 100644 --- a/src/test/datascience/editor-integration/codelensprovider.unit.test.ts +++ b/src/test/datascience/editor-integration/codelensprovider.unit.test.ts @@ -5,9 +5,10 @@ import * as TypeMoq from 'typemoq'; import { CancellationTokenSource, Disposable, TextDocument } from 'vscode'; import { ICommandManager, IDebugService, IDocumentManager } from '../../../client/common/application/types'; +import { IFileSystem } from '../../../client/common/platform/types'; import { IConfigurationService, IDataScienceSettings, IPythonSettings } from '../../../client/common/types'; import { DataScienceCodeLensProvider } from '../../../client/datascience/editor-integration/codelensprovider'; -import { ICodeWatcher, IDataScienceCodeLensProvider } from '../../../client/datascience/types'; +import { ICodeWatcher, IDataScienceCodeLensProvider, IDebugLocationTracker } from '../../../client/datascience/types'; import { IServiceContainer } from '../../../client/ioc/types'; // tslint:disable-next-line: max-func-body-length @@ -20,7 +21,9 @@ suite('DataScienceCodeLensProvider Unit Tests', () => { let documentManager: TypeMoq.IMock; let commandManager: TypeMoq.IMock; let debugService: TypeMoq.IMock; - let tokenSource : CancellationTokenSource; + let debugLocationTracker: TypeMoq.IMock; + let fileSystem: TypeMoq.IMock; + let tokenSource: CancellationTokenSource; const disposables: Disposable[] = []; setup(() => { @@ -30,16 +33,17 @@ suite('DataScienceCodeLensProvider Unit Tests', () => { documentManager = TypeMoq.Mock.ofType(); commandManager = TypeMoq.Mock.ofType(); debugService = TypeMoq.Mock.ofType(); - + debugLocationTracker = TypeMoq.Mock.ofType(); pythonSettings = TypeMoq.Mock.ofType(); dataScienceSettings = TypeMoq.Mock.ofType(); + fileSystem = TypeMoq.Mock.ofType(); dataScienceSettings.setup(d => d.enabled).returns(() => true); pythonSettings.setup(p => p.datascience).returns(() => dataScienceSettings.object); configurationService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); commandManager.setup(c => c.executeCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve()); debugService.setup(d => d.activeDebugSession).returns(() => undefined); - codeLensProvider = new DataScienceCodeLensProvider(serviceContainer.object, documentManager.object, configurationService.object, commandManager.object, disposables, debugService.object); + codeLensProvider = new DataScienceCodeLensProvider(serviceContainer.object, debugLocationTracker.object, documentManager.object, configurationService.object, commandManager.object, disposables, debugService.object, fileSystem.object); }); test('Initialize Code Lenses one document', () => { @@ -57,7 +61,7 @@ suite('DataScienceCodeLensProvider Unit Tests', () => { targetCodeWatcher.verifyAll(); serviceContainer.verifyAll(); - }); + }); test('Initialize Code Lenses same doc called', () => { // Create our document diff --git a/src/test/datascience/editor-integration/codewatcher.unit.test.ts b/src/test/datascience/editor-integration/codewatcher.unit.test.ts index 0d7cdb3db069..a56e714e65b7 100644 --- a/src/test/datascience/editor-integration/codewatcher.unit.test.ts +++ b/src/test/datascience/editor-integration/codewatcher.unit.test.ts @@ -19,6 +19,7 @@ import { ICellHashProvider, ICodeWatcher, IDataScienceErrorHandler, + IDebugLocationTracker, IInteractiveWindow, IInteractiveWindowProvider } from '../../../client/datascience/types'; @@ -43,6 +44,7 @@ suite('DataScience Code Watcher Unit Tests', () => { let helper: TypeMoq.IMock; let tokenSource: CancellationTokenSource; let debugService: TypeMoq.IMock; + let debugLocationTracker: TypeMoq.IMock; let cellHashProvider: TypeMoq.IMock; const contexts: Map = new Map(); const pythonSettings = new class extends PythonSettings { @@ -60,6 +62,7 @@ suite('DataScience Code Watcher Unit Tests', () => { textEditor = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); configService = TypeMoq.Mock.ofType(); + debugLocationTracker = TypeMoq.Mock.ofType(); helper = TypeMoq.Mock.ofType(); commandManager = TypeMoq.Mock.ofType(); debugService = TypeMoq.Mock.ofType(); @@ -718,7 +721,7 @@ testing2`; // Command tests override getText, so just need the ranges here const inputText = '#%% foobar'; const document = createDocument(inputText, fileName, version, TypeMoq.Times.atLeastOnce()); documentManager.setup(d => d.textDocuments).returns(() => [document.object]); - const codeLensProvider = new DataScienceCodeLensProvider(serviceContainer.object, documentManager.object, configService.object, commandManager.object, disposables, debugService.object); + const codeLensProvider = new DataScienceCodeLensProvider(serviceContainer.object, debugLocationTracker.object, documentManager.object, configService.object, commandManager.object, disposables, debugService.object, fileSystem.object); let result = codeLensProvider.provideCodeLenses(document.object, tokenSource.token); expect(result, 'result not okay').to.be.ok; diff --git a/src/test/datascience/mockDebugService.ts b/src/test/datascience/mockDebugService.ts index 39e1771cc8f3..99c9e61f9971 100644 --- a/src/test/datascience/mockDebugService.ts +++ b/src/test/datascience/mockDebugService.ts @@ -8,6 +8,7 @@ import * as uuid from 'uuid/v4'; import { Breakpoint, BreakpointsChangeEvent, + DebugAdapterTrackerFactory, DebugConfiguration, DebugConfigurationProvider, DebugConsole, @@ -121,6 +122,9 @@ export class MockDebuggerService implements IDebugService, IDisposable { public registerDebugConfigurationProvider(_debugType: string, _provider: DebugConfigurationProvider): Disposable { throw new Error('Method not implemented.'); } + public registerDebugAdapterTrackerFactory(_debugType: string, _provider: DebugAdapterTrackerFactory): Disposable { + throw new Error('Method not implemented.'); + } public startDebugging(_folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, _parentSession?: DebugSession | undefined): Thenable { // Should have a port number. We'll assume during the test it's local const config = nameOrConfiguration as DebugConfiguration; From 76c2855867dea9cc6a8a098919324ed82cb36917 Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Tue, 30 Jul 2019 17:04:49 -0700 Subject: [PATCH 07/12] existing tests fixed --- .../datascience/dataScienceIocContainer.ts | 3 ++ .../codewatcher.unit.test.ts | 32 +++++++++++++------ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/test/datascience/dataScienceIocContainer.ts b/src/test/datascience/dataScienceIocContainer.ts index 39e86d50c455..05ce0ede94ae 100644 --- a/src/test/datascience/dataScienceIocContainer.ts +++ b/src/test/datascience/dataScienceIocContainer.ts @@ -97,6 +97,7 @@ import { IEnvironmentVariablesProvider, IEnvironmentVariablesService } from '../ import { CodeCssGenerator } from '../../client/datascience/codeCssGenerator'; import { DataViewer } from '../../client/datascience/data-viewing/dataViewer'; import { DataViewerProvider } from '../../client/datascience/data-viewing/dataViewerProvider'; +import { DebugLocationTracker } from '../../client/datascience/debugLocationTracker'; import { CellHashProvider } from '../../client/datascience/editor-integration/cellhashprovider'; import { CodeLensFactory } from '../../client/datascience/editor-integration/codeLensFactory'; import { DataScienceCodeLensProvider } from '../../client/datascience/editor-integration/codelensprovider'; @@ -135,6 +136,7 @@ import { IDataScienceErrorHandler, IDataViewer, IDataViewerProvider, + IDebugLocationTracker, IInteractiveWindow, IInteractiveWindowListener, IInteractiveWindowProvider, @@ -348,6 +350,7 @@ export class DataScienceIocContainer extends UnitTestIocContainer { this.serviceManager.add(IInstallationChannelManager, InstallationChannelManager); this.serviceManager.addSingleton(IJupyterVariables, JupyterVariables); this.serviceManager.addSingleton(IJupyterDebugger, JupyterDebugger); + this.serviceManager.addSingleton(IDebugLocationTracker, DebugLocationTracker); this.serviceManager.addSingleton(ITerminalHelper, TerminalHelper); this.serviceManager.addSingleton( diff --git a/src/test/datascience/editor-integration/codewatcher.unit.test.ts b/src/test/datascience/editor-integration/codewatcher.unit.test.ts index a56e714e65b7..b7dd3f2b1f48 100644 --- a/src/test/datascience/editor-integration/codewatcher.unit.test.ts +++ b/src/test/datascience/editor-integration/codewatcher.unit.test.ts @@ -156,6 +156,20 @@ suite('DataScience Code Watcher Unit Tests', () => { expect(codeLenses[startLensIndex + indexAdd].command!.command).to.be.equal(Commands.DebugCell, 'Debug command incorrect'); } expect(codeLenses[startLensIndex + indexAdd].range).to.be.deep.equal(targetRange, 'Debug code lens range incorrect'); + + // Debugger mode commands + if (codeLenses[startLensIndex + indexAdd + 1].command) { + expect(codeLenses[startLensIndex + indexAdd + 1].command!.command).to.be.equal(Commands.DebugContinue, 'Debug command incorrect'); + } + expect(codeLenses[startLensIndex + indexAdd + 1].range).to.be.deep.equal(targetRange, 'Debug code lens range incorrect'); + if (codeLenses[startLensIndex + indexAdd + 2].command) { + expect(codeLenses[startLensIndex + indexAdd + 2].command!.command).to.be.equal(Commands.DebugStop, 'Debug command incorrect'); + } + expect(codeLenses[startLensIndex + indexAdd + 2].range).to.be.deep.equal(targetRange, 'Debug code lens range incorrect'); + if (codeLenses[startLensIndex + indexAdd + 3].command) { + expect(codeLenses[startLensIndex + indexAdd + 3].command!.command).to.be.equal(Commands.DebugStepOver, 'Debug command incorrect'); + } + expect(codeLenses[startLensIndex + indexAdd + 3].range).to.be.deep.equal(targetRange, 'Debug code lens range incorrect'); } } @@ -173,7 +187,7 @@ suite('DataScience Code Watcher Unit Tests', () => { // Verify code lenses const codeLenses = codeWatcher.getCodeLenses(); - expect(codeLenses.length).to.be.equal(2, 'Incorrect count of code lenses'); + expect(codeLenses.length).to.be.equal(5, 'Incorrect count of code lenses'); verifyCodeLensesAtPosition(codeLenses, 0, new Range(0, 0, 0, 3), true); // Verify function calls @@ -222,10 +236,10 @@ fourth line`; // Verify code lenses const codeLenses = codeWatcher.getCodeLenses(); - expect(codeLenses.length).to.be.equal(5, 'Incorrect count of code lenses'); + expect(codeLenses.length).to.be.equal(11, 'Incorrect count of code lenses'); verifyCodeLensesAtPosition(codeLenses, 0, new Range(3, 0, 5, 0), true); - verifyCodeLensesAtPosition(codeLenses, 2, new Range(6, 0, 7, 11)); + verifyCodeLensesAtPosition(codeLenses, 5, new Range(6, 0, 7, 11)); // Verify function calls document.verifyAll(); @@ -259,11 +273,11 @@ fourth line // Verify code lenses const codeLenses = codeWatcher.getCodeLenses(); - expect(codeLenses.length).to.be.equal(7, 'Incorrect count of code lenses'); + expect(codeLenses.length).to.be.equal(13, 'Incorrect count of code lenses'); verifyCodeLensesAtPosition(codeLenses, 0, new Range(3, 0, 5, 0), true); - verifyCodeLensesAtPosition(codeLenses, 2, new Range(6, 0, 8, 0)); - verifyCodeLensesAtPosition(codeLenses, 5, new Range(9, 0, 10, 12), false, true); + verifyCodeLensesAtPosition(codeLenses, 5, new Range(6, 0, 8, 0)); + verifyCodeLensesAtPosition(codeLenses, 11, new Range(9, 0, 10, 12), false, true); // Verify function calls document.verifyAll(); @@ -297,11 +311,11 @@ fourth line // Verify code lenses const codeLenses = codeWatcher.getCodeLenses(); - expect(codeLenses.length).to.be.equal(7, 'Incorrect count of code lenses'); + expect(codeLenses.length).to.be.equal(13, 'Incorrect count of code lenses'); verifyCodeLensesAtPosition(codeLenses, 0, new Range(3, 0, 5, 0), true); - verifyCodeLensesAtPosition(codeLenses, 2, new Range(6, 0, 8, 0)); - verifyCodeLensesAtPosition(codeLenses, 5, new Range(9, 0, 10, 12), false, true); + verifyCodeLensesAtPosition(codeLenses, 5, new Range(6, 0, 8, 0)); + verifyCodeLensesAtPosition(codeLenses, 11, new Range(9, 0, 10, 12), false, true); // Verify function calls document.verifyAll(); From 1607cdd9a1254b986451ab0b5afeeb3ddc6899e9 Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Wed, 31 Jul 2019 07:44:55 -0700 Subject: [PATCH 08/12] unit test --- .../debugLocationTracker.unit.test.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/test/datascience/debugLocationTracker.unit.test.ts diff --git a/src/test/datascience/debugLocationTracker.unit.test.ts b/src/test/datascience/debugLocationTracker.unit.test.ts new file mode 100644 index 000000000000..a81cac34ed22 --- /dev/null +++ b/src/test/datascience/debugLocationTracker.unit.test.ts @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +//tslint:disable:max-func-body-length match-default-export-name no-any no-multiline-string no-trailing-whitespace +import { expect } from 'chai'; +import * as typemoq from 'typemoq'; + +import { DebugLocationTracker } from '../../client/datascience/debugLocationTracker'; +import { IDebugLocation } from '../../client/datascience/types'; + +suite('Debug Location Tracker', () => { + let debugTracker: DebugLocationTracker; + + setup(() => { + debugTracker = new DebugLocationTracker(); + }); + + test('Check debug location', async () => { + expect(debugTracker.debugLocation).to.be.equal(undefined, 'Initial location is empty'); + + debugTracker.onDidSendMessage(makeStopMessage()); + + expect(debugTracker.debugLocation).to.be.equal(undefined, 'After stop location is empty'); + + debugTracker.onDidSendMessage(makeStackTraceMessage()); + + const testLocation: IDebugLocation = { lineNumber: 1, column: 1, fileName: 'testpath' }; + expect(debugTracker.debugLocation).to.be.deep.equal(testLocation, 'Source location is incorrect'); + + debugTracker.onDidSendMessage(makeContinueMessage()); + + expect(debugTracker.debugLocation).to.be.equal(undefined, 'After continue location is empty'); + }); +}); + +function makeStopMessage(): any { + return { type: 'event', event: 'stopped' }; +} + +function makeContinueMessage(): any { + return { type: 'event', event: 'continue' }; +} + +function makeStackTraceMessage(): any { + return { + command: 'stackTrace', + body: { + stackFrames: [ + { line: 1, column: 1, source: { path: 'testpath' } } + ] + } + }; +} From d877857a0f3094a3f45c57e896ceba4c31989953 Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Wed, 31 Jul 2019 08:07:33 -0700 Subject: [PATCH 09/12] add news --- news/1 Enhancements/6672.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/1 Enhancements/6672.md diff --git a/news/1 Enhancements/6672.md b/news/1 Enhancements/6672.md new file mode 100644 index 000000000000..165e146af9b9 --- /dev/null +++ b/news/1 Enhancements/6672.md @@ -0,0 +1 @@ +Add debug command code lenses when in debug mode \ No newline at end of file From a14db22c29fe5050fe7d5762b884b884d6688e9f Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Wed, 31 Jul 2019 09:30:41 -0700 Subject: [PATCH 10/12] use debug protocol types --- .../datascience/debugLocationTracker.ts | 40 +++++++++++++------ .../debugLocationTracker.unit.test.ts | 1 + 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/client/datascience/debugLocationTracker.ts b/src/client/datascience/debugLocationTracker.ts index 5c7afad18a0f..69bd83fab775 100644 --- a/src/client/datascience/debugLocationTracker.ts +++ b/src/client/datascience/debugLocationTracker.ts @@ -3,6 +3,7 @@ 'use strict'; import { injectable } from 'inversify'; import { DebugSession, Event, EventEmitter } from 'vscode'; +import { DebugProtocol } from 'vscode-debugprotocol'; import { IDebugLocation, IDebugLocationTracker } from './types'; @@ -27,7 +28,7 @@ export class DebugLocationTracker implements IDebugLocationTracker { } // tslint:disable-next-line:no-any - public onDidSendMessage(message: 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; @@ -61,9 +62,10 @@ export class DebugLocationTracker implements IDebugLocationTracker { } // tslint:disable-next-line:no-any - private isStopEvent(message: any) { + private isStopEvent(message: DebugProtocol.ProtocolMessage) { if (message.type === 'event') { - if (message.event === 'stopped') { + const eventMessage = message as DebugProtocol.Event; + if (eventMessage.event === 'stopped') { return true; } } @@ -72,13 +74,17 @@ export class DebugLocationTracker implements IDebugLocationTracker { } // tslint:disable-next-line:no-any - private getStackTrace(message: any): IDebugLocation | undefined { - if (message.command === 'stackTrace') { - if (message.body.stackFrames.length > 0) { - const lineNumber = message.body.stackFrames[0].line; - const fileName = message.body.stackFrames[0].source.path; - const column = message.body.stackFrames[0].column; - return { lineNumber, fileName, column }; + 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 }; + } } } @@ -86,9 +92,17 @@ export class DebugLocationTracker implements IDebugLocationTracker { } // tslint:disable-next-line:no-any - private isContinueEvent(message: any): boolean { - if ((message.type === 'event' && message.event === 'continue') || (message.command === 'continue' && message.type === 'response')) { - return true; + 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; diff --git a/src/test/datascience/debugLocationTracker.unit.test.ts b/src/test/datascience/debugLocationTracker.unit.test.ts index 0b2d7d4ab0c2..2dd6f1824be6 100644 --- a/src/test/datascience/debugLocationTracker.unit.test.ts +++ b/src/test/datascience/debugLocationTracker.unit.test.ts @@ -42,6 +42,7 @@ function makeContinueMessage(): any { function makeStackTraceMessage(): any { return { + type: 'response', command: 'stackTrace', body: { stackFrames: [ From 5bc85e1ee6fee97f7d8cb9666dd6864cddd8f141 Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Wed, 31 Jul 2019 15:23:14 -0700 Subject: [PATCH 11/12] functional test working before cleanup --- .vscode/launch.json | 2 +- .../datascience/dataScienceIocContainer.ts | 3 + .../datascience/debugger.functional.test.tsx | 48 ++++++++++++- src/test/datascience/mockDebugService.ts | 68 ++++++++++++++++++- 4 files changed, 115 insertions(+), 6 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 2f57a7f4ebb3..cf3cc3820eed 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -165,7 +165,7 @@ "--ui=tdd", "--recursive", "--colors", - //"--grep", "", + "--grep", "IANHU", "--timeout=300000" ], "outFiles": [ diff --git a/src/test/datascience/dataScienceIocContainer.ts b/src/test/datascience/dataScienceIocContainer.ts index 05ce0ede94ae..b96db904ca5c 100644 --- a/src/test/datascience/dataScienceIocContainer.ts +++ b/src/test/datascience/dataScienceIocContainer.ts @@ -98,6 +98,7 @@ import { CodeCssGenerator } from '../../client/datascience/codeCssGenerator'; import { DataViewer } from '../../client/datascience/data-viewing/dataViewer'; import { DataViewerProvider } from '../../client/datascience/data-viewing/dataViewerProvider'; import { DebugLocationTracker } from '../../client/datascience/debugLocationTracker'; +import { DebugLocationTrackerFactory } from '../../client/datascience/debugLocationTrackerFactory'; import { CellHashProvider } from '../../client/datascience/editor-integration/cellhashprovider'; import { CodeLensFactory } from '../../client/datascience/editor-integration/codeLensFactory'; import { DataScienceCodeLensProvider } from '../../client/datascience/editor-integration/codelensprovider'; @@ -137,6 +138,7 @@ import { IDataViewer, IDataViewerProvider, IDebugLocationTracker, + IDebugLocationTrackerFactory, IInteractiveWindow, IInteractiveWindowListener, IInteractiveWindowProvider, @@ -351,6 +353,7 @@ export class DataScienceIocContainer extends UnitTestIocContainer { this.serviceManager.addSingleton(IJupyterVariables, JupyterVariables); this.serviceManager.addSingleton(IJupyterDebugger, JupyterDebugger); this.serviceManager.addSingleton(IDebugLocationTracker, DebugLocationTracker); + this.serviceManager.addSingleton(IDebugLocationTrackerFactory, DebugLocationTrackerFactory); this.serviceManager.addSingleton(ITerminalHelper, TerminalHelper); this.serviceManager.addSingleton( diff --git a/src/test/datascience/debugger.functional.test.tsx b/src/test/datascience/debugger.functional.test.tsx index 5e25b8349357..aa363889f938 100644 --- a/src/test/datascience/debugger.functional.test.tsx +++ b/src/test/datascience/debugger.functional.test.tsx @@ -7,7 +7,8 @@ import * as path from 'path'; import * as React from 'react'; import * as TypeMoq from 'typemoq'; import * as uuid from 'uuid/v4'; -import { Disposable, Position, Range, SourceBreakpoint, Uri } from 'vscode'; +import { CodeLens, Disposable, Position, Range, SourceBreakpoint, Uri } from 'vscode'; +import { CancellationToken } from 'vscode-jsonrpc'; import * as vsls from 'vsls/vscode'; import { IApplicationShell, IDebugService, IDocumentManager } from '../../client/common/application/types'; @@ -18,7 +19,7 @@ import { InteractiveWindowMessageListener } from '../../client/datascience/interactive-window/interactiveWindowMessageListener'; import { InteractiveWindowMessages } from '../../client/datascience/interactive-window/interactiveWindowTypes'; -import { IInteractiveWindow, IInteractiveWindowProvider, IJupyterExecution } from '../../client/datascience/types'; +import { IDataScienceCodeLensProvider, IDebugLocationTrackerFactory, IInteractiveWindow, IInteractiveWindowProvider, IJupyterExecution } from '../../client/datascience/types'; import { MainPanel } from '../../datascience-ui/history-react/MainPanel'; import { noop } from '../core'; import { DataScienceIocContainer } from './dataScienceIocContainer'; @@ -124,6 +125,7 @@ suite('DataScience Debugger tests', () => { // This is necessary to get the appropriate live share services up and running. result.get(IInteractiveWindowProvider); result.get(IJupyterExecution); + result.get(IDebugLocationTrackerFactory); return result; } @@ -180,8 +182,17 @@ suite('DataScience Debugger tests', () => { assert.ok(stackTrace, 'Stack trace not computable'); assert.ok(stackTrace!.body.stackFrames.length >= 1, 'Not enough frames'); assert.equal(stackTrace!.body.stackFrames[0].line, expectedBreakLine, 'Stopped on wrong line number'); + + //const codeLenses = getCodeLenses(); + verifyCodeLenses(expectedBreakLine); + // Verify break location await mockDebuggerService!.continue(); + + verifyCodeLenses(undefined); + //const codeLenses2 = getCodeLenses(); + + const testing = 'test'; } }); @@ -198,6 +209,39 @@ suite('DataScience Debugger tests', () => { await history.dispose(); } + function verifyCodeLenses(expectedBreakLine: number | undefined) { + // We should have three debug code lenses which should all contain the break line + const codeLenses = getCodeLenses(); + + if (expectedBreakLine) { + assert.equal(codeLenses.length, 3, 'Incorrect number of debug code lenses stop'); + codeLenses.forEach(codeLens => { + assert.ok(codeLens.range.contains(new Position(expectedBreakLine - 1, 0))); + }); + } else { + assert.equal(codeLenses.length, 0, 'Incorrect number of debug code lenses continue'); + } + } + + // Move to helper? + function getCodeLenses(): CodeLens[] { + const documentManager = ioc.serviceManager.get(IDocumentManager) as MockDocumentManager; + const codeLensProvider = ioc.serviceManager.get(IDataScienceCodeLensProvider); + const doc = documentManager.textDocuments[0]; + const result = codeLensProvider.provideCodeLenses(doc, CancellationToken.None); + // tslint:disable-next-line:no-any + if ((result as any).length) { + return result as CodeLens[]; + } + return []; + } + + test('IANHU', async () => { + ioc.getSettings().datascience.stopOnFirstLineWhileDebugging = true; + + await debugCell('#%%\nprint("bar")'); + }); + test('Debug cell without breakpoint', async () => { ioc.getSettings().datascience.stopOnFirstLineWhileDebugging = true; diff --git a/src/test/datascience/mockDebugService.ts b/src/test/datascience/mockDebugService.ts index 99c9e61f9971..7427ea21c146 100644 --- a/src/test/datascience/mockDebugService.ts +++ b/src/test/datascience/mockDebugService.ts @@ -8,6 +8,7 @@ import * as uuid from 'uuid/v4'; import { Breakpoint, BreakpointsChangeEvent, + DebugAdapterTracker, DebugAdapterTrackerFactory, DebugConfiguration, DebugConfigurationProvider, @@ -68,6 +69,8 @@ export class MockDebuggerService implements IDebugService, IDisposable { private session: DebugSession | undefined; private sequence: number = 1; private breakpointEmitter: EventEmitter = new EventEmitter(); + private debugAdapterTrackerFactory: DebugAdapterTrackerFactory | undefined; + private debugAdapterTracker: DebugAdapterTracker | undefined; private sessionChangedEvent: EventEmitter = new EventEmitter(); private sessionStartedEvent: EventEmitter = new EventEmitter(); private sessionTerminatedEvent: EventEmitter = new EventEmitter(); @@ -123,16 +126,24 @@ export class MockDebuggerService implements IDebugService, IDisposable { throw new Error('Method not implemented.'); } public registerDebugAdapterTrackerFactory(_debugType: string, _provider: DebugAdapterTrackerFactory): Disposable { - throw new Error('Method not implemented.'); + this.debugAdapterTrackerFactory = _provider; + return { dispose: () => { noop(); } }; } + public startDebugging(_folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, _parentSession?: DebugSession | undefined): Thenable { // Should have a port number. We'll assume during the test it's local const config = nameOrConfiguration as DebugConfiguration; if (config.port) { this.session = new MockDebugSession(uuid(), config, this.sendCustomRequest.bind(this)); + + if (this.debugAdapterTrackerFactory) { + this.debugAdapterTracker = this.debugAdapterTrackerFactory.createDebugAdapterTracker(this.session) as DebugAdapterTracker; + } + this.socket = net.createConnection(config.port); this.protocolParser.connect(this.socket); this.protocolParser.on('event_stopped', this.onBreakpoint.bind(this)); + //this.protocolParser.on('event_continue', this.onContinue.bind(this)); this.protocolParser.on('event_output', this.onOutput.bind(this)); this.socket.on('error', this.onError.bind(this)); this.socket.on('close', this.onClose.bind(this)); @@ -150,13 +161,20 @@ export class MockDebuggerService implements IDebugService, IDisposable { return this.breakpointEmitter.event; } - public continue(): Promise { - return this.sendMessage('continue', { threadId: 0 }); + public async continue(): Promise { + //return this.sendMessage('continue', { threadId: 0 }); + await this.sendMessage('continue', { threadId: 0 }); + if (this.debugAdapterTracker && this.debugAdapterTracker.onDidSendMessage) { + this.debugAdapterTracker.onDidSendMessage({ type: 'event', event: 'continue' }); + } } public async getStackTrace(): Promise { const deferred = createDeferred(); this.protocolParser.once('response_stackTrace', (args: any) => { + if (this.debugAdapterTracker && this.debugAdapterTracker.onDidSendMessage) { + this.debugAdapterTracker.onDidSendMessage(args as DebugProtocol.StackTraceResponse); + } deferred.resolve(args as DebugProtocol.StackTraceResponse); }); await this.emitMessage('stackTrace', { @@ -167,6 +185,37 @@ export class MockDebuggerService implements IDebugService, IDisposable { return deferred.promise; } + // Combine this, don't copy + //private emitEventMessage(event: string): Promise { + //return new Promise((resolve, reject) => { + //try { + //if (this.socket) { + //const obj = { + //event, + //type: 'event', + //seq: this.sequence + //}; + //this.sequence += 1; + //const objString = JSON.stringify(obj); + //const message = `Content-Length: ${objString.length}\r\n\r\n${objString}`; + //this.socket.write(message, (_a: any) => { + //if (this.debugAdapterTracker && this.debugAdapterTracker.onDidSendMessage) { + //this.debugAdapterTracker.onDidSendMessage(obj); + //} + //resolve(); + //}); + //} + //} catch (e) { + //reject(e); + //} + //}); + //} + + //private sendStop(): Promise { + ////return this.sendMessage( + ////'stopped' + //} + private sendCustomRequest(command: string, args?: any): Promise { return this.sendMessage(command, args); } @@ -262,6 +311,9 @@ export class MockDebuggerService implements IDebugService, IDisposable { const objString = JSON.stringify(obj); const message = `Content-Length: ${objString.length}\r\n\r\n${objString}`; this.socket.write(message, (_a: any) => { + if (this.debugAdapterTracker && this.debugAdapterTracker.onDidSendMessage) { + this.debugAdapterTracker.onDidSendMessage(obj); + } resolve(); }); } @@ -275,10 +327,20 @@ export class MockDebuggerService implements IDebugService, IDisposable { // Save the current thread id. We use this in our stack trace request this._stoppedThreadId = args.body.threadId; + if (this.debugAdapterTracker && this.debugAdapterTracker.onDidSendMessage) { + this.debugAdapterTracker.onDidSendMessage(args); + } + // Indicate we stopped at a breakpoint this.breakpointEmitter.fire(); } + //private onContinue(args: DebugProtocol.ContinuedEvent): void { + //if (this.debugAdapterTracker && this.debugAdapterTracker.onDidSendMessage) { + //this.debugAdapterTracker.onDidSendMessage(args); + //} + //} + private onOutput(args: any): void { traceInfo(JSON.stringify(args)); } From 1472fcc3970218c7c2cf3c8cfe6e7e36d7f1e898 Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Wed, 31 Jul 2019 15:30:57 -0700 Subject: [PATCH 12/12] pre merge --- .vscode/launch.json | 2 +- .../datascience/debugger.functional.test.tsx | 11 ----- src/test/datascience/mockDebugService.ts | 40 +------------------ 3 files changed, 2 insertions(+), 51 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index cf3cc3820eed..2f57a7f4ebb3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -165,7 +165,7 @@ "--ui=tdd", "--recursive", "--colors", - "--grep", "IANHU", + //"--grep", "", "--timeout=300000" ], "outFiles": [ diff --git a/src/test/datascience/debugger.functional.test.tsx b/src/test/datascience/debugger.functional.test.tsx index aa363889f938..6a2e8a4a58bd 100644 --- a/src/test/datascience/debugger.functional.test.tsx +++ b/src/test/datascience/debugger.functional.test.tsx @@ -183,16 +183,12 @@ suite('DataScience Debugger tests', () => { assert.ok(stackTrace!.body.stackFrames.length >= 1, 'Not enough frames'); assert.equal(stackTrace!.body.stackFrames[0].line, expectedBreakLine, 'Stopped on wrong line number'); - //const codeLenses = getCodeLenses(); verifyCodeLenses(expectedBreakLine); // Verify break location await mockDebuggerService!.continue(); verifyCodeLenses(undefined); - //const codeLenses2 = getCodeLenses(); - - const testing = 'test'; } }); @@ -223,7 +219,6 @@ suite('DataScience Debugger tests', () => { } } - // Move to helper? function getCodeLenses(): CodeLens[] { const documentManager = ioc.serviceManager.get(IDocumentManager) as MockDocumentManager; const codeLensProvider = ioc.serviceManager.get(IDataScienceCodeLensProvider); @@ -236,12 +231,6 @@ suite('DataScience Debugger tests', () => { return []; } - test('IANHU', async () => { - ioc.getSettings().datascience.stopOnFirstLineWhileDebugging = true; - - await debugCell('#%%\nprint("bar")'); - }); - test('Debug cell without breakpoint', async () => { ioc.getSettings().datascience.stopOnFirstLineWhileDebugging = true; diff --git a/src/test/datascience/mockDebugService.ts b/src/test/datascience/mockDebugService.ts index 7427ea21c146..fce6f894cf76 100644 --- a/src/test/datascience/mockDebugService.ts +++ b/src/test/datascience/mockDebugService.ts @@ -136,6 +136,7 @@ export class MockDebuggerService implements IDebugService, IDisposable { if (config.port) { this.session = new MockDebugSession(uuid(), config, this.sendCustomRequest.bind(this)); + // Create our debug adapter tracker at session start if (this.debugAdapterTrackerFactory) { this.debugAdapterTracker = this.debugAdapterTrackerFactory.createDebugAdapterTracker(this.session) as DebugAdapterTracker; } @@ -143,7 +144,6 @@ export class MockDebuggerService implements IDebugService, IDisposable { this.socket = net.createConnection(config.port); this.protocolParser.connect(this.socket); this.protocolParser.on('event_stopped', this.onBreakpoint.bind(this)); - //this.protocolParser.on('event_continue', this.onContinue.bind(this)); this.protocolParser.on('event_output', this.onOutput.bind(this)); this.socket.on('error', this.onError.bind(this)); this.socket.on('close', this.onClose.bind(this)); @@ -162,7 +162,6 @@ export class MockDebuggerService implements IDebugService, IDisposable { } public async continue(): Promise { - //return this.sendMessage('continue', { threadId: 0 }); await this.sendMessage('continue', { threadId: 0 }); if (this.debugAdapterTracker && this.debugAdapterTracker.onDidSendMessage) { this.debugAdapterTracker.onDidSendMessage({ type: 'event', event: 'continue' }); @@ -185,37 +184,6 @@ export class MockDebuggerService implements IDebugService, IDisposable { return deferred.promise; } - // Combine this, don't copy - //private emitEventMessage(event: string): Promise { - //return new Promise((resolve, reject) => { - //try { - //if (this.socket) { - //const obj = { - //event, - //type: 'event', - //seq: this.sequence - //}; - //this.sequence += 1; - //const objString = JSON.stringify(obj); - //const message = `Content-Length: ${objString.length}\r\n\r\n${objString}`; - //this.socket.write(message, (_a: any) => { - //if (this.debugAdapterTracker && this.debugAdapterTracker.onDidSendMessage) { - //this.debugAdapterTracker.onDidSendMessage(obj); - //} - //resolve(); - //}); - //} - //} catch (e) { - //reject(e); - //} - //}); - //} - - //private sendStop(): Promise { - ////return this.sendMessage( - ////'stopped' - //} - private sendCustomRequest(command: string, args?: any): Promise { return this.sendMessage(command, args); } @@ -335,12 +303,6 @@ export class MockDebuggerService implements IDebugService, IDisposable { this.breakpointEmitter.fire(); } - //private onContinue(args: DebugProtocol.ContinuedEvent): void { - //if (this.debugAdapterTracker && this.debugAdapterTracker.onDidSendMessage) { - //this.debugAdapterTracker.onDidSendMessage(args); - //} - //} - private onOutput(args: any): void { traceInfo(JSON.stringify(args)); }