Skip to content
2 changes: 1 addition & 1 deletion src/features/envCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ async function resolvePackageCommandOptions(
}

if (e instanceof Uri) {
const environment = await em.getEnvironmentManager(e)?.get(e);
const environment = await em.getEnvironment(e);
const packageManager = em.getPackageManager(e);
if (environment && packageManager) {
return { environment, packageManager };
Expand Down
172 changes: 160 additions & 12 deletions src/features/envManagers.ts

Large diffs are not rendered by default.

56 changes: 48 additions & 8 deletions src/features/interpreterSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,10 @@ async function autoDiscoverEnvironment(
* @param projectManager - The project manager
* @param nativeFinder - Native Python finder for path resolution
* @param api - The Python environment API
* @param activationToReadyDurationMs - Time from activation until environment managers were ready, for telemetry
* @param globalScopeDeferredRef - Receives whether the global scope was resolved in the background
* @param changeEvent - The settings change that triggered a re-run. The global scope is resolved again only when
* a setting it depends on changed, so a user's global selection is kept otherwise.
*/
export async function applyInitialEnvironmentSelection(
envManagers: EnvironmentManagers,
Expand All @@ -284,6 +288,7 @@ export async function applyInitialEnvironmentSelection(
api: PythonEnvironmentApi,
activationToReadyDurationMs?: number,
globalScopeDeferredRef?: { value: 'deferred' | 'not_deferred' | 'unknown' },
changeEvent?: ConfigurationChangeEvent,
): Promise<void> {
const folders = getWorkspaceFolders() ?? [];
traceInfo(
Expand Down Expand Up @@ -323,8 +328,12 @@ export async function applyInitialEnvironmentSelection(
hasPersistedSelection: env !== undefined,
});

// Cache only — NO settings.json write (shouldPersistSettings = false)
await envManagers.setEnvironment(folder.uri, env, false);
// Cache only — NO settings.json write (shouldPersistSettings = false). An environment chosen by a
// setting is explicit, so it is honored even when it is not owned by the manager that routing picks
// for the folder.
await envManagers.setEnvironment(folder.uri, env, false, {
explicit: isExplicitSelection(result, env),
});

if (env) {
workspaceFolderResolved = true;
Expand Down Expand Up @@ -363,7 +372,9 @@ export async function applyInitialEnvironmentSelection(
});

// Cache only — NO settings.json write
await envManagers.setEnvironments('global', env, false);
await envManagers.setEnvironments('global', env, false, {
explicit: isExplicitSelection(result, env),
});

traceInfo(`[interpreterSelection] global: ${env?.displayName ?? 'none'} (source: ${result.source})`);

Expand All @@ -374,7 +385,16 @@ export async function applyInitialEnvironmentSelection(
}
};

if (workspaceFolderResolved) {
// pythonProjects never applies to the global scope. Resolving it again when only that changed would replace
// a user's global selection with the unchanged result of the other settings.
const globalSettingsChanged =
!changeEvent ||
changeEvent.affectsConfiguration('python-envs.defaultEnvManager') ||
changeEvent.affectsConfiguration('python.defaultInterpreterPath');

if (!globalSettingsChanged) {
traceVerbose('[interpreterSelection] Settings for the global scope did not change, keeping its environment');
} else if (workspaceFolderResolved) {
// Defer global scope so it doesn't block post-selection startup.
traceInfo('[interpreterSelection] Workspace env resolved, deferring global scope to background');
if (globalScopeDeferredRef) {
Expand Down Expand Up @@ -646,14 +666,15 @@ async function tryResolveInterpreterPath(
}

if (manager && resolvedEnv) {
// Create a wrapper environment that uses the user's specified path
// Create a wrapper environment that uses the user's specified path, named like the
// resolved environment so the status bar and views show a normal interpreter label.
const newEnv: PythonEnvironment = {
envId: {
id: `defaultInterpreterPath:${interpreterPath}`,
managerId: manager.id,
},
name: 'defaultInterpreterPath: ' + (resolved.version ?? ''),
displayName: 'defaultInterpreterPath: ' + (resolved.version ?? ''),
name: resolvedEnv.name,
displayName: resolvedEnv.displayName,
version: resolved.version ?? '',
displayPath: interpreterPath,
environmentPath: Uri.file(interpreterPath),
Expand Down Expand Up @@ -700,7 +721,26 @@ export function registerInterpreterSettingsChangeListener(
'[interpreterSelection] Interpreter settings changed, re-evaluating priority chain for all scopes',
);
// Re-run the interpreter selection priority chain to apply new settings immediately
await applyInitialEnvironmentSelection(envManagers, projectManager, nativeFinder, api);
await applyInitialEnvironmentSelection(
envManagers,
projectManager,
nativeFinder,
api,
undefined,
undefined,
e,
);
}
});
}

/**
* Whether a priority-chain result is an explicit choice. Settings choose explicitly, except that when the manager a
* setting names has no environment of its own, the fallback it returns (owned by another manager) is not a choice.
*/
function isExplicitSelection(result: PriorityChainResult, env: PythonEnvironment | undefined): boolean {
if (result.source === 'autoDiscovery') {
return false;
}
return result.environment !== undefined || env?.envId.managerId === result.manager.id;
}
1 change: 1 addition & 0 deletions src/managers/poetry/poetryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ export class PoetryManager implements EnvironmentManager, Disposable {

async set(scope: SetEnvironmentScope, environment?: PythonEnvironment | undefined): Promise<void> {
if (scope === undefined) {
this.globalEnv = environment;
await setPoetryForGlobal(environment?.environmentPath?.fsPath);
} else if (scope instanceof Uri) {
const folder = this.api.getPythonProject(scope);
Expand Down
1 change: 1 addition & 0 deletions src/managers/pyenv/pyenvManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ export class PyEnvManager implements EnvironmentManager, Disposable {
}
async set(scope: SetEnvironmentScope, environment?: PythonEnvironment | undefined): Promise<void> {
if (scope === undefined) {
this.globalEnv = environment;
await setPyenvForGlobal(environment?.environmentPath?.fsPath);
} else if (scope instanceof Uri) {
const folder = this.api.getPythonProject(scope);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,7 @@ suite('Inline environment package command guard', () => {

function createManagers(environment: PythonEnvironment): EnvironmentManagers {
const managerMock: Partial<EnvironmentManagers> = {
getEnvironmentManager: () =>
({ get: async () => environment } as unknown as InternalEnvironmentManager),
getEnvironment: async () => environment,
getPackageManager: () => packageManager,
};
return managerMock as EnvironmentManagers;
Expand Down
Loading
Loading