Skip to content

Commit 24f0fe0

Browse files
committed
fix: some bugs with terminal shell activation
1 parent 8475e75 commit 24f0fe0

File tree

9 files changed

+193
-122
lines changed

9 files changed

+193
-122
lines changed

src/common/window.apis.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
TextEditor,
3030
Uri,
3131
window,
32+
WindowState,
3233
} from 'vscode';
3334
import { createDeferred } from './utils/deferred';
3435

@@ -347,3 +348,11 @@ export function createLogOutputChannel(name: string): LogOutputChannel {
347348
export function registerFileDecorationProvider(provider: FileDecorationProvider): Disposable {
348349
return window.registerFileDecorationProvider(provider);
349350
}
351+
352+
export function onDidChangeWindowState(
353+
listener: (e: WindowState) => any,
354+
thisArgs?: any,
355+
disposables?: Disposable[],
356+
): Disposable {
357+
return window.onDidChangeWindowState(listener, thisArgs, disposables);
358+
}

src/features/terminal/shellStartupSetupHandlers.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { l10n } from 'vscode';
1+
import { l10n, ProgressLocation } from 'vscode';
22
import { executeCommand } from '../../common/command.api';
33
import { Common, ShellStartupActivationStrings } from '../../common/localize';
44
import { traceInfo, traceVerbose } from '../../common/logging';
5-
import { showErrorMessage, showInformationMessage } from '../../common/window.apis';
5+
import { showErrorMessage, showInformationMessage, withProgress } from '../../common/window.apis';
66
import { ShellScriptEditState, ShellStartupScriptProvider } from './shells/startupProvider';
77
import { getAutoActivationType, setAutoActivationType } from './utils';
88

@@ -23,8 +23,16 @@ export async function handleSettingUpShellProfile(
2323

2424
if (response === Common.yes) {
2525
traceVerbose(`User chose to set up shell profiles for ${shells} shells`);
26-
const states = (await Promise.all(providers.map((provider) => provider.setupScripts()))).filter(
27-
(state) => state !== ShellScriptEditState.NotInstalled,
26+
const states = await withProgress(
27+
{
28+
location: ProgressLocation.Notification,
29+
title: l10n.t('Setting up shell profiles for {0}', shells),
30+
},
31+
async () => {
32+
return (await Promise.all(providers.map((provider) => provider.setupScripts()))).filter(
33+
(state) => state !== ShellScriptEditState.NotInstalled,
34+
);
35+
},
2836
);
2937
if (states.every((state) => state === ShellScriptEditState.Edited)) {
3038
setImmediate(async () => {

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export class BashEnvsProvider implements ShellEnvsProvider {
1414
const bashActivation = getShellActivationCommand(this.shellType, env);
1515
if (bashActivation) {
1616
const command = getShellCommandAsString(this.shellType, bashActivation);
17+
const v = collection.get(BASH_ENV_KEY);
18+
if (v?.value === command) {
19+
return;
20+
}
1721
collection.replace(BASH_ENV_KEY, command);
1822
} else {
1923
collection.delete(BASH_ENV_KEY);
@@ -49,23 +53,27 @@ export class BashEnvsProvider implements ShellEnvsProvider {
4953

5054
export class ZshEnvsProvider implements ShellEnvsProvider {
5155
public readonly shellType: string = ShellConstants.ZSH;
52-
updateEnvVariables(envVars: EnvironmentVariableCollection, env: PythonEnvironment): void {
56+
updateEnvVariables(collection: EnvironmentVariableCollection, env: PythonEnvironment): void {
5357
try {
5458
const zshActivation = getShellActivationCommand(this.shellType, env);
5559
if (zshActivation) {
5660
const command = getShellCommandAsString(this.shellType, zshActivation);
57-
envVars.replace(ZSH_ENV_KEY, command);
61+
const v = collection.get(ZSH_ENV_KEY);
62+
if (v?.value === command) {
63+
return;
64+
}
65+
collection.replace(ZSH_ENV_KEY, command);
5866
} else {
59-
envVars.delete(ZSH_ENV_KEY);
67+
collection.delete(ZSH_ENV_KEY);
6068
}
6169
} catch (err) {
6270
traceError('Failed to update env variables for zsh', err);
63-
envVars.delete(ZSH_ENV_KEY);
71+
collection.delete(ZSH_ENV_KEY);
6472
}
6573
}
6674

67-
removeEnvVariables(envVars: EnvironmentVariableCollection): void {
68-
envVars.delete(ZSH_ENV_KEY);
75+
removeEnvVariables(collection: EnvironmentVariableCollection): void {
76+
collection.delete(ZSH_ENV_KEY);
6977
}
7078

7179
getEnvVariables(env?: PythonEnvironment): Map<string, string | undefined> | undefined {

src/features/terminal/shells/cmd/cmdEnvs.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export class CmdEnvsProvider implements ShellEnvsProvider {
1313
const cmdActivation = getShellActivationCommand(this.shellType, env);
1414
if (cmdActivation) {
1515
const command = getShellCommandAsString(this.shellType, cmdActivation);
16+
const v = collection.get(CMD_ENV_KEY);
17+
if (v?.value === command) {
18+
return;
19+
}
1620
collection.replace(CMD_ENV_KEY, command);
1721
} else {
1822
collection.delete(CMD_ENV_KEY);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export class FishEnvsProvider implements ShellEnvsProvider {
1313
const fishActivation = getShellActivationCommand(this.shellType, env);
1414
if (fishActivation) {
1515
const command = getShellCommandAsString(this.shellType, fishActivation);
16+
const v = collection.get(FISH_ENV_KEY);
17+
if (v?.value === command) {
18+
return;
19+
}
1620
collection.replace(FISH_ENV_KEY, command);
1721
} else {
1822
collection.delete(FISH_ENV_KEY);

src/features/terminal/shells/providers.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,25 @@ import { CmdStartupProvider } from './cmd/cmdStartup';
77
import { FishEnvsProvider } from './fish/fishEnvs';
88
import { FishStartupProvider } from './fish/fishStartup';
99
import { PowerShellEnvsProvider } from './pwsh/pwshEnvs';
10-
import { PowerShellClassicStartupProvider, PwshStartupProvider } from './pwsh/pwshStartup';
10+
import { PwshStartupProvider } from './pwsh/pwshStartup';
1111
import { ShellEnvsProvider, ShellStartupScriptProvider } from './startupProvider';
1212

1313
export function createShellStartupProviders(): ShellStartupScriptProvider[] {
1414
if (isWindows()) {
1515
return [
16-
new PowerShellClassicStartupProvider(),
17-
new PwshStartupProvider(),
16+
// PowerShell classic is the default on Windows, so it is included here explicitly.
17+
// pwsh is the new PowerShell Core, which is cross-platform and preferred.
18+
new PwshStartupProvider([ShellConstants.PWSH, 'powershell']),
1819
new GitBashStartupProvider(),
1920
new CmdStartupProvider(),
2021
];
2122
}
22-
return [new PwshStartupProvider(), new BashStartupProvider(), new FishStartupProvider(), new ZshStartupProvider()];
23+
return [
24+
new PwshStartupProvider([ShellConstants.PWSH]),
25+
new BashStartupProvider(),
26+
new FishStartupProvider(),
27+
new ZshStartupProvider(),
28+
];
2329
}
2430

2531
export function createShellEnvProviders(): ShellEnvsProvider[] {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export class PowerShellEnvsProvider implements ShellEnvsProvider {
1414
const pwshActivation = getShellActivationCommand(this.shellType, env);
1515
if (pwshActivation) {
1616
const command = getShellCommandAsString(this.shellType, pwshActivation);
17+
const v = collection.get(POWERSHELL_ENV_KEY);
18+
if (v?.value === command) {
19+
return;
20+
}
1721
collection.replace(POWERSHELL_ENV_KEY, command);
1822
} else {
1923
collection.delete(POWERSHELL_ENV_KEY);

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

Lines changed: 99 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { isWindows } from '../../../../common/utils/platformUtils';
77
import { ShellScriptEditState, ShellSetupState, ShellStartupScriptProvider } from '../startupProvider';
88
import { runCommand } from '../utils';
99

10+
import assert from 'assert';
1011
import { getGlobalPersistentState } from '../../../../common/persistentState';
1112
import { ShellConstants } from '../../../common/shellConstants';
1213
import { hasStartupCode, insertStartupCode, removeStartupCode } from '../common/editUtils';
@@ -185,130 +186,134 @@ async function removePowerShellStartup(shell: string, profile: string): Promise<
185186
}
186187
}
187188

188-
export class PowerShellClassicStartupProvider implements ShellStartupScriptProvider {
189-
public readonly name: string = 'PowerShell5';
190-
public readonly shellType: string = 'powershell';
191-
private _isInstalled: boolean | undefined;
189+
type PowerShellType = 'powershell' | 'pwsh';
192190

193-
private async checkInstallation(): Promise<boolean> {
194-
if (this._isInstalled === undefined) {
195-
this._isInstalled = await isPowerShellInstalled('powershell');
196-
}
197-
return this._isInstalled;
198-
}
199-
async isSetup(): Promise<ShellSetupState> {
200-
const isInstalled = await this.checkInstallation();
201-
if (!isInstalled) {
202-
return ShellSetupState.NotInstalled;
203-
}
191+
export class PwshStartupProvider implements ShellStartupScriptProvider {
192+
public readonly name: string = 'PowerShell';
193+
public readonly shellType: string = ShellConstants.PWSH;
204194

205-
try {
206-
const profile = await getProfileForShell('powershell');
207-
const isSetup = await isPowerShellStartupSetup('powershell', profile);
208-
return isSetup ? ShellSetupState.Setup : ShellSetupState.NotSetup;
209-
} catch (err) {
210-
traceError('Failed to check if PowerShell startup is setup', err);
211-
}
212-
return ShellSetupState.NotSetup;
195+
private _isPwshInstalled: boolean | undefined;
196+
private _isPs5Installed: boolean | undefined;
197+
private _supportedShells: PowerShellType[];
198+
199+
constructor(supportedShells: PowerShellType[]) {
200+
assert(supportedShells.length > 0, 'At least one PowerShell shell must be supported');
201+
this._supportedShells = supportedShells;
213202
}
214203

215-
async setupScripts(): Promise<ShellScriptEditState> {
216-
const isInstalled = await this.checkInstallation();
217-
if (!isInstalled) {
218-
return ShellScriptEditState.NotInstalled;
219-
}
204+
private async checkInstallations(): Promise<Map<PowerShellType, boolean>> {
205+
const results = new Map<PowerShellType, boolean>();
220206

221-
try {
222-
const profile = await getProfileForShell('powershell');
223-
const success = await setupPowerShellStartup('powershell', profile);
224-
return success ? ShellScriptEditState.Edited : ShellScriptEditState.NotEdited;
225-
} catch (err) {
226-
traceError('Failed to setup PowerShell startup', err);
227-
}
228-
return ShellScriptEditState.NotEdited;
207+
await Promise.all(
208+
this._supportedShells.map(async (shell) => {
209+
if (shell === 'pwsh' && this._isPwshInstalled !== undefined) {
210+
results.set(shell, this._isPwshInstalled);
211+
} else if (shell === 'powershell' && this._isPs5Installed !== undefined) {
212+
results.set(shell, this._isPs5Installed);
213+
} else {
214+
const isInstalled = await isPowerShellInstalled(shell);
215+
if (shell === 'pwsh') {
216+
this._isPwshInstalled = isInstalled;
217+
} else {
218+
this._isPs5Installed = isInstalled;
219+
}
220+
results.set(shell, isInstalled);
221+
}
222+
}),
223+
);
224+
225+
return results;
229226
}
230227

231-
async teardownScripts(): Promise<ShellScriptEditState> {
232-
const isInstalled = await this.checkInstallation();
233-
if (!isInstalled) {
234-
return ShellScriptEditState.NotInstalled;
235-
}
228+
async isSetup(): Promise<ShellSetupState> {
229+
const installations = await this.checkInstallations();
236230

237-
try {
238-
const profile = await getProfileForShell('powershell');
239-
const success = await removePowerShellStartup('powershell', profile);
240-
return success ? ShellScriptEditState.Edited : ShellScriptEditState.NotEdited;
241-
} catch (err) {
242-
traceError('Failed to remove PowerShell startup', err);
231+
if (Array.from(installations.values()).every((installed) => !installed)) {
232+
return ShellSetupState.NotInstalled;
243233
}
244-
return ShellScriptEditState.NotEdited;
245-
}
246-
async clearCache(): Promise<void> {
247-
await clearPwshCache('powershell');
248-
}
249-
}
250-
251-
export class PwshStartupProvider implements ShellStartupScriptProvider {
252-
public readonly name: string = 'PowerShell';
253-
public readonly shellType: string = ShellConstants.PWSH;
254234

255-
private _isInstalled: boolean | undefined;
235+
const results: ShellSetupState[] = [];
236+
for (const [shell, installed] of installations.entries()) {
237+
if (!installed) {
238+
continue;
239+
}
256240

257-
private async checkInstallation(): Promise<boolean> {
258-
if (this._isInstalled === undefined) {
259-
this._isInstalled = await isPowerShellInstalled('pwsh');
241+
try {
242+
const profile = await getProfileForShell(shell);
243+
const isSetup = await isPowerShellStartupSetup(shell, profile);
244+
results.push(isSetup ? ShellSetupState.Setup : ShellSetupState.NotSetup);
245+
} catch (err) {
246+
traceError(`Failed to check if ${shell} startup is setup`, err);
247+
results.push(ShellSetupState.NotSetup);
248+
}
260249
}
261-
return this._isInstalled;
262-
}
263250

264-
async isSetup(): Promise<ShellSetupState> {
265-
const isInstalled = await this.checkInstallation();
266-
if (!isInstalled) {
267-
return ShellSetupState.NotInstalled;
251+
if (results.includes(ShellSetupState.NotSetup)) {
252+
return ShellSetupState.NotSetup;
268253
}
269254

270-
try {
271-
const profile = await getProfileForShell('pwsh');
272-
const isSetup = await isPowerShellStartupSetup('pwsh', profile);
273-
return isSetup ? ShellSetupState.Setup : ShellSetupState.NotSetup;
274-
} catch (err) {
275-
traceError('Failed to check if PowerShell startup is setup', err);
276-
}
277-
return ShellSetupState.NotSetup;
255+
return ShellSetupState.Setup;
278256
}
279257

280258
async setupScripts(): Promise<ShellScriptEditState> {
281-
const isInstalled = await this.checkInstallation();
282-
if (!isInstalled) {
259+
const installations = await this.checkInstallations();
260+
261+
if (Array.from(installations.values()).every((installed) => !installed)) {
283262
return ShellScriptEditState.NotInstalled;
284263
}
285264

286-
try {
287-
const profile = await getProfileForShell('pwsh');
288-
const success = await setupPowerShellStartup('pwsh', profile);
289-
return success ? ShellScriptEditState.Edited : ShellScriptEditState.NotEdited;
290-
} catch (err) {
291-
traceError('Failed to setup PowerShell startup', err);
265+
const anyEdited = [];
266+
for (const [shell, installed] of installations.entries()) {
267+
if (!installed) {
268+
continue;
269+
}
270+
271+
try {
272+
const profile = await getProfileForShell(shell);
273+
const success = await setupPowerShellStartup(shell, profile);
274+
anyEdited.push(success ? ShellScriptEditState.Edited : ShellScriptEditState.NotEdited);
275+
} catch (err) {
276+
traceError(`Failed to setup ${shell} startup`, err);
277+
}
292278
}
293-
return ShellScriptEditState.NotEdited;
279+
return anyEdited.every((state) => state === ShellScriptEditState.Edited)
280+
? ShellScriptEditState.Edited
281+
: ShellScriptEditState.NotEdited;
294282
}
295283

296284
async teardownScripts(): Promise<ShellScriptEditState> {
297-
const isInstalled = await this.checkInstallation();
298-
if (!isInstalled) {
285+
const installations = await this.checkInstallations();
286+
287+
if (Array.from(installations.values()).every((installed) => !installed)) {
299288
return ShellScriptEditState.NotInstalled;
300289
}
301290

302-
try {
303-
const profile = await getProfileForShell('pwsh');
304-
const success = await removePowerShellStartup('pwsh', profile);
305-
return success ? ShellScriptEditState.Edited : ShellScriptEditState.NotEdited;
306-
} catch (err) {
307-
traceError('Failed to remove PowerShell startup', err);
291+
const anyEdited = [];
292+
for (const [shell, installed] of installations.entries()) {
293+
if (!installed) {
294+
continue;
295+
}
296+
297+
try {
298+
const profile = await getProfileForShell(shell);
299+
const success = await removePowerShellStartup(shell, profile);
300+
anyEdited.push(success ? ShellScriptEditState.Edited : ShellScriptEditState.NotEdited);
301+
} catch (err) {
302+
traceError(`Failed to remove ${shell} startup`, err);
303+
}
308304
}
309-
return ShellScriptEditState.NotEdited;
305+
return anyEdited.every((state) => state === ShellScriptEditState.Edited)
306+
? ShellScriptEditState.Edited
307+
: ShellScriptEditState.NotEdited;
310308
}
309+
311310
async clearCache(): Promise<void> {
312-
await clearPwshCache('pwsh');
311+
for (const shell of this._supportedShells) {
312+
await clearPwshCache(shell);
313+
}
314+
315+
// Reset installation check cache as well
316+
this._isPwshInstalled = undefined;
317+
this._isPs5Installed = undefined;
313318
}
314319
}

0 commit comments

Comments
 (0)