From 3fa967af444d6263f20290ff4fe3f62cd31755e1 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Thu, 19 Jun 2025 15:49:02 -0700 Subject: [PATCH 1/2] add exp support --- package.json | 26 ++- src/extension.ts | 589 ++++++++++++++++++++++++----------------------- 2 files changed, 322 insertions(+), 293 deletions(-) diff --git a/package.json b/package.json index 9bee47cf..090c51ba 100644 --- a/package.json +++ b/package.json @@ -254,7 +254,8 @@ { "command": "python-envs.reportIssue", "title": "%python-envs.reportIssue.title%", - "category": "Python Environments" + "category": "Python Environments", + "when": "config.python.useEnvironmentsExtension != false" } ], "menus": { @@ -301,7 +302,7 @@ }, { "command": "python-envs.runAsTask", - "when": "true" + "when": "config.python.useEnvironmentsExtension != false" }, { "command": "python-envs.terminal.activate", @@ -326,6 +327,18 @@ { "command": "python-envs.createAny", "when": "false" + }, + { + "command": "python-envs.createNewProjectFromTemplate", + "when": "config.python.useEnvironmentsExtension != false" + }, + { + "command": "python-envs.terminal.revertStartupScriptChanges", + "when": "config.python.useEnvironmentsExtension != false" + }, + { + "command": "python-envs.reportIssue", + "when": "config.python.useEnvironmentsExtension != false" } ], "view/item/context": [ @@ -463,7 +476,8 @@ { "id": "python", "title": "Python", - "icon": "files/logo.svg" + "icon": "files/logo.svg", + "when": "config.python.useEnvironmentsExtension != false" } ] }, @@ -473,13 +487,15 @@ "id": "python-projects", "name": "Python Projects", "icon": "files/logo.svg", - "contextualTitle": "Python Projects" + "contextualTitle": "Python Projects", + "when": "config.python.useEnvironmentsExtension != false" }, { "id": "env-managers", "name": "Environment Managers", "icon": "files/logo.svg", - "contextualTitle": "Environment Managers" + "contextualTitle": "Environment Managers", + "when": "config.python.useEnvironmentsExtension != false" } ] }, diff --git a/src/extension.ts b/src/extension.ts index 7386d97e..207687a4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,7 +1,7 @@ -import { commands, extensions, ExtensionContext, LogOutputChannel, Terminal, Uri, window, workspace } from 'vscode'; +import { commands, ExtensionContext, extensions, LogOutputChannel, Terminal, Uri, window, workspace } from 'vscode'; import { PythonEnvironment, PythonEnvironmentApi, PythonProjectCreator } from './api'; import { ensureCorrectVersion } from './common/extVersion'; -import { registerLogger, traceError, traceInfo } from './common/logging'; +import { registerLogger, traceError, traceInfo, traceWarn } from './common/logging'; import { clearPersistentState, setPersistentState } from './common/persistentState'; import { newProjectSelection } from './common/pickers/managers'; import { StopWatch } from './common/stopWatch'; @@ -15,6 +15,7 @@ import { onDidChangeActiveTerminal, onDidChangeTerminalShellIntegration, } from './common/window.apis'; +import { getConfiguration } from './common/workspace.apis'; import { createManagerReady } from './features/common/managerReady'; import { AutoFindProjects } from './features/creators/autoFindProjects'; import { ExistingProjects } from './features/creators/existingProjects'; @@ -75,27 +76,27 @@ import { registerPyenvFeatures } from './managers/pyenv/main'; async function collectEnvironmentInfo( context: ExtensionContext, envManagers: EnvironmentManagers, - projectManager: PythonProjectManager + projectManager: PythonProjectManager, ): Promise { const info: string[] = []; - + try { // Extension version const extensionVersion = context.extension?.packageJSON?.version || 'unknown'; info.push(`Extension Version: ${extensionVersion}`); - + // Python extension version const pythonExtension = extensions.getExtension('ms-python.python'); const pythonVersion = pythonExtension?.packageJSON?.version || 'not installed'; info.push(`Python Extension Version: ${pythonVersion}`); - + // Environment managers const managers = envManagers.managers; info.push(`\nRegistered Environment Managers (${managers.length}):`); - managers.forEach(manager => { + managers.forEach((manager) => { info.push(` - ${manager.id} (${manager.displayName})`); }); - + // Available environments const allEnvironments: PythonEnvironment[] = []; for (const manager of managers) { @@ -106,7 +107,7 @@ async function collectEnvironmentInfo( info.push(` Error getting environments from ${manager.id}: ${err}`); } } - + info.push(`\nTotal Available Environments: ${allEnvironments.length}`); if (allEnvironments.length > 0) { info.push('Environment Details:'); @@ -117,7 +118,7 @@ async function collectEnvironmentInfo( info.push(` ... and ${allEnvironments.length - 10} more environments`); } } - + // Python projects const projects = projectManager.getProjects(); info.push(`\nPython Projects (${projects.length}):`); @@ -133,326 +134,338 @@ async function collectEnvironmentInfo( info.push(` Error getting environment: ${err}`); } } - + // Current settings (non-sensitive) const config = workspace.getConfiguration('python-envs'); info.push('\nExtension Settings:'); info.push(` Default Environment Manager: ${config.get('defaultEnvManager')}`); info.push(` Default Package Manager: ${config.get('defaultPackageManager')}`); info.push(` Terminal Auto Activation: ${config.get('terminal.autoActivationType')}`); - } catch (err) { info.push(`\nError collecting environment information: ${err}`); } - + return info.join('\n'); } -export async function activate(context: ExtensionContext): Promise { - const start = new StopWatch(); +export async function activate(context: ExtensionContext): Promise { + // Add a 5 second delay before continuing activation + await new Promise((resolve) => setTimeout(resolve, 5000)); + const useEnvironmentsExtension = getConfiguration('python').get('useEnvironmentsExtension', true); + if (!useEnvironmentsExtension) { + traceWarn( + 'The Python environments extension has been disabled via a setting. If you would like to opt into using the extension, please add the following to your user settings (note that updating this setting requires a window reload afterwards):\n\n"python.useEnvironmentsExtension": true', + ); + return; + // const extension = extensions.getExtension(context.extension.id); + // if (extension) { + // await extension.deactivate(); + } else { + const start = new StopWatch(); - // Logging should be set up before anything else. - const outputChannel: LogOutputChannel = createLogOutputChannel('Python Environments'); - context.subscriptions.push(outputChannel, registerLogger(outputChannel)); + // Logging should be set up before anything else. + const outputChannel: LogOutputChannel = createLogOutputChannel('Python Environments'); + context.subscriptions.push(outputChannel, registerLogger(outputChannel)); - ensureCorrectVersion(); + ensureCorrectVersion(); - // Setup the persistent state for the extension. - setPersistentState(context); + // Setup the persistent state for the extension. + setPersistentState(context); - const statusBar = new PythonStatusBarImpl(); - context.subscriptions.push(statusBar); + const statusBar = new PythonStatusBarImpl(); + context.subscriptions.push(statusBar); - const projectManager: PythonProjectManager = new PythonProjectManagerImpl(); - context.subscriptions.push(projectManager); + const projectManager: PythonProjectManager = new PythonProjectManagerImpl(); + context.subscriptions.push(projectManager); - // Helper function to check if a resource is an existing Python project - const isExistingProject = (uri: Uri | undefined): boolean => { - if (!uri) { - return false; - } - return projectManager.get(uri) !== undefined; - }; + // Helper function to check if a resource is an existing Python project + const isExistingProject = (uri: Uri | undefined): boolean => { + if (!uri) { + return false; + } + return projectManager.get(uri) !== undefined; + }; - const envVarManager: EnvVarManager = new PythonEnvVariableManager(projectManager); - context.subscriptions.push(envVarManager); + const envVarManager: EnvVarManager = new PythonEnvVariableManager(projectManager); + context.subscriptions.push(envVarManager); - const envManagers: EnvironmentManagers = new PythonEnvironmentManagers(projectManager); - createManagerReady(envManagers, projectManager, context.subscriptions); - context.subscriptions.push(envManagers); + const envManagers: EnvironmentManagers = new PythonEnvironmentManagers(projectManager); + createManagerReady(envManagers, projectManager, context.subscriptions); + context.subscriptions.push(envManagers); - const terminalActivation = new TerminalActivationImpl(); - const shellEnvsProviders = createShellEnvProviders(); - const shellStartupProviders = createShellStartupProviders(); + const terminalActivation = new TerminalActivationImpl(); + const shellEnvsProviders = createShellEnvProviders(); + const shellStartupProviders = createShellStartupProviders(); - const terminalManager: TerminalManager = new TerminalManagerImpl( - terminalActivation, - shellEnvsProviders, - shellStartupProviders, - ); - context.subscriptions.push(terminalActivation, terminalManager); + const terminalManager: TerminalManager = new TerminalManagerImpl( + terminalActivation, + shellEnvsProviders, + shellStartupProviders, + ); + context.subscriptions.push(terminalActivation, terminalManager); - const projectCreators: ProjectCreators = new ProjectCreatorsImpl(); - context.subscriptions.push( - projectCreators, - projectCreators.registerPythonProjectCreator(new ExistingProjects(projectManager)), - projectCreators.registerPythonProjectCreator(new AutoFindProjects(projectManager)), - projectCreators.registerPythonProjectCreator(new NewPackageProject(envManagers, projectManager)), - projectCreators.registerPythonProjectCreator(new NewScriptProject(projectManager)), - ); + const projectCreators: ProjectCreators = new ProjectCreatorsImpl(); + context.subscriptions.push( + projectCreators, + projectCreators.registerPythonProjectCreator(new ExistingProjects(projectManager)), + projectCreators.registerPythonProjectCreator(new AutoFindProjects(projectManager)), + projectCreators.registerPythonProjectCreator(new NewPackageProject(envManagers, projectManager)), + projectCreators.registerPythonProjectCreator(new NewScriptProject(projectManager)), + ); - setPythonApi(envManagers, projectManager, projectCreators, terminalManager, envVarManager); - const api = await getPythonApi(); - const sysPythonManager = createDeferred(); - const managerView = new EnvManagerView(envManagers); - context.subscriptions.push(managerView); + setPythonApi(envManagers, projectManager, projectCreators, terminalManager, envVarManager); + const api = await getPythonApi(); + const sysPythonManager = createDeferred(); + const managerView = new EnvManagerView(envManagers); + context.subscriptions.push(managerView); - const workspaceView = new ProjectView(envManagers, projectManager); - context.subscriptions.push(workspaceView); - workspaceView.initialize(); + const workspaceView = new ProjectView(envManagers, projectManager); + context.subscriptions.push(workspaceView); + workspaceView.initialize(); - const monitoredTerminals = new Map(); - const shellStartupVarsMgr = new ShellStartupActivationVariablesManagerImpl( - context.environmentVariableCollection, - shellEnvsProviders, - api, - ); + const monitoredTerminals = new Map(); + const shellStartupVarsMgr = new ShellStartupActivationVariablesManagerImpl( + context.environmentVariableCollection, + shellEnvsProviders, + api, + ); - context.subscriptions.push( - shellStartupVarsMgr, - registerCompletionProvider(envManagers), - commands.registerCommand('python-envs.terminal.revertStartupScriptChanges', async () => { - await cleanupStartupScripts(shellStartupProviders); - }), - commands.registerCommand('python-envs.viewLogs', () => outputChannel.show()), - commands.registerCommand('python-envs.refreshAllManagers', async () => { - await Promise.all(envManagers.managers.map((m) => m.refresh(undefined))); - }), - commands.registerCommand('python-envs.refreshPackages', async (item) => { - await refreshPackagesCommand(item, envManagers); - }), - commands.registerCommand('python-envs.create', async (item) => { - return await createEnvironmentCommand(item, envManagers, projectManager); - }), - commands.registerCommand('python-envs.createAny', async (options) => { - return await createAnyEnvironmentCommand( - envManagers, - projectManager, - options ?? { selectEnvironment: true }, - ); - }), - commands.registerCommand('python-envs.remove', async (item) => { - await removeEnvironmentCommand(item, envManagers); - }), - commands.registerCommand('python-envs.packages', async (options: unknown) => { - const { environment, packageManager } = await getPackageCommandOptions( - options, - envManagers, - projectManager, - ); - try { - packageManager.manage(environment, { install: [] }); - } catch (err) { - traceError('Error when running command python-envs.packages', err); - } - }), - commands.registerCommand('python-envs.uninstallPackage', async (context: unknown) => { - await handlePackageUninstall(context, envManagers); - }), - commands.registerCommand('python-envs.set', async (item) => { - await setEnvironmentCommand(item, envManagers, projectManager); - }), - commands.registerCommand('python-envs.setEnv', async (item) => { - await setEnvironmentCommand(item, envManagers, projectManager); - }), - commands.registerCommand('python-envs.setEnvManager', async () => { - await setEnvManagerCommand(envManagers, projectManager); - }), - commands.registerCommand('python-envs.setPkgManager', async () => { - await setPackageManagerCommand(envManagers, projectManager); - }), - commands.registerCommand('python-envs.addPythonProject', async () => { - await addPythonProjectCommand(undefined, projectManager, envManagers, projectCreators); - }), - commands.registerCommand('python-envs.addPythonProjectGivenResource', async (resource) => { - // Set context to show/hide menu item depending on whether the resource is already a Python project - if (resource instanceof Uri) { - commands.executeCommand('setContext', 'python-envs:isExistingProject', isExistingProject(resource)); - } - await addPythonProjectCommand(resource, projectManager, envManagers, projectCreators); - }), - commands.registerCommand('python-envs.removePythonProject', async (item) => { - // Clear environment association before removing project - if (item instanceof ProjectItem) { - const uri = item.project.uri; - const manager = envManagers.getEnvironmentManager(uri); - if (manager) { - manager.set(uri, undefined); - } else { - traceError(`No environment manager found for ${uri.fsPath}`); + context.subscriptions.push( + shellStartupVarsMgr, + registerCompletionProvider(envManagers), + commands.registerCommand('python-envs.terminal.revertStartupScriptChanges', async () => { + await cleanupStartupScripts(shellStartupProviders); + }), + commands.registerCommand('python-envs.viewLogs', () => outputChannel.show()), + commands.registerCommand('python-envs.refreshAllManagers', async () => { + await Promise.all(envManagers.managers.map((m) => m.refresh(undefined))); + }), + commands.registerCommand('python-envs.refreshPackages', async (item) => { + await refreshPackagesCommand(item, envManagers); + }), + commands.registerCommand('python-envs.create', async (item) => { + return await createEnvironmentCommand(item, envManagers, projectManager); + }), + commands.registerCommand('python-envs.createAny', async (options) => { + return await createAnyEnvironmentCommand( + envManagers, + projectManager, + options ?? { selectEnvironment: true }, + ); + }), + commands.registerCommand('python-envs.remove', async (item) => { + await removeEnvironmentCommand(item, envManagers); + }), + commands.registerCommand('python-envs.packages', async (options: unknown) => { + const { environment, packageManager } = await getPackageCommandOptions( + options, + envManagers, + projectManager, + ); + try { + packageManager.manage(environment, { install: [] }); + } catch (err) { + traceError('Error when running command python-envs.packages', err); } - } - await removePythonProject(item, projectManager); - }), - commands.registerCommand('python-envs.clearCache', async () => { - await clearPersistentState(); - await envManagers.clearCache(undefined); - await clearShellProfileCache(shellStartupProviders); - }), - commands.registerCommand('python-envs.runInTerminal', (item) => { - return runInTerminalCommand(item, api, terminalManager); - }), - commands.registerCommand('python-envs.runInDedicatedTerminal', (item) => { - return runInDedicatedTerminalCommand(item, api, terminalManager); - }), - commands.registerCommand('python-envs.runAsTask', (item) => { - return runAsTaskCommand(item, api); - }), - commands.registerCommand('python-envs.createTerminal', (item) => { - return createTerminalCommand(item, api, terminalManager); - }), - commands.registerCommand('python-envs.copyEnvPath', async (item) => { - await copyPathToClipboard(item); - }), - commands.registerCommand('python-envs.copyProjectPath', async (item) => { - await copyPathToClipboard(item); - }), - commands.registerCommand('python-envs.terminal.activate', async () => { - const terminal = activeTerminal(); - if (terminal) { - const env = await getEnvironmentForTerminal(api, terminal); - if (env) { - await terminalManager.activate(terminal, env); + }), + commands.registerCommand('python-envs.uninstallPackage', async (context: unknown) => { + await handlePackageUninstall(context, envManagers); + }), + commands.registerCommand('python-envs.set', async (item) => { + await setEnvironmentCommand(item, envManagers, projectManager); + }), + commands.registerCommand('python-envs.setEnv', async (item) => { + await setEnvironmentCommand(item, envManagers, projectManager); + }), + commands.registerCommand('python-envs.setEnvManager', async () => { + await setEnvManagerCommand(envManagers, projectManager); + }), + commands.registerCommand('python-envs.setPkgManager', async () => { + await setPackageManagerCommand(envManagers, projectManager); + }), + commands.registerCommand('python-envs.addPythonProject', async () => { + await addPythonProjectCommand(undefined, projectManager, envManagers, projectCreators); + }), + commands.registerCommand('python-envs.addPythonProjectGivenResource', async (resource) => { + // Set context to show/hide menu item depending on whether the resource is already a Python project + if (resource instanceof Uri) { + commands.executeCommand('setContext', 'python-envs:isExistingProject', isExistingProject(resource)); } - } - }), - commands.registerCommand('python-envs.terminal.deactivate', async () => { - const terminal = activeTerminal(); - if (terminal) { - await terminalManager.deactivate(terminal); - } - }), - commands.registerCommand( - 'python-envs.createNewProjectFromTemplate', - async (projectType: string, quickCreate: boolean, newProjectName: string, newProjectPath: string) => { - if (quickCreate) { - if (!projectType || !newProjectName || !newProjectPath) { - throw new Error('Project type, name, and path are required for quick create.'); - } - const creators = projectCreators.getProjectCreators(); - let selected: PythonProjectCreator | undefined; - if (projectType === 'python-package') { - selected = creators.find((c) => c.name === 'newPackage'); + await addPythonProjectCommand(resource, projectManager, envManagers, projectCreators); + }), + commands.registerCommand('python-envs.removePythonProject', async (item) => { + // Clear environment association before removing project + if (item instanceof ProjectItem) { + const uri = item.project.uri; + const manager = envManagers.getEnvironmentManager(uri); + if (manager) { + manager.set(uri, undefined); + } else { + traceError(`No environment manager found for ${uri.fsPath}`); } - if (projectType === 'python-script') { - selected = creators.find((c) => c.name === 'newScript'); + } + await removePythonProject(item, projectManager); + }), + commands.registerCommand('python-envs.clearCache', async () => { + await clearPersistentState(); + await envManagers.clearCache(undefined); + await clearShellProfileCache(shellStartupProviders); + }), + commands.registerCommand('python-envs.runInTerminal', (item) => { + return runInTerminalCommand(item, api, terminalManager); + }), + commands.registerCommand('python-envs.runInDedicatedTerminal', (item) => { + return runInDedicatedTerminalCommand(item, api, terminalManager); + }), + commands.registerCommand('python-envs.runAsTask', (item) => { + return runAsTaskCommand(item, api); + }), + commands.registerCommand('python-envs.createTerminal', (item) => { + return createTerminalCommand(item, api, terminalManager); + }), + commands.registerCommand('python-envs.copyEnvPath', async (item) => { + await copyPathToClipboard(item); + }), + commands.registerCommand('python-envs.copyProjectPath', async (item) => { + await copyPathToClipboard(item); + }), + commands.registerCommand('python-envs.terminal.activate', async () => { + const terminal = activeTerminal(); + if (terminal) { + const env = await getEnvironmentForTerminal(api, terminal); + if (env) { + await terminalManager.activate(terminal, env); } - if (!selected) { - throw new Error(`Project creator for type "${projectType}" not found.`); + } + }), + commands.registerCommand('python-envs.terminal.deactivate', async () => { + const terminal = activeTerminal(); + if (terminal) { + await terminalManager.deactivate(terminal); + } + }), + commands.registerCommand( + 'python-envs.createNewProjectFromTemplate', + async (projectType: string, quickCreate: boolean, newProjectName: string, newProjectPath: string) => { + if (quickCreate) { + if (!projectType || !newProjectName || !newProjectPath) { + throw new Error('Project type, name, and path are required for quick create.'); + } + const creators = projectCreators.getProjectCreators(); + let selected: PythonProjectCreator | undefined; + if (projectType === 'python-package') { + selected = creators.find((c) => c.name === 'newPackage'); + } + if (projectType === 'python-script') { + selected = creators.find((c) => c.name === 'newScript'); + } + if (!selected) { + throw new Error(`Project creator for type "${projectType}" not found.`); + } + await selected.create({ + quickCreate: true, + name: newProjectName, + rootUri: Uri.file(newProjectPath), + }); + } else { + const selected = await newProjectSelection(projectCreators.getProjectCreators()); + if (selected) { + await selected.create(); + } } - await selected.create({ - quickCreate: true, - name: newProjectName, - rootUri: Uri.file(newProjectPath), + }, + ), + commands.registerCommand('python-envs.reportIssue', async () => { + try { + const issueData = await collectEnvironmentInfo(context, envManagers, projectManager); + + await commands.executeCommand('workbench.action.openIssueReporter', { + extensionId: 'ms-python.vscode-python-envs', + issueTitle: '[Python Environments] ', + issueBody: `\n\n\n\n
\nEnvironment Information\n\n\`\`\`\n${issueData}\n\`\`\`\n\n
`, }); - } else { - const selected = await newProjectSelection(projectCreators.getProjectCreators()); - if (selected) { - await selected.create(); - } - } - }, - ), - commands.registerCommand('python-envs.reportIssue', async () => { - try { - const issueData = await collectEnvironmentInfo(context, envManagers, projectManager); - - await commands.executeCommand('workbench.action.openIssueReporter', { - extensionId: 'ms-python.vscode-python-envs', - issueTitle: '[Python Environments] ', - issueBody: `\n\n\n\n
\nEnvironment Information\n\n\`\`\`\n${issueData}\n\`\`\`\n\n
` - }); - } catch (error) { - window.showErrorMessage(`Failed to open issue reporter: ${error}`); - } - }), - terminalActivation.onDidChangeTerminalActivationState(async (e) => { - await setActivateMenuButtonContext(e.terminal, e.environment, e.activated); - }), - onDidChangeActiveTerminal(async (t) => { - if (t) { - const env = terminalActivation.getEnvironment(t) ?? (await getEnvironmentForTerminal(api, t)); - if (env) { - await setActivateMenuButtonContext(t, env, terminalActivation.isActivated(t)); + } catch (error) { + window.showErrorMessage(`Failed to open issue reporter: ${error}`); } - } - }), - window.onDidChangeActiveTextEditor(async () => { - updateViewsAndStatus(statusBar, workspaceView, managerView, api); - }), - envManagers.onDidChangeEnvironment(async () => { - updateViewsAndStatus(statusBar, workspaceView, managerView, api); - }), - envManagers.onDidChangeEnvironments(async () => { - updateViewsAndStatus(statusBar, workspaceView, managerView, api); - }), - envManagers.onDidChangeEnvironmentFiltered(async (e) => { - managerView.environmentChanged(e); - const location = e.uri?.fsPath ?? 'global'; - traceInfo( - `Internal: Changed environment from ${e.old?.displayName} to ${e.new?.displayName} for: ${location}`, - ); - updateViewsAndStatus(statusBar, workspaceView, managerView, api); - }), - onDidChangeTerminalShellIntegration(async (e) => { - const shellEnv = e.shellIntegration?.env; - if (!shellEnv) { - return; - } - const envVar = shellEnv.value; - if (envVar) { - if (envVar['VIRTUAL_ENV']) { - const envPath = normalizeShellPath(envVar['VIRTUAL_ENV'], e.terminal.state.shell); - const env = await api.resolveEnvironment(Uri.file(envPath)); + }), + terminalActivation.onDidChangeTerminalActivationState(async (e) => { + await setActivateMenuButtonContext(e.terminal, e.environment, e.activated); + }), + onDidChangeActiveTerminal(async (t) => { + if (t) { + const env = terminalActivation.getEnvironment(t) ?? (await getEnvironmentForTerminal(api, t)); if (env) { - monitoredTerminals.set(e.terminal, env); - terminalActivation.updateActivationState(e.terminal, env, true); + await setActivateMenuButtonContext(t, env, terminalActivation.isActivated(t)); } - } else if (monitoredTerminals.has(e.terminal)) { - const env = monitoredTerminals.get(e.terminal); - if (env) { - terminalActivation.updateActivationState(e.terminal, env, false); + } + }), + window.onDidChangeActiveTextEditor(async () => { + updateViewsAndStatus(statusBar, workspaceView, managerView, api); + }), + envManagers.onDidChangeEnvironment(async () => { + updateViewsAndStatus(statusBar, workspaceView, managerView, api); + }), + envManagers.onDidChangeEnvironments(async () => { + updateViewsAndStatus(statusBar, workspaceView, managerView, api); + }), + envManagers.onDidChangeEnvironmentFiltered(async (e) => { + managerView.environmentChanged(e); + const location = e.uri?.fsPath ?? 'global'; + traceInfo( + `Internal: Changed environment from ${e.old?.displayName} to ${e.new?.displayName} for: ${location}`, + ); + updateViewsAndStatus(statusBar, workspaceView, managerView, api); + }), + onDidChangeTerminalShellIntegration(async (e) => { + const shellEnv = e.shellIntegration?.env; + if (!shellEnv) { + return; + } + const envVar = shellEnv.value; + if (envVar) { + if (envVar['VIRTUAL_ENV']) { + const envPath = normalizeShellPath(envVar['VIRTUAL_ENV'], e.terminal.state.shell); + const env = await api.resolveEnvironment(Uri.file(envPath)); + if (env) { + monitoredTerminals.set(e.terminal, env); + terminalActivation.updateActivationState(e.terminal, env, true); + } + } else if (monitoredTerminals.has(e.terminal)) { + const env = monitoredTerminals.get(e.terminal); + if (env) { + terminalActivation.updateActivationState(e.terminal, env, false); + } } } - } - }), - ); + }), + ); - /** - * Below are all the contributed features using the APIs. - */ - setImmediate(async () => { - // This is the finder that is used by all the built in environment managers - const nativeFinder: NativePythonFinder = await createNativePythonFinder(outputChannel, api, context); - context.subscriptions.push(nativeFinder); - const sysMgr = new SysPythonManager(nativeFinder, api, outputChannel); - sysPythonManager.resolve(sysMgr); - await Promise.all([ - registerSystemPythonFeatures(nativeFinder, context.subscriptions, outputChannel, sysMgr), - registerCondaFeatures(nativeFinder, context.subscriptions, outputChannel), - registerPyenvFeatures(nativeFinder, context.subscriptions), - registerPoetryFeatures(nativeFinder, context.subscriptions, outputChannel), - shellStartupVarsMgr.initialize(), - ]); + /** + * Below are all the contributed features using the APIs. + */ + setImmediate(async () => { + // This is the finder that is used by all the built in environment managers + const nativeFinder: NativePythonFinder = await createNativePythonFinder(outputChannel, api, context); + context.subscriptions.push(nativeFinder); + const sysMgr = new SysPythonManager(nativeFinder, api, outputChannel); + sysPythonManager.resolve(sysMgr); + await Promise.all([ + registerSystemPythonFeatures(nativeFinder, context.subscriptions, outputChannel, sysMgr), + registerCondaFeatures(nativeFinder, context.subscriptions, outputChannel), + registerPyenvFeatures(nativeFinder, context.subscriptions), + registerPoetryFeatures(nativeFinder, context.subscriptions, outputChannel), + shellStartupVarsMgr.initialize(), + ]); - sendTelemetryEvent(EventNames.EXTENSION_MANAGER_REGISTRATION_DURATION, start.elapsedTime); - await terminalManager.initialize(api); - sendManagerSelectionTelemetry(projectManager); - }); + sendTelemetryEvent(EventNames.EXTENSION_MANAGER_REGISTRATION_DURATION, start.elapsedTime); + await terminalManager.initialize(api); + sendManagerSelectionTelemetry(projectManager); + }); - sendTelemetryEvent(EventNames.EXTENSION_ACTIVATION_DURATION, start.elapsedTime); + sendTelemetryEvent(EventNames.EXTENSION_ACTIVATION_DURATION, start.elapsedTime); - return api; + return api; + } } export function deactivate() {} From c19ce7d0ba0cebb8207bd3dba29a816c712da628 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Mon, 23 Jun 2025 10:54:48 -0700 Subject: [PATCH 2/2] handle disposables --- src/extension.ts | 29 +++++++++++++++++++++++------ src/managers/common/types.ts | 4 ++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 207687a4..b82047d1 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -66,6 +66,7 @@ import { EnvironmentManagers, ProjectCreators, PythonProjectManager } from './in import { registerSystemPythonFeatures } from './managers/builtin/main'; import { SysPythonManager } from './managers/builtin/sysPythonManager'; import { createNativePythonFinder, NativePythonFinder } from './managers/common/nativePythonFinder'; +import { IDisposable } from './managers/common/types'; import { registerCondaFeatures } from './managers/conda/main'; import { registerPoetryFeatures } from './managers/poetry/main'; import { registerPyenvFeatures } from './managers/pyenv/main'; @@ -149,17 +150,13 @@ async function collectEnvironmentInfo( } export async function activate(context: ExtensionContext): Promise { - // Add a 5 second delay before continuing activation - await new Promise((resolve) => setTimeout(resolve, 5000)); const useEnvironmentsExtension = getConfiguration('python').get('useEnvironmentsExtension', true); if (!useEnvironmentsExtension) { traceWarn( 'The Python environments extension has been disabled via a setting. If you would like to opt into using the extension, please add the following to your user settings (note that updating this setting requires a window reload afterwards):\n\n"python.useEnvironmentsExtension": true', ); + deactivate(context); return; - // const extension = extensions.getExtension(context.extension.id); - // if (extension) { - // await extension.deactivate(); } else { const start = new StopWatch(); @@ -468,4 +465,24 @@ export async function activate(context: ExtensionContext): Promise { + await Promise.all( + disposables.map(async (d) => { + try { + return Promise.resolve(d.dispose()); + } catch (_err) { + // do nothing + } + return Promise.resolve(); + }), + ); +} + +export async function deactivate(context: ExtensionContext) { + await disposeAll(context.subscriptions); + context.subscriptions.length = 0; // Clear subscriptions to prevent memory leaks + traceInfo('Python Environments extension deactivated.'); +} diff --git a/src/managers/common/types.ts b/src/managers/common/types.ts index b6e518dc..a756271d 100644 --- a/src/managers/common/types.ts +++ b/src/managers/common/types.ts @@ -43,3 +43,7 @@ export interface Installable { */ readonly uri?: Uri; } + +export interface IDisposable { + dispose(): void | undefined | Promise; +}