From 4584f5fd6395a2985efd4923ea9d1a6537a4b53c Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Tue, 5 May 2026 15:03:32 -0700 Subject: [PATCH 1/3] Fix duplicate command python.getRecommendedEnvironment Adds guards when calling RecommendedEnvironmentService::activate per folder on a multiroot workspace to avoid registering the command several times. --- .../configuration/recommededEnvironmentService.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/client/interpreter/configuration/recommededEnvironmentService.ts b/src/client/interpreter/configuration/recommededEnvironmentService.ts index c5356409fcee..d0045b37e1b0 100644 --- a/src/client/interpreter/configuration/recommededEnvironmentService.ts +++ b/src/client/interpreter/configuration/recommededEnvironmentService.ts @@ -17,6 +17,7 @@ const MEMENTO_KEY = 'userSelectedEnvPath'; @injectable() export class RecommendedEnvironmentService implements IRecommendedEnvironmentService, IExtensionActivationService { private api?: PythonExtension['environments']; + private hasRegisteredCommand = false; constructor(@inject(IExtensionContext) private readonly extensionContext: IExtensionContext) {} supportedWorkspaceTypes: { untrustedWorkspace: boolean; virtualWorkspace: boolean } = { untrustedWorkspace: true, @@ -24,6 +25,10 @@ export class RecommendedEnvironmentService implements IRecommendedEnvironmentSer }; async activate(_resource: Resource, _startupStopWatch?: StopWatch): Promise { + if (this.hasRegisteredCommand) { + return; + } + this.hasRegisteredCommand = true; this.extensionContext.subscriptions.push( commands.registerCommand('python.getRecommendedEnvironment', async (resource: Resource) => { return this.getRecommededEnvironment(resource); @@ -47,9 +52,7 @@ export class RecommendedEnvironmentService implements IRecommendedEnvironmentSer } } - async getRecommededEnvironment( - resource: Resource, - ): Promise< + async getRecommededEnvironment(resource: Resource): Promise< | { environment: ResolvedEnvironment; reason: 'globalUserSelected' | 'workspaceUserSelected' | 'defaultRecommended'; From 1b5cae1719c2b7e5f9f4af6288581b1149cbcc80 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Tue, 5 May 2026 15:20:45 -0700 Subject: [PATCH 2/3] Add test --- .../recommededEnvironmentService.unit.test.ts | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/test/interpreters/recommededEnvironmentService.unit.test.ts diff --git a/src/test/interpreters/recommededEnvironmentService.unit.test.ts b/src/test/interpreters/recommededEnvironmentService.unit.test.ts new file mode 100644 index 000000000000..7cb5aed58239 --- /dev/null +++ b/src/test/interpreters/recommededEnvironmentService.unit.test.ts @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { expect } from 'chai'; +import { anything, reset, verify, when } from 'ts-mockito'; +import { Disposable, Uri } from 'vscode'; +import { mockedVSCodeNamespaces } from '../vscode-mock'; +import { RecommendedEnvironmentService } from '../../client/interpreter/configuration/recommededEnvironmentService'; + +suite('RecommendedEnvironmentService - activate', () => { + let service: RecommendedEnvironmentService; + let subscriptions: Disposable[]; + + setup(() => { + subscriptions = []; + const extensionContext = { + subscriptions, + globalState: { + get: () => undefined, + update: () => Promise.resolve(), + }, + } as any; + + when(mockedVSCodeNamespaces.commands!.registerCommand(anything(), anything())).thenReturn({ + dispose: () => {}, + } as Disposable); + + service = new RecommendedEnvironmentService(extensionContext); + }); + + teardown(() => { + reset(mockedVSCodeNamespaces.commands!); + }); + + test('Multiroot workspace: command is registered only once across multiple activate calls', async () => { + // Simulate multiroot workspace where activate is called once per workspace root + const workspaceRoot1 = Uri.file('/workspace/root1'); + const workspaceRoot2 = Uri.file('/workspace/root2'); + const workspaceRoot3 = Uri.file('/workspace/root3'); + + await service.activate(workspaceRoot1); + await service.activate(workspaceRoot2); + await service.activate(workspaceRoot3); + + verify(mockedVSCodeNamespaces.commands!.registerCommand('python.getRecommendedEnvironment', anything())).once(); + expect(subscriptions).to.have.lengthOf(1); + }); +}); From ab9086a4f4a828569d9fe02f844bdf627a33e7b2 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Tue, 5 May 2026 15:54:49 -0700 Subject: [PATCH 3/3] Lint --- .../interpreter/configuration/recommededEnvironmentService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/client/interpreter/configuration/recommededEnvironmentService.ts b/src/client/interpreter/configuration/recommededEnvironmentService.ts index d0045b37e1b0..39f4edfde1d6 100644 --- a/src/client/interpreter/configuration/recommededEnvironmentService.ts +++ b/src/client/interpreter/configuration/recommededEnvironmentService.ts @@ -52,7 +52,9 @@ export class RecommendedEnvironmentService implements IRecommendedEnvironmentSer } } - async getRecommededEnvironment(resource: Resource): Promise< + async getRecommededEnvironment( + resource: Resource, + ): Promise< | { environment: ResolvedEnvironment; reason: 'globalUserSelected' | 'workspaceUserSelected' | 'defaultRecommended';