From 251c495da5fd30d4349fe652a4f12488ff8e89ef Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 24 Apr 2023 13:04:30 -0700 Subject: [PATCH 1/4] Update startup logic to handle session failure reasons These used to be sent given that the logic existed, but the server was no longer sending them at all. Now that it does, we can handle them. --- src/session.ts | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/session.ts b/src/session.ts index c28708669f..a6fb0391c0 100644 --- a/src/session.ts +++ b/src/session.ts @@ -44,7 +44,6 @@ export enum RunspaceType { export interface IEditorServicesSessionDetails { status: string; reason: string; - detail: string; powerShellVersion: string; channel: string; languageServicePort: number; @@ -430,7 +429,7 @@ export class SessionManager implements Middleware { private async startPowerShell(): Promise { if (this.PowerShellExeDetails === undefined) { - this.setSessionFailure("Unable to find PowerShell."); + void this.setSessionFailedGetPowerShell("Unable to find PowerShell, try installing it?"); return; } @@ -498,10 +497,10 @@ export class SessionManager implements Middleware { void this.setSessionFailedOpenBug("Language client failed to start: " + (err instanceof Error ? err.message : "unknown")); } } else if (this.sessionDetails.status === "failed") { // Server started but indicated it failed - if (this.sessionDetails.reason === "unsupported") { + if (this.sessionDetails.reason === "powerShellVersion") { void this.setSessionFailedGetPowerShell(`PowerShell ${this.sessionDetails.powerShellVersion} is not supported, please update!`); - } else if (this.sessionDetails.reason === "languageMode") { - this.setSessionFailure(`PowerShell language features are disabled due to an unsupported LanguageMode: ${this.sessionDetails.detail}`); + } else if (this.sessionDetails.reason === "dotNetVersion") { // Only applies to PowerShell 5.1 + void this.setSessionFailedGetDotNet(".NET Framework is out-of-date, please install at least 4.8!"); } else { void this.setSessionFailedOpenBug(`PowerShell could not be started for an unknown reason: ${this.sessionDetails.reason}`); } @@ -728,7 +727,7 @@ Type 'help' to get help. try { await this.languageClient.start(); } catch (err) { - this.setSessionFailure("Could not start language service: ", err instanceof Error ? err.message : "unknown"); + void this.setSessionFailedOpenBug("Could not start language service: " + (err instanceof Error ? err.message : "unknown")); return; } @@ -799,11 +798,6 @@ Type 'help' to get help. this.setSessionStatus("Executing...", SessionStatus.Busy); } - private setSessionFailure(message: string, ...additionalMessages: string[]): void { - this.setSessionStatus("Initialization Error!", SessionStatus.Failed); - void this.logger.writeAndShowError(message, ...additionalMessages); - } - private async setSessionFailedOpenBug(message: string): Promise { this.setSessionStatus("Initialization Error!", SessionStatus.Failed); await this.logger.writeAndShowErrorWithActions(message, [{ @@ -825,6 +819,17 @@ Type 'help' to get help. ); } + private async setSessionFailedGetDotNet(message: string): Promise { + this.setSessionStatus("Initialization Error!", SessionStatus.Failed); + await this.logger.writeAndShowErrorWithActions(message, [{ + prompt: "Open .NET Framework Documentation", + action: async (): Promise => { + await vscode.env.openExternal( + vscode.Uri.parse("https://dotnet.microsoft.com/en-us/download/dotnet-framework")); + }}] + ); + } + private async changePowerShellDefaultVersion(exePath: IPowerShellExeDetails): Promise { this.suppressRestartPrompt = true; try { From 686b4c9c45ffd11de61d40f2898cd6b10385a9ad Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 24 Apr 2023 13:50:33 -0700 Subject: [PATCH 2/4] Fix "Show Session Menu" to use updated settings Otherwise you could add and you'd have to restart to get an updated menu! --- src/session.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/session.ts b/src/session.ts index a6fb0391c0..97ecc73922 100644 --- a/src/session.ts +++ b/src/session.ts @@ -862,7 +862,8 @@ Type 'help' to get help. private async showSessionMenu(): Promise { const powershellExeFinder = new PowerShellExeFinder( this.platformDetails, - this.sessionSettings.powerShellAdditionalExePaths, + // We don't pull from session settings because we want them fresh! + getSettings().powerShellAdditionalExePaths, this.logger); const availablePowerShellExes = await powershellExeFinder.getAllAvailablePowerShellInstallations(); From b0f85f3b5a278b022d2633eee12152597bca25ed Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 24 Apr 2023 13:52:31 -0700 Subject: [PATCH 3/4] Fix handling the Extension Terminal failing to start Now we dispose on the exited event (so we don't have to do it in the catch around `start`). We subscribe the `onDidCloseTerminal` event right before creating the terminal so it fires even if the terminal immediately closes. We check if the terminal is still defined (not crashed) when waiting for the session file. --- src/process.ts | 37 ++++++++++++++++++++++--------------- src/session.ts | 3 --- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/process.ts b/src/process.ts index 06d0f0d36e..1078f316b8 100644 --- a/src/process.ts +++ b/src/process.ts @@ -111,28 +111,28 @@ export class PowerShellProcess { hideFromUser: this.sessionSettings.integratedConsole.startInBackground, }; + // Subscribe a log event for when the terminal closes (this fires for + // all terminals and the event itself checks if it's our terminal). This + // subscription should happen before we create the terminal so if it + // fails immediately, the event fires. + this.consoleCloseSubscription = vscode.window.onDidCloseTerminal((terminal) => this.onTerminalClose(terminal)); + this.consoleTerminal = vscode.window.createTerminal(terminalOptions); const pwshName = path.basename(this.exePath); this.logger.write(`${pwshName} started.`); + // Log that the PowerShell terminal process has been started + const pid = await this.getPid(); + this.logTerminalPid(pid ?? 0, pwshName); + if (this.sessionSettings.integratedConsole.showOnStartup && !this.sessionSettings.integratedConsole.startInBackground) { // We still need to run this to set the active terminal to the extension terminal. this.consoleTerminal.show(true); } - // Start the language client - const sessionDetails = await this.waitForSessionFile(); - - // Subscribe a log event for when the terminal closes - this.consoleCloseSubscription = vscode.window.onDidCloseTerminal((terminal) => this.onTerminalClose(terminal)); - - // Log that the PowerShell terminal process has been started - const pid = await this.getPid(); - this.logTerminalPid(pid ?? 0, pwshName); - - return sessionDetails; + return await this.waitForSessionFile(); } // This function should only be used after a failure has occurred because it is slow! @@ -154,7 +154,7 @@ export class PowerShellProcess { public async dispose(): Promise { // Clean up the session file - this.logger.write("Terminating PowerShell process..."); + this.logger.write("Disposing PowerShell Extension Terminal..."); this.consoleTerminal?.dispose(); this.consoleTerminal = undefined; @@ -199,8 +199,8 @@ export class PowerShellProcess { private static async deleteSessionFile(sessionFilePath: vscode.Uri): Promise { try { await vscode.workspace.fs.delete(sessionFilePath); - } catch (e) { - // TODO: Be more specific about what we're catching + } catch { + // We don't care about any reasons for it to fail. } } @@ -212,6 +212,12 @@ export class PowerShellProcess { // Check every 2 seconds this.logger.write("Waiting for session file..."); for (let i = numOfTries; i > 0; i--) { + if (this.consoleTerminal === undefined) { + const err = "PowerShell Extension Terminal didn't start!"; + this.logger.write(err); + throw new Error(err); + } + if (await utils.checkIfFileExists(this.sessionFilePath)) { this.logger.write("Session file found!"); const sessionDetails = await PowerShellProcess.readSessionFile(this.sessionFilePath); @@ -237,7 +243,8 @@ export class PowerShellProcess { return; } - this.logger.write("powershell.exe terminated or terminal UI was closed"); + this.logger.write("PowerShell process terminated or Extension Terminal was closed!"); this.onExitedEmitter.fire(); + void this.dispose(); } } diff --git a/src/session.ts b/src/session.ts index 97ecc73922..f771c2ff74 100644 --- a/src/session.ts +++ b/src/session.ts @@ -457,9 +457,6 @@ export class SessionManager implements Middleware { try { this.sessionDetails = await languageServerProcess.start("EditorServices"); } catch (err) { - // We should kill the process in case it's stuck. - void languageServerProcess.dispose(); - // PowerShell never started, probably a bad version! const version = await languageServerProcess.getVersionCli(); let shouldUpdate = true; From 2d74846f9fc40ba0baccf12f4a17d70de6f78f27 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 24 Apr 2023 14:05:30 -0700 Subject: [PATCH 4/4] Get version for status icon when PowerShell fails to start Grab it off the executable details since it won't be coming from the LSP request. --- src/session.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/session.ts b/src/session.ts index f771c2ff74..3577a7b5ef 100644 --- a/src/session.ts +++ b/src/session.ts @@ -757,6 +757,9 @@ Type 'help' to get help. const semver = new SemVer(this.versionDetails.version); this.languageStatusItem.text = `$(terminal-powershell) ${semver.major}.${semver.minor}`; this.languageStatusItem.detail += ` ${this.versionDetails.commit} (${this.versionDetails.architecture.toLowerCase()})`; + } else if (this.PowerShellExeDetails?.displayName) { // In case it didn't start. + this.languageStatusItem.text = `$(terminal-powershell) ${this.PowerShellExeDetails.displayName}`; + this.languageStatusItem.detail += `: ${this.PowerShellExeDetails.exePath}`; } if (statusText) {