Skip to content

Commit

Permalink
Fix open in data viewer (#10511)
Browse files Browse the repository at this point in the history
  • Loading branch information
IanMatthewHuff authored Jun 21, 2022
1 parent 7a2e7a9 commit 669d502
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 22 deletions.
1 change: 1 addition & 0 deletions news/2 Fixes/10007.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixes to pick the correct python version when opening in the DataViewer from python debug menu.
2 changes: 2 additions & 0 deletions src/test/datascience/data-viewing/dataViewing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
my_list = [1, 2, 3]
breakpoint()
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
import * as vscode from 'vscode';
import * as path from '../../../platform/vscode-path/path';
import * as sinon from 'sinon';
import { traceInfo } from '../../../platform/logging';
import { IDisposable } from '../../../platform/common/types';
import { captureScreenShot, openFile } from '../../common.node';
import { initialize } from '../../initialize.node';
import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../constants.node';
import { waitForCondition } from '../../common.node';
import { defaultNotebookTestTimeout } from '../notebook/helper';
import { createDeferred } from '../../../platform/common/utils/async';
import { disposeAllDisposables } from '../../../platform/common/helpers';
import { IShowDataViewerFromVariablePanel } from '../../../platform/messageTypes';

/* eslint-disable @typescript-eslint/no-explicit-any, no-invalid-this */
suite('DataScience - VSCode Notebook - (DataViewer)', function () {
const disposables: IDisposable[] = [];
const testPythonFile = path.join(
EXTENSION_ROOT_DIR_FOR_TESTS,
'src',
'test',
'datascience',
'data-viewing',
'dataViewing.py'
);
this.timeout(120_000);
suiteSetup(async function () {
traceInfo('Suite Setup');
this.timeout(120_000);
try {
await initialize();
sinon.restore();
traceInfo('Suite Setup (completed)');
} catch (e) {
await captureScreenShot('data-viewer-suite');
throw e;
}
});
// Cleanup after suite is finished
suiteTeardown(() => {
disposeAllDisposables(disposables);
});
setup(async () => {
// Close documents and stop debugging
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await vscode.commands.executeCommand('workbench.action.closeAllGroups');
await vscode.commands.executeCommand('workbench.debug.viewlet.action.removeAllBreakpoints');
});
teardown(async () => {
// Close documents and stop debugging
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await vscode.commands.executeCommand('workbench.action.closeAllGroups');
await vscode.commands.executeCommand('workbench.action.debug.stop');
await vscode.commands.executeCommand('workbench.debug.viewlet.action.removeAllBreakpoints');
});
// Start debugging using the python extension
test('Open from Python debug variables', async () => {
// First off, open up our python test file and make sure editor and groups are how we want them
const textDocument = await openFile(testPythonFile);

// Wait for it to be opened and active, then get the editor
await waitForCondition(
async () => {
return vscode.window.activeTextEditor?.document === textDocument;
},
defaultNotebookTestTimeout,
`Waiting for editor to switch`
);
const textEditor = vscode.window.activeTextEditor!;

// Next, place a breakpoint on the second line
const bpPosition = new vscode.Position(1, 0);
textEditor.selection = new vscode.Selection(bpPosition, bpPosition);

await vscode.commands.executeCommand('editor.debug.action.toggleBreakpoint');

// Prep to see when we are stopped
const stoppedDef = createDeferred<void>();
let variablesReference = -1;

// Keep an eye on debugger messages to see when we stop
disposables.push(
vscode.debug.registerDebugAdapterTrackerFactory('*', {
createDebugAdapterTracker(_session: vscode.DebugSession) {
return {
onWillReceiveMessage: (m) => {
if (m.command && m.command === 'variables') {
// When we get the variables event track the reference and release the code
variablesReference = m.arguments.variablesReference;
stoppedDef.resolve();
}
}
};
}
})
);

// Now start the debugger
await vscode.commands.executeCommand('python.debugInTerminal');

// Wait until we stop
await stoppedDef.promise;

// Properties that we want to show the data viewer with
const props: IShowDataViewerFromVariablePanel = {
container: {},
variable: {
evaluateName: 'my_list',
name: 'my_list',
value: '[1, 2, 3]',
variablesReference
}
};

// Run our command to actually open the variable view
await vscode.commands.executeCommand('jupyter.showDataViewer', props);

// Wait until a new tab group opens with the right name
await waitForCondition(
async () => {
// return vscode.window.tabGroups.all[1].activeTab?.label === 'Data Viewer - my_list';
let tabFound = false;
vscode.window.tabGroups.all.forEach((tg) => {
if (
tg.tabs.some((tab) => {
return tab.label === 'Data Viewer - my_list';
})
) {
tabFound = true;
}
});
return tabFound;
},
40_000,
'Failed to open the data viewer from python variables'
);
});
});
64 changes: 43 additions & 21 deletions src/webviews/extension-side/dataviewer/dataViewerCommandRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
'use strict';

import { inject, injectable, named, optional } from 'inversify';
import { Uri } from 'vscode';
import { DebugConfiguration, Uri } from 'vscode';
import { DebugProtocol } from 'vscode-debugprotocol';
import { convertDebugProtocolVariableToIJupyterVariable } from '../../../kernels/variables/helpers';
import { IJupyterVariables } from '../../../kernels/variables/types';
Expand All @@ -23,10 +23,11 @@ import { DataScience } from '../../../platform/common/utils/localize';
import { noop } from '../../../platform/common/utils/misc';
import { untildify } from '../../../platform/common/utils/platform';
import { IInterpreterService } from '../../../platform/interpreter/contracts';
import { traceError } from '../../../platform/logging';
import { traceError, traceInfo } from '../../../platform/logging';
import { IShowDataViewerFromVariablePanel } from '../../../platform/messageTypes';
import { sendTelemetryEvent } from '../../../telemetry';
import { EventName } from '../../../telemetry/constants';
import { PythonEnvironment } from '../api/extension';
import { IDataScienceErrorHandler } from '../../../kernels/errors/types';
import { DataViewerChecker } from './dataViewerChecker';
import { IDataViewerDependencyService, IDataViewerFactory, IJupyterVariableDataProviderFactory } from './types';
Expand Down Expand Up @@ -89,29 +90,15 @@ export class DataViewerCommandRegistry implements IExtensionSingleActivationServ
try {
// First find out the current python environment that we are working with
if (
this.debugService.activeDebugSession.configuration.python &&
this.debugService.activeDebugSession.configuration &&
this.dataViewerDependencyService &&
this.interpreterService
) {
// Check to see that we are actually getting a string here as it looks like one customer was not
if (typeof this.debugService.activeDebugSession.configuration.python !== 'string') {
// https://github.com/microsoft/vscode-jupyter/issues/10007
// Error thrown here will be caught and logged by the catch below to send
throw new Error(
`active.DebugSession.configuration.python is not a string: ${JSON.stringify(
this.debugService.activeDebugSession.configuration.python
)}`
);
}
// Check the debug adapter session to get the python env that launched it
const pythonEnv = await this.getDebugAdapterPython(
this.debugService.activeDebugSession.configuration
);

// Uri won't work with ~ so untildify first
let untildePath = this.debugService.activeDebugSession.configuration.python;
if (untildePath.startsWith('~') && this.platformService.homeDir) {
untildePath = untildify(untildePath, this.platformService.homeDir.path);
}

const pythonEnv = await this.interpreterService.getInterpreterDetails(Uri.file(untildePath));
// Check that we have dependencies installed for data viewer
pythonEnv && (await this.dataViewerDependencyService.checkAndInstallMissingDependencies(pythonEnv));
}

Expand All @@ -136,4 +123,39 @@ export class DataViewerCommandRegistry implements IExtensionSingleActivationServ
}
}
}

