From 8c4449023bd44af94ab0a5e3bebb22c00d76e64f Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Thu, 17 Sep 2020 13:43:08 -0700 Subject: [PATCH 01/15] Port ipykernel install fix to release (#13975) * Fix installing ipykernel into interpreters for raw kernel (#13959) * update news Co-authored-by: Ian Huff --- CHANGELOG.md | 2 + package.nls.json | 3 +- src/client/common/utils/localize.ts | 4 ++ .../jupyter/kernels/kernelSelector.ts | 29 +++++++++++++- .../kernel-launcher/kernelFinder.ts | 38 ++----------------- .../kernels/kernelSelector.unit.test.ts | 5 ++- .../datascience/kernelFinder.unit.test.ts | 8 +--- 7 files changed, 45 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f89e1e35e763..3a6881c00fdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,8 @@ ([#13612](https://github.com/Microsoft/vscode-python/issues/13612)) 1. Fix the behavior of the 'python.showStartPage' setting. ([#13706](https://github.com/Microsoft/vscode-python/issues/13706)) +1. Correctly install ipykernel when launching from an interpreter. + ([#13956](https://github.com/Microsoft/vscode-python/issues/13956)) ### Code Health diff --git a/package.nls.json b/package.nls.json index ec6378814166..16a537b3a669 100644 --- a/package.nls.json +++ b/package.nls.json @@ -595,5 +595,6 @@ "DataScience.interactiveWindowModeBannerTitle": "Do you want to open a new Python Interactive window for this file? [More Information](command:workbench.action.openSettings?%5B%22python.dataScience.interactiveWindowMode%22%5D).", "DataScience.interactiveWindowModeBannerSwitchYes": "Yes", "DataScience.interactiveWindowModeBannerSwitchAlways": "Always", - "DataScience.interactiveWindowModeBannerSwitchNo": "No" + "DataScience.interactiveWindowModeBannerSwitchNo": "No", + "DataScience.ipykernelNotInstalled": "IPyKernel not installed into interpreter {0}" } diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 8207de65015f..4a4981f8ae63 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -1116,6 +1116,10 @@ export namespace DataScience { ); export const connected = localize('DataScience.connected', 'Connected'); export const disconnected = localize('DataScience.disconnected', 'Disconnected'); + export const ipykernelNotInstalled = localize( + 'DataScience.ipykernelNotInstalled', + 'IPyKernel not installed into interpreter {0}' + ); } export namespace StartPage { diff --git a/src/client/datascience/jupyter/kernels/kernelSelector.ts b/src/client/datascience/jupyter/kernels/kernelSelector.ts index d2623d2df616..e398ded46d5f 100644 --- a/src/client/datascience/jupyter/kernels/kernelSelector.ts +++ b/src/client/datascience/jupyter/kernels/kernelSelector.ts @@ -27,7 +27,8 @@ import { IJupyterSessionManagerFactory, IKernelDependencyService, INotebookMetadataLive, - INotebookProviderConnection + INotebookProviderConnection, + KernelInterpreterDependencyResponse } from '../../types'; import { createDefaultKernelSpec, getDisplayNameOrNameOfKernelConnection } from './helpers'; import { KernelSelectionProvider } from './kernelSelections'; @@ -504,6 +505,8 @@ export class KernelSelector implements IKernelSelectionUsage { if (!kernelSpec && !activeInterpreter) { return; } else if (!kernelSpec && activeInterpreter) { + await this.installDependenciesIntoInterpreter(activeInterpreter, ignoreDependencyCheck, cancelToken); + // Return current interpreter. return { kind: 'startUsingPythonInterpreter', @@ -512,6 +515,11 @@ export class KernelSelector implements IKernelSelectionUsage { } else if (kernelSpec) { // Locate the interpreter that matches our kernelspec const interpreter = await this.kernelService.findMatchingInterpreter(kernelSpec, cancelToken); + + if (interpreter) { + await this.installDependenciesIntoInterpreter(interpreter, ignoreDependencyCheck, cancelToken); + } + return { kind: 'startUsingKernelSpec', kernelSpec, interpreter }; } } @@ -545,6 +553,25 @@ export class KernelSelector implements IKernelSelectionUsage { return { kernelSpec, interpreter, kind: 'startUsingPythonInterpreter' }; } + // If we need to install our dependencies now (for non-native scenarios) + // then install ipykernel into the interpreter or throw error + private async installDependenciesIntoInterpreter( + interpreter: PythonEnvironment, + ignoreDependencyCheck?: boolean, + cancelToken?: CancellationToken + ) { + if (!ignoreDependencyCheck) { + if ( + (await this.kernelDependencyService.installMissingDependencies(interpreter, cancelToken)) !== + KernelInterpreterDependencyResponse.ok + ) { + throw new Error( + localize.DataScience.ipykernelNotInstalled().format(interpreter.displayName || interpreter.path) + ); + } + } + } + /** * Use the provided interpreter as a kernel. * If `displayNameOfKernelNotFound` is provided, then display a message indicating we're using the `current interpreter`. diff --git a/src/client/datascience/kernel-launcher/kernelFinder.ts b/src/client/datascience/kernel-launcher/kernelFinder.ts index 4958ea42216d..5315c09caf05 100644 --- a/src/client/datascience/kernel-launcher/kernelFinder.ts +++ b/src/client/datascience/kernel-launcher/kernelFinder.ts @@ -5,13 +5,12 @@ import type { nbformat } from '@jupyterlab/coreutils'; import { inject, injectable, named } from 'inversify'; import * as path from 'path'; -import { CancellationToken, CancellationTokenSource } from 'vscode'; +import { CancellationToken } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; -import { wrapCancellationTokens } from '../../common/cancellation'; import { traceError, traceInfo } from '../../common/logger'; import { IPlatformService } from '../../common/platform/types'; import { IPythonExecutionFactory } from '../../common/process/types'; -import { IExtensionContext, IInstaller, InstallerResponse, IPathUtils, Product, Resource } from '../../common/types'; +import { IExtensionContext, IPathUtils, Resource } from '../../common/types'; import { IEnvironmentVariablesProvider } from '../../common/variables/types'; import { IInterpreterLocatorService, IInterpreterService, KNOWN_PATH_SERVICE } from '../../interpreter/contracts'; import { captureTelemetry } from '../../telemetry'; @@ -20,7 +19,6 @@ import { Telemetry } from '../constants'; import { defaultKernelSpecName } from '../jupyter/kernels/helpers'; import { JupyterKernelSpec } from '../jupyter/kernels/jupyterKernelSpec'; import { IDataScienceFileSystem, IJupyterKernelSpec } from '../types'; -import { getKernelInterpreter } from './helpers'; import { IKernelFinder } from './types'; // tslint:disable-next-line:no-require-imports no-var-requires const flatten = require('lodash/flatten') as typeof import('lodash/flatten'); @@ -56,7 +54,6 @@ export class KernelFinder implements IKernelFinder { @inject(IPlatformService) private platformService: IPlatformService, @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, @inject(IPathUtils) private readonly pathUtils: IPathUtils, - @inject(IInstaller) private installer: IInstaller, @inject(IExtensionContext) private readonly context: IExtensionContext, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, @inject(IPythonExecutionFactory) private readonly exeFactory: IPythonExecutionFactory, @@ -65,9 +62,7 @@ export class KernelFinder implements IKernelFinder { @captureTelemetry(Telemetry.KernelFinderPerf) public async findKernelSpec( resource: Resource, - kernelSpecMetadata?: nbformat.IKernelspecMetadata, - cancelToken?: CancellationToken, - ignoreDependencyCheck?: boolean + kernelSpecMetadata?: nbformat.IKernelspecMetadata ): Promise { await this.readCache(); let foundKernel: IJupyterKernelSpec | undefined; @@ -108,8 +103,7 @@ export class KernelFinder implements IKernelFinder { this.writeCache().ignoreErrors(); - // Verify that ipykernel is installed into the given kernelspec interpreter - return ignoreDependencyCheck || !foundKernel ? foundKernel : this.verifyIpyKernel(foundKernel, cancelToken); + return foundKernel; } // Search all our local file system locations for installed kernel specs and return them @@ -318,30 +312,6 @@ export class KernelFinder implements IKernelFinder { return flatten(fullPathResults); } - // For the given kernelspec return back the kernelspec with ipykernel installed into it or error - private async verifyIpyKernel( - kernelSpec: IJupyterKernelSpec, - cancelToken?: CancellationToken - ): Promise { - const interpreter = await getKernelInterpreter(kernelSpec, this.interpreterService); - - if (await this.installer.isInstalled(Product.ipykernel, interpreter)) { - return kernelSpec; - } else { - const token = new CancellationTokenSource(); - const response = await this.installer.promptToInstall( - Product.ipykernel, - interpreter, - wrapCancellationTokens(cancelToken, token.token) - ); - if (response === InstallerResponse.Installed) { - return kernelSpec; - } - } - - throw new Error(`IPyKernel not installed into interpreter ${interpreter.displayName}`); - } - private async getKernelSpecFromActiveInterpreter( kernelName: string, resource: Resource diff --git a/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts b/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts index 515e6c4984de..2b15157b520b 100644 --- a/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts +++ b/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts @@ -31,7 +31,7 @@ import { LiveKernelModel } from '../../../../client/datascience/jupyter/kernels/types'; import { IKernelFinder } from '../../../../client/datascience/kernel-launcher/types'; -import { IJupyterSessionManager } from '../../../../client/datascience/types'; +import { IJupyterSessionManager, KernelInterpreterDependencyResponse } from '../../../../client/datascience/types'; import { IInterpreterService } from '../../../../client/interpreter/contracts'; import { InterpreterService } from '../../../../client/interpreter/interpreterService'; import { EnvironmentType, PythonEnvironment } from '../../../../client/pythonEnvironments/info'; @@ -72,6 +72,9 @@ suite('DataScience - KernelSelector', () => { kernelSelectionProvider = mock(KernelSelectionProvider); appShell = mock(ApplicationShell); dependencyService = mock(KernelDependencyService); + when(dependencyService.installMissingDependencies(anything(), anything())).thenResolve( + KernelInterpreterDependencyResponse.ok + ); interpreterService = mock(InterpreterService); kernelFinder = mock(); const jupyterSessionManagerFactory = mock(JupyterSessionManagerFactory); diff --git a/src/test/datascience/kernelFinder.unit.test.ts b/src/test/datascience/kernelFinder.unit.test.ts index 696864406153..bf37540a375c 100644 --- a/src/test/datascience/kernelFinder.unit.test.ts +++ b/src/test/datascience/kernelFinder.unit.test.ts @@ -11,7 +11,7 @@ import { Uri } from 'vscode'; import { IWorkspaceService } from '../../client/common/application/types'; import { IPlatformService } from '../../client/common/platform/types'; import { PythonExecutionFactory } from '../../client/common/process/pythonExecutionFactory'; -import { IExtensionContext, IInstaller, IPathUtils, Resource } from '../../client/common/types'; +import { IExtensionContext, IPathUtils, Resource } from '../../client/common/types'; import { Architecture } from '../../client/common/utils/platform'; import { IEnvironmentVariablesProvider } from '../../client/common/variables/types'; import { defaultKernelSpecName } from '../../client/datascience/jupyter/kernels/helpers'; @@ -30,7 +30,6 @@ suite('Kernel Finder', () => { let pathUtils: typemoq.IMock; let context: typemoq.IMock; let envVarsProvider: typemoq.IMock; - let installer: IInstaller; let workspaceService: IWorkspaceService; let kernelFinder: IKernelFinder; let activeInterpreter: PythonEnvironment; @@ -83,9 +82,6 @@ suite('Kernel Finder', () => { context.setup((c) => c.globalStoragePath).returns(() => './'); fileSystem = typemoq.Mock.ofType(); - installer = mock(); - when(installer.isInstalled(anything(), anything())).thenResolve(true); - platformService = typemoq.Mock.ofType(); platformService.setup((ps) => ps.isWindows).returns(() => true); platformService.setup((ps) => ps.isMac).returns(() => true); @@ -325,7 +321,6 @@ suite('Kernel Finder', () => { platformService.object, fileSystem.object, pathUtils.object, - instance(installer), context.object, instance(workspaceService), instance(executionFactory), @@ -408,7 +403,6 @@ suite('Kernel Finder', () => { platformService.object, fileSystem.object, pathUtils.object, - instance(installer), context.object, instance(workspaceService), instance(executionFactory), From b92203aae441d1b264a23ed1db584dc698f0c643 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Thu, 17 Sep 2020 14:40:12 -0700 Subject: [PATCH 02/15] Merge in changes to release (#13976) * Up release version for new release (#13928) * Up release version * Update changelog * Update changelog * Workaround test issue (#13930) * Try different version of VS code in release * Change to make it use the actual variable * Use a real version * More tests failing with gpu error (#13935) * Try different version of VS code in release * Change to make it use the actual variable * Use a real version * Two more version changes * Fix kernel and server name missing in certain situations (#13974) * Fix kernel name and server name * Fixup server name for remote situations * Add some functional tests * Add news entry * Delete news file --- CHANGELOG.md | 5 ++ build/ci/templates/globals.yml | 1 + .../interactive-common/interactiveBase.ts | 80 ++++++++++++------- .../interactive-ipynb/nativeEditor.ts | 4 +- .../datascience/jupyter/kernels/helpers.ts | 7 +- .../history-react/interactivePanel.tsx | 3 +- .../interactive-common/jupyterInfo.tsx | 42 +--------- .../interactive-common/mainState.ts | 8 +- .../redux/reducers/kernel.ts | 4 +- .../interactive-common/redux/store.ts | 4 +- src/datascience-ui/native-editor/toolbar.tsx | 1 - .../interactivePanel.functional.test.tsx | 4 +- .../nativeEditor.functional.test.tsx | 9 ++- .../nativeEditor.toolbar.functional.test.tsx | 8 +- src/test/datascience/testHelpers.tsx | 9 +++ src/test/debuggerTest.ts | 3 +- src/test/multiRootTest.ts | 4 +- src/test/standardTest.ts | 4 +- 18 files changed, 108 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a6881c00fdb..cae1f6150600 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,11 @@ ([#13729](https://github.com/Microsoft/vscode-python/issues/13729)) 1. Fix nighly failure with beakerx. ([#13734](https://github.com/Microsoft/vscode-python/issues/13734)) +## 2020.8.6 (15 September 2020) + +### Fixes + +1. Workaround problem caused by https://github.com/microsoft/vscode/issues/106547 ### Thanks diff --git a/build/ci/templates/globals.yml b/build/ci/templates/globals.yml index 03457023e99e..98bd51685dd0 100644 --- a/build/ci/templates/globals.yml +++ b/build/ci/templates/globals.yml @@ -11,3 +11,4 @@ variables: npm_config_cache: $(Pipeline.Workspace)/.npm vmImageMacOS: 'macOS-10.15' TS_NODE_FILES: true # Temporarily enabled to allow using types from vscode.proposed.d.ts from ts-node (for tests). + VSC_PYTHON_CI_TEST_VSC_CHANNEL: '1.48.0' # Enforce this until https://github.com/microsoft/vscode-test/issues/73 is fixed diff --git a/src/client/datascience/interactive-common/interactiveBase.ts b/src/client/datascience/interactive-common/interactiveBase.ts index 1290190964d3..5e0c1d11e16c 100644 --- a/src/client/datascience/interactive-common/interactiveBase.ts +++ b/src/client/datascience/interactive-common/interactiveBase.ts @@ -47,7 +47,7 @@ import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; import { generateCellRangesFromDocument } from '../cellFactory'; import { CellMatcher } from '../cellMatcher'; import { addToUriList, translateKernelLanguageToMonaco } from '../common'; -import { Commands, Identifiers, Telemetry } from '../constants'; +import { Commands, Identifiers, Settings, Telemetry } from '../constants'; import { ColumnWarningSize, IDataViewerFactory } from '../data-viewing/types'; import { IAddedSysInfo, @@ -865,9 +865,54 @@ export abstract class InteractiveBase extends WebviewPanelHost { + // If we don't have a server connection, make one if remote. We need the remote connection in order + // to compute the display name. However only do this if the user is allowing auto start. + if ( + !serverConnection && + this.configService.getSettings(this.owningResource).datascience.jupyterServerURI !== + Settings.JupyterServerLocalLaunch && + !this.configService.getSettings(this.owningResource).datascience.disableJupyterAutoStart + ) { + serverConnection = await this.notebookProvider.connect({ disableUI: true }); + } + + let displayName = + serverConnection?.displayName || + (!serverConnection?.localLaunch ? serverConnection?.url : undefined) || + (this.configService.getSettings().datascience.jupyterServerURI === Settings.JupyterServerLocalLaunch + ? localize.DataScience.localJupyterServer() + : localize.DataScience.serverNotStarted()); + + if (serverConnection) { + // Determine the connection URI of the connected server to display + if (serverConnection.localLaunch) { + displayName = localize.DataScience.localJupyterServer(); + } else { + // Log this remote URI into our MRU list + addToUriList( + this.globalStorage, + !isNil(serverConnection.url) ? serverConnection.url : serverConnection.displayName, + Date.now(), + serverConnection.displayName + ); + } + } + + return displayName; + } + private combineData( oldData: nbformat.ICodeCell | nbformat.IRawCell | nbformat.IMarkdownCell | undefined, cell: ICell @@ -1154,27 +1199,6 @@ export abstract class InteractiveBase extends WebviewPanelHost { const statusChangeHandler = async (status: ServerStatus) => { const connectionMetadata = notebook.getKernelConnection(); @@ -1182,8 +1206,8 @@ export abstract class InteractiveBase extends WebviewPanelHost } private renderKernelSelection() { - if (this.props.kernel.localizedUri === getLocString('DataScience.localJupyterServer', 'local')) { + if (this.props.kernel.serverName === getLocString('DataScience.localJupyterServer', 'local')) { return; } @@ -235,7 +235,6 @@ ${buildSettingsCss(this.props.settings)}`} selectServer={this.props.selectServer} selectKernel={this.props.selectKernel} shouldShowTrustMessage={false} - settings={this.props.settings} /> ); } diff --git a/src/datascience-ui/interactive-common/jupyterInfo.tsx b/src/datascience-ui/interactive-common/jupyterInfo.tsx index ec94902757f3..62d16d111053 100644 --- a/src/datascience-ui/interactive-common/jupyterInfo.tsx +++ b/src/datascience-ui/interactive-common/jupyterInfo.tsx @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; -import { isEmpty, isNil } from 'lodash'; import * as React from 'react'; -import { IDataScienceExtraSettings } from '../../client/datascience/types'; import { Image, ImageName } from '../react-common/image'; import { getLocString } from '../react-common/locReactSide'; import { IFont, IServerState, ServerStatus } from './mainState'; @@ -16,7 +14,6 @@ export interface IJupyterInfoProps { kernel: IServerState; isNotebookTrusted?: boolean; shouldShowTrustMessage: boolean; - settings?: IDataScienceExtraSettings | undefined; selectServer(): void; launchNotebookTrustPrompt?(): void; // Native editor-specific selectKernel(): void; @@ -37,17 +34,10 @@ export class JupyterInfo extends React.Component { } public render() { - let jupyterServerDisplayName: string = this.props.kernel.localizedUri; - if (!isNil(this.props.settings) && isEmpty(jupyterServerDisplayName)) { - const jupyterServerUriSetting: string = this.props.settings.jupyterServerURI; - if (!isEmpty(jupyterServerUriSetting) && this.isUriOfComputeInstance(jupyterServerUriSetting)) { - jupyterServerDisplayName = this.getComputeInstanceNameFromId(jupyterServerUriSetting); - } - } - + const jupyterServerDisplayName: string = this.props.kernel.serverName; const serverTextSize = getLocString('DataScience.jupyterServer', 'Jupyter Server').length + jupyterServerDisplayName.length + 4; // plus 4 for the icon - const displayNameTextSize = this.props.kernel.displayName.length + this.props.kernel.jupyterServerStatus.length; + const displayNameTextSize = this.props.kernel.kernelName.length + this.props.kernel.jupyterServerStatus.length; const dynamicFont: React.CSSProperties = { fontSize: 'var(--vscode-font-size)', // Use the same font and size as the menu fontFamily: 'var(--vscode-font-family)', @@ -98,11 +88,11 @@ export class JupyterInfo extends React.Component { role="button" aria-disabled={ariaDisabled} > - {this.props.kernel.displayName}: {this.props.kernel.jupyterServerStatus} + {this.props.kernel.kernelName}: {this.props.kernel.jupyterServerStatus} ); } else { - const displayName = this.props.kernel.displayName ?? getLocString('DataScience.noKernel', 'No Kernel'); + const displayName = this.props.kernel.kernelName ?? getLocString('DataScience.noKernel', 'No Kernel'); return (
{displayName}: {this.props.kernel.jupyterServerStatus} @@ -138,30 +128,6 @@ export class JupyterInfo extends React.Component { : getLocString('DataScience.connected', 'Connected'); } - private isUriOfComputeInstance(uri: string): boolean { - try { - const parsedUrl: URL = new URL(uri); - return parsedUrl.searchParams.get('id') === 'azureml_compute_instances'; - } catch (e) { - return false; - } - } - - private getComputeInstanceNameFromId(id: string | undefined): string { - if (isNil(id)) { - return ''; - } - - const res: string[] | null = id.match( - /\/providers\/Microsoft.MachineLearningServices\/workspaces\/[^\/]+\/computes\/([^\/]+)(\/)?/ - ); - if (isNil(res) || res.length < 2) { - return ''; - } - - return res[1]; - } - private selectServer(): void { this.props.selectServer(); } diff --git a/src/datascience-ui/interactive-common/mainState.ts b/src/datascience-ui/interactive-common/mainState.ts index 250831ac5c3a..f84713746568 100644 --- a/src/datascience-ui/interactive-common/mainState.ts +++ b/src/datascience-ui/interactive-common/mainState.ts @@ -134,8 +134,8 @@ export interface IFont { export interface IServerState { jupyterServerStatus: ServerStatus; - localizedUri: string; - displayName: string; + serverName: string; + kernelName: string; language: string; } @@ -192,8 +192,8 @@ export function generateTestState(filePath: string = '', editable: boolean = fal loaded: false, testMode: true, kernel: { - localizedUri: 'No Kernel', - displayName: 'Python', + serverName: '', + kernelName: 'Python', jupyterServerStatus: ServerStatus.NotStarted, language: PYTHON_LANGUAGE }, diff --git a/src/datascience-ui/interactive-common/redux/reducers/kernel.ts b/src/datascience-ui/interactive-common/redux/reducers/kernel.ts index 7f8f2a8ad95c..c4f3f1de1387 100644 --- a/src/datascience-ui/interactive-common/redux/reducers/kernel.ts +++ b/src/datascience-ui/interactive-common/redux/reducers/kernel.ts @@ -40,9 +40,9 @@ export namespace Kernel { return { ...arg.prevState, kernel: { - localizedUri: arg.payload.data.localizedUri, + serverName: arg.payload.data.serverName, jupyterServerStatus: arg.payload.data.jupyterServerStatus, - displayName: arg.payload.data.displayName, + kernelName: arg.payload.data.kernelName, language: arg.payload.data.language } }; diff --git a/src/datascience-ui/interactive-common/redux/store.ts b/src/datascience-ui/interactive-common/redux/store.ts index 345be932aa1f..7ee0e58627ee 100644 --- a/src/datascience-ui/interactive-common/redux/store.ts +++ b/src/datascience-ui/interactive-common/redux/store.ts @@ -68,8 +68,8 @@ function generateDefaultState( monacoReady: testMode, // When testing, monaco starts out ready loaded: false, kernel: { - displayName: getLocString('DataScience.noKernel', 'No Kernel'), - localizedUri: getLocString('DataScience.serverNotStarted', 'Not Started'), + kernelName: getLocString('DataScience.noKernel', 'No Kernel'), + serverName: getLocString('DataScience.serverNotStarted', 'Not Started'), jupyterServerStatus: ServerStatus.NotStarted, language: PYTHON_LANGUAGE }, diff --git a/src/datascience-ui/native-editor/toolbar.tsx b/src/datascience-ui/native-editor/toolbar.tsx index 63ffab3abf18..ecba9b191e02 100644 --- a/src/datascience-ui/native-editor/toolbar.tsx +++ b/src/datascience-ui/native-editor/toolbar.tsx @@ -268,7 +268,6 @@ export class Toolbar extends React.PureComponent { shouldShowTrustMessage={true} isNotebookTrusted={this.props.isNotebookTrusted} launchNotebookTrustPrompt={launchNotebookTrustPrompt} - settings={this.props.settings} />
diff --git a/src/test/datascience/interactivePanel.functional.test.tsx b/src/test/datascience/interactivePanel.functional.test.tsx index 0ef60f1e23cd..8f2e1179bc41 100644 --- a/src/test/datascience/interactivePanel.functional.test.tsx +++ b/src/test/datascience/interactivePanel.functional.test.tsx @@ -76,9 +76,9 @@ suite('DataScience Interactive Panel', () => { interruptKernel: noopAny, isAtBottom: false, kernel: { - displayName: '', + kernelName: '', jupyterServerStatus: ServerStatus.Busy, - localizedUri: '', + serverName: '', language: PYTHON_LANGUAGE }, knownDark: false, diff --git a/src/test/datascience/nativeEditor.functional.test.tsx b/src/test/datascience/nativeEditor.functional.test.tsx index 041b71198905..f2ec387bd364 100644 --- a/src/test/datascience/nativeEditor.functional.test.tsx +++ b/src/test/datascience/nativeEditor.functional.test.tsx @@ -83,7 +83,8 @@ import { typeCode, verifyCellIndex, verifyCellSource, - verifyHtmlOnCell + verifyHtmlOnCell, + verifyServerStatus } from './testHelpers'; import { ITestNativeEditorProvider } from './testNativeEditorProvider'; @@ -713,6 +714,8 @@ df.head()`; // Make sure it has a server assert.ok(editor.editor.notebook, 'Notebook did not start with a server'); + // Make sure it does have a name though + verifyServerStatus(editor.mount.wrapper, 'local'); } else { context.skip(); } @@ -721,6 +724,7 @@ df.head()`; runMountedTest('Server load skipped', async (context) => { if (ioc.mockJupyter) { ioc.getSettings().datascience.disableJupyterAutoStart = true; + ioc.getSettings().datascience.jupyterServerURI = 'https://remotetest'; await ioc.activate(); // Create an editor so something is listening to messages @@ -731,6 +735,9 @@ df.head()`; // Make sure it does not have a server assert.notOk(editor.editor.notebook, 'Notebook should not start with a server'); + + // Make sure it does have a name though + verifyServerStatus(editor.mount.wrapper, 'Not Started'); } else { context.skip(); } diff --git a/src/test/datascience/nativeEditor.toolbar.functional.test.tsx b/src/test/datascience/nativeEditor.toolbar.functional.test.tsx index b1e86f026daa..e51a44826a41 100644 --- a/src/test/datascience/nativeEditor.toolbar.functional.test.tsx +++ b/src/test/datascience/nativeEditor.toolbar.functional.test.tsx @@ -45,9 +45,9 @@ suite('DataScience Native Toolbar', () => { font: { family: '', size: 1 }, interruptKernel: sinon.stub(), kernel: { - displayName: '', + kernelName: '', jupyterServerStatus: ServerStatus.Busy, - localizedUri: '', + serverName: '', language: PYTHON_LANGUAGE }, restartKernel: sinon.stub(), @@ -245,9 +245,9 @@ suite('DataScience Native Toolbar', () => { font: { family: '', size: 1 }, interruptKernel: sinon.stub(), kernel: { - displayName: '', + kernelName: '', jupyterServerStatus: ServerStatus.Busy, - localizedUri: '', + serverName: '', language: PYTHON_LANGUAGE }, restartKernel: sinon.stub(), diff --git a/src/test/datascience/testHelpers.tsx b/src/test/datascience/testHelpers.tsx index d8c07d1236b6..88798f4334f9 100644 --- a/src/test/datascience/testHelpers.tsx +++ b/src/test/datascience/testHelpers.tsx @@ -221,6 +221,15 @@ export function verifyCellSource( assert.deepStrictEqual(inst.state.model?.getValue(), source, 'Source does not match on cell'); } +export function verifyServerStatus(wrapper: ReactWrapper, React.Component>, statusText: string) { + wrapper.update(); + + const foundResult = wrapper.find('div.kernel-status-server'); + assert.ok(foundResult.length >= 1, "Didn't find server status"); + const html = foundResult.html(); + assert.ok(html.includes(statusText), `${statusText} not found in server status`); +} + export function verifyHtmlOnCell( wrapper: ReactWrapper, React.Component>, cellType: 'NativeCell' | 'InteractiveCell', diff --git a/src/test/debuggerTest.ts b/src/test/debuggerTest.ts index c7fc3e1058d1..3720b0e662ed 100644 --- a/src/test/debuggerTest.ts +++ b/src/test/debuggerTest.ts @@ -10,6 +10,7 @@ import { EXTENSION_ROOT_DIR_FOR_TESTS } from './constants'; const workspacePath = path.join(__dirname, '..', '..', 'src', 'testMultiRootWkspc', 'multi.code-workspace'); process.env.IS_CI_SERVER_TEST_DEBUGGER = '1'; process.env.VSC_PYTHON_CI_TEST = '1'; +const channel = process.env.VSC_PYTHON_CI_TEST_VSC_CHANNEL || 'stable'; function start() { console.log('*'.repeat(100)); @@ -18,7 +19,7 @@ function start() { extensionDevelopmentPath: EXTENSION_ROOT_DIR_FOR_TESTS, extensionTestsPath: path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'out', 'test', 'index'), launchArgs: [workspacePath], - version: 'stable', + version: channel, extensionTestsEnv: { ...process.env, UITEST_DISABLE_INSIDERS: '1' } }).catch((ex) => { console.error('End Debugger tests (with errors)', ex); diff --git a/src/test/multiRootTest.ts b/src/test/multiRootTest.ts index 5859708a8e99..04631bd1b2ca 100644 --- a/src/test/multiRootTest.ts +++ b/src/test/multiRootTest.ts @@ -11,6 +11,8 @@ process.env.VSC_PYTHON_CI_TEST = '1'; initializeLogger(); +const channel = process.env.VSC_PYTHON_CI_TEST_VSC_CHANNEL || 'stable'; + function start() { console.log('*'.repeat(100)); console.log('Start Multiroot tests'); @@ -18,7 +20,7 @@ function start() { extensionDevelopmentPath: EXTENSION_ROOT_DIR_FOR_TESTS, extensionTestsPath: path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'out', 'test', 'index'), launchArgs: [workspacePath], - version: 'stable', + version: channel, extensionTestsEnv: { ...process.env, UITEST_DISABLE_INSIDERS: '1' } }).catch((ex) => { console.error('End Multiroot tests (with errors)', ex); diff --git a/src/test/standardTest.ts b/src/test/standardTest.ts index 4a6dd9f2e287..76739ae4b667 100644 --- a/src/test/standardTest.ts +++ b/src/test/standardTest.ts @@ -16,9 +16,7 @@ const extensionDevelopmentPath = process.env.CODE_EXTENSIONS_PATH ? process.env.CODE_EXTENSIONS_PATH : EXTENSION_ROOT_DIR_FOR_TESTS; -const channel = (process.env.VSC_PYTHON_CI_TEST_VSC_CHANNEL || '').toLowerCase().includes('insiders') - ? 'insiders' - : 'stable'; +const channel = process.env.VSC_PYTHON_CI_TEST_VSC_CHANNEL || 'stable'; function start() { console.log('*'.repeat(100)); From 8c4a0ec1ac0bfac255f9664c3a6201a7f2d28c28 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Fri, 18 Sep 2020 12:01:43 -0700 Subject: [PATCH 03/15] Port two fixes to the release branch (#13995) * Disable split views of custom editors (#13985) * Fix backup storage by looking at the options correctly (#13983) * Fix backup storage by looking at the options correctly * Fix backup by being more explicit * Only linux tests are failing. Hopefully fix them * Fixup changelog Co-authored-by: Don Jayamanne --- CHANGELOG.md | 2 + src/client/datascience/export/exportUtil.ts | 4 +- .../nativeEditorProviderOld.ts | 4 +- .../interactive-ipynb/trustCommandHandler.ts | 2 +- .../interactive-window/interactiveWindow.ts | 2 +- .../datascience/notebook/contentProvider.ts | 15 ++-- .../notebook/notebookEditorProvider.ts | 2 +- .../notebookStorage/nativeEditorProvider.ts | 37 +++++---- .../notebookStorage/nativeEditorStorage.ts | 82 ++++++------------- .../notebookStorageProvider.ts | 34 ++------ src/client/datascience/types.ts | 22 ++--- .../datascience/export/exportUtil.test.ts | 2 +- .../nativeEditorStorage.unit.test.ts | 20 ++--- .../datascience/mockCustomEditorService.ts | 2 +- .../notebook/contentProvider.ds.test.ts | 2 +- .../notebook/contentProvider.unit.test.ts | 8 +- 16 files changed, 95 insertions(+), 145 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cae1f6150600..38b5f71f1330 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,8 @@ ([#13706](https://github.com/Microsoft/vscode-python/issues/13706)) 1. Correctly install ipykernel when launching from an interpreter. ([#13956](https://github.com/Microsoft/vscode-python/issues/13956)) +1. Backup on custom editors is being ignored. + ([#13981](https://github.com/Microsoft/vscode-python/issues/13981)) ### Code Health diff --git a/src/client/datascience/export/exportUtil.ts b/src/client/datascience/export/exportUtil.ts index 237b93ff4ca1..9e9f2c9c97c8 100644 --- a/src/client/datascience/export/exportUtil.ts +++ b/src/client/datascience/export/exportUtil.ts @@ -57,7 +57,7 @@ export class ExportUtil { await this.jupyterExporter.exportToFile(cells, tempFile.filePath, false); const newPath = path.join(tempDir.path, '.ipynb'); await this.fs.copyLocal(tempFile.filePath, newPath); - model = await this.notebookStorage.getOrCreateModel(Uri.file(newPath)); + model = await this.notebookStorage.getOrCreateModel({ file: Uri.file(newPath) }); } finally { tempFile.dispose(); tempDir.dispose(); @@ -67,7 +67,7 @@ export class ExportUtil { } public async removeSvgs(source: Uri) { - const model = await this.notebookStorage.getOrCreateModel(source); + const model = await this.notebookStorage.getOrCreateModel({ file: source }); const newCells: ICell[] = []; for (const cell of model.cells) { diff --git a/src/client/datascience/interactive-ipynb/nativeEditorProviderOld.ts b/src/client/datascience/interactive-ipynb/nativeEditorProviderOld.ts index 9d7b37ffcd3d..99a857a61834 100644 --- a/src/client/datascience/interactive-ipynb/nativeEditorProviderOld.ts +++ b/src/client/datascience/interactive-ipynb/nativeEditorProviderOld.ts @@ -299,7 +299,7 @@ export class NativeEditorProviderOld extends NativeEditorProvider { this.activeEditors.set(e.file.fsPath, e); // Remove backup storage - this.loadModel(Uri.file(oldPath)) + this.loadModel({ file: Uri.file(oldPath) }) .then((m) => this.storage.deleteBackup(m)) .ignoreErrors(); } @@ -347,7 +347,7 @@ export class NativeEditorProviderOld extends NativeEditorProvider { * I.e. document is already opened in a VSC Notebook. */ private async isDocumentOpenedInVSCodeNotebook(document: TextDocument): Promise { - const model = await this.loadModel(document.uri); + const model = await this.loadModel({ file: document.uri }); // This is temporary code. return model instanceof VSCodeNotebookModel; } diff --git a/src/client/datascience/interactive-ipynb/trustCommandHandler.ts b/src/client/datascience/interactive-ipynb/trustCommandHandler.ts index 3718379cc804..b325032c8fc4 100644 --- a/src/client/datascience/interactive-ipynb/trustCommandHandler.ts +++ b/src/client/datascience/interactive-ipynb/trustCommandHandler.ts @@ -42,7 +42,7 @@ export class TrustCommandHandler implements IExtensionSingleActivationService { return; } - const model = await this.storageProvider.getOrCreateModel(uri); + const model = await this.storageProvider.getOrCreateModel({ file: uri }); if (model.isTrusted) { return; } diff --git a/src/client/datascience/interactive-window/interactiveWindow.ts b/src/client/datascience/interactive-window/interactiveWindow.ts index fd1b2388a161..3dd40ec874cd 100644 --- a/src/client/datascience/interactive-window/interactiveWindow.ts +++ b/src/client/datascience/interactive-window/interactiveWindow.ts @@ -256,7 +256,7 @@ export class InteractiveWindow extends InteractiveBase implements IInteractiveWi break; case InteractiveWindowMessages.ExportNotebookAs: - this.handleMessage(message, payload, this.exportAs); + this.handleMessage(message, payload, this.exportAs.bind); break; case InteractiveWindowMessages.HasCellResponse: diff --git a/src/client/datascience/notebook/contentProvider.ts b/src/client/datascience/notebook/contentProvider.ts index 12e3e0e9d41f..a94bc091a61c 100644 --- a/src/client/datascience/notebook/contentProvider.ts +++ b/src/client/datascience/notebook/contentProvider.ts @@ -70,9 +70,12 @@ export class NotebookContentProvider implements INotebookContentProvider { }; } // If there's no backup id, then skip loading dirty contents. - const model = await (openContext.backupId - ? this.notebookStorage.getOrCreateModel(uri, undefined, openContext.backupId, true) - : this.notebookStorage.getOrCreateModel(uri, undefined, true, true)); + const model = await this.notebookStorage.getOrCreateModel({ + file: uri, + backupId: openContext.backupId, + isNative: true, + skipLoadingDirtyContents: openContext.backupId === undefined + }); if (!(model instanceof VSCodeNotebookModel)) { throw new Error('Incorrect NotebookModel, expected VSCodeNotebookModel'); } @@ -82,7 +85,7 @@ export class NotebookContentProvider implements INotebookContentProvider { } @captureTelemetry(Telemetry.Save, undefined, true) public async saveNotebook(document: NotebookDocument, cancellation: CancellationToken) { - const model = await this.notebookStorage.getOrCreateModel(document.uri, undefined, undefined, true); + const model = await this.notebookStorage.getOrCreateModel({ file: document.uri, isNative: true }); if (cancellation.isCancellationRequested) { return; } @@ -94,7 +97,7 @@ export class NotebookContentProvider implements INotebookContentProvider { document: NotebookDocument, cancellation: CancellationToken ): Promise { - const model = await this.notebookStorage.getOrCreateModel(document.uri, undefined, undefined, true); + const model = await this.notebookStorage.getOrCreateModel({ file: document.uri, isNative: true }); if (!cancellation.isCancellationRequested) { await this.notebookStorage.saveAs(model, targetResource); } @@ -104,7 +107,7 @@ export class NotebookContentProvider implements INotebookContentProvider { _context: NotebookDocumentBackupContext, cancellation: CancellationToken ): Promise { - const model = await this.notebookStorage.getOrCreateModel(document.uri, undefined, undefined, true); + const model = await this.notebookStorage.getOrCreateModel({ file: document.uri, isNative: true }); const id = this.notebookStorage.generateBackupId(model); await this.notebookStorage.backup(model, cancellation, id); return { diff --git a/src/client/datascience/notebook/notebookEditorProvider.ts b/src/client/datascience/notebook/notebookEditorProvider.ts index 6ed78c1a62fa..1cc4b962ff96 100644 --- a/src/client/datascience/notebook/notebookEditorProvider.ts +++ b/src/client/datascience/notebook/notebookEditorProvider.ts @@ -145,7 +145,7 @@ export class NotebookEditorProvider implements INotebookEditorProvider { return; } const uri = doc.uri; - const model = await this.storage.getOrCreateModel(uri, undefined, undefined, true); + const model = await this.storage.getOrCreateModel({ file: uri, isNative: true }); if (model instanceof VSCodeNotebookModel) { model.associateNotebookDocument(doc); } diff --git a/src/client/datascience/notebookStorage/nativeEditorProvider.ts b/src/client/datascience/notebookStorage/nativeEditorProvider.ts index 5d166c119789..23fcaa04da53 100644 --- a/src/client/datascience/notebookStorage/nativeEditorProvider.ts +++ b/src/client/datascience/notebookStorage/nativeEditorProvider.ts @@ -51,6 +51,7 @@ import { IJupyterDebugger, IJupyterVariableDataProviderFactory, IJupyterVariables, + IModelLoadOptions, INotebookEditor, INotebookEditorProvider, INotebookExporter, @@ -119,7 +120,7 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit enableFindWidget: true, retainContextWhenHidden: true }, - supportsMultipleEditorsPerDocument: true + supportsMultipleEditorsPerDocument: false }); } @@ -128,22 +129,26 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit context: CustomDocumentOpenContext, // This has info about backups. right now we use our own data. _cancellation: CancellationToken ): Promise { - const model = await this.loadModel(uri, undefined, context.backupId); + const model = await this.loadModel({ + file: uri, + backupId: context.backupId, + skipLoadingDirtyContents: context.backupId === undefined + }); return { uri, dispose: () => model.dispose() }; } public async saveCustomDocument(document: CustomDocument, cancellation: CancellationToken): Promise { - const model = await this.loadModel(document.uri); + const model = await this.loadModel({ file: document.uri }); return this.storage.save(model, cancellation); } public async saveCustomDocumentAs(document: CustomDocument, targetResource: Uri): Promise { - const model = await this.loadModel(document.uri); + const model = await this.loadModel({ file: document.uri }); return this.storage.saveAs(model, targetResource); } public async revertCustomDocument(document: CustomDocument, cancellation: CancellationToken): Promise { - const model = await this.loadModel(document.uri); + const model = await this.loadModel({ file: document.uri }); return this.storage.revert(model, cancellation); } public async backupCustomDocument( @@ -151,7 +156,7 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit _context: CustomDocumentBackupContext, cancellation: CancellationToken ): Promise { - const model = await this.loadModel(document.uri); + const model = await this.loadModel({ file: document.uri }); const id = this.storage.generateBackupId(model); await this.storage.backup(model, cancellation, id); return { @@ -167,7 +172,7 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit public async resolveCustomDocument(document: CustomDocument): Promise { this.customDocuments.set(document.uri.fsPath, document); - await this.loadModel(document.uri); + await this.loadModel({ file: document.uri }); } public async open(file: Uri): Promise { @@ -199,30 +204,26 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit } @captureTelemetry(Telemetry.CreateNewNotebook, undefined, false) - public async createNew(contents?: string, title?: string): Promise { + public async createNew(possibleContents?: string, title?: string): Promise { // Create a new URI for the dummy file using our root workspace path const uri = this.getNextNewNotebookUri(title); // Set these contents into the storage before the file opens. Make sure not // load from the memento storage though as this is an entirely brand new file. - await this.loadModel(uri, contents, true); + await this.loadModel({ file: uri, possibleContents, skipLoadingDirtyContents: true }); return this.open(uri); } - public async loadModel(file: Uri, contents?: string, skipDirtyContents?: boolean): Promise; - // tslint:disable-next-line: unified-signatures - public async loadModel(file: Uri, contents?: string, backupId?: string): Promise; - // tslint:disable-next-line: no-any - public async loadModel(file: Uri, contents?: string, options?: any): Promise { + public async loadModel(options: IModelLoadOptions): Promise { // Get the model that may match this file - let model = [...this.models.values()].find((m) => this.fs.arePathsSame(m.file, file)); + let model = [...this.models.values()].find((m) => this.fs.arePathsSame(m.file, options.file)); if (!model) { // Every time we load a new untitled file, up the counter past the max value for this counter - this.untitledCounter = getNextUntitledCounter(file, this.untitledCounter); + this.untitledCounter = getNextUntitledCounter(options.file, this.untitledCounter); // Load our model from our storage object. - model = await this.storage.getOrCreateModel(file, contents, options); + model = await this.storage.getOrCreateModel(options); // Make sure to listen to events on the model this.trackModel(model); @@ -273,7 +274,7 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit protected async loadNotebookEditor(resource: Uri, panel?: WebviewPanel) { try { // Get the model - const model = await this.loadModel(resource); + const model = await this.loadModel({ file: resource }); // Load it (should already be visible) return this.createNotebookEditor(model, panel); diff --git a/src/client/datascience/notebookStorage/nativeEditorStorage.ts b/src/client/datascience/notebookStorage/nativeEditorStorage.ts index 0ca96a90bf9e..f3e71c597de0 100644 --- a/src/client/datascience/notebookStorage/nativeEditorStorage.ts +++ b/src/client/datascience/notebookStorage/nativeEditorStorage.ts @@ -17,6 +17,7 @@ import { CellState, IDataScienceFileSystem, IJupyterExecution, + IModelLoadOptions, INotebookModel, INotebookStorage, ITrustService @@ -75,27 +76,8 @@ export class NativeEditorStorage implements INotebookStorage { public get(_file: Uri): INotebookModel | undefined { return undefined; } - public getOrCreateModel( - file: Uri, - possibleContents?: string, - backupId?: string, - forVSCodeNotebook?: boolean - ): Promise; - public getOrCreateModel( - file: Uri, - possibleContents?: string, - // tslint:disable-next-line: unified-signatures - skipDirtyContents?: boolean, - forVSCodeNotebook?: boolean - ): Promise; - public getOrCreateModel( - file: Uri, - possibleContents?: string, - // tslint:disable-next-line: no-any - options?: any, - forVSCodeNotebook?: boolean - ): Promise { - return this.loadFromFile(file, possibleContents, options, forVSCodeNotebook); + public getOrCreateModel(options: IModelLoadOptions): Promise { + return this.loadFromFile(options); } public async save(model: INotebookModel, _cancellation: CancellationToken): Promise { const contents = model.getContent(); @@ -154,8 +136,8 @@ export class NativeEditorStorage implements INotebookStorage { } public async revert(model: INotebookModel, _cancellation: CancellationToken): Promise { - // Revert to what is in the hot exit file - await this.loadFromFile(model.file); + // Revert to what is in the real file. This is only used for the custom editor + await this.loadFromFile({ file: model.file, skipLoadingDirtyContents: true }); } public async deleteBackup(model: INotebookModel, backupId: string): Promise { @@ -253,54 +235,44 @@ export class NativeEditorStorage implements INotebookStorage { noop(); } } - private loadFromFile( - file: Uri, - possibleContents?: string, - backupId?: string, - forVSCodeNotebook?: boolean - ): Promise; - private loadFromFile( - file: Uri, - possibleContents?: string, - // tslint:disable-next-line: unified-signatures - skipDirtyContents?: boolean, - forVSCodeNotebook?: boolean - ): Promise; - private async loadFromFile( - file: Uri, - possibleContents?: string, - options?: boolean | string, - forVSCodeNotebook?: boolean - ): Promise { + private async loadFromFile(options: IModelLoadOptions): Promise { try { // Attempt to read the contents if a viable file - const contents = NativeEditorStorage.isUntitledFile(file) ? possibleContents : await this.fs.readFile(file); + const contents = NativeEditorStorage.isUntitledFile(options.file) + ? options.possibleContents + : await this.fs.readFile(options.file); - const skipDirtyContents = typeof options === 'boolean' ? options : !!options; - // Use backupId provided, else use static storage key. - const backupId = - typeof options === 'string' ? options : skipDirtyContents ? undefined : this.getStaticStorageKey(file); + // Get backup id from the options if available. + const backupId = options.backupId ? options.backupId : this.getStaticStorageKey(options.file); // If skipping dirty contents, delete the dirty hot exit file now - if (skipDirtyContents) { - await this.clearHotExit(file, backupId); + if (options.skipLoadingDirtyContents) { + await this.clearHotExit(options.file, backupId); } // See if this file was stored in storage prior to shutdown - const dirtyContents = skipDirtyContents ? undefined : await this.getStoredContents(file, backupId); + const dirtyContents = options.skipLoadingDirtyContents + ? undefined + : await this.getStoredContents(options.file, backupId); if (dirtyContents) { // This means we're dirty. Indicate dirty and load from this content - return this.loadContents(file, dirtyContents, true, forVSCodeNotebook); + return this.loadContents(options.file, dirtyContents, true, options.isNative); } else { // Load without setting dirty - return this.loadContents(file, contents, undefined, forVSCodeNotebook); + return this.loadContents(options.file, contents, undefined, options.isNative); } } catch (ex) { // May not exist at this time. Should always have a single cell though - traceError(`Failed to load notebook file ${file.toString()}`, ex); + traceError(`Failed to load notebook file ${options.file.toString()}`, ex); return this.factory.createModel( - { trusted: true, file, cells: [], crypto: this.crypto, globalMemento: this.globalStorage }, - forVSCodeNotebook + { + trusted: true, + file: options.file, + cells: [], + crypto: this.crypto, + globalMemento: this.globalStorage + }, + options.isNative ); } } diff --git a/src/client/datascience/notebookStorage/notebookStorageProvider.ts b/src/client/datascience/notebookStorage/notebookStorageProvider.ts index 4067751614cc..48e28f048c89 100644 --- a/src/client/datascience/notebookStorage/notebookStorageProvider.ts +++ b/src/client/datascience/notebookStorage/notebookStorageProvider.ts @@ -9,7 +9,7 @@ import { CancellationToken } from 'vscode-jsonrpc'; import { IWorkspaceService } from '../../common/application/types'; import { IDisposable, IDisposableRegistry } from '../../common/types'; import { generateNewNotebookUri } from '../common'; -import { INotebookModel, INotebookStorage } from '../types'; +import { IModelLoadOptions, INotebookModel, INotebookStorage } from '../types'; import { getNextUntitledCounter } from './nativeEditorStorage'; import { VSCodeNotebookModel } from './vscNotebookModel'; @@ -66,35 +66,15 @@ export class NotebookStorageProvider implements INotebookStorageProvider { return this.resolvedStorageAndModels.get(file.toString()); } - public getOrCreateModel( - file: Uri, - contents?: string, - backupId?: string, - forVSCodeNotebook?: boolean - ): Promise; - public getOrCreateModel( - file: Uri, - contents?: string, - // tslint:disable-next-line: unified-signatures - skipDirtyContents?: boolean, - forVSCodeNotebook?: boolean - ): Promise; - - public getOrCreateModel( - file: Uri, - contents?: string, - // tslint:disable-next-line: no-any - options?: any, - forVSCodeNotebook?: boolean - ): Promise { - const key = file.toString(); + public getOrCreateModel(options: IModelLoadOptions): Promise { + const key = options.file.toString(); if (!this.storageAndModels.has(key)) { // Every time we load a new untitled file, up the counter past the max value for this counter NotebookStorageProvider.untitledCounter = getNextUntitledCounter( - file, + options.file, NotebookStorageProvider.untitledCounter ); - const promise = this.storage.getOrCreateModel(file, contents, options, forVSCodeNotebook); + const promise = this.storage.getOrCreateModel(options); this.storageAndModels.set(key, promise.then(this.trackModel.bind(this))); } return this.storageAndModels.get(key)!; @@ -105,12 +85,12 @@ export class NotebookStorageProvider implements INotebookStorageProvider { } } - public async createNew(contents?: string, forVSCodeNotebooks?: boolean): Promise { + public async createNew(possibleContents?: string, forVSCodeNotebooks?: boolean): Promise { // Create a new URI for the dummy file using our root workspace path const uri = this.getNextNewNotebookUri(forVSCodeNotebooks); // Always skip loading from the hot exit file. When creating a new file we want a new file. - return this.getOrCreateModel(uri, contents, true, forVSCodeNotebooks); + return this.getOrCreateModel({ file: uri, possibleContents, skipLoadingDirtyContents: true }); } private getNextNewNotebookUri(forVSCodeNotebooks?: boolean): Uri { diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index 3a8b406a24ec..b651af2b7750 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -1106,6 +1106,14 @@ export interface INotebookModel { trust(): void; } +export interface IModelLoadOptions { + isNative?: boolean; + file: Uri; + possibleContents?: string; + backupId?: string; + skipLoadingDirtyContents?: boolean; +} + export const INotebookStorage = Symbol('INotebookStorage'); export interface INotebookStorage { @@ -1114,19 +1122,7 @@ export interface INotebookStorage { saveAs(model: INotebookModel, targetResource: Uri): Promise; backup(model: INotebookModel, cancellation: CancellationToken, backupId?: string): Promise; get(file: Uri): INotebookModel | undefined; - getOrCreateModel( - file: Uri, - contents?: string, - backupId?: string, - forVSCodeNotebook?: boolean - ): Promise; - getOrCreateModel( - file: Uri, - contents?: string, - // tslint:disable-next-line: unified-signatures - skipDirtyContents?: boolean, - forVSCodeNotebook?: boolean - ): Promise; + getOrCreateModel(options: IModelLoadOptions): Promise; revert(model: INotebookModel, cancellation: CancellationToken): Promise; deleteBackup(model: INotebookModel, backupId?: string): Promise; } diff --git a/src/test/datascience/export/exportUtil.test.ts b/src/test/datascience/export/exportUtil.test.ts index c59d133f3feb..1e80292e1829 100644 --- a/src/test/datascience/export/exportUtil.test.ts +++ b/src/test/datascience/export/exportUtil.test.ts @@ -37,7 +37,7 @@ suite('DataScience - Export Util', () => { ); await exportUtil.removeSvgs(file); - const model = await notebookStorage.getOrCreateModel(file); + const model = await notebookStorage.getOrCreateModel({ file }); // make sure no svg exists in model const SVG = 'image/svg+xml'; diff --git a/src/test/datascience/interactive-ipynb/nativeEditorStorage.unit.test.ts b/src/test/datascience/interactive-ipynb/nativeEditorStorage.unit.test.ts index ad5093ca7361..103d2d40b8ca 100644 --- a/src/test/datascience/interactive-ipynb/nativeEditorStorage.unit.test.ts +++ b/src/test/datascience/interactive-ipynb/nativeEditorStorage.unit.test.ts @@ -425,7 +425,7 @@ suite('DataScience - Native Editor Storage', () => { } test('Create new editor and add some cells', async () => { - model = await storage.getOrCreateModel(baseUri); + model = await storage.getOrCreateModel({ file: baseUri }); insertCell(0, '1'); const cells = model.cells; expect(cells).to.be.lengthOf(4); @@ -434,7 +434,7 @@ suite('DataScience - Native Editor Storage', () => { }); test('Move cells around', async () => { - model = await storage.getOrCreateModel(baseUri); + model = await storage.getOrCreateModel({ file: baseUri }); swapCells('NotebookImport#0', 'NotebookImport#1'); const cells = model.cells; expect(cells).to.be.lengthOf(3); @@ -443,7 +443,7 @@ suite('DataScience - Native Editor Storage', () => { }); test('Edit/delete cells', async () => { - model = await storage.getOrCreateModel(baseUri); + model = await storage.getOrCreateModel({ file: baseUri }); expect(model.isDirty).to.be.equal(false, 'Editor should not be dirty'); editCell( [ @@ -483,7 +483,7 @@ suite('DataScience - Native Editor Storage', () => { test('Editing a file and closing will keep contents', async () => { await filesConfig?.update('autoSave', 'off'); - model = await storage.getOrCreateModel(baseUri); + model = await storage.getOrCreateModel({ file: baseUri }); expect(model.isDirty).to.be.equal(false, 'Editor should not be dirty'); editCell( [ @@ -512,7 +512,7 @@ suite('DataScience - Native Editor Storage', () => { // Recreate storage = createStorage(); - model = await storage.getOrCreateModel(baseUri); + model = await storage.getOrCreateModel({ file: baseUri }); const cells = model.cells; expect(cells).to.be.lengthOf(3); @@ -522,7 +522,7 @@ suite('DataScience - Native Editor Storage', () => { }); test('Editing a new file and closing will keep contents', async () => { - model = await storage.getOrCreateModel(untiledUri, undefined, true); + model = await storage.getOrCreateModel({ file: untiledUri, skipLoadingDirtyContents: true }); expect(model.isDirty).to.be.equal(false, 'Editor should not be dirty'); insertCell(0, 'a=1'); @@ -531,7 +531,7 @@ suite('DataScience - Native Editor Storage', () => { // Recreate storage = createStorage(); - model = await storage.getOrCreateModel(untiledUri); + model = await storage.getOrCreateModel({ file: untiledUri }); const cells = model.cells; expect(cells).to.be.lengthOf(2); @@ -550,7 +550,7 @@ suite('DataScience - Native Editor Storage', () => { // Put the regular file into the local storage await localMemento.update(`notebook-storage-${file.toString()}`, differentFile); - model = await storage.getOrCreateModel(file); + model = await storage.getOrCreateModel({ file }); // It should load with that value const cells = model.cells; @@ -571,7 +571,7 @@ suite('DataScience - Native Editor Storage', () => { contents: differentFile, lastModifiedTimeMs: Date.now() }); - model = await storage.getOrCreateModel(file); + model = await storage.getOrCreateModel({ file }); // It should load with that value const cells = model.cells; @@ -601,7 +601,7 @@ suite('DataScience - Native Editor Storage', () => { lastModifiedTimeMs: Date.now() }); - model = await storage.getOrCreateModel(file); + model = await storage.getOrCreateModel({ file }); // It should load with that value const cells = model.cells; diff --git a/src/test/datascience/mockCustomEditorService.ts b/src/test/datascience/mockCustomEditorService.ts index 03ceee2b8c25..fa59c744d1b0 100644 --- a/src/test/datascience/mockCustomEditorService.ts +++ b/src/test/datascience/mockCustomEditorService.ts @@ -138,7 +138,7 @@ export class MockCustomEditorService implements ICustomEditorService { private async getModel(file: Uri): Promise { const nativeProvider = this.provider as NativeEditorProvider; if (nativeProvider) { - return (nativeProvider.loadModel(file) as unknown) as Promise; + return (nativeProvider.loadModel({ file }) as unknown) as Promise; } return undefined; } diff --git a/src/test/datascience/notebook/contentProvider.ds.test.ts b/src/test/datascience/notebook/contentProvider.ds.test.ts index d2a64d11ef61..fa283388337f 100644 --- a/src/test/datascience/notebook/contentProvider.ds.test.ts +++ b/src/test/datascience/notebook/contentProvider.ds.test.ts @@ -60,7 +60,7 @@ suite('DataScience - VSCode Notebook - (Open)', function () { 'notebook', 'testJsonContents.ipynb' ); - const model = await storageProvider.getOrCreateModel(Uri.file(file)); + const model = await storageProvider.getOrCreateModel({ file: Uri.file(file) }); disposables.push(model); model.trust(); const jsonStr = fs.readFileSync(file, { encoding: 'utf8' }); diff --git a/src/test/datascience/notebook/contentProvider.unit.test.ts b/src/test/datascience/notebook/contentProvider.unit.test.ts index 39fd8e5b3464..5ccae9026d79 100644 --- a/src/test/datascience/notebook/contentProvider.unit.test.ts +++ b/src/test/datascience/notebook/contentProvider.unit.test.ts @@ -55,9 +55,7 @@ suite('DataScience - NativeNotebook ContentProvider', () => { ] } ); - when(storageProvider.getOrCreateModel(anything(), anything(), anything(), anything())).thenResolve( - model - ); + when(storageProvider.getOrCreateModel(anything())).thenResolve(model); const notebook = await contentProvider.openNotebook(fileUri, {}); @@ -135,9 +133,7 @@ suite('DataScience - NativeNotebook ContentProvider', () => { ] } ); - when(storageProvider.getOrCreateModel(anything(), anything(), anything(), anything())).thenResolve( - model - ); + when(storageProvider.getOrCreateModel(anything())).thenResolve(model); const notebook = await contentProvider.openNotebook(fileUri, {}); From 6cb43d5eba89da9112a52f2b744583a53b361585 Mon Sep 17 00:00:00 2001 From: David Kutugata Date: Fri, 18 Sep 2020 13:05:45 -0700 Subject: [PATCH 04/15] add jedi-language-server to 3rd party notices (#13977) * add jedi-language-server to 3rd party notices * move license from distribution to repository file --- ThirdPartyNotices-Distribution.txt | 1 - ThirdPartyNotices-Repository.txt | 30 +++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/ThirdPartyNotices-Distribution.txt b/ThirdPartyNotices-Distribution.txt index 2e62e34b8564..06d2e73f3084 100644 --- a/ThirdPartyNotices-Distribution.txt +++ b/ThirdPartyNotices-Distribution.txt @@ -15141,4 +15141,3 @@ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. You just DO WHAT THE FUCK YOU WANT TO. --------------------------------------------------------- - diff --git a/ThirdPartyNotices-Repository.txt b/ThirdPartyNotices-Repository.txt index 1162db48bc9c..98c64072ca51 100644 --- a/ThirdPartyNotices-Repository.txt +++ b/ThirdPartyNotices-Repository.txt @@ -1167,4 +1167,32 @@ trademarks does not indicate endorsement of the trademark holder by Font Awesome, nor vice versa. **Please do not use brand logos for any purpose except to represent the company, product, or service to which they refer.** ========================================= -END OF font-awesome NOTICES, INFORMATION, AND LICENSE \ No newline at end of file +END OF font-awesome NOTICES, INFORMATION, AND LICENSE + +%% jedi-language-server NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +jedi-language-server + +MIT License + +Copyright (c) 2019 Sam Roeca + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +========================================= +END OF jedi-language-server NOTICES, INFORMATION, AND LICENSE \ No newline at end of file From 832025ac4011678c88ef9ba9397de607780d6f52 Mon Sep 17 00:00:00 2001 From: David Kutugata Date: Tue, 22 Sep 2020 10:34:09 -0700 Subject: [PATCH 05/15] disable test_discover_complex_default and (#14024) test_discover_complex_doctest --- pythonFiles/tests/testing_tools/adapter/test_functional.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pythonFiles/tests/testing_tools/adapter/test_functional.py b/pythonFiles/tests/testing_tools/adapter/test_functional.py index bd6c6b200314..86ca7275fd56 100644 --- a/pythonFiles/tests/testing_tools/adapter/test_functional.py +++ b/pythonFiles/tests/testing_tools/adapter/test_functional.py @@ -141,6 +141,7 @@ def test_discover_simple(self): ], ) + @pytest.mark.skip(reason="https://github.com/microsoft/vscode-python/issues/14023") def test_discover_complex_default(self): projroot, testroot = resolve_testroot("complex") expected = self.complex(projroot) @@ -161,6 +162,7 @@ def test_discover_complex_default(self): self.maxDiff = None self.assertEqual(result, expected) + @pytest.mark.skip(reason="https://github.com/microsoft/vscode-python/issues/14023") def test_discover_complex_doctest(self): projroot, _ = resolve_testroot("complex") expected = self.complex(projroot) From 074024c92852b06eaa20cc702ac384cdec3a2c37 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 23 Sep 2020 01:59:22 +0530 Subject: [PATCH 06/15] Upgrade isort to 5.5.3 (#14035) (#14037) --- news/1 Enhancements/14027.md | 1 + requirements.in | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 news/1 Enhancements/14027.md diff --git a/news/1 Enhancements/14027.md b/news/1 Enhancements/14027.md new file mode 100644 index 000000000000..810963c7b177 --- /dev/null +++ b/news/1 Enhancements/14027.md @@ -0,0 +1 @@ +Upgraded to isort `5.5.3`. diff --git a/requirements.in b/requirements.in index b2d0327ce838..f8f1eaf05370 100644 --- a/requirements.in +++ b/requirements.in @@ -9,6 +9,6 @@ git+git://github.com/vscode-python/jedi-language-server@42823a2598d4b6369e9273c5ad237a48c5d67553; python_version >= '3.6' pygls==0.9.0; python_version >= '3.6' # Sort Imports -isort==5.5.2; python_version >= '3.6' +isort==5.5.3; python_version >= '3.6' # DS Python daemon python-jsonrpc-server==0.2.0 diff --git a/requirements.txt b/requirements.txt index c309b6ab206b..3a7efb00e04e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ # click==7.1.2 # via jedi-language-server future==0.18.2 # via python-jsonrpc-server -isort==5.5.2 ; python_version >= "3.6" # via -r requirements.in +isort==5.5.3 ; python_version >= "3.6" # via -r requirements.in git+git://github.com/vscode-python/jedi-language-server@42823a2598d4b6369e9273c5ad237a48c5d67553 ; python_version >= "3.6" # via -r requirements.in jedi==0.17.2 # via jedi-language-server parso==0.7.0 # via jedi From c1258f025c23556eb35423e51366db2e2ff68e6a Mon Sep 17 00:00:00 2001 From: David Kutugata Date: Tue, 22 Sep 2020 16:58:04 -0700 Subject: [PATCH 07/15] prepare release (#14042) --- CHANGELOG.md | 6 +- ThirdPartyNotices-Distribution.txt | 133 +++++++++++++++-------------- news/1 Enhancements/14027.md | 1 - package-lock.json | 2 +- package.json | 2 +- 5 files changed, 73 insertions(+), 71 deletions(-) delete mode 100644 news/1 Enhancements/14027.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 38b5f71f1330..826423317b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2020.9.0 (14 September 2020) +## 2020.9.0 (22 September 2020) ### Enhancements @@ -19,6 +19,8 @@ ([#13831](https://github.com/Microsoft/vscode-python/issues/13831)) 1. Enable custom editor support in stable VS code at 20%. ([#13890](https://github.com/Microsoft/vscode-python/issues/13890)) +1. Upgraded to isort `5.5.3`. + ([#14027](https://github.com/Microsoft/vscode-python/issues/14027)) ### Fixes @@ -60,7 +62,7 @@ 1. Correctly install ipykernel when launching from an interpreter. ([#13956](https://github.com/Microsoft/vscode-python/issues/13956)) 1. Backup on custom editors is being ignored. - ([#13981](https://github.com/Microsoft/vscode-python/issues/13981)) + ([#13981](https://github.com/Microsoft/vscode-python/issues/13981)) ### Code Health diff --git a/ThirdPartyNotices-Distribution.txt b/ThirdPartyNotices-Distribution.txt index 06d2e73f3084..ddb4327c18dd 100644 --- a/ThirdPartyNotices-Distribution.txt +++ b/ThirdPartyNotices-Distribution.txt @@ -3265,12 +3265,13 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND --------------------------------------------------------- -estraverse 1.5.1 - BSD-2-Clause -https://github.com/Constellation/estraverse +estraverse 4.3.0 - BSD-2-Clause +https://github.com/estools/estraverse +Copyright (c) 2014 Yusuke Suzuki Copyright (c) 2012 Ariya Hidayat Copyright (c) 2012-2013 Yusuke Suzuki -Copyright (c) 2012-2013 Yusuke Suzuki (http://github.com/Constellation) +Copyright (c) 2012-2016 Yusuke Suzuki (http://github.com/Constellation) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -3297,13 +3298,12 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -estraverse 4.3.0 - BSD-2-Clause -https://github.com/estools/estraverse +estraverse 1.5.1 - BSD-2-Clause +https://github.com/Constellation/estraverse -Copyright (c) 2014 Yusuke Suzuki Copyright (c) 2012 Ariya Hidayat Copyright (c) 2012-2013 Yusuke Suzuki -Copyright (c) 2012-2016 Yusuke Suzuki (http://github.com/Constellation) +Copyright (c) 2012-2013 Yusuke Suzuki (http://github.com/Constellation) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -4436,7 +4436,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -hoist-non-react-statics 3.3.0 - BSD-3-Clause +hoist-non-react-statics 3.3.1 - BSD-3-Clause https://github.com/mridgway/hoist-non-react-statics#readme Copyright 2015, Yahoo! Inc. @@ -4477,7 +4477,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -hoist-non-react-statics 3.3.1 - BSD-3-Clause +hoist-non-react-statics 3.3.0 - BSD-3-Clause https://github.com/mridgway/hoist-non-react-statics#readme Copyright 2015, Yahoo! Inc. @@ -4597,7 +4597,7 @@ The complete list of contributors can be found at: https://github.com/hapijs/qs/ --------------------------------------------------------- -source-map 0.5.7 - BSD-3-Clause +source-map 0.6.1 - BSD-3-Clause https://github.com/mozilla/source-map Copyright 2011 The Closure Compiler @@ -4640,7 +4640,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -source-map 0.6.1 - BSD-3-Clause +source-map 0.5.7 - BSD-3-Clause https://github.com/mozilla/source-map Copyright 2011 The Closure Compiler @@ -5502,8 +5502,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. --------------------------------------------------------- -@babel/runtime 7.8.3 - MIT -https://babeljs.io/docs/en/next/babel-runtime +@babel/runtime 7.5.4 - MIT + Copyright (c) 2014-present Sebastian McKenzie and other contributors @@ -5535,8 +5535,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -@babel/runtime 7.5.4 - MIT - +@babel/runtime 7.8.3 - MIT +https://babeljs.io/docs/en/next/babel-runtime Copyright (c) 2014-present Sebastian McKenzie and other contributors @@ -7420,7 +7420,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -date-format 3.0.0 - MIT +date-format 2.1.0 - MIT https://github.com/nomiddlename/date-format#readme Copyright (c) 2013 Gareth Jones @@ -7451,7 +7451,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -date-format 2.1.0 - MIT +date-format 3.0.0 - MIT https://github.com/nomiddlename/date-format#readme Copyright (c) 2013 Gareth Jones @@ -7482,7 +7482,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -debug 4.1.1 - MIT +debug 3.1.0 - MIT https://github.com/visionmedia/debug#readme Copyright (c) 2014 TJ Holowaychuk @@ -7575,7 +7575,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -debug 3.1.0 - MIT +debug 4.1.1 - MIT https://github.com/visionmedia/debug#readme Copyright (c) 2014 TJ Holowaychuk @@ -8928,7 +8928,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -got 8.3.2 - MIT +got 9.6.0 - MIT https://github.com/sindresorhus/got#readme Copyright (c) Sindre Sorhus (sindresorhus.com) @@ -8948,7 +8948,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -got 9.6.0 - MIT +got 8.3.2 - MIT https://github.com/sindresorhus/got#readme Copyright (c) Sindre Sorhus (sindresorhus.com) @@ -9431,7 +9431,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -isarray 1.0.0 - MIT +isarray 0.0.1 - MIT https://github.com/juliangruber/isarray Copyright (c) 2013 Julian Gruber @@ -9450,7 +9450,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -isarray 0.0.1 - MIT +isarray 1.0.0 - MIT https://github.com/juliangruber/isarray Copyright (c) 2013 Julian Gruber @@ -9638,7 +9638,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -isort 5.5.2 - MIT +isort 5.5.3 - MIT Copyright 2017 Jack Evans @@ -11173,10 +11173,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -normalize-url 2.0.1 - MIT +normalize-url 4.5.0 - MIT https://github.com/sindresorhus/normalize-url#readme -(c) Sindre Sorhus (https://sindresorhus.com) Copyright (c) Sindre Sorhus (sindresorhus.com) MIT License @@ -11194,9 +11193,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -normalize-url 4.5.0 - MIT +normalize-url 2.0.1 - MIT https://github.com/sindresorhus/normalize-url#readme +(c) Sindre Sorhus (https://sindresorhus.com) Copyright (c) Sindre Sorhus (sindresorhus.com) MIT License @@ -11558,7 +11558,7 @@ SOFTWARE. --------------------------------------------------------- -p-cancelable 0.4.1 - MIT +p-cancelable 1.1.0 - MIT https://github.com/sindresorhus/p-cancelable#readme (c) Sindre Sorhus (https://sindresorhus.com) @@ -11579,7 +11579,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -p-cancelable 1.1.0 - MIT +p-cancelable 0.4.1 - MIT https://github.com/sindresorhus/p-cancelable#readme (c) Sindre Sorhus (https://sindresorhus.com) @@ -11931,7 +11931,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -process-nextick-args 2.0.1 - MIT +process-nextick-args 1.0.7 - MIT https://github.com/calvinmetcalf/process-nextick-args Copyright (c) 2015 Calvin Metcalf @@ -11961,7 +11961,7 @@ SOFTWARE.** --------------------------------------------------------- -process-nextick-args 1.0.7 - MIT +process-nextick-args 2.0.1 - MIT https://github.com/calvinmetcalf/process-nextick-args Copyright (c) 2015 Calvin Metcalf @@ -12128,7 +12128,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -pump 3.0.0 - MIT +pump 2.0.1 - MIT https://github.com/mafintosh/pump#readme Copyright (c) 2014 Mathias Buus @@ -12159,7 +12159,7 @@ THE SOFTWARE. --------------------------------------------------------- -pump 2.0.1 - MIT +pump 3.0.0 - MIT https://github.com/mafintosh/pump#readme Copyright (c) 2014 Mathias Buus @@ -12429,7 +12429,7 @@ IN THE SOFTWARE. --------------------------------------------------------- -readable-stream 2.3.7 - MIT +readable-stream 2.3.6 - MIT https://github.com/nodejs/readable-stream#readme Copyright Joyent, Inc. and other Node contributors. @@ -12487,7 +12487,7 @@ IN THE SOFTWARE. --------------------------------------------------------- -readable-stream 2.3.6 - MIT +readable-stream 2.3.7 - MIT https://github.com/nodejs/readable-stream#readme Copyright Joyent, Inc. and other Node contributors. @@ -12703,8 +12703,8 @@ SOFTWARE. --------------------------------------------------------- -resolve 1.1.7 - MIT -https://github.com/substack/node-resolve#readme +resolve 1.11.1 - MIT +https://github.com/browserify/resolve#readme This software is released under the MIT license: @@ -12731,8 +12731,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -resolve 1.11.1 - MIT -https://github.com/browserify/resolve#readme +resolve 1.1.7 - MIT +https://github.com/substack/node-resolve#readme This software is released under the MIT license: @@ -13134,7 +13134,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -static-module 2.2.5 - MIT +static-module 3.0.3 - MIT https://github.com/substack/static-module @@ -13162,7 +13162,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -static-module 3.0.3 - MIT +static-module 2.2.5 - MIT https://github.com/substack/static-module @@ -14067,7 +14067,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -unicode-trie 2.0.0 - MIT +unicode-trie 1.0.0 - MIT https://github.com/devongovett/unicode-trie Copyright 2018 @@ -14086,7 +14086,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -unicode-trie 1.0.0 - MIT +unicode-trie 2.0.0 - MIT https://github.com/devongovett/unicode-trie Copyright 2018 @@ -14320,6 +14320,27 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--------------------------------------------------------- + +--------------------------------------------------------- + +uuid 8.2.0 - MIT +https://github.com/uuidjs/uuid#readme + +Copyright 2011, Sebastian Tschan https://blueimp.net +Copyright (c) Paul Johnston 1999 - 2009 Other contributors Greg Holt, Andrew Kepert, Ydnar, Lostinet + +The MIT License (MIT) + +Copyright (c) 2010-2020 Robert Kieffer and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------- --------------------------------------------------------- @@ -14354,27 +14375,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------------------------------------------------- - ---------------------------------------------------------- - -uuid 8.2.0 - MIT -https://github.com/uuidjs/uuid#readme - -Copyright 2011, Sebastian Tschan https://blueimp.net -Copyright (c) Paul Johnston 1999 - 2009 Other contributors Greg Holt, Andrew Kepert, Ydnar, Lostinet - -The MIT License (MIT) - -Copyright (c) 2010-2020 Robert Kieffer and other contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - --------------------------------------------------------- --------------------------------------------------------- @@ -14825,7 +14825,7 @@ SOFTWARE. --------------------------------------------------------- -xml2js 0.4.19 - MIT +xml2js 0.2.8 - MIT https://github.com/Leonidas-from-XIV/node-xml2js Copyright 2010, 2011, 2012, 2013. @@ -14855,7 +14855,7 @@ IN THE SOFTWARE. --------------------------------------------------------- -xml2js 0.2.8 - MIT +xml2js 0.4.19 - MIT https://github.com/Leonidas-from-XIV/node-xml2js Copyright 2010, 2011, 2012, 2013. @@ -15141,3 +15141,4 @@ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. You just DO WHAT THE FUCK YOU WANT TO. --------------------------------------------------------- + diff --git a/news/1 Enhancements/14027.md b/news/1 Enhancements/14027.md deleted file mode 100644 index 810963c7b177..000000000000 --- a/news/1 Enhancements/14027.md +++ /dev/null @@ -1 +0,0 @@ -Upgraded to isort `5.5.3`. diff --git a/package-lock.json b/package-lock.json index 3c3d66258cc9..02c8932d6c64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2020.9.0-rc", + "version": "2020.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a91c5a5be59d..b34f0ee31e6b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, Jupyter Notebooks, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2020.9.0-rc", + "version": "2020.9.0", "featureFlags": { "usingNewInterpreterStorage": true }, From 0b41a8631fbd7eaf5024d3d0ed31185ee5edc69b Mon Sep 17 00:00:00 2001 From: David Kutugata Date: Tue, 22 Sep 2020 20:54:44 -0700 Subject: [PATCH 08/15] fixed annoying warnings (#14049) --- CHANGELOG.md | 2 +- package.nls.json | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 826423317b57..dc0ec9e5af07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2020.9.0 (22 September 2020) +## 2020.9.0 (23 September 2020) ### Enhancements diff --git a/package.nls.json b/package.nls.json index 16a537b3a669..b1b7007f43d8 100644 --- a/package.nls.json +++ b/package.nls.json @@ -59,6 +59,8 @@ "python.command.python.datascience.debugFileInteractive.title": "Debug Current File in Python Interactive Window", "python.command.python.datascience.runallcells.title": "Run All Cells", "python.command.python.datascience.notebookeditor.runallcells.title": "Run All Notebook Cells", + "python.command.python.datascience.notebookeditor.expandallcells.title": "Expand All Notebook Cells", + "python.command.python.datascience.notebookeditor.collapseallcells.title": "Collapse All Notebook Cells", "python.command.python.datascience.runallcellsabove.title": "Run Above", "python.command.python.datascience.runcellandallbelow.title": "Run Below", "python.command.python.datascience.runallcellsabove.palette.title": "Run Cells Above Current Cell", From 8d337a7b785e191f3a3a34e00cf178d9ab61e8f1 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Mon, 28 Sep 2020 02:40:00 -0700 Subject: [PATCH 09/15] Cherry pick to address path issues. (#14125) * Do not quote isolated in exec module (#14108) * Do not quote isolated in exec module * Revert "Do not quote isolated in exec module" This reverts commit b9fa04c06be0876861017eff5ee032ca71acda3d. * Revert "IPyKernel install issue with windows paths (#13667)" This reverts commit 23725abd4249811f24475e215c2d78ed5e508e79. * Fix unit test broken by recent revert (#14122) --- src/client/common/process/internal/python.ts | 2 +- src/client/common/process/internal/scripts/index.ts | 2 +- src/test/common/installer/moduleInstaller.unit.test.ts | 2 +- src/test/common/moduleInstaller.test.ts | 2 +- src/test/common/process/pythonProcess.unit.test.ts | 2 +- .../common/terminals/synchronousTerminalService.unit.test.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/common/process/internal/python.ts b/src/client/common/process/internal/python.ts index d553e54293c1..86123367f852 100644 --- a/src/client/common/process/internal/python.ts +++ b/src/client/common/process/internal/python.ts @@ -28,7 +28,7 @@ export function execCode(code: string, isolated = true): string[] { export function execModule(name: string, moduleArgs: string[], isolated = true): string[] { const args = ['-m', name, ...moduleArgs]; if (isolated) { - args[0] = ISOLATED.fileToCommandArgument(); + args[0] = ISOLATED; // replace } // "code" isn't specific enough to know how to parse it, // so we only return the args. diff --git a/src/client/common/process/internal/scripts/index.ts b/src/client/common/process/internal/scripts/index.ts index 74408e81e1e6..ec59c6f6a483 100644 --- a/src/client/common/process/internal/scripts/index.ts +++ b/src/client/common/process/internal/scripts/index.ts @@ -306,7 +306,7 @@ export function shell_exec(command: string, lockfile: string, shellArgs: string[ // We don't bother with a "parse" function since the output // could be anything. return [ - ISOLATED.fileToCommandArgument(), + ISOLATED, script, command.fileToCommandArgument(), // The shell args must come after the command diff --git a/src/test/common/installer/moduleInstaller.unit.test.ts b/src/test/common/installer/moduleInstaller.unit.test.ts index 93594c050e46..73ad8ded6dfa 100644 --- a/src/test/common/installer/moduleInstaller.unit.test.ts +++ b/src/test/common/installer/moduleInstaller.unit.test.ts @@ -47,7 +47,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; import { EnvironmentType, PythonEnvironment } from '../../../client/pythonEnvironments/info'; import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../constants'; -const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py').replace(/\\/g, '/'); +const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py'); /* Complex test to ensure we cover all combinations: We could have written separate tests for each installer, but we'd be replicate code. diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index a5bccb537869..abae5cd316a5 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -140,7 +140,7 @@ import { closeActiveWindows, initializeTest } from './../initialize'; chai_use(chaiAsPromised); -const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py').replace(/\\/g, '/'); +const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py'); const info: PythonEnvironment = { architecture: Architecture.Unknown, diff --git a/src/test/common/process/pythonProcess.unit.test.ts b/src/test/common/process/pythonProcess.unit.test.ts index cc0a847e7e7e..646ad8473c05 100644 --- a/src/test/common/process/pythonProcess.unit.test.ts +++ b/src/test/common/process/pythonProcess.unit.test.ts @@ -12,7 +12,7 @@ import { IProcessService, StdErrError } from '../../../client/common/process/typ import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../constants'; import { noop } from '../../core'; -const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py').replace(/\\/g, '/'); +const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py'); use(chaiAsPromised); diff --git a/src/test/common/terminals/synchronousTerminalService.unit.test.ts b/src/test/common/terminals/synchronousTerminalService.unit.test.ts index 75b0e27bb63a..082eeb1953ca 100644 --- a/src/test/common/terminals/synchronousTerminalService.unit.test.ts +++ b/src/test/common/terminals/synchronousTerminalService.unit.test.ts @@ -67,7 +67,7 @@ suite('Terminal Service (synchronous)', () => { }); }); suite('sendCommand', () => { - const isolated = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pyvsc-run-isolated.py').replace(/\\/g, '/'); + const isolated = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pyvsc-run-isolated.py'); const shellExecFile = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'shell_exec.py'); test('run sendCommand in terminalService if there is no cancellation token', async () => { From 5da34fcb80a71a7b3af9869906e0a432262c7130 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Mon, 28 Sep 2020 10:51:17 -0700 Subject: [PATCH 10/15] Port escape fix to release branch (#14133) * Fix HTML escaping to match what Jupyter does (#14038) * Basic idea * add some functional tests * Add news entry * Fix functional tests * Update changelog --- CHANGELOG.md | 61 +++++++++++++++++++ .../datascience/jupyter/jupyterNotebook.ts | 27 +++++--- .../datascience/jupyter/kernelVariables.ts | 6 +- .../jupyter/kernels/cellExecution.ts | 18 +++++- .../jupyter/oldJupyterVariables.ts | 6 +- .../interactive-common/cellOutput.tsx | 24 +++++--- src/test/datascience/Untitled-1.ipynb | 47 ++++++++++++++ .../interactiveWindow.functional.test.tsx | 34 +++++------ .../datascience/liveshare.functional.test.tsx | 28 ++++----- .../nativeEditor.functional.test.tsx | 18 +++--- .../datascience/notebook.functional.test.ts | 15 ++++- .../trustedNotebooks.functional.test.tsx | 12 ++-- .../uiTests/ipywidget.ui.functional.test.ts | 2 +- 13 files changed, 225 insertions(+), 73 deletions(-) create mode 100644 src/test/datascience/Untitled-1.ipynb diff --git a/CHANGELOG.md b/CHANGELOG.md index dc0ec9e5af07..4d212c9f9067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,66 @@ # Changelog +## 2020.9.1 (29 September 2020) + +### Fixes + +1. Fix escaping of output to encode HTML chars correctly. + ([#5678](https://github.com/Microsoft/vscode-python/issues/5678)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + ## 2020.9.0 (23 September 2020) ### Enhancements diff --git a/src/client/datascience/jupyter/jupyterNotebook.ts b/src/client/datascience/jupyter/jupyterNotebook.ts index de8886658b00..ab1a015065e3 100644 --- a/src/client/datascience/jupyter/jupyterNotebook.ts +++ b/src/client/datascience/jupyter/jupyterNotebook.ts @@ -40,6 +40,10 @@ import { KernelConnectionMetadata } from './kernels/types'; // tslint:disable-next-line: no-require-imports import cloneDeep = require('lodash/cloneDeep'); +// tslint:disable-next-line: no-require-imports +import escape = require('lodash/escape'); +// tslint:disable-next-line: no-require-imports +import unescape = require('lodash/unescape'); import { concatMultilineString, formatStreamText } from '../../../datascience-ui/common'; import { RefBool } from '../../common/refBool'; import { PythonEnvironment } from '../../pythonEnvironments/info'; @@ -783,12 +787,12 @@ export class JupyterNotebookBase implements INotebook { outputs.forEach((o) => { if (o.output_type === 'stream') { const stream = o as nbformat.IStream; - result = result.concat(formatStreamText(concatMultilineString(stream.text, true))); + result = result.concat(formatStreamText(unescape(concatMultilineString(stream.text, true)))); } else { const data = o.data; if (data && data.hasOwnProperty('text/plain')) { // tslint:disable-next-line:no-any - result = result.concat((data as any)['text/plain']); + result = result.concat(unescape((data as any)['text/plain'])); } } }); @@ -1233,7 +1237,7 @@ export class JupyterNotebookBase implements INotebook { ) { // Check our length on text output if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { - msg.content.data['text/plain'] = trimFunc(msg.content.data['text/plain'] as string); + msg.content.data['text/plain'] = escape(trimFunc(msg.content.data['text/plain'] as string)); } this.addToCellData( @@ -1262,7 +1266,7 @@ export class JupyterNotebookBase implements INotebook { if (o.data && o.data.hasOwnProperty('text/plain')) { // tslint:disable-next-line: no-any const str = (o.data as any)['text/plain'].toString(); - const data = trimFunc(str) as string; + const data = escape(trimFunc(str)) as string; this.addToCellData( cell, { @@ -1310,13 +1314,13 @@ export class JupyterNotebookBase implements INotebook { : undefined; if (existing) { // tslint:disable-next-line:restrict-plus-operands - existing.text = existing.text + msg.content.text; + existing.text = existing.text + escape(msg.content.text); const originalText = formatStreamText(concatMultilineString(existing.text)); originalTextLength = originalText.length; existing.text = trimFunc(originalText); trimmedTextLength = existing.text.length; } else { - const originalText = formatStreamText(concatMultilineString(msg.content.text)); + const originalText = formatStreamText(concatMultilineString(escape(msg.content.text))); originalTextLength = originalText.length; // Create a new stream entry const output: nbformat.IStream = { @@ -1346,6 +1350,11 @@ export class JupyterNotebookBase implements INotebook { } private handleDisplayData(msg: KernelMessage.IDisplayDataMsg, clearState: RefBool, cell: ICell) { + // Escape text output + if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { + msg.content.data['text/plain'] = escape(msg.content.data['text/plain'] as string); + } + const output: nbformat.IDisplayData = { output_type: 'display_data', data: msg.content.data, @@ -1393,9 +1402,9 @@ export class JupyterNotebookBase implements INotebook { private handleError(msg: KernelMessage.IErrorMsg, clearState: RefBool, cell: ICell) { const output: nbformat.IError = { output_type: 'error', - ename: msg.content.ename, - evalue: msg.content.evalue, - traceback: msg.content.traceback + ename: escape(msg.content.ename), + evalue: escape(msg.content.evalue), + traceback: msg.content.traceback.map(escape) }; this.addToCellData(cell, output, clearState); cell.state = CellState.error; diff --git a/src/client/datascience/jupyter/kernelVariables.ts b/src/client/datascience/jupyter/kernelVariables.ts index 7d3d1b004bd3..501b3f25ef17 100644 --- a/src/client/datascience/jupyter/kernelVariables.ts +++ b/src/client/datascience/jupyter/kernelVariables.ts @@ -6,6 +6,8 @@ import { inject, injectable } from 'inversify'; import stripAnsi from 'strip-ansi'; import * as uuid from 'uuid/v4'; +// tslint:disable-next-line: no-require-imports +import unescape = require('lodash/unescape'); import { CancellationToken, Event, EventEmitter, Uri } from 'vscode'; import { PYTHON_LANGUAGE } from '../../common/constants'; import { traceError } from '../../common/logger'; @@ -246,7 +248,7 @@ export class KernelVariables implements IJupyterVariables { // Pull our text result out of the Jupyter cell private deserializeJupyterResult(cells: ICell[]): T { - const text = this.extractJupyterResultText(cells); + const text = unescape(this.extractJupyterResultText(cells)); return JSON.parse(text) as T; } @@ -371,7 +373,7 @@ export class KernelVariables implements IJupyterVariables { // Now execute the query if (notebook && query) { const cells = await notebook.execute(query.query, Identifiers.EmptyFileName, 0, uuid(), token, true); - const text = this.extractJupyterResultText(cells); + const text = unescape(this.extractJupyterResultText(cells)); // Apply the expression to it const matches = this.getAllMatches(query.parser, text); diff --git a/src/client/datascience/jupyter/kernels/cellExecution.ts b/src/client/datascience/jupyter/kernels/cellExecution.ts index f951d3979927..a0be2d08c54e 100644 --- a/src/client/datascience/jupyter/kernels/cellExecution.ts +++ b/src/client/datascience/jupyter/kernels/cellExecution.ts @@ -35,6 +35,8 @@ import { import { IKernel } from './types'; // tslint:disable-next-line: no-var-requires no-require-imports const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed'); +// tslint:disable-next-line: no-require-imports +import escape = require('lodash/escape'); export class CellExecutionFactory { constructor( @@ -406,6 +408,11 @@ export class CellExecution { // See this for docs on the messages: // https://jupyter-client.readthedocs.io/en/latest/messaging.html#messaging-in-jupyter private handleExecuteResult(msg: KernelMessage.IExecuteResultMsg, clearState: RefBool) { + // Escape text output + if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { + msg.content.data['text/plain'] = escape(msg.content.data['text/plain'] as string); + } + this.addToCellData( { output_type: 'execute_result', @@ -429,7 +436,7 @@ export class CellExecution { // Mark as stream output so the text is formatted because it likely has ansi codes in it. output_type: 'stream', // tslint:disable-next-line: no-any - text: (o.data as any)['text/plain'].toString(), + text: escape((o.data as any)['text/plain'].toString()), metadata: {}, execution_count: reply.execution_count }, @@ -463,10 +470,10 @@ export class CellExecution { lastOutput && lastOutput.outputKind === CellOutputKind.Text ? lastOutput : undefined; if (existing) { // tslint:disable-next-line:restrict-plus-operands - existing.text = formatStreamText(concatMultilineString(existing.text + msg.content.text)); + existing.text = formatStreamText(concatMultilineString(existing.text + escape(msg.content.text))); this.cell.outputs = [...this.cell.outputs]; // This is necessary to get VS code to update (for now) } else { - const originalText = formatStreamText(concatMultilineString(msg.content.text)); + const originalText = formatStreamText(concatMultilineString(escape(msg.content.text))); // Create a new stream entry const output: nbformat.IStream = { output_type: 'stream', @@ -478,6 +485,11 @@ export class CellExecution { } private handleDisplayData(msg: KernelMessage.IDisplayDataMsg, clearState: RefBool) { + // Escape text output + if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { + msg.content.data['text/plain'] = escape(msg.content.data['text/plain'] as string); + } + const output: nbformat.IDisplayData = { output_type: 'display_data', data: msg.content.data, diff --git a/src/client/datascience/jupyter/oldJupyterVariables.ts b/src/client/datascience/jupyter/oldJupyterVariables.ts index 7c1d77de7135..6c25a3a3e547 100644 --- a/src/client/datascience/jupyter/oldJupyterVariables.ts +++ b/src/client/datascience/jupyter/oldJupyterVariables.ts @@ -11,6 +11,8 @@ import { Event, EventEmitter, Uri } from 'vscode'; import { PYTHON_LANGUAGE } from '../../common/constants'; import { traceError } from '../../common/logger'; +// tslint:disable-next-line: no-require-imports +import unescape = require('lodash/unescape'); import { IConfigurationService } from '../../common/types'; import * as localize from '../../common/utils/localize'; import { EXTENSION_ROOT_DIR } from '../../constants'; @@ -232,7 +234,7 @@ export class OldJupyterVariables implements IJupyterVariables { // Pull our text result out of the Jupyter cell private deserializeJupyterResult(cells: ICell[]): T { - const text = this.extractJupyterResultText(cells); + const text = unescape(this.extractJupyterResultText(cells)); return JSON.parse(text) as T; } @@ -357,7 +359,7 @@ export class OldJupyterVariables implements IJupyterVariables { // Now execute the query if (notebook && query) { const cells = await notebook.execute(query.query, Identifiers.EmptyFileName, 0, uuid(), undefined, true); - const text = this.extractJupyterResultText(cells); + const text = unescape(this.extractJupyterResultText(cells)); // Apply the expression to it const matches = this.getAllMatches(query.parser, text); diff --git a/src/datascience-ui/interactive-common/cellOutput.tsx b/src/datascience-ui/interactive-common/cellOutput.tsx index b48cc8d69cfd..b9a9d5bb2eee 100644 --- a/src/datascience-ui/interactive-common/cellOutput.tsx +++ b/src/datascience-ui/interactive-common/cellOutput.tsx @@ -314,26 +314,34 @@ export class CellOutput extends React.Component { input = JSON.stringify(output.data); renderWithScrollbars = true; isText = true; + } else if (output.output_type === 'execute_result' && input && input.hasOwnProperty('text/plain')) { + // Plain text should actually be shown as html so that escaped HTML shows up correctly + mimeType = 'text/html'; + isText = true; + isError = false; + renderWithScrollbars = true; + // tslint:disable-next-line: no-any + const text = (input as any)['text/plain']; + input = { + 'text/html': text // XML tags should have already been escaped. + }; } else if (output.output_type === 'stream') { - // Stream output needs to be wrapped in xmp so it - // show literally. Otherwise < chars start a new html element. mimeType = 'text/html'; isText = true; isError = false; renderWithScrollbars = true; // Sonar is wrong, TS won't compile without this AS const stream = output as nbformat.IStream; // NOSONAR - const formatted = concatMultilineString(stream.text); + const concatted = concatMultilineString(stream.text); input = { - 'text/html': formatted.includes('<') ? `${formatted}` : `
${formatted}
` + 'text/html': concatted // XML tags should have already been escaped. }; - // Output may have goofy ascii colorization chars in it. Try - // colorizing if we don't have html that needs around it (ex. <type ='string'>) + // Output may have ascii colorization chars in it. try { - if (ansiRegex().test(formatted)) { + if (ansiRegex().test(concatted)) { const converter = new CellOutput.ansiToHtmlClass(CellOutput.getAnsiToHtmlOptions()); - const html = converter.toHtml(formatted); + const html = converter.toHtml(concatted); input = { 'text/html': html }; diff --git a/src/test/datascience/Untitled-1.ipynb b/src/test/datascience/Untitled-1.ipynb new file mode 100644 index 000000000000..603be536258d --- /dev/null +++ b/src/test/datascience/Untitled-1.ipynb @@ -0,0 +1,47 @@ +{ + "metadata": { + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.6-final" + }, + "orig_nbformat": 2 + }, + "nbformat": 4, + "nbformat_minor": 2, + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "1" + }, + "metadata": {}, + "execution_count": 1 + } + ], + "source": [ + "a=1\n", + "a" + ] + } + ] +} \ No newline at end of file diff --git a/src/test/datascience/interactiveWindow.functional.test.tsx b/src/test/datascience/interactiveWindow.functional.test.tsx index 504e3499f401..d40cb82906ac 100644 --- a/src/test/datascience/interactiveWindow.functional.test.tsx +++ b/src/test/datascience/interactiveWindow.functional.test.tsx @@ -128,7 +128,7 @@ suite('DataScience Interactive Window output tests', () => { async () => { await addCode(ioc, 'a=1\na'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -171,7 +171,7 @@ for i in range(10): addMockData(ioc, 'a=1', undefined, 'text/plain'); await addCode(ioc, 'a=1'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.First); + verifyHtmlOnInteractiveCell('1', CellPosition.First); verifyHtmlOnInteractiveCell(undefined, CellPosition.Last); }, () => { @@ -236,7 +236,7 @@ for i in range(10): } } - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -300,7 +300,7 @@ for i in range(10): } } - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -698,7 +698,7 @@ for i in range(0, 100): const window = (await interactiveWindowProvider.getOrCreate(undefined)) as InteractiveWindow; await addCode(ioc, 'a=1\na'); const activeInterpreter = await interpreterService.getActiveInterpreter(window.owningResource); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); assert.equal( window.notebook!.getMatchingInterpreter()?.path, activeInterpreter?.path, @@ -724,7 +724,7 @@ for i in range(0, 100): activeInterpreter?.path, 'Active intrepreter used to launch second notebook when it should not have' ); - verifyHtmlOnCell(ioc.getWrapper('interactive'), 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(ioc.getWrapper('interactive'), 'InteractiveCell', '1', CellPosition.Last); } else { context.skip(); } @@ -867,7 +867,7 @@ for i in range(0, 100): // Then enter some code. await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -888,7 +888,7 @@ for i in range(0, 100): // Then enter some code. await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); const ImageButtons = getLastOutputCell(mount.wrapper, 'InteractiveCell').find(ImageButton); assert.equal(ImageButtons.length, 4, 'Cell buttons not found'); const copyToSource = ImageButtons.at(2); @@ -911,7 +911,7 @@ for i in range(0, 100): // Then enter some code. await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); // Then delete the node const lastCell = getLastOutputCell(mount.wrapper, 'InteractiveCell'); @@ -928,7 +928,7 @@ for i in range(0, 100): // Should be able to enter again await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); // Try a 3rd time with some new input addMockData(ioc, 'print("hello")', 'hello'); @@ -952,7 +952,7 @@ for i in range(0, 100): async () => { // Prime the pump await addCode(ioc, 'a=1\na'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); // Then something that could possibly timeout addContinuousMockData(ioc, 'import time\r\ntime.sleep(1000)', (_c) => { @@ -979,7 +979,7 @@ for i in range(0, 100): // Now see if our wrapper still works. Interactive window should have forced a restart await window.addCode('a=1\na', Uri.file('foo'), 0); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -1071,7 +1071,7 @@ for i in range(0, 100): // Then enter some code. await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); const ImageButtons = getLastOutputCell(mount.wrapper, 'InteractiveCell').find(ImageButton); assert.equal(ImageButtons.length, 4, 'Cell buttons not found'); const gatherCode = ImageButtons.at(0); @@ -1198,21 +1198,21 @@ for i in range(0, 100): await addCell(ne.mount, 'a=1\na', true); // Make sure both are correct - verifyHtmlOnCell(iw.mount.wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); - verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(iw.mount.wrapper, 'InteractiveCell', '1', CellPosition.Last); + verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '1', CellPosition.Last); // Close the interactive editor. await closeInteractiveWindow(ioc, iw.window); // Run another cell and make sure it works in the notebook await addCell(ne.mount, 'b=2\nb', true); - verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '<span>2</span>', CellPosition.Last); + verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '2', CellPosition.Last); // Rerun the interactive window iw = await getOrCreateInteractiveWindow(ioc); await addCode(ioc, 'a=1\na'); - verifyHtmlOnCell(iw.mount.wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(iw.mount.wrapper, 'InteractiveCell', '1', CellPosition.Last); }); test('Multiple interactive windows', async () => { ioc.forceDataScienceSettingsChanged({ interactiveWindowMode: 'multiple' }); diff --git a/src/test/datascience/liveshare.functional.test.tsx b/src/test/datascience/liveshare.functional.test.tsx index 0a8b6a0fa3dc..55d45c04742c 100644 --- a/src/test/datascience/liveshare.functional.test.tsx +++ b/src/test/datascience/liveshare.functional.test.tsx @@ -217,7 +217,7 @@ suite('DataScience LiveShare tests', () => { // Just run some code in the host const wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); }); test('Host & Guest Simple', async function () { @@ -234,14 +234,14 @@ suite('DataScience LiveShare tests', () => { // Send code through the host const wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); // Verify it ended up on the guest too assert.ok(guestContainer.getInteractiveWebPanel(undefined), 'Guest wrapper not created'); verifyHtmlOnCell( guestContainer.getInteractiveWebPanel(undefined).wrapper, 'InteractiveCell', - '<span>1</span>', + '1', CellPosition.Last ); }); @@ -253,7 +253,7 @@ suite('DataScience LiveShare tests', () => { addMockData(hostContainer!, 'b=2\nb', 2); await getOrCreateInteractiveWindow(vsls.Role.Host); let wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); await startSession(vsls.Role.Host); await getOrCreateInteractiveWindow(vsls.Role.Guest); @@ -265,7 +265,7 @@ suite('DataScience LiveShare tests', () => { verifyHtmlOnCell( guestContainer.getInteractiveWebPanel(undefined).wrapper, 'InteractiveCell', - '<span>2</span>', + '2', CellPosition.Last ); }); @@ -280,14 +280,14 @@ suite('DataScience LiveShare tests', () => { // Send code through the host let wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); // Stop the session await stopSession(vsls.Role.Host); // Send code again. It should still work. wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); }); test('Host startup and guest restart', async function () { @@ -302,7 +302,7 @@ suite('DataScience LiveShare tests', () => { // Send code through the host let wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); // Shutdown the host host.window.dispose(); @@ -310,13 +310,13 @@ suite('DataScience LiveShare tests', () => { // Startup a guest and run some code. await startSession(vsls.Role.Guest); wrapper = await addCodeToRole(vsls.Role.Guest, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); assert.ok(hostContainer.getInteractiveWebPanel(undefined), 'Host wrapper not created'); verifyHtmlOnCell( hostContainer.getInteractiveWebPanel(undefined).wrapper, 'InteractiveCell', - '<span>1</span>', + '1', CellPosition.Last ); }); @@ -349,12 +349,12 @@ suite('DataScience LiveShare tests', () => { assert.ok(both, 'Expected both guest and host to be used'); await codeWatcher.runAllCells(); }); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); assert.ok(hostContainer.getInteractiveWebPanel(undefined), 'Host wrapper not created for some reason'); verifyHtmlOnCell( hostContainer.getInteractiveWebPanel(undefined).wrapper, 'InteractiveCell', - '<span>1</span>', + '1', CellPosition.Last ); }); @@ -425,7 +425,7 @@ suite('DataScience LiveShare tests', () => { // Start just the host and verify it works await startSession(vsls.Role.Host); let wrapper = await addCodeToRole(vsls.Role.Host, '#%%\na=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); // Disable guest checking on the guest (same as if the guest doesn't have the python extension) await startSession(vsls.Role.Guest); @@ -434,7 +434,7 @@ suite('DataScience LiveShare tests', () => { // Host should now be in a state that if any code runs, the session should end. However // the code should still run wrapper = await addCodeToRole(vsls.Role.Host, '#%%\na=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); assert.equal(isSessionStarted(vsls.Role.Host), false, 'Host should have exited session'); assert.equal(isSessionStarted(vsls.Role.Guest), false, 'Guest should have exited session'); assert.ok(lastErrorMessage, 'Error was not set during session shutdown'); diff --git a/src/test/datascience/nativeEditor.functional.test.tsx b/src/test/datascience/nativeEditor.functional.test.tsx index f2ec387bd364..82e4f21ad91a 100644 --- a/src/test/datascience/nativeEditor.functional.test.tsx +++ b/src/test/datascience/nativeEditor.functional.test.tsx @@ -223,7 +223,7 @@ suite('DataScience Native Editor', () => { // Add a cell into the UI and wait for it to render await addCell(mount, 'a=1\na'); - verifyHtmlOnCell(mount.wrapper, 'NativeCell', '<span>1</span>', 1); + verifyHtmlOnCell(mount.wrapper, 'NativeCell', '1', 1); }); runMountedTest('Invalid session still runs', async (context) => { @@ -238,7 +238,7 @@ suite('DataScience Native Editor', () => { // Run the first cell. Should fail but then ask for another await addCell(mount, 'a=1\na'); - verifyHtmlOnCell(mount.wrapper, 'NativeCell', '<span>1</span>', 1); + verifyHtmlOnCell(mount.wrapper, 'NativeCell', '1', 1); } else { context.skip(); } @@ -414,7 +414,7 @@ suite('DataScience Native Editor', () => { // Run the first cell. Should fail but then ask for another await addCell(ne.mount, 'a=1\na'); - verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '<span>1</span>', 1); + verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '1', 1); } else { context.skip(); } @@ -448,7 +448,7 @@ suite('DataScience Native Editor', () => { // Verify we picked the valid kernel. await addCell(ne.mount, 'a=1\na'); - verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '<span>1</span>', 2); + verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '1', 2); } else { context.skip(); } @@ -1694,7 +1694,7 @@ df.head()`; wrapper.update(); // Ensure cell was executed. - verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', 1); + verifyHtmlOnCell(wrapper, 'NativeCell', '2', 1); // The third cell should be selected. assert.ok(isCellSelected(wrapper, 'NativeCell', 2)); @@ -1732,7 +1732,7 @@ df.head()`; await update; // Ensure cell was executed. - verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', 1); + verifyHtmlOnCell(wrapper, 'NativeCell', '2', 1); // The first cell should be selected. assert.ok(isCellSelected(wrapper, 'NativeCell', 1)); @@ -1962,7 +1962,7 @@ df.head()`; await update; // Ensure cell was executed. - verifyHtmlOnCell(wrapper, 'NativeCell', '<span>3</span>', 2); + verifyHtmlOnCell(wrapper, 'NativeCell', '3', 2); // Hide the output update = waitForMessage(ioc, InteractiveWindowMessages.OutputToggled); @@ -1970,7 +1970,7 @@ df.head()`; await update; // Ensure cell output is hidden (looking for cell results will throw an exception). - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>3</span>', 2)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '3', 2)); // Display the output update = waitForMessage(ioc, InteractiveWindowMessages.OutputToggled); @@ -1978,7 +1978,7 @@ df.head()`; await update; // Ensure cell output is visible again. - verifyHtmlOnCell(wrapper, 'NativeCell', '<span>3</span>', 2); + verifyHtmlOnCell(wrapper, 'NativeCell', '3', 2); }); test("Toggle line numbers using the 'l' key", async () => { diff --git a/src/test/datascience/notebook.functional.test.ts b/src/test/datascience/notebook.functional.test.ts index 5e89e9f675bd..aeec51594400 100644 --- a/src/test/datascience/notebook.functional.test.ts +++ b/src/test/datascience/notebook.functional.test.ts @@ -6,6 +6,8 @@ import { assert } from 'chai'; import { ChildProcess } from 'child_process'; import * as fs from 'fs-extra'; import { injectable } from 'inversify'; +// tslint:disable-next-line: no-require-imports +import escape = require('lodash/escape'); import * as os from 'os'; import * as path from 'path'; import { SemVer } from 'semver'; @@ -159,7 +161,7 @@ suite('DataScience notebook tests', () => { const data = extractDataOutput(cells[0]); if (pathVerify) { // For a path comparison normalize output - const normalizedOutput = path.normalize(data).toUpperCase().replace(/'/g, ''); + const normalizedOutput = path.normalize(data).toUpperCase().replace(/&#39;/g, ''); const normalizedTarget = path.normalize(expectedValue).toUpperCase().replace(/'/g, ''); assert.equal(normalizedOutput, normalizedTarget, 'Cell path values does not match'); } else { @@ -1022,6 +1024,15 @@ a`, result: 1, verifyValue: (d) => assert.equal(d, 1, 'Plain text invalid') }, + { + markdownRegEx: undefined, + code: `a="<a href=f>" +a`, + mimeType: 'text/plain', + cellType: 'code', + result: `<a href=f>`, + verifyValue: (d) => assert.equal(d, escape(`<a href=f>`), 'XML not escaped') + }, { markdownRegEx: undefined, code: `import pandas as pd @@ -1032,7 +1043,7 @@ df.head()`, cellType: 'error', // tslint:disable-next-line:quotemark verifyValue: (d) => - assert.ok((d as string).includes("has no attribute 'read'"), 'Unexpected error result') + assert.ok((d as string).includes(escape("has no attribute 'read'")), 'Unexpected error result') }, { markdownRegEx: undefined, diff --git a/src/test/datascience/trustedNotebooks.functional.test.tsx b/src/test/datascience/trustedNotebooks.functional.test.tsx index 57d5daef5269..302ad724e214 100644 --- a/src/test/datascience/trustedNotebooks.functional.test.tsx +++ b/src/test/datascience/trustedNotebooks.functional.test.tsx @@ -273,9 +273,9 @@ suite('Notebook trust', () => { suite('Open an untrusted notebook', async () => { test('Outputs are not rendered', () => { // No outputs should have rendered - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>1</span>', 0)); - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', 1)); - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>3</span>', 2)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '1', 0)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '2', 1)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '3', 2)); }); test('Cannot edit cell contents', async () => { await focusCell(0); @@ -332,7 +332,7 @@ suite('Notebook trust', () => { // Waiting for an execution rendered message should timeout await expect(promise).to.eventually.be.rejected; // No output should have been rendered - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', cellIndex)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '2', cellIndex)); }); test('Shift+enter does not execute cell or advance to next cell', async () => { const cellIndex = 1; @@ -344,7 +344,7 @@ suite('Notebook trust', () => { // Waiting for an execution rendered message should timeout await expect(promise).to.eventually.be.rejected; // No output should have been rendered - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', cellIndex)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '2', cellIndex)); // 3rd cell should be neither selected nor focused assert.isFalse(isCellSelected(wrapper, 'NativeCell', cellIndex + 1)); assert.isFalse(isCellFocused(wrapper, 'NativeCell', cellIndex + 1)); @@ -360,7 +360,7 @@ suite('Notebook trust', () => { // Waiting for an execution rendered message should timeout await expect(promise).to.eventually.be.rejected; // No output should have been rendered - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', cellIndex)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '2', cellIndex)); // No cell should have been added assert.equal(wrapper.find('NativeCell').length, 3, 'Cell added'); }); diff --git a/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts b/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts index f6916d491134..8c5850e37309 100644 --- a/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts +++ b/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts @@ -155,7 +155,7 @@ use(chaiAsPromised); await retryIfFail(async () => { await assert.eventually.isTrue(notebookUI.cellHasOutput(0)); const outputHtml = await notebookUI.getCellOutputHTML(0); - assert.include(outputHtml, '<span>1</span>'); + assert.include(outputHtml, '1'); }); }); From 5065d31530081d34b201d9deb089715986d499fe Mon Sep 17 00:00:00 2001 From: David Kutugata <dakutuga@microsoft.com> Date: Mon, 28 Sep 2020 13:34:00 -0700 Subject: [PATCH 11/15] update version and changelog (#14139) --- CHANGELOG.md | 4 +++- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d212c9f9067..b1fb41aa0ef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,11 @@ ### Fixes +1. Fix IPyKernel install issue with windows paths. + ([#13493](https://github.com/microsoft/vscode-python/issues/13493)) 1. Fix escaping of output to encode HTML chars correctly. ([#5678](https://github.com/Microsoft/vscode-python/issues/5678)) - + ### Thanks Thanks to the following projects which we fully rely on to provide some of diff --git a/package-lock.json b/package-lock.json index 02c8932d6c64..e57358bdbf7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2020.9.0", + "version": "2020.9.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b34f0ee31e6b..5cec5744359e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, Jupyter Notebooks, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2020.9.0", + "version": "2020.9.1", "featureFlags": { "usingNewInterpreterStorage": true }, From f276e07b47b454b6aa91d14d74ac86a14272bef2 Mon Sep 17 00:00:00 2001 From: Rich Chiodo <rchiodo@users.noreply.github.com> Date: Tue, 29 Sep 2020 10:30:38 -0700 Subject: [PATCH 12/15] Escaping fix broke a number of things (#14145) (#14154) * Fixes for escaping * Push a comment ot start PR again * Cache task is failing * Remove cache task * Not fixing so just put back cache task --- src/client/datascience/jupyter/jupyterDebugger.ts | 6 +++++- src/datascience-ui/interactive-common/cellOutput.tsx | 7 ++++++- src/test/datascience/notebook.functional.test.ts | 6 +++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/client/datascience/jupyter/jupyterDebugger.ts b/src/client/datascience/jupyter/jupyterDebugger.ts index afa7df942137..796dd18ab771 100644 --- a/src/client/datascience/jupyter/jupyterDebugger.ts +++ b/src/client/datascience/jupyter/jupyterDebugger.ts @@ -3,6 +3,8 @@ 'use strict'; import type { nbformat } from '@jupyterlab/coreutils'; import { inject, injectable, named } from 'inversify'; +// tslint:disable-next-line: no-require-imports +import unescape = require('lodash/unescape'); import * as path from 'path'; import * as uuid from 'uuid/v4'; import { DebugConfiguration, Disposable } from 'vscode'; @@ -473,8 +475,10 @@ export class JupyterDebugger implements IJupyterDebugger, ICellHashListener { if (outputs.length > 0) { const data = outputs[0].data; if (data && data.hasOwnProperty('text/plain')) { + // Plain text should be escaped by our execution engine. Unescape it so + // we can parse it. // tslint:disable-next-line:no-any - return (data as any)['text/plain']; + return unescape((data as any)['text/plain']); } if (outputs[0].output_type === 'stream') { const stream = outputs[0] as nbformat.IStream; diff --git a/src/datascience-ui/interactive-common/cellOutput.tsx b/src/datascience-ui/interactive-common/cellOutput.tsx index b9a9d5bb2eee..8b545eac7086 100644 --- a/src/datascience-ui/interactive-common/cellOutput.tsx +++ b/src/datascience-ui/interactive-common/cellOutput.tsx @@ -314,7 +314,12 @@ export class CellOutput extends React.Component<ICellOutputProps> { input = JSON.stringify(output.data); renderWithScrollbars = true; isText = true; - } else if (output.output_type === 'execute_result' && input && input.hasOwnProperty('text/plain')) { + } else if ( + output.output_type === 'execute_result' && + input && + input.hasOwnProperty('text/plain') && + !input.hasOwnProperty('text/html') + ) { // Plain text should actually be shown as html so that escaped HTML shows up correctly mimeType = 'text/html'; isText = true; diff --git a/src/test/datascience/notebook.functional.test.ts b/src/test/datascience/notebook.functional.test.ts index aeec51594400..b8ff1d720cbd 100644 --- a/src/test/datascience/notebook.functional.test.ts +++ b/src/test/datascience/notebook.functional.test.ts @@ -182,7 +182,7 @@ suite('DataScience notebook tests', () => { const error = cell.outputs[0].evalue; if (error) { assert.ok(error, 'Error not found when expected'); - assert.equal(error, errorString, 'Unexpected error found'); + assert.ok(error.toString().includes(errorString), 'Unexpected error found'); } } @@ -757,7 +757,7 @@ suite('DataScience notebook tests', () => { await server!.waitForIdle(10000); console.log('Verifying restart'); - await verifyError(server, 'a', `name 'a' is not defined`); + await verifyError(server, 'a', `is not defined`); } catch (exc) { assert.ok( exc instanceof JupyterKernelPromiseFailedError, @@ -1031,7 +1031,7 @@ a`, mimeType: 'text/plain', cellType: 'code', result: `<a href=f>`, - verifyValue: (d) => assert.equal(d, escape(`<a href=f>`), 'XML not escaped') + verifyValue: (d) => assert.ok(d.includes(escape(`<a href=f>`)), 'XML not escaped') }, { markdownRegEx: undefined, From ec931637ff04f5fcf917e7857a26698c5bd3d1ad Mon Sep 17 00:00:00 2001 From: David Kutugata <dakutuga@microsoft.com> Date: Wed, 30 Sep 2020 09:19:49 -0700 Subject: [PATCH 13/15] remove leftovers --- build/ci/templates/globals.yml | 1 - src/client/datascience/jupyter/kernels/cellExecution.ts | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/build/ci/templates/globals.yml b/build/ci/templates/globals.yml index 98bd51685dd0..03457023e99e 100644 --- a/build/ci/templates/globals.yml +++ b/build/ci/templates/globals.yml @@ -11,4 +11,3 @@ variables: npm_config_cache: $(Pipeline.Workspace)/.npm vmImageMacOS: 'macOS-10.15' TS_NODE_FILES: true # Temporarily enabled to allow using types from vscode.proposed.d.ts from ts-node (for tests). - VSC_PYTHON_CI_TEST_VSC_CHANNEL: '1.48.0' # Enforce this until https://github.com/microsoft/vscode-test/issues/73 is fixed diff --git a/src/client/datascience/jupyter/kernels/cellExecution.ts b/src/client/datascience/jupyter/kernels/cellExecution.ts index 12c3b90dc358..926266776f3d 100644 --- a/src/client/datascience/jupyter/kernels/cellExecution.ts +++ b/src/client/datascience/jupyter/kernels/cellExecution.ts @@ -38,8 +38,6 @@ import { import { IKernel } from './types'; // tslint:disable-next-line: no-var-requires no-require-imports const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed'); -// tslint:disable-next-line: no-require-imports -import escape = require('lodash/escape'); export class CellExecutionFactory { constructor( @@ -48,7 +46,7 @@ export class CellExecutionFactory { private readonly editorProvider: INotebookEditorProvider, private readonly appShell: IApplicationShell, private readonly vscNotebook: IVSCodeNotebook - ) { } + ) {} public create(cell: NotebookCell) { // tslint:disable-next-line: no-use-before-declare From c8827ff6c60d30680526ec98b8175a4d141e00f1 Mon Sep 17 00:00:00 2001 From: David Kutugata <dakutuga@microsoft.com> Date: Wed, 30 Sep 2020 10:14:24 -0700 Subject: [PATCH 14/15] import pytest --- pythonFiles/tests/testing_tools/adapter/test_functional.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pythonFiles/tests/testing_tools/adapter/test_functional.py b/pythonFiles/tests/testing_tools/adapter/test_functional.py index c41fa8f5d4c7..bbc092552eb1 100644 --- a/pythonFiles/tests/testing_tools/adapter/test_functional.py +++ b/pythonFiles/tests/testing_tools/adapter/test_functional.py @@ -9,6 +9,7 @@ import subprocess import sys import unittest +import pytest from ...__main__ import TESTING_TOOLS_ROOT from testing_tools.adapter.util import fix_path, PATH_SEP From 77b527b6f0a294e4a7363e123035891a2be65a7a Mon Sep 17 00:00:00 2001 From: David Kutugata <dakutuga@microsoft.com> Date: Wed, 30 Sep 2020 13:06:20 -0700 Subject: [PATCH 15/15] stop skipping python tests --- pythonFiles/tests/testing_tools/adapter/test_functional.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pythonFiles/tests/testing_tools/adapter/test_functional.py b/pythonFiles/tests/testing_tools/adapter/test_functional.py index bbc092552eb1..153ad5508d9b 100644 --- a/pythonFiles/tests/testing_tools/adapter/test_functional.py +++ b/pythonFiles/tests/testing_tools/adapter/test_functional.py @@ -9,7 +9,6 @@ import subprocess import sys import unittest -import pytest from ...__main__ import TESTING_TOOLS_ROOT from testing_tools.adapter.util import fix_path, PATH_SEP @@ -149,7 +148,6 @@ def test_discover_simple(self): ], ) - @pytest.mark.skip(reason="https://github.com/microsoft/vscode-python/issues/14023") def test_discover_complex_default(self): projroot, testroot = resolve_testroot("complex") expected = self.complex(projroot) @@ -170,7 +168,6 @@ def test_discover_complex_default(self): self.maxDiff = None self.assertEqual(sorted_object(result), sorted_object(expected)) - @pytest.mark.skip(reason="https://github.com/microsoft/vscode-python/issues/14023") def test_discover_complex_doctest(self): projroot, _ = resolve_testroot("complex") expected = self.complex(projroot)