diff --git a/src/kernels/errors/kernelErrorHandler.node.ts b/src/kernels/errors/kernelErrorHandler.node.ts index ecc1b39f36b..6bc3ed1de1e 100644 --- a/src/kernels/errors/kernelErrorHandler.node.ts +++ b/src/kernels/errors/kernelErrorHandler.node.ts @@ -54,9 +54,8 @@ export class DataScienceErrorHandlerNode extends DataScienceErrorHandler { ) { // Looks like some other module is missing. // Sometimes when you create files like xml.py, then kernel startup fails due to xml.dom module not being found. - const problematicFiles = await this.getFilesInWorkingDirectoryThatCouldPotentiallyOverridePythonModules( - resource - ); + const problematicFiles = + await this.getFilesInWorkingDirectoryThatCouldPotentiallyOverridePythonModules(resource); if (problematicFiles.length > 0) { const cwd = resource ? path.dirname(resource) : undefined; const fileLinks = problematicFiles.map((item) => { diff --git a/src/kernels/errors/kernelErrorHandler.ts b/src/kernels/errors/kernelErrorHandler.ts index c8cca83f24b..c03ab598c6b 100644 --- a/src/kernels/errors/kernelErrorHandler.ts +++ b/src/kernels/errors/kernelErrorHandler.ts @@ -375,8 +375,8 @@ export abstract class DataScienceErrorHandler implements IDataScienceErrorHandle err instanceof InvalidRemoteJupyterServerUriHandleError ? '' : err instanceof RemoteJupyterServerConnectionError - ? err.originalError.message || '' - : err.originalError?.message || err.message; + ? err.originalError.message || '' + : err.originalError?.message || err.message; const extensionId = err.serverProviderHandle.extensionId; const id = err.serverProviderHandle.id; diff --git a/src/kernels/execution/helpers.ts b/src/kernels/execution/helpers.ts index c5e59c4228a..24cba3dffa0 100644 --- a/src/kernels/execution/helpers.ts +++ b/src/kernels/execution/helpers.ts @@ -110,8 +110,9 @@ export function traceCellMessage(cell: NotebookCell, message: string | (() => st () => `Cell Index:${cell.index}, of document ${uriPath.basename( cell.notebook.uri - )} with state:${NotebookCellStateTracker.getCellStatus(cell)}, exec: ${cell.executionSummary - ?.executionOrder}. ${messageToLog()}. called from ${getExtensionSpecificStack()}` + )} with state:${NotebookCellStateTracker.getCellStatus(cell)}, exec: ${ + cell.executionSummary?.executionOrder + }. ${messageToLog()}. called from ${getExtensionSpecificStack()}` ); } @@ -651,8 +652,8 @@ export async function updateNotebookMetadataWithSelectedKernel( kernelConnection && kernelConnectionMetadataHasKernelModel(kernelConnection) ? kernelConnection.kernelModel : kernelConnection && 'kernelSpec' in kernelConnection - ? kernelConnection.kernelSpec - : undefined; + ? kernelConnection.kernelSpec + : undefined; if (kernelConnection?.kind === 'startUsingPythonInterpreter') { // Store interpreter name, we expect the kernel finder will find the corresponding interpreter based on this name. const kernelSpec = kernelConnection.kernelSpec; diff --git a/src/kernels/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.unit.test.ts b/src/kernels/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.unit.test.ts index b9271dee504..88e5c391f7e 100644 --- a/src/kernels/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.unit.test.ts +++ b/src/kernels/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.unit.test.ts @@ -118,9 +118,8 @@ suite('Jupyter InterpreterSubCommandExecutionService', () => { }); test('Jupyter cannot be started because no interpreter has been selected', async () => { when(interpreterService.getActiveInterpreter(undefined)).thenResolve(undefined); - const reason = await jupyterInterpreterExecutionService.getReasonForJupyterNotebookNotBeingSupported( - undefined - ); + const reason = + await jupyterInterpreterExecutionService.getReasonForJupyterNotebookNotBeingSupported(undefined); assert.equal(reason, DataScience.selectJupyterInterpreter); }); test('Jupyter cannot be started because jupyter is not installed', async () => { @@ -152,9 +151,8 @@ suite('Jupyter InterpreterSubCommandExecutionService', () => { when(jupyterDependencyService.getDependenciesNotInstalled(activePythonInterpreter, undefined)).thenResolve([ Product.jupyter ]); - const reason = await jupyterInterpreterExecutionService.getReasonForJupyterNotebookNotBeingSupported( - undefined - ); + const reason = + await jupyterInterpreterExecutionService.getReasonForJupyterNotebookNotBeingSupported(undefined); assert.equal(reason, expectedReason); }); test('Jupyter cannot be started because notebook is not installed', async () => { @@ -183,9 +181,8 @@ suite('Jupyter InterpreterSubCommandExecutionService', () => { when(jupyterDependencyService.getDependenciesNotInstalled(activePythonInterpreter, undefined)).thenResolve([ Product.notebook ]); - const reason = await jupyterInterpreterExecutionService.getReasonForJupyterNotebookNotBeingSupported( - undefined - ); + const reason = + await jupyterInterpreterExecutionService.getReasonForJupyterNotebookNotBeingSupported(undefined); assert.equal(reason, expectedReason); }); test('Cannot start notebook', async () => { @@ -306,9 +303,8 @@ suite('Jupyter InterpreterSubCommandExecutionService', () => { jupyterDependencyService.getDependenciesNotInstalled(selectedJupyterInterpreter, undefined) ).thenResolve([Product.jupyter]); - let reason = await jupyterInterpreterExecutionService.getReasonForJupyterNotebookNotBeingSupported( - undefined - ); + let reason = + await jupyterInterpreterExecutionService.getReasonForJupyterNotebookNotBeingSupported(undefined); // replace interpreter name with *** to workaround flakey test. reason = reason.replace(/('.*?')/g, "'***'"); @@ -343,9 +339,8 @@ suite('Jupyter InterpreterSubCommandExecutionService', () => { jupyterDependencyService.getDependenciesNotInstalled(selectedJupyterInterpreter, undefined) ).thenResolve([Product.notebook]); - let reason = await jupyterInterpreterExecutionService.getReasonForJupyterNotebookNotBeingSupported( - undefined - ); + let reason = + await jupyterInterpreterExecutionService.getReasonForJupyterNotebookNotBeingSupported(undefined); // replace interpreter name with *** to workaround flakey test. reason = reason.replace(/('.*?')/g, "'***'"); @@ -356,9 +351,8 @@ suite('Jupyter InterpreterSubCommandExecutionService', () => { jupyterDependencyService.getDependenciesNotInstalled(selectedJupyterInterpreter, undefined) ).thenResolve([Product.kernelspec]); - const reason = await jupyterInterpreterExecutionService.getReasonForJupyterNotebookNotBeingSupported( - undefined - ); + const reason = + await jupyterInterpreterExecutionService.getReasonForJupyterNotebookNotBeingSupported(undefined); assert.equal(reason, DataScience.jupyterKernelSpecModuleNotFound(selectedJupyterInterpreter.uri.fsPath)); }); diff --git a/src/kernels/kernel.ts b/src/kernels/kernel.ts index 51c8a8787bb..97fdc3850eb 100644 --- a/src/kernels/kernel.ts +++ b/src/kernels/kernel.ts @@ -343,8 +343,8 @@ abstract class BaseKernel implements IBaseKernel { this._session = this._session ? this._session : this._jupyterSessionPromise - ? await this._jupyterSessionPromise.catch(() => undefined) - : undefined; + ? await this._jupyterSessionPromise.catch(() => undefined) + : undefined; this._jupyterSessionPromise = undefined; this._postInitializedOnStartPromise = undefined; if (this._session) { @@ -875,11 +875,11 @@ abstract class BaseKernel implements IBaseKernel { logger.error('Failed to determine version of IPyWidgets', ex) ); if (Array.isArray(version)) { - const isVersion8 = version.some( - (output) => (output.text || '')?.toString().includes(`${widgetVersionOutPrefix}8.`) + const isVersion8 = version.some((output) => + (output.text || '')?.toString().includes(`${widgetVersionOutPrefix}8.`) ); - const isVersion7 = version.some( - (output) => (output.text || '')?.toString().includes(`${widgetVersionOutPrefix}7.`) + const isVersion7 = version.some((output) => + (output.text || '')?.toString().includes(`${widgetVersionOutPrefix}7.`) ); const newVersion = (this._ipywidgetsVersion = isVersion7 ? 7 : isVersion8 ? 8 : undefined); diff --git a/src/kernels/raw/session/kernelWorkingDirectory.node.ts b/src/kernels/raw/session/kernelWorkingDirectory.node.ts index 077fc4eccf8..9706d1bce06 100644 --- a/src/kernels/raw/session/kernelWorkingDirectory.node.ts +++ b/src/kernels/raw/session/kernelWorkingDirectory.node.ts @@ -59,8 +59,8 @@ export async function computeLocalWorkingDirectory( suggestedDir && suggestedDir.includes('${') ? suggestedDir : suggestedDir - ? getFilePath(Uri.file(suggestedDir)) - : undefined; + ? getFilePath(Uri.file(suggestedDir)) + : undefined; const expandedWorkingDir = expandWorkingDir(workingDir, resource, configService.getSettings(resource)); if (await fs.exists(Uri.file(expandedWorkingDir))) { return expandedWorkingDir; diff --git a/src/notebooks/debugger/kernelDebugAdapter.ts b/src/notebooks/debugger/kernelDebugAdapter.ts index 17ba03fb03e..da96949a23e 100644 --- a/src/notebooks/debugger/kernelDebugAdapter.ts +++ b/src/notebooks/debugger/kernelDebugAdapter.ts @@ -51,7 +51,7 @@ export class KernelDebugAdapter extends KernelDebugAdapterBase { return; } const cell = this.notebookDocument.getCells().find((c) => c.document.uri.toString() === mapping.toString()); - const offset = cell ? this.lineOffsets.get(cell) ?? 0 : 0; + const offset = cell ? (this.lineOffsets.get(cell) ?? 0) : 0; source.name = path.basename(mapping.path); source.path = mapping.toString(); if (offset && typeof location?.endLine === 'number') { @@ -79,7 +79,7 @@ export class KernelDebugAdapter extends KernelDebugAdapterBase { return; } const cell = this.notebookDocument.getCells().find((c) => c.document.uri.toString() === source.path); - const offset = cell ? this.lineOffsets.get(cell) ?? 0 : 0; + const offset = cell ? (this.lineOffsets.get(cell) ?? 0) : 0; source.path = mapping; if (offset && typeof location.line === 'number') { if (location.line < offset) { diff --git a/src/platform/api/pythonApi.ts b/src/platform/api/pythonApi.ts index 3e68cc60f67..311df4b5e03 100644 --- a/src/platform/api/pythonApi.ts +++ b/src/platform/api/pythonApi.ts @@ -508,14 +508,14 @@ export class InterpreterService implements IInterpreterService { return isUri(pythonPath) ? areInterpreterPathsSame(item.executable.uri, pythonPath) : typeof pythonPath === 'string' - ? item.id === pythonPath - : areInterpreterPathsSame(Uri.file(item.path), Uri.file(pythonPath.path)); + ? item.id === pythonPath + : areInterpreterPathsSame(Uri.file(item.path), Uri.file(pythonPath.path)); }); const pythonPathForLogging = isUri(pythonPath) ? getDisplayPath(pythonPath) : typeof pythonPath === 'string' - ? pythonPath - : getDisplayPath(Uri.file(pythonPath.path)); + ? pythonPath + : getDisplayPath(Uri.file(pythonPath.path)); if (matchedPythonEnv) { const env = await api.environments.resolveEnvironment(matchedPythonEnv); const resolved = this.trackResolvedEnvironment(env); diff --git a/src/platform/common/application/workspace.base.ts b/src/platform/common/application/workspace.base.ts index f38b95232a9..4635e19f1ff 100644 --- a/src/platform/common/application/workspace.base.ts +++ b/src/platform/common/application/workspace.base.ts @@ -18,8 +18,8 @@ export function getWorkspaceFolderIdentifier(resource: Resource, defaultValue: s const workspaceFolder = resource ? workspace.getWorkspaceFolder(resource) : workspace.workspaceFolders - ? workspace.workspaceFolders[0] // Default to first folder if resource not passed in. - : undefined; + ? workspace.workspaceFolders[0] // Default to first folder if resource not passed in. + : undefined; return workspaceFolder ? path.normalize( getOSType() === OSType.Windows ? workspaceFolder.uri.path.toUpperCase() : workspaceFolder.uri.path diff --git a/src/platform/common/configuration/service.base.ts b/src/platform/common/configuration/service.base.ts index 9b17a6ea555..405645c0b36 100644 --- a/src/platform/common/configuration/service.base.ts +++ b/src/platform/common/configuration/service.base.ts @@ -72,8 +72,8 @@ export abstract class BaseConfigurationService implements IConfigurationService target === ConfigurationTarget.Global ? setting.globalValue : target === ConfigurationTarget.Workspace - ? setting.workspaceValue - : setting.workspaceFolderValue; + ? setting.workspaceValue + : setting.workspaceFolderValue; if (actual === value) { break; } diff --git a/src/platform/common/providerBasedQuickPick.ts b/src/platform/common/providerBasedQuickPick.ts index 468a1bcf9c8..206032dafdd 100644 --- a/src/platform/common/providerBasedQuickPick.ts +++ b/src/platform/common/providerBasedQuickPick.ts @@ -474,8 +474,8 @@ export class BaseProviderBasedQuickPick extends Dispos const activeItems = selectedQuickPickItem ? [selectedQuickPickItem] : quickPick.activeItems.length - ? [quickPick.activeItems[0]] - : []; + ? [quickPick.activeItems[0]] + : []; if (activeItems.length && !items.includes(activeItems[0])) { const oldActiveItem = activeItems[0]; const newActiveQuickPickItem = diff --git a/src/platform/common/utils.ts b/src/platform/common/utils.ts index 612644e7f4e..3ea89e85cd9 100644 --- a/src/platform/common/utils.ts +++ b/src/platform/common/utils.ts @@ -296,13 +296,13 @@ export function parseForComments( const isMultilineComment = trim.startsWith(SingleQuoteMultiline) ? SingleQuoteMultiline : trim.startsWith(DoubleQuoteMultiline) - ? DoubleQuoteMultiline - : undefined; + ? DoubleQuoteMultiline + : undefined; const isMultilineQuote = trim.includes(SingleQuoteMultiline) ? SingleQuoteMultiline : trim.includes(DoubleQuoteMultiline) - ? DoubleQuoteMultiline - : undefined; + ? DoubleQuoteMultiline + : undefined; // Check for ending quotes of multiline string if (insideMultilineQuote) { diff --git a/src/platform/common/variables/customEnvironmentVariablesProvider.node.ts b/src/platform/common/variables/customEnvironmentVariablesProvider.node.ts index 7b056477ed3..924eb74b729 100644 --- a/src/platform/common/variables/customEnvironmentVariablesProvider.node.ts +++ b/src/platform/common/variables/customEnvironmentVariablesProvider.node.ts @@ -53,8 +53,8 @@ export class CustomEnvironmentVariablesProvider implements ICustomEnvironmentVar resource = resource ? resource : workspace.workspaceFolders?.length - ? workspace.workspaceFolders[0].uri - : undefined; + ? workspace.workspaceFolders[0].uri + : undefined; if (purpose === 'RunPythonCode') { // No need to cache for Python code, as we get these env vars from Python extension. @@ -87,8 +87,8 @@ export class CustomEnvironmentVariablesProvider implements ICustomEnvironmentVar resource = resource ? resource : workspace.workspaceFolders?.length - ? workspace.workspaceFolders[0].uri - : undefined; + ? workspace.workspaceFolders[0].uri + : undefined; const workspaceFolderUri = this.getWorkspaceFolderUri(resource); if (!workspaceFolderUri) { logger.ci(`No workspace folder found for ${resource ? resource.fsPath : ''}`); diff --git a/src/platform/interpreter/environmentActivationService.node.ts b/src/platform/interpreter/environmentActivationService.node.ts index 23b8bee2074..0d9740cceff 100644 --- a/src/platform/interpreter/environmentActivationService.node.ts +++ b/src/platform/interpreter/environmentActivationService.node.ts @@ -149,8 +149,8 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi resource = resource ? resource : workspace.workspaceFolders?.length - ? workspace.workspaceFolders[0].uri - : undefined; + ? workspace.workspaceFolders[0].uri + : undefined; const stopWatch = new StopWatch(); // We'll need this later. const customEnvVarsPromise = this.customEnvVarsService diff --git a/src/platform/interpreter/helpers.ts b/src/platform/interpreter/helpers.ts index af8ff4dd83d..229e2fadabe 100644 --- a/src/platform/interpreter/helpers.ts +++ b/src/platform/interpreter/helpers.ts @@ -81,7 +81,8 @@ const environmentTypes = [ EnvironmentType.Pyenv, EnvironmentType.Venv, EnvironmentType.VirtualEnv, - EnvironmentType.VirtualEnvWrapper + EnvironmentType.VirtualEnvWrapper, + EnvironmentType.UV ]; export function getEnvironmentType(interpreter: { id: string }): EnvironmentType { @@ -93,6 +94,11 @@ function getEnvironmentTypeImpl(env: Environment): EnvironmentType { return EnvironmentType.Conda; } + // Check for UV environment by looking for uv tools + if (env.tools.some((tool) => tool.toLowerCase() === 'uv')) { + return EnvironmentType.UV; + } + // Map the Python env tool to a Jupyter environment type. const orderOrEnvs: [pythonEnvTool: KnownEnvironmentTools, JupyterEnv: EnvironmentType][] = [ ['Conda', EnvironmentType.Conda], diff --git a/src/platform/interpreter/installer/channelManager.node.ts b/src/platform/interpreter/installer/channelManager.node.ts index 5bae53aa98f..5d8c892a36e 100644 --- a/src/platform/interpreter/installer/channelManager.node.ts +++ b/src/platform/interpreter/installer/channelManager.node.ts @@ -8,7 +8,7 @@ import { IPlatformService } from '../../common/platform/types'; import { Installer } from '../../common/utils/localize'; import { IServiceContainer } from '../../ioc/types'; import { IInstallationChannelManager, IModuleInstaller, Product } from './types'; -import { Uri, env, window } from 'vscode'; +import { Uri, env, window, l10n } from 'vscode'; import { getEnvironmentType } from '../helpers'; /** @@ -61,21 +61,29 @@ export class InstallationChannelManager implements IInstallationChannelManager { public async showNoInstallersMessage(interpreter: PythonEnvironment): Promise { const envType = getEnvironmentType(interpreter); - const result = await window.showErrorMessage( - envType === EnvironmentType.Conda ? Installer.noCondaOrPipInstaller : Installer.noPipInstaller, - { modal: true }, - Installer.searchForHelp - ); + let message: string; + let searchTerm: string; + + switch (envType) { + case EnvironmentType.Conda: + message = Installer.noCondaOrPipInstaller; + searchTerm = 'Install Pip Conda'; + break; + case EnvironmentType.UV: + message = l10n.t('There is no UV installer available in the selected environment.'); + searchTerm = 'Install UV Python'; + break; + default: + message = Installer.noPipInstaller; + searchTerm = 'Install Pip'; + break; + } + + const result = await window.showErrorMessage(message, { modal: true }, Installer.searchForHelp); if (result === Installer.searchForHelp) { const platform = this.serviceContainer.get(IPlatformService); const osName = platform.isWindows ? 'Windows' : platform.isMac ? 'MacOS' : 'Linux'; - void env.openExternal( - Uri.parse( - `https://www.bing.com/search?q=Install Pip ${osName} ${ - envType === EnvironmentType.Conda ? 'Conda' : '' - }` - ) - ); + void env.openExternal(Uri.parse(`https://www.bing.com/search?q=${searchTerm} ${osName}`)); } } } diff --git a/src/platform/interpreter/installer/pipInstaller.node.ts b/src/platform/interpreter/installer/pipInstaller.node.ts index ae53dde6277..4c713220d85 100644 --- a/src/platform/interpreter/installer/pipInstaller.node.ts +++ b/src/platform/interpreter/installer/pipInstaller.node.ts @@ -41,11 +41,12 @@ export class PipInstaller extends ModuleInstaller { } public async isSupported(interpreter: PythonEnvironment | Environment): Promise { const envType = getEnvironmentType(interpreter); - // Skip this on conda, poetry, and pipenv environments + // Skip this on conda, poetry, pipenv, and UV environments switch (envType) { case EnvironmentType.Conda: case EnvironmentType.Pipenv: case EnvironmentType.Poetry: + case EnvironmentType.UV: return false; } diff --git a/src/platform/interpreter/installer/types.ts b/src/platform/interpreter/installer/types.ts index f2f8028d3c5..0768c24d459 100644 --- a/src/platform/interpreter/installer/types.ts +++ b/src/platform/interpreter/installer/types.ts @@ -43,7 +43,8 @@ export enum ModuleInstallerType { Conda = 'Conda', Pip = 'Pip', Poetry = 'Poetry', - Pipenv = 'Pipenv' + Pipenv = 'Pipenv', + UV = 'UV' } export enum ProductType { diff --git a/src/platform/interpreter/installer/uvInstaller.node.ts b/src/platform/interpreter/installer/uvInstaller.node.ts new file mode 100644 index 00000000000..e6cce9f4043 --- /dev/null +++ b/src/platform/interpreter/installer/uvInstaller.node.ts @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { EnvironmentType, PythonEnvironment } from '../../pythonEnvironments/info'; +import { ExecutionInstallArgs, ModuleInstaller } from './moduleInstaller.node'; +import { ModuleInstallerType, ModuleInstallFlags } from './types'; +import { IServiceContainer } from '../../ioc/types'; +import { Environment } from '@vscode/python-extension'; +import { getEnvironmentType } from '../helpers'; +import { workspace } from 'vscode'; + +/** + * Installer for UV environments. + */ +@injectable() +export class UvInstaller extends ModuleInstaller { + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + super(serviceContainer); + } + + public get name(): string { + return 'UV'; + } + + public get type(): ModuleInstallerType { + return ModuleInstallerType.UV; + } + + public get displayName() { + return 'UV'; + } + + public get priority(): number { + return 10; + } + + public async isSupported(interpreter: PythonEnvironment | Environment): Promise { + // Check if this is a UV environment + const envType = getEnvironmentType(interpreter); + if (envType === EnvironmentType.UV) { + return true; + } + + // For now, we'll be conservative and only support explicitly detected UV environments + // In the future, we could add more sophisticated detection like: + // - Checking for pyproject.toml with [tool.uv] configuration + // - Checking if 'uv' command is available in PATH + // - Checking if the interpreter path suggests UV management + return false; + } + + protected async getExecutionArgs( + moduleName: string, + interpreter: PythonEnvironment | Environment, + flags: ModuleInstallFlags = 0 + ): Promise { + const args: string[] = []; + const proxy = workspace.getConfiguration('http').get('proxy', ''); + if (proxy.length > 0) { + args.push('--proxy'); + args.push(proxy); + } + + // Use UV pip install syntax + args.push('pip', 'install'); + + if (flags & ModuleInstallFlags.upgrade) { + args.push('--upgrade'); + } + if (flags & ModuleInstallFlags.reInstall) { + args.push('--force-reinstall'); + } + if (flags & ModuleInstallFlags.updateDependencies) { + args.push('--upgrade-strategy', 'eager'); + } + + args.push(moduleName); + + return { + exe: 'uv', + args + }; + } +} diff --git a/src/platform/interpreter/installer/uvInstaller.unit.test.ts b/src/platform/interpreter/installer/uvInstaller.unit.test.ts new file mode 100644 index 00000000000..9b2776f503d --- /dev/null +++ b/src/platform/interpreter/installer/uvInstaller.unit.test.ts @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { mock } from 'ts-mockito'; +import { expect } from 'chai'; +import { UvInstaller } from './uvInstaller.node'; +import { IServiceContainer } from '../../ioc/types'; +import { ModuleInstallerType, ModuleInstallFlags } from './types'; +import { EnvironmentType, PythonEnvironment } from '../../pythonEnvironments/info'; +import { Environment } from '@vscode/python-extension'; + +// Mock helper to simulate getEnvironmentType +const mockGetEnvironmentType = (envType: EnvironmentType) => { + // This would need to be properly mocked in a real test environment + return envType; +}; + +describe('UV Installer', () => { + let installer: UvInstaller; + let serviceContainer: IServiceContainer; + + beforeEach(() => { + serviceContainer = mock(); + installer = new UvInstaller(serviceContainer); + }); + + it('Should have correct properties', () => { + expect(installer.name).to.equal('UV'); + expect(installer.displayName).to.equal('UV'); + expect(installer.type).to.equal(ModuleInstallerType.UV); + expect(installer.priority).to.equal(10); + }); + + it('Should support UV environments', async () => { + const mockInterpreter: PythonEnvironment = { + id: 'test-uv-env', + uri: { fsPath: '/path/to/uv/env' } as any + }; + + // This test would need to be enhanced with proper mocking + // For now, it demonstrates the expected structure + expect(installer.isSupported).to.be.a('function'); + }); + + it('Should generate correct execution args for UV pip install', async () => { + const mockInterpreter: Environment = { + id: 'test-uv-env', + path: '/path/to/uv/env', + tools: ['uv'] + } as any; + + // Test basic installation + const args = await (installer as any).getExecutionArgs('numpy', mockInterpreter, ModuleInstallFlags.None); + + expect(args.exe).to.equal('uv'); + expect(args.args).to.include('pip'); + expect(args.args).to.include('install'); + expect(args.args).to.include('numpy'); + }); + + it('Should handle upgrade flag correctly', async () => { + const mockInterpreter: Environment = { + id: 'test-uv-env', + path: '/path/to/uv/env', + tools: ['uv'] + } as any; + + const args = await (installer as any).getExecutionArgs('numpy', mockInterpreter, ModuleInstallFlags.upgrade); + + expect(args.args).to.include('--upgrade'); + }); + + it('Should handle reinstall flag correctly', async () => { + const mockInterpreter: Environment = { + id: 'test-uv-env', + path: '/path/to/uv/env', + tools: ['uv'] + } as any; + + const args = await (installer as any).getExecutionArgs('numpy', mockInterpreter, ModuleInstallFlags.reInstall); + + expect(args.args).to.include('--force-reinstall'); + }); +}); diff --git a/src/platform/interpreter/pythonEnvironmentPicker.node.ts b/src/platform/interpreter/pythonEnvironmentPicker.node.ts index a2ae336a0ae..3cffa61541f 100644 --- a/src/platform/interpreter/pythonEnvironmentPicker.node.ts +++ b/src/platform/interpreter/pythonEnvironmentPicker.node.ts @@ -16,8 +16,8 @@ export function pythonEnvironmentQuickPick(item: Environment, quickPick: BasePro item.id === quickPick.recommended?.id ? '$(star-full) ' : isCondaEnvironmentWithoutPython(item) - ? '$(warning) ' - : ''; + ? '$(warning) ' + : ''; const quickPickItem: QuickPickItem = { label: `${icon}${label}` }; quickPickItem.description = getDisplayPath( item.executable.uri || item.path, diff --git a/src/platform/interpreter/pythonExecutionFactory.node.ts b/src/platform/interpreter/pythonExecutionFactory.node.ts index bd0759dd10c..f37092933cd 100644 --- a/src/platform/interpreter/pythonExecutionFactory.node.ts +++ b/src/platform/interpreter/pythonExecutionFactory.node.ts @@ -46,8 +46,8 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { options.resource = options.resource ? options.resource : workspace.workspaceFolders?.length - ? workspace.workspaceFolders[0].uri - : undefined; + ? workspace.workspaceFolders[0].uri + : undefined; // This should never happen, but if it does ensure we never run code accidentally in untrusted workspaces. if (!workspace.isTrusted) { diff --git a/src/platform/interpreter/serviceRegistry.node.ts b/src/platform/interpreter/serviceRegistry.node.ts index e146bb4d43f..cebddc2e004 100644 --- a/src/platform/interpreter/serviceRegistry.node.ts +++ b/src/platform/interpreter/serviceRegistry.node.ts @@ -16,6 +16,7 @@ import { CondaInstaller } from './installer/condaInstaller.node'; import { PipEnvInstaller } from './installer/pipEnvInstaller.node'; import { PipInstaller } from './installer/pipInstaller.node'; import { PoetryInstaller } from './installer/poetryInstaller.node'; +import { UvInstaller } from './installer/uvInstaller.node'; import { ProductInstaller } from './installer/productInstaller.node'; import { DataScienceProductPathService } from './installer/productPath.node'; import { ProductService } from './installer/productService.node'; @@ -78,6 +79,7 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IModuleInstaller, PipInstaller); serviceManager.addSingleton(IModuleInstaller, PipEnvInstaller); serviceManager.addSingleton(IModuleInstaller, PoetryInstaller); + serviceManager.addSingleton(IModuleInstaller, UvInstaller); serviceManager.addSingleton(IInstallationChannelManager, InstallationChannelManager); serviceManager.addSingleton(IProductService, ProductService); serviceManager.addSingleton(IInstaller, ProductInstaller); diff --git a/src/platform/pythonEnvironments/info/index.ts b/src/platform/pythonEnvironments/info/index.ts index 221f597419c..216c0708d93 100644 --- a/src/platform/pythonEnvironments/info/index.ts +++ b/src/platform/pythonEnvironments/info/index.ts @@ -16,6 +16,7 @@ export enum EnvironmentType { Venv = 'Venv', Poetry = 'Poetry', VirtualEnvWrapper = 'VirtualEnvWrapper', + UV = 'UV', } /** diff --git a/src/platform/telemetry/index.ts b/src/platform/telemetry/index.ts index 95b1ef42de1..f5528913992 100644 --- a/src/platform/telemetry/index.ts +++ b/src/platform/telemetry/index.ts @@ -104,8 +104,8 @@ function sanitizeProperties(eventName: string, data: Record) { typeof data[prop] === 'string' ? data[prop] : typeof data[prop] === 'object' - ? 'object' - : data[prop].toString(); + ? 'object' + : data[prop].toString(); } catch (ex) { logger.error(`Failed to serialize ${prop} for ${eventName}`, ex); } @@ -154,19 +154,15 @@ function isPerfMeasurementOnCI(eventName: string) { ); } -export type TelemetryProperties< - E extends keyof P, - P extends IEventNamePropertyMapping = IEventNamePropertyMapping -> = P[E] extends TelemetryEventInfo - ? ExcludeType extends never | undefined - ? undefined - : ExcludeType - : undefined | undefined; +export type TelemetryProperties = + P[E] extends TelemetryEventInfo + ? ExcludeType extends never | undefined + ? undefined + : ExcludeType + : undefined | undefined; -export type TelemetryMeasures< - E extends keyof P, - P extends IEventNamePropertyMapping = IEventNamePropertyMapping -> = P[E] extends TelemetryEventInfo ? PickType, number> : undefined; +export type TelemetryMeasures = + P[E] extends TelemetryEventInfo ? PickType, number> : undefined; function sendTelemetryEventInternal

( eventName: E, diff --git a/src/standalone/chat/createVirtualEnv.python.node.ts b/src/standalone/chat/createVirtualEnv.python.node.ts index 368f86dad06..e5500b237e0 100644 --- a/src/standalone/chat/createVirtualEnv.python.node.ts +++ b/src/standalone/chat/createVirtualEnv.python.node.ts @@ -139,8 +139,8 @@ function getWorkspaceVenvOrCondaEnv(resource: Uri | undefined, api: PythonExtens resource && workspace.workspaceFolders?.length ? workspace.getWorkspaceFolder(resource) : workspace.workspaceFolders?.length === 1 - ? workspace.workspaceFolders[0] - : undefined; + ? workspace.workspaceFolders[0] + : undefined; if (!workspaceFolder) { return; } diff --git a/src/standalone/chat/helper.ts b/src/standalone/chat/helper.ts index ca061da03f9..d42ccfb3468 100644 --- a/src/standalone/chat/helper.ts +++ b/src/standalone/chat/helper.ts @@ -69,10 +69,10 @@ export abstract class BaseTool implements LanguageMod const failureCategory = isCancelled ? 'cancelled' : error - ? error instanceof BaseError - ? error.category - : 'error' - : undefined; + ? error instanceof BaseError + ? error.category + : 'error' + : undefined; const resourceHash = notebookUri ? // eslint-disable-next-line local-rules/dont-use-fspath getTelemetrySafeHashedString(notebookUri.fsPath) diff --git a/src/standalone/userJupyterServer/userServerUrlProvider.ts b/src/standalone/userJupyterServer/userServerUrlProvider.ts index 0ec631526c2..7c489a4524d 100644 --- a/src/standalone/userJupyterServer/userServerUrlProvider.ts +++ b/src/standalone/userJupyterServer/userServerUrlProvider.ts @@ -565,8 +565,8 @@ export class UserJupyterServerUrlProvider previousStep = isInsecureConnection ? 'Check Insecure Connections' : requiresPassword && jupyterServerUri.token.length === 0 - ? 'Check Passwords' - : 'Get Url'; + ? 'Check Passwords' + : 'Get Url'; if (previousStep === 'Get Url') { // If we were given a Url, then back should get out of this flow. previousStep = initialUrlWasValid && initialUrl ? undefined : 'Get Url'; diff --git a/src/telemetry.ts b/src/telemetry.ts index 14cb893d310..6185298eff0 100644 --- a/src/telemetry.ts +++ b/src/telemetry.ts @@ -148,35 +148,35 @@ type EventPropertiesData = AllEventPropertiesData; type GdprEventDefinition

= P extends never | undefined ? IEventData : keyof EventPropertiesData, number | undefined>> extends never | undefined - ? IEventData & { - measures: EventPropertiesData> | EventPropertiesData>; - } - : keyof (EventPropertiesData> & EventPropertiesData>) extends - | never - | undefined - ? IEventData & { - properties: EventPropertiesData, number | undefined>>; - } - : IEventData & { - measures: EventPropertiesData> | EventPropertiesData>; - properties: EventPropertiesData, number | undefined>>; - }; + ? IEventData & { + measures: EventPropertiesData> | EventPropertiesData>; + } + : keyof (EventPropertiesData> & EventPropertiesData>) extends + | never + | undefined + ? IEventData & { + properties: EventPropertiesData, number | undefined>>; + } + : IEventData & { + measures: EventPropertiesData> | EventPropertiesData>; + properties: EventPropertiesData, number | undefined>>; + }; type PropertyMeasureDefinition

= P extends never ? never : keyof EventPropertiesData, number | undefined>> extends never - ? { - measures: EventPropertiesData> | EventPropertiesData>; - } - : keyof (EventPropertiesData> & - EventPropertiesData>) extends never - ? { - properties: EventPropertiesData, number | undefined>>; - } - : { - measures: EventPropertiesData> | EventPropertiesData>; - properties: EventPropertiesData, number | undefined>>; - }; + ? { + measures: EventPropertiesData> | EventPropertiesData>; + } + : keyof (EventPropertiesData> & + EventPropertiesData>) extends never + ? { + properties: EventPropertiesData, number | undefined>>; + } + : { + measures: EventPropertiesData> | EventPropertiesData>; + properties: EventPropertiesData, number | undefined>>; + }; function globallySharedProperties(): PropertyMeasureDefinition['properties'] { return { diff --git a/src/test/datascience/fakeKernelConnection.node.ts b/src/test/datascience/fakeKernelConnection.node.ts index 2a9baf15b3d..de46dad0129 100644 --- a/src/test/datascience/fakeKernelConnection.node.ts +++ b/src/test/datascience/fakeKernelConnection.node.ts @@ -22,8 +22,8 @@ function deserialize(msg: string | ArrayBuffer): KernelMessage.IMessage { return typeof msg === 'string' ? JSON.parse(msg) : msg instanceof ArrayBuffer - ? jupyterLabSerialize.deserialize(msg) - : msg; + ? jupyterLabSerialize.deserialize(msg) + : msg; } function serialize(msg: KernelMessage.IMessage, protocol?: string): string | ArrayBuffer { return jupyterLabSerialize.serialize(msg, protocol); diff --git a/src/test/datascience/notebook/controllerPreferredService.ts b/src/test/datascience/notebook/controllerPreferredService.ts index 212c778a269..d3e399fd3fb 100644 --- a/src/test/datascience/notebook/controllerPreferredService.ts +++ b/src/test/datascience/notebook/controllerPreferredService.ts @@ -221,8 +221,9 @@ export class ControllerPreferredService { // & now that we have more controllers, we know more about what needs to be matched // & since we no longer have a preferred, we should probably unset the previous preferred logger.debug( - `Resetting the previous preferred controller ${this.preferredControllers.get(document) - ?.id} to default affinity for document ${getDisplayPath(document.uri)}` + `Resetting the previous preferred controller ${ + this.preferredControllers.get(document)?.id + } to default affinity for document ${getDisplayPath(document.uri)}` ); await this.preferredControllers .get(document) @@ -309,8 +310,9 @@ export class ControllerPreferredService { // & now that we have more controllers, we know more about what needs to be matched // & since we no longer have a preferred, we should probably unset the previous preferred logger.debug( - `Resetting the previous preferred controller ${this.preferredControllers.get(document) - ?.id} to default affinity for document ${getDisplayPath(document.uri)}` + `Resetting the previous preferred controller ${ + this.preferredControllers.get(document)?.id + } to default affinity for document ${getDisplayPath(document.uri)}` ); await this.preferredControllers .get(document) diff --git a/src/test/datascience/notebook/helper.ts b/src/test/datascience/notebook/helper.ts index 8fc66014eba..1cbc6fb5361 100644 --- a/src/test/datascience/notebook/helper.ts +++ b/src/test/datascience/notebook/helper.ts @@ -520,8 +520,9 @@ async function waitForKernelToChangeImpl( const criteria = typeof searchCriteria === 'function' ? await searchCriteria() : searchCriteria; lastCriteria = JSON.stringify(lastCriteria); logger.ci( - `Notebook select.kernel command switching to kernel id ${controller?.connection - .kind}${controller?.id}: Try ${tryCount} for ${JSON.stringify(criteria)}` + `Notebook select.kernel command switching to kernel id ${ + controller?.connection.kind + }${controller?.id}: Try ${tryCount} for ${JSON.stringify(criteria)}` ); // Send a select kernel on the active notebook editor. Keep sending it if it fails. await commands.executeCommand('notebook.selectKernel', { @@ -651,11 +652,9 @@ async function selectActiveInterpreterController(notebookEditor: NotebookEditor, controllerRegistration.getSelected(notebookEditor.notebook)?.viewType === notebookEditor.notebook.notebookType, timeout, - `Controller ${ - controller.id - } not selected for ${notebookEditor.notebook.uri.toString()}, currently selected ${controllerRegistration.getSelected( - notebookEditor.notebook - )?.id} (1)` + `Controller ${controller.id} not selected for ${notebookEditor.notebook.uri.toString()}, currently selected ${ + controllerRegistration.getSelected(notebookEditor.notebook)?.id + } (1)` ); } async function selectPythonRemoteKernelConnectionForActiveInterpreter( @@ -687,11 +686,9 @@ async function selectPythonRemoteKernelConnectionForActiveInterpreter( await waitForCondition( () => controllerRegistration.getSelected(notebookEditor.notebook)?.id === controller.id, timeout, - `Controller ${ - controller.id - } not selected for ${notebookEditor.notebook.uri.toString()}, currently selected ${controllerRegistration.getSelected( - notebookEditor.notebook - )?.id} (2)` + `Controller ${controller.id} not selected for ${notebookEditor.notebook.uri.toString()}, currently selected ${ + controllerRegistration.getSelected(notebookEditor.notebook)?.id + } (2)` ); } export async function waitForKernelToGetAutoSelected( diff --git a/src/test/datascience/notebook/nonPythonKernels.vscode.test.ts b/src/test/datascience/notebook/nonPythonKernels.vscode.test.ts index 2809bcc4288..c68aca3dfc0 100644 --- a/src/test/datascience/notebook/nonPythonKernels.vscode.test.ts +++ b/src/test/datascience/notebook/nonPythonKernels.vscode.test.ts @@ -85,9 +85,9 @@ suite('Non-Python Kernel @nonPython ', async function () { } }, defaultNotebookTestTimeout, - `Preferred controller not found for Notebook, currently preferred ${controllerPreferred.getPreferred( - notebook - )?.connection.kind}:${controllerPreferred.getPreferred(notebook)?.connection.id}`, + `Preferred controller not found for Notebook, currently preferred ${ + controllerPreferred.getPreferred(notebook)?.connection.kind + }:${controllerPreferred.getPreferred(notebook)?.connection.id}`, 500 ); }); @@ -104,9 +104,9 @@ suite('Non-Python Kernel @nonPython ', async function () { } }, defaultNotebookTestTimeout, - `Preferred controller not found for Notebook, currently preferred ${controllerPreferred.getPreferred( - notebook - )?.connection.kind}:${controllerPreferred.getPreferred(notebook)?.connection.id}`, + `Preferred controller not found for Notebook, currently preferred ${ + controllerPreferred.getPreferred(notebook)?.connection.kind + }:${controllerPreferred.getPreferred(notebook)?.connection.id}`, 500 ); const cell = await notebook.appendCodeCell('123456', 'typescript'); diff --git a/src/test/datascience/notebook/stackTraceParsing.vscode.test.ts b/src/test/datascience/notebook/stackTraceParsing.vscode.test.ts index 91bcf72a674..c0d08cdcf1a 100644 --- a/src/test/datascience/notebook/stackTraceParsing.vscode.test.ts +++ b/src/test/datascience/notebook/stackTraceParsing.vscode.test.ts @@ -118,7 +118,7 @@ myLib.throwEx()`); assert(range, 'should have found a range'); assert.equal(range.start.line, 0, 'wrong start line'); assert.equal(range.start.character, 0, 'wrong start character'); - assert.equal(range.end.line, 0), 'wrong end line'; + (assert.equal(range.end.line, 0), 'wrong end line'); assert.equal(range.end.character, 'print(1/0)'.length, 'wrong end character'); }); diff --git a/src/webviews/extension-side/dataviewer/dataViewerCommandRegistry.ts b/src/webviews/extension-side/dataviewer/dataViewerCommandRegistry.ts index 99ff4cfa170..8851a336075 100644 --- a/src/webviews/extension-side/dataviewer/dataViewerCommandRegistry.ts +++ b/src/webviews/extension-side/dataviewer/dataViewerCommandRegistry.ts @@ -160,9 +160,8 @@ export class DataViewerCommandRegistry implements IExtensionSyncActivationServic pythonEnv && (await this.dataViewerDependencyService.checkAndInstallMissingDependencies(pythonEnv)); } - const jupyterVariableDataProvider = await this.jupyterVariableDataProviderFactory.create( - requestVariable - ); + const jupyterVariableDataProvider = + await this.jupyterVariableDataProviderFactory.create(requestVariable); const dataFrameInfo = await jupyterVariableDataProvider.getDataFrameInfo(); const columnSize = dataFrameInfo?.columns?.length; if (columnSize && (await this.dataViewerChecker.isRequestedColumnSizeAllowed(columnSize))) {