Skip to content

Commit 2e85142

Browse files
committed
Prevent double/triple activation from two extensions
1 parent bec2bbd commit 2e85142

File tree

7 files changed

+122
-5
lines changed

7 files changed

+122
-5
lines changed

src/client/common/terminal/activator/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { IConfigurationService, IExperimentService } from '../../types';
99
import { ITerminalActivationHandler, ITerminalActivator, ITerminalHelper, TerminalActivationOptions } from '../types';
1010
import { BaseTerminalActivator } from './base';
1111
import { inTerminalEnvVarExperiment } from '../../experiments/helpers';
12-
import { useEnvExtension } from '../../../envExt/api.internal';
12+
import { shouldEnvExtHandleActivation } from '../../../envExt/api.internal';
1313
import { EventName } from '../../../telemetry/constants';
1414
import { sendTelemetryEvent } from '../../../telemetry';
1515

@@ -44,8 +44,8 @@ export class TerminalActivator implements ITerminalActivator {
4444
const settings = this.configurationService.getSettings(options?.resource);
4545
const activateEnvironment =
4646
settings.terminal.activateEnvironment && !inTerminalEnvVarExperiment(this.experimentService);
47-
if (!activateEnvironment || options?.hideFromUser || useEnvExtension()) {
48-
if (useEnvExtension()) {
47+
if (!activateEnvironment || options?.hideFromUser || shouldEnvExtHandleActivation()) {
48+
if (shouldEnvExtHandleActivation()) {
4949
sendTelemetryEvent(EventName.PYTHON_INTERPRETER_ACTIVATION_FOR_TERMINAL);
5050
}
5151
return false;

src/client/envExt/api.internal.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,27 @@ import { Interpreters } from '../common/utils/localize';
1919

2020
export const ENVS_EXTENSION_ID = 'ms-python.vscode-python-envs';
2121

22+
export function isEnvExtensionInstalled(): boolean {
23+
return !!getExtension(ENVS_EXTENSION_ID);
24+
}
25+
26+
/**
27+
* Returns true if the Python Environments extension is installed and not explicitly
28+
* disabled by the user. Mirrors the envs extension's own activation logic: it
29+
* deactivates only when `python.useEnvironmentsExtension` is explicitly set to false.
30+
*/
31+
export function shouldEnvExtHandleActivation(): boolean {
32+
if (!isEnvExtensionInstalled()) {
33+
return false;
34+
}
35+
const config = getConfiguration('python');
36+
const inspection = config.inspect<boolean>('useEnvironmentsExtension');
37+
if (inspection?.globalValue === false || inspection?.workspaceValue === false) {
38+
return false;
39+
}
40+
return true;
41+
}
42+
2243
let _useExt: boolean | undefined;
2344
export function useEnvExtension(): boolean {
2445
if (_useExt !== undefined) {

src/client/providers/terminalProvider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { swallowExceptions } from '../common/utils/decorators';
1111
import { IServiceContainer } from '../ioc/types';
1212
import { captureTelemetry, sendTelemetryEvent } from '../telemetry';
1313
import { EventName } from '../telemetry/constants';
14-
import { useEnvExtension } from '../envExt/api.internal';
14+
import { useEnvExtension, shouldEnvExtHandleActivation } from '../envExt/api.internal';
1515

1616
export class TerminalProvider implements Disposable {
1717
private disposables: Disposable[] = [];
@@ -33,7 +33,7 @@ export class TerminalProvider implements Disposable {
3333
currentTerminal &&
3434
pythonSettings.terminal.activateEnvInCurrentTerminal &&
3535
!inTerminalEnvVarExperiment(experimentService) &&
36-
!useEnvExtension()
36+
!shouldEnvExtHandleActivation()
3737
) {
3838
const hideFromUser =
3939
'hideFromUser' in currentTerminal.creationOptions && currentTerminal.creationOptions.hideFromUser;

src/client/terminals/activation.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { IActiveResourceService, ITerminalManager } from '../common/application/
99
import { ITerminalActivator } from '../common/terminal/types';
1010
import { IDisposable, IDisposableRegistry } from '../common/types';
1111
import { ITerminalAutoActivation } from './types';
12+
import { shouldEnvExtHandleActivation } from '../envExt/api.internal';
1213

1314
@injectable()
1415
export class TerminalAutoActivation implements ITerminalAutoActivation {
@@ -49,6 +50,9 @@ export class TerminalAutoActivation implements ITerminalAutoActivation {
4950
if (this.terminalsNotToAutoActivate.has(terminal)) {
5051
return;
5152
}
53+
if (shouldEnvExtHandleActivation()) {
54+
return;
55+
}
5256
if ('hideFromUser' in terminal.creationOptions && terminal.creationOptions.hideFromUser) {
5357
return;
5458
}

src/test/common/terminals/activator/index.unit.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
ITerminalSettings,
2121
} from '../../../../client/common/types';
2222
import * as extapi from '../../../../client/envExt/api.internal';
23+
import * as workspaceApis from '../../../../client/common/vscodeApis/workspaceApis';
24+
import * as extensionsApi from '../../../../client/common/vscodeApis/extensionsApi';
2325

2426
suite('Terminal Activator', () => {
2527
let activator: TerminalActivator;
@@ -29,9 +31,12 @@ suite('Terminal Activator', () => {
2931
let terminalSettings: TypeMoq.IMock<ITerminalSettings>;
3032
let experimentService: TypeMoq.IMock<IExperimentService>;
3133
let useEnvExtensionStub: sinon.SinonStub;
34+
let shouldEnvExtHandleActivationStub: sinon.SinonStub;
3235
setup(() => {
3336
useEnvExtensionStub = sinon.stub(extapi, 'useEnvExtension');
3437
useEnvExtensionStub.returns(false);
38+
shouldEnvExtHandleActivationStub = sinon.stub(extapi, 'shouldEnvExtHandleActivation');
39+
shouldEnvExtHandleActivationStub.returns(false);
3540

3641
baseActivator = TypeMoq.Mock.ofType<ITerminalActivator>();
3742
terminalSettings = TypeMoq.Mock.ofType<ITerminalSettings>();
@@ -113,4 +118,72 @@ suite('Terminal Activator', () => {
113118
test('Terminal is not activated if auto-activate setting is set to true but terminal is hidden', () =>
114119
testActivationAndHandlers(false, true, true));
115120
test('Terminal is not activated and handlers are invoked', () => testActivationAndHandlers(false, false));
121+
122+
test('Terminal is not activated from Python extension when Env extension should handle activation', async () => {
123+
shouldEnvExtHandleActivationStub.returns(true);
124+
terminalSettings.setup((b) => b.activateEnvironment).returns(() => true);
125+
baseActivator
126+
.setup((b) => b.activateEnvironmentInTerminal(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
127+
.returns(() => Promise.resolve(true))
128+
.verifiable(TypeMoq.Times.never());
129+
130+
const terminal = TypeMoq.Mock.ofType<Terminal>();
131+
const activated = await activator.activateEnvironmentInTerminal(terminal.object, {
132+
preserveFocus: true,
133+
});
134+
135+
assert.strictEqual(activated, false);
136+
baseActivator.verifyAll();
137+
});
138+
});
139+
140+
suite('shouldEnvExtHandleActivation', () => {
141+
let getExtensionStub: sinon.SinonStub;
142+
let getConfigurationStub: sinon.SinonStub;
143+
144+
setup(() => {
145+
getExtensionStub = sinon.stub(extensionsApi, 'getExtension');
146+
getConfigurationStub = sinon.stub(workspaceApis, 'getConfiguration');
147+
});
148+
149+
teardown(() => {
150+
sinon.restore();
151+
});
152+
153+
test('Returns false when envs extension is not installed', () => {
154+
getExtensionStub.returns(undefined);
155+
assert.strictEqual(extapi.shouldEnvExtHandleActivation(), false);
156+
});
157+
158+
test('Returns true when envs extension is installed and setting is not explicitly set', () => {
159+
getExtensionStub.returns({ id: extapi.ENVS_EXTENSION_ID });
160+
getConfigurationStub.returns({
161+
inspect: () => ({ globalValue: undefined, workspaceValue: undefined }),
162+
});
163+
assert.strictEqual(extapi.shouldEnvExtHandleActivation(), true);
164+
});
165+
166+
test('Returns false when envs extension is installed but globalValue is false', () => {
167+
getExtensionStub.returns({ id: extapi.ENVS_EXTENSION_ID });
168+
getConfigurationStub.returns({
169+
inspect: () => ({ globalValue: false, workspaceValue: undefined }),
170+
});
171+
assert.strictEqual(extapi.shouldEnvExtHandleActivation(), false);
172+
});
173+
174+
test('Returns false when envs extension is installed but workspaceValue is false', () => {
175+
getExtensionStub.returns({ id: extapi.ENVS_EXTENSION_ID });
176+
getConfigurationStub.returns({
177+
inspect: () => ({ globalValue: undefined, workspaceValue: false }),
178+
});
179+
assert.strictEqual(extapi.shouldEnvExtHandleActivation(), false);
180+
});
181+
182+
test('Returns true when envs extension is installed and setting is explicitly true', () => {
183+
getExtensionStub.returns({ id: extapi.ENVS_EXTENSION_ID });
184+
getConfigurationStub.returns({
185+
inspect: () => ({ globalValue: true, workspaceValue: undefined }),
186+
});
187+
assert.strictEqual(extapi.shouldEnvExtHandleActivation(), true);
188+
});
116189
});

src/test/providers/terminal.unit.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@ suite('Terminal Provider', () => {
2929
let experimentService: TypeMoq.IMock<IExperimentService>;
3030
let terminalProvider: TerminalProvider;
3131
let useEnvExtensionStub: sinon.SinonStub;
32+
let shouldEnvExtHandleActivationStub: sinon.SinonStub;
3233
const resource = Uri.parse('a');
3334
setup(() => {
3435
useEnvExtensionStub = sinon.stub(extapi, 'useEnvExtension');
3536
useEnvExtensionStub.returns(false);
37+
shouldEnvExtHandleActivationStub = sinon.stub(extapi, 'shouldEnvExtHandleActivation');
38+
shouldEnvExtHandleActivationStub.returns(false);
3639

3740
serviceContainer = TypeMoq.Mock.ofType<IServiceContainer>();
3841
commandManager = TypeMoq.Mock.ofType<ICommandManager>();

src/test/terminals/activation.unit.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
import { anything, instance, mock, verify, when } from 'ts-mockito';
5+
import * as sinon from 'sinon';
56
import { EventEmitter, Terminal } from 'vscode';
67
import { ActiveResourceService } from '../../client/common/application/activeResource';
78
import { TerminalManager } from '../../client/common/application/terminalManager';
@@ -11,6 +12,7 @@ import { ITerminalActivator } from '../../client/common/terminal/types';
1112
import { TerminalAutoActivation } from '../../client/terminals/activation';
1213
import { ITerminalAutoActivation } from '../../client/terminals/types';
1314
import { noop } from '../core';
15+
import * as extapi from '../../client/envExt/api.internal';
1416

1517
suite('Terminal', () => {
1618
suite('Terminal Auto Activation', () => {
@@ -21,8 +23,12 @@ suite('Terminal', () => {
2123
let onDidOpenTerminalEventEmitter: EventEmitter<Terminal>;
2224
let terminal: Terminal;
2325
let nonActivatedTerminal: Terminal;
26+
let shouldEnvExtHandleActivationStub: sinon.SinonStub;
2427

2528
setup(() => {
29+
shouldEnvExtHandleActivationStub = sinon.stub(extapi, 'shouldEnvExtHandleActivation');
30+
shouldEnvExtHandleActivationStub.returns(false);
31+
2632
manager = mock(TerminalManager);
2733
activator = mock(TerminalActivator);
2834
resourceService = mock(ActiveResourceService);
@@ -60,6 +66,9 @@ suite('Terminal', () => {
6066
autoActivation.register();
6167
});
6268
// teardown(() => fakeTimer.uninstall());
69+
teardown(() => {
70+
sinon.restore();
71+
});
6372

6473
test('Should activate terminal', async () => {
6574
// Trigger opening a terminal.
@@ -77,5 +86,12 @@ suite('Terminal', () => {
7786
// The terminal should get activated.
7887
verify(activator.activateEnvironmentInTerminal(anything(), anything())).never();
7988
});
89+
test('Should not activate terminal when envs extension should handle activation', async () => {
90+
shouldEnvExtHandleActivationStub.returns(true);
91+
92+
await ((onDidOpenTerminalEventEmitter.fire(terminal) as unknown) as Promise<void>);
93+
94+
verify(activator.activateEnvironmentInTerminal(anything(), anything())).never();
95+
});
8096
});
8197
});

0 commit comments

Comments
 (0)