From 22fd881298b6e37b8818cedd24e14e7a31c97a73 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:59:16 +0000 Subject: [PATCH 1/5] Initial plan From fecb873c9abd83de468edf77136708daf6fb0409 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 16:05:07 +0000 Subject: [PATCH 2/5] feat: add "Don't Show Again" button to env file notification Adds a "Don't Show Again" button to the information notification that appears when an environment file is configured but terminal environment injection is disabled. The preference is persisted in global state so it survives between sessions. - Add `Common.dontShowAgain` localized string - Add `ENV_FILE_NOTIFICATION_DONT_SHOW_KEY` persistent state key - Use `showInformationMessage` wrapper instead of direct `window` API - Extract notification logic to `showEnvFileNotification()` method - Add 5 unit tests covering the new behavior Fixes #419 Co-authored-by: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/vscode-python-environments/sessions/d9efb122-775e-4477-9005-10a94641311d --- src/common/localize.ts | 1 + .../terminal/terminalEnvVarInjector.ts | 34 ++++- .../terminalEnvVarInjector.unit.test.ts | 129 +++++++++++++++++- 3 files changed, 158 insertions(+), 6 deletions(-) diff --git a/src/common/localize.ts b/src/common/localize.ts index dd7c637f..ae23033a 100644 --- a/src/common/localize.ts +++ b/src/common/localize.ts @@ -15,6 +15,7 @@ export namespace Common { export const ok = l10n.t('Ok'); export const quickCreate = l10n.t('Quick Create'); export const installPython = l10n.t('Install Python'); + export const dontShowAgain = l10n.t("Don't Show Again"); } export namespace WorkbenchStrings { diff --git a/src/features/terminal/terminalEnvVarInjector.ts b/src/features/terminal/terminalEnvVarInjector.ts index 0a8d685e..418ccb6d 100644 --- a/src/features/terminal/terminalEnvVarInjector.ts +++ b/src/features/terminal/terminalEnvVarInjector.ts @@ -7,15 +7,19 @@ import { Disposable, EnvironmentVariableScope, GlobalEnvironmentVariableCollection, - window, workspace, WorkspaceFolder, } from 'vscode'; -import { traceError, traceVerbose } from '../../common/logging'; +import { Common } from '../../common/localize'; +import { traceError, traceLog, traceVerbose } from '../../common/logging'; +import { getGlobalPersistentState } from '../../common/persistentState'; import { resolveVariables } from '../../common/utils/internalVariables'; +import { showInformationMessage } from '../../common/window.apis'; import { getConfiguration, getWorkspaceFolder } from '../../common/workspace.apis'; import { EnvVarManager } from '../execution/envVariableManager'; +export const ENV_FILE_NOTIFICATION_DONT_SHOW_KEY = 'python-envs:terminal:ENV_FILE_NOTIFICATION_DONT_SHOW'; + /** * Manages injection of workspace-specific environment variables into VS Code terminals * using the GlobalEnvironmentVariableCollection API. @@ -65,9 +69,9 @@ export class TerminalEnvVarInjector implements Disposable { // Only show notification when env vars change and we have an env file but injection is disabled if (!useEnvFile && envFilePath) { - window.showInformationMessage( - 'An environment file is configured but terminal environment injection is disabled. Enable "python.terminal.useEnvFile" to use environment variables from .env files in terminals.', - ); + this.showEnvFileNotification().catch((error) => { + traceError('Failed to show env file notification:', error); + }); } if (args.changeType === 2) { @@ -208,6 +212,26 @@ export class TerminalEnvVarInjector implements Disposable { } } + /** + * Show a notification about env file injection being disabled, with a "Don't Show Again" option. + */ + private async showEnvFileNotification(): Promise { + const state = await getGlobalPersistentState(); + const dontShow = await state.get(ENV_FILE_NOTIFICATION_DONT_SHOW_KEY); + if (dontShow) { + return; + } + + const result = await showInformationMessage( + 'An environment file is configured but terminal environment injection is disabled. Enable "python.terminal.useEnvFile" to use environment variables from .env files in terminals.', + Common.dontShowAgain, + ); + if (result === Common.dontShowAgain) { + await state.set(ENV_FILE_NOTIFICATION_DONT_SHOW_KEY, true); + traceLog('User selected "Don\'t Show Again" for env file notification'); + } + } + /** * Dispose of the injector and clean up resources. */ diff --git a/src/test/features/terminalEnvVarInjector.unit.test.ts b/src/test/features/terminalEnvVarInjector.unit.test.ts index 0d5a1050..9f2b70f9 100644 --- a/src/test/features/terminalEnvVarInjector.unit.test.ts +++ b/src/test/features/terminalEnvVarInjector.unit.test.ts @@ -13,9 +13,15 @@ import { WorkspaceFolder, workspace, } from 'vscode'; +import { Common } from '../../common/localize'; +import * as persistentState from '../../common/persistentState'; +import * as windowApis from '../../common/window.apis'; import * as workspaceApis from '../../common/workspace.apis'; import { EnvVarManager } from '../../features/execution/envVariableManager'; -import { TerminalEnvVarInjector } from '../../features/terminal/terminalEnvVarInjector'; +import { + ENV_FILE_NOTIFICATION_DONT_SHOW_KEY, + TerminalEnvVarInjector, +} from '../../features/terminal/terminalEnvVarInjector'; interface MockScopedCollection { clear: sinon.SinonStub; @@ -307,4 +313,125 @@ suite('TerminalEnvVarInjector', () => { } }); }); + + suite('env file notification with Don\'t Show Again', () => { + let envChangeCallback: ((args: { uri?: Uri; changeType: number }) => void) | undefined; + let mockState: { get: sinon.SinonStub; set: sinon.SinonStub; clear: sinon.SinonStub }; + let showInfoMessageStub: sinon.SinonStub; + + setup(() => { + mockState = { + get: sinon.stub(), + set: sinon.stub().resolves(), + clear: sinon.stub().resolves(), + }; + sinon.stub(persistentState, 'getGlobalPersistentState').resolves(mockState); + showInfoMessageStub = sinon.stub(windowApis, 'showInformationMessage'); + + // Capture the onDidChangeEnvironmentVariables listener + envVarManager.reset(); + envVarManager + .setup((m) => m.onDidChangeEnvironmentVariables) + .returns(() => { + return (listener: (args: { uri?: Uri; changeType: number }) => void): Disposable => { + envChangeCallback = listener; + return new Disposable(() => {}); + }; + }); + envVarManager + .setup((m) => m.getEnvironmentVariables(typeMoq.It.isAny())) + .returns(() => Promise.resolve({})); + + sinon.stub(workspaceApis, 'getWorkspaceFolder').returns(testWorkspaceFolder); + }); + + test('should show notification with Don\'t Show Again button when env file configured but injection disabled', async () => { + getConfigurationStub.returns( + createMockConfig({ useEnvFile: false, envFilePath: '${workspaceFolder}/.env' }) as WorkspaceConfiguration, + ); + mockState.get.resolves(false); + showInfoMessageStub.resolves(undefined); + + injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object); + await new Promise((resolve) => setTimeout(resolve, 50)); + + assert.ok(envChangeCallback, 'Event handler should be registered'); + envChangeCallback!({ uri: Uri.file(testWorkspacePath), changeType: 1 }); + await new Promise((resolve) => setTimeout(resolve, 50)); + + assert.ok(showInfoMessageStub.calledOnce, 'Should show notification'); + assert.ok( + showInfoMessageStub.calledWith(sinon.match.string, Common.dontShowAgain), + 'Should include Don\'t Show Again button', + ); + }); + + test('should not show notification when Don\'t Show Again was previously selected', async () => { + getConfigurationStub.returns( + createMockConfig({ useEnvFile: false, envFilePath: '${workspaceFolder}/.env' }) as WorkspaceConfiguration, + ); + mockState.get.resolves(true); + + injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object); + await new Promise((resolve) => setTimeout(resolve, 50)); + + assert.ok(envChangeCallback, 'Event handler should be registered'); + envChangeCallback!({ uri: Uri.file(testWorkspacePath), changeType: 1 }); + await new Promise((resolve) => setTimeout(resolve, 50)); + + assert.ok(showInfoMessageStub.notCalled, 'Should not show notification when dismissed'); + }); + + test('should persist preference when Don\'t Show Again is clicked', async () => { + getConfigurationStub.returns( + createMockConfig({ useEnvFile: false, envFilePath: '${workspaceFolder}/.env' }) as WorkspaceConfiguration, + ); + mockState.get.resolves(false); + showInfoMessageStub.resolves(Common.dontShowAgain); + + injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object); + await new Promise((resolve) => setTimeout(resolve, 50)); + + assert.ok(envChangeCallback, 'Event handler should be registered'); + envChangeCallback!({ uri: Uri.file(testWorkspacePath), changeType: 1 }); + await new Promise((resolve) => setTimeout(resolve, 50)); + + assert.ok( + mockState.set.calledWith(ENV_FILE_NOTIFICATION_DONT_SHOW_KEY, true), + 'Should persist Don\'t Show Again preference', + ); + }); + + test('should not show notification when useEnvFile is true', async () => { + getConfigurationStub.returns( + createMockConfig({ useEnvFile: true, envFilePath: '${workspaceFolder}/.env' }) as WorkspaceConfiguration, + ); + mockState.get.resolves(false); + + injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object); + await new Promise((resolve) => setTimeout(resolve, 50)); + + assert.ok(envChangeCallback, 'Event handler should be registered'); + envChangeCallback!({ uri: Uri.file(testWorkspacePath), changeType: 1 }); + await new Promise((resolve) => setTimeout(resolve, 50)); + + assert.ok(showInfoMessageStub.notCalled, 'Should not show notification when useEnvFile is true'); + }); + + test('should not show notification when no envFile is configured', async () => { + getConfigurationStub.returns( + createMockConfig({ useEnvFile: false }) as WorkspaceConfiguration, + ); + mockState.get.resolves(false); + + injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object); + await new Promise((resolve) => setTimeout(resolve, 50)); + + assert.ok(envChangeCallback, 'Event handler should be registered'); + envChangeCallback!({ uri: Uri.file(testWorkspacePath), changeType: 1 }); + await new Promise((resolve) => setTimeout(resolve, 50)); + + assert.ok(showInfoMessageStub.notCalled, 'Should not show notification when no envFile configured'); + }); + }); }); From 39f75310300709f9345d0c2adf9a438f76a73f7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 16:06:57 +0000 Subject: [PATCH 3/5] style: use template literal for log message readability Co-authored-by: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/vscode-python-environments/sessions/d9efb122-775e-4477-9005-10a94641311d --- src/features/terminal/terminalEnvVarInjector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/terminal/terminalEnvVarInjector.ts b/src/features/terminal/terminalEnvVarInjector.ts index 418ccb6d..d73c403f 100644 --- a/src/features/terminal/terminalEnvVarInjector.ts +++ b/src/features/terminal/terminalEnvVarInjector.ts @@ -228,7 +228,7 @@ export class TerminalEnvVarInjector implements Disposable { ); if (result === Common.dontShowAgain) { await state.set(ENV_FILE_NOTIFICATION_DONT_SHOW_KEY, true); - traceLog('User selected "Don\'t Show Again" for env file notification'); + traceLog(`User selected "Don't Show Again" for env file notification`); } } From f799d7992a9cd81aab335d106ae0061d2e35ff9a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 05:09:38 +0000 Subject: [PATCH 4/5] refactor: localize env file notification message using l10n.t() Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/vscode-python-environments/sessions/32215d3b-54dd-4074-a6f7-4906d013f21c --- src/common/localize.ts | 3 +++ src/features/terminal/terminalEnvVarInjector.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/common/localize.ts b/src/common/localize.ts index ae23033a..72b9abb3 100644 --- a/src/common/localize.ts +++ b/src/common/localize.ts @@ -216,6 +216,9 @@ export namespace ActivationStrings { Commands.viewLogs, ); export const activatingEnvironment = l10n.t('Activating environment'); + export const envFileInjectionDisabled = l10n.t( + 'An environment file is configured but terminal environment injection is disabled. Enable "python.terminal.useEnvFile" to use environment variables from .env files in terminals.', + ); } export namespace UvInstallStrings { diff --git a/src/features/terminal/terminalEnvVarInjector.ts b/src/features/terminal/terminalEnvVarInjector.ts index d73c403f..cbf0600e 100644 --- a/src/features/terminal/terminalEnvVarInjector.ts +++ b/src/features/terminal/terminalEnvVarInjector.ts @@ -10,7 +10,7 @@ import { workspace, WorkspaceFolder, } from 'vscode'; -import { Common } from '../../common/localize'; +import { ActivationStrings, Common } from '../../common/localize'; import { traceError, traceLog, traceVerbose } from '../../common/logging'; import { getGlobalPersistentState } from '../../common/persistentState'; import { resolveVariables } from '../../common/utils/internalVariables'; @@ -223,7 +223,7 @@ export class TerminalEnvVarInjector implements Disposable { } const result = await showInformationMessage( - 'An environment file is configured but terminal environment injection is disabled. Enable "python.terminal.useEnvFile" to use environment variables from .env files in terminals.', + ActivationStrings.envFileInjectionDisabled, Common.dontShowAgain, ); if (result === Common.dontShowAgain) { From 650079025bc68030414cf3d48d5987a4326d5739 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Fri, 27 Mar 2026 08:39:09 -0700 Subject: [PATCH 5/5] update2 --- src/features/terminal/terminalEnvVarInjector.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/features/terminal/terminalEnvVarInjector.ts b/src/features/terminal/terminalEnvVarInjector.ts index cbf0600e..1af2269c 100644 --- a/src/features/terminal/terminalEnvVarInjector.ts +++ b/src/features/terminal/terminalEnvVarInjector.ts @@ -222,10 +222,7 @@ export class TerminalEnvVarInjector implements Disposable { return; } - const result = await showInformationMessage( - ActivationStrings.envFileInjectionDisabled, - Common.dontShowAgain, - ); + const result = await showInformationMessage(ActivationStrings.envFileInjectionDisabled, Common.dontShowAgain); if (result === Common.dontShowAgain) { await state.set(ENV_FILE_NOTIFICATION_DONT_SHOW_KEY, true); traceLog(`User selected "Don't Show Again" for env file notification`);