Skip to content
This repository was archived by the owner on Oct 23, 2025. It is now read-only.

Commit 38912d4

Browse files
authored
Improve shell startup experience using setting value (#921)
Resolves: microsoft/vscode-python-environments#919 which will improve on top of microsoft/vscode-python-environments#915 I want to bulletproof shell startup as much as possible. We shouldn't be showing profile modification prompt if user has shell integration. We should also ensure proper clean up.
1 parent f93faa5 commit 38912d4

File tree

5 files changed

+74
-22
lines changed

5 files changed

+74
-22
lines changed

src/features/terminal/shells/bash/bashStartup.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import which from 'which';
55
import { traceError, traceInfo, traceVerbose } from '../../../../common/logging';
66
import { ShellConstants } from '../../../common/shellConstants';
77
import { hasStartupCode, insertStartupCode, removeStartupCode } from '../common/editUtils';
8-
import { isWsl, shellIntegrationForActiveTerminal } from '../common/shellUtils';
8+
import { getShellIntegrationEnabledCache, isWsl, shellIntegrationForActiveTerminal } from '../common/shellUtils';
99
import { ShellScriptEditState, ShellSetupState, ShellStartupScriptProvider } from '../startupProvider';
1010
import { BASH_ENV_KEY, BASH_OLD_ENV_KEY, BASH_SCRIPT_VERSION, ZSH_ENV_KEY, ZSH_OLD_ENV_KEY } from './bashConstants';
1111

@@ -69,7 +69,8 @@ async function isStartupSetup(profile: string, key: string): Promise<ShellSetupS
6969
return ShellSetupState.NotSetup;
7070
}
7171
async function setupStartup(profile: string, key: string, name: string): Promise<boolean> {
72-
if ((await shellIntegrationForActiveTerminal(name, profile)) && !isWsl()) {
72+
const shellIntegrationEnabled = await getShellIntegrationEnabledCache();
73+
if ((shellIntegrationEnabled || (await shellIntegrationForActiveTerminal(name, profile))) && !isWsl()) {
7374
removeStartup(profile, key);
7475
return true;
7576
}

src/features/terminal/shells/common/shellUtils.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { PythonCommandRunConfiguration, PythonEnvironment } from '../../../../api';
22
import { traceInfo } from '../../../../common/logging';
3+
import { getGlobalPersistentState } from '../../../../common/persistentState';
34
import { sleep } from '../../../../common/utils/asyncUtils';
45
import { isWindows } from '../../../../common/utils/platformUtils';
56
import { activeTerminalShellIntegration } from '../../../../common/window.apis';
7+
import { getConfiguration } from '../../../../common/workspace.apis';
68
import { ShellConstants } from '../../../common/shellConstants';
79
import { quoteArgs } from '../../../execution/execUtils';
810
import { SHELL_INTEGRATION_POLL_INTERVAL, SHELL_INTEGRATION_TIMEOUT } from '../../utils';
911

12+
export const SHELL_INTEGRATION_STATE_KEY = 'shellIntegration.enabled';
13+
1014
function getCommandAsString(command: PythonCommandRunConfiguration[], shell: string, delimiter: string): string {
1115
const parts = [];
1216
for (const cmd of command) {
@@ -114,6 +118,11 @@ export async function shellIntegrationForActiveTerminal(name: string, profile?:
114118
traceInfo(
115119
`SHELL: Shell integration is available on your active terminal, with name ${name} and profile ${profile}. Python activate scripts will be evaluated at shell integration level, except in WSL.`,
116120
);
121+
122+
// Update persistent storage to reflect that shell integration is available
123+
const persistentState = await getGlobalPersistentState();
124+
await persistentState.set(SHELL_INTEGRATION_STATE_KEY, true);
125+
117126
return true;
118127
}
119128
return false;
@@ -123,3 +132,32 @@ export function isWsl(): boolean {
123132
// WSL sets these environment variables
124133
return !!(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP || process.env.WSLENV);
125134
}
135+
136+
export async function getShellIntegrationEnabledCache(): Promise<boolean> {
137+
const persistentState = await getGlobalPersistentState();
138+
const shellIntegrationInspect =
139+
getConfiguration('terminal.integrated').inspect<boolean>('shellIntegration.enabled');
140+
141+
let shellIntegrationEnabled = true;
142+
if (shellIntegrationInspect) {
143+
// Priority: workspaceFolder > workspace > globalRemoteValue > globalLocalValue > global > default
144+
const inspectValue = shellIntegrationInspect as Record<string, unknown>;
145+
146+
if (shellIntegrationInspect.workspaceFolderValue !== undefined) {
147+
shellIntegrationEnabled = shellIntegrationInspect.workspaceFolderValue;
148+
} else if (shellIntegrationInspect.workspaceValue !== undefined) {
149+
shellIntegrationEnabled = shellIntegrationInspect.workspaceValue;
150+
} else if ('globalRemoteValue' in shellIntegrationInspect && inspectValue.globalRemoteValue !== undefined) {
151+
shellIntegrationEnabled = inspectValue.globalRemoteValue as boolean;
152+
} else if ('globalLocalValue' in shellIntegrationInspect && inspectValue.globalLocalValue !== undefined) {
153+
shellIntegrationEnabled = inspectValue.globalLocalValue as boolean;
154+
} else if (shellIntegrationInspect.globalValue !== undefined) {
155+
shellIntegrationEnabled = shellIntegrationInspect.globalValue;
156+
} else if (shellIntegrationInspect.defaultValue !== undefined) {
157+
shellIntegrationEnabled = shellIntegrationInspect.defaultValue;
158+
}
159+
}
160+
161+
await persistentState.set(SHELL_INTEGRATION_STATE_KEY, shellIntegrationEnabled);
162+
return shellIntegrationEnabled;
163+
}

src/features/terminal/shells/fish/fishStartup.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import which from 'which';
66
import { traceError, traceInfo, traceVerbose } from '../../../../common/logging';
77
import { ShellConstants } from '../../../common/shellConstants';
88
import { hasStartupCode, insertStartupCode, removeStartupCode } from '../common/editUtils';
9-
import { isWsl, shellIntegrationForActiveTerminal } from '../common/shellUtils';
9+
import { getShellIntegrationEnabledCache, isWsl, shellIntegrationForActiveTerminal } from '../common/shellUtils';
1010
import { ShellScriptEditState, ShellSetupState, ShellStartupScriptProvider } from '../startupProvider';
1111
import { FISH_ENV_KEY, FISH_OLD_ENV_KEY, FISH_SCRIPT_VERSION } from './fishConstants';
1212

@@ -58,7 +58,8 @@ async function isStartupSetup(profilePath: string, key: string): Promise<boolean
5858

5959
async function setupStartup(profilePath: string, key: string): Promise<boolean> {
6060
try {
61-
if ((await shellIntegrationForActiveTerminal('fish', profilePath)) && !isWsl()) {
61+
const shellIntegrationEnabled = await getShellIntegrationEnabledCache();
62+
if ((shellIntegrationEnabled || (await shellIntegrationForActiveTerminal('fish', profilePath))) && !isWsl()) {
6263
removeFishStartup(profilePath, key);
6364
return true;
6465
}

src/features/terminal/shells/pwsh/pwshStartup.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ShellConstants } from '../../../common/shellConstants';
1313
import { hasStartupCode, insertStartupCode, removeStartupCode } from '../common/editUtils';
1414
import {
1515
extractProfilePath,
16+
getShellIntegrationEnabledCache,
1617
isWsl,
1718
PROFILE_TAG_END,
1819
PROFILE_TAG_START,
@@ -168,7 +169,9 @@ async function isPowerShellStartupSetup(shell: string, profile: string): Promise
168169
}
169170

170171
async function setupPowerShellStartup(shell: string, profile: string): Promise<boolean> {
171-
if ((await shellIntegrationForActiveTerminal(shell, profile)) && !isWsl()) {
172+
const shellIntegrationEnabled = await getShellIntegrationEnabledCache();
173+
174+
if ((shellIntegrationEnabled || (await shellIntegrationForActiveTerminal(shell, profile))) && !isWsl()) {
172175
removePowerShellStartup(shell, profile, POWERSHELL_OLD_ENV_KEY);
173176
removePowerShellStartup(shell, profile, POWERSHELL_ENV_KEY);
174177
return true;

src/features/terminal/terminalManager.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { getConfiguration, onDidChangeConfiguration } from '../../common/workspa
1616
import { isActivatableEnvironment } from '../common/activation';
1717
import { identifyTerminalShell } from '../common/shellDetector';
1818
import { getPythonApi } from '../pythonApi';
19-
import { isWsl, shellIntegrationForActiveTerminal } from './shells/common/shellUtils';
19+
import { getShellIntegrationEnabledCache, isWsl, shellIntegrationForActiveTerminal } from './shells/common/shellUtils';
2020
import { ShellEnvsProvider, ShellSetupState, ShellStartupScriptProvider } from './shells/startupProvider';
2121
import { handleSettingUpShellProfile } from './shellStartupSetupHandlers';
2222
import {
@@ -137,6 +137,20 @@ export class TerminalManagerImpl implements TerminalManager {
137137
this.shellSetup.clear();
138138
}
139139
}
140+
if (e.affectsConfiguration('terminal.integrated.shellIntegration.enabled')) {
141+
traceInfo('Shell integration setting changed, invalidating cache');
142+
const updatedShellIntegrationSetting = await getShellIntegrationEnabledCache();
143+
if (!updatedShellIntegrationSetting) {
144+
const shells = new Set(
145+
terminals()
146+
.map((t) => identifyTerminalShell(t))
147+
.filter((t) => t !== 'unknown'),
148+
);
149+
if (shells.size > 0) {
150+
await this.handleSetupCheck(shells);
151+
}
152+
}
153+
}
140154
}),
141155
onDidChangeWindowState((e) => {
142156
this.hasFocus = e.focused;
@@ -152,27 +166,19 @@ export class TerminalManagerImpl implements TerminalManager {
152166
await Promise.all(
153167
providers.map(async (p) => {
154168
const state = await p.isSetup();
155-
const currentSetup = state === ShellSetupState.Setup;
156-
// Check if we already processed this shell and the state hasn't changed
157-
if (this.shellSetup.has(p.shellType)) {
158-
const cachedSetup = this.shellSetup.get(p.shellType);
159-
if (currentSetup === cachedSetup) {
160-
traceVerbose(`Shell profile for ${p.shellType} already checked, state unchanged.`);
161-
return;
162-
}
163-
traceVerbose(
164-
`Shell profile for ${p.shellType} state changed from ${cachedSetup} to ${currentSetup}, re-evaluating.`,
165-
);
166-
}
167-
traceVerbose(`Checking shell profile for ${p.shellType}.`);
169+
const shellIntegrationEnabled = await getShellIntegrationEnabledCache();
170+
traceVerbose(`Checking shell profile for ${p.shellType}, with state: ${state}`);
168171
if (state === ShellSetupState.NotSetup) {
169172
traceVerbose(
170-
`WSL detected: ${isWsl()}, Shell integration available: ${await shellIntegrationForActiveTerminal(
173+
`WSL detected: ${isWsl()}, Shell integration available from setting, or active terminal: ${shellIntegrationEnabled}, or ${await shellIntegrationForActiveTerminal(
171174
p.name,
172175
)}`,
173176
);
174177

175-
if ((await shellIntegrationForActiveTerminal(p.name)) && !isWsl()) {
178+
if (
179+
(shellIntegrationEnabled || (await shellIntegrationForActiveTerminal(p.name))) &&
180+
!isWsl()
181+
) {
176182
// Shell integration available and NOT in WSL - skip setup
177183
await p.teardownScripts();
178184
this.shellSetup.set(p.shellType, true);
@@ -188,7 +194,10 @@ export class TerminalManagerImpl implements TerminalManager {
188194
);
189195
}
190196
} else if (state === ShellSetupState.Setup) {
191-
if ((await shellIntegrationForActiveTerminal(p.name)) && !isWsl()) {
197+
if (
198+
(shellIntegrationEnabled || (await shellIntegrationForActiveTerminal(p.name))) &&
199+
!isWsl()
200+
) {
192201
await p.teardownScripts();
193202
traceVerbose(
194203
`Shell integration available for ${p.shellType}, removed profile script in favor of shell integration.`,

0 commit comments

Comments
 (0)