From bd0382a25904cb0eb7062208c5ccbd35a4778f41 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Fri, 15 Apr 2022 09:53:33 -0700 Subject: [PATCH] Fix unit tests on windows and add some missing bindings for web --- src/extension.web.ts | 2 ++ src/intellisense/languageServer.node.ts | 8 +++-- src/intellisense/serviceRegistry.web.ts | 22 ++++++++++++ .../editor-integration/cellhashprovider.ts | 13 ++++--- .../interactiveWindow.node.ts | 7 ++-- src/kernels/helpers.ts | 14 ++++---- src/kernels/installer/pipEnvInstaller.node.ts | 5 +-- .../jupyter/launcher/serverUriStorage.ts | 6 ++-- src/kernels/kernel.base.ts | 5 ++- src/kernels/serviceRegistry.web.ts | 5 +++ .../controllers/liveKernelSwitcher.ts | 4 +-- src/notebooks/serviceRegistry.web.ts | 3 +- src/platform/api/pythonApi.ts | 9 +++-- src/platform/common/platform/fs-paths.ts | 26 ++++++++------ .../common/process/pythonEnvironment.node.ts | 10 +++--- src/platform/common/serviceRegistry.web.ts | 5 ++- .../pythonEnvironments/info/interpreter.ts | 3 +- src/telemetry/telemetry.ts | 5 +-- .../process/pythonEnvironment.unit.test.ts | 36 +++++++++++-------- .../common/process/pythonProcess.unit.test.ts | 24 +++++++------ 20 files changed, 129 insertions(+), 83 deletions(-) create mode 100644 src/intellisense/serviceRegistry.web.ts diff --git a/src/extension.web.ts b/src/extension.web.ts index 3ab9cf8597b..820a780be12 100644 --- a/src/extension.web.ts +++ b/src/extension.web.ts @@ -67,6 +67,7 @@ import { registerTypes as registerTelemetryTypes } from './telemetry/serviceRegi import { registerTypes as registerKernelTypes } from './kernels/serviceRegistry.web'; import { registerTypes as registerNotebookTypes } from './notebooks/serviceRegistry.web'; import { registerTypes as registerInteractiveTypes } from './interactive-window/serviceRegistry.web'; +import { registerTypes as registerIntellisenseTypes } from './intellisense/serviceRegistry.web'; import { IExtensionActivationManager } from './platform/activation/types'; import { isCI, isTestExecution, STANDARD_OUTPUT_CHANNEL } from './platform/common/constants'; import { getJupyterOutputChannel } from './platform/devTools/jupyterOutputChannel'; @@ -280,6 +281,7 @@ async function activateLegacy( registerNotebookTypes(serviceManager); registerKernelTypes(serviceManager, isDevMode); registerInteractiveTypes(serviceManager); + registerIntellisenseTypes(serviceManager, isDevMode); // Load the two data science experiments that we need to register types // Await here to keep the register method sync diff --git a/src/intellisense/languageServer.node.ts b/src/intellisense/languageServer.node.ts index 9a4dd7291dc..479900d5d68 100644 --- a/src/intellisense/languageServer.node.ts +++ b/src/intellisense/languageServer.node.ts @@ -28,6 +28,7 @@ import { getInterpreterId } from '../platform/pythonEnvironments/info/interprete import { noop } from '../platform/common/utils/misc'; import { sleep } from '../platform/common/utils/async'; import { PythonEnvironment } from '../platform/pythonEnvironments/info'; +import { getFilePath } from '../platform/common/platform/fs-paths'; // eslint-disable-next-line @typescript-eslint/no-explicit-any function ensure(target: any, key: string) { @@ -134,14 +135,14 @@ export class LanguageServer implements Disposable { () => languageClient, () => noop, // Don't trace output. Slows things down too much NOTEBOOK_SELECTOR, - interpreter.uri.fsPath || interpreter.uri.path, + getFilePath(interpreter.uri), (uri) => shouldAllowIntellisense(uri, interpreterId, interpreter.uri), getNotebookHeader ) : createPylanceMiddleware( () => languageClient, NOTEBOOK_SELECTOR, - interpreter.uri.fsPath || interpreter.uri.path, + getFilePath(interpreter.uri), (uri) => shouldAllowIntellisense(uri, interpreterId, interpreter.uri), getNotebookHeader ); @@ -227,8 +228,9 @@ export class LanguageServer implements Disposable { if (python) { const runJediPath = path.join(python.extensionPath, 'pythonFiles', 'run-jedi-language-server.py'); if (await fs.pathExists(runJediPath)) { + const interpreterPath = getFilePath(interpreter.uri); const serverOptions: ServerOptions = { - command: interpreter.uri.fsPath || 'python', + command: interpreterPath.length > 0 ? interpreterPath : 'python', args: [runJediPath] }; return serverOptions; diff --git a/src/intellisense/serviceRegistry.web.ts b/src/intellisense/serviceRegistry.web.ts new file mode 100644 index 00000000000..3826f262ea7 --- /dev/null +++ b/src/intellisense/serviceRegistry.web.ts @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; + +import { IExtensionSingleActivationService, IExtensionSyncActivationService } from '../platform/activation/types'; +import { IServiceManager } from '../platform/ioc/types'; +import { NotebookCellLanguageService } from './cellLanguageService'; +import { NotebookCellBangInstallDiagnosticsProvider } from './diagnosticsProvider'; +import { EmptyNotebookCellLanguageService } from './emptyNotebookCellLanguageService'; + +export function registerTypes(serviceManager: IServiceManager, _isDevMode: boolean) { + serviceManager.addSingleton( + IExtensionSyncActivationService, + NotebookCellBangInstallDiagnosticsProvider + ); + serviceManager.addSingleton(NotebookCellLanguageService, NotebookCellLanguageService); + serviceManager.addBinding(NotebookCellLanguageService, IExtensionSingleActivationService); + serviceManager.addSingleton( + IExtensionSingleActivationService, + EmptyNotebookCellLanguageService + ); +} diff --git a/src/interactive-window/editor-integration/cellhashprovider.ts b/src/interactive-window/editor-integration/cellhashprovider.ts index 8288c8310be..d0eb1d18d68 100644 --- a/src/interactive-window/editor-integration/cellhashprovider.ts +++ b/src/interactive-window/editor-integration/cellhashprovider.ts @@ -28,10 +28,9 @@ import { CellMatcher } from './cellMatcher'; import { ICellHash, ICellHashProvider, ICellHashListener, IFileHashes } from './types'; import { getAssociatedNotebookDocument } from '../../notebooks/controllers/kernelSelector'; import { getInteractiveCellMetadata } from '../helpers'; -import { getDisplayPath } from '../../platform/common/platform/fs-paths'; +import { getDisplayPath, getFilePath } from '../../platform/common/platform/fs-paths'; import { IPlatformService } from '../../platform/common/platform/types'; import { untildify } from '../../platform/common/utils/platform'; -import { originalFSPath } from '../../platform/vscode-path/resources'; // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires const _escapeRegExp = require('lodash/escapeRegExp') as typeof import('lodash/escapeRegExp'); // NOSONAR @@ -247,7 +246,7 @@ export class CellHashProvider implements ICellHashProvider { // exceptions in output. Track backs only work on local files. if (!this.traceBackRegexes.has(uristring)) { const uri = Uri.parse(uristring); - const fileMatchRegex = new RegExp(`\\[.*?;32m${_escapeRegExp(originalFSPath(uri))}`); + const fileMatchRegex = new RegExp(`\\[.*?;32m${_escapeRegExp(getFilePath(uri))}`); const fileDisplayNameMatchRegex = new RegExp(`\\[.*?;32m${_escapeRegExp(getDisplayPath(uri))}`); this.traceBackRegexes.set(uristring, [fileMatchRegex, fileDisplayNameMatchRegex]); } @@ -503,7 +502,7 @@ export class CellHashProvider implements ICellHashProvider { // Then replace the input line with our uri for this cell return afterLineReplace.replace( /.*?\n/, - `\u001b[1;32m${originalFSPath(matchUri)}\u001b[0m in \u001b[0;36m${inputMatch[2]}\n` + `\u001b[1;32m${getFilePath(matchUri)}\u001b[0m in \u001b[0;36m${inputMatch[2]}\n` ); } else if (this.kernel && notebook && notebook.notebookType !== InteractiveWindowView) { const matchingCellUri = this.executionCounts.get(executionCount); @@ -522,7 +521,7 @@ export class CellHashProvider implements ICellHashProvider { return afterLineReplace.replace( /.*?\n/, `\u001b[1;32m${localize.DataScience.cellAtFormat().format( - originalFSPath(matchUri), + getFilePath(matchUri), (cellIndex + 1).toString() )}\u001b[0m in \u001b[0;36m${inputMatch[2]}\n` ); @@ -533,7 +532,7 @@ export class CellHashProvider implements ICellHashProvider { const fileMatch = /^File.*?\[\d;32m(.*):\d+.*\u001b.*\n/.exec(traceFrame); if (fileMatch && fileMatch.length > 1) { // We need to untilde the file path here for the link to work in VS Code - const detildePath = untildify(fileMatch[1], originalFSPath(this.platformService.homeDir)); + const detildePath = untildify(fileMatch[1], getFilePath(this.platformService.homeDir)); const fileUri = Uri.file(detildePath); // We have a match, replace source lines with hrefs return traceFrame.replace(LineNumberMatchRegex, (_s, prefix, num, suffix) => { @@ -572,7 +571,7 @@ export class CellHashProvider implements ICellHashProvider { } else { const matchingFile = regexes.find((e) => { const uri = Uri.parse(e[0]); - return traceFrame.includes(originalFSPath(uri)); + return traceFrame.includes(getFilePath(uri)); }); if (matchingFile) { const offset = this.findCellOffset(this.hashes.get(matchingFile[0]), traceFrame); diff --git a/src/interactive-window/interactiveWindow.node.ts b/src/interactive-window/interactiveWindow.node.ts index ab43b106757..eabcbbcb749 100644 --- a/src/interactive-window/interactiveWindow.node.ts +++ b/src/interactive-window/interactiveWindow.node.ts @@ -57,6 +57,7 @@ import { IVSCodeNotebookController } from '../notebooks/controllers/types'; import { DisplayOptions } from '../kernels/displayOptions'; import { getInteractiveCellMetadata, InteractiveCellMetadata } from './helpers'; import { KernelConnector } from '../kernels/kernelConnector'; +import { getFilePath } from '../platform/common/platform/fs-paths'; export class InteractiveWindow implements IInteractiveWindowLoadable { public get onDidChangeViewState(): Event { @@ -431,7 +432,7 @@ export class InteractiveWindow implements IInteractiveWindowLoadable { public async debugCode(code: string, fileUri: Uri, line: number): Promise { let saved = true; - const file = fileUri.fsPath; + const file = getFilePath(fileUri); // Make sure the file is saved before debugging const doc = this.documentManager.textDocuments.find((d) => this.fs.areLocalPathsSame(d.fileName, file)); if (doc && doc.isUntitled) { @@ -607,7 +608,7 @@ export class InteractiveWindow implements IInteractiveWindowLoadable { } // If the file isn't unknown, set the active kernel's __file__ variable to point to that same file. - await this.setFileInKernel(fileUri.fsPath, kernel!); + await this.setFileInKernel(getFilePath(fileUri), kernel!); traceInfoIfCI('file in kernel set for IW'); } @@ -799,7 +800,7 @@ export class InteractiveWindow implements IInteractiveWindowLoadable { // Bring up the export file dialog box const uri = await this.exportDialog.showDialog(ExportFormat.ipynb, this.owningResource); if (uri) { - await this.jupyterExporter.exportToFile(cells, uri.fsPath); + await this.jupyterExporter.exportToFile(cells, getFilePath(uri)); } } } diff --git a/src/kernels/helpers.ts b/src/kernels/helpers.ts index 23a785b9b67..ebdbf8d3e43 100644 --- a/src/kernels/helpers.ts +++ b/src/kernels/helpers.ts @@ -22,7 +22,7 @@ import { Uri } from 'vscode'; import { IWorkspaceService } from '../platform/common/application/types'; import { isCI, PYTHON_LANGUAGE, Settings, Telemetry } from '../platform/common/constants'; import { traceError, traceInfo, traceInfoIfCI, traceWarning } from '../platform/logging'; -import { getDisplayPath } from '../platform/common/platform/fs-paths'; +import { getDisplayPath, getFilePath } from '../platform/common/platform/fs-paths'; import { DataScience } from '../platform/common/utils/localize'; import { SysInfoReason } from '../platform/messageTypes'; import { getNormalizedInterpreterPath, getInterpreterHash } from '../platform/pythonEnvironments/info/interpreter'; @@ -58,7 +58,7 @@ export function createInterpreterKernelSpec( ): IJupyterKernelSpec { const interpreterMetadata = interpreter ? { - path: uriPath.originalFSPath(interpreter.uri) + path: getFilePath(interpreter.uri) } : {}; // This creates a kernel spec for an interpreter. When launched, 'python' argument will map to using the interpreter @@ -83,8 +83,8 @@ export function createInterpreterKernelSpec( return new JupyterKernelSpec( defaultSpec, - specFile ? uriPath.originalFSPath(specFile) : undefined, - uriPath.originalFSPath(interpreter?.uri), + specFile ? getFilePath(specFile) : undefined, + getFilePath(interpreter?.uri), 'registeredByNewVersionOfExt' ); } @@ -992,10 +992,8 @@ export function getKernelId(spec: IJupyterKernelSpec, interpreter?: PythonEnviro argsForGenerationOfId = spec.argv.join('#').toLowerCase(); } const prefixForRemoteKernels = remoteBaseUrl ? `${remoteBaseUrl}.` : ''; - const specPath = uriPath.originalFSPath( - getNormalizedInterpreterPath(fsPathToUri(spec.interpreterPath) || spec.uri) - ); - const interpreterPath = uriPath.originalFSPath(getNormalizedInterpreterPath(interpreter?.uri)) || ''; + const specPath = getFilePath(getNormalizedInterpreterPath(fsPathToUri(spec.interpreterPath) || spec.uri)); + const interpreterPath = getFilePath(getNormalizedInterpreterPath(interpreter?.uri)) || ''; return `${prefixForRemoteKernels}${ spec.id || '' }.${specName}.${specPath}.${interpreterPath}.${argsForGenerationOfId}`; diff --git a/src/kernels/installer/pipEnvInstaller.node.ts b/src/kernels/installer/pipEnvInstaller.node.ts index 2e8bf404155..e22bd043758 100644 --- a/src/kernels/installer/pipEnvInstaller.node.ts +++ b/src/kernels/installer/pipEnvInstaller.node.ts @@ -12,7 +12,7 @@ import { ModuleInstallerType, ModuleInstallFlags } from './types'; import { isPipenvEnvironmentRelatedToFolder } from './pipenv.node'; import { getInterpreterWorkspaceFolder } from '../helpers'; import { IServiceContainer } from '../../platform/ioc/types'; -import { originalFSPath } from '../../platform/vscode-path/resources'; +import { getFilePath } from '../../platform/common/platform/fs-paths'; export const pipenvName = 'pipenv'; @@ -67,10 +67,11 @@ export class PipEnvInstaller extends ModuleInstaller { flags & ModuleInstallFlags.updateDependencies || flags & ModuleInstallFlags.upgrade; const args = [update ? 'update' : 'install', moduleName, '--dev']; + const workspaceFolder = getInterpreterWorkspaceFolder(interpreter, this.workspaceService); return { args, exe: pipenvName, - cwd: originalFSPath(getInterpreterWorkspaceFolder(interpreter, this.workspaceService)) + cwd: workspaceFolder ? getFilePath(workspaceFolder) : undefined }; } } diff --git a/src/kernels/jupyter/launcher/serverUriStorage.ts b/src/kernels/jupyter/launcher/serverUriStorage.ts index 903837c0850..5d3d9390a18 100644 --- a/src/kernels/jupyter/launcher/serverUriStorage.ts +++ b/src/kernels/jupyter/launcher/serverUriStorage.ts @@ -8,8 +8,8 @@ import { IApplicationEnvironment } from '../../../platform/common/application/types'; import { Settings } from '../../../platform/common/constants'; +import { getFilePath } from '../../../platform/common/platform/fs-paths'; import { IConfigurationService, ICryptoUtils, IMemento, GLOBAL_MEMENTO } from '../../../platform/common/types'; -import { originalFSPath } from '../../../platform/vscode-path/resources'; import { IJupyterServerUriStorage } from '../types'; /** @@ -187,10 +187,10 @@ export class JupyterServerUriStorage implements IJupyterServerUriStorage { private getUriAccountKey(): string { if (this.workspaceService.rootFolder) { // Folder situation - return this.crypto.createHash(originalFSPath(this.workspaceService.rootFolder), 'string', 'SHA512'); + return this.crypto.createHash(getFilePath(this.workspaceService.rootFolder), 'string', 'SHA512'); } else if (this.workspaceService.workspaceFile) { // Workspace situation - return this.crypto.createHash(originalFSPath(this.workspaceService.workspaceFile), 'string', 'SHA512'); + return this.crypto.createHash(getFilePath(this.workspaceService.workspaceFile), 'string', 'SHA512'); } return this.appEnv.machineId; // Global key when no folder or workspace file } diff --git a/src/kernels/kernel.base.ts b/src/kernels/kernel.base.ts index 32a97173dfe..3f9c88ee986 100644 --- a/src/kernels/kernel.base.ts +++ b/src/kernels/kernel.base.ts @@ -20,7 +20,7 @@ import { IApplicationShell, IWorkspaceService } from '../platform/common/applica import { WrappedError } from '../platform/errors/types'; import { disposeAllDisposables } from '../platform/common/helpers'; import { traceInfo, traceInfoIfCI, traceError, traceVerbose, traceWarning } from '../platform/logging'; -import { getDisplayPath } from '../platform/common/platform/fs-paths'; +import { getDisplayPath, getFilePath } from '../platform/common/platform/fs-paths'; import { Resource, IDisposableRegistry, @@ -57,7 +57,6 @@ import { Cancellation } from '../platform/common/cancellation'; import { KernelProgressReporter } from '../platform/progress/kernelProgressReporter'; import { DisplayOptions } from './displayOptions'; import { SilentExecutionErrorOptions } from './helpers'; -import { originalFSPath } from '../platform/vscode-path/resources'; export abstract class BaseKernel implements IKernel { get connection(): INotebookProviderConnection | undefined { @@ -453,7 +452,7 @@ export abstract class BaseKernel implements IKernel { result.push(...changeDirScripts); // Set the ipynb file - const file = originalFSPath(this.resourceUri); + const file = getFilePath(this.resourceUri); if (file) { result.push(`__vsc_ipynb_file__ = "${file.replace(/\\/g, '\\\\')}"`); } diff --git a/src/kernels/serviceRegistry.web.ts b/src/kernels/serviceRegistry.web.ts index eae2ae8322e..c06155f9c5a 100644 --- a/src/kernels/serviceRegistry.web.ts +++ b/src/kernels/serviceRegistry.web.ts @@ -22,6 +22,7 @@ import { LocalKernelFinder } from './raw/finder/localKernelFinder.web'; import { NotebookProvider } from './jupyter/launcher/notebookProvider'; import { IKernelProvider, INotebookProvider } from './types'; import { KernelProvider } from './kernelProvider.web'; +import { PreferredRemoteKernelIdProvider } from './raw/finder/preferredRemoteKernelIdProvider'; @injectable() class RawNotebookSupportedService implements IRawNotebookSupportedService { @@ -62,6 +63,10 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea serviceManager.addSingleton(IRemoteKernelFinder, RemoteKernelFinder); serviceManager.addSingleton(INotebookProvider, NotebookProvider); serviceManager.addSingleton(IKernelProvider, KernelProvider); + serviceManager.addSingleton( + PreferredRemoteKernelIdProvider, + PreferredRemoteKernelIdProvider + ); // Subdirectories registerWidgetTypes(serviceManager, isDevMode); diff --git a/src/notebooks/controllers/liveKernelSwitcher.ts b/src/notebooks/controllers/liveKernelSwitcher.ts index 397dfcf3957..349ddff869e 100644 --- a/src/notebooks/controllers/liveKernelSwitcher.ts +++ b/src/notebooks/controllers/liveKernelSwitcher.ts @@ -10,7 +10,7 @@ import { IDisposableRegistry, IMemento, WORKSPACE_MEMENTO } from '../../platform import { IKernelProvider, LiveRemoteKernelConnectionMetadata } from '../../kernels/types'; import { INotebookControllerManager } from '../types'; import { switchKernel } from './kernelSelector'; -import { originalFSPath } from '../../platform/vscode-path/resources'; +import { getFilePath } from '../../platform/common/platform/fs-paths'; const MEMENTO_BASE_KEY = 'jupyter-notebook-remote-session-'; @@ -38,7 +38,7 @@ export class LiveKernelSwitcher implements IExtensionSingleActivationService { } private getKey(notebookUri: Uri) { - return `${MEMENTO_BASE_KEY}${originalFSPath(notebookUri)}`; + return `${MEMENTO_BASE_KEY}${getFilePath(notebookUri)}`; } private onDidOpenNotebook(n: NotebookDocument) { diff --git a/src/notebooks/serviceRegistry.web.ts b/src/notebooks/serviceRegistry.web.ts index 7d8dfb4e07f..0c7d9ccd4ad 100644 --- a/src/notebooks/serviceRegistry.web.ts +++ b/src/notebooks/serviceRegistry.web.ts @@ -11,14 +11,13 @@ import { LiveKernelSwitcher } from './controllers/liveKernelSwitcher'; import { NotebookControllerManager } from './controllers/notebookControllerManager'; import { RemoteSwitcher } from './controllers/remoteSwitcher'; import { CellOutputDisplayIdTracker } from './execution/cellDisplayIdTracker'; -import { INotebookLanguageClientProvider, INotebookControllerManager, INotebookEditorProvider } from './types'; +import { INotebookControllerManager, INotebookEditorProvider } from './types'; import { NotebookUsageTracker } from './notebookUsageTracker'; import { NotebookEditorProvider } from './notebookEditorProvider'; export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IExtensionSingleActivationService, RemoteSwitcher); serviceManager.addSingleton(CellOutputDisplayIdTracker, CellOutputDisplayIdTracker); - serviceManager.addBinding(INotebookLanguageClientProvider, IExtensionSingleActivationService); serviceManager.addSingleton(INotebookControllerManager, NotebookControllerManager); serviceManager.addSingleton(IExtensionSyncActivationService, KernelFilterUI); serviceManager.addBinding(INotebookControllerManager, IExtensionSyncActivationService); diff --git a/src/platform/api/pythonApi.ts b/src/platform/api/pythonApi.ts index 1a005f60cf3..5b6b2c18936 100644 --- a/src/platform/api/pythonApi.ts +++ b/src/platform/api/pythonApi.ts @@ -6,7 +6,6 @@ // Licensed under the MIT License. import { Disposable, EventEmitter, Event, Uri, workspace } from 'vscode'; -import { originalFSPath } from '../vscode-path/resources'; import { fsPathToUri } from '../vscode-path/utils'; import { PythonEnvironment } from './extension'; import { @@ -26,7 +25,7 @@ import { isCI, PythonExtension, Telemetry } from '../common/constants'; import { IExtensions, IDisposableRegistry, Resource } from '../common/types'; import { createDeferred } from '../common/utils/async'; import { traceDecoratorVerbose, traceError, traceInfo, traceVerbose } from '../logging'; -import { getDisplayPath } from '../common/platform/fs-paths'; +import { getDisplayPath, getFilePath } from '../common/platform/fs-paths'; import { IInterpreterSelector, IInterpreterQuickPickItem } from '../interpreter/configuration/types'; import { IInterpreterService } from '../interpreter/contracts'; import { areInterpreterPathsSame } from '../pythonEnvironments/info/interpreter'; @@ -51,8 +50,8 @@ export function serializePythonEnvironment( if (jupyterVersion) { return { ...jupyterVersion, - path: originalFSPath(jupyterVersion.uri), - envPath: jupyterVersion.envPath ? originalFSPath(jupyterVersion.envPath) : undefined + path: getFilePath(jupyterVersion.uri), + envPath: jupyterVersion.envPath ? getFilePath(jupyterVersion.envPath) : undefined }; } } @@ -346,7 +345,7 @@ export class InterpreterService implements IInterpreterService { try { return await this.apiProvider .getApi() - .then((api) => api.getInterpreterDetails(originalFSPath(pythonPath), resource)) + .then((api) => api.getInterpreterDetails(getFilePath(pythonPath), resource)) .then(deserializePythonEnvironment); } catch { // If the python extension cannot get the details here, don't fail. Just don't use them. diff --git a/src/platform/common/platform/fs-paths.ts b/src/platform/common/platform/fs-paths.ts index 00155aac085..fb9f6231a1e 100644 --- a/src/platform/common/platform/fs-paths.ts +++ b/src/platform/common/platform/fs-paths.ts @@ -6,6 +6,20 @@ import * as path from '../../vscode-path/path'; import * as uriPath from '../../vscode-path/resources'; import { getOSType, OSType } from '../utils/platform'; +export function getFilePath(file: Uri | undefined) { + const isWindows = getOSType() === OSType.Windows; + if (file) { + const fsPath = uriPath.originalFSPath(file); + + // Remove separator on the front + if (fsPath && fsPath.startsWith(path.sep) && isWindows) { + return fsPath.slice(1); + } + return fsPath || ''; + } + return ''; +} + export function getDisplayPath( filename: Uri | undefined, workspaceFolders: readonly WorkspaceFolder[] | WorkspaceFolder[] = [], @@ -46,15 +60,5 @@ function getDisplayPathImpl(file: Uri | undefined, cwd: Uri | undefined, homePat } } - if (file) { - const fsPath = uriPath.originalFSPath(file); - - // Remove separator on the front - if (fsPath && fsPath.startsWith(path.sep) && isWindows) { - return fsPath.slice(1); - } - return fsPath || ''; - } - - return ''; + return getFilePath(file); } diff --git a/src/platform/common/process/pythonEnvironment.node.ts b/src/platform/common/process/pythonEnvironment.node.ts index f4d16d21dd6..7b1c910ac86 100644 --- a/src/platform/common/process/pythonEnvironment.node.ts +++ b/src/platform/common/process/pythonEnvironment.node.ts @@ -11,7 +11,7 @@ import * as internalPython from './internal/python.node'; import { ExecutionResult, IProcessService, ShellOptions, SpawnOptions } from './types.node'; import { compare, SemVer } from 'semver'; import type { PythonEnvironment as PyEnv } from '../../pythonEnvironments/info'; -import { getDisplayPath } from '../platform/fs-paths'; +import { getDisplayPath, getFilePath } from '../platform/fs-paths'; import { Uri } from 'vscode'; class PythonEnvironment { private cachedInterpreterInformation: InterpreterInformation | undefined | null = null; @@ -86,8 +86,8 @@ function createDeps( shellExec: (command: string, options?: ShellOptions) => Promise> ) { return { - getPythonArgv: (python: Uri) => pythonArgv || [getDisplayPath(python)], - getObservablePythonArgv: (python: Uri) => observablePythonArgv || [getDisplayPath(python)], + getPythonArgv: (python: Uri) => pythonArgv || [getFilePath(python)], + getObservablePythonArgv: (python: Uri) => observablePythonArgv || [getFilePath(python)], isValidExecutable, exec: async (cmd: string, args: string[]) => exec(cmd, args, { throwOnStdErr: true }), shellExec: async (text: string, timeout: number) => shellExec(text, { timeout }) @@ -101,7 +101,7 @@ export function createPythonEnv( fs: IFileSystem ): PythonEnvironment { const deps = createDeps( - async (filename: Uri) => fs.localFileExists(filename.fsPath), + async (filename: Uri) => fs.localFileExists(getFilePath(filename)), // We use the default: [pythonPath]. undefined, undefined, @@ -137,7 +137,7 @@ export function createCondaEnv( } const pythonArgv = [condaFile, ...runArgs, 'python']; const deps = createDeps( - async (filename) => fs.localFileExists(filename.fsPath), + async (filename) => fs.localFileExists(getFilePath(filename)), pythonArgv, // eslint-disable-next-line // TODO: Use pythonArgv here once 'conda run' can be diff --git a/src/platform/common/serviceRegistry.web.ts b/src/platform/common/serviceRegistry.web.ts index 7fd726221dd..b6cd83380fe 100644 --- a/src/platform/common/serviceRegistry.web.ts +++ b/src/platform/common/serviceRegistry.web.ts @@ -11,7 +11,8 @@ import { IPersistentStateFactory, IExtensions, ICryptoUtils, - IAsyncDisposableRegistry + IAsyncDisposableRegistry, + IBrowserService } from './types'; import { registerTypes as registerPlatformTypes } from './platform/serviceRegistry.web'; import { Extensions } from './application/extensions.web'; @@ -23,6 +24,7 @@ import { VSCodeNotebook } from './application/notebook'; import { ClipboardService } from './application/clipboard'; import { AsyncDisposableRegistry } from './asyncDisposableRegistry'; import { IMultiStepInputFactory, MultiStepInputFactory } from './utils/multiStepInput'; +import { BrowserService } from './net/browser'; export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingletonInstance(IsWindows, false); @@ -37,6 +39,7 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IClipboard, ClipboardService); serviceManager.addSingleton(IAsyncDisposableRegistry, AsyncDisposableRegistry); serviceManager.addSingleton(IMultiStepInputFactory, MultiStepInputFactory); + serviceManager.addSingleton(IBrowserService, BrowserService); registerPlatformTypes(serviceManager); } diff --git a/src/platform/pythonEnvironments/info/interpreter.ts b/src/platform/pythonEnvironments/info/interpreter.ts index e7541d70dc5..7a31a2a16d8 100644 --- a/src/platform/pythonEnvironments/info/interpreter.ts +++ b/src/platform/pythonEnvironments/info/interpreter.ts @@ -6,6 +6,7 @@ import { Uri } from 'vscode'; import * as uriPath from '../../vscode-path/resources'; import { PythonEnvironment } from '.'; import { getOSType, OSType } from '../../common/utils/platform'; +import { getFilePath } from '../../common/platform/fs-paths'; export function getInterpreterHash(interpreter: PythonEnvironment | {uri: Uri}){ const interpreterPath = getNormalizedInterpreterPath(interpreter.uri); @@ -36,7 +37,7 @@ export function areInterpreterPathsSame(path1: Uri = Uri.file(''), path2:Uri = U * This function will take that into account. */ export function getNormalizedInterpreterPath(path:Uri = Uri.file(''), ostype = getOSType(), forceLowerCase: boolean = false){ - let fsPath = uriPath.originalFSPath(path); + let fsPath = getFilePath(path); if (forceLowerCase) { fsPath = fsPath.toLowerCase(); } diff --git a/src/telemetry/telemetry.ts b/src/telemetry/telemetry.ts index be63e1469fe..a1514a1738e 100644 --- a/src/telemetry/telemetry.ts +++ b/src/telemetry/telemetry.ts @@ -19,7 +19,8 @@ import { getResourceType } from '../platform/common/utils'; import { populateTelemetryWithErrorInfo } from '../platform/errors'; import { setSharedProperty, IEventNamePropertyMapping, sendTelemetryEvent, waitBeforeSending } from '.'; import { IServiceContainer } from '../platform/ioc/types'; -import { getComparisonKey, originalFSPath } from '../platform/vscode-path/resources'; +import { getComparisonKey } from '../platform/vscode-path/resources'; +import { getFilePath } from '../platform/common/platform/fs-paths'; /** * This information is sent with each telemetry event. @@ -238,7 +239,7 @@ export function trackKernelResourceInformation(resource: Resource, information: ); currentData.pythonEnvironmentType = interpreter.envType; currentData.pythonEnvironmentPath = getTelemetrySafeHashedString( - originalFSPath(getNormalizedInterpreterPath(interpreter.uri)) + getFilePath(getNormalizedInterpreterPath(interpreter.uri)) ); pythonEnvironmentsByHash.set(currentData.pythonEnvironmentPath, interpreter); if (interpreter.version) { diff --git a/src/test/common/process/pythonEnvironment.unit.test.ts b/src/test/common/process/pythonEnvironment.unit.test.ts index afb472f1fa5..52fb8015263 100644 --- a/src/test/common/process/pythonEnvironment.unit.test.ts +++ b/src/test/common/process/pythonEnvironment.unit.test.ts @@ -8,6 +8,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { Uri } from 'vscode'; +import { getFilePath } from '../../../platform/common/platform/fs-paths'; import { IFileSystem } from '../../../platform/common/platform/types.node'; import { createCondaEnv, @@ -131,7 +132,7 @@ suite('PythonEnvironment', () => { }); test('getExecutablePath should return pythonPath if pythonPath is a file', async () => { - fileSystem.setup((f) => f.localFileExists(pythonPath.fsPath)).returns(() => Promise.resolve(true)); + fileSystem.setup((f) => f.localFileExists(getFilePath(pythonPath))).returns(() => Promise.resolve(true)); const env = createPythonEnv({ uri: pythonPath } as PythonEnvironment, processService.object, fileSystem.object); const result = await env.getExecutablePath(); @@ -141,10 +142,10 @@ suite('PythonEnvironment', () => { test('getExecutablePath should not return pythonPath if pythonPath is not a file', async () => { const executablePath = 'path/to/dummy/executable'; - fileSystem.setup((f) => f.localFileExists(pythonPath.fsPath)).returns(() => Promise.resolve(false)); + fileSystem.setup((f) => f.localFileExists(getFilePath(pythonPath))).returns(() => Promise.resolve(false)); const argv = ['-c', 'import sys;print(sys.executable)']; processService - .setup((p) => p.exec(pythonPath.fsPath, argv, { throwOnStdErr: true })) + .setup((p) => p.exec(getFilePath(pythonPath), argv, { throwOnStdErr: true })) .returns(() => Promise.resolve({ stdout: executablePath })); const env = createPythonEnv({ uri: pythonPath } as PythonEnvironment, processService.object, fileSystem.object); @@ -158,10 +159,10 @@ suite('PythonEnvironment', () => { test('getExecutablePath should throw if the result of exec() writes to stderr', async () => { const stderr = 'bar'; - fileSystem.setup((f) => f.localFileExists(pythonPath.fsPath)).returns(() => Promise.resolve(false)); + fileSystem.setup((f) => f.localFileExists(getFilePath(pythonPath))).returns(() => Promise.resolve(false)); const argv = ['-c', 'import sys;print(sys.executable)']; processService - .setup((p) => p.exec(pythonPath.fsPath, argv, { throwOnStdErr: true })) + .setup((p) => p.exec(getFilePath(pythonPath), argv, { throwOnStdErr: true })) .returns(() => Promise.reject(new StdErrError(stderr))); const env = createPythonEnv({ uri: pythonPath } as PythonEnvironment, processService.object, fileSystem.object); @@ -174,7 +175,7 @@ suite('PythonEnvironment', () => { const moduleName = 'foo'; const argv = ['-c', `import ${moduleName}`]; processService - .setup((p) => p.exec(pythonPath.fsPath, argv, { throwOnStdErr: true })) + .setup((p) => p.exec(getFilePath(pythonPath), argv, { throwOnStdErr: true })) .returns(() => Promise.resolve({ stdout: '' })) .verifiable(TypeMoq.Times.once()); const env = createPythonEnv({ uri: pythonPath } as PythonEnvironment, processService.object, fileSystem.object); @@ -188,7 +189,7 @@ suite('PythonEnvironment', () => { const moduleName = 'foo'; const argv = ['-c', `import ${moduleName}`]; processService - .setup((p) => p.exec(pythonPath.fsPath, argv, { throwOnStdErr: true })) + .setup((p) => p.exec(getFilePath(pythonPath), argv, { throwOnStdErr: true })) .returns(() => Promise.resolve({ stdout: '' })); const env = createPythonEnv({ uri: pythonPath } as PythonEnvironment, processService.object, fileSystem.object); @@ -201,7 +202,7 @@ suite('PythonEnvironment', () => { const moduleName = 'foo'; const argv = ['-c', `import ${moduleName}`]; processService - .setup((p) => p.exec(pythonPath.fsPath, argv, { throwOnStdErr: true })) + .setup((p) => p.exec(getFilePath(pythonPath), argv, { throwOnStdErr: true })) .returns(() => Promise.reject(new StdErrError('bar'))); const env = createPythonEnv({ uri: pythonPath } as PythonEnvironment, processService.object, fileSystem.object); @@ -217,7 +218,12 @@ suite('PythonEnvironment', () => { const result = env.getExecutionInfo(args); expect(result).to.deep.equal( - { command: pythonPath.fsPath, args, python: [pythonPath.fsPath], pythonExecutable: pythonPath.fsPath }, + { + command: getFilePath(pythonPath), + args, + python: [getFilePath(pythonPath)], + pythonExecutable: getFilePath(pythonPath) + }, 'getExecutionInfo should return pythonPath and the command and execution arguments as is' ); }); @@ -277,10 +283,10 @@ suite('CondaEnvironment', () => { test('getExecutionObservableInfo with a named environment should return execution info using pythonPath only', () => { const expected = { - command: pythonPath.fsPath, + command: getFilePath(pythonPath), args, - python: [pythonPath.fsPath], - pythonExecutable: pythonPath.fsPath + python: [getFilePath(pythonPath)], + pythonExecutable: getFilePath(pythonPath) }; const condaInfo = { name: 'foo', path: 'bar', version: undefined }; const env = createCondaEnv( @@ -298,10 +304,10 @@ suite('CondaEnvironment', () => { test('getExecutionObservableInfo with a non-named environment should return execution info using pythonPath only', () => { const expected = { - command: pythonPath.fsPath, + command: getFilePath(pythonPath), args, - python: [pythonPath.fsPath], - pythonExecutable: pythonPath.fsPath + python: [getFilePath(pythonPath)], + pythonExecutable: getFilePath(pythonPath) }; const condaInfo = { name: '', path: 'bar', version: undefined }; const env = createCondaEnv( diff --git a/src/test/common/process/pythonProcess.unit.test.ts b/src/test/common/process/pythonProcess.unit.test.ts index 10b3e5ca56f..f3d7b66a420 100644 --- a/src/test/common/process/pythonProcess.unit.test.ts +++ b/src/test/common/process/pythonProcess.unit.test.ts @@ -5,6 +5,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as TypeMoq from 'typemoq'; import { Uri } from 'vscode'; +import { getFilePath } from '../../../platform/common/platform/fs-paths'; import { IFileSystem } from '../../../platform/common/platform/types.node'; import { createPythonEnv } from '../../../platform/common/process/pythonEnvironment.node'; import { createPythonProcessService } from '../../../platform/common/process/pythonProcess.node'; @@ -36,13 +37,13 @@ suite('PythonProcessService', () => { noop(); } }; - processService.setup((p) => p.execObservable(pythonPath.fsPath, args, options)).returns(() => observable); + processService.setup((p) => p.execObservable(getFilePath(pythonPath), args, options)).returns(() => observable); const env = createPythonEnv({ uri: pythonPath } as PythonEnvironment, processService.object, fileSystem.object); const procs = createPythonProcessService(processService.object, env); const result = procs.execObservable(args, options); - processService.verify((p) => p.execObservable(pythonPath.fsPath, args, options), TypeMoq.Times.once()); + processService.verify((p) => p.execObservable(getFilePath(pythonPath), args, options), TypeMoq.Times.once()); expect(result).to.be.equal(observable, 'execObservable should return an observable'); }); @@ -60,14 +61,17 @@ suite('PythonProcessService', () => { } }; processService - .setup((p) => p.execObservable(pythonPath.fsPath, expectedArgs, options)) + .setup((p) => p.execObservable(getFilePath(pythonPath), expectedArgs, options)) .returns(() => observable); const env = createPythonEnv({ uri: pythonPath } as PythonEnvironment, processService.object, fileSystem.object); const procs = createPythonProcessService(processService.object, env); const result = procs.execModuleObservable(moduleName, args, options); - processService.verify((p) => p.execObservable(pythonPath.fsPath, expectedArgs, options), TypeMoq.Times.once()); + processService.verify( + (p) => p.execObservable(getFilePath(pythonPath), expectedArgs, options), + TypeMoq.Times.once() + ); expect(result).to.be.equal(observable, 'execModuleObservable should return an observable'); }); @@ -76,14 +80,14 @@ suite('PythonProcessService', () => { const options = {}; const stdout = 'foo'; processService - .setup((p) => p.exec(pythonPath.fsPath, args, options)) + .setup((p) => p.exec(getFilePath(pythonPath), args, options)) .returns(() => Promise.resolve({ stdout })); const env = createPythonEnv({ uri: pythonPath } as PythonEnvironment, processService.object, fileSystem.object); const procs = createPythonProcessService(processService.object, env); const result = await procs.exec(args, options); - processService.verify((p) => p.exec(pythonPath.fsPath, args, options), TypeMoq.Times.once()); + processService.verify((p) => p.exec(getFilePath(pythonPath), args, options), TypeMoq.Times.once()); expect(result.stdout).to.be.equal(stdout, 'exec should return the content of stdout'); }); @@ -94,14 +98,14 @@ suite('PythonProcessService', () => { const options = {}; const stdout = 'bar'; processService - .setup((p) => p.exec(pythonPath.fsPath, expectedArgs, options)) + .setup((p) => p.exec(getFilePath(pythonPath), expectedArgs, options)) .returns(() => Promise.resolve({ stdout })); const env = createPythonEnv({ uri: pythonPath } as PythonEnvironment, processService.object, fileSystem.object); const procs = createPythonProcessService(processService.object, env); const result = await procs.execModule(moduleName, args, options); - processService.verify((p) => p.exec(pythonPath.fsPath, expectedArgs, options), TypeMoq.Times.once()); + processService.verify((p) => p.exec(getFilePath(pythonPath), expectedArgs, options), TypeMoq.Times.once()); expect(result.stdout).to.be.equal(stdout, 'exec should return the content of stdout'); }); @@ -111,10 +115,10 @@ suite('PythonProcessService', () => { const expectedArgs = ['-m', moduleName, ...args]; const options = {}; processService - .setup((p) => p.exec(pythonPath.fsPath, expectedArgs, options)) + .setup((p) => p.exec(getFilePath(pythonPath), expectedArgs, options)) .returns(() => Promise.resolve({ stdout: 'bar', stderr: `Error: No module named ${moduleName}` })); processService - .setup((p) => p.exec(pythonPath.fsPath, ['-c', `import ${moduleName}`], { throwOnStdErr: true })) + .setup((p) => p.exec(getFilePath(pythonPath), ['-c', `import ${moduleName}`], { throwOnStdErr: true })) .returns(() => Promise.reject(new StdErrError('not installed'))); const env = createPythonEnv({ uri: pythonPath } as PythonEnvironment, processService.object, fileSystem.object); const procs = createPythonProcessService(processService.object, env);