// For the given debug adapter, return the PythonEnvironment used to launch it
// Mirrors the logic from Python extension here:
// https://github.com/microsoft/vscode-python/blob/35b813f37d1ceec547277180e3aa07bd24d86f89/src/client/debugger/extension/adapter/factory.ts#L116
private async getDebugAdapterPython(
debugConfiguration: DebugConfiguration
): Promise<PythonEnvironment | undefined> {
if (!this.interpreterService) {
// Interpreter service is optional
traceInfo('Interpreter Service missing when trying getDebugAdapterPython');
return;
}

// Check debugAdapterPython and pythonPath
let pythonPath: string = '';
if (debugConfiguration.debugAdapterPython !== undefined) {
traceInfo('Found debugAdapterPython on Debug Configuration to use');
pythonPath = debugConfiguration.debugAdapterPython;
} else if (debugConfiguration.pythonPath) {
traceInfo('Found pythonPath on Debug Configuration to use');
pythonPath = debugConfiguration.pythonPath;
}

if (pythonPath) {
let untildePath = debugConfiguration.python;
if (untildePath.startsWith('~') && this.platformService.homeDir) {
untildePath = untildify(untildePath, this.platformService.homeDir.path);
}

return this.interpreterService.getInterpreterDetails(Uri.file(untildePath));
} else {
// Failed to find the expected configuration items, use active interpreter (might be attach scenario)
return this.interpreterService.getActiveInterpreter();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export class DataViewerDependencyService implements IDataViewerDependencyService
const tokenSource = new CancellationTokenSource();
try {
const pandasVersion = await this.getVersionOfPandas(interpreter, tokenSource.token);

if (Cancellation.isCanceled(tokenSource.token)) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export class DataViewerFactory implements IDataViewerFactory, IAsyncDisposable {
this.knownViewers.add(dataExplorer);
dataExplorer.onDidDisposeDataViewer(this.updateOpenDataViewers, this, this.disposables);
dataExplorer.onDidChangeDataViewerViewState(this.updateViewStateContext, this, this.disposables);

// Show the window and the data
await dataExplorer.showData(dataProvider, title);
result = dataExplorer;
Expand Down

0 comments on commit 669d502

Please sign in to comment.