From 41a2b1d5e2fb2b978e970d19e080ebe34e65ddb7 Mon Sep 17 00:00:00 2001 From: Chawye Hsu Date: Thu, 30 Apr 2026 17:50:45 +0800 Subject: [PATCH 1/3] Add new `additionalPowerShellLocations` option Signed-off-by: Chawye Hsu --- package.json | 40 ++++++ src/platform.ts | 221 ++++++++++++++++++++++---------- src/session.ts | 177 ++++++++++++++++++++++---- test/core/platform.test.ts | 250 +++++++++++++++++++++++++++++++++++++ 4 files changed, 599 insertions(+), 89 deletions(-) diff --git a/package.json b/package.json index 4a6cc697f6..98352bc0f1 100644 --- a/package.json +++ b/package.json @@ -968,6 +968,46 @@ "type": "string" } }, + "powershell.additionalPowerShellLocations": { + "type": "array", + "default": [], + "markdownDescription": "Specifies a list of named PowerShell executables tied to a specific platform. When this setting is non-empty, `#powershell.powerShellAdditionalExePaths#` and `#powershell.powerShellDefaultVersion#` are ignored. Entries are ranked by `weight` (highest first), and entries with the same `weight` keep definition order.", + "items": { + "type": "object", + "required": [ + "name", + "path", + "platform" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "markdownDescription": "The display name shown in the Session Menu." + }, + "path": { + "type": "string", + "markdownDescription": "The absolute path to the PowerShell executable or containing folder." + }, + "platform": { + "type": "string", + "enum": [ + "windows-x64", + "windows-arm64", + "macos-x64", + "macos-arm64", + "linux-x64", + "linux-aarch64" + ], + "markdownDescription": "The target host platform for this PowerShell location." + }, + "weight": { + "type": "number", + "markdownDescription": "Ranking for this location on its platform. Higher values are preferred; equal values preserve definition order." + } + } + } + }, "powershell.cwd": { "type": "string", "default": "", diff --git a/src/platform.ts b/src/platform.ts index c1037e05f9..e30b0b52ac 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -34,6 +34,21 @@ export enum OperatingSystem { Linux, } +export type SupportedPlatform = + | "windows-x64" + | "windows-arm64" + | "macos-x64" + | "macos-arm64" + | "linux-x64" + | "linux-aarch64"; + +export interface IAdditionalPowerShellLocation { + readonly name: string; + readonly path: string; + readonly platform: SupportedPlatform; + readonly weight?: number; +} + export interface IPlatformDetails { operatingSystem: OperatingSystem; isOS64Bit: boolean; @@ -46,6 +61,46 @@ export interface IPowerShellExeDetails { readonly supportsProperArguments: boolean; } +export function getSupportedPlatform( + platformDetails: IPlatformDetails, + processArchitecture: NodeJS.Architecture = process.arch, +): SupportedPlatform | undefined { + switch (platformDetails.operatingSystem) { + case OperatingSystem.Windows: + switch (processArchitecture) { + case "x64": + return "windows-x64"; + case "arm64": + return "windows-arm64"; + default: + return undefined; + } + + case OperatingSystem.MacOS: + switch (processArchitecture) { + case "x64": + return "macos-x64"; + case "arm64": + return "macos-arm64"; + default: + return undefined; + } + + case OperatingSystem.Linux: + switch (processArchitecture) { + case "x64": + return "linux-x64"; + case "arm64": + return "linux-aarch64"; + default: + return undefined; + } + + case OperatingSystem.Unknown: + return undefined; + } +} + export function getPlatformDetails(): IPlatformDetails { let operatingSystem = OperatingSystem.Unknown; @@ -91,6 +146,8 @@ export class PowerShellExeFinder { // Additional configured PowerShells private additionalPowerShellExes: Record, private logger?: ILogger, + private additionalPowerShellLocations: IAdditionalPowerShellLocation[] = [], + private processArchitecture: NodeJS.Architecture = process.arch, ) {} /** @@ -158,7 +215,11 @@ export class PowerShellExeFinder { // Also show any additionally configured PowerShells // These may be duplicates of the default installations, but given a different name. - for await (const additionalPwsh of this.enumerateAdditionalPowerShellInstallations()) { + const configuredPowerShells = + this.additionalPowerShellLocations.length > 0 + ? this.enumerateAdditionalPowerShellLocations() + : this.enumerateAdditionalPowerShellInstallations(); + for await (const additionalPwsh of configuredPowerShells) { if (await additionalPwsh.exists()) { yield additionalPwsh; } else if (!additionalPwsh.suppressWarning) { @@ -269,69 +330,75 @@ export class PowerShellExeFinder { * without checking for their existence. */ public async *enumerateAdditionalPowerShellInstallations(): AsyncIterable { - for (const versionName in this.additionalPowerShellExes) { - if ( - Object.prototype.hasOwnProperty.call( - this.additionalPowerShellExes, - versionName, - ) - ) { - let exePath: string | undefined = utils.stripQuotePair( - this.additionalPowerShellExes[versionName], - ); - if (!exePath) { - continue; - } + yield* this.enumerateConfiguredPowerShellInstallations( + Object.entries(this.additionalPowerShellExes), + ); + } - exePath = untildify(exePath); - const args: [string, undefined, boolean, boolean] = - // Must be a tuple type and is suppressing the warning - [versionName, undefined, true, true]; + /** + * Iterates through the configured additional PowerShell locations for the current platform, + * without checking for their existence. + */ + public async *enumerateAdditionalPowerShellLocations(): AsyncIterable { + const supportedPlatform = getSupportedPlatform( + this.platformDetails, + this.processArchitecture, + ); + if (!supportedPlatform) { + return; + } - // Always search for what the user gave us first, but with the warning - // suppressed so we can display it after all possibilities are exhausted - let pwsh = new PossiblePowerShellExe(exePath, ...args); - if (await pwsh.exists()) { - yield pwsh; - continue; - } + yield* this.enumerateConfiguredPowerShellInstallations( + this.additionalPowerShellLocations + .filter((location) => location.platform === supportedPlatform) + // Higher weight wins. Equal weights preserve definition order. + .sort((a, b) => (b.weight ?? 0) - (a.weight ?? 0)) + .map((location): [string, string] => [ + location.name, + location.path, + ]), + ); + } - // Also search for `pwsh[.exe]` and `powershell[.exe]` if missing + private async *enumerateConfiguredPowerShellInstallations( + configuredPowerShells: Iterable, + ): AsyncIterable { + for (const [versionName, configuredPath] of configuredPowerShells) { + let exePath: string | undefined = + utils.stripQuotePair(configuredPath); + if (!exePath) { + continue; + } + + exePath = untildify(exePath); + const args: [string, undefined, boolean, boolean] = + // Must be a tuple type and is suppressing the warning + [versionName, undefined, true, true]; + + // Always search for what the user gave us first, but with the warning + // suppressed so we can display it after all possibilities are exhausted + let pwsh = new PossiblePowerShellExe(exePath, ...args); + if (await pwsh.exists()) { + yield pwsh; + continue; + } + + // Also search for `pwsh[.exe]` and `powershell[.exe]` if missing + if ( + this.platformDetails.operatingSystem === OperatingSystem.Windows + ) { + // Handle Windows where '.exe' and 'powershell' are things if ( - this.platformDetails.operatingSystem === - OperatingSystem.Windows + !exePath.endsWith("pwsh.exe") && + !exePath.endsWith("powershell.exe") ) { - // Handle Windows where '.exe' and 'powershell' are things if ( - !exePath.endsWith("pwsh.exe") && - !exePath.endsWith("powershell.exe") + exePath.endsWith("pwsh") || + exePath.endsWith("powershell") ) { - if ( - exePath.endsWith("pwsh") || - exePath.endsWith("powershell") - ) { - // Add extension if that was missing - pwsh = new PossiblePowerShellExe( - exePath + ".exe", - ...args, - ); - if (await pwsh.exists()) { - yield pwsh; - continue; - } - } - // Also add full exe names (this isn't an else just in case - // the folder was named "pwsh" or "powershell") - pwsh = new PossiblePowerShellExe( - path.join(exePath, "pwsh.exe"), - ...args, - ); - if (await pwsh.exists()) { - yield pwsh; - continue; - } + // Add extension if that was missing pwsh = new PossiblePowerShellExe( - path.join(exePath, "powershell.exe"), + exePath + ".exe", ...args, ); if (await pwsh.exists()) { @@ -339,27 +406,47 @@ export class PowerShellExeFinder { continue; } } - } else if (!exePath.endsWith("pwsh")) { - // Always just 'pwsh' on non-Windows + + // Also add full exe names (this isn't an else just in case + // the folder was named "pwsh" or "powershell") pwsh = new PossiblePowerShellExe( - path.join(exePath, "pwsh"), + path.join(exePath, "pwsh.exe"), ...args, ); if (await pwsh.exists()) { yield pwsh; continue; } - } - // If we're still being iterated over, no permutation of the given path existed so yield an object with the warning unsuppressed - yield new PossiblePowerShellExe( - exePath, - versionName, - false, - undefined, - false, + pwsh = new PossiblePowerShellExe( + path.join(exePath, "powershell.exe"), + ...args, + ); + if (await pwsh.exists()) { + yield pwsh; + continue; + } + } + } else if (!exePath.endsWith("pwsh")) { + // Always just 'pwsh' on non-Windows + pwsh = new PossiblePowerShellExe( + path.join(exePath, "pwsh"), + ...args, ); + if (await pwsh.exists()) { + yield pwsh; + continue; + } } + + // If we're still being iterated over, no permutation of the given path existed so yield an object with the warning unsuppressed + yield new PossiblePowerShellExe( + exePath, + versionName, + false, + undefined, + false, + ); } } diff --git a/src/session.ts b/src/session.ts index b2c61ed702..79125b4e22 100644 --- a/src/session.ts +++ b/src/session.ts @@ -38,11 +38,13 @@ import { SemVer, satisfies } from "semver"; import { UpdatePowerShell } from "./features/UpdatePowerShell"; import { LanguageClientConsumer } from "./languageClientConsumer"; import { + type IAdditionalPowerShellLocation, type IPlatformDetails, type IPowerShellExeDetails, OperatingSystem, PowerShellExeFinder, getPlatformDetails, + getSupportedPlatform, } from "./platform"; enum SessionStatus { @@ -673,13 +675,10 @@ export class SessionManager implements Middleware { wantedName?: string, ): Promise { this.logger.writeDebug("Finding PowerShell..."); - const powerShellAdditionalExePaths = vscode.workspace - .getConfiguration("powershell") - .get>("powerShellAdditionalExePaths", {}); - const powershellExeFinder = new PowerShellExeFinder( - this.platformDetails, - powerShellAdditionalExePaths, - this.logger, + const additionalPowerShellLocations = + this.getAdditionalPowerShellLocations(); + const powershellExeFinder = this.createPowerShellExeFinder( + additionalPowerShellLocations, ); let foundPowerShell: IPowerShellExeDetails | undefined; @@ -687,9 +686,7 @@ export class SessionManager implements Middleware { let defaultPowerShell: IPowerShellExeDetails | undefined; wantedName ??= this.powerShellVersionOverride ?? - vscode.workspace - .getConfiguration("powershell") - .get("powerShellDefaultVersion", ""); + this.getConfiguredPowerShellName(additionalPowerShellLocations); if (wantedName !== "") { for await (const details of powershellExeFinder.enumeratePowerShellInstallations()) { // Need to compare names case-insensitively, from https://stackoverflow.com/a/2140723 @@ -714,7 +711,7 @@ export class SessionManager implements Middleware { foundPowerShell !== undefined ) { void this.logger.writeAndShowWarning( - `The 'powerShellDefaultVersion' setting was '${wantedName}' but this was not found!` + + `The requested PowerShell '${wantedName}' was not found!` + ` Instead using first available installation '${foundPowerShell.displayName}' at '${foundPowerShell.exePath}'!`, ); } @@ -727,6 +724,78 @@ export class SessionManager implements Middleware { return foundPowerShell; } + private getAdditionalPowerShellLocations(): IAdditionalPowerShellLocation[] { + return vscode.workspace + .getConfiguration("powershell") + .get< + IAdditionalPowerShellLocation[] + >("additionalPowerShellLocations", []); + } + + private usesAdditionalPowerShellLocations( + additionalPowerShellLocations = this.getAdditionalPowerShellLocations(), + ): boolean { + return additionalPowerShellLocations.length > 0; + } + + private getConfiguredPowerShellName( + additionalPowerShellLocations = this.getAdditionalPowerShellLocations(), + ): string { + if ( + this.usesAdditionalPowerShellLocations( + additionalPowerShellLocations, + ) + ) { + return ( + this.getTopRankedAdditionalPowerShellLocation( + additionalPowerShellLocations, + )?.name ?? "" + ); + } + + return vscode.workspace + .getConfiguration("powershell") + .get("powerShellDefaultVersion", ""); + } + + private getTopRankedAdditionalPowerShellLocation( + additionalPowerShellLocations = this.getAdditionalPowerShellLocations(), + ): IAdditionalPowerShellLocation | undefined { + const supportedPlatform = getSupportedPlatform(this.platformDetails); + if (!supportedPlatform) { + return undefined; + } + + return ( + additionalPowerShellLocations + .filter((location) => location.platform === supportedPlatform) + // Higher weight wins. Equal weights preserve definition order. + .sort((a, b) => (b.weight ?? 0) - (a.weight ?? 0))[0] + ); + } + + private createPowerShellExeFinder( + additionalPowerShellLocations = this.getAdditionalPowerShellLocations(), + ): PowerShellExeFinder { + const powerShellAdditionalExePaths = + this.usesAdditionalPowerShellLocations( + additionalPowerShellLocations, + ) + ? {} + : vscode.workspace + .getConfiguration("powershell") + .get< + Record + >("powerShellAdditionalExePaths", {}); + + return new PowerShellExeFinder( + this.platformDetails, + powerShellAdditionalExePaths, + this.logger, + additionalPowerShellLocations, + ); + } + private async startLanguageServerProcess( powerShellExeDetails: IPowerShellExeDetails, cancellationToken: vscode.CancellationToken, @@ -1194,9 +1263,7 @@ Type 'help' to get help. } else { const wantedVersion = this.powerShellVersionOverride ?? - vscode.workspace - .getConfiguration("powershell") - .get("powerShellDefaultVersion", ""); + this.getConfiguredPowerShellName(); if (wantedVersion) { // When it hasn't been found yet. this.languageStatusItem.text += ` ${wantedVersion}`; @@ -1319,6 +1386,62 @@ Type 'help' to get help. await this.restartSession(exePath.displayName); } + private async increaseAdditionalPowerShellLocationWeight( + exePath: IPowerShellExeDetails, + ): Promise { + const additionalPowerShellLocations = + this.getAdditionalPowerShellLocations(); + const supportedPlatform = getSupportedPlatform(this.platformDetails); + + if (!supportedPlatform) { + await this.restartSession(exePath.displayName); + return; + } + + const selectedIndex = additionalPowerShellLocations.findIndex( + (location) => + location.platform === supportedPlatform && + location.name === exePath.displayName, + ); + + if (selectedIndex < 0) { + await this.restartSession(exePath.displayName); + return; + } + + const highestWeightForPlatform = additionalPowerShellLocations + .filter((location) => location.platform === supportedPlatform) + .reduce( + (highestWeight, location) => + Math.max(highestWeight, location.weight ?? 0), + 0, + ); + + const updatedLocations = additionalPowerShellLocations.map( + (location, index) => + index === selectedIndex + ? { + ...location, + weight: highestWeightForPlatform + 1, + } + : location, + ); + + this.suppressRestartPrompt = true; + try { + await changeSetting( + "additionalPowerShellLocations", + updatedLocations, + true, + this.logger, + ); + } finally { + this.suppressRestartPrompt = false; + } + + await this.restartSession(exePath.displayName); + } + // Shows the temp debug terminal if it exists, otherwise the session terminal. public showDebugTerminal(isExecute?: boolean): void { const preserveFocus = @@ -1344,13 +1467,14 @@ Type 'help' to get help. } private async showSessionMenu(): Promise { - const powerShellAdditionalExePaths = vscode.workspace - .getConfiguration("powershell") - .get>("powerShellAdditionalExePaths", {}); - const powershellExeFinder = new PowerShellExeFinder( - this.platformDetails, - powerShellAdditionalExePaths, - this.logger, + const additionalPowerShellLocations = + this.getAdditionalPowerShellLocations(); + const usesAdditionalPowerShellLocations = + this.usesAdditionalPowerShellLocations( + additionalPowerShellLocations, + ); + const powershellExeFinder = this.createPowerShellExeFinder( + additionalPowerShellLocations, ); const availablePowerShellExes = await powershellExeFinder.getAllAvailablePowerShellInstallations(); @@ -1364,6 +1488,13 @@ Type 'help' to get help. return new SessionMenuItem( `Switch to: ${item.displayName}`, async () => { + if (usesAdditionalPowerShellLocations) { + await this.increaseAdditionalPowerShellLocationWeight( + item, + ); + return; + } + await this.changePowerShellDefaultVersion(item); }, ); @@ -1403,7 +1534,9 @@ Type 'help' to get help. async () => { await vscode.commands.executeCommand( "workbench.action.openSettings", - "powerShellAdditionalExePaths", + usesAdditionalPowerShellLocations + ? "additionalPowerShellLocations" + : "powerShellAdditionalExePaths", ); }, ), diff --git a/test/core/platform.test.ts b/test/core/platform.test.ts index e6de6a5f3a..74ee566d66 100644 --- a/test/core/platform.test.ts +++ b/test/core/platform.test.ts @@ -807,11 +807,111 @@ function setupTestEnvironment(testPlatform: ITestPlatform): void { } } +function getSupportedPlatformForTest( + testPlatform: ITestPlatform, +): platform.SupportedPlatform { + switch (testPlatform.platformDetails.operatingSystem) { + case platform.OperatingSystem.Windows: + return "windows-x64"; + case platform.OperatingSystem.MacOS: + return "macos-x64"; + case platform.OperatingSystem.Linux: + return "linux-x64"; + default: + throw new Error("Unsupported test platform"); + } +} + +function getNonMatchingSupportedPlatformForTest( + supportedPlatform: platform.SupportedPlatform, +): platform.SupportedPlatform { + switch (supportedPlatform) { + case "windows-x64": + case "windows-arm64": + return "linux-x64"; + case "macos-x64": + case "macos-arm64": + return "windows-x64"; + case "linux-x64": + case "linux-aarch64": + return "windows-x64"; + } +} + describe("Platform module", function () { afterEach(function () { mockFS.restore(); }); + it("Maps supported platforms from OS and architecture", function () { + assert.strictEqual( + platform.getSupportedPlatform( + { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: true, + }, + "x64", + ), + "windows-x64", + ); + assert.strictEqual( + platform.getSupportedPlatform( + { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: true, + }, + "arm64", + ), + "windows-arm64", + ); + assert.strictEqual( + platform.getSupportedPlatform( + { + operatingSystem: platform.OperatingSystem.MacOS, + isOS64Bit: true, + isProcess64Bit: true, + }, + "x64", + ), + "macos-x64", + ); + assert.strictEqual( + platform.getSupportedPlatform( + { + operatingSystem: platform.OperatingSystem.MacOS, + isOS64Bit: true, + isProcess64Bit: true, + }, + "arm64", + ), + "macos-arm64", + ); + assert.strictEqual( + platform.getSupportedPlatform( + { + operatingSystem: platform.OperatingSystem.Linux, + isOS64Bit: true, + isProcess64Bit: true, + }, + "x64", + ), + "linux-x64", + ); + assert.strictEqual( + platform.getSupportedPlatform( + { + operatingSystem: platform.OperatingSystem.Linux, + isOS64Bit: true, + isProcess64Bit: true, + }, + "arm64", + ), + "linux-aarch64", + ); + }); + it("Gets the correct platform details", function () { const platformDetails: platform.IPlatformDetails = platform.getPlatformDetails(); @@ -1064,4 +1164,154 @@ describe("Platform module", function () { }); } }); + + describe("PowerShell executables from 'additionalPowerShellLocations' are found", function () { + for (const testPlatform of successAdditionalTestCases) { + it(`Finds and filters for ${testPlatform.name}`, async function () { + setupTestEnvironment(testPlatform); + + const supportedPlatform = + getSupportedPlatformForTest(testPlatform); + const nonMatchingPlatform = + getNonMatchingSupportedPlatformForTest(supportedPlatform); + const additionalPowerShellLocations: platform.IAdditionalPowerShellLocation[] = + testPlatform.expectedPowerShellSequence.map( + (expectedPowerShell, index) => ({ + name: expectedPowerShell.displayName, + path: expectedPowerShell.exePath, + platform: supportedPlatform, + weight: + testPlatform.expectedPowerShellSequence.length - + index, + }), + ); + + additionalPowerShellLocations.push({ + name: "Wrong Platform", + path: "does-not-exist", + platform: nonMatchingPlatform, + weight: 100, + }); + + const powerShellExeFinder = new platform.PowerShellExeFinder( + testPlatform.platformDetails, + {}, + undefined, + additionalPowerShellLocations, + "x64", + ); + + let i = 0; + for await (const additionalPwsh of powerShellExeFinder.enumerateAdditionalPowerShellLocations()) { + const expectedPowerShell = + testPlatform.expectedPowerShellSequence[i]; + i++; + + assert.strictEqual( + additionalPwsh.exePath, + expectedPowerShell.exePath, + ); + assert.strictEqual( + additionalPwsh.displayName, + expectedPowerShell.displayName, + ); + } + + assert.strictEqual( + i, + testPlatform.expectedPowerShellSequence.length, + ); + }); + + it(`Takes priority over legacy setting for ${testPlatform.name}`, async function () { + setupTestEnvironment(testPlatform); + + const supportedPlatform = + getSupportedPlatformForTest(testPlatform); + const expectedPowerShell = + testPlatform.expectedPowerShellSequence[0]; + const powerShellExeFinder = new platform.PowerShellExeFinder( + testPlatform.platformDetails, + { + "Old PowerShell": "does-not-exist", + }, + undefined, + [ + { + name: expectedPowerShell.displayName, + path: expectedPowerShell.exePath, + platform: supportedPlatform, + weight: 10, + }, + ], + "x64", + ); + + const foundPowerShells = + await powerShellExeFinder.getAllAvailablePowerShellInstallations(); + + assert.strictEqual( + foundPowerShells.some( + (powerShell) => + powerShell.displayName === + expectedPowerShell.displayName, + ), + true, + ); + assert.strictEqual( + foundPowerShells.some( + (powerShell) => + powerShell.displayName === "Old PowerShell", + ), + false, + ); + }); + } + + it("Sorts matching entries by descending weight", async function () { + const testPlatform = successAdditionalTestCases[0]; + setupTestEnvironment(testPlatform); + + const supportedPlatform = getSupportedPlatformForTest(testPlatform); + const lowWeightPowerShell = + testPlatform.expectedPowerShellSequence[0]; + const highWeightPowerShell = + testPlatform.expectedPowerShellSequence[1]; + const powerShellExeFinder = new platform.PowerShellExeFinder( + testPlatform.platformDetails, + {}, + undefined, + [ + { + name: lowWeightPowerShell.displayName, + path: lowWeightPowerShell.exePath, + platform: supportedPlatform, + weight: 1, + }, + { + name: highWeightPowerShell.displayName, + path: highWeightPowerShell.exePath, + platform: supportedPlatform, + weight: 10, + }, + ], + "x64", + ); + + const foundPowerShells = + await powerShellExeFinder.getAllAvailablePowerShellInstallations(); + const highWeightIndex = foundPowerShells.findIndex( + (powerShell) => + powerShell.displayName === highWeightPowerShell.displayName, + ); + const lowWeightIndex = foundPowerShells.findIndex( + (powerShell) => + powerShell.displayName === lowWeightPowerShell.displayName, + ); + + assert.strictEqual(highWeightIndex >= 0, true); + assert.strictEqual(lowWeightIndex >= 0, true); + assert.strictEqual(highWeightIndex < lowWeightIndex, true); + }); + }); }); From bf2208ef989a8ec3fe4001735de7b77b882775fd Mon Sep 17 00:00:00 2001 From: Chawye Hsu Date: Thu, 30 Apr 2026 20:15:32 +0800 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/session.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/session.ts b/src/session.ts index 79125b4e22..e0cd96188d 100644 --- a/src/session.ts +++ b/src/session.ts @@ -1401,7 +1401,8 @@ Type 'help' to get help. const selectedIndex = additionalPowerShellLocations.findIndex( (location) => location.platform === supportedPlatform && - location.name === exePath.displayName, + location.name === exePath.displayName && + location.path === exePath.exePath, ); if (selectedIndex < 0) { From 49e228d737f4210f6105c5e6a66947bc28a39a46 Mon Sep 17 00:00:00 2001 From: Chawye Hsu Date: Fri, 1 May 2026 00:37:10 +0800 Subject: [PATCH 3/3] Tweak naming Signed-off-by: Chawye Hsu --- src/platform.ts | 18 +++++++++-------- src/session.ts | 41 +++++++++++++++++++++++--------------- test/core/platform.test.ts | 2 +- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index e30b0b52ac..65efecdae6 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -138,14 +138,16 @@ export class PowerShellExeFinder { /** * Create a new PowerShellFinder object to discover PowerShell installations. * @param platformDetails Information about the machine we are running on. - * @param additionalPowerShellExes Additional PowerShell installations as configured in the settings. + * @param additionalPowerShellExePaths `additionalPowerShellExePaths` in the settings. + * @param additionalPowerShellLocations `additionalPowerShellLocations` in the settings. */ constructor( // The platform details descriptor for the platform we're on private platformDetails: IPlatformDetails, - // Additional configured PowerShells - private additionalPowerShellExes: Record, + // (Old) Additional configured PowerShells + private additionalPowerShellExePaths: Record, private logger?: ILogger, + // (New) Additional configured PowerShell locations private additionalPowerShellLocations: IAdditionalPowerShellLocation[] = [], private processArchitecture: NodeJS.Architecture = process.arch, ) {} @@ -218,7 +220,7 @@ export class PowerShellExeFinder { const configuredPowerShells = this.additionalPowerShellLocations.length > 0 ? this.enumerateAdditionalPowerShellLocations() - : this.enumerateAdditionalPowerShellInstallations(); + : this.enumerateAdditionalPowerShellExePaths(); for await (const additionalPwsh of configuredPowerShells) { if (await additionalPwsh.exists()) { yield additionalPwsh; @@ -326,17 +328,17 @@ export class PowerShellExeFinder { } /** - * Iterates through the configured additional PowerShell executable locations, + * Iterates through the configured `additionalPowerShellExePaths`, * without checking for their existence. */ - public async *enumerateAdditionalPowerShellInstallations(): AsyncIterable { + public async *enumerateAdditionalPowerShellExePaths(): AsyncIterable { yield* this.enumerateConfiguredPowerShellInstallations( - Object.entries(this.additionalPowerShellExes), + Object.entries(this.additionalPowerShellExePaths), ); } /** - * Iterates through the configured additional PowerShell locations for the current platform, + * Iterates through the configured `additionalPowerShellLocations` for the current platform, * without checking for their existence. */ public async *enumerateAdditionalPowerShellLocations(): AsyncIterable { diff --git a/src/session.ts b/src/session.ts index e0cd96188d..c984b28fe2 100644 --- a/src/session.ts +++ b/src/session.ts @@ -724,6 +724,18 @@ export class SessionManager implements Middleware { return foundPowerShell; } + /** + * Read `powershell.powerShellAdditionalExePaths` setting + */ + private getAdditionalPowerShellExePaths(): Record { + return vscode.workspace + .getConfiguration("powershell") + .get>("powerShellAdditionalExePaths", {}); + } + + /** + * Read `powershell.additionalPowerShellLocations` setting + */ private getAdditionalPowerShellLocations(): IAdditionalPowerShellLocation[] { return vscode.workspace .getConfiguration("powershell") @@ -732,7 +744,10 @@ export class SessionManager implements Middleware { >("additionalPowerShellLocations", []); } - private usesAdditionalPowerShellLocations( + /** + * Check if `powershell.additionalPowerShellLocations` setting has any entries + */ + private hasAdditionalPowerShellLocations( additionalPowerShellLocations = this.getAdditionalPowerShellLocations(), ): boolean { return additionalPowerShellLocations.length > 0; @@ -742,9 +757,7 @@ export class SessionManager implements Middleware { additionalPowerShellLocations = this.getAdditionalPowerShellLocations(), ): string { if ( - this.usesAdditionalPowerShellLocations( - additionalPowerShellLocations, - ) + this.hasAdditionalPowerShellLocations(additionalPowerShellLocations) ) { return ( this.getTopRankedAdditionalPowerShellLocation( @@ -777,16 +790,12 @@ export class SessionManager implements Middleware { private createPowerShellExeFinder( additionalPowerShellLocations = this.getAdditionalPowerShellLocations(), ): PowerShellExeFinder { + // Set `additionalPowerShellExePaths` to empty if + // `additionalPowerShellLocations` is configured const powerShellAdditionalExePaths = - this.usesAdditionalPowerShellLocations( - additionalPowerShellLocations, - ) + this.hasAdditionalPowerShellLocations(additionalPowerShellLocations) ? {} - : vscode.workspace - .getConfiguration("powershell") - .get< - Record - >("powerShellAdditionalExePaths", {}); + : this.getAdditionalPowerShellExePaths(); return new PowerShellExeFinder( this.platformDetails, @@ -1470,8 +1479,8 @@ Type 'help' to get help. private async showSessionMenu(): Promise { const additionalPowerShellLocations = this.getAdditionalPowerShellLocations(); - const usesAdditionalPowerShellLocations = - this.usesAdditionalPowerShellLocations( + const hasAdditionalPowerShellLocations = + this.hasAdditionalPowerShellLocations( additionalPowerShellLocations, ); const powershellExeFinder = this.createPowerShellExeFinder( @@ -1489,7 +1498,7 @@ Type 'help' to get help. return new SessionMenuItem( `Switch to: ${item.displayName}`, async () => { - if (usesAdditionalPowerShellLocations) { + if (hasAdditionalPowerShellLocations) { await this.increaseAdditionalPowerShellLocationWeight( item, ); @@ -1535,7 +1544,7 @@ Type 'help' to get help. async () => { await vscode.commands.executeCommand( "workbench.action.openSettings", - usesAdditionalPowerShellLocations + hasAdditionalPowerShellLocations ? "additionalPowerShellLocations" : "powerShellAdditionalExePaths", ); diff --git a/test/core/platform.test.ts b/test/core/platform.test.ts index 74ee566d66..3917b6078a 100644 --- a/test/core/platform.test.ts +++ b/test/core/platform.test.ts @@ -1147,7 +1147,7 @@ describe("Platform module", function () { ); let i = 0; - for await (const additionalPwsh of powerShellExeFinder.enumerateAdditionalPowerShellInstallations()) { + for await (const additionalPwsh of powerShellExeFinder.enumerateAdditionalPowerShellExePaths()) { const expectedPowerShell = testPlatform.expectedPowerShellSequence[i]; i++;