From f4411746667ef87683d9d34cd4908e28001609ed Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 17 Jun 2020 22:18:16 -0700 Subject: [PATCH 001/176] Add progress --- .../PowerShellEditorServices.csproj | 2 +- .../PowerShell/Console/ColorConfiguration.cs | 37 ++ .../PowerShell/Console/ConsoleProxy.cs | 201 ++++++ .../PowerShell/Console/ConsoleReadLine.cs | 619 ++++++++++++++++++ .../PowerShell/Console/IConsoleOperations.cs | 140 ++++ .../PowerShell/Console/PSReadLineProxy.cs | 184 ++++++ .../Console/UnixConsoleOperations.cs | 298 +++++++++ .../Console/WindowsConsoleOperations.cs | 77 +++ .../Execution/ExecutionCanceledException.cs | 12 + .../Execution/PowerShellExecutionOptions.cs | 29 + .../Execution/SynchronousDelegateTask.cs | 45 ++ .../Execution/SynchronousPowerShellTask.cs | 82 +++ .../PowerShell/Execution/SynchronousTask.cs | 68 ++ .../PowerShell/Host/ConsoleColorProxy.cs | 153 +++++ .../Host/EditorServicesConsolePSHost.cs | 85 +++ ...orServicesConsolePSHostRawUserInterface.cs | 56 ++ ...ditorServicesConsolePSHostUserInterface.cs | 131 ++++ .../Services/PowerShell/Host/IReadLine.cs | 10 + .../PowerShell/PowerShellConsoleService.cs | 97 +++ .../PowerShell/PowerShellExecutionService.cs | 182 +++++ .../Utility/ErrorRecordExtensions.cs | 55 ++ .../PowerShell/Utility/PSCommandExtensions.cs | 48 ++ .../Utility/PowerShellExtensions.cs | 78 +++ .../PowerShell/Utility/RunspaceExtensions.cs | 29 + 24 files changed, 2717 insertions(+), 1 deletion(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleProxy.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Console/IConsoleOperations.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Console/UnixConsoleOperations.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Console/WindowsConsoleOperations.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionCanceledException.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/ConsoleColorProxy.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/IReadLine.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 55802fc82..04af52eaf 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -42,6 +42,6 @@ - + diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs new file mode 100644 index 000000000..e7ebee377 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace PowerShellEditorServices.Services.PowerShell.Console +{ + internal class ColorConfiguration + { + public ConsoleColor ForegroundColor { get; set; } + + public ConsoleColor BackgroundColor { get; set; } + + public ConsoleColor FormatAccentColor { get; set; } + + public ConsoleColor ErrorAccentColor { get; set; } + + public ConsoleColor ErrorForegroundColor { get; set; } + + public ConsoleColor ErrorBackgroundColor { get; set; } + + public ConsoleColor WarningForegroundColor { get; set; } + + public ConsoleColor WarningBackgroundColor { get; set; } + + public ConsoleColor DebugForegroundColor { get; set; } + + public ConsoleColor DebugBackgroundColor { get; set; } + + public ConsoleColor VerboseForegroundColor { get; set; } + + public ConsoleColor VerboseBackgroundColor { get; set; } + + public ConsoleColor ProgressForegroundColor { get; set; } + + public ConsoleColor ProgressBackgroundColor { get; set; } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleProxy.cs new file mode 100644 index 000000000..22892b445 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleProxy.cs @@ -0,0 +1,201 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console +{ + /// + /// Provides asynchronous implementations of the API's as well as + /// synchronous implementations that work around platform specific issues. + /// + internal static class ConsoleProxy + { + private static IConsoleOperations s_consoleProxy; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline", Justification = "Platform specific initialization")] + static ConsoleProxy() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + s_consoleProxy = new WindowsConsoleOperations(); + return; + } + + s_consoleProxy = new UnixConsoleOperations(); + } + + /// + /// Obtains the next character or function key pressed by the user asynchronously. + /// Does not block when other console API's are called. + /// + /// + /// Determines whether to display the pressed key in the console window. + /// to not display the pressed key; otherwise, . + /// + /// The CancellationToken to observe. + /// + /// An object that describes the constant and Unicode character, if any, + /// that correspond to the pressed console key. The object also + /// describes, in a bitwise combination of values, whether + /// one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously with the console key. + /// + public static ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken) => + s_consoleProxy.ReadKey(intercept, cancellationToken); + + /// + /// Obtains the next character or function key pressed by the user asynchronously. + /// Does not block when other console API's are called. + /// + /// + /// Determines whether to display the pressed key in the console window. + /// to not display the pressed key; otherwise, . + /// + /// The CancellationToken to observe. + /// + /// A task that will complete with a result of the key pressed by the user. + /// + public static Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken) => + s_consoleProxy.ReadKeyAsync(intercept, cancellationToken); + + /// + /// Obtains the horizontal position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// The horizontal position of the console cursor. + public static int GetCursorLeft() => + s_consoleProxy.GetCursorLeft(); + + /// + /// Obtains the horizontal position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// The to observe. + /// The horizontal position of the console cursor. + public static int GetCursorLeft(CancellationToken cancellationToken) => + s_consoleProxy.GetCursorLeft(cancellationToken); + + /// + /// Obtains the horizontal position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// + /// A representing the asynchronous operation. The + /// property will return the horizontal position + /// of the console cursor. + /// + public static Task GetCursorLeftAsync() => + s_consoleProxy.GetCursorLeftAsync(); + + /// + /// Obtains the horizontal position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// The to observe. + /// + /// A representing the asynchronous operation. The + /// property will return the horizontal position + /// of the console cursor. + /// + public static Task GetCursorLeftAsync(CancellationToken cancellationToken) => + s_consoleProxy.GetCursorLeftAsync(cancellationToken); + + /// + /// Obtains the vertical position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// The vertical position of the console cursor. + public static int GetCursorTop() => + s_consoleProxy.GetCursorTop(); + + /// + /// Obtains the vertical position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// The to observe. + /// The vertical position of the console cursor. + public static int GetCursorTop(CancellationToken cancellationToken) => + s_consoleProxy.GetCursorTop(cancellationToken); + + /// + /// Obtains the vertical position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// + /// A representing the asynchronous operation. The + /// property will return the vertical position + /// of the console cursor. + /// + public static Task GetCursorTopAsync() => + s_consoleProxy.GetCursorTopAsync(); + + /// + /// Obtains the vertical position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// The to observe. + /// + /// A representing the asynchronous operation. The + /// property will return the vertical position + /// of the console cursor. + /// + public static Task GetCursorTopAsync(CancellationToken cancellationToken) => + s_consoleProxy.GetCursorTopAsync(cancellationToken); + + /// + /// This method is sent to PSReadLine as a workaround for issues with the System.Console + /// implementation. Functionally it is the same as System.Console.ReadKey, + /// with the exception that it will not lock the standard input stream. + /// + /// + /// Determines whether to display the pressed key in the console window. + /// true to not display the pressed key; otherwise, false. + /// + /// + /// The that can be used to cancel the request. + /// + /// + /// An object that describes the ConsoleKey constant and Unicode character, if any, + /// that correspond to the pressed console key. The ConsoleKeyInfo object also describes, + /// in a bitwise combination of ConsoleModifiers values, whether one or more Shift, Alt, + /// or Ctrl modifier keys was pressed simultaneously with the console key. + /// + internal static ConsoleKeyInfo SafeReadKey(bool intercept, CancellationToken cancellationToken) + { + try + { + return s_consoleProxy.ReadKey(intercept, cancellationToken); + } + catch (OperationCanceledException) + { + return new ConsoleKeyInfo( + keyChar: ' ', + ConsoleKey.DownArrow, + shift: false, + alt: false, + control: false); + } + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs new file mode 100644 index 000000000..d6e116e9b --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -0,0 +1,619 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console +{ + using System; + using System.Management.Automation; + using System.Management.Automation.Language; + using System.Security; + + internal class ConsoleReadLine + { + #region Private Field + private readonly PowerShellExecutionService _executionService; + + #endregion + + #region Constructors + + public ConsoleReadLine(PowerShellExecutionService executionService) + { + _executionService = executionService; + } + + #endregion + + #region Public Methods + + public Task ReadCommandLineAsync(CancellationToken cancellationToken) + { + return ReadLineAsync(true, cancellationToken); + } + + public Task ReadSimpleLineAsync(CancellationToken cancellationToken) + { + return ReadLineAsync(false, cancellationToken); + } + + public async Task ReadSecureLineAsync(CancellationToken cancellationToken) + { + SecureString secureString = new SecureString(); + + // TODO: Are these values used? + int initialPromptRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false); + int initialPromptCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken).ConfigureAwait(false); + int previousInputLength = 0; + + Console.TreatControlCAsInput = true; + + try + { + while (!cancellationToken.IsCancellationRequested) + { + ConsoleKeyInfo keyInfo = await ReadKeyAsync(cancellationToken).ConfigureAwait(false); + + if ((int)keyInfo.Key == 3 || + keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) + { + throw new PipelineStoppedException(); + } + if (keyInfo.Key == ConsoleKey.Enter) + { + // Break to return the completed string + break; + } + if (keyInfo.Key == ConsoleKey.Tab) + { + continue; + } + if (keyInfo.Key == ConsoleKey.Backspace) + { + if (secureString.Length > 0) + { + secureString.RemoveAt(secureString.Length - 1); + } + } + else if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar)) + { + secureString.AppendChar(keyInfo.KeyChar); + } + + // Re-render the secure string characters + int currentInputLength = secureString.Length; + int consoleWidth = Console.WindowWidth; + + if (currentInputLength > previousInputLength) + { + Console.Write('*'); + } + else if (previousInputLength > 0 && currentInputLength < previousInputLength) + { + int row = await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false); + int col = await ConsoleProxy.GetCursorLeftAsync(cancellationToken).ConfigureAwait(false); + + // Back up the cursor before clearing the character + col--; + if (col < 0) + { + col = consoleWidth - 1; + row--; + } + + Console.SetCursorPosition(col, row); + Console.Write(' '); + Console.SetCursorPosition(col, row); + } + + previousInputLength = currentInputLength; + } + } + finally + { + Console.TreatControlCAsInput = false; + } + + return secureString; + } + + #endregion + + #region Private Methods + + private static Task ReadKeyAsync(CancellationToken cancellationToken) + { + return ConsoleProxy.ReadKeyAsync(intercept: true, cancellationToken); + } + + private Task ReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) + { + return this.powerShellContext.InvokeReadLineAsync(isCommandLine, cancellationToken); + } + + /// + /// Invokes a custom ReadLine method that is similar to but more basic than PSReadLine. + /// This method should be used when PSReadLine is disabled, either by user settings or + /// unsupported PowerShell versions. + /// + /// + /// Indicates whether ReadLine should act like a command line. + /// + /// + /// The cancellation token that will be checked prior to completing the returned task. + /// + /// + /// A task object representing the asynchronus operation. The Result property on + /// the task object returns the user input string. + /// + internal async Task InvokeLegacyReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) + { + // TODO: Is inputBeforeCompletion used? + string inputBeforeCompletion = null; + string inputAfterCompletion = null; + CommandCompletion currentCompletion = null; + + int historyIndex = -1; + Collection currentHistory = null; + + StringBuilder inputLine = new StringBuilder(); + + int initialCursorCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken).ConfigureAwait(false); + int initialCursorRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false); + + // TODO: Are these used? + int initialWindowLeft = Console.WindowLeft; + int initialWindowTop = Console.WindowTop; + + int currentCursorIndex = 0; + + Console.TreatControlCAsInput = true; + + try + { + while (!cancellationToken.IsCancellationRequested) + { + ConsoleKeyInfo keyInfo = await ReadKeyAsync(cancellationToken).ConfigureAwait(false); + + // Do final position calculation after the key has been pressed + // because the window could have been resized before then + int promptStartCol = initialCursorCol; + int promptStartRow = initialCursorRow; + int consoleWidth = Console.WindowWidth; + + if ((int)keyInfo.Key == 3 || + keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) + { + throw new PipelineStoppedException(); + } + else if (keyInfo.Key == ConsoleKey.Tab && isCommandLine) + { + if (currentCompletion == null) + { + inputBeforeCompletion = inputLine.ToString(); + inputAfterCompletion = null; + + // TODO: This logic should be moved to AstOperations or similar! + + if (this.powerShellContext.IsDebuggerStopped) + { + PSCommand command = new PSCommand(); + command.AddCommand("TabExpansion2"); + command.AddParameter("InputScript", inputBeforeCompletion); + command.AddParameter("CursorColumn", currentCursorIndex); + command.AddParameter("Options", null); + + var results = await this.powerShellContext + .ExecuteCommandAsync(command, sendOutputToHost: false, sendErrorToHost: false) + .ConfigureAwait(false); + + currentCompletion = results.FirstOrDefault(); + } + else + { + using (RunspaceHandle runspaceHandle = await this.powerShellContext.GetRunspaceHandleAsync().ConfigureAwait(false)) + using (PowerShell powerShell = PowerShell.Create()) + { + powerShell.Runspace = runspaceHandle.Runspace; + currentCompletion = + CommandCompletion.CompleteInput( + inputBeforeCompletion, + currentCursorIndex, + null, + powerShell); + + if (currentCompletion.CompletionMatches.Count > 0) + { + int replacementEndIndex = + currentCompletion.ReplacementIndex + + currentCompletion.ReplacementLength; + + inputAfterCompletion = + inputLine.ToString( + replacementEndIndex, + inputLine.Length - replacementEndIndex); + } + else + { + currentCompletion = null; + } + } + } + } + + CompletionResult completion = + currentCompletion?.GetNextResult( + !keyInfo.Modifiers.HasFlag(ConsoleModifiers.Shift)); + + if (completion != null) + { + currentCursorIndex = + this.InsertInput( + inputLine, + promptStartCol, + promptStartRow, + $"{completion.CompletionText}{inputAfterCompletion}", + currentCursorIndex, + insertIndex: currentCompletion.ReplacementIndex, + replaceLength: inputLine.Length - currentCompletion.ReplacementIndex, + finalCursorIndex: currentCompletion.ReplacementIndex + completion.CompletionText.Length); + } + } + else if (keyInfo.Key == ConsoleKey.LeftArrow) + { + currentCompletion = null; + + if (currentCursorIndex > 0) + { + currentCursorIndex = + this.MoveCursorToIndex( + promptStartCol, + promptStartRow, + consoleWidth, + currentCursorIndex - 1); + } + } + else if (keyInfo.Key == ConsoleKey.Home) + { + currentCompletion = null; + + currentCursorIndex = + this.MoveCursorToIndex( + promptStartCol, + promptStartRow, + consoleWidth, + 0); + } + else if (keyInfo.Key == ConsoleKey.RightArrow) + { + currentCompletion = null; + + if (currentCursorIndex < inputLine.Length) + { + currentCursorIndex = + this.MoveCursorToIndex( + promptStartCol, + promptStartRow, + consoleWidth, + currentCursorIndex + 1); + } + } + else if (keyInfo.Key == ConsoleKey.End) + { + currentCompletion = null; + + currentCursorIndex = + this.MoveCursorToIndex( + promptStartCol, + promptStartRow, + consoleWidth, + inputLine.Length); + } + else if (keyInfo.Key == ConsoleKey.UpArrow && isCommandLine) + { + currentCompletion = null; + + // TODO: Ctrl+Up should allow navigation in multi-line input + + if (currentHistory == null) + { + historyIndex = -1; + + PSCommand command = new PSCommand(); + command.AddCommand("Get-History"); + + currentHistory = await this.powerShellContext.ExecuteCommandAsync(command, sendOutputToHost: false, sendErrorToHost: false) + .ConfigureAwait(false) + as Collection; + + if (currentHistory != null) + { + historyIndex = currentHistory.Count; + } + } + + if (currentHistory != null && currentHistory.Count > 0 && historyIndex > 0) + { + historyIndex--; + + currentCursorIndex = + this.InsertInput( + inputLine, + promptStartCol, + promptStartRow, + (string)currentHistory[historyIndex].Properties["CommandLine"].Value, + currentCursorIndex, + insertIndex: 0, + replaceLength: inputLine.Length); + } + } + else if (keyInfo.Key == ConsoleKey.DownArrow && isCommandLine) + { + currentCompletion = null; + + // The down arrow shouldn't cause history to be loaded, + // it's only for navigating an active history array + + if (historyIndex > -1 && historyIndex < currentHistory.Count && + currentHistory != null && currentHistory.Count > 0) + { + historyIndex++; + + if (historyIndex < currentHistory.Count) + { + currentCursorIndex = + this.InsertInput( + inputLine, + promptStartCol, + promptStartRow, + (string)currentHistory[historyIndex].Properties["CommandLine"].Value, + currentCursorIndex, + insertIndex: 0, + replaceLength: inputLine.Length); + } + else if (historyIndex == currentHistory.Count) + { + currentCursorIndex = + this.InsertInput( + inputLine, + promptStartCol, + promptStartRow, + string.Empty, + currentCursorIndex, + insertIndex: 0, + replaceLength: inputLine.Length); + } + } + } + else if (keyInfo.Key == ConsoleKey.Escape) + { + currentCompletion = null; + historyIndex = currentHistory != null ? currentHistory.Count : -1; + + currentCursorIndex = + this.InsertInput( + inputLine, + promptStartCol, + promptStartRow, + string.Empty, + currentCursorIndex, + insertIndex: 0, + replaceLength: inputLine.Length); + } + else if (keyInfo.Key == ConsoleKey.Backspace) + { + currentCompletion = null; + + if (currentCursorIndex > 0) + { + currentCursorIndex = + this.InsertInput( + inputLine, + promptStartCol, + promptStartRow, + string.Empty, + currentCursorIndex, + insertIndex: currentCursorIndex - 1, + replaceLength: 1, + finalCursorIndex: currentCursorIndex - 1); + } + } + else if (keyInfo.Key == ConsoleKey.Delete) + { + currentCompletion = null; + + if (currentCursorIndex < inputLine.Length) + { + currentCursorIndex = + this.InsertInput( + inputLine, + promptStartCol, + promptStartRow, + string.Empty, + currentCursorIndex, + replaceLength: 1, + finalCursorIndex: currentCursorIndex); + } + } + else if (keyInfo.Key == ConsoleKey.Enter) + { + string completedInput = inputLine.ToString(); + currentCompletion = null; + currentHistory = null; + + //if ((keyInfo.Modifiers & ConsoleModifiers.Shift) == ConsoleModifiers.Shift) + //{ + // // TODO: Start a new line! + // continue; + //} + + Parser.ParseInput( + completedInput, + out Token[] tokens, + out ParseError[] parseErrors); + + //if (parseErrors.Any(e => e.IncompleteInput)) + //{ + // // TODO: Start a new line! + // continue; + //} + + return completedInput; + } + else if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar)) + { + // Normal character input + currentCompletion = null; + + currentCursorIndex = + this.InsertInput( + inputLine, + promptStartCol, + promptStartRow, + keyInfo.KeyChar.ToString(), // TODO: Determine whether this should take culture into account + currentCursorIndex, + finalCursorIndex: currentCursorIndex + 1); + } + } + } + finally + { + Console.TreatControlCAsInput = false; + } + + return null; + } + + // TODO: Is this used? + private int CalculateIndexFromCursor( + int promptStartCol, + int promptStartRow, + int consoleWidth) + { + return + ((ConsoleProxy.GetCursorTop() - promptStartRow) * consoleWidth) + + ConsoleProxy.GetCursorLeft() - promptStartCol; + } + + private void CalculateCursorFromIndex( + int promptStartCol, + int promptStartRow, + int consoleWidth, + int inputIndex, + out int cursorCol, + out int cursorRow) + { + cursorCol = promptStartCol + inputIndex; + cursorRow = promptStartRow + cursorCol / consoleWidth; + cursorCol = cursorCol % consoleWidth; + } + + private int InsertInput( + StringBuilder inputLine, + int promptStartCol, + int promptStartRow, + string insertedInput, + int cursorIndex, + int insertIndex = -1, + int replaceLength = 0, + int finalCursorIndex = -1) + { + int consoleWidth = Console.WindowWidth; + int previousInputLength = inputLine.Length; + + if (insertIndex == -1) + { + insertIndex = cursorIndex; + } + + // Move the cursor to the new insertion point + this.MoveCursorToIndex( + promptStartCol, + promptStartRow, + consoleWidth, + insertIndex); + + // Edit the input string based on the insertion + if (insertIndex < inputLine.Length) + { + if (replaceLength > 0) + { + inputLine.Remove(insertIndex, replaceLength); + } + + inputLine.Insert(insertIndex, insertedInput); + } + else + { + inputLine.Append(insertedInput); + } + + // Re-render affected section + Console.Write( + inputLine.ToString( + insertIndex, + inputLine.Length - insertIndex)); + + if (inputLine.Length < previousInputLength) + { + Console.Write( + new string( + ' ', + previousInputLength - inputLine.Length)); + } + + // Automatically set the final cursor position to the end + // of the new input string. This is needed if the previous + // input string is longer than the new one and needed to have + // its old contents overwritten. This will position the cursor + // back at the end of the new text + if (finalCursorIndex == -1 && inputLine.Length < previousInputLength) + { + finalCursorIndex = inputLine.Length; + } + + if (finalCursorIndex > -1) + { + // Move the cursor to the final position + return + this.MoveCursorToIndex( + promptStartCol, + promptStartRow, + consoleWidth, + finalCursorIndex); + } + else + { + return inputLine.Length; + } + } + + private int MoveCursorToIndex( + int promptStartCol, + int promptStartRow, + int consoleWidth, + int newCursorIndex) + { + this.CalculateCursorFromIndex( + promptStartCol, + promptStartRow, + consoleWidth, + newCursorIndex, + out int newCursorCol, + out int newCursorRow); + + Console.SetCursorPosition(newCursorCol, newCursorRow); + + return newCursorIndex; + } + + #endregion + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/IConsoleOperations.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/IConsoleOperations.cs new file mode 100644 index 000000000..6efe2d26e --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/IConsoleOperations.cs @@ -0,0 +1,140 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console +{ + /// + /// Provides platform specific console utilities. + /// + internal interface IConsoleOperations + { + /// + /// Obtains the next character or function key pressed by the user asynchronously. + /// Does not block when other console API's are called. + /// + /// + /// Determines whether to display the pressed key in the console window. + /// to not display the pressed key; otherwise, . + /// + /// The CancellationToken to observe. + /// + /// An object that describes the constant and Unicode character, if any, + /// that correspond to the pressed console key. The object also + /// describes, in a bitwise combination of values, whether + /// one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously with the console key. + /// + ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken); + + /// + /// Obtains the next character or function key pressed by the user asynchronously. + /// Does not block when other console API's are called. + /// + /// + /// Determines whether to display the pressed key in the console window. + /// to not display the pressed key; otherwise, . + /// + /// The CancellationToken to observe. + /// + /// A task that will complete with a result of the key pressed by the user. + /// + Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken); + + /// + /// Obtains the horizontal position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// The horizontal position of the console cursor. + int GetCursorLeft(); + + /// + /// Obtains the horizontal position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// The to observe. + /// The horizontal position of the console cursor. + int GetCursorLeft(CancellationToken cancellationToken); + + /// + /// Obtains the horizontal position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// + /// A representing the asynchronous operation. The + /// property will return the horizontal position + /// of the console cursor. + /// + Task GetCursorLeftAsync(); + + /// + /// Obtains the horizontal position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// The to observe. + /// + /// A representing the asynchronous operation. The + /// property will return the horizontal position + /// of the console cursor. + /// + Task GetCursorLeftAsync(CancellationToken cancellationToken); + + /// + /// Obtains the vertical position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// The vertical position of the console cursor. + int GetCursorTop(); + + /// + /// Obtains the vertical position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// The to observe. + /// The vertical position of the console cursor. + int GetCursorTop(CancellationToken cancellationToken); + + /// + /// Obtains the vertical position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// + /// A representing the asynchronous operation. The + /// property will return the vertical position + /// of the console cursor. + /// + Task GetCursorTopAsync(); + + /// + /// Obtains the vertical position of the console cursor. Use this method + /// instead of to avoid triggering + /// pending calls to + /// on Unix platforms. + /// + /// The to observe. + /// + /// A representing the asynchronous operation. The + /// property will return the vertical position + /// of the console cursor. + /// + Task GetCursorTopAsync(CancellationToken cancellationToken); + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs new file mode 100644 index 000000000..94a80ca1b --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs @@ -0,0 +1,184 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.IO; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console +{ + internal class PSReadLineProxy + { + private const string FieldMemberType = "field"; + + private const string MethodMemberType = "method"; + + private const string AddToHistoryMethodName = "AddToHistory"; + + private const string SetKeyHandlerMethodName = "SetKeyHandler"; + + private const string ReadLineMethodName = "ReadLine"; + + private const string ReadKeyOverrideFieldName = "_readKeyOverride"; + + private const string VirtualTerminalTypeName = "Microsoft.PowerShell.Internal.VirtualTerminal"; + + private const string ForcePSEventHandlingMethodName = "ForcePSEventHandling"; + + private static readonly Type[] s_setKeyHandlerTypes = + { + typeof(string[]), + typeof(Action), + typeof(string), + typeof(string) + }; + + private static readonly Type[] s_addToHistoryTypes = { typeof(string) }; + + private static readonly string _psReadLineModulePath = Path.GetFullPath( + Path.Combine( + Path.GetDirectoryName(typeof(PSReadLineProxy).Assembly.Location), + "..", + "..", + "..", + "PSReadLine")); + + private static readonly string ReadLineInitScript = $@" + [System.Diagnostics.DebuggerHidden()] + [System.Diagnostics.DebuggerStepThrough()] + param() + end {{ + $module = Get-Module -ListAvailable PSReadLine | + Where-Object {{ $_.Version -ge '2.0.2' }} | + Sort-Object -Descending Version | + Select-Object -First 1 + if (-not $module) {{ + Import-Module '{_psReadLineModulePath.Replace("'", "''")}' + return [Microsoft.PowerShell.PSConsoleReadLine] + }} + + Import-Module -ModuleInfo $module + return [Microsoft.PowerShell.PSConsoleReadLine] + }}"; + + + public static async Task LoadAndCreateAsync(ILogger logger, PowerShellExecutionService executionService) + { + var importPsrlCommand = new PSCommand().AddScript(ReadLineInitScript); + var executionOptions = new PowerShellExecutionOptions + { + UseNewScope = true, + }; + + Type psConsoleReadLineType = (await executionService.ExecutePSCommandAsync(importPsrlCommand, executionOptions, CancellationToken.None).ConfigureAwait(false)).FirstOrDefault(); + + Type type = Type.GetType("Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2"); + await executionService.ExecuteDelegateAsync((cancellationToken) => RuntimeHelpers.RunClassConstructor(type.TypeHandle), CancellationToken.None).ConfigureAwait(false); + + return new PSReadLineProxy(psConsoleReadLineType, logger); + } + + private readonly FieldInfo _readKeyOverrideField; + + public PSReadLineProxy(Type psConsoleReadLine, ILogger logger) + { + ReadLine = (Func)psConsoleReadLine.GetMethod( + ReadLineMethodName, + new[] { typeof(Runspace), typeof(EngineIntrinsics), typeof(CancellationToken) }) + ?.CreateDelegate(typeof(Func)); + + ForcePSEventHandling = (Action)psConsoleReadLine.GetMethod( + ForcePSEventHandlingMethodName, + BindingFlags.Static | BindingFlags.NonPublic) + ?.CreateDelegate(typeof(Action)); + + AddToHistory = (Action)psConsoleReadLine.GetMethod( + AddToHistoryMethodName, + s_addToHistoryTypes) + ?.CreateDelegate(typeof(Action)); + + SetKeyHandler = (Action, string, string>)psConsoleReadLine.GetMethod( + SetKeyHandlerMethodName, + s_setKeyHandlerTypes) + ?.CreateDelegate(typeof(Action, string, string>)); + + _readKeyOverrideField = psConsoleReadLine.GetTypeInfo().Assembly + .GetType(VirtualTerminalTypeName) + ?.GetField(ReadKeyOverrideFieldName, BindingFlags.Static | BindingFlags.NonPublic); + + if (_readKeyOverrideField == null) + { + throw NewInvalidPSReadLineVersionException( + FieldMemberType, + ReadKeyOverrideFieldName, + logger); + } + + if (ReadLine == null) + { + throw NewInvalidPSReadLineVersionException( + MethodMemberType, + ReadLineMethodName, + logger); + } + + if (SetKeyHandler == null) + { + throw NewInvalidPSReadLineVersionException( + MethodMemberType, + SetKeyHandlerMethodName, + logger); + } + + if (AddToHistory == null) + { + throw NewInvalidPSReadLineVersionException( + MethodMemberType, + AddToHistoryMethodName, + logger); + } + + if (ForcePSEventHandling == null) + { + throw NewInvalidPSReadLineVersionException( + MethodMemberType, + ForcePSEventHandlingMethodName, + logger); + } + } + + internal Action AddToHistory { get; } + + internal Action, string, string> SetKeyHandler { get; } + + internal Action ForcePSEventHandling { get; } + + internal Func ReadLine { get; } + + internal void OverrideReadKey(Func readKeyFunc) + { + _readKeyOverrideField.SetValue(null, readKeyFunc); + } + + private static InvalidOperationException NewInvalidPSReadLineVersionException( + string memberType, + string memberName, + ILogger logger) + { + logger.LogError( + $"The loaded version of PSReadLine is not supported. The {memberType} \"{memberName}\" was not found."); + + return new InvalidOperationException(); + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/UnixConsoleOperations.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/UnixConsoleOperations.cs new file mode 100644 index 000000000..6197a8dc4 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/UnixConsoleOperations.cs @@ -0,0 +1,298 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; +using UnixConsoleEcho; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console +{ + internal class UnixConsoleOperations : IConsoleOperations + { + private const int LongWaitForKeySleepTime = 300; + + private const int ShortWaitForKeyTimeout = 5000; + + private const int ShortWaitForKeySpinUntilSleepTime = 30; + + private static readonly ManualResetEventSlim s_waitHandle = new ManualResetEventSlim(); + + private static readonly SemaphoreSlim s_readKeyHandle = AsyncUtils.CreateSimpleLockingSemaphore(); + + private static readonly SemaphoreSlim s_stdInHandle = AsyncUtils.CreateSimpleLockingSemaphore(); + + private Func WaitForKeyAvailable; + + private Func> WaitForKeyAvailableAsync; + + internal UnixConsoleOperations() + { + // Switch between long and short wait periods depending on if the + // user has recently (last 5 seconds) pressed a key to avoid preventing + // the CPU from entering low power mode. + WaitForKeyAvailable = LongWaitForKey; + WaitForKeyAvailableAsync = LongWaitForKeyAsync; + } + + public ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken) + { + s_readKeyHandle.Wait(cancellationToken); + + // On Unix platforms System.Console.ReadKey has an internal lock on stdin. Because + // of this, if a ReadKey call is pending in one thread and in another thread + // Console.CursorLeft is called, both threads block until a key is pressed. + + // To work around this we wait for a key to be pressed before actually calling Console.ReadKey. + // However, any pressed keys during this time will be echoed to the console. To get around + // this we use the UnixConsoleEcho package to disable echo prior to waiting. + if (VersionUtils.IsPS6) + { + InputEcho.Disable(); + } + + try + { + // The WaitForKeyAvailable delegate switches between a long delay between waits and + // a short timeout depending on how recently a key has been pressed. This allows us + // to let the CPU enter low power mode without compromising responsiveness. + while (!WaitForKeyAvailable(cancellationToken)); + } + finally + { + if (VersionUtils.IsPS6) + { + InputEcho.Disable(); + } + s_readKeyHandle.Release(); + } + + // A key has been pressed, so aquire a lock on our internal stdin handle. This is done + // so any of our calls to cursor position API's do not release ReadKey. + s_stdInHandle.Wait(cancellationToken); + try + { + return System.Console.ReadKey(intercept); + } + finally + { + s_stdInHandle.Release(); + } + } + + public async Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken) + { + await s_readKeyHandle.WaitAsync(cancellationToken).ConfigureAwait(false); + + // I tried to replace this library with a call to `stty -echo`, but unfortunately + // the library also sets up allowing backspace to trigger `Console.KeyAvailable`. + if (VersionUtils.IsPS6) + { + InputEcho.Disable(); + } + + try + { + while (!await WaitForKeyAvailableAsync(cancellationToken).ConfigureAwait(false)) ; + } + finally + { + if (VersionUtils.IsPS6) + { + InputEcho.Enable(); + } + s_readKeyHandle.Release(); + } + + await s_stdInHandle.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + return System.Console.ReadKey(intercept); + } + finally + { + s_stdInHandle.Release(); + } + } + + public int GetCursorLeft() + { + return GetCursorLeft(CancellationToken.None); + } + + public int GetCursorLeft(CancellationToken cancellationToken) + { + s_stdInHandle.Wait(cancellationToken); + try + { + return System.Console.CursorLeft; + } + finally + { + s_stdInHandle.Release(); + } + } + + public Task GetCursorLeftAsync() + { + return GetCursorLeftAsync(CancellationToken.None); + } + + public async Task GetCursorLeftAsync(CancellationToken cancellationToken) + { + await s_stdInHandle.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + return System.Console.CursorLeft; + } + finally + { + s_stdInHandle.Release(); + } + } + + public int GetCursorTop() + { + return GetCursorTop(CancellationToken.None); + } + + public int GetCursorTop(CancellationToken cancellationToken) + { + s_stdInHandle.Wait(cancellationToken); + try + { + return System.Console.CursorTop; + } + finally + { + s_stdInHandle.Release(); + } + } + + public Task GetCursorTopAsync() + { + return GetCursorTopAsync(CancellationToken.None); + } + + public async Task GetCursorTopAsync(CancellationToken cancellationToken) + { + await s_stdInHandle.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + return System.Console.CursorTop; + } + finally + { + s_stdInHandle.Release(); + } + } + + private bool LongWaitForKey(CancellationToken cancellationToken) + { + // Wait for a key to be buffered (in other words, wait for Console.KeyAvailable to become + // true) with a long delay between checks. + while (!IsKeyAvailable(cancellationToken)) + { + s_waitHandle.Wait(LongWaitForKeySleepTime, cancellationToken); + } + + // As soon as a key is buffered, return true and switch the wait logic to be more + // responsive, but also more expensive. + WaitForKeyAvailable = ShortWaitForKey; + return true; + } + + private async Task LongWaitForKeyAsync(CancellationToken cancellationToken) + { + while (!await IsKeyAvailableAsync(cancellationToken).ConfigureAwait(false)) + { + await Task.Delay(LongWaitForKeySleepTime, cancellationToken).ConfigureAwait(false); + } + + WaitForKeyAvailableAsync = ShortWaitForKeyAsync; + return true; + } + + private bool ShortWaitForKey(CancellationToken cancellationToken) + { + // Check frequently for a new key to be buffered. + if (SpinUntilKeyAvailable(ShortWaitForKeyTimeout, cancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + return true; + } + + // If the user has not pressed a key before the end of the SpinUntil timeout then + // the user is idle and we can switch back to long delays between KeyAvailable checks. + cancellationToken.ThrowIfCancellationRequested(); + WaitForKeyAvailable = LongWaitForKey; + return false; + } + + private async Task ShortWaitForKeyAsync(CancellationToken cancellationToken) + { + if (await SpinUntilKeyAvailableAsync(ShortWaitForKeyTimeout, cancellationToken).ConfigureAwait(false)) + { + cancellationToken.ThrowIfCancellationRequested(); + return true; + } + + cancellationToken.ThrowIfCancellationRequested(); + WaitForKeyAvailableAsync = LongWaitForKeyAsync; + return false; + } + + private bool SpinUntilKeyAvailable(int millisecondsTimeout, CancellationToken cancellationToken) + { + return SpinWait.SpinUntil( + () => + { + s_waitHandle.Wait(ShortWaitForKeySpinUntilSleepTime, cancellationToken); + return IsKeyAvailable(cancellationToken); + }, + millisecondsTimeout); + } + + private Task SpinUntilKeyAvailableAsync(int millisecondsTimeout, CancellationToken cancellationToken) + { + return Task.Factory.StartNew( + () => SpinWait.SpinUntil( + () => + { + // The wait handle is never set, it's just used to enable cancelling the wait. + s_waitHandle.Wait(ShortWaitForKeySpinUntilSleepTime, cancellationToken); + return IsKeyAvailable(cancellationToken); + }, + millisecondsTimeout)); + } + + private bool IsKeyAvailable(CancellationToken cancellationToken) + { + s_stdInHandle.Wait(cancellationToken); + try + { + return System.Console.KeyAvailable; + } + finally + { + s_stdInHandle.Release(); + } + } + + private async Task IsKeyAvailableAsync(CancellationToken cancellationToken) + { + await s_stdInHandle.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + return System.Console.KeyAvailable; + } + finally + { + s_stdInHandle.Release(); + } + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/WindowsConsoleOperations.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/WindowsConsoleOperations.cs new file mode 100644 index 000000000..e4568b65b --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/WindowsConsoleOperations.cs @@ -0,0 +1,77 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console +{ + internal class WindowsConsoleOperations : IConsoleOperations + { + private ConsoleKeyInfo? _bufferedKey; + + private SemaphoreSlim _readKeyHandle = AsyncUtils.CreateSimpleLockingSemaphore(); + + public int GetCursorLeft() => System.Console.CursorLeft; + + public int GetCursorLeft(CancellationToken cancellationToken) => System.Console.CursorLeft; + + public Task GetCursorLeftAsync() => Task.FromResult(System.Console.CursorLeft); + + public Task GetCursorLeftAsync(CancellationToken cancellationToken) => Task.FromResult(System.Console.CursorLeft); + + public int GetCursorTop() => System.Console.CursorTop; + + public int GetCursorTop(CancellationToken cancellationToken) => System.Console.CursorTop; + + public Task GetCursorTopAsync() => Task.FromResult(System.Console.CursorTop); + + public Task GetCursorTopAsync(CancellationToken cancellationToken) => Task.FromResult(System.Console.CursorTop); + + public async Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken) + { + await _readKeyHandle.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + if (_bufferedKey == null) + { + _bufferedKey = await Task.Run(() => System.Console.ReadKey(intercept)).ConfigureAwait(false); + } + + return _bufferedKey.Value; + } + finally + { + _readKeyHandle.Release(); + + // Throw if we're cancelled so the buffered key isn't cleared. + cancellationToken.ThrowIfCancellationRequested(); + _bufferedKey = null; + } + } + + public ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken) + { + _readKeyHandle.Wait(cancellationToken); + try + { + return + _bufferedKey.HasValue + ? _bufferedKey.Value + : (_bufferedKey = System.Console.ReadKey(intercept)).Value; + } + finally + { + _readKeyHandle.Release(); + + // Throw if we're cancelled so the buffered key isn't cleared. + cancellationToken.ThrowIfCancellationRequested(); + _bufferedKey = null; + } + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionCanceledException.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionCanceledException.cs new file mode 100644 index 000000000..16fb5cf65 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionCanceledException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + public class ExecutionCanceledException : Exception + { + public ExecutionCanceledException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs new file mode 100644 index 000000000..90ff630ee --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs @@ -0,0 +1,29 @@ +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + public struct PowerShellExecutionOptions + { + public static PowerShellExecutionOptions Default = new PowerShellExecutionOptions + { + WriteOutputToHost = true, + WriteErrorsToHost = true, + }; + + public bool WriteOutputToHost { get; set; } + + public bool WriteErrorsToHost { get; set; } + + public bool AddToHistory { get; set; } + + public bool InterruptCommandPrompt { get; set; } + + public bool WriteInputToHost { get; set; } + + public string InputStringToDisplay { get; set; } + + public bool UseNewScope { get; set; } + + internal bool IsReadLine { get; set; } + + internal bool ShouldExecuteInOriginalRunspace { get; set; } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs new file mode 100644 index 000000000..b17660e58 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Threading; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + internal class SynchronousDelegateTask : SynchronousTask + { + private readonly Action _action; + + public SynchronousDelegateTask( + ILogger logger, + Action action, + CancellationToken cancellationToken) + : base(logger, cancellationToken) + { + _action = action; + } + + public override object Run(CancellationToken cancellationToken) + { + _action(cancellationToken); + return null; + } + } + + internal class SynchronousDelegateTask : SynchronousTask + { + private readonly Func _func; + + public SynchronousDelegateTask( + ILogger logger, + Func func, + CancellationToken cancellationToken) + : base(logger, cancellationToken) + { + _func = func; + } + + public override TResult Run(CancellationToken cancellationToken) + { + return _func(cancellationToken); + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs new file mode 100644 index 000000000..e01807ef7 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -0,0 +1,82 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.ObjectModel; +using System.Management.Automation; +using System.Management.Automation.Remoting; +using System.Threading; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + internal class SynchronousPowerShellTask : SynchronousTask> + { + private readonly SMA.PowerShell _pwsh; + + private readonly PSCommand _psCommand; + + private readonly PowerShellExecutionOptions _executionOptions; + + public SynchronousPowerShellTask( + ILogger logger, + SMA.PowerShell pwsh, + PSCommand command, + PowerShellExecutionOptions executionOptions, + CancellationToken cancellationToken) + : base(logger, cancellationToken) + { + _pwsh = pwsh; + _psCommand = command; + _executionOptions = executionOptions; + } + + public override Collection Run(CancellationToken cancellationToken) + { + cancellationToken.Register(Cancel); + + _pwsh.Commands = _psCommand; + + if (_executionOptions.WriteOutputToHost) + { + _pwsh.AddOutputCommand(); + } + + Collection result = null; + try + { + result = _pwsh.InvokeAndClear(); + } + catch (Exception e) when (e is PipelineStoppedException || e is PSRemotingDataStructureException) + { + string message = $"Pipeline stopped while executing command:{Environment.NewLine}{Environment.NewLine}{e}"; + Logger.LogError(message); + throw new ExecutionCanceledException(message, e); + } + catch (RuntimeException e) + { + Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}"); + + if (_executionOptions.WriteErrorsToHost) + { + _pwsh.AddOutputCommand() + .AddParameter("InputObject", e.ErrorRecord.AsPSObject()) + .InvokeAndClear(); + } + + throw; + } + + + if (_pwsh.HadErrors) + { + _pwsh.Streams.Error.Clear(); + } + + return result; + } + + private void Cancel() + { + _pwsh.Stop(); + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs new file mode 100644 index 000000000..264c753df --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs @@ -0,0 +1,68 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + internal interface ISynchronousTask + { + bool IsCanceled { get; } + + void ExecuteSynchronously(ref CancellationTokenSource cancellationSource, CancellationToken threadCancellationToken); + } + + internal abstract class SynchronousTask : ISynchronousTask + { + private readonly TaskCompletionSource _taskCompletionSource; + + private readonly CancellationToken _taskCancellationToken; + + private bool _executionCanceled; + + protected SynchronousTask(ILogger logger, CancellationToken cancellationToken) + { + Logger = logger; + _taskCompletionSource = new TaskCompletionSource(); + _taskCancellationToken = cancellationToken; + _executionCanceled = false; + } + + protected ILogger Logger { get; } + + public Task Task => _taskCompletionSource.Task; + + public bool IsCanceled => _taskCancellationToken.IsCancellationRequested || _executionCanceled; + + public abstract TResult Run(CancellationToken cancellationToken); + + public void ExecuteSynchronously(ref CancellationTokenSource cancellationSource, CancellationToken threadCancellation) + { + if (_taskCancellationToken.IsCancellationRequested || threadCancellation.IsCancellationRequested) + { + Cancel(); + return; + } + + cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_taskCancellationToken, threadCancellation); + try + { + _taskCompletionSource.SetResult(Run(cancellationSource.Token)); + } + catch (OperationCanceledException) + { + Cancel(); + } + catch (Exception e) + { + _taskCompletionSource.SetException(e); + } + } + + private void Cancel() + { + _executionCanceled = true; + _taskCompletionSource.SetCanceled(); + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/ConsoleColorProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/ConsoleColorProxy.cs new file mode 100644 index 000000000..1814b2816 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/ConsoleColorProxy.cs @@ -0,0 +1,153 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +/* +using System; +using System.Management.Automation.Host; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host +{ + internal class ConsoleColorProxy + { + internal ConsoleColorProxy(EditorServicesConsolePSHostUserInterface hostUserInterface) + { + if (hostUserInterface == null) throw new ArgumentNullException(); + _hostUserInterface = hostUserInterface; + } + + /// + /// The Accent Color for Formatting + /// + public ConsoleColor FormatAccentColor + { + get + { return _hostUserInterface.FormatAccentColor; } + set + { _hostUserInterface.FormatAccentColor = value; } + } + + /// + /// The Accent Color for Error + /// + public ConsoleColor ErrorAccentColor + { + get + { return _hostUserInterface.ErrorAccentColor; } + set + { _hostUserInterface.ErrorAccentColor = value; } + } + + /// + /// The ForegroundColor for Error + /// + public ConsoleColor ErrorForegroundColor + { + get + { return _hostUserInterface.ErrorForegroundColor; } + set + { _hostUserInterface.ErrorForegroundColor = value; } + } + + /// + /// The BackgroundColor for Error + /// + public ConsoleColor ErrorBackgroundColor + { + get + { return _hostUserInterface.ErrorBackgroundColor; } + set + { _hostUserInterface.ErrorBackgroundColor = value; } + } + + /// + /// The ForegroundColor for Warning + /// + public ConsoleColor WarningForegroundColor + { + get + { return _hostUserInterface.WarningForegroundColor; } + set + { _hostUserInterface.WarningForegroundColor = value; } + } + + /// + /// The BackgroundColor for Warning + /// + public ConsoleColor WarningBackgroundColor + { + get + { return _hostUserInterface.WarningBackgroundColor; } + set + { _hostUserInterface.WarningBackgroundColor = value; } + } + + /// + /// The ForegroundColor for Debug + /// + public ConsoleColor DebugForegroundColor + { + get + { return _hostUserInterface.DebugForegroundColor; } + set + { _hostUserInterface.DebugForegroundColor = value; } + } + + /// + /// The BackgroundColor for Debug + /// + public ConsoleColor DebugBackgroundColor + { + get + { return _hostUserInterface.DebugBackgroundColor; } + set + { _hostUserInterface.DebugBackgroundColor = value; } + } + + /// + /// The ForegroundColor for Verbose + /// + public ConsoleColor VerboseForegroundColor + { + get + { return _hostUserInterface.VerboseForegroundColor; } + set + { _hostUserInterface.VerboseForegroundColor = value; } + } + + /// + /// The BackgroundColor for Verbose + /// + public ConsoleColor VerboseBackgroundColor + { + get + { return _hostUserInterface.VerboseBackgroundColor; } + set + { _hostUserInterface.VerboseBackgroundColor = value; } + } + + /// + /// The ForegroundColor for Progress + /// + public ConsoleColor ProgressForegroundColor + { + get + { return _hostUserInterface.ProgressForegroundColor; } + set + { _hostUserInterface.ProgressForegroundColor = value; } + } + + /// + /// The BackgroundColor for Progress + /// + public ConsoleColor ProgressBackgroundColor + { + get + { return _hostUserInterface.ProgressBackgroundColor; } + set + { _hostUserInterface.ProgressBackgroundColor = value; } + } + } +} +*/ diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs new file mode 100644 index 000000000..7d9e6c50e --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -0,0 +1,85 @@ +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; +using System; +using System.Globalization; +using System.Management.Automation.Host; +using System.Management.Automation.Runspaces; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host +{ + internal class EditorServicesConsolePSHost : PSHost, IHostSupportsInteractiveSession + { + private readonly ILogger _logger; + + private readonly HostStartupInfo _hostInfo; + + private Runspace _pushedRunspace; + + public EditorServicesConsolePSHost( + ILogger logger, + EditorServicesConsolePSHostUserInterface ui, + HostStartupInfo hostInfo) + { + _logger = logger; + _hostInfo = hostInfo; + _pushedRunspace = null; + UI = ui; + } + + public override CultureInfo CurrentCulture => CultureInfo.CurrentCulture; + + public override CultureInfo CurrentUICulture => CultureInfo.CurrentUICulture; + + public override Guid InstanceId { get; } = Guid.NewGuid(); + + public override string Name => _hostInfo.Name; + + public override PSHostUserInterface UI { get; } + + public override Version Version => _hostInfo.Version; + + public bool IsRunspacePushed { get; private set; } + + public Runspace Runspace { get; private set; } + + public override void EnterNestedPrompt() + { + throw new NotImplementedException(); + } + + public override void ExitNestedPrompt() + { + throw new NotImplementedException(); + } + + public override void NotifyBeginApplication() + { + } + + public override void NotifyEndApplication() + { + } + + public void PopRunspace() + { + Runspace = _pushedRunspace; + _pushedRunspace = null; + IsRunspacePushed = false; + } + + public void PushRunspace(Runspace runspace) + { + _pushedRunspace = Runspace; + Runspace = runspace; + IsRunspacePushed = true; + } + + public override void SetShouldExit(int exitCode) + { + if (IsRunspacePushed) + { + PopRunspace(); + } + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs new file mode 100644 index 000000000..36623ed73 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation.Host; +using System.Text; + +namespace PowerShellEditorServices.Services.PowerShell.Host +{ + internal class EditorServicesConsolePSHostRawUserInterface : PSHostRawUserInterface + { + public override ConsoleColor BackgroundColor { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public override Size BufferSize { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public override Coordinates CursorPosition { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public override int CursorSize { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public override ConsoleColor ForegroundColor { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public override bool KeyAvailable => throw new NotImplementedException(); + + public override Size MaxPhysicalWindowSize => throw new NotImplementedException(); + + public override Size MaxWindowSize => throw new NotImplementedException(); + + public override Coordinates WindowPosition { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public override Size WindowSize { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public override string WindowTitle { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public override void FlushInputBuffer() + { + throw new NotImplementedException(); + } + + public override BufferCell[,] GetBufferContents(Rectangle rectangle) + { + throw new NotImplementedException(); + } + + public override KeyInfo ReadKey(ReadKeyOptions options) + { + throw new NotImplementedException(); + } + + public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill) + { + throw new NotImplementedException(); + } + + public override void SetBufferContents(Coordinates origin, BufferCell[,] contents) + { + throw new NotImplementedException(); + } + + public override void SetBufferContents(Rectangle rectangle, BufferCell fill) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs new file mode 100644 index 000000000..832704bb1 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs @@ -0,0 +1,131 @@ +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using PowerShellEditorServices.Services.PowerShell.Host; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation; +using System.Management.Automation.Host; +using System.Security; +using System.Text; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host +{ + internal class EditorServicesConsolePSHostUserInterface : PSHostUserInterface + { + private readonly ILogger _logger; + + private readonly PowerShellExecutionService _executionService; + + private readonly PSHostUserInterface _underlyingHostUI; + + public EditorServicesConsolePSHostUserInterface( + ILogger logger, + EditorServicesConsolePSHostRawUserInterface rawUI, + PowerShellExecutionService executionService, + PSHostUserInterface underlyingHostUI, + bool supportsVirtualTerminal) + { + _logger = logger; + _executionService = executionService; + _underlyingHostUI = underlyingHostUI; + RawUI = rawUI; + SupportsVirtualTerminal = supportsVirtualTerminal; + } + + public override PSHostRawUserInterface RawUI { get; } + + public override bool SupportsVirtualTerminal { get; } + + public override Dictionary Prompt(string caption, string message, Collection descriptions) + { + throw new NotImplementedException(); + } + + public override int PromptForChoice(string caption, string message, Collection choices, int defaultChoice) + { + throw new NotImplementedException(); + } + + public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) + { + throw new NotImplementedException(); + } + + public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName) + { + throw new NotImplementedException(); + } + + public override string ReadLine() + { + throw new NotImplementedException(); + } + + public override SecureString ReadLineAsSecureString() + { + throw new NotImplementedException(); + } + + public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) => + WriteOutput(value, foregroundColor, backgroundColor, includeNewline: false); + + public override void Write(string value) + { + throw new NotImplementedException(); + } + + public override void WriteDebugLine(string message) + { + throw new NotImplementedException(); + } + + public override void WriteErrorLine(string value) + { + throw new NotImplementedException(); + } + + public override void WriteLine(string value) + { + throw new NotImplementedException(); + } + + public override void WriteProgress(long sourceId, ProgressRecord record) => _underlyingHostUI.WriteProgress(sourceId, record); + + public override void WriteVerboseLine(string message) + { + throw new NotImplementedException(); + } + + public override void WriteWarningLine(string message) + { + throw new NotImplementedException(); + } + + private void WriteOutput( + string outputString, + ConsoleColor foregroundColor, + ConsoleColor backgroundColor, + bool includeNewline) + { + ConsoleColor oldForegroundColor = System.Console.ForegroundColor; + ConsoleColor oldBackgroundColor = System.Console.BackgroundColor; + + System.Console.ForegroundColor = foregroundColor; + System.Console.BackgroundColor = backgroundColor; + + if (includeNewline) + { + System.Console.WriteLine(outputString); + } + else + { + System.Console.Write(outputString); + } + + System.Console.ForegroundColor = oldForegroundColor; + System.Console.BackgroundColor = oldBackgroundColor; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/IReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/IReadLine.cs new file mode 100644 index 000000000..47cd57919 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/IReadLine.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host +{ + internal interface IReadLine + { + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs new file mode 100644 index 000000000..ad72ef4b8 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -0,0 +1,97 @@ +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell +{ + internal class PowerShellConsoleService : IDisposable + { + public static PowerShellConsoleService CreateAndStart( + ILogger logger, + PowerShellExecutionService executionService) + { + var consoleService = new PowerShellConsoleService(logger, executionService); + consoleService.StartRepl(); + return consoleService; + } + + private readonly ILogger _logger; + + private readonly PowerShellExecutionService _executionService; + + private Task _consoleLoopThread; + + private CancellationTokenSource _replLoopCancellationSource; + + private CancellationTokenSource _currentCommandCancellationSource; + + private PSReadLineProxy _psrlProxy; + + private PowerShellConsoleService(ILogger logger, PowerShellExecutionService executionService) + { + _logger = logger; + _executionService = executionService; + } + + public void Dispose() + { + System.Console.CancelKeyPress -= HandleConsoleCancellation; + } + + private void StartRepl() + { + _replLoopCancellationSource = new CancellationTokenSource(); + System.Console.CancelKeyPress += HandleConsoleCancellation; + System.Console.OutputEncoding = Encoding.UTF8; + _consoleLoopThread = Task.Run(RunReplLoopAsync, _replLoopCancellationSource.Token); + } + + private async Task RunReplLoopAsync() + { + _psrlProxy = await PSReadLineProxy.LoadAndCreateAsync(_logger, _executionService).ConfigureAwait(false); + + while (!_replLoopCancellationSource.IsCancellationRequested) + { + _currentCommandCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_replLoopCancellationSource.Token); + + try + { + await InvokePromptFunctionAsync(); + } + catch (OperationCanceledException) + { + break; + } + } + } + + private Task InvokePromptFunctionAsync() + { + var promptCommand = new PSCommand().AddCommand("prompt"); + var executionOptions = new PowerShellExecutionOptions + { + WriteOutputToHost = true, + }; + + return _executionService.ExecutePSCommandAsync( + promptCommand, + executionOptions, + _currentCommandCancellationSource.Token); + } + + private Task InvokeReadLineAsync() + { + } + + private void HandleConsoleCancellation(object sender, ConsoleCancelEventArgs args) + { + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs new file mode 100644 index 000000000..12148a871 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -0,0 +1,182 @@ +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using PowerShellEditorServices.Services.PowerShell.Utility; +using System; +using System.Collections.Concurrent; +using System.Collections.ObjectModel; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Host; +using System.Management.Automation.Runspaces; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell +{ + internal class PowerShellExecutionService : IDisposable + { + private static readonly string s_commandsModulePath = Path.GetFullPath( + Path.Combine( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + "../../Commands/PowerShellEditorServices.Commands.psd1")); + + private readonly SMA.PowerShell _pwsh; + + private readonly CancellationTokenSource _stopThreadCancellationSource; + + private readonly BlockingCollection _executionQueue; + + private Thread _pipelineThread; + + private CancellationTokenSource _currentExecutionCancellationSource; + + private ILogger _logger; + + public static PowerShellExecutionService CreateAndStart( + ILogger logger, + HostStartupInfo hostInfo, + PSLanguageMode languageMode) + { + var pwsh = SMA.PowerShell.Create(); + + var executionService = new PowerShellExecutionService(logger, pwsh); + var psHost = new EditorServicesConsolePSHost(logger, hostInfo, executionService); + + executionService.PSHost = psHost; + + pwsh.Runspace = CreateRunspace(psHost, languageMode); + + executionService.Start(); + + executionService.EnqueueModuleImport(s_commandsModulePath); + + if (hostInfo.AdditionalModules != null && hostInfo.AdditionalModules.Count > 0) + { + foreach (string module in hostInfo.AdditionalModules) + { + executionService.EnqueueModuleImport(module); + } + } + + return executionService; + } + + private PowerShellExecutionService( + ILogger logger, + SMA.PowerShell pwsh) + { + _logger = logger; + _pwsh = pwsh; + _stopThreadCancellationSource = new CancellationTokenSource(); + _executionQueue = new BlockingCollection(); + } + + public PSHost PSHost { get; private set; } + + public Task ExecuteDelegateAsync(Func func, CancellationToken cancellationToken) + { + var delegateTask = new SynchronousDelegateTask(_logger, func, cancellationToken); + _executionQueue.Add(delegateTask); + return delegateTask.Task; + } + + public Task ExecuteDelegateAsync(Action action, CancellationToken cancellationToken) + { + var delegateTask = new SynchronousDelegateTask(_logger, action, cancellationToken); + _executionQueue.Add(delegateTask); + return delegateTask.Task; + } + + public Task> ExecutePSCommandAsync( + PSCommand psCommand, + PowerShellExecutionOptions executionOptions, + CancellationToken cancellationToken) + { + var psTask = new SynchronousPowerShellTask(_logger, _pwsh, psCommand, executionOptions, cancellationToken); + _executionQueue.Add(psTask); + return psTask.Task; + } + + public Task ExecutePSCommandAsync( + PSCommand psCommand, + PowerShellExecutionOptions executionOptions, + CancellationToken cancellationToken) => ExecutePSCommandAsync(psCommand, executionOptions, cancellationToken); + + public void Stop() + { + _stopThreadCancellationSource.Cancel(); + _pipelineThread.Join(); + } + + public void CancelCurrentTask() + { + if (_currentExecutionCancellationSource != null) + { + _currentExecutionCancellationSource.Cancel(); + } + } + + public void Dispose() + { + Stop(); + _pwsh.Dispose(); + } + + private void Start() + { + _pipelineThread = new Thread(RunConsumerLoop); + _pipelineThread.Start(); + } + + private void RunConsumerLoop() + { + try + { + foreach (ISynchronousTask synchronousTask in _executionQueue.GetConsumingEnumerable()) + { + if (synchronousTask.IsCanceled) + { + continue; + } + + synchronousTask.ExecuteSynchronously(ref _currentExecutionCancellationSource, _stopThreadCancellationSource.Token); + } + } + catch (OperationCanceledException) + { + // End nicely + } + } + + private static Runspace CreateRunspace( + PSHost psHost, + PSLanguageMode languageMode) + { + InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" + ? InitialSessionState.CreateDefault() + : InitialSessionState.CreateDefault2(); + + iss.LanguageMode = languageMode; + + Runspace runspace = RunspaceFactory.CreateRunspace(psHost, iss); + + runspace.SetApartmentStateToSta(); + runspace.ThreadOptions = PSThreadOptions.ReuseThread; + return runspace; + } + + private void EnqueueModuleImport(string moduleNameOrPath) + { + var command = new PSCommand() + .AddCommand("Microsoft.PowerShell.Core\\Import-Module") + .AddParameter("-Name", moduleNameOrPath); + + ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None); + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs new file mode 100644 index 000000000..a7e820ae7 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs @@ -0,0 +1,55 @@ +using Microsoft.PowerShell.EditorServices.Utility; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using System.Management.Automation; +using System.Reflection; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility +{ + internal static class ErrorRecordExtensions + { + private static Action s_setWriteStreamProperty = null; + + [SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline", Justification = "cctor needed for version specific initialization")] + static ErrorRecordExtensions() + { + if (VersionUtils.IsPS7OrGreater) + { + // Used to write ErrorRecords to the Error stream. Using Public and NonPublic because the plan is to make this property + // public in 7.0.1 + PropertyInfo writeStreamProperty = typeof(PSObject).GetProperty("WriteStream", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + Type writeStreamType = typeof(PSObject).Assembly.GetType("System.Management.Automation.WriteStreamType"); + object errorStreamType = Enum.Parse(writeStreamType, "Error"); + + var errorObjectParameter = Expression.Parameter(typeof(PSObject)); + + s_setWriteStreamProperty = Expression.Lambda>( + Expression.Call( + errorObjectParameter, + writeStreamProperty.GetSetMethod(), + Expression.Constant(errorStreamType)), + errorObjectParameter) + .Compile(); + } + } + + public static PSObject AsPSObject(this ErrorRecord errorRecord) + { + var errorObject = PSObject.AsPSObject(errorRecord); + + // Used to write ErrorRecords to the Error stream so they are rendered in the console correctly. + if (s_setWriteStreamProperty != null) + { + s_setWriteStreamProperty(errorObject); + } + else + { + var note = new PSNoteProperty("writeErrorStream", true); + errorObject.Properties.Add(note); + } + + return errorObject; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs new file mode 100644 index 000000000..10df46bb3 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs @@ -0,0 +1,48 @@ +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Text; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility +{ + internal static class PSCommandExtensions + { + public static string GetInvocationText(this PSCommand command) + { + Command lastCommand = command.Commands[0]; + var sb = new StringBuilder().AddCommandText(command.Commands[0]); + + for (int i = 1; i < command.Commands.Count; i++) + { + sb.Append(lastCommand.IsEndOfStatement ? "; " : " | "); + lastCommand = command.Commands[i]; + sb.AddCommandText(lastCommand); + } + + return sb.ToString(); + } + + private static StringBuilder AddCommandText(this StringBuilder sb, Command command) + { + sb.Append(command.CommandText); + if (command.Parameters != null) + { + foreach (CommandParameter parameter in command.Parameters) + { + if (parameter.Name != null) + { + sb.Append(" -").Append(parameter.Name); + } + + if (parameter.Value != null) + { + // This isn't going to get PowerShell's string form of the value, + // but it's good enough, and not as complex or expensive + sb.Append(' ').Append(parameter.Value); + } + } + } + + return sb; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs new file mode 100644 index 000000000..d809a14c3 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.ObjectModel; +using System.Management.Automation; +using System.Text; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility +{ + internal static class PowerShellExtensions + { + public static Collection InvokeAndClear(this SMA.PowerShell pwsh) + { + try + { + return pwsh.Invoke(); + } + finally + { + pwsh.Commands.Clear(); + } + } + + public static void InvokeAndClear(this SMA.PowerShell pwsh) + { + try + { + pwsh.Invoke(); + } + finally + { + pwsh.Commands.Clear(); + } + } + + public static SMA.PowerShell AddOutputCommand(this SMA.PowerShell pwsh) + { + return pwsh.AddCommand("Microsoft.Powershell.Core\\Out-Default", useLocalScope: true); + } + + public static string GetErrorString(this SMA.PowerShell pwsh) + { + var sb = new StringBuilder(capacity: 1024) + .Append("Execution of the following command(s) completed with errors:") + .AppendLine() + .AppendLine() + .Append(pwsh.Commands.GetInvocationText()); + + sb.AddErrorString(pwsh.Streams.Error[0], errorIndex: 1); + for (int i = 1; i < pwsh.Streams.Error.Count; i++) + { + sb.AppendLine().AppendLine(); + sb.AddErrorString(pwsh.Streams.Error[i], errorIndex: i + 1); + } + + return sb.ToString(); + } + + private static StringBuilder AddErrorString(this StringBuilder sb, ErrorRecord error, int errorIndex) + { + sb.Append("Error #").Append(errorIndex).Append(':').AppendLine() + .Append(error).AppendLine() + .Append("ScriptStackTrace:").AppendLine() + .Append(error.ScriptStackTrace ?? "").AppendLine() + .Append("Exception:").AppendLine() + .Append(" ").Append(error.Exception.ToString() ?? ""); + + Exception innerException = error.Exception?.InnerException; + while (innerException != null) + { + sb.Append("InnerException:").AppendLine() + .Append(" ").Append(innerException); + innerException = innerException.InnerException; + } + + return sb; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs new file mode 100644 index 000000000..65133f126 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs @@ -0,0 +1,29 @@ +using Microsoft.PowerShell.EditorServices.Utility; +using System; +using System.Management.Automation.Runspaces; +using System.Reflection; +using System.Threading; + +namespace PowerShellEditorServices.Services.PowerShell.Utility +{ + internal static class RunspaceExtensions + { + private static readonly Action s_runspaceApartmentStateSetter; + + static RunspaceExtensions() + { + // PowerShell ApartmentState APIs aren't available in PSStandard, so we need to use reflection. + if (!VersionUtils.IsNetCore || VersionUtils.IsPS7OrGreater) + { + MethodInfo setterInfo = typeof(Runspace).GetProperty("ApartmentState").GetSetMethod(); + Delegate setter = Delegate.CreateDelegate(typeof(Action), firstArgument: null, method: setterInfo); + s_runspaceApartmentStateSetter = (Action)setter; + } + } + + public static void SetApartmentStateToSta(this Runspace runspace) + { + s_runspaceApartmentStateSetter?.Invoke(runspace, ApartmentState.STA); + } + } +} From 325711dca02318c1550d1bc1c2d3bcd83cc85197 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 22 Jun 2020 12:32:14 -0700 Subject: [PATCH 002/176] Get to console repl, remove PSContext dependency --- .../Internal/EditorServicesRunner.cs | 2 +- .../Server/PsesDebugServer.cs | 10 +- .../Server/PsesLanguageServer.cs | 21 +- .../Server/PsesServiceCollectionExtensions.cs | 24 +- .../DebugAdapter/BreakpointService.cs | 35 +- .../DebugAdapter/DebugEventHandlerService.cs | 19 +- .../Handlers/ConfigurationDoneHandler.cs | 29 +- .../Handlers/DisconnectHandler.cs | 11 +- .../Handlers/LaunchAndAttachHandler.cs | 5 +- .../PowerShell/Console/ConsoleReadLine.cs | 102 +++--- .../PowerShell/Console/PSReadLineProxy.cs | 26 +- .../Execution/SynchronousPowerShellTask.cs | 1 + .../Host/EditorServicesConsolePSHost.cs | 12 +- ...orServicesConsolePSHostRawUserInterface.cs | 316 ++++++++++++++++-- ...ditorServicesConsolePSHostUserInterface.cs | 73 ++-- .../PowerShell/PowerShellConsoleService.cs | 85 ++++- .../PowerShell/PowerShellExecutionService.cs | 52 ++- .../PowerShell/PowerShellStartupService.cs | 81 +++++ .../PowerShell/Utility/RunspaceExtensions.cs | 2 +- .../EditorOperationsService.cs | 9 +- .../PowerShellContext/ExtensionService.cs | 24 +- .../Handlers/EvaluateHandler.cs | 19 +- .../Handlers/ExpandAliasHandler.cs | 11 +- .../Handlers/GetCommandHandler.cs | 11 +- .../Handlers/GetVersionHandler.cs | 31 +- .../PSHostProcessAndRunspaceHandlers.cs | 12 +- .../Handlers/ShowHelpHandler.cs | 11 +- .../PowerShellContextService.cs | 21 +- .../RemoteFileManagerService.cs | 42 ++- .../PowerShellContext/TemplateService.cs | 58 ++-- .../Utilities/CommandHelpers.cs | 34 +- .../Services/Symbols/SymbolDetails.cs | 9 +- .../Services/Symbols/SymbolsService.cs | 16 +- .../Services/Symbols/Vistors/AstOperations.cs | 30 +- .../Handlers/CompletionHandler.cs | 13 +- .../Handlers/SignatureHelpHandler.cs | 10 +- .../Handlers/ConfigurationHandler.cs | 12 +- 37 files changed, 870 insertions(+), 409 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/PowerShellStartupService.cs diff --git a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs index 896a3d09f..0d2a50ac1 100644 --- a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs +++ b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs @@ -130,7 +130,7 @@ private async Task CreateEditorServicesAndRunUntilShutdown() _logger.Log(PsesLogLevel.Diagnostic, "Creating/running editor services"); bool creatingLanguageServer = _config.LanguageServiceTransport != null; - bool creatingDebugServer = _config.DebugServiceTransport != null; + bool creatingDebugServer = false;// _config.DebugServiceTransport != null; bool isTempDebugSession = creatingDebugServer && !creatingLanguageServer; // Set up information required to instantiate servers diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index e4327cfbe..76acc7adc 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.DebugAdapter.Protocol; using OmniSharp.Extensions.DebugAdapter.Protocol.Serialization; @@ -42,7 +43,8 @@ internal class PsesDebugServer : IDisposable private readonly TaskCompletionSource _serverStopped; private DebugAdapterServer _debugAdapterServer; - private PowerShellContextService _powerShellContextService; + + private PowerShellExecutionService _executionService; protected readonly ILoggerFactory _loggerFactory; @@ -75,9 +77,9 @@ public async Task StartAsync() { // We need to let the PowerShell Context Service know that we are in a debug session // so that it doesn't send the powerShell/startDebugger message. - _powerShellContextService = ServiceProvider.GetService(); - _powerShellContextService.IsDebugServerActive = true; + _executionService = ServiceProvider.GetService(); + /* // Needed to make sure PSReadLine's static properties are initialized in the pipeline thread. // This is only needed for Temp sessions who only have a debug server. if (_usePSReadLine && _useTempSession && Interlocked.Exchange(ref s_hasRunPsrlStaticCtor, 1) == 0) @@ -91,6 +93,7 @@ public async Task StartAsync() .GetAwaiter() .GetResult(); } + */ options .WithInput(_inputStream) @@ -136,7 +139,6 @@ public async Task StartAsync() public void Dispose() { - _powerShellContextService.IsDebugServerActive = false; // TODO: If the debugger has stopped, should we clear the breakpoints? _debugAdapterServer.Dispose(); _inputStream.Dispose(); diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index bf74f8bba..a806d67f7 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -1,13 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using System.IO; +using System.Management.Automation; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using OmniSharp.Extensions.LanguageServer.Server; using Serilog; @@ -112,7 +116,10 @@ public async Task StartAsync() { Log.Logger.Debug("Initializing OmniSharp Language Server"); - var serviceProvider = languageServer.Services; + IServiceProvider serviceProvider = languageServer.Services; + + serviceProvider.GetService().StartRepl(); + var workspaceService = serviceProvider.GetService(); // Grab the workspace path from the parameters @@ -130,6 +137,18 @@ public async Task StartAsync() break; } } + + // Set the working directory of the PowerShell session to the workspace path + if (workspaceService.WorkspacePath != null + && Directory.Exists(workspaceService.WorkspacePath)) + { + await serviceProvider.GetService() + .ExecutePSCommandAsync( + new PSCommand().AddCommand("Set-Location").AddParameter("-Path", workspaceService.WorkspacePath), + new PowerShellExecutionOptions(), + cancellationToken) + .ConfigureAwait(false); + } }); }).ConfigureAwait(false); diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index e0fda77c0..6527de6a7 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; namespace Microsoft.PowerShell.EditorServices.Server { @@ -20,25 +21,20 @@ public static IServiceCollection AddPsesLanguageServices( return collection.AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton( - (provider) => - PowerShellContextService.Create( - provider.GetService(), - // NOTE: Giving the context service access to the language server this - // early is dangerous because it allows it to start sending - // notifications etc. before it has initialized, potentially resulting - // in deadlocks. We're working on a solution to this. - provider.GetService(), - hostStartupInfo)) - .AddSingleton() // TODO: What's the difference between this and the TemplateHandler? + .AddSingleton( + (provider) => PowerShellStartupService.Create(provider.GetService(), hostStartupInfo)) + .AddSingleton( + (provider) => PowerShellExecutionService.CreateAndStart(provider.GetService(), hostStartupInfo, provider.GetService())) + .AddSingleton( + (provider) => PowerShellConsoleService.CreateAndStart(provider.GetService(), provider.GetService(), provider.GetService())) + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton( (provider) => { var extensionService = new ExtensionService( - provider.GetService(), - // NOTE: See above warning. + provider.GetService(), provider.GetService()); extensionService.InitializeAsync( serviceProvider: provider, @@ -55,7 +51,7 @@ public static IServiceCollection AddPsesDebugServices( PsesDebugServer psesDebugServer, bool useTempSession) { - return collection.AddSingleton(languageServiceProvider.GetService()) + return collection.AddSingleton(languageServiceProvider.GetService()) .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(psesDebugServer) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs index 01de55b5a..01419754d 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs @@ -5,17 +5,22 @@ using System.Collections.Generic; using System.Linq; using System.Management.Automation; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; namespace Microsoft.PowerShell.EditorServices.Services { internal class BreakpointService { private readonly ILogger _logger; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; + private readonly EditorServicesConsolePSHost _editorServicesHost; private readonly DebugStateService _debugStateService; // TODO: This needs to be managed per nested session @@ -27,11 +32,13 @@ internal class BreakpointService public BreakpointService( ILoggerFactory factory, - PowerShellContextService powerShellContextService, + PowerShellExecutionService executionService, + EditorServicesConsolePSHost editorServicesHost, DebugStateService debugStateService) { _logger = factory.CreateLogger(); - _powerShellContextService = powerShellContextService; + _executionService = executionService; + _editorServicesHost = editorServicesHost; _debugStateService = debugStateService; } @@ -40,14 +47,14 @@ public async Task> GetBreakpointsAsync() if (BreakpointApiUtils.SupportsBreakpointApis) { return BreakpointApiUtils.GetBreakpoints( - _powerShellContextService.CurrentRunspace.Runspace.Debugger, + _editorServicesHost.Runspace.Debugger, _debugStateService.RunspaceId); } // Legacy behavior PSCommand psCommand = new PSCommand(); psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint"); - IEnumerable breakpoints = await _powerShellContextService.ExecuteCommandAsync(psCommand).ConfigureAwait(false); + IEnumerable breakpoints = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None); return breakpoints.ToList(); } @@ -59,7 +66,7 @@ public async Task> SetBreakpointsAsync(string esc { try { - BreakpointApiUtils.SetBreakpoint(_powerShellContextService.CurrentRunspace.Runspace.Debugger, breakpointDetails, _debugStateService.RunspaceId); + BreakpointApiUtils.SetBreakpoint(_editorServicesHost.Runspace.Debugger, breakpointDetails, _debugStateService.RunspaceId); } catch(InvalidOperationException e) { @@ -133,7 +140,7 @@ public async Task> SetBreakpointsAsync(string esc if (psCommand != null) { IEnumerable setBreakpoints = - await _powerShellContextService.ExecuteCommandAsync(psCommand).ConfigureAwait(false); + await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None); configuredBreakpoints.AddRange( setBreakpoints.Select((breakpoint) => BreakpointDetails.Create(breakpoint)) ); @@ -150,7 +157,7 @@ public async Task> SetCommandBreakpoints(I { try { - BreakpointApiUtils.SetBreakpoint(_powerShellContextService.CurrentRunspace.Runspace.Debugger, commandBreakpointDetails, _debugStateService.RunspaceId); + BreakpointApiUtils.SetBreakpoint(_editorServicesHost.Runspace.Debugger, commandBreakpointDetails, _debugStateService.RunspaceId); } catch(InvalidOperationException e) { @@ -211,7 +218,7 @@ public async Task> SetCommandBreakpoints(I if (psCommand != null) { IEnumerable setBreakpoints = - await _powerShellContextService.ExecuteCommandAsync(psCommand).ConfigureAwait(false); + await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None); configuredBreakpoints.AddRange( setBreakpoints.Select(CommandBreakpointDetails.Create)); } @@ -229,13 +236,13 @@ public async Task RemoveAllBreakpointsAsync(string scriptPath = null) if (BreakpointApiUtils.SupportsBreakpointApis) { foreach (Breakpoint breakpoint in BreakpointApiUtils.GetBreakpoints( - _powerShellContextService.CurrentRunspace.Runspace.Debugger, + _editorServicesHost.Runspace.Debugger, _debugStateService.RunspaceId)) { if (scriptPath == null || scriptPath == breakpoint.Script) { BreakpointApiUtils.RemoveBreakpoint( - _powerShellContextService.CurrentRunspace.Runspace.Debugger, + _editorServicesHost.Runspace.Debugger, breakpoint, _debugStateService.RunspaceId); } @@ -256,7 +263,7 @@ public async Task RemoveAllBreakpointsAsync(string scriptPath = null) psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Remove-PSBreakpoint"); - await _powerShellContextService.ExecuteCommandAsync(psCommand).ConfigureAwait(false); + await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); } catch (Exception e) { @@ -271,7 +278,7 @@ public async Task RemoveBreakpointsAsync(IEnumerable breakpoints) foreach (Breakpoint breakpoint in breakpoints) { BreakpointApiUtils.RemoveBreakpoint( - _powerShellContextService.CurrentRunspace.Runspace.Debugger, + _editorServicesHost.Runspace.Debugger, breakpoint, _debugStateService.RunspaceId); @@ -302,7 +309,7 @@ public async Task RemoveBreakpointsAsync(IEnumerable breakpoints) psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Remove-PSBreakpoint"); psCommand.AddParameter("Id", breakpoints.Select(b => b.Id).ToArray()); - await _powerShellContextService.ExecuteCommandAsync(psCommand).ConfigureAwait(false); + await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None); } } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs index a83e3e87d..e645a4c5d 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs @@ -4,6 +4,7 @@ using System.Management.Automation; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.DebugAdapter.Protocol.Events; @@ -14,20 +15,20 @@ namespace Microsoft.PowerShell.EditorServices.Services internal class DebugEventHandlerService { private readonly ILogger _logger; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; private readonly DebugService _debugService; private readonly DebugStateService _debugStateService; private readonly IDebugAdapterServerFacade _debugAdapterServer; public DebugEventHandlerService( ILoggerFactory factory, - PowerShellContextService powerShellContextService, + PowerShellExecutionService executionService, DebugService debugService, DebugStateService debugStateService, IDebugAdapterServerFacade debugAdapterServer) { _logger = factory.CreateLogger(); - _powerShellContextService = powerShellContextService; + _executionService = executionService; _debugService = debugService; _debugStateService = debugStateService; _debugAdapterServer = debugAdapterServer; @@ -35,18 +36,18 @@ public DebugEventHandlerService( internal void RegisterEventHandlers() { - _powerShellContextService.RunspaceChanged += PowerShellContext_RunspaceChanged; + //_powerShellContextService.RunspaceChanged += PowerShellContext_RunspaceChanged; _debugService.BreakpointUpdated += DebugService_BreakpointUpdated; _debugService.DebuggerStopped += DebugService_DebuggerStopped; - _powerShellContextService.DebuggerResumed += PowerShellContext_DebuggerResumed; + //_powerShellContextService.DebuggerResumed += PowerShellContext_DebuggerResumed; } internal void UnregisterEventHandlers() { - _powerShellContextService.RunspaceChanged -= PowerShellContext_RunspaceChanged; + //_powerShellContextService.RunspaceChanged -= PowerShellContext_RunspaceChanged; _debugService.BreakpointUpdated -= DebugService_BreakpointUpdated; _debugService.DebuggerStopped -= DebugService_DebuggerStopped; - _powerShellContextService.DebuggerResumed -= PowerShellContext_DebuggerResumed; + //_powerShellContextService.DebuggerResumed -= PowerShellContext_DebuggerResumed; } #region Public methods @@ -98,8 +99,8 @@ private void PowerShellContext_RunspaceChanged(object sender, RunspaceChangedEve _debugStateService.ServerStarted.SetResult(true); } else if ( - e.ChangeAction == RunspaceChangeAction.Exit && - _powerShellContextService.IsDebuggerStopped) + e.ChangeAction == RunspaceChangeAction.Exit && false) + // _powerShellContextService.IsDebuggerStopped) { // Exited the session while the debugger is stopped, // send a ContinuedEvent so that the client changes the diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index abe43e3ac..5740b4447 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -8,6 +8,8 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using OmniSharp.Extensions.DebugAdapter.Protocol.Events; @@ -23,7 +25,7 @@ internal class ConfigurationDoneHandler : IConfigurationDoneHandler private readonly DebugService _debugService; private readonly DebugStateService _debugStateService; private readonly DebugEventHandlerService _debugEventHandlerService; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; private readonly WorkspaceService _workspaceService; public ConfigurationDoneHandler( @@ -32,7 +34,7 @@ public ConfigurationDoneHandler( DebugService debugService, DebugStateService debugStateService, DebugEventHandlerService debugEventHandlerService, - PowerShellContextService powerShellContextService, + PowerShellExecutionService executionService, WorkspaceService workspaceService) { _logger = loggerFactory.CreateLogger(); @@ -40,7 +42,7 @@ public ConfigurationDoneHandler( _debugService = debugService; _debugStateService = debugStateService; _debugEventHandlerService = debugEventHandlerService; - _powerShellContextService = powerShellContextService; + _executionService = executionService; _workspaceService = workspaceService; } @@ -52,12 +54,12 @@ public Task Handle(ConfigurationDoneArguments request { // If this is a debug-only session, we need to start // the command loop manually - _powerShellContextService.ConsoleReader.StartCommandLoop(); + //_powerShellContextService.ConsoleReader.StartCommandLoop(); } if (!string.IsNullOrEmpty(_debugStateService.ScriptToLaunch)) { - if (_powerShellContextService.SessionState == PowerShellContextState.Ready) + if (false)//_powerShellContextService.SessionState == PowerShellContextState.Ready) { // Configuration is done, launch the script var nonAwaitedTask = LaunchScriptAsync(_debugStateService.ScriptToLaunch) @@ -83,7 +85,7 @@ public Task Handle(ConfigurationDoneArguments request { // If this is an interactive session and there's a pending breakpoint that has not been propagated through // the debug service, fire the debug service's OnDebuggerStop event. - _debugService.OnDebuggerStopAsync(null, _powerShellContextService.CurrentDebuggerStopEventArgs); + //_debugService.OnDebuggerStopAsync(null, _powerShellContextService.CurrentDebuggerStopEventArgs); } } } @@ -108,21 +110,24 @@ private async Task LaunchScriptAsync(string scriptToLaunch) // This seems to be the simplest way to invoke a script block (which contains breakpoint information) via the PowerShell API. var cmd = new PSCommand().AddScript(". $args[0]").AddArgument(ast.GetScriptBlock()); - await _powerShellContextService - .ExecuteCommandAsync(cmd, sendOutputToHost: true, sendErrorToHost:true) + await _executionService + .ExecutePSCommandAsync(cmd, new PowerShellExecutionOptions { WriteOutputToHost = true, WriteErrorsToHost = true }, CancellationToken.None) .ConfigureAwait(false); } else { - await _powerShellContextService - .ExecuteScriptStringAsync(untitledScript.Contents, writeInputToHost: true, writeOutputToHost: true) + await _executionService + .ExecutePSCommandAsync( + new PSCommand().AddScript(untitledScript.Contents), + new PowerShellExecutionOptions { WriteOutputToHost = true, WriteErrorsToHost = true }, + CancellationToken.None) .ConfigureAwait(false); } } else { - await _powerShellContextService - .ExecuteScriptWithArgsAsync(scriptToLaunch, _debugStateService.Arguments, writeInputToHost: true).ConfigureAwait(false); + //await _powerShellContextService + // .ExecuteScriptWithArgsAsync(scriptToLaunch, _debugStateService.Arguments, writeInputToHost: true).ConfigureAwait(false); } _debugAdapterServer.SendNotification(EventNames.Terminated); diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs index 4f2b8e85b..971b02f47 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs @@ -8,6 +8,7 @@ using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Server; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; @@ -16,7 +17,7 @@ namespace Microsoft.PowerShell.EditorServices.Handlers internal class DisconnectHandler : IDisconnectHandler { private readonly ILogger _logger; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; private readonly DebugService _debugService; private readonly DebugStateService _debugStateService; private readonly DebugEventHandlerService _debugEventHandlerService; @@ -25,14 +26,14 @@ internal class DisconnectHandler : IDisconnectHandler public DisconnectHandler( ILoggerFactory factory, PsesDebugServer psesDebugServer, - PowerShellContextService powerShellContextService, + PowerShellExecutionService executionService, DebugService debugService, DebugStateService debugStateService, DebugEventHandlerService debugEventHandlerService) { _logger = factory.CreateLogger(); _psesDebugServer = psesDebugServer; - _powerShellContextService = powerShellContextService; + _executionService = executionService; _debugService = debugService; _debugStateService = debugStateService; _debugEventHandlerService = debugEventHandlerService; @@ -44,11 +45,12 @@ public async Task Handle(DisconnectArguments request, Cancel if (_debugStateService.ExecutionCompleted == false) { _debugStateService.ExecutionCompleted = true; - _powerShellContextService.AbortExecution(shouldAbortDebugSession: true); + _executionService.CancelCurrentTask(); if (_debugStateService.IsInteractiveDebugSession && _debugStateService.IsAttachSession) { // Pop the sessions + /* if (_powerShellContextService.CurrentRunspace.Context == RunspaceContext.EnteredProcess) { try @@ -66,6 +68,7 @@ public async Task Handle(DisconnectArguments request, Cancel _logger.LogException("Caught exception while popping attached process after debugging", e); } } + */ } _debugService.IsClientAttached = false; diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index 040bb4a3f..4f0ee3f15 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -17,6 +17,7 @@ using Microsoft.PowerShell.EditorServices.Services.TextDocument; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; using OmniSharp.Extensions.DebugAdapter.Protocol.Server; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; namespace Microsoft.PowerShell.EditorServices.Handlers { @@ -87,12 +88,14 @@ internal class LaunchAndAttachHandler : ILaunchHandler _logger; private readonly BreakpointService _breakpointService; private readonly DebugService _debugService; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; private readonly DebugStateService _debugStateService; private readonly DebugEventHandlerService _debugEventHandlerService; private readonly IDebugAdapterServerFacade _debugAdapterServer; private readonly RemoteFileManagerService _remoteFileManagerService; + private readonly PowerShellContextService _powerShellContextService; + public LaunchAndAttachHandler( ILoggerFactory factory, IDebugAdapterServerFacade debugAdapterServer, diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index d6e116e9b..e1397726f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -11,6 +11,8 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using System; using System.Management.Automation; using System.Management.Automation.Language; @@ -18,21 +20,35 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console internal class ConsoleReadLine { - #region Private Field - private readonly PowerShellExecutionService _executionService; + private PSReadLineProxy _psrlProxy; - #endregion + private EngineIntrinsics _engineIntrinsics; + + private PowerShellExecutionService _executionService; + + private EditorServicesConsolePSHost _editorServicesHost; #region Constructors - public ConsoleReadLine(PowerShellExecutionService executionService) + #endregion + + #region Public Methods + + public void RegisterPowerShellEngine(EditorServicesConsolePSHost editorServicesHost, EngineIntrinsics engineIntrinsics) { - _executionService = executionService; + _editorServicesHost = editorServicesHost; + _engineIntrinsics = engineIntrinsics; } - #endregion + public void RegisterExecutionService(PowerShellExecutionService executionService) + { + _executionService = executionService; + } - #region Public Methods + public void RegisterPSReadLineProxy(PSReadLineProxy psrlProxy) + { + _psrlProxy = psrlProxy; + } public Task ReadCommandLineAsync(CancellationToken cancellationToken) { @@ -135,9 +151,15 @@ private static Task ReadKeyAsync(CancellationToken cancellationT private Task ReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) { - return this.powerShellContext.InvokeReadLineAsync(isCommandLine, cancellationToken); + return _executionService.ExecuteDelegateAsync(InvokePSReadLine, cancellationToken); + } + + private string InvokePSReadLine(CancellationToken cancellationToken) + { + return _psrlProxy.ReadLine(_editorServicesHost.Runspace, _engineIntrinsics, cancellationToken); } + /// /// Invokes a custom ReadLine method that is similar to but more basic than PSReadLine. /// This method should be used when PSReadLine is disabled, either by user settings or @@ -155,8 +177,6 @@ private Task ReadLineAsync(bool isCommandLine, CancellationToken cancell /// internal async Task InvokeLegacyReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) { - // TODO: Is inputBeforeCompletion used? - string inputBeforeCompletion = null; string inputAfterCompletion = null; CommandCompletion currentCompletion = null; @@ -195,6 +215,7 @@ internal async Task InvokeLegacyReadLineAsync(bool isCommandLine, Cancel } else if (keyInfo.Key == ConsoleKey.Tab && isCommandLine) { + /* if (currentCompletion == null) { inputBeforeCompletion = inputLine.ToString(); @@ -218,35 +239,36 @@ internal async Task InvokeLegacyReadLineAsync(bool isCommandLine, Cancel } else { - using (RunspaceHandle runspaceHandle = await this.powerShellContext.GetRunspaceHandleAsync().ConfigureAwait(false)) - using (PowerShell powerShell = PowerShell.Create()) + */ + /* + using (PowerShell powerShell = PowerShell.Create()) + { + powerShell.Runspace = _ + currentCompletion = + CommandCompletion.CompleteInput( + inputBeforeCompletion, + currentCursorIndex, + null, + powerShell); + + if (currentCompletion.CompletionMatches.Count > 0) + { + int replacementEndIndex = + currentCompletion.ReplacementIndex + + currentCompletion.ReplacementLength; + + inputAfterCompletion = + inputLine.ToString( + replacementEndIndex, + inputLine.Length - replacementEndIndex); + } + else { - powerShell.Runspace = runspaceHandle.Runspace; - currentCompletion = - CommandCompletion.CompleteInput( - inputBeforeCompletion, - currentCursorIndex, - null, - powerShell); - - if (currentCompletion.CompletionMatches.Count > 0) - { - int replacementEndIndex = - currentCompletion.ReplacementIndex + - currentCompletion.ReplacementLength; - - inputAfterCompletion = - inputLine.ToString( - replacementEndIndex, - inputLine.Length - replacementEndIndex); - } - else - { - currentCompletion = null; - } + currentCompletion = null; } } } + */ CompletionResult completion = currentCompletion?.GetNextResult( @@ -326,12 +348,12 @@ internal async Task InvokeLegacyReadLineAsync(bool isCommandLine, Cancel { historyIndex = -1; - PSCommand command = new PSCommand(); - command.AddCommand("Get-History"); + PSCommand command = new PSCommand().AddCommand("Get-History"); - currentHistory = await this.powerShellContext.ExecuteCommandAsync(command, sendOutputToHost: false, sendErrorToHost: false) - .ConfigureAwait(false) - as Collection; + currentHistory = await _executionService.ExecutePSCommandAsync( + command, + new PowerShellExecutionOptions(), + cancellationToken).ConfigureAwait(false); if (currentHistory != null) { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs index 94a80ca1b..88331e2d5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs @@ -11,9 +11,9 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using SMA = System.Management.Automation; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { @@ -71,26 +71,24 @@ internal class PSReadLineProxy return [Microsoft.PowerShell.PSConsoleReadLine] }}"; - - public static async Task LoadAndCreateAsync(ILogger logger, PowerShellExecutionService executionService) + public static PSReadLineProxy LoadAndCreate( + ILogger logger, + SMA.PowerShell pwsh) { - var importPsrlCommand = new PSCommand().AddScript(ReadLineInitScript); - var executionOptions = new PowerShellExecutionOptions - { - UseNewScope = true, - }; - - Type psConsoleReadLineType = (await executionService.ExecutePSCommandAsync(importPsrlCommand, executionOptions, CancellationToken.None).ConfigureAwait(false)).FirstOrDefault(); + Type psConsoleReadLineType = pwsh.AddScript(ReadLineInitScript).InvokeAndClear().FirstOrDefault(); Type type = Type.GetType("Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2"); - await executionService.ExecuteDelegateAsync((cancellationToken) => RuntimeHelpers.RunClassConstructor(type.TypeHandle), CancellationToken.None).ConfigureAwait(false); - return new PSReadLineProxy(psConsoleReadLineType, logger); + RuntimeHelpers.RunClassConstructor(type.TypeHandle); + + return new PSReadLineProxy(logger, psConsoleReadLineType); } private readonly FieldInfo _readKeyOverrideField; - public PSReadLineProxy(Type psConsoleReadLine, ILogger logger) + public PSReadLineProxy( + ILogger logger, + Type psConsoleReadLine) { ReadLine = (Func)psConsoleReadLine.GetMethod( ReadLineMethodName, diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index e01807ef7..55a1a484e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using System; using System.Collections.ObjectModel; using System.Management.Automation; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index 7d9e6c50e..32274e5b1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -11,18 +11,18 @@ internal class EditorServicesConsolePSHost : PSHost, IHostSupportsInteractiveSes { private readonly ILogger _logger; - private readonly HostStartupInfo _hostInfo; - private Runspace _pushedRunspace; public EditorServicesConsolePSHost( ILogger logger, EditorServicesConsolePSHostUserInterface ui, - HostStartupInfo hostInfo) + string name, + Version version) { _logger = logger; - _hostInfo = hostInfo; _pushedRunspace = null; + Name = name; + Version = version; UI = ui; } @@ -32,11 +32,11 @@ public EditorServicesConsolePSHost( public override Guid InstanceId { get; } = Guid.NewGuid(); - public override string Name => _hostInfo.Name; + public override string Name { get; } public override PSHostUserInterface UI { get; } - public override Version Version => _hostInfo.Version; + public override Version Version { get; } public bool IsRunspacePushed { get; private set; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs index 36623ed73..3b6fcf996 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs @@ -1,56 +1,322 @@ -using System; -using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; +using System; +using System.Management.Automation; using System.Management.Automation.Host; -using System.Text; +using System.Threading; -namespace PowerShellEditorServices.Services.PowerShell.Host +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host { internal class EditorServicesConsolePSHostRawUserInterface : PSHostRawUserInterface { - public override ConsoleColor BackgroundColor { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public override Size BufferSize { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public override Coordinates CursorPosition { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public override int CursorSize { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public override ConsoleColor ForegroundColor { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + #region Private Fields - public override bool KeyAvailable => throw new NotImplementedException(); + private readonly PSHostRawUserInterface _internalRawUI; + private readonly ILogger _logger; + private KeyInfo? _lastKeyDown; - public override Size MaxPhysicalWindowSize => throw new NotImplementedException(); + #endregion - public override Size MaxWindowSize => throw new NotImplementedException(); + #region Constructors - public override Coordinates WindowPosition { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public override Size WindowSize { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public override string WindowTitle { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + /// + /// Creates a new instance of the TerminalPSHostRawUserInterface + /// class with the given IConsoleHost implementation. + /// + /// The ILogger implementation to use for this instance. + /// The InternalHost instance from the origin runspace. + public EditorServicesConsolePSHostRawUserInterface( + ILogger logger, + PSHostRawUserInterface internalRawUI) + { + this._logger = logger; + this._internalRawUI = internalRawUI; + } + + #endregion + + #region PSHostRawUserInterface Implementation + + /// + /// Gets or sets the background color of the console. + /// + public override ConsoleColor BackgroundColor + { + get { return System.Console.BackgroundColor; } + set { System.Console.BackgroundColor = value; } + } + + /// + /// Gets or sets the foreground color of the console. + /// + public override ConsoleColor ForegroundColor + { + get { return System.Console.ForegroundColor; } + set { System.Console.ForegroundColor = value; } + } + + /// + /// Gets or sets the size of the console buffer. + /// + public override Size BufferSize + { + get => this._internalRawUI.BufferSize; + set => this._internalRawUI.BufferSize = value; + } + + /// + /// Gets or sets the cursor's position in the console buffer. + /// + public override Coordinates CursorPosition + { + get + { + return new Coordinates( + ConsoleProxy.GetCursorLeft(), + ConsoleProxy.GetCursorTop()); + } + + set => this._internalRawUI.CursorPosition = value; + } + + /// + /// Gets or sets the size of the cursor in the console buffer. + /// + public override int CursorSize + { + get => this._internalRawUI.CursorSize; + set => this._internalRawUI.CursorSize = value; + } + + /// + /// Gets or sets the position of the console's window. + /// + public override Coordinates WindowPosition + { + get => this._internalRawUI.WindowPosition; + set => this._internalRawUI.WindowPosition = value; + } + + /// + /// Gets or sets the size of the console's window. + /// + public override Size WindowSize + { + get => this._internalRawUI.WindowSize; + set => this._internalRawUI.WindowSize = value; + } + + /// + /// Gets or sets the console window's title. + /// + public override string WindowTitle + { + get => this._internalRawUI.WindowTitle; + set => this._internalRawUI.WindowTitle = value; + } + + /// + /// Gets a boolean that determines whether a keypress is available. + /// + public override bool KeyAvailable => this._internalRawUI.KeyAvailable; + + /// + /// Gets the maximum physical size of the console window. + /// + public override Size MaxPhysicalWindowSize => this._internalRawUI.MaxPhysicalWindowSize; + + /// + /// Gets the maximum size of the console window. + /// + public override Size MaxWindowSize => this._internalRawUI.MaxWindowSize; + + /// + /// Reads the current key pressed in the console. + /// + /// Options for reading the current keypress. + /// A KeyInfo struct with details about the current keypress. + public override KeyInfo ReadKey(ReadKeyOptions options) + { + + bool includeUp = (options & ReadKeyOptions.IncludeKeyUp) != 0; + + // Key Up was requested and we have a cached key down we can return. + if (includeUp && this._lastKeyDown != null) + { + KeyInfo info = this._lastKeyDown.Value; + this._lastKeyDown = null; + return new KeyInfo( + info.VirtualKeyCode, + info.Character, + info.ControlKeyState, + keyDown: false); + } + + bool intercept = (options & ReadKeyOptions.NoEcho) != 0; + bool includeDown = (options & ReadKeyOptions.IncludeKeyDown) != 0; + if (!(includeDown || includeUp)) + { + throw new PSArgumentException( + "Cannot read key options. To read options, set one or both of the following: IncludeKeyDown, IncludeKeyUp.", + nameof(options)); + } + + // Allow ControlC as input so we can emulate pipeline stop requests. We can't actually + // determine if a stop is requested without using non-public API's. + bool oldValue = System.Console.TreatControlCAsInput; + try + { + System.Console.TreatControlCAsInput = true; + ConsoleKeyInfo key = ConsoleProxy.ReadKey(intercept, default(CancellationToken)); + + if (IsCtrlC(key)) + { + // Caller wants CtrlC as input so return it. + if ((options & ReadKeyOptions.AllowCtrlC) != 0) + { + return ProcessKey(key, includeDown); + } + + // Caller doesn't want CtrlC so throw a PipelineStoppedException to emulate + // a real stop. This will not show an exception to a script based caller and it + // will avoid having to return something like default(KeyInfo). + throw new PipelineStoppedException(); + } + return ProcessKey(key, includeDown); + } + finally + { + System.Console.TreatControlCAsInput = oldValue; + } + } + + /// + /// Flushes the current input buffer. + /// public override void FlushInputBuffer() { - throw new NotImplementedException(); + _logger.LogWarning( + "PSHostRawUserInterface.FlushInputBuffer was called"); } + /// + /// Gets the contents of the console buffer in a rectangular area. + /// + /// The rectangle inside which buffer contents will be accessed. + /// A BufferCell array with the requested buffer contents. public override BufferCell[,] GetBufferContents(Rectangle rectangle) { - throw new NotImplementedException(); + return this._internalRawUI.GetBufferContents(rectangle); } - public override KeyInfo ReadKey(ReadKeyOptions options) + /// + /// Scrolls the contents of the console buffer. + /// + /// The source rectangle to scroll. + /// The destination coordinates by which to scroll. + /// The rectangle inside which the scrolling will be clipped. + /// The cell with which the buffer will be filled. + public override void ScrollBufferContents( + Rectangle source, + Coordinates destination, + Rectangle clip, + BufferCell fill) { - throw new NotImplementedException(); + this._internalRawUI.ScrollBufferContents(source, destination, clip, fill); } - public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill) + /// + /// Sets the contents of the buffer inside the specified rectangle. + /// + /// The rectangle inside which buffer contents will be filled. + /// The BufferCell which will be used to fill the requested space. + public override void SetBufferContents( + Rectangle rectangle, + BufferCell fill) { - throw new NotImplementedException(); + // If the rectangle is all -1s then it means clear the visible buffer + if (rectangle.Top == -1 && + rectangle.Bottom == -1 && + rectangle.Left == -1 && + rectangle.Right == -1) + { + System.Console.Clear(); + return; + } + + this._internalRawUI.SetBufferContents(rectangle, fill); } - public override void SetBufferContents(Coordinates origin, BufferCell[,] contents) + /// + /// Sets the contents of the buffer at the given coordinate. + /// + /// The coordinate at which the buffer will be changed. + /// The new contents for the buffer at the given coordinate. + public override void SetBufferContents( + Coordinates origin, + BufferCell[,] contents) { - throw new NotImplementedException(); + this._internalRawUI.SetBufferContents(origin, contents); } - public override void SetBufferContents(Rectangle rectangle, BufferCell fill) + #endregion + + /// + /// Determines if a key press represents the input Ctrl + C. + /// + /// The key to test. + /// + /// if the key represents the input Ctrl + C, + /// otherwise . + /// + private static bool IsCtrlC(ConsoleKeyInfo keyInfo) { - throw new NotImplementedException(); + // In the VSCode terminal Ctrl C is processed as virtual key code "3", which + // is not a named value in the ConsoleKey enum. + if ((int)keyInfo.Key == 3) + { + return true; + } + + return keyInfo.Key == ConsoleKey.C && (keyInfo.Modifiers & ConsoleModifiers.Control) != 0; + } + + /// + /// Converts objects to objects and caches + /// key down events for the next key up request. + /// + /// The key to convert. + /// + /// A value indicating whether the result should be a key down event. + /// + /// The converted value. + private KeyInfo ProcessKey(ConsoleKeyInfo key, bool isDown) + { + // Translate ConsoleModifiers to ControlKeyStates + ControlKeyStates states = default; + if ((key.Modifiers & ConsoleModifiers.Alt) != 0) + { + states |= ControlKeyStates.LeftAltPressed; + } + + if ((key.Modifiers & ConsoleModifiers.Control) != 0) + { + states |= ControlKeyStates.LeftCtrlPressed; + } + + if ((key.Modifiers & ConsoleModifiers.Shift) != 0) + { + states |= ControlKeyStates.ShiftPressed; + } + + var result = new KeyInfo((int)key.Key, key.KeyChar, states, isDown); + if (isDown) + { + this._lastKeyDown = result; + } + + return result; } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs index 832704bb1..992531b77 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs @@ -1,14 +1,12 @@ using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Hosting; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; -using PowerShellEditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Host; using System.Security; -using System.Text; +using System.Threading; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host { @@ -16,116 +14,91 @@ internal class EditorServicesConsolePSHostUserInterface : PSHostUserInterface { private readonly ILogger _logger; - private readonly PowerShellExecutionService _executionService; + private readonly ConsoleReadLine _readLine; private readonly PSHostUserInterface _underlyingHostUI; public EditorServicesConsolePSHostUserInterface( ILogger logger, EditorServicesConsolePSHostRawUserInterface rawUI, - PowerShellExecutionService executionService, - PSHostUserInterface underlyingHostUI, - bool supportsVirtualTerminal) + ConsoleReadLine readLine, + PSHostUserInterface underlyingHostUI) { _logger = logger; - _executionService = executionService; + _readLine = readLine; _underlyingHostUI = underlyingHostUI; RawUI = rawUI; - SupportsVirtualTerminal = supportsVirtualTerminal; } public override PSHostRawUserInterface RawUI { get; } - public override bool SupportsVirtualTerminal { get; } + public override bool SupportsVirtualTerminal => _underlyingHostUI.SupportsVirtualTerminal; public override Dictionary Prompt(string caption, string message, Collection descriptions) { - throw new NotImplementedException(); + return _underlyingHostUI.Prompt(caption, message, descriptions); } public override int PromptForChoice(string caption, string message, Collection choices, int defaultChoice) { - throw new NotImplementedException(); + return _underlyingHostUI.PromptForChoice(caption, message, choices, defaultChoice); } public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) { - throw new NotImplementedException(); + return _underlyingHostUI.PromptForCredential(caption, message, userName, targetName, allowedCredentialTypes, options); } public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName) { - throw new NotImplementedException(); + return _underlyingHostUI.PromptForCredential(caption, message, userName, targetName); } public override string ReadLine() { - throw new NotImplementedException(); + return _readLine.ReadCommandLineAsync(CancellationToken.None).GetAwaiter().GetResult(); } public override SecureString ReadLineAsSecureString() { - throw new NotImplementedException(); + return _readLine.ReadSecureLineAsync(CancellationToken.None).GetAwaiter().GetResult(); } - public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) => - WriteOutput(value, foregroundColor, backgroundColor, includeNewline: false); + public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) + { + _underlyingHostUI.Write(foregroundColor, backgroundColor, value); + } public override void Write(string value) { - throw new NotImplementedException(); + _underlyingHostUI.Write(value); } public override void WriteDebugLine(string message) { - throw new NotImplementedException(); + _underlyingHostUI.WriteDebugLine(message); } public override void WriteErrorLine(string value) { - throw new NotImplementedException(); + _underlyingHostUI.WriteErrorLine(value); } public override void WriteLine(string value) { - throw new NotImplementedException(); + _underlyingHostUI.WriteLine(value); } public override void WriteProgress(long sourceId, ProgressRecord record) => _underlyingHostUI.WriteProgress(sourceId, record); public override void WriteVerboseLine(string message) { - throw new NotImplementedException(); + _underlyingHostUI.WriteVerboseLine(message); } public override void WriteWarningLine(string message) { - throw new NotImplementedException(); - } - - private void WriteOutput( - string outputString, - ConsoleColor foregroundColor, - ConsoleColor backgroundColor, - bool includeNewline) - { - ConsoleColor oldForegroundColor = System.Console.ForegroundColor; - ConsoleColor oldBackgroundColor = System.Console.BackgroundColor; - - System.Console.ForegroundColor = foregroundColor; - System.Console.BackgroundColor = backgroundColor; - - if (includeNewline) - { - System.Console.WriteLine(outputString); - } - else - { - System.Console.Write(outputString); - } - - System.Console.ForegroundColor = oldForegroundColor; - System.Console.BackgroundColor = oldBackgroundColor; + _underlyingHostUI.WriteWarningLine(message); } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs index ad72ef4b8..f00aab0ee 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -1,9 +1,8 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Management.Automation; using System.Text; using System.Threading; @@ -15,10 +14,16 @@ internal class PowerShellConsoleService : IDisposable { public static PowerShellConsoleService CreateAndStart( ILogger logger, + PowerShellStartupService startupService, PowerShellExecutionService executionService) { - var consoleService = new PowerShellConsoleService(logger, executionService); - consoleService.StartRepl(); + var consoleService = new PowerShellConsoleService( + logger, + executionService, + startupService.EngineIntrinsics, + startupService.EditorServicesHost, + startupService.ReadLine); + return consoleService; } @@ -26,18 +31,30 @@ public static PowerShellConsoleService CreateAndStart( private readonly PowerShellExecutionService _executionService; + private readonly EngineIntrinsics _engineIntrinsics; + + private readonly EditorServicesConsolePSHost _editorServicesHost; + + private readonly ConsoleReadLine _readLine; + private Task _consoleLoopThread; private CancellationTokenSource _replLoopCancellationSource; private CancellationTokenSource _currentCommandCancellationSource; - private PSReadLineProxy _psrlProxy; - - private PowerShellConsoleService(ILogger logger, PowerShellExecutionService executionService) + private PowerShellConsoleService( + ILogger logger, + PowerShellExecutionService executionService, + EngineIntrinsics engineIntrinsics, + EditorServicesConsolePSHost editorServicesHost, + ConsoleReadLine readLine) { _logger = logger; _executionService = executionService; + _engineIntrinsics = engineIntrinsics; + _editorServicesHost = editorServicesHost; + _readLine = readLine; } public void Dispose() @@ -45,7 +62,7 @@ public void Dispose() System.Console.CancelKeyPress -= HandleConsoleCancellation; } - private void StartRepl() + public void StartRepl() { _replLoopCancellationSource = new CancellationTokenSource(); System.Console.CancelKeyPress += HandleConsoleCancellation; @@ -55,20 +72,30 @@ private void StartRepl() private async Task RunReplLoopAsync() { - _psrlProxy = await PSReadLineProxy.LoadAndCreateAsync(_logger, _executionService).ConfigureAwait(false); - while (!_replLoopCancellationSource.IsCancellationRequested) { _currentCommandCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_replLoopCancellationSource.Token); try { - await InvokePromptFunctionAsync(); + await InvokePromptFunctionAsync().ConfigureAwait(false); } catch (OperationCanceledException) { break; } + + try + { + // Poll for user input here so that the prompt does not block + string userInput = await InvokeReadLineAsync(); + + await InvokeInputAsync(userInput).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + continue; + } } } @@ -86,8 +113,42 @@ private Task InvokePromptFunctionAsync() _currentCommandCancellationSource.Token); } - private Task InvokeReadLineAsync() + private async Task InvokeReadLineAsync() + { + string input = null; + while (string.IsNullOrEmpty(input)) + { + try + { + input = await InvokePSReadLineAsync(timeoutMillis: 30); + } + catch (OperationCanceledException) + { + continue; + } + } + + return input; + } + + private Task InvokePSReadLineAsync(int timeoutMillis) { + var readlineCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_currentCommandCancellationSource.Token); + readlineCancellationSource.CancelAfter(timeoutMillis); + + return _readLine.ReadCommandLineAsync(readlineCancellationSource.Token); + } + + private Task InvokeInputAsync(string input) + { + var command = new PSCommand().AddScript(input); + var executionOptions = new PowerShellExecutionOptions + { + WriteOutputToHost = true, + AddToHistory = true + }; + + return _executionService.ExecutePSCommandAsync(command, executionOptions, _currentCommandCancellationSource.Token); } private void HandleConsoleCancellation(object sender, ConsoleCancelEventArgs args) diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 12148a871..d99527d00 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -1,17 +1,13 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using PowerShellEditorServices.Services.PowerShell.Utility; using System; using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.IO; using System.Management.Automation; -using System.Management.Automation.Host; using System.Management.Automation.Runspaces; using System.Reflection; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using SMA = System.Management.Automation; @@ -40,19 +36,14 @@ internal class PowerShellExecutionService : IDisposable public static PowerShellExecutionService CreateAndStart( ILogger logger, HostStartupInfo hostInfo, - PSLanguageMode languageMode) + PowerShellStartupService startupService) { - var pwsh = SMA.PowerShell.Create(); - - var executionService = new PowerShellExecutionService(logger, pwsh); - var psHost = new EditorServicesConsolePSHost(logger, hostInfo, executionService); - - executionService.PSHost = psHost; - - pwsh.Runspace = CreateRunspace(psHost, languageMode); + var executionService = new PowerShellExecutionService(logger, startupService.PowerShell); executionService.Start(); + startupService.ReadLine.RegisterExecutionService(executionService); + executionService.EnqueueModuleImport(s_commandsModulePath); if (hostInfo.AdditionalModules != null && hostInfo.AdditionalModules.Count > 0) @@ -76,7 +67,17 @@ private PowerShellExecutionService( _executionQueue = new BlockingCollection(); } - public PSHost PSHost { get; private set; } + public Task ExecuteDelegateAsync(Func func, CancellationToken cancellationToken) + { + TResult appliedFunc(CancellationToken cancellationToken) => func(_pwsh, cancellationToken); + return ExecuteDelegateAsync(appliedFunc, cancellationToken); + } + + public Task ExecuteDelegateAsync(Action action, CancellationToken cancellationToken) + { + void appliedAction(CancellationToken cancellationToken) => action(_pwsh, cancellationToken); + return ExecuteDelegateAsync(appliedAction, cancellationToken); + } public Task ExecuteDelegateAsync(Func func, CancellationToken cancellationToken) { @@ -129,12 +130,17 @@ public void Dispose() private void Start() { - _pipelineThread = new Thread(RunConsumerLoop); + _pipelineThread = new Thread(RunConsumerLoop) + { + Name = "PSES Execution Service Thread" + }; _pipelineThread.Start(); } private void RunConsumerLoop() { + Runspace.DefaultRunspace = _pwsh.Runspace; + try { foreach (ISynchronousTask synchronousTask in _executionQueue.GetConsumingEnumerable()) @@ -153,22 +159,6 @@ private void RunConsumerLoop() } } - private static Runspace CreateRunspace( - PSHost psHost, - PSLanguageMode languageMode) - { - InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" - ? InitialSessionState.CreateDefault() - : InitialSessionState.CreateDefault2(); - - iss.LanguageMode = languageMode; - - Runspace runspace = RunspaceFactory.CreateRunspace(psHost, iss); - - runspace.SetApartmentStateToSta(); - runspace.ThreadOptions = PSThreadOptions.ReuseThread; - return runspace; - } private void EnqueueModuleImport(string moduleNameOrPath) { diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellStartupService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellStartupService.cs new file mode 100644 index 000000000..b9cacbbb7 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellStartupService.cs @@ -0,0 +1,81 @@ +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using System; +using System.Management.Automation; +using System.Management.Automation.Host; +using System.Management.Automation.Runspaces; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell +{ + internal class PowerShellStartupService + { + public static PowerShellStartupService Create( + ILogger logger, + HostStartupInfo hostStartupInfo) + { + var pwsh = SMA.PowerShell.Create(); + + var readLine = new ConsoleReadLine(); + + var rawUI = new EditorServicesConsolePSHostRawUserInterface(logger, hostStartupInfo.PSHost.UI.RawUI); + + var ui = new EditorServicesConsolePSHostUserInterface(logger, rawUI, readLine, hostStartupInfo.PSHost.UI); + + var psHost = new EditorServicesConsolePSHost(logger, ui, hostStartupInfo.Name, hostStartupInfo.Version); + + pwsh.Runspace = CreateRunspace(psHost, hostStartupInfo.LanguageMode); + Runspace.DefaultRunspace = pwsh.Runspace; + + var engineIntrinsics = (EngineIntrinsics)pwsh.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); + + readLine.RegisterPSReadLineProxy(PSReadLineProxy.LoadAndCreate(logger, pwsh)); + readLine.RegisterPowerShellEngine(psHost, engineIntrinsics); + + return new PowerShellStartupService(pwsh, engineIntrinsics, psHost, readLine); + } + + private PowerShellStartupService( + SMA.PowerShell pwsh, + EngineIntrinsics engineIntrinsics, + EditorServicesConsolePSHost editorServicesHost, + ConsoleReadLine readLine) + { + PowerShell = pwsh; + EngineIntrinsics = engineIntrinsics; + EditorServicesHost = editorServicesHost; + ReadLine = readLine; + } + + public SMA.PowerShell PowerShell { get; } + + public EngineIntrinsics EngineIntrinsics { get; } + + public EditorServicesConsolePSHost EditorServicesHost { get; } + + public ConsoleReadLine ReadLine { get; } + + private static Runspace CreateRunspace( + PSHost psHost, + PSLanguageMode languageMode) + { + InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" + ? InitialSessionState.CreateDefault() + : InitialSessionState.CreateDefault2(); + + iss.LanguageMode = languageMode; + + Runspace runspace = RunspaceFactory.CreateRunspace(psHost, iss); + + runspace.SetApartmentStateToSta(); + runspace.ThreadOptions = PSThreadOptions.ReuseThread; + + runspace.Open(); + + return runspace; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs index 65133f126..23f3bb12e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs @@ -4,7 +4,7 @@ using System.Reflection; using System.Threading; -namespace PowerShellEditorServices.Services.PowerShell.Utility +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility { internal static class RunspaceExtensions { diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs index 1c1775562..66e490156 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs @@ -3,6 +3,7 @@ using Microsoft.PowerShell.EditorServices.Extensions; using Microsoft.PowerShell.EditorServices.Handlers; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; @@ -16,16 +17,16 @@ internal class EditorOperationsService : IEditorOperations private const bool DefaultPreviewSetting = true; private readonly WorkspaceService _workspaceService; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellStartupService _startupService; private readonly ILanguageServerFacade _languageServer; public EditorOperationsService( WorkspaceService workspaceService, - PowerShellContextService powerShellContextService, + PowerShellStartupService startupService, ILanguageServerFacade languageServer) { _workspaceService = workspaceService; - _powerShellContextService = powerShellContextService; + _startupService = startupService; _languageServer = languageServer; } @@ -272,7 +273,7 @@ private bool TestHasLanguageServer(bool warnUser = true) if (warnUser) { - _powerShellContextService.ExternalHost.UI.WriteWarningLine( + _startupService.EditorServicesHost.UI.WriteWarningLine( "Editor operations are not supported in temporary consoles. Re-run the command in the main PowerShell Intergrated Console."); } diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs index db9d6ced9..13d9296e8 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs @@ -4,8 +4,11 @@ using System; using System.Collections.Generic; using System.Management.Automation; +using System.Threading; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Extensions; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.LanguageServer.Protocol.Server; @@ -44,7 +47,7 @@ internal sealed class ExtensionService /// /// Gets the PowerShellContext in which extension code will be executed. /// - internal PowerShellContextService PowerShellContext { get; private set; } + internal PowerShellExecutionService ExecutionService { get; private set; } #endregion @@ -55,9 +58,9 @@ internal sealed class ExtensionService /// PowerShellContext for loading and executing extension code. /// /// A PowerShellContext used to execute extension code. - internal ExtensionService(PowerShellContextService powerShellContext, ILanguageServerFacade languageServer) + internal ExtensionService(PowerShellExecutionService executionService, ILanguageServerFacade languageServer) { - this.PowerShellContext = powerShellContext; + ExecutionService = executionService; _languageServer = languageServer; } @@ -90,13 +93,10 @@ internal async Task InitializeAsync( this.EditorObject.SetAsStaticInstance(); // Register the editor object in the runspace - PSCommand variableCommand = new PSCommand(); - using (RunspaceHandle handle = await this.PowerShellContext.GetRunspaceHandleAsync().ConfigureAwait(false)) + await ExecutionService.ExecuteDelegateAsync((pwsh, cancellationToken) => { - handle.Runspace.SessionStateProxy.PSVariable.Set( - "psEditor", - this.EditorObject); - } + pwsh.Runspace.SessionStateProxy.PSVariable.Set("psEditor", EditorObject); + }, CancellationToken.None); } /// @@ -115,10 +115,10 @@ public async Task InvokeCommandAsync(string commandName, EditorContext editorCon executeCommand.AddParameter("ScriptBlock", editorCommand.ScriptBlock); executeCommand.AddParameter("ArgumentList", new object[] { editorContext }); - await this.PowerShellContext.ExecuteCommandAsync( + await ExecutionService.ExecutePSCommandAsync( executeCommand, - sendOutputToHost: !editorCommand.SuppressOutput, - sendErrorToHost: true).ConfigureAwait(false); + new PowerShellExecutionOptions { WriteOutputToHost = !editorCommand.SuppressOutput, WriteErrorsToHost = true }, + CancellationToken.None).ConfigureAwait(false); } else { diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/EvaluateHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/EvaluateHandler.cs index ca37a1cc3..2c6925fb2 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/EvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/EvaluateHandler.cs @@ -1,31 +1,32 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Management.Automation; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; namespace Microsoft.PowerShell.EditorServices.Handlers { internal class EvaluateHandler : IEvaluateHandler { private readonly ILogger _logger; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; - public EvaluateHandler(ILoggerFactory factory, PowerShellContextService powerShellContextService) + public EvaluateHandler(ILoggerFactory factory, PowerShellExecutionService executionService) { _logger = factory.CreateLogger(); - _powerShellContextService = powerShellContextService; + _executionService = executionService; } public Task Handle(EvaluateRequestArguments request, CancellationToken cancellationToken) { - _powerShellContextService.ExecuteScriptStringAsync( - request.Expression, - writeInputToHost: true, - writeOutputToHost: true, - addToHistory: true); + _executionService.ExecutePSCommandAsync( + new PSCommand().AddScript(request.Expression), + new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true }, + cancellationToken); return Task.FromResult(new EvaluateResponseBody { diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ExpandAliasHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ExpandAliasHandler.cs index 08da235c8..9bc7c0c9f 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ExpandAliasHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ExpandAliasHandler.cs @@ -9,6 +9,8 @@ using Microsoft.PowerShell.EditorServices.Services; using MediatR; using OmniSharp.Extensions.JsonRpc; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; namespace Microsoft.PowerShell.EditorServices.Handlers { @@ -28,12 +30,12 @@ internal class ExpandAliasResult internal class ExpandAliasHandler : IExpandAliasHandler { private readonly ILogger _logger; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; - public ExpandAliasHandler(ILoggerFactory factory, PowerShellContextService powerShellContextService) + public ExpandAliasHandler(ILoggerFactory factory, PowerShellExecutionService executionService) { _logger = factory.CreateLogger(); - _powerShellContextService = powerShellContextService; + _executionService = executionService; } public async Task Handle(ExpandAliasParams request, CancellationToken cancellationToken) @@ -69,8 +71,7 @@ function __Expand-Alias { .AddStatement() .AddCommand("__Expand-Alias") .AddArgument(request.Text); - var result = await _powerShellContextService.ExecuteCommandAsync( - psCommand, cancellationToken: cancellationToken).ConfigureAwait(false); + var result = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), cancellationToken).ConfigureAwait(false); return new ExpandAliasResult { diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommandHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommandHandler.cs index 91931a3bf..bbf3b862f 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommandHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommandHandler.cs @@ -9,6 +9,8 @@ using Microsoft.PowerShell.EditorServices.Services; using MediatR; using OmniSharp.Extensions.JsonRpc; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; namespace Microsoft.PowerShell.EditorServices.Handlers { @@ -33,12 +35,12 @@ internal class PSCommandMessage internal class GetCommandHandler : IGetCommandHandler { private readonly ILogger _logger; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; - public GetCommandHandler(ILoggerFactory factory, PowerShellContextService powerShellContextService) + public GetCommandHandler(ILoggerFactory factory, PowerShellExecutionService executionService) { _logger = factory.CreateLogger(); - _powerShellContextService = powerShellContextService; + _executionService = executionService; } public async Task> Handle(GetCommandParams request, CancellationToken cancellationToken) @@ -53,8 +55,7 @@ public async Task> Handle(GetCommandParams request, Cance .AddCommand("Microsoft.PowerShell.Utility\\Sort-Object") .AddParameter("Property", "Name"); - IEnumerable result = await _powerShellContextService.ExecuteCommandAsync( - psCommand, cancellationToken: cancellationToken).ConfigureAwait(false); + IEnumerable result = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), cancellationToken).ConfigureAwait(false); var commandList = new List(); if (result != null) diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetVersionHandler.cs index ec629b66b..cd976ba8a 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetVersionHandler.cs @@ -3,11 +3,14 @@ using System; using System.Management.Automation; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; @@ -20,18 +23,21 @@ internal class GetVersionHandler : IGetVersionHandler private static readonly Version s_desiredPackageManagementVersion = new Version(1, 4, 6); private readonly ILogger _logger; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; + private readonly PowerShellStartupService _startupService; private readonly ILanguageServerFacade _languageServer; private readonly ConfigurationService _configurationService; public GetVersionHandler( ILoggerFactory factory, - PowerShellContextService powerShellContextService, + PowerShellExecutionService executionService, + PowerShellStartupService startupService, ILanguageServerFacade languageServer, ConfigurationService configurationService) { _logger = factory.CreateLogger(); - _powerShellContextService = powerShellContextService; + _executionService = executionService; + _startupService = startupService; _languageServer = languageServer; _configurationService = configurationService; } @@ -77,7 +83,7 @@ private enum PowerShellProcessArchitecture private async Task CheckPackageManagement() { PSCommand getModule = new PSCommand().AddCommand("Get-Module").AddParameter("ListAvailable").AddParameter("Name", "PackageManagement"); - foreach (PSModuleInfo module in await _powerShellContextService.ExecuteCommandAsync(getModule).ConfigureAwait(false)) + foreach (PSModuleInfo module in await _executionService.ExecutePSCommandAsync(getModule, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false)) { // The user has a good enough version of PackageManagement if (module.Version >= s_desiredPackageManagementVersion) @@ -87,7 +93,7 @@ private async Task CheckPackageManagement() _logger.LogDebug("Old version of PackageManagement detected."); - if (_powerShellContextService.CurrentRunspace.Runspace.SessionStateProxy.LanguageMode != PSLanguageMode.FullLanguage) + if (_startupService.EditorServicesHost.Runspace.SessionStateProxy.LanguageMode != PSLanguageMode.FullLanguage) { _languageServer.Window.ShowWarning("You have an older version of PackageManagement known to cause issues with the PowerShell extension. Please run the following command in a new Windows PowerShell session and then restart the PowerShell extension: `Install-Module PackageManagement -Force -AllowClobber -MinimumVersion 1.4.6`"); return; @@ -114,14 +120,14 @@ private async Task CheckPackageManagement() // If the user chose "Not now" ignore it for the rest of the session. if (messageAction?.Title == takeActionText) { - StringBuilder errors = new StringBuilder(); - await _powerShellContextService.ExecuteScriptStringAsync( - "powershell.exe -NoLogo -NoProfile -Command '[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Install-Module -Name PackageManagement -Force -MinimumVersion 1.4.6 -Scope CurrentUser -AllowClobber -Repository PSGallery'", - errors, - writeInputToHost: true, - writeOutputToHost: true, - addToHistory: true).ConfigureAwait(false); + var command = new PSCommand().AddScript("powershell.exe -NoLogo -NoProfile -Command 'Install-Module -Name PackageManagement -Force -MinimumVersion 1.4.6 -Scope CurrentUser -AllowClobber'"); + await _executionService.ExecutePSCommandAsync( + command, + new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true }, + CancellationToken.None).ConfigureAwait(false); + + /* if (errors.Length == 0) { _logger.LogDebug("PackageManagement is updated."); @@ -141,6 +147,7 @@ await _powerShellContextService.ExecuteScriptStringAsync( Message = "PackageManagement update failed. This might be due to PowerShell Gallery using TLS 1.2. More info can be found at https://aka.ms/psgallerytls" }); } + */ } } } diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs index 77a47f845..9745d96f0 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs @@ -11,17 +11,19 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { + using Microsoft.PowerShell.EditorServices.Services.PowerShell; + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using System.Management.Automation; internal class PSHostProcessAndRunspaceHandlers : IGetPSHostProcessesHandler, IGetRunspaceHandler { private readonly ILogger _logger; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; - public PSHostProcessAndRunspaceHandlers(ILoggerFactory factory, PowerShellContextService powerShellContextService) + public PSHostProcessAndRunspaceHandlers(ILoggerFactory factory, PowerShellExecutionService executionService) { _logger = factory.CreateLogger(); - _powerShellContextService = powerShellContextService; + _executionService = executionService; } public Task Handle(GetPSHostProcesssesParams request, CancellationToken cancellationToken) @@ -85,10 +87,8 @@ public async Task Handle(GetRunspaceParams request, Cancella else { var psCommand = new PSCommand().AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace"); - var sb = new StringBuilder(); // returns (not deserialized) Runspaces. For simpler code, we use PSObject and rely on dynamic later. - runspaces = await _powerShellContextService.ExecuteCommandAsync( - psCommand, sb, cancellationToken: cancellationToken).ConfigureAwait(false); + runspaces = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), cancellationToken).ConfigureAwait(false); } var runspaceResponses = new List(); diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ShowHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ShowHelpHandler.cs index 76a69c1a8..0f8500f9d 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ShowHelpHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ShowHelpHandler.cs @@ -8,6 +8,8 @@ using Microsoft.PowerShell.EditorServices.Services; using MediatR; using OmniSharp.Extensions.JsonRpc; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; namespace Microsoft.PowerShell.EditorServices.Handlers { @@ -22,12 +24,12 @@ internal class ShowHelpParams : IRequest internal class ShowHelpHandler : IShowHelpHandler { private readonly ILogger _logger; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; - public ShowHelpHandler(ILoggerFactory factory, PowerShellContextService powerShellContextService) + public ShowHelpHandler(ILoggerFactory factory, PowerShellExecutionService executionService) { _logger = factory.CreateLogger(); - _powerShellContextService = powerShellContextService; + _executionService = executionService; } public async Task Handle(ShowHelpParams request, CancellationToken cancellationToken) @@ -72,8 +74,7 @@ public async Task Handle(ShowHelpParams request, CancellationToken cancell // TODO: Rather than print the help in the console, we should send the string back // to VSCode to display in a help pop-up (or similar) - await _powerShellContextService.ExecuteCommandAsync( - checkHelpPSCommand, sendOutputToHost: true, cancellationToken: cancellationToken).ConfigureAwait(false); + await _executionService.ExecutePSCommandAsync(checkHelpPSCommand, new PowerShellExecutionOptions { WriteOutputToHost = true }, cancellationToken).ConfigureAwait(false); return Unit.Value; } } diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs index f251d3e63..ea932161d 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs @@ -20,6 +20,7 @@ using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Utility; +using SMA = System.Management.Automation; namespace Microsoft.PowerShell.EditorServices.Services { @@ -79,7 +80,7 @@ static PowerShellContextService() private readonly bool isPSReadLineEnabled; private readonly ILogger logger; - private PowerShell powerShell; + private SMA.PowerShell powerShell; private bool ownsInitialRunspace; private RunspaceDetails initialRunspace; private SessionDetails mostRecentSessionDetails; @@ -337,7 +338,7 @@ public void Initialize( initialRunspace, this.logger); - this.powerShell = PowerShell.Create(); + this.powerShell = SMA.PowerShell.Create(); this.powerShell.Runspace = initialRunspace; this.initialRunspace = @@ -736,7 +737,9 @@ public async Task> ExecuteCommandAsync( AddToHistory = executionOptions.AddToHistory }; - PowerShell shell = this.PromptNest.GetPowerShell(executionOptions.IsReadLine); + this.logger.LogTrace("Passing to PowerShell"); + + SMA.PowerShell shell = this.PromptNest.GetPowerShell(executionOptions.IsReadLine); // Due to the following PowerShell bug, we can't just assign shell.Commands to psCommand // because PowerShell strips out CommandInfo: @@ -1021,7 +1024,7 @@ public Task> ExecuteScriptStringAsync( try { var scriptBlock = ScriptBlock.Create(scriptString); - PowerShell ps = scriptBlock.GetPowerShell(isTrustedInput: false, null); + SMA.PowerShell ps = scriptBlock.GetPowerShell(isTrustedInput: false, null); command = ps.Commands; } catch (Exception e) @@ -1163,7 +1166,7 @@ internal void ForcePSEventHandling() /// This method is called automatically by . Consider using /// that method instead of calling this directly when possible. /// - internal Task InvokeOnPipelineThreadAsync(Action invocationAction) + internal Task InvokeOnPipelineThreadAsync(Action invocationAction) { if (this.PromptNest.IsReadLineBusy()) { @@ -1190,7 +1193,7 @@ internal static TResult ExecuteScriptAndGetItem( TResult defaultValue = default, bool useLocalScope = false) { - using (PowerShell pwsh = PowerShell.Create()) + using (SMA.PowerShell pwsh = SMA.PowerShell.Create()) { pwsh.Runspace = runspace; IEnumerable results = pwsh.AddScript(scriptToExecute, useLocalScope).Invoke(); @@ -1523,7 +1526,7 @@ private void CloseRunspace(RunspaceDetails runspaceDetails) try { - using (PowerShell ps = PowerShell.Create()) + using (SMA.PowerShell ps = SMA.PowerShell.Create()) { ps.Runspace = runspaceDetails.Runspace; ps.AddCommand(exitCommand); @@ -2263,7 +2266,7 @@ private SessionDetails GetSessionDetailsInRunspace(Runspace runspace) this.GetSessionDetails( command => { - using (PowerShell powerShell = PowerShell.Create()) + using (SMA.PowerShell powerShell = SMA.PowerShell.Create()) { powerShell.Runspace = runspace; powerShell.Commands = command; @@ -2300,7 +2303,7 @@ private SessionDetails GetSessionDetailsInNestedPipeline() return this.GetSessionDetails( command => { - using (var localPwsh = PowerShell.Create(RunspaceMode.CurrentRunspace)) + using (var localPwsh = SMA.PowerShell.Create(RunspaceMode.CurrentRunspace)) { localPwsh.Commands = command; return localPwsh.Invoke().FirstOrDefault(); diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs index 2685067a5..6c3d97856 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs @@ -4,6 +4,8 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Extensions; using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Utility; using System; @@ -14,6 +16,7 @@ using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Services @@ -29,7 +32,8 @@ internal class RemoteFileManagerService private ILogger logger; private string remoteFilesPath; private string processTempPath; - private PowerShellContextService powerShellContext; + private readonly PowerShellStartupService _startupService; + private readonly PowerShellExecutionService _executionService; private IEditorOperations editorOperations; private Dictionary filesPerComputer = @@ -247,14 +251,14 @@ function New-EditorFile { /// public RemoteFileManagerService( ILoggerFactory factory, - PowerShellContextService powerShellContext, + PowerShellStartupService startupService, + PowerShellExecutionService executionService, EditorOperationsService editorOperations) { - Validate.IsNotNull(nameof(powerShellContext), powerShellContext); - this.logger = factory.CreateLogger(); - this.powerShellContext = powerShellContext; - this.powerShellContext.RunspaceChanged += HandleRunspaceChangedAsync; + _executionService = executionService; + _startupService = startupService; + //this.powerShellContext.RunspaceChanged += HandleRunspaceChangedAsync; this.editorOperations = editorOperations; @@ -269,7 +273,7 @@ public RemoteFileManagerService( this.TryDeleteTemporaryPath(); // Register the psedit function in the current runspace - this.RegisterPSEditFunction(this.powerShellContext.CurrentRunspace); + //this.RegisterPSEditFunction(this.powerShellContext.CurrentRunspace); } #endregion @@ -314,7 +318,7 @@ public async Task FetchRemoteFileAsync( command.AddParameter("Encoding", "Byte"); byte[] fileContent = - (await this.powerShellContext.ExecuteCommandAsync(command, false, false).ConfigureAwait(false)) + (await this._executionService.ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false)) .FirstOrDefault(); if (fileContent != null) @@ -354,7 +358,7 @@ public async Task SaveRemoteFileAsync(string localFilePath) string remoteFilePath = this.GetMappedPath( localFilePath, - this.powerShellContext.CurrentRunspace); + null); //_startupService.EditorServicesHost.Runspace); this.logger.LogTrace( $"Saving remote file {remoteFilePath} (local path: {localFilePath})"); @@ -379,18 +383,17 @@ public async Task SaveRemoteFileAsync(string localFilePath) .AddParameter("RemoteFilePath", remoteFilePath) .AddParameter("Content", localFileContents); - StringBuilder errorMessages = new StringBuilder(); - - await powerShellContext.ExecuteCommandAsync( + await _executionService.ExecutePSCommandAsync( saveCommand, - errorMessages, - false, - false).ConfigureAwait(false); + new PowerShellExecutionOptions(), + CancellationToken.None).ConfigureAwait(false); + /* if (errorMessages.Length > 0) { this.logger.LogError($"Remote file save failed due to an error:\r\n\r\n{errorMessages}"); } + */ } /// @@ -541,6 +544,8 @@ private async void HandleRunspaceChangedAsync(object sender, RunspaceChangedEven private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) { + return; + if (string.Equals(RemoteSessionOpenFile, args.SourceIdentifier, StringComparison.CurrentCultureIgnoreCase)) { try @@ -551,7 +556,7 @@ private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) string remoteFilePath = args.SourceArgs[0] as string; // Is this a local process runspace? Treat as a local file - if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Local) + if (false) //_executionService.CurrentRunspace.Location == RunspaceLocation.Local) { localFilePath = remoteFilePath; } @@ -584,7 +589,8 @@ private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) this.StoreRemoteFile( remoteFilePath, fileContent, - this.powerShellContext.CurrentRunspace); + (RunspaceDetails)null); + //this.powerShellContext.CurrentRunspace); } else { @@ -628,7 +634,7 @@ private void RegisterPSEditFunction(RunspaceDetails runspaceDetails) if (runspaceDetails.Context == RunspaceContext.DebuggedRunspace) { - this.powerShellContext.ExecuteCommandAsync(createCommand).Wait(); + _executionService.ExecutePSCommandAsync(createCommand, new PowerShellExecutionOptions(), CancellationToken.None).GetAwaiter().GetResult(); } else { diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs index 6be0b1679..7ab52ab94 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs @@ -3,11 +3,15 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Handlers; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Utility; using System; +using System.Collections.ObjectModel; using System.Linq; using System.Management.Automation; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Services @@ -21,10 +25,10 @@ internal class TemplateService { #region Private Fields - private readonly ILogger logger; + private readonly ILogger _logger; private bool isPlasterLoaded; private bool? isPlasterInstalled; - private readonly PowerShellContextService powerShellContext; + private readonly PowerShellExecutionService _executionService; #endregion @@ -35,12 +39,10 @@ internal class TemplateService /// /// The PowerShellContext to use for this service. /// An ILoggerFactory implementation used for writing log messages. - public TemplateService(PowerShellContextService powerShellContext, ILoggerFactory factory) + public TemplateService(PowerShellExecutionService executionService, ILoggerFactory factory) { - Validate.IsNotNull(nameof(powerShellContext), powerShellContext); - - this.logger = factory.CreateLogger(); - this.powerShellContext = powerShellContext; + _logger = factory.CreateLogger(); + _executionService = executionService; } #endregion @@ -71,24 +73,21 @@ public async Task ImportPlasterIfInstalledAsync() .AddCommand("Select-Object") .AddParameter("First", 1); - this.logger.LogTrace("Checking if Plaster is installed..."); + this._logger.LogTrace("Checking if Plaster is installed..."); - var getResult = - await this.powerShellContext.ExecuteCommandAsync( - psCommand, false, false).ConfigureAwait(false); + PSObject moduleObject = (await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false)).First(); - PSObject moduleObject = getResult.First(); this.isPlasterInstalled = moduleObject != null; string installedQualifier = this.isPlasterInstalled.Value ? string.Empty : "not "; - this.logger.LogTrace($"Plaster is {installedQualifier}installed!"); + this._logger.LogTrace($"Plaster is {installedQualifier}installed!"); // Attempt to load plaster if (this.isPlasterInstalled.Value && this.isPlasterLoaded == false) { - this.logger.LogTrace("Loading Plaster..."); + this._logger.LogTrace("Loading Plaster..."); psCommand = new PSCommand(); psCommand @@ -96,16 +95,14 @@ await this.powerShellContext.ExecuteCommandAsync( .AddParameter("ModuleInfo", (PSModuleInfo)moduleObject.ImmediateBaseObject) .AddParameter("PassThru"); - var importResult = - await this.powerShellContext.ExecuteCommandAsync( - psCommand, false, false).ConfigureAwait(false); + Collection importResult = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); this.isPlasterLoaded = importResult.Any(); string loadedQualifier = this.isPlasterInstalled.Value ? "was" : "could not be"; - this.logger.LogTrace($"Plaster {loadedQualifier} loaded successfully!"); + this._logger.LogTrace($"Plaster {loadedQualifier} loaded successfully!"); } } @@ -137,11 +134,12 @@ public async Task GetAvailableTemplatesAsync( psCommand.AddParameter("IncludeModules"); } - var templateObjects = - await this.powerShellContext.ExecuteCommandAsync( - psCommand, false, false).ConfigureAwait(false); + Collection templateObjects = await _executionService.ExecutePSCommandAsync( + psCommand, + new PowerShellExecutionOptions(), + CancellationToken.None).ConfigureAwait(false); - this.logger.LogTrace($"Found {templateObjects.Count()} Plaster templates"); + this._logger.LogTrace($"Found {templateObjects.Count()} Plaster templates"); return templateObjects @@ -161,7 +159,7 @@ public async Task CreateFromTemplateAsync( string templatePath, string destinationPath) { - this.logger.LogTrace( + this._logger.LogTrace( $"Invoking Plaster...\n\n TemplatePath: {templatePath}\n DestinationPath: {destinationPath}"); PSCommand command = new PSCommand(); @@ -169,19 +167,13 @@ public async Task CreateFromTemplateAsync( command.AddParameter("TemplatePath", templatePath); command.AddParameter("DestinationPath", destinationPath); - var errorString = new System.Text.StringBuilder(); - await this.powerShellContext.ExecuteCommandAsync( + await _executionService.ExecutePSCommandAsync( command, - errorString, - new ExecutionOptions - { - WriteOutputToHost = false, - WriteErrorsToHost = true, - InterruptCommandPrompt = true - }).ConfigureAwait(false); + new PowerShellExecutionOptions { WriteErrorsToHost = true, InterruptCommandPrompt = true }, + CancellationToken.None).ConfigureAwait(false); // If any errors were written out, creation was not successful - return errorString.Length == 0; + return true; } #endregion diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs index 4dc94b55b..b64c39f46 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs @@ -3,9 +3,13 @@ using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Management.Automation; +using System.Threading; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext @@ -61,10 +65,10 @@ internal static class CommandHelpers /// A CommandInfo object with details about the specified command. public static async Task GetCommandInfoAsync( string commandName, - PowerShellContextService powerShellContext) + PowerShellExecutionService executionService) { Validate.IsNotNull(nameof(commandName), commandName); - Validate.IsNotNull(nameof(powerShellContext), powerShellContext); + Validate.IsNotNull(nameof(executionService), executionService); // If we have a CommandInfo cached, return that. if (s_commandInfoCache.TryGetValue(commandName, out CommandInfo cmdInfo)) @@ -83,15 +87,17 @@ public static async Task GetCommandInfoAsync( return null; } - PSCommand command = new PSCommand(); - command.AddCommand(@"Microsoft.PowerShell.Core\Get-Command"); - command.AddArgument(commandName); - command.AddParameter("ErrorAction", "Ignore"); + PSCommand command = new PSCommand() + .AddCommand(@"Microsoft.PowerShell.Core\Get-Command") + .AddArgument(commandName) + .AddParameter("ErrorAction", "Ignore"); - CommandInfo commandInfo = (await powerShellContext.ExecuteCommandAsync(command, sendOutputToHost: false, sendErrorToHost: false).ConfigureAwait(false)) - .Select(o => o.BaseObject) - .OfType() - .FirstOrDefault(); + CommandInfo commandInfo = null; + foreach (CommandInfo result in await executionService.ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false)) + { + commandInfo = result; + break; + } // Only cache CmdletInfos since they're exposed in binaries they are likely to not change throughout the session. if (commandInfo?.CommandType == CommandTypes.Cmdlet) @@ -106,14 +112,14 @@ public static async Task GetCommandInfoAsync( /// Gets the command's "Synopsis" documentation section. /// /// The CommandInfo instance for the command. - /// The PowerShellContext to use for getting command documentation. + /// The PowerShellContext to use for getting command documentation. /// public static async Task GetCommandSynopsisAsync( CommandInfo commandInfo, - PowerShellContextService powerShellContext) + PowerShellExecutionService executionService) { Validate.IsNotNull(nameof(commandInfo), commandInfo); - Validate.IsNotNull(nameof(powerShellContext), powerShellContext); + Validate.IsNotNull(nameof(executionService), executionService); // A small optimization to not run Get-Help on things like DSC resources. if (commandInfo.CommandType != CommandTypes.Cmdlet && @@ -140,7 +146,7 @@ public static async Task GetCommandSynopsisAsync( .AddParameter("Online", false) .AddParameter("ErrorAction", "Ignore"); - var results = await powerShellContext.ExecuteCommandAsync(command, sendOutputToHost: false, sendErrorToHost: false).ConfigureAwait(false); + Collection results = await executionService.ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); PSObject helpObject = results.FirstOrDefault(); // Extract the synopsis string from the object diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs index 4498c54cf..fbc6949ea 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Management.Automation; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; namespace Microsoft.PowerShell.EditorServices.Services.Symbols @@ -36,9 +37,9 @@ internal class SymbolDetails #region Constructors - static internal async Task CreateAsync( + internal static async Task CreateAsync( SymbolReference symbolReference, - PowerShellContextService powerShellContext) + PowerShellExecutionService executionService) { SymbolDetails symbolDetails = new SymbolDetails { @@ -50,14 +51,14 @@ static internal async Task CreateAsync( case SymbolType.Function: CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( symbolReference.SymbolName, - powerShellContext).ConfigureAwait(false); + executionService).ConfigureAwait(false); if (commandInfo != null) { symbolDetails.Documentation = await CommandHelpers.GetCommandSynopsisAsync( commandInfo, - powerShellContext).ConfigureAwait(false); + executionService).ConfigureAwait(false); if (commandInfo.CommandType == CommandTypes.Application) { diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index bbbd24484..871cd1bbe 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -15,6 +15,7 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.CodeLenses; using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Services.TextDocument; @@ -31,7 +32,7 @@ internal class SymbolsService #region Private Fields private readonly ILogger _logger; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; private readonly WorkspaceService _workspaceService; private readonly ConcurrentDictionary _codeLensProviders; @@ -48,12 +49,12 @@ internal class SymbolsService /// An ILoggerFactory implementation used for writing log messages. public SymbolsService( ILoggerFactory factory, - PowerShellContextService powerShellContextService, + PowerShellExecutionService executionService, WorkspaceService workspaceService, ConfigurationService configurationService) { _logger = factory.CreateLogger(); - _powerShellContextService = powerShellContextService; + _executionService = executionService; _workspaceService = workspaceService; _codeLensProviders = new ConcurrentDictionary(); @@ -319,7 +320,7 @@ public async Task FindSymbolDetailsAtLocationAsync( symbolReference.FilePath = scriptFile.FilePath; SymbolDetails symbolDetails = await SymbolDetails.CreateAsync( symbolReference, - _powerShellContextService).ConfigureAwait(false); + _executionService).ConfigureAwait(false); return symbolDetails; } @@ -334,8 +335,7 @@ public async Task FindSymbolDetailsAtLocationAsync( public async Task FindParameterSetsInFileAsync( ScriptFile file, int lineNumber, - int columnNumber, - PowerShellContextService powerShellContext) + int columnNumber) { SymbolReference foundSymbol = AstOperations.FindCommandAtPosition( @@ -355,7 +355,7 @@ public async Task FindParameterSetsInFileAsync( CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( foundSymbol.SymbolName, - powerShellContext).ConfigureAwait(false); + _executionService).ConfigureAwait(false); if (commandInfo == null) { @@ -471,7 +471,7 @@ public async Task GetDefinitionOfSymbolAsync( CommandInfo cmdInfo = await CommandHelpers.GetCommandInfoAsync( foundSymbol.SymbolName, - _powerShellContextService).ConfigureAwait(false); + _executionService).ConfigureAwait(false); foundDefinition = FindDeclarationForBuiltinCommand( diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index 2251c56b3..4f1ad1038 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -11,7 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.Symbols @@ -58,7 +58,7 @@ public static async Task GetCompletionsAsync( Ast scriptAst, Token[] currentTokens, int fileOffset, - PowerShellContextService powerShellContext, + PowerShellExecutionService executionService, ILogger logger, CancellationToken cancellationToken) { @@ -80,16 +80,19 @@ public static async Task GetCompletionsAsync( cursorPosition.LineNumber, cursorPosition.ColumnNumber)); + /* if (!powerShellContext.IsAvailable) { return null; } + */ var stopwatch = new Stopwatch(); // If the current runspace is out of process we can use // CommandCompletion.CompleteInput because PSReadLine won't be taking up the // main runspace. + /* if (powerShellContext.IsCurrentRunspaceOutOfProcess()) { using (RunspaceHandle runspaceHandle = await powerShellContext.GetRunspaceHandleAsync(cancellationToken).ConfigureAwait(false)) @@ -113,19 +116,20 @@ public static async Task GetCompletionsAsync( } } } + */ CommandCompletion commandCompletion = null; - await powerShellContext.InvokeOnPipelineThreadAsync( - pwsh => - { - stopwatch.Start(); - commandCompletion = CommandCompletion.CompleteInput( - scriptAst, - currentTokens, - cursorPosition, - options: null, - powershell: pwsh); - }).ConfigureAwait(false); + await executionService.ExecuteDelegateAsync((pwsh, cancellationToken) => + { + stopwatch.Start(); + commandCompletion = CommandCompletion.CompleteInput( + scriptAst, + currentTokens, + cursorPosition, + options: null, + powershell: pwsh); + }, cancellationToken); + stopwatch.Stop(); logger.LogTrace($"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs index 4c77fe934..b325abd00 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Services.TextDocument; @@ -26,7 +27,7 @@ internal class PsesCompletionHandler : ICompletionHandler, ICompletionResolveHan private readonly SemaphoreSlim _completionLock = AsyncUtils.CreateSimpleLockingSemaphore(); private readonly SemaphoreSlim _completionResolveLock = AsyncUtils.CreateSimpleLockingSemaphore(); private readonly ILogger _logger; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; private readonly WorkspaceService _workspaceService; private CompletionResults _mostRecentCompletions; private int _mostRecentRequestLine; @@ -38,11 +39,11 @@ internal class PsesCompletionHandler : ICompletionHandler, ICompletionResolveHan public PsesCompletionHandler( ILoggerFactory factory, - PowerShellContextService powerShellContextService, + PowerShellExecutionService executionService, WorkspaceService workspaceService) { _logger = factory.CreateLogger(); - _powerShellContextService = powerShellContextService; + _executionService = executionService; _workspaceService = workspaceService; } @@ -139,13 +140,13 @@ public async Task Handle(CompletionItem request, CancellationTok CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( request.Label, - _powerShellContextService).ConfigureAwait(false); + _executionService).ConfigureAwait(false); if (commandInfo != null) { request = request with { - Documentation = await CommandHelpers.GetCommandSynopsisAsync(commandInfo, _powerShellContextService).ConfigureAwait(false) + Documentation = await CommandHelpers.GetCommandSynopsisAsync(commandInfo, _executionService).ConfigureAwait(false) }; } @@ -201,7 +202,7 @@ await AstOperations.GetCompletionsAsync( scriptFile.ScriptAst, scriptFile.ScriptTokens, fileOffset, - _powerShellContextService, + _executionService, _logger, cts.Token).ConfigureAwait(false); } diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs index 9b1b2c48d..2fd71f452 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Utility; @@ -20,18 +21,18 @@ internal class PsesSignatureHelpHandler : SignatureHelpHandlerBase private readonly ILogger _logger; private readonly SymbolsService _symbolsService; private readonly WorkspaceService _workspaceService; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; public PsesSignatureHelpHandler( ILoggerFactory factory, SymbolsService symbolsService, WorkspaceService workspaceService, - PowerShellContextService powerShellContextService) + PowerShellExecutionService executionService) { _logger = factory.CreateLogger(); _symbolsService = symbolsService; _workspaceService = workspaceService; - _powerShellContextService = powerShellContextService; + _executionService = executionService; } protected override SignatureHelpRegistrationOptions CreateRegistrationOptions(SignatureHelpCapability capability, ClientCapabilities clientCapabilities) => new SignatureHelpRegistrationOptions @@ -55,8 +56,7 @@ public override async Task Handle(SignatureHelpParams request, Ca await _symbolsService.FindParameterSetsInFileAsync( scriptFile, request.Position.Line + 1, - request.Position.Character + 1, - _powerShellContextService).ConfigureAwait(false); + request.Position.Character + 1).ConfigureAwait(false); if (parameterSets == null) { diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index 8cf04e5de..612a830ab 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -17,6 +17,9 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Server; using OmniSharp.Extensions.LanguageServer.Protocol.Window; using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; +using System.IO; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; + namespace Microsoft.PowerShell.EditorServices.Handlers { @@ -26,7 +29,9 @@ internal class PsesConfigurationHandler : DidChangeConfigurationHandlerBase private readonly WorkspaceService _workspaceService; private readonly ConfigurationService _configurationService; private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; private readonly ILanguageServerFacade _languageServer; + private DidChangeConfigurationCapability _capability; private bool _profilesLoaded; private bool _consoleReplStarted; private bool _cwdSet; @@ -37,13 +42,16 @@ public PsesConfigurationHandler( AnalysisService analysisService, ConfigurationService configurationService, PowerShellContextService powerShellContextService, + PowerShellExecutionService executionService, ILanguageServerFacade languageServer) { _logger = factory.CreateLogger(); _workspaceService = workspaceService; _configurationService = configurationService; _powerShellContextService = powerShellContextService; + _executionService = executionService; _languageServer = languageServer; + ConfigurationUpdated += analysisService.OnConfigurationUpdated; } @@ -102,7 +110,7 @@ await _powerShellContextService.SetWorkingDirectoryAsync( && (!this._profilesLoaded || !profileLoadingPreviouslyEnabled)) { this._logger.LogTrace("Loading profiles..."); - await _powerShellContextService.LoadHostProfilesAsync().ConfigureAwait(false); + //await _executionService.LoadHostProfilesAsync().ConfigureAwait(false); this._profilesLoaded = true; this._logger.LogTrace("Loaded!"); } @@ -113,7 +121,7 @@ await _powerShellContextService.SetWorkingDirectoryAsync( { // Start the interactive terminal this._logger.LogTrace("Starting command loop"); - _powerShellContextService.ConsoleReader.StartCommandLoop(); + //_executionService.ConsoleReader.StartCommandLoop(); this._consoleReplStarted = true; } From 7e962de2a17686c078f2ae001e09edb22a88fb4e Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 23 Jun 2020 13:54:48 -0700 Subject: [PATCH 003/176] Completions working --- integrated.sln | 125 +++++++++++++ .../Server/PsesServiceCollectionExtensions.cs | 6 +- .../PowerShell/Console/ConsoleReadLine.cs | 21 +-- .../PowerShell/Console/PSReadLineProxy.cs | 31 +++- .../Host/EditorServicesConsolePSHost.cs | 18 +- ...orServicesConsolePSHostRawUserInterface.cs | 50 ++--- ...ditorServicesConsolePSHostUserInterface.cs | 7 +- .../PowerShell/PowerShellConsoleService.cs | 40 +--- .../PowerShell/PowerShellExecutionService.cs | 173 ++++++++++++++---- .../PowerShell/PowerShellStartupService.cs | 81 -------- .../EditorOperationsService.cs | 10 +- .../Handlers/GetVersionHandler.cs | 5 +- .../RemoteFileManagerService.cs | 3 - .../Services/Symbols/Vistors/AstOperations.cs | 122 ++++++------ .../Handlers/CompletionHandler.cs | 90 +++------ 15 files changed, 431 insertions(+), 351 deletions(-) create mode 100644 integrated.sln delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/PowerShellStartupService.cs diff --git a/integrated.sln b/integrated.sln new file mode 100644 index 000000000..61cad8e31 --- /dev/null +++ b/integrated.sln @@ -0,0 +1,125 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{31BB87E9-A4A1-4266-B150-CEACE7C424C4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices", "src\PowerShellEditorServices\PowerShellEditorServices.csproj", "{00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices.Hosting", "src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj", "{C6D0523A-B537-4115-ADB3-218E902684DA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices.VSCode", "src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj", "{6A33B29C-74FE-43C2-9207-CE51DC2ABF37}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Management.Automation", "..\PowerShell\src\System.Management.Automation\System.Management.Automation.csproj", "{1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSReadLine", "..\PSReadLine\PSReadLine\PSReadLine.csproj", "{DB03A789-7930-4BB2-B01E-65C2A754FC42}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine", "..\PSScriptAnalyzer\Engine\Engine.csproj", "{04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rules", "..\PSScriptAnalyzer\Rules\Rules.csproj", "{6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|x64.ActiveCfg = Debug|Any CPU + {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|x64.Build.0 = Debug|Any CPU + {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|x86.ActiveCfg = Debug|Any CPU + {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|x86.Build.0 = Debug|Any CPU + {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|Any CPU.Build.0 = Release|Any CPU + {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|x64.ActiveCfg = Release|Any CPU + {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|x64.Build.0 = Release|Any CPU + {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|x86.ActiveCfg = Release|Any CPU + {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|x86.Build.0 = Release|Any CPU + {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|x64.ActiveCfg = Debug|Any CPU + {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|x64.Build.0 = Debug|Any CPU + {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|x86.ActiveCfg = Debug|Any CPU + {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|x86.Build.0 = Debug|Any CPU + {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|Any CPU.Build.0 = Release|Any CPU + {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|x64.ActiveCfg = Release|Any CPU + {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|x64.Build.0 = Release|Any CPU + {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|x86.ActiveCfg = Release|Any CPU + {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|x86.Build.0 = Release|Any CPU + {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|x64.ActiveCfg = Debug|Any CPU + {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|x64.Build.0 = Debug|Any CPU + {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|x86.ActiveCfg = Debug|Any CPU + {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|x86.Build.0 = Debug|Any CPU + {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|Any CPU.Build.0 = Release|Any CPU + {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|x64.ActiveCfg = Release|Any CPU + {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|x64.Build.0 = Release|Any CPU + {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|x86.ActiveCfg = Release|Any CPU + {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|x86.Build.0 = Release|Any CPU + {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|x64.ActiveCfg = Debug|Any CPU + {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|x64.Build.0 = Debug|Any CPU + {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|x86.ActiveCfg = Debug|Any CPU + {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|x86.Build.0 = Debug|Any CPU + {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|Any CPU.Build.0 = Release|Any CPU + {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|x64.ActiveCfg = Release|Any CPU + {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|x64.Build.0 = Release|Any CPU + {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|x86.ActiveCfg = Release|Any CPU + {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|x86.Build.0 = Release|Any CPU + {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|x64.ActiveCfg = Debug|Any CPU + {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|x64.Build.0 = Debug|Any CPU + {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|x86.ActiveCfg = Debug|Any CPU + {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|x86.Build.0 = Debug|Any CPU + {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|Any CPU.Build.0 = Release|Any CPU + {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|x64.ActiveCfg = Release|Any CPU + {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|x64.Build.0 = Release|Any CPU + {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|x86.ActiveCfg = Release|Any CPU + {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|x86.Build.0 = Release|Any CPU + {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|x64.ActiveCfg = Debug|Any CPU + {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|x64.Build.0 = Debug|Any CPU + {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|x86.ActiveCfg = Debug|Any CPU + {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|x86.Build.0 = Debug|Any CPU + {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|Any CPU.Build.0 = Release|Any CPU + {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|x64.ActiveCfg = Release|Any CPU + {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|x64.Build.0 = Release|Any CPU + {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|x86.ActiveCfg = Release|Any CPU + {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|x86.Build.0 = Release|Any CPU + {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|x64.ActiveCfg = Debug|Any CPU + {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|x64.Build.0 = Debug|Any CPU + {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|x86.ActiveCfg = Debug|Any CPU + {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|x86.Build.0 = Debug|Any CPU + {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|Any CPU.Build.0 = Release|Any CPU + {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x64.ActiveCfg = Release|Any CPU + {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x64.Build.0 = Release|Any CPU + {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x86.ActiveCfg = Release|Any CPU + {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09} = {31BB87E9-A4A1-4266-B150-CEACE7C424C4} + {C6D0523A-B537-4115-ADB3-218E902684DA} = {31BB87E9-A4A1-4266-B150-CEACE7C424C4} + {6A33B29C-74FE-43C2-9207-CE51DC2ABF37} = {31BB87E9-A4A1-4266-B150-CEACE7C424C4} + EndGlobalSection +EndGlobal diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index 6527de6a7..5a7c449d2 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -21,12 +21,10 @@ public static IServiceCollection AddPsesLanguageServices( return collection.AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton( - (provider) => PowerShellStartupService.Create(provider.GetService(), hostStartupInfo)) .AddSingleton( - (provider) => PowerShellExecutionService.CreateAndStart(provider.GetService(), hostStartupInfo, provider.GetService())) + (provider) => PowerShellExecutionService.CreateAndStart(provider.GetService(), hostStartupInfo)) .AddSingleton( - (provider) => PowerShellConsoleService.CreateAndStart(provider.GetService(), provider.GetService(), provider.GetService())) + (provider) => PowerShellConsoleService.CreateAndStart(provider.GetService(), provider.GetService())) .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index e1397726f..cb0001452 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -22,31 +22,19 @@ internal class ConsoleReadLine { private PSReadLineProxy _psrlProxy; - private EngineIntrinsics _engineIntrinsics; - private PowerShellExecutionService _executionService; - private EditorServicesConsolePSHost _editorServicesHost; - #region Constructors #endregion #region Public Methods - public void RegisterPowerShellEngine(EditorServicesConsolePSHost editorServicesHost, EngineIntrinsics engineIntrinsics) - { - _editorServicesHost = editorServicesHost; - _engineIntrinsics = engineIntrinsics; - } - - public void RegisterExecutionService(PowerShellExecutionService executionService) + public void RegisterExecutionDependencies( + PowerShellExecutionService executionService, + PSReadLineProxy psrlProxy) { _executionService = executionService; - } - - public void RegisterPSReadLineProxy(PSReadLineProxy psrlProxy) - { _psrlProxy = psrlProxy; } @@ -156,10 +144,9 @@ private Task ReadLineAsync(bool isCommandLine, CancellationToken cancell private string InvokePSReadLine(CancellationToken cancellationToken) { - return _psrlProxy.ReadLine(_editorServicesHost.Runspace, _engineIntrinsics, cancellationToken); + return _psrlProxy.ReadLine(_executionService.EditorServicesHost.Runspace, _executionService.EngineIntrinsics, cancellationToken); } - /// /// Invokes a custom ReadLine method that is similar to but more basic than PSReadLine. /// This method should be used when PSReadLine is disabled, either by user settings or diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs index 88331e2d5..c2db8d33f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs @@ -31,6 +31,8 @@ internal class PSReadLineProxy private const string ReadKeyOverrideFieldName = "_readKeyOverride"; + private const string HandleIdleOverrideName = "_handleIdleOverride"; + private const string VirtualTerminalTypeName = "Microsoft.PowerShell.Internal.VirtualTerminal"; private const string ForcePSEventHandlingMethodName = "ForcePSEventHandling"; @@ -72,7 +74,7 @@ internal class PSReadLineProxy }}"; public static PSReadLineProxy LoadAndCreate( - ILogger logger, + ILoggerFactory loggerFactory, SMA.PowerShell pwsh) { Type psConsoleReadLineType = pwsh.AddScript(ReadLineInitScript).InvokeAndClear().FirstOrDefault(); @@ -81,15 +83,21 @@ public static PSReadLineProxy LoadAndCreate( RuntimeHelpers.RunClassConstructor(type.TypeHandle); - return new PSReadLineProxy(logger, psConsoleReadLineType); + return new PSReadLineProxy(loggerFactory, psConsoleReadLineType); } private readonly FieldInfo _readKeyOverrideField; + private readonly FieldInfo _handleIdleOverrideField; + + private readonly ILogger _logger; + public PSReadLineProxy( - ILogger logger, + ILoggerFactory loggerFactory, Type psConsoleReadLine) { + _logger = loggerFactory.CreateLogger(); + ReadLine = (Func)psConsoleReadLine.GetMethod( ReadLineMethodName, new[] { typeof(Runspace), typeof(EngineIntrinsics), typeof(CancellationToken) }) @@ -110,6 +118,8 @@ public PSReadLineProxy( s_setKeyHandlerTypes) ?.CreateDelegate(typeof(Action, string, string>)); + _handleIdleOverrideField = psConsoleReadLine.GetField(HandleIdleOverrideName, BindingFlags.Static | BindingFlags.NonPublic); + _readKeyOverrideField = psConsoleReadLine.GetTypeInfo().Assembly .GetType(VirtualTerminalTypeName) ?.GetField(ReadKeyOverrideFieldName, BindingFlags.Static | BindingFlags.NonPublic); @@ -119,7 +129,7 @@ public PSReadLineProxy( throw NewInvalidPSReadLineVersionException( FieldMemberType, ReadKeyOverrideFieldName, - logger); + _logger); } if (ReadLine == null) @@ -127,7 +137,7 @@ public PSReadLineProxy( throw NewInvalidPSReadLineVersionException( MethodMemberType, ReadLineMethodName, - logger); + _logger); } if (SetKeyHandler == null) @@ -135,7 +145,7 @@ public PSReadLineProxy( throw NewInvalidPSReadLineVersionException( MethodMemberType, SetKeyHandlerMethodName, - logger); + _logger); } if (AddToHistory == null) @@ -143,7 +153,7 @@ public PSReadLineProxy( throw NewInvalidPSReadLineVersionException( MethodMemberType, AddToHistoryMethodName, - logger); + _logger); } if (ForcePSEventHandling == null) @@ -151,7 +161,7 @@ public PSReadLineProxy( throw NewInvalidPSReadLineVersionException( MethodMemberType, ForcePSEventHandlingMethodName, - logger); + _logger); } } @@ -168,6 +178,11 @@ internal void OverrideReadKey(Func readKeyFunc) _readKeyOverrideField.SetValue(null, readKeyFunc); } + internal void OverrideIdleHandler(Action idleAction) + { + _handleIdleOverrideField.SetValue(null, idleAction); + } + private static InvalidOperationException NewInvalidPSReadLineVersionException( string memberType, string memberName, diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index 32274e5b1..614b32136 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -1,5 +1,5 @@ using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using System; using System.Globalization; using System.Management.Automation.Host; @@ -14,16 +14,17 @@ internal class EditorServicesConsolePSHost : PSHost, IHostSupportsInteractiveSes private Runspace _pushedRunspace; public EditorServicesConsolePSHost( - ILogger logger, - EditorServicesConsolePSHostUserInterface ui, + ILoggerFactory loggerFactory, string name, - Version version) + Version version, + PSHost internalHost, + ConsoleReadLine readline) { - _logger = logger; + _logger = loggerFactory.CreateLogger(); _pushedRunspace = null; Name = name; Version = version; - UI = ui; + UI = new EditorServicesConsolePSHostUserInterface(loggerFactory, readline, internalHost.UI); } public override CultureInfo CurrentCulture => CultureInfo.CurrentCulture; @@ -81,5 +82,10 @@ public override void SetShouldExit(int exitCode) PopRunspace(); } } + + public void RegisterRunspace(Runspace runspace) + { + Runspace = runspace; + } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs index 3b6fcf996..defaa0f7e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs @@ -26,11 +26,11 @@ internal class EditorServicesConsolePSHostRawUserInterface : PSHostRawUserInterf /// The ILogger implementation to use for this instance. /// The InternalHost instance from the origin runspace. public EditorServicesConsolePSHostRawUserInterface( - ILogger logger, + ILoggerFactory loggerFactory, PSHostRawUserInterface internalRawUI) { - this._logger = logger; - this._internalRawUI = internalRawUI; + _logger = loggerFactory.CreateLogger(); + _internalRawUI = internalRawUI; } #endregion @@ -60,8 +60,8 @@ public override ConsoleColor ForegroundColor /// public override Size BufferSize { - get => this._internalRawUI.BufferSize; - set => this._internalRawUI.BufferSize = value; + get => _internalRawUI.BufferSize; + set => _internalRawUI.BufferSize = value; } /// @@ -76,7 +76,7 @@ public override Coordinates CursorPosition ConsoleProxy.GetCursorTop()); } - set => this._internalRawUI.CursorPosition = value; + set => _internalRawUI.CursorPosition = value; } /// @@ -84,8 +84,8 @@ public override Coordinates CursorPosition /// public override int CursorSize { - get => this._internalRawUI.CursorSize; - set => this._internalRawUI.CursorSize = value; + get => _internalRawUI.CursorSize; + set => _internalRawUI.CursorSize = value; } /// @@ -93,8 +93,8 @@ public override int CursorSize /// public override Coordinates WindowPosition { - get => this._internalRawUI.WindowPosition; - set => this._internalRawUI.WindowPosition = value; + get => _internalRawUI.WindowPosition; + set => _internalRawUI.WindowPosition = value; } /// @@ -102,8 +102,8 @@ public override Coordinates WindowPosition /// public override Size WindowSize { - get => this._internalRawUI.WindowSize; - set => this._internalRawUI.WindowSize = value; + get => _internalRawUI.WindowSize; + set => _internalRawUI.WindowSize = value; } /// @@ -111,24 +111,24 @@ public override Size WindowSize /// public override string WindowTitle { - get => this._internalRawUI.WindowTitle; - set => this._internalRawUI.WindowTitle = value; + get => _internalRawUI.WindowTitle; + set => _internalRawUI.WindowTitle = value; } /// /// Gets a boolean that determines whether a keypress is available. /// - public override bool KeyAvailable => this._internalRawUI.KeyAvailable; + public override bool KeyAvailable => _internalRawUI.KeyAvailable; /// /// Gets the maximum physical size of the console window. /// - public override Size MaxPhysicalWindowSize => this._internalRawUI.MaxPhysicalWindowSize; + public override Size MaxPhysicalWindowSize => _internalRawUI.MaxPhysicalWindowSize; /// /// Gets the maximum size of the console window. /// - public override Size MaxWindowSize => this._internalRawUI.MaxWindowSize; + public override Size MaxWindowSize => _internalRawUI.MaxWindowSize; /// /// Reads the current key pressed in the console. @@ -141,10 +141,10 @@ public override KeyInfo ReadKey(ReadKeyOptions options) bool includeUp = (options & ReadKeyOptions.IncludeKeyUp) != 0; // Key Up was requested and we have a cached key down we can return. - if (includeUp && this._lastKeyDown != null) + if (includeUp && _lastKeyDown != null) { - KeyInfo info = this._lastKeyDown.Value; - this._lastKeyDown = null; + KeyInfo info = _lastKeyDown.Value; + _lastKeyDown = null; return new KeyInfo( info.VirtualKeyCode, info.Character, @@ -207,7 +207,7 @@ public override void FlushInputBuffer() /// A BufferCell array with the requested buffer contents. public override BufferCell[,] GetBufferContents(Rectangle rectangle) { - return this._internalRawUI.GetBufferContents(rectangle); + return _internalRawUI.GetBufferContents(rectangle); } /// @@ -223,7 +223,7 @@ public override void ScrollBufferContents( Rectangle clip, BufferCell fill) { - this._internalRawUI.ScrollBufferContents(source, destination, clip, fill); + _internalRawUI.ScrollBufferContents(source, destination, clip, fill); } /// @@ -245,7 +245,7 @@ public override void SetBufferContents( return; } - this._internalRawUI.SetBufferContents(rectangle, fill); + _internalRawUI.SetBufferContents(rectangle, fill); } /// @@ -257,7 +257,7 @@ public override void SetBufferContents( Coordinates origin, BufferCell[,] contents) { - this._internalRawUI.SetBufferContents(origin, contents); + _internalRawUI.SetBufferContents(origin, contents); } #endregion @@ -313,7 +313,7 @@ private KeyInfo ProcessKey(ConsoleKeyInfo key, bool isDown) var result = new KeyInfo((int)key.Key, key.KeyChar, states, isDown); if (isDown) { - this._lastKeyDown = result; + _lastKeyDown = result; } return result; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs index 992531b77..dda6bb8b7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs @@ -19,15 +19,14 @@ internal class EditorServicesConsolePSHostUserInterface : PSHostUserInterface private readonly PSHostUserInterface _underlyingHostUI; public EditorServicesConsolePSHostUserInterface( - ILogger logger, - EditorServicesConsolePSHostRawUserInterface rawUI, + ILoggerFactory loggerFactory, ConsoleReadLine readLine, PSHostUserInterface underlyingHostUI) { - _logger = logger; + _logger = loggerFactory.CreateLogger(); _readLine = readLine; _underlyingHostUI = underlyingHostUI; - RawUI = rawUI; + RawUI = new EditorServicesConsolePSHostRawUserInterface(loggerFactory, underlyingHostUI.RawUI); } public override PSHostRawUserInterface RawUI { get; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs index f00aab0ee..63bda25cc 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -13,16 +13,15 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell internal class PowerShellConsoleService : IDisposable { public static PowerShellConsoleService CreateAndStart( - ILogger logger, - PowerShellStartupService startupService, + ILoggerFactory loggerFactory, PowerShellExecutionService executionService) { var consoleService = new PowerShellConsoleService( - logger, + loggerFactory, executionService, - startupService.EngineIntrinsics, - startupService.EditorServicesHost, - startupService.ReadLine); + executionService.EngineIntrinsics, + executionService.EditorServicesHost, + executionService.ReadLine); return consoleService; } @@ -44,13 +43,13 @@ public static PowerShellConsoleService CreateAndStart( private CancellationTokenSource _currentCommandCancellationSource; private PowerShellConsoleService( - ILogger logger, + ILoggerFactory loggerFactory, PowerShellExecutionService executionService, EngineIntrinsics engineIntrinsics, EditorServicesConsolePSHost editorServicesHost, ConsoleReadLine readLine) { - _logger = logger; + _logger = loggerFactory.CreateLogger(); _executionService = executionService; _engineIntrinsics = engineIntrinsics; _editorServicesHost = editorServicesHost; @@ -113,30 +112,9 @@ private Task InvokePromptFunctionAsync() _currentCommandCancellationSource.Token); } - private async Task InvokeReadLineAsync() + private Task InvokeReadLineAsync() { - string input = null; - while (string.IsNullOrEmpty(input)) - { - try - { - input = await InvokePSReadLineAsync(timeoutMillis: 30); - } - catch (OperationCanceledException) - { - continue; - } - } - - return input; - } - - private Task InvokePSReadLineAsync(int timeoutMillis) - { - var readlineCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_currentCommandCancellationSource.Token); - readlineCancellationSource.CancelAfter(timeoutMillis); - - return _readLine.ReadCommandLineAsync(readlineCancellationSource.Token); + return _readLine.ReadCommandLineAsync(_currentCommandCancellationSource.Token); } private Task InvokeInputAsync(string input) diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index d99527d00..a9b62df2c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -1,11 +1,16 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Management.Automation; +using System.Management.Automation.Host; using System.Management.Automation.Runspaces; using System.Reflection; using System.Threading; @@ -21,52 +26,79 @@ internal class PowerShellExecutionService : IDisposable Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "../../Commands/PowerShellEditorServices.Commands.psd1")); - private readonly SMA.PowerShell _pwsh; + private static readonly PropertyInfo s_shouldProcessInExecutionThreadProperty = + typeof(PSEventSubscriber) + .GetProperty( + "ShouldProcessInExecutionThread", + BindingFlags.Instance | BindingFlags.NonPublic); + + + public static PowerShellExecutionService CreateAndStart( + ILoggerFactory loggerFactory, + HostStartupInfo hostInfo) + { + var executionService = new PowerShellExecutionService( + loggerFactory, + hostInfo.Name, + hostInfo.Version, + hostInfo.LanguageMode, + hostInfo.PSHost, + hostInfo.AdditionalModules); + + executionService.Start(); + + return executionService; + } private readonly CancellationTokenSource _stopThreadCancellationSource; private readonly BlockingCollection _executionQueue; - private Thread _pipelineThread; + private readonly ILoggerFactory _loggerFactory; - private CancellationTokenSource _currentExecutionCancellationSource; + private readonly ILogger _logger; - private ILogger _logger; + private readonly string _hostName; - public static PowerShellExecutionService CreateAndStart( - ILogger logger, - HostStartupInfo hostInfo, - PowerShellStartupService startupService) - { - var executionService = new PowerShellExecutionService(logger, startupService.PowerShell); + private readonly Version _hostVersion; - executionService.Start(); + private readonly PSLanguageMode _languageMode; - startupService.ReadLine.RegisterExecutionService(executionService); + private readonly PSHost _internalHost; - executionService.EnqueueModuleImport(s_commandsModulePath); + private readonly IReadOnlyList _additionalModulesToLoad; - if (hostInfo.AdditionalModules != null && hostInfo.AdditionalModules.Count > 0) - { - foreach (string module in hostInfo.AdditionalModules) - { - executionService.EnqueueModuleImport(module); - } - } + private Thread _pipelineThread; - return executionService; - } + private CancellationTokenSource _currentExecutionCancellationSource; + + private SMA.PowerShell _pwsh; private PowerShellExecutionService( - ILogger logger, - SMA.PowerShell pwsh) + ILoggerFactory loggerFactory, + string hostName, + Version hostVersion, + PSLanguageMode languageMode, + PSHost internalHost, + IReadOnlyList additionalModules) { - _logger = logger; - _pwsh = pwsh; + _loggerFactory = loggerFactory; + _logger = loggerFactory.CreateLogger(); _stopThreadCancellationSource = new CancellationTokenSource(); _executionQueue = new BlockingCollection(); + _hostName = hostName; + _hostVersion = hostVersion; + _languageMode = languageMode; + _internalHost = internalHost; + _additionalModulesToLoad = additionalModules; } + public EngineIntrinsics EngineIntrinsics { get; private set; } + + public EditorServicesConsolePSHost EditorServicesHost { get; private set; } + + public ConsoleReadLine ReadLine { get; private set; } + public Task ExecuteDelegateAsync(Func func, CancellationToken cancellationToken) { TResult appliedFunc(CancellationToken cancellationToken) => func(_pwsh, cancellationToken); @@ -137,20 +169,46 @@ private void Start() _pipelineThread.Start(); } - private void RunConsumerLoop() + private void Initialize() { + _pwsh = SMA.PowerShell.Create(); + + ReadLine = new ConsoleReadLine(); + + EditorServicesHost = new EditorServicesConsolePSHost(_loggerFactory, _hostName, _hostVersion, _internalHost, ReadLine); + + _pwsh.Runspace = CreateRunspace(EditorServicesHost, _languageMode); Runspace.DefaultRunspace = _pwsh.Runspace; + EditorServicesHost.RegisterRunspace(_pwsh.Runspace); + + var engineIntrinsics = (EngineIntrinsics)_pwsh.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); + + var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, _pwsh); + psrlProxy.OverrideIdleHandler(HandlePowerShellOnIdle); + ReadLine.RegisterExecutionDependencies(this, psrlProxy); + + EnqueueModuleImport(s_commandsModulePath); + + if (_additionalModulesToLoad != null && _additionalModulesToLoad.Count > 0) + { + foreach (string module in _additionalModulesToLoad) + { + EnqueueModuleImport(module); + } + } + + Runspace.DefaultRunspace = _pwsh.Runspace; + } + + private void RunConsumerLoop() + { + Initialize(); try { foreach (ISynchronousTask synchronousTask in _executionQueue.GetConsumingEnumerable()) { - if (synchronousTask.IsCanceled) - { - continue; - } - - synchronousTask.ExecuteSynchronously(ref _currentExecutionCancellationSource, _stopThreadCancellationSource.Token); + RunTaskSynchronously(synchronousTask); } } catch (OperationCanceledException) @@ -159,6 +217,26 @@ private void RunConsumerLoop() } } + private void HandlePowerShellOnIdle() + { + while (_pwsh.InvocationStateInfo.State == PSInvocationState.Completed + && _executionQueue.TryTake(out ISynchronousTask task)) + { + RunTaskSynchronously(task); + } + + // TODO: Run nested pipeline here for engine event handling + } + + private void RunTaskSynchronously(ISynchronousTask task) + { + if (task.IsCanceled) + { + return; + } + + task.ExecuteSynchronously(ref _currentExecutionCancellationSource, _stopThreadCancellationSource.Token); + } private void EnqueueModuleImport(string moduleNameOrPath) { @@ -168,5 +246,34 @@ private void EnqueueModuleImport(string moduleNameOrPath) ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None); } + + private static void SetSubscriberExecutionThreadWithReflection(PSEventSubscriber subscriber) + { + // We need to create the PowerShell object in the same thread so we can get a nested + // PowerShell. This is the only way to consistently take control of the pipeline. The + // alternative is to make the subscriber a script block and have that create and process + // the PowerShell object, but that puts us in a different SessionState and is a lot slower. + s_shouldProcessInExecutionThreadProperty.SetValue(subscriber, true); + } + + private static Runspace CreateRunspace( + PSHost psHost, + PSLanguageMode languageMode) + { + InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" + ? InitialSessionState.CreateDefault() + : InitialSessionState.CreateDefault2(); + + iss.LanguageMode = languageMode; + + Runspace runspace = RunspaceFactory.CreateRunspace(psHost, iss); + + runspace.SetApartmentStateToSta(); + runspace.ThreadOptions = PSThreadOptions.ReuseThread; + + runspace.Open(); + + return runspace; + } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellStartupService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellStartupService.cs deleted file mode 100644 index b9cacbbb7..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellStartupService.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Hosting; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; -using System; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; -using SMA = System.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell -{ - internal class PowerShellStartupService - { - public static PowerShellStartupService Create( - ILogger logger, - HostStartupInfo hostStartupInfo) - { - var pwsh = SMA.PowerShell.Create(); - - var readLine = new ConsoleReadLine(); - - var rawUI = new EditorServicesConsolePSHostRawUserInterface(logger, hostStartupInfo.PSHost.UI.RawUI); - - var ui = new EditorServicesConsolePSHostUserInterface(logger, rawUI, readLine, hostStartupInfo.PSHost.UI); - - var psHost = new EditorServicesConsolePSHost(logger, ui, hostStartupInfo.Name, hostStartupInfo.Version); - - pwsh.Runspace = CreateRunspace(psHost, hostStartupInfo.LanguageMode); - Runspace.DefaultRunspace = pwsh.Runspace; - - var engineIntrinsics = (EngineIntrinsics)pwsh.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); - - readLine.RegisterPSReadLineProxy(PSReadLineProxy.LoadAndCreate(logger, pwsh)); - readLine.RegisterPowerShellEngine(psHost, engineIntrinsics); - - return new PowerShellStartupService(pwsh, engineIntrinsics, psHost, readLine); - } - - private PowerShellStartupService( - SMA.PowerShell pwsh, - EngineIntrinsics engineIntrinsics, - EditorServicesConsolePSHost editorServicesHost, - ConsoleReadLine readLine) - { - PowerShell = pwsh; - EngineIntrinsics = engineIntrinsics; - EditorServicesHost = editorServicesHost; - ReadLine = readLine; - } - - public SMA.PowerShell PowerShell { get; } - - public EngineIntrinsics EngineIntrinsics { get; } - - public EditorServicesConsolePSHost EditorServicesHost { get; } - - public ConsoleReadLine ReadLine { get; } - - private static Runspace CreateRunspace( - PSHost psHost, - PSLanguageMode languageMode) - { - InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" - ? InitialSessionState.CreateDefault() - : InitialSessionState.CreateDefault2(); - - iss.LanguageMode = languageMode; - - Runspace runspace = RunspaceFactory.CreateRunspace(psHost, iss); - - runspace.SetApartmentStateToSta(); - runspace.ThreadOptions = PSThreadOptions.ReuseThread; - - runspace.Open(); - - return runspace; - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs index 66e490156..c2922e182 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs @@ -17,16 +17,18 @@ internal class EditorOperationsService : IEditorOperations private const bool DefaultPreviewSetting = true; private readonly WorkspaceService _workspaceService; - private readonly PowerShellStartupService _startupService; + private readonly ILanguageServerFacade _languageServer; + private readonly PowerShellExecutionService _executionService; + public EditorOperationsService( WorkspaceService workspaceService, - PowerShellStartupService startupService, + PowerShellExecutionService executionService, ILanguageServerFacade languageServer) { _workspaceService = workspaceService; - _startupService = startupService; + _executionService = executionService; _languageServer = languageServer; } @@ -273,7 +275,7 @@ private bool TestHasLanguageServer(bool warnUser = true) if (warnUser) { - _startupService.EditorServicesHost.UI.WriteWarningLine( + _executionService.EditorServicesHost.UI.WriteWarningLine( "Editor operations are not supported in temporary consoles. Re-run the command in the main PowerShell Intergrated Console."); } diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetVersionHandler.cs index cd976ba8a..44f541b18 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetVersionHandler.cs @@ -24,20 +24,17 @@ internal class GetVersionHandler : IGetVersionHandler private readonly ILogger _logger; private readonly PowerShellExecutionService _executionService; - private readonly PowerShellStartupService _startupService; private readonly ILanguageServerFacade _languageServer; private readonly ConfigurationService _configurationService; public GetVersionHandler( ILoggerFactory factory, PowerShellExecutionService executionService, - PowerShellStartupService startupService, ILanguageServerFacade languageServer, ConfigurationService configurationService) { _logger = factory.CreateLogger(); _executionService = executionService; - _startupService = startupService; _languageServer = languageServer; _configurationService = configurationService; } @@ -93,7 +90,7 @@ private async Task CheckPackageManagement() _logger.LogDebug("Old version of PackageManagement detected."); - if (_startupService.EditorServicesHost.Runspace.SessionStateProxy.LanguageMode != PSLanguageMode.FullLanguage) + if (_executionService.EditorServicesHost.Runspace.SessionStateProxy.LanguageMode != PSLanguageMode.FullLanguage) { _languageServer.Window.ShowWarning("You have an older version of PackageManagement known to cause issues with the PowerShell extension. Please run the following command in a new Windows PowerShell session and then restart the PowerShell extension: `Install-Module PackageManagement -Force -AllowClobber -MinimumVersion 1.4.6`"); return; diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs index 6c3d97856..887cc2351 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs @@ -32,7 +32,6 @@ internal class RemoteFileManagerService private ILogger logger; private string remoteFilesPath; private string processTempPath; - private readonly PowerShellStartupService _startupService; private readonly PowerShellExecutionService _executionService; private IEditorOperations editorOperations; @@ -251,13 +250,11 @@ function New-EditorFile { /// public RemoteFileManagerService( ILoggerFactory factory, - PowerShellStartupService startupService, PowerShellExecutionService executionService, EditorOperationsService editorOperations) { this.logger = factory.CreateLogger(); _executionService = executionService; - _startupService = startupService; //this.powerShellContext.RunspaceChanged += HandleRunspaceChangedAsync; this.editorOperations = editorOperations; diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index 4f1ad1038..b84c4612f 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -27,8 +27,6 @@ internal static class AstOperations .GetType("System.Management.Automation.Language.InternalScriptPosition") .GetMethod("CloneWithNewOffset", BindingFlags.Instance | BindingFlags.NonPublic); - private static readonly SemaphoreSlim s_completionHandle = AsyncUtils.CreateSimpleLockingSemaphore(); - // TODO: BRING THIS BACK /// /// Gets completions for the symbol found in the Ast at @@ -62,83 +60,71 @@ public static async Task GetCompletionsAsync( ILogger logger, CancellationToken cancellationToken) { - if (!s_completionHandle.Wait(0, CancellationToken.None)) + IScriptPosition cursorPosition = (IScriptPosition)s_extentCloneWithNewOffset.Invoke( + scriptAst.Extent.StartScriptPosition, + new object[] { fileOffset }); + + logger.LogTrace( + string.Format( + "Getting completions at offset {0} (line: {1}, column: {2})", + fileOffset, + cursorPosition.LineNumber, + cursorPosition.ColumnNumber)); + + /* + if (!powerShellContext.IsAvailable) { return null; } + */ - try - { - IScriptPosition cursorPosition = (IScriptPosition)s_extentCloneWithNewOffset.Invoke( - scriptAst.Extent.StartScriptPosition, - new object[] { fileOffset }); - - logger.LogTrace( - string.Format( - "Getting completions at offset {0} (line: {1}, column: {2})", - fileOffset, - cursorPosition.LineNumber, - cursorPosition.ColumnNumber)); + var stopwatch = new Stopwatch(); - /* - if (!powerShellContext.IsAvailable) - { - return null; - } - */ - - var stopwatch = new Stopwatch(); - - // If the current runspace is out of process we can use - // CommandCompletion.CompleteInput because PSReadLine won't be taking up the - // main runspace. - /* - if (powerShellContext.IsCurrentRunspaceOutOfProcess()) + // If the current runspace is out of process we can use + // CommandCompletion.CompleteInput because PSReadLine won't be taking up the + // main runspace. + /* + if (powerShellContext.IsCurrentRunspaceOutOfProcess()) + { + using (RunspaceHandle runspaceHandle = await powerShellContext.GetRunspaceHandleAsync(cancellationToken).ConfigureAwait(false)) + using (System.Management.Automation.PowerShell powerShell = System.Management.Automation.PowerShell.Create()) { - using (RunspaceHandle runspaceHandle = await powerShellContext.GetRunspaceHandleAsync(cancellationToken).ConfigureAwait(false)) - using (System.Management.Automation.PowerShell powerShell = System.Management.Automation.PowerShell.Create()) + powerShell.Runspace = runspaceHandle.Runspace; + stopwatch.Start(); + try { - powerShell.Runspace = runspaceHandle.Runspace; - stopwatch.Start(); - try - { - return CommandCompletion.CompleteInput( - scriptAst, - currentTokens, - cursorPosition, - options: null, - powershell: powerShell); - } - finally - { - stopwatch.Stop(); - logger.LogTrace($"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); - } + return CommandCompletion.CompleteInput( + scriptAst, + currentTokens, + cursorPosition, + options: null, + powershell: powerShell); + } + finally + { + stopwatch.Stop(); + logger.LogTrace($"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); } } - */ - - CommandCompletion commandCompletion = null; - await executionService.ExecuteDelegateAsync((pwsh, cancellationToken) => - { - stopwatch.Start(); - commandCompletion = CommandCompletion.CompleteInput( - scriptAst, - currentTokens, - cursorPosition, - options: null, - powershell: pwsh); - }, cancellationToken); - - stopwatch.Stop(); - logger.LogTrace($"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); - - return commandCompletion; } - finally + */ + + CommandCompletion commandCompletion = null; + await executionService.ExecuteDelegateAsync((pwsh, cancellationToken) => { - s_completionHandle.Release(); - } + stopwatch.Start(); + commandCompletion = CommandCompletion.CompleteInput( + scriptAst, + currentTokens, + cursorPosition, + options: null, + powershell: pwsh); + }, cancellationToken); + + stopwatch.Stop(); + logger.LogTrace($"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); + + return commandCompletion; } /// diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs index b325abd00..e110d89eb 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs @@ -24,8 +24,6 @@ namespace Microsoft.PowerShell.EditorServices.Handlers internal class PsesCompletionHandler : ICompletionHandler, ICompletionResolveHandler { const int DefaultWaitTimeoutMilliseconds = 5000; - private readonly SemaphoreSlim _completionLock = AsyncUtils.CreateSimpleLockingSemaphore(); - private readonly SemaphoreSlim _completionResolveLock = AsyncUtils.CreateSimpleLockingSemaphore(); private readonly ILogger _logger; private readonly PowerShellExecutionService _executionService; private readonly WorkspaceService _workspaceService; @@ -61,47 +59,30 @@ public async Task Handle(CompletionParams request, CancellationT ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri); - try - { - await _completionLock.WaitAsync(cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) + if (cancellationToken.IsCancellationRequested) { _logger.LogDebug("Completion request canceled for file: {0}", request.TextDocument.Uri); return Array.Empty(); } - try - { - if (cancellationToken.IsCancellationRequested) - { - _logger.LogDebug("Completion request canceled for file: {0}", request.TextDocument.Uri); - return Array.Empty(); - } - - CompletionResults completionResults = - await GetCompletionsInFileAsync( - scriptFile, - cursorLine, - cursorColumn).ConfigureAwait(false); - - if (completionResults == null) - { - return Array.Empty(); - } + CompletionResults completionResults = + await GetCompletionsInFileAsync( + scriptFile, + cursorLine, + cursorColumn).ConfigureAwait(false); - CompletionItem[] completionItems = new CompletionItem[completionResults.Completions.Length]; - for (int i = 0; i < completionItems.Length; i++) - { - completionItems[i] = CreateCompletionItem(completionResults.Completions[i], completionResults.ReplacedRange, i + 1); - } - - return completionItems; + if (completionResults == null) + { + return Array.Empty(); } - finally + + CompletionItem[] completionItems = new CompletionItem[completionResults.Completions.Length]; + for (int i = 0; i < completionItems.Length; i++) { - _completionLock.Release(); + completionItems[i] = CreateCompletionItem(completionResults.Completions[i], completionResults.ReplacedRange, i + 1); } + + return completionItems; } public static bool CanResolve(CompletionItem value) @@ -124,39 +105,22 @@ public async Task Handle(CompletionItem request, CancellationTok return request; } - try - { - await _completionResolveLock.WaitAsync(cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - _logger.LogDebug("CompletionItemResolve request canceled for item: {0}", request.Label); - return request; - } + // Get the documentation for the function + CommandInfo commandInfo = + await CommandHelpers.GetCommandInfoAsync( + request.Label, + _executionService).ConfigureAwait(false); - try + if (commandInfo != null) { - // Get the documentation for the function - CommandInfo commandInfo = - await CommandHelpers.GetCommandInfoAsync( - request.Label, - _executionService).ConfigureAwait(false); - - if (commandInfo != null) + request = request with { - request = request with - { - Documentation = await CommandHelpers.GetCommandSynopsisAsync(commandInfo, _executionService).ConfigureAwait(false) - }; - } - - // Send back the updated CompletionItem - return request; - } - finally - { - _completionResolveLock.Release(); + Documentation = await CommandHelpers.GetCommandSynopsisAsync(commandInfo, _executionService).ConfigureAwait(false) + }; } + + // Send back the updated CompletionItem + return request; } public void SetCapability(CompletionCapability capability, ClientCapabilities clientCapabilities) From 2eab92dc4d93e80b621707f6d66275bb827379cb Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 23 Jun 2020 14:18:06 -0700 Subject: [PATCH 004/176] Convert MethodInfo to compiled delegate call --- .../Services/Symbols/Vistors/AstOperations.cs | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index b84c4612f..b5d236ee0 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; +using System.Linq.Expressions; using System.Management.Automation; using System.Management.Automation.Language; using System.Reflection; @@ -21,13 +23,26 @@ namespace Microsoft.PowerShell.EditorServices.Services.Symbols /// internal static class AstOperations { - // TODO: When netstandard is upgraded to 2.0, see if - // Delegate.CreateDelegate can be used here instead - private static readonly MethodInfo s_extentCloneWithNewOffset = typeof(PSObject).Assembly - .GetType("System.Management.Automation.Language.InternalScriptPosition") - .GetMethod("CloneWithNewOffset", BindingFlags.Instance | BindingFlags.NonPublic); + private static readonly Func s_clonePositionWithNewOffset; + static AstOperations() + { + Type internalScriptPositionType = typeof(PSObject).GetTypeInfo().Assembly + .GetType("System.Management.Automation.Language.InternalScriptPosition"); + + MethodInfo cloneWithNewOffsetMethod = internalScriptPositionType.GetMethod("CloneWithNewOffset", BindingFlags.Instance | BindingFlags.NonPublic); + + ParameterExpression originalPosition = Expression.Parameter(typeof(IScriptPosition)); + ParameterExpression newOffset = Expression.Parameter(typeof(int)); + + var parameters = new ParameterExpression[] { originalPosition, newOffset }; + s_clonePositionWithNewOffset = Expression.Lambda>( + Expression.Call( + Expression.Convert(originalPosition, internalScriptPositionType), + cloneWithNewOffsetMethod, + newOffset), + parameters).Compile(); + } - // TODO: BRING THIS BACK /// /// Gets completions for the symbol found in the Ast at /// the given file offset. @@ -60,9 +75,7 @@ public static async Task GetCompletionsAsync( ILogger logger, CancellationToken cancellationToken) { - IScriptPosition cursorPosition = (IScriptPosition)s_extentCloneWithNewOffset.Invoke( - scriptAst.Extent.StartScriptPosition, - new object[] { fileOffset }); + IScriptPosition cursorPosition = s_clonePositionWithNewOffset(scriptAst.Extent.StartScriptPosition, fileOffset); logger.LogTrace( string.Format( From 0534a341731ff297682823c8ed0c9c88c8f836f6 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 23 Jun 2020 21:34:56 -0700 Subject: [PATCH 005/176] Continue tweaking REPL --- .../PowerShell/Console/ConsoleReadLine.cs | 2 +- .../Execution/PowerShellExecutionOptions.cs | 10 +-- .../Execution/SynchronousDelegateTask.cs | 18 +++++ .../Execution/SynchronousPowerShellTask.cs | 39 +++++++--- .../PowerShell/Execution/SynchronousTask.cs | 6 +- .../PowerShell/PowerShellConsoleService.cs | 42 ++++++++--- .../PowerShell/PowerShellExecutionService.cs | 73 ++++++++++++------- .../PowerShellContext/ExtensionService.cs | 2 +- .../Handlers/EvaluateHandler.cs | 8 +- .../Services/Symbols/Vistors/AstOperations.cs | 2 +- 10 files changed, 139 insertions(+), 63 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index cb0001452..abc2be9b0 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -139,7 +139,7 @@ private static Task ReadKeyAsync(CancellationToken cancellationT private Task ReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) { - return _executionService.ExecuteDelegateAsync(InvokePSReadLine, cancellationToken); + return _executionService.ExecuteDelegateAsync(InvokePSReadLine, representation: "ReadLine", cancellationToken); } private string InvokePSReadLine(CancellationToken cancellationToken) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs index 90ff630ee..011c861fe 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs @@ -14,16 +14,10 @@ public struct PowerShellExecutionOptions public bool AddToHistory { get; set; } - public bool InterruptCommandPrompt { get; set; } - public bool WriteInputToHost { get; set; } - public string InputStringToDisplay { get; set; } - - public bool UseNewScope { get; set; } + public bool PropagateCancellationToCaller { get; set; } - internal bool IsReadLine { get; set; } - - internal bool ShouldExecuteInOriginalRunspace { get; set; } + public bool InterruptCommandPrompt { get; set; } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs index b17660e58..d6d61be6a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs @@ -8,13 +8,17 @@ internal class SynchronousDelegateTask : SynchronousTask { private readonly Action _action; + private readonly string _representation; + public SynchronousDelegateTask( ILogger logger, Action action, + string representation, CancellationToken cancellationToken) : base(logger, cancellationToken) { _action = action; + _representation = representation; } public override object Run(CancellationToken cancellationToken) @@ -22,24 +26,38 @@ public override object Run(CancellationToken cancellationToken) _action(cancellationToken); return null; } + + public override string ToString() + { + return _representation; + } } internal class SynchronousDelegateTask : SynchronousTask { private readonly Func _func; + private readonly string _representation; + public SynchronousDelegateTask( ILogger logger, Func func, + string representation, CancellationToken cancellationToken) : base(logger, cancellationToken) { _func = func; + _representation = representation; } public override TResult Run(CancellationToken cancellationToken) { return _func(cancellationToken); } + + public override string ToString() + { + return _representation; + } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 55a1a484e..4012b8bb5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -3,6 +3,7 @@ using System; using System.Collections.ObjectModel; using System.Management.Automation; +using System.Management.Automation.Host; using System.Management.Automation.Remoting; using System.Threading; using SMA = System.Management.Automation; @@ -15,17 +16,21 @@ internal class SynchronousPowerShellTask : SynchronousTask Run(CancellationToken cancellationToken) _pwsh.Commands = _psCommand; + if (_executionOptions.WriteInputToHost) + { + _psHost.UI.WriteLine(_psCommand.GetInvocationText()); + } + if (_executionOptions.WriteOutputToHost) { _pwsh.AddOutputCommand(); @@ -45,6 +55,11 @@ public override Collection Run(CancellationToken cancellationToken) try { result = _pwsh.InvokeAndClear(); + + if (_executionOptions.PropagateCancellationToCaller) + { + cancellationToken.ThrowIfCancellationRequested(); + } } catch (Exception e) when (e is PipelineStoppedException || e is PSRemotingDataStructureException) { @@ -56,25 +71,31 @@ public override Collection Run(CancellationToken cancellationToken) { Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}"); - if (_executionOptions.WriteErrorsToHost) + if (!_executionOptions.WriteErrorsToHost) { - _pwsh.AddOutputCommand() - .AddParameter("InputObject", e.ErrorRecord.AsPSObject()) - .InvokeAndClear(); + throw; } - throw; + _pwsh.AddOutputCommand() + .AddParameter("InputObject", e.ErrorRecord.AsPSObject()) + .InvokeAndClear(); } - - - if (_pwsh.HadErrors) + finally { - _pwsh.Streams.Error.Clear(); + if (_pwsh.HadErrors) + { + _pwsh.Streams.Error.Clear(); + } } return result; } + public override string ToString() + { + return _psCommand.GetInvocationText(); + } + private void Cancel() { _pwsh.Stop(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs index 264c753df..ddba731b2 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs @@ -36,6 +36,8 @@ protected SynchronousTask(ILogger logger, CancellationToken cancellationToken) public abstract TResult Run(CancellationToken cancellationToken); + public abstract override string ToString(); + public void ExecuteSynchronously(ref CancellationTokenSource cancellationSource, CancellationToken threadCancellation) { if (_taskCancellationToken.IsCancellationRequested || threadCancellation.IsCancellationRequested) @@ -47,7 +49,9 @@ public void ExecuteSynchronously(ref CancellationTokenSource cancellationSource, cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_taskCancellationToken, threadCancellation); try { - _taskCompletionSource.SetResult(Run(cancellationSource.Token)); + TResult result = Run(cancellationSource.Token); + + _taskCompletionSource.SetResult(result); } catch (OperationCanceledException) { diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs index 63bda25cc..a41825f7f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -21,7 +21,8 @@ public static PowerShellConsoleService CreateAndStart( executionService, executionService.EngineIntrinsics, executionService.EditorServicesHost, - executionService.ReadLine); + executionService.ReadLine, + executionService.PSReadLineProxy); return consoleService; } @@ -36,6 +37,8 @@ public static PowerShellConsoleService CreateAndStart( private readonly ConsoleReadLine _readLine; + private readonly PSReadLineProxy _psrlProxy; + private Task _consoleLoopThread; private CancellationTokenSource _replLoopCancellationSource; @@ -47,13 +50,15 @@ private PowerShellConsoleService( PowerShellExecutionService executionService, EngineIntrinsics engineIntrinsics, EditorServicesConsolePSHost editorServicesHost, - ConsoleReadLine readLine) + ConsoleReadLine readLine, + PSReadLineProxy psrlProxy) { _logger = loggerFactory.CreateLogger(); _executionService = executionService; _engineIntrinsics = engineIntrinsics; _editorServicesHost = editorServicesHost; _readLine = readLine; + _psrlProxy = psrlProxy; } public void Dispose() @@ -66,7 +71,14 @@ public void StartRepl() _replLoopCancellationSource = new CancellationTokenSource(); System.Console.CancelKeyPress += HandleConsoleCancellation; System.Console.OutputEncoding = Encoding.UTF8; + _psrlProxy.OverrideReadKey(ReadKey); _consoleLoopThread = Task.Run(RunReplLoopAsync, _replLoopCancellationSource.Token); + _executionService.RegisterConsoleService(this); + } + + public void CancelCurrentPrompt() + { + _currentCommandCancellationSource?.Cancel(); } private async Task RunReplLoopAsync() @@ -78,16 +90,8 @@ private async Task RunReplLoopAsync() try { await InvokePromptFunctionAsync().ConfigureAwait(false); - } - catch (OperationCanceledException) - { - break; - } - try - { - // Poll for user input here so that the prompt does not block - string userInput = await InvokeReadLineAsync(); + string userInput = await InvokeReadLineAsync().ConfigureAwait(false); await InvokeInputAsync(userInput).ConfigureAwait(false); } @@ -95,12 +99,20 @@ private async Task RunReplLoopAsync() { continue; } + catch (Exception e) + { + + } } } private Task InvokePromptFunctionAsync() { - var promptCommand = new PSCommand().AddCommand("prompt"); + var promptCommand = new PSCommand() + .AddCommand("prompt") + .AddCommand("Write-Host") + .AddParameter("NoNewline"); + var executionOptions = new PowerShellExecutionOptions { WriteOutputToHost = true, @@ -131,6 +143,12 @@ private Task InvokeInputAsync(string input) private void HandleConsoleCancellation(object sender, ConsoleCancelEventArgs args) { + _currentCommandCancellationSource.Cancel(); + } + + private ConsoleKeyInfo ReadKey(bool intercept) + { + return ConsoleProxy.SafeReadKey(intercept, _currentCommandCancellationSource?.Token ?? CancellationToken.None); } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index a9b62df2c..0da9b0e0a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -74,6 +74,8 @@ public static PowerShellExecutionService CreateAndStart( private SMA.PowerShell _pwsh; + private PowerShellConsoleService _consoleService; + private PowerShellExecutionService( ILoggerFactory loggerFactory, string hostName, @@ -97,32 +99,42 @@ private PowerShellExecutionService( public EditorServicesConsolePSHost EditorServicesHost { get; private set; } + public PSReadLineProxy PSReadLineProxy { get; private set; } + public ConsoleReadLine ReadLine { get; private set; } - public Task ExecuteDelegateAsync(Func func, CancellationToken cancellationToken) + public Task ExecuteDelegateAsync( + Func func, + string representation, + CancellationToken cancellationToken) { TResult appliedFunc(CancellationToken cancellationToken) => func(_pwsh, cancellationToken); - return ExecuteDelegateAsync(appliedFunc, cancellationToken); + return ExecuteDelegateAsync(appliedFunc, representation, cancellationToken); } - public Task ExecuteDelegateAsync(Action action, CancellationToken cancellationToken) + public Task ExecuteDelegateAsync( + Action action, + string representation, + CancellationToken cancellationToken) { void appliedAction(CancellationToken cancellationToken) => action(_pwsh, cancellationToken); - return ExecuteDelegateAsync(appliedAction, cancellationToken); + return ExecuteDelegateAsync(appliedAction, representation, cancellationToken); } - public Task ExecuteDelegateAsync(Func func, CancellationToken cancellationToken) + public Task ExecuteDelegateAsync( + Func func, + string representation, + CancellationToken cancellationToken) { - var delegateTask = new SynchronousDelegateTask(_logger, func, cancellationToken); - _executionQueue.Add(delegateTask); - return delegateTask.Task; + return QueueTask(new SynchronousDelegateTask(_logger, func, representation, cancellationToken)); } - public Task ExecuteDelegateAsync(Action action, CancellationToken cancellationToken) + public Task ExecuteDelegateAsync( + Action action, + string representation, + CancellationToken cancellationToken) { - var delegateTask = new SynchronousDelegateTask(_logger, action, cancellationToken); - _executionQueue.Add(delegateTask); - return delegateTask.Task; + return QueueTask(new SynchronousDelegateTask(_logger, action, representation, cancellationToken)); } public Task> ExecutePSCommandAsync( @@ -130,9 +142,14 @@ public Task> ExecutePSCommandAsync( PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) { - var psTask = new SynchronousPowerShellTask(_logger, _pwsh, psCommand, executionOptions, cancellationToken); - _executionQueue.Add(psTask); - return psTask.Task; + Task> result = QueueTask(new SynchronousPowerShellTask(_logger, _pwsh, EditorServicesHost, psCommand, executionOptions, cancellationToken)); + + if (executionOptions.InterruptCommandPrompt) + { + _consoleService?.CancelCurrentPrompt(); + } + + return result; } public Task ExecutePSCommandAsync( @@ -154,12 +171,23 @@ public void CancelCurrentTask() } } + public void RegisterConsoleService(PowerShellConsoleService consoleService) + { + _consoleService = consoleService; + } + public void Dispose() { Stop(); _pwsh.Dispose(); } + private Task QueueTask(SynchronousTask task) + { + _executionQueue.Add(task); + return task.Task; + } + private void Start() { _pipelineThread = new Thread(RunConsumerLoop) @@ -183,9 +211,9 @@ private void Initialize() var engineIntrinsics = (EngineIntrinsics)_pwsh.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); - var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, _pwsh); - psrlProxy.OverrideIdleHandler(HandlePowerShellOnIdle); - ReadLine.RegisterExecutionDependencies(this, psrlProxy); + PSReadLineProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, _pwsh); + PSReadLineProxy.OverrideIdleHandler(HandlePowerShellOnIdle); + ReadLine.RegisterExecutionDependencies(this, PSReadLineProxy); EnqueueModuleImport(s_commandsModulePath); @@ -247,15 +275,6 @@ private void EnqueueModuleImport(string moduleNameOrPath) ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None); } - private static void SetSubscriberExecutionThreadWithReflection(PSEventSubscriber subscriber) - { - // We need to create the PowerShell object in the same thread so we can get a nested - // PowerShell. This is the only way to consistently take control of the pipeline. The - // alternative is to make the subscriber a script block and have that create and process - // the PowerShell object, but that puts us in a different SessionState and is a lot slower. - s_shouldProcessInExecutionThreadProperty.SetValue(subscriber, true); - } - private static Runspace CreateRunspace( PSHost psHost, PSLanguageMode languageMode) diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs index 13d9296e8..ac9fa7d59 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs @@ -96,7 +96,7 @@ internal async Task InitializeAsync( await ExecutionService.ExecuteDelegateAsync((pwsh, cancellationToken) => { pwsh.Runspace.SessionStateProxy.PSVariable.Set("psEditor", EditorObject); - }, CancellationToken.None); + }, representation: "Set PSEditor", CancellationToken.None); } /// diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/EvaluateHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/EvaluateHandler.cs index 2c6925fb2..457d1e8ae 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/EvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/EvaluateHandler.cs @@ -15,7 +15,9 @@ internal class EvaluateHandler : IEvaluateHandler private readonly ILogger _logger; private readonly PowerShellExecutionService _executionService; - public EvaluateHandler(ILoggerFactory factory, PowerShellExecutionService executionService) + public EvaluateHandler( + ILoggerFactory factory, + PowerShellExecutionService executionService) { _logger = factory.CreateLogger(); _executionService = executionService; @@ -25,8 +27,8 @@ public Task Handle(EvaluateRequestArguments request, Cance { _executionService.ExecutePSCommandAsync( new PSCommand().AddScript(request.Expression), - new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true }, - cancellationToken); + new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true, InterruptCommandPrompt = true }, + CancellationToken.None); return Task.FromResult(new EvaluateResponseBody { diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index b5d236ee0..d9c4e1ddd 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -132,7 +132,7 @@ await executionService.ExecuteDelegateAsync((pwsh, cancellationToken) => cursorPosition, options: null, powershell: pwsh); - }, cancellationToken); + }, representation: "CompleteInput", cancellationToken); stopwatch.Stop(); logger.LogTrace($"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); From 69bc568e81f791fc6d3008c57a07a129dee5ff6c Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 25 Jun 2020 11:15:47 -0700 Subject: [PATCH 006/176] Implement cancellation properly, make F8 work --- integrated.sln | 14 +++++ .../Services/Analysis/AnalysisService.cs | 3 +- .../Handlers/ConfigurationDoneHandler.cs | 4 +- .../Handlers/DisconnectHandler.cs | 2 +- .../Execution/PowerShellExecutionOptions.cs | 8 --- .../Execution/SynchronousPowerShellTask.cs | 2 +- .../PowerShell/Execution/SynchronousTask.cs | 48 +++++++-------- .../PowerShell/PowerShellConsoleService.cs | 58 +++++++++++++------ .../PowerShell/PowerShellExecutionService.cs | 51 +++++++++++++++- .../Utility/PowerShellExtensions.cs | 4 ++ .../PowerShellContext/ExtensionService.cs | 2 +- .../PowerShellContext/TemplateService.cs | 2 +- .../Handlers/TextDocumentHandler.cs | 4 +- 13 files changed, 139 insertions(+), 63 deletions(-) diff --git a/integrated.sln b/integrated.sln index 61cad8e31..d5bed48e9 100644 --- a/integrated.sln +++ b/integrated.sln @@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine", "..\PSScriptAnalyz EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rules", "..\PSScriptAnalyzer\Rules\Rules.csproj", "{6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.Commands.Utility", "..\PowerShell\src\Microsoft.PowerShell.Commands.Utility\Microsoft.PowerShell.Commands.Utility.csproj", "{E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -116,6 +118,18 @@ Global {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x64.Build.0 = Release|Any CPU {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x86.ActiveCfg = Release|Any CPU {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x86.Build.0 = Release|Any CPU + {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|x64.ActiveCfg = Debug|Any CPU + {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|x64.Build.0 = Debug|Any CPU + {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|x86.ActiveCfg = Debug|Any CPU + {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|x86.Build.0 = Debug|Any CPU + {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|Any CPU.Build.0 = Release|Any CPU + {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x64.ActiveCfg = Release|Any CPU + {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x64.Build.0 = Release|Any CPU + {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x86.ActiveCfg = Release|Any CPU + {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09} = {31BB87E9-A4A1-4266-B150-CEACE7C424C4} diff --git a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs index eee266e0f..22c61adc6 100644 --- a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs @@ -14,7 +14,6 @@ using Microsoft.PowerShell.EditorServices.Services.Analysis; using Microsoft.PowerShell.EditorServices.Services.Configuration; using Microsoft.PowerShell.EditorServices.Services.TextDocument; -using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; @@ -133,7 +132,7 @@ public AnalysisService( /// The files to run script analysis on. /// A cancellation token to cancel this call with. /// A task that finishes when script diagnostics have been published. - public void RunScriptDiagnostics( + public void StartScriptDiagnostics( ScriptFile[] filesToAnalyze) { if (_configurationService.CurrentSettings.ScriptAnalysis.Enable == false) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 5740b4447..0cf270d8b 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -111,7 +111,7 @@ private async Task LaunchScriptAsync(string scriptToLaunch) // This seems to be the simplest way to invoke a script block (which contains breakpoint information) via the PowerShell API. var cmd = new PSCommand().AddScript(". $args[0]").AddArgument(ast.GetScriptBlock()); await _executionService - .ExecutePSCommandAsync(cmd, new PowerShellExecutionOptions { WriteOutputToHost = true, WriteErrorsToHost = true }, CancellationToken.None) + .ExecutePSCommandAsync(cmd, new PowerShellExecutionOptions { WriteOutputToHost = true }, CancellationToken.None) .ConfigureAwait(false); } else @@ -119,7 +119,7 @@ await _executionService await _executionService .ExecutePSCommandAsync( new PSCommand().AddScript(untitledScript.Contents), - new PowerShellExecutionOptions { WriteOutputToHost = true, WriteErrorsToHost = true }, + new PowerShellExecutionOptions { WriteOutputToHost = true }, CancellationToken.None) .ConfigureAwait(false); } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs index 971b02f47..248aff048 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs @@ -45,7 +45,7 @@ public async Task Handle(DisconnectArguments request, Cancel if (_debugStateService.ExecutionCompleted == false) { _debugStateService.ExecutionCompleted = true; - _executionService.CancelCurrentTask(); + //_executionService.CancelCurrentTask(); if (_debugStateService.IsInteractiveDebugSession && _debugStateService.IsAttachSession) { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs index 011c861fe..b2c046f93 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs @@ -2,16 +2,8 @@ { public struct PowerShellExecutionOptions { - public static PowerShellExecutionOptions Default = new PowerShellExecutionOptions - { - WriteOutputToHost = true, - WriteErrorsToHost = true, - }; - public bool WriteOutputToHost { get; set; } - public bool WriteErrorsToHost { get; set; } - public bool AddToHistory { get; set; } public bool WriteInputToHost { get; set; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 4012b8bb5..70b8c01a1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -71,7 +71,7 @@ public override Collection Run(CancellationToken cancellationToken) { Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}"); - if (!_executionOptions.WriteErrorsToHost) + if (!_executionOptions.WriteOutputToHost) { throw; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs index ddba731b2..24c451bc1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs @@ -9,14 +9,14 @@ internal interface ISynchronousTask { bool IsCanceled { get; } - void ExecuteSynchronously(ref CancellationTokenSource cancellationSource, CancellationToken threadCancellationToken); + void ExecuteSynchronously(CancellationToken threadCancellationToken); } internal abstract class SynchronousTask : ISynchronousTask { private readonly TaskCompletionSource _taskCompletionSource; - private readonly CancellationToken _taskCancellationToken; + private readonly CancellationToken _taskRequesterCancellationToken; private bool _executionCanceled; @@ -24,7 +24,7 @@ protected SynchronousTask(ILogger logger, CancellationToken cancellationToken) { Logger = logger; _taskCompletionSource = new TaskCompletionSource(); - _taskCancellationToken = cancellationToken; + _taskRequesterCancellationToken = cancellationToken; _executionCanceled = false; } @@ -32,38 +32,40 @@ protected SynchronousTask(ILogger logger, CancellationToken cancellationToken) public Task Task => _taskCompletionSource.Task; - public bool IsCanceled => _taskCancellationToken.IsCancellationRequested || _executionCanceled; + public bool IsCanceled => _executionCanceled || _taskRequesterCancellationToken.IsCancellationRequested; public abstract TResult Run(CancellationToken cancellationToken); public abstract override string ToString(); - public void ExecuteSynchronously(ref CancellationTokenSource cancellationSource, CancellationToken threadCancellation) + public void ExecuteSynchronously(CancellationToken executorCancellationToken) { - if (_taskCancellationToken.IsCancellationRequested || threadCancellation.IsCancellationRequested) + using (var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_taskRequesterCancellationToken, executorCancellationToken)) { - Cancel(); - return; - } + if (cancellationSource.IsCancellationRequested) + { + SetCanceled(); + return; + } - cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_taskCancellationToken, threadCancellation); - try - { - TResult result = Run(cancellationSource.Token); + try + { + TResult result = Run(cancellationSource.Token); - _taskCompletionSource.SetResult(result); - } - catch (OperationCanceledException) - { - Cancel(); - } - catch (Exception e) - { - _taskCompletionSource.SetException(e); + _taskCompletionSource.SetResult(result); + } + catch (OperationCanceledException) + { + SetCanceled(); + } + catch (Exception e) + { + _taskCompletionSource.SetException(e); + } } } - private void Cancel() + private void SetCanceled() { _executionCanceled = true; _taskCompletionSource.SetCanceled(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs index a41825f7f..9658fa097 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -3,6 +3,8 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using System; +using System.Collections.ObjectModel; +using System.Linq; using System.Management.Automation; using System.Text; using System.Threading; @@ -16,15 +18,13 @@ public static PowerShellConsoleService CreateAndStart( ILoggerFactory loggerFactory, PowerShellExecutionService executionService) { - var consoleService = new PowerShellConsoleService( + return new PowerShellConsoleService( loggerFactory, executionService, executionService.EngineIntrinsics, executionService.EditorServicesHost, executionService.ReadLine, executionService.PSReadLineProxy); - - return consoleService; } private readonly ILogger _logger; @@ -45,6 +45,8 @@ public static PowerShellConsoleService CreateAndStart( private CancellationTokenSource _currentCommandCancellationSource; + private bool _canCancel; + private PowerShellConsoleService( ILoggerFactory loggerFactory, PowerShellExecutionService executionService, @@ -78,7 +80,15 @@ public void StartRepl() public void CancelCurrentPrompt() { - _currentCommandCancellationSource?.Cancel(); + if (_canCancel) + { + _currentCommandCancellationSource?.Cancel(); + } + } + + public void Stop() + { + _replLoopCancellationSource.Cancel(); } private async Task RunReplLoopAsync() @@ -86,13 +96,20 @@ private async Task RunReplLoopAsync() while (!_replLoopCancellationSource.IsCancellationRequested) { _currentCommandCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_replLoopCancellationSource.Token); - + _canCancel = true; try { - await InvokePromptFunctionAsync().ConfigureAwait(false); + string promptString = (await GetPromptOutputAsync().ConfigureAwait(false)).FirstOrDefault() ?? "PS> "; + + WritePrompt(promptString); string userInput = await InvokeReadLineAsync().ConfigureAwait(false); + if (_currentCommandCancellationSource.IsCancellationRequested) + { + continue; + } + await InvokeInputAsync(userInput).ConfigureAwait(false); } catch (OperationCanceledException) @@ -103,25 +120,28 @@ private async Task RunReplLoopAsync() { } + finally + { + _canCancel = false; + _currentCommandCancellationSource.Dispose(); + _currentCommandCancellationSource = null; + } } } - private Task InvokePromptFunctionAsync() + private Task> GetPromptOutputAsync() { - var promptCommand = new PSCommand() - .AddCommand("prompt") - .AddCommand("Write-Host") - .AddParameter("NoNewline"); - - var executionOptions = new PowerShellExecutionOptions - { - WriteOutputToHost = true, - }; + var promptCommand = new PSCommand().AddCommand("prompt"); - return _executionService.ExecutePSCommandAsync( + return _executionService.ExecutePSCommandAsync( promptCommand, - executionOptions, - _currentCommandCancellationSource.Token); + new PowerShellExecutionOptions(), + CancellationToken.None); + } + + private void WritePrompt(string promptString) + { + _editorServicesHost.UI.Write(promptString); } private Task InvokeReadLineAsync() diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 0da9b0e0a..ac4f5f5c5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -43,6 +43,7 @@ public static PowerShellExecutionService CreateAndStart( hostInfo.Version, hostInfo.LanguageMode, hostInfo.PSHost, + hostInfo.ProfilePaths, hostInfo.AdditionalModules); executionService.Start(); @@ -66,6 +67,8 @@ public static PowerShellExecutionService CreateAndStart( private readonly PSHost _internalHost; + private readonly ProfilePathInfo _profilePaths; + private readonly IReadOnlyList _additionalModulesToLoad; private Thread _pipelineThread; @@ -76,12 +79,15 @@ public static PowerShellExecutionService CreateAndStart( private PowerShellConsoleService _consoleService; + private bool _taskRunning; + private PowerShellExecutionService( ILoggerFactory loggerFactory, string hostName, Version hostVersion, PSLanguageMode languageMode, PSHost internalHost, + ProfilePathInfo profilePaths, IReadOnlyList additionalModules) { _loggerFactory = loggerFactory; @@ -92,6 +98,7 @@ private PowerShellExecutionService( _hostVersion = hostVersion; _languageMode = languageMode; _internalHost = internalHost; + _profilePaths = profilePaths; _additionalModulesToLoad = additionalModules; } @@ -165,9 +172,9 @@ public void Stop() public void CancelCurrentTask() { - if (_currentExecutionCancellationSource != null) + if (_taskRunning) { - _currentExecutionCancellationSource.Cancel(); + _currentExecutionCancellationSource?.Cancel(); } } @@ -215,6 +222,10 @@ private void Initialize() PSReadLineProxy.OverrideIdleHandler(HandlePowerShellOnIdle); ReadLine.RegisterExecutionDependencies(this, PSReadLineProxy); + //SetExecutionPolicy(); + + LoadProfiles(); + EnqueueModuleImport(s_commandsModulePath); if (_additionalModulesToLoad != null && _additionalModulesToLoad.Count > 0) @@ -263,7 +274,41 @@ private void RunTaskSynchronously(ISynchronousTask task) return; } - task.ExecuteSynchronously(ref _currentExecutionCancellationSource, _stopThreadCancellationSource.Token); + using (_currentExecutionCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_stopThreadCancellationSource.Token)) + { + _taskRunning = true; + try + { + task.ExecuteSynchronously(_currentExecutionCancellationSource.Token); + } + finally + { + _taskRunning = false; + } + } + } + + private void LoadProfiles() + { + var profileVariable = new PSObject(); + + AddProfileMemberAndQueueDotSourceIfExists(profileVariable, nameof(_profilePaths.AllUsersAllHosts), _profilePaths.AllUsersAllHosts); + AddProfileMemberAndQueueDotSourceIfExists(profileVariable, nameof(_profilePaths.AllUsersCurrentHost), _profilePaths.AllUsersCurrentHost); + AddProfileMemberAndQueueDotSourceIfExists(profileVariable, nameof(_profilePaths.CurrentUserAllHosts), _profilePaths.CurrentUserAllHosts); + AddProfileMemberAndQueueDotSourceIfExists(profileVariable, nameof(_profilePaths.CurrentUserCurrentHost), _profilePaths.CurrentUserCurrentHost); + + _pwsh.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); + } + + private void AddProfileMemberAndQueueDotSourceIfExists(PSObject profileVariable, string profileName, string profilePath) + { + profileVariable.Members.Add(new PSNoteProperty(profileName, profilePath)); + + if (File.Exists(profilePath)) + { + var command = new PSCommand().AddScript(profilePath, useLocalScope: false); + ExecutePSCommandAsync(command, new PowerShellExecutionOptions { WriteOutputToHost = true }, CancellationToken.None); + } } private void EnqueueModuleImport(string moduleNameOrPath) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs index d809a14c3..bd9d1b879 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.ObjectModel; using System.Management.Automation; +using System.Management.Automation.Runspaces; using System.Text; using SMA = System.Management.Automation; @@ -34,6 +35,9 @@ public static void InvokeAndClear(this SMA.PowerShell pwsh) public static SMA.PowerShell AddOutputCommand(this SMA.PowerShell pwsh) { + Command lastCommand = pwsh.Commands.Commands[pwsh.Commands.Commands.Count - 1]; + lastCommand.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output); + lastCommand.MergeMyResults(PipelineResultTypes.Information, PipelineResultTypes.Output); return pwsh.AddCommand("Microsoft.Powershell.Core\\Out-Default", useLocalScope: true); } diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs index ac9fa7d59..112cbaa11 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs @@ -117,7 +117,7 @@ public async Task InvokeCommandAsync(string commandName, EditorContext editorCon await ExecutionService.ExecutePSCommandAsync( executeCommand, - new PowerShellExecutionOptions { WriteOutputToHost = !editorCommand.SuppressOutput, WriteErrorsToHost = true }, + new PowerShellExecutionOptions { WriteOutputToHost = !editorCommand.SuppressOutput, }, CancellationToken.None).ConfigureAwait(false); } else diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs index 7ab52ab94..822a60fd5 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs @@ -169,7 +169,7 @@ public async Task CreateFromTemplateAsync( await _executionService.ExecutePSCommandAsync( command, - new PowerShellExecutionOptions { WriteErrorsToHost = true, InterruptCommandPrompt = true }, + new PowerShellExecutionOptions { WriteOutputToHost = true, InterruptCommandPrompt = true }, CancellationToken.None).ConfigureAwait(false); // If any errors were written out, creation was not successful diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/TextDocumentHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/TextDocumentHandler.cs index ed69b6f09..d35de6850 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/TextDocumentHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/TextDocumentHandler.cs @@ -55,7 +55,7 @@ public override Task Handle(DidChangeTextDocumentParams notification, Canc // Kick off script diagnostics without blocking the response // TODO: Get all recently edited files in the workspace - _analysisService.RunScriptDiagnostics(new ScriptFile[] { changedFile }); + _analysisService.StartScriptDiagnostics(new ScriptFile[] { changedFile }); return Unit.Task; } @@ -81,7 +81,7 @@ public override Task Handle(DidOpenTextDocumentParams notification, Cancel { // Kick off script diagnostics if we got a PowerShell file without blocking the response // TODO: Get all recently edited files in the workspace - _analysisService.RunScriptDiagnostics(new ScriptFile[] { openedFile }); + _analysisService.StartScriptDiagnostics(new ScriptFile[] { openedFile }); } _logger.LogTrace("Finished opening document."); From d8ba6a4f02022e00c4132556f68cb7f33c2b2cba Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 25 Jun 2020 11:23:51 -0700 Subject: [PATCH 007/176] Set execution policy --- .../PowerShell/PowerShellExecutionService.cs | 69 ++++++++++++++++++- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index ac4f5f5c5..00b37e8e1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -4,6 +4,7 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -222,9 +223,12 @@ private void Initialize() PSReadLineProxy.OverrideIdleHandler(HandlePowerShellOnIdle); ReadLine.RegisterExecutionDependencies(this, PSReadLineProxy); - //SetExecutionPolicy(); + if (VersionUtils.IsWindows) + { + ExecuteDelegateAsync(SetExecutionPolicy, nameof(SetExecutionPolicy), CancellationToken.None); + } - LoadProfiles(); + EnqueueProfileLoads(); EnqueueModuleImport(s_commandsModulePath); @@ -288,7 +292,66 @@ private void RunTaskSynchronously(ISynchronousTask task) } } - private void LoadProfiles() + private void SetExecutionPolicy(SMA.PowerShell pwsh, CancellationToken cancellationToken) + { + // We want to get the list hierarchy of execution policies + // Calling the cmdlet is the simplest way to do that + IReadOnlyList policies = pwsh + .AddCommand("Microsoft.PowerShell.Security\\Get-ExecutionPolicy") + .AddParameter("-List") + .InvokeAndClear(); + + // The policies come out in the following order: + // - MachinePolicy + // - UserPolicy + // - Process + // - CurrentUser + // - LocalMachine + // We want to ignore policy settings, since we'll already have those anyway. + // Then we need to look at the CurrentUser setting, and then the LocalMachine setting. + // + // Get-ExecutionPolicy -List emits PSObjects with Scope and ExecutionPolicy note properties + // set to expected values, so we must sift through those. + + ExecutionPolicy policyToSet = ExecutionPolicy.Bypass; + var currentUserPolicy = (ExecutionPolicy)policies[policies.Count - 2].Members["ExecutionPolicy"].Value; + if (currentUserPolicy != ExecutionPolicy.Undefined) + { + policyToSet = currentUserPolicy; + } + else + { + var localMachinePolicy = (ExecutionPolicy)policies[policies.Count - 1].Members["ExecutionPolicy"].Value; + if (localMachinePolicy != ExecutionPolicy.Undefined) + { + policyToSet = localMachinePolicy; + } + } + + // If there's nothing to do, save ourselves a PowerShell invocation + if (policyToSet == ExecutionPolicy.Bypass) + { + _logger.LogTrace("Execution policy already set to Bypass. Skipping execution policy set"); + return; + } + + // Finally set the inherited execution policy + _logger.LogTrace("Setting execution policy to {Policy}", policyToSet); + try + { + pwsh.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") + .AddParameter("Scope", ExecutionPolicyScope.Process) + .AddParameter("ExecutionPolicy", policyToSet) + .AddParameter("Force") + .InvokeAndClear(); + } + catch (CmdletInvocationException e) + { + _logger.LogError(e, "Error occurred calling 'Set-ExecutionPolicy -Scope Process -ExecutionPolicy {Policy} -Force'", policyToSet); + } + } + + private void EnqueueProfileLoads() { var profileVariable = new PSObject(); From 1e9e7d5b2ba0c4644e9628f88da59dc620411074 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 25 Jun 2020 11:28:02 -0700 Subject: [PATCH 008/176] Make initialisation executions synchronous --- .../PowerShell/PowerShellExecutionService.cs | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 00b37e8e1..87894a616 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -225,18 +225,18 @@ private void Initialize() if (VersionUtils.IsWindows) { - ExecuteDelegateAsync(SetExecutionPolicy, nameof(SetExecutionPolicy), CancellationToken.None); + SetExecutionPolicy(); } - EnqueueProfileLoads(); + LoadProfiles(); - EnqueueModuleImport(s_commandsModulePath); + ImportModule(s_commandsModulePath); if (_additionalModulesToLoad != null && _additionalModulesToLoad.Count > 0) { foreach (string module in _additionalModulesToLoad) { - EnqueueModuleImport(module); + ImportModule(module); } } @@ -292,11 +292,11 @@ private void RunTaskSynchronously(ISynchronousTask task) } } - private void SetExecutionPolicy(SMA.PowerShell pwsh, CancellationToken cancellationToken) + private void SetExecutionPolicy() { // We want to get the list hierarchy of execution policies // Calling the cmdlet is the simplest way to do that - IReadOnlyList policies = pwsh + IReadOnlyList policies = _pwsh .AddCommand("Microsoft.PowerShell.Security\\Get-ExecutionPolicy") .AddParameter("-List") .InvokeAndClear(); @@ -339,7 +339,7 @@ private void SetExecutionPolicy(SMA.PowerShell pwsh, CancellationToken cancellat _logger.LogTrace("Setting execution policy to {Policy}", policyToSet); try { - pwsh.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") + _pwsh.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") .AddParameter("Scope", ExecutionPolicyScope.Process) .AddParameter("ExecutionPolicy", policyToSet) .AddParameter("Force") @@ -351,36 +351,33 @@ private void SetExecutionPolicy(SMA.PowerShell pwsh, CancellationToken cancellat } } - private void EnqueueProfileLoads() + private void LoadProfiles() { var profileVariable = new PSObject(); - AddProfileMemberAndQueueDotSourceIfExists(profileVariable, nameof(_profilePaths.AllUsersAllHosts), _profilePaths.AllUsersAllHosts); - AddProfileMemberAndQueueDotSourceIfExists(profileVariable, nameof(_profilePaths.AllUsersCurrentHost), _profilePaths.AllUsersCurrentHost); - AddProfileMemberAndQueueDotSourceIfExists(profileVariable, nameof(_profilePaths.CurrentUserAllHosts), _profilePaths.CurrentUserAllHosts); - AddProfileMemberAndQueueDotSourceIfExists(profileVariable, nameof(_profilePaths.CurrentUserCurrentHost), _profilePaths.CurrentUserCurrentHost); + AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.AllUsersAllHosts), _profilePaths.AllUsersAllHosts); + AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.AllUsersCurrentHost), _profilePaths.AllUsersCurrentHost); + AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.CurrentUserAllHosts), _profilePaths.CurrentUserAllHosts); + AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.CurrentUserCurrentHost), _profilePaths.CurrentUserCurrentHost); _pwsh.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); } - private void AddProfileMemberAndQueueDotSourceIfExists(PSObject profileVariable, string profileName, string profilePath) + private void AddProfileMemberAndLoadIfExists(PSObject profileVariable, string profileName, string profilePath) { profileVariable.Members.Add(new PSNoteProperty(profileName, profilePath)); if (File.Exists(profilePath)) { - var command = new PSCommand().AddScript(profilePath, useLocalScope: false); - ExecutePSCommandAsync(command, new PowerShellExecutionOptions { WriteOutputToHost = true }, CancellationToken.None); + _pwsh.AddScript(profilePath, useLocalScope: false).AddOutputCommand().InvokeAndClear(); } } - private void EnqueueModuleImport(string moduleNameOrPath) + private void ImportModule(string moduleNameOrPath) { - var command = new PSCommand() - .AddCommand("Microsoft.PowerShell.Core\\Import-Module") - .AddParameter("-Name", moduleNameOrPath); - - ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None); + _pwsh.AddCommand("Microsoft.PowerShell.Core\\Import-Module") + .AddParameter("-Name", moduleNameOrPath) + .InvokeAndClear(); } private static Runspace CreateRunspace( From 4941342f1777be207ee7f7e438ab610700d5128b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 25 Jun 2020 11:28:59 -0700 Subject: [PATCH 009/176] Remove extraneous runspace set --- .../Services/PowerShell/PowerShellExecutionService.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 87894a616..f9683c7d3 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -239,8 +239,6 @@ private void Initialize() ImportModule(module); } } - - Runspace.DefaultRunspace = _pwsh.Runspace; } private void RunConsumerLoop() From ba8da6f1efcb58c8aba27916227871ef3d91d23b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 25 Jun 2020 15:08:40 -0700 Subject: [PATCH 010/176] Reuse conhost for prompts --- integrated.sln | 14 ++++++ ...ditorServicesConsolePSHostUserInterface.cs | 46 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/integrated.sln b/integrated.sln index d5bed48e9..0b0680083 100644 --- a/integrated.sln +++ b/integrated.sln @@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rules", "..\PSScriptAnalyze EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.Commands.Utility", "..\PowerShell\src\Microsoft.PowerShell.Commands.Utility\Microsoft.PowerShell.Commands.Utility.csproj", "{E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.ConsoleHost", "..\PowerShell\src\Microsoft.PowerShell.ConsoleHost\Microsoft.PowerShell.ConsoleHost.csproj", "{39FB7ECC-F839-474C-89CC-467E8A4ADB51}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -130,6 +132,18 @@ Global {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x64.Build.0 = Release|Any CPU {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x86.ActiveCfg = Release|Any CPU {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x86.Build.0 = Release|Any CPU + {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|x64.ActiveCfg = Debug|Any CPU + {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|x64.Build.0 = Debug|Any CPU + {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|x86.ActiveCfg = Debug|Any CPU + {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|x86.Build.0 = Debug|Any CPU + {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|Any CPU.Build.0 = Release|Any CPU + {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|x64.ActiveCfg = Release|Any CPU + {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|x64.Build.0 = Release|Any CPU + {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|x86.ActiveCfg = Release|Any CPU + {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09} = {31BB87E9-A4A1-4266-B150-CEACE7C424C4} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs index dda6bb8b7..55f864dcc 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs @@ -5,6 +5,7 @@ using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Host; +using System.Reflection; using System.Security; using System.Threading; @@ -18,6 +19,8 @@ internal class EditorServicesConsolePSHostUserInterface : PSHostUserInterface private readonly PSHostUserInterface _underlyingHostUI; + private readonly PSHostUserInterface _consoleHostUI; + public EditorServicesConsolePSHostUserInterface( ILoggerFactory loggerFactory, ConsoleReadLine readLine, @@ -27,6 +30,12 @@ public EditorServicesConsolePSHostUserInterface( _readLine = readLine; _underlyingHostUI = underlyingHostUI; RawUI = new EditorServicesConsolePSHostRawUserInterface(loggerFactory, underlyingHostUI.RawUI); + + _consoleHostUI = GetConsoleHostUI(_underlyingHostUI); + if (_consoleHostUI != null) + { + SetConsoleHostUIToInteractive(_consoleHostUI); + } } public override PSHostRawUserInterface RawUI { get; } @@ -35,21 +44,41 @@ public EditorServicesConsolePSHostUserInterface( public override Dictionary Prompt(string caption, string message, Collection descriptions) { + if (_consoleHostUI != null) + { + return _consoleHostUI.Prompt(caption, message, descriptions); + } + return _underlyingHostUI.Prompt(caption, message, descriptions); } public override int PromptForChoice(string caption, string message, Collection choices, int defaultChoice) { + if (_consoleHostUI != null) + { + return _consoleHostUI.PromptForChoice(caption, message, choices, defaultChoice); + } + return _underlyingHostUI.PromptForChoice(caption, message, choices, defaultChoice); } public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) { + if (_consoleHostUI != null) + { + return _consoleHostUI.PromptForCredential(caption, message, userName, targetName, allowedCredentialTypes, options); + } + return _underlyingHostUI.PromptForCredential(caption, message, userName, targetName, allowedCredentialTypes, options); } public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName) { + if (_consoleHostUI != null) + { + return _consoleHostUI.PromptForCredential(caption, message, userName, targetName); + } + return _underlyingHostUI.PromptForCredential(caption, message, userName, targetName); } @@ -99,5 +128,22 @@ public override void WriteWarningLine(string message) { _underlyingHostUI.WriteWarningLine(message); } + + private PSHostUserInterface GetConsoleHostUI(PSHostUserInterface ui) + { + FieldInfo externalUIField = ui.GetType().GetField("_externalUI", BindingFlags.NonPublic | BindingFlags.Instance); + + if (externalUIField == null) + { + return null; + } + + return (PSHostUserInterface)externalUIField.GetValue(ui); + } + + private void SetConsoleHostUIToInteractive(PSHostUserInterface ui) + { + ui.GetType().GetProperty("ThrowOnReadAndPrompt", BindingFlags.NonPublic | BindingFlags.Instance)?.SetValue(ui, false); + } } } From 8365f6240fbbb47170c6c227200429b5344b870c Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 25 Jun 2020 15:36:25 -0700 Subject: [PATCH 011/176] Fix newlines --- .../Services/PowerShell/PowerShellConsoleService.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs index 9658fa097..224335693 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -107,10 +107,16 @@ private async Task RunReplLoopAsync() if (_currentCommandCancellationSource.IsCancellationRequested) { + _editorServicesHost.UI.WriteLine(); continue; } await InvokeInputAsync(userInput).ConfigureAwait(false); + + if (_currentCommandCancellationSource.IsCancellationRequested) + { + _editorServicesHost.UI.WriteLine(); + } } catch (OperationCanceledException) { From 726787d727c139f91748250c1901588661a0a7bb Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 7 Jul 2020 16:37:11 -0700 Subject: [PATCH 012/176] Begin nested prompt implementation --- .../Server/PsesLanguageServer.cs | 2 +- .../Server/PsesServiceCollectionExtensions.cs | 13 +- .../PowerShell/Console/ConsoleReadLine.cs | 3 +- .../Execution/SynchronousPowerShellTask.cs | 53 +++++- .../Host/EditorServicesConsolePSHost.cs | 9 +- .../PowerShell/PowerShellConsoleService.cs | 33 +++- .../PowerShell/PowerShellEventService.cs | 152 ++++++++++++++++++ .../PowerShell/PowerShellExecutionService.cs | 106 +++++++++--- .../PowerShellContext/TemplateService.cs | 5 +- .../Utilities/CommandHelpers.cs | 2 +- 10 files changed, 333 insertions(+), 45 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/PowerShellEventService.cs diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index a806d67f7..ce2df7f4a 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -144,7 +144,7 @@ public async Task StartAsync() { await serviceProvider.GetService() .ExecutePSCommandAsync( - new PSCommand().AddCommand("Set-Location").AddParameter("-Path", workspaceService.WorkspacePath), + new PSCommand().AddCommand("Set-Location").AddParameter("-LiteralPath", workspaceService.WorkspacePath), new PowerShellExecutionOptions(), cancellationToken) .ConfigureAwait(false); diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index 5a7c449d2..805f3bce3 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -9,6 +9,7 @@ using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace Microsoft.PowerShell.EditorServices.Server { @@ -21,10 +22,18 @@ public static IServiceCollection AddPsesLanguageServices( return collection.AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton( - (provider) => PowerShellExecutionService.CreateAndStart(provider.GetService(), hostStartupInfo)) + (provider) => PowerShellExecutionService.CreateAndStart( + provider.GetService(), + provider.GetService(), + provider.GetService(), + hostStartupInfo)) .AddSingleton( - (provider) => PowerShellConsoleService.CreateAndStart(provider.GetService(), provider.GetService())) + (provider) => PowerShellConsoleService.CreateAndStart( + provider.GetService(), + provider.GetService(), + provider.GetService())) .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index abc2be9b0..80cd0b93f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -14,6 +14,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using System; + using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Language; using System.Security; @@ -168,7 +169,7 @@ internal async Task InvokeLegacyReadLineAsync(bool isCommandLine, Cancel CommandCompletion currentCompletion = null; int historyIndex = -1; - Collection currentHistory = null; + IReadOnlyList currentHistory = null; StringBuilder inputLine = new StringBuilder(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 70b8c01a1..d8b53f4e8 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Host; @@ -10,8 +11,12 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution { - internal class SynchronousPowerShellTask : SynchronousTask> + internal class SynchronousPowerShellTask : SynchronousTask> { + private readonly ILogger _logger; + + private readonly PowerShellEventService _eventService; + private readonly SMA.PowerShell _pwsh; private readonly PSCommand _psCommand; @@ -22,6 +27,7 @@ internal class SynchronousPowerShellTask : SynchronousTask Run(CancellationToken cancellationToken) + public override IReadOnlyList Run(CancellationToken cancellationToken) { cancellationToken.Register(Cancel); - _pwsh.Commands = _psCommand; - if (_executionOptions.WriteInputToHost) { _psHost.UI.WriteLine(_psCommand.GetInvocationText()); } + if (_pwsh.Runspace.Debugger.IsActive) + { + return ExecuteInDebugger(cancellationToken); + } + + return ExecuteNormally(cancellationToken); + } + + public override string ToString() + { + return _psCommand.GetInvocationText(); + } + + private IReadOnlyList ExecuteNormally(CancellationToken cancellationToken) + { + _pwsh.Commands = _psCommand; + if (_executionOptions.WriteOutputToHost) { _pwsh.AddOutputCommand(); @@ -91,9 +114,27 @@ public override Collection Run(CancellationToken cancellationToken) return result; } - public override string ToString() + private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationToken) { - return _psCommand.GetInvocationText(); + var outputCollection = new PSDataCollection(); + DebuggerCommandResults debuggerResult = _pwsh.Runspace.Debugger.ProcessCommand(_psCommand, outputCollection); + _eventService.ProcessDebuggerCommandResults(debuggerResult); + + if (typeof(TResult) == typeof(PSObject)) + { + return (IReadOnlyList)outputCollection; + } + + var results = new List(outputCollection.Count); + foreach (PSObject outputResult in outputCollection) + { + if (LanguagePrimitives.TryConvertTo(outputResult, typeof(TResult), out object result)) + { + results.Add((TResult)result); + } + } + + return results; } private void Cancel() diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index 614b32136..1c859f2f0 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; +using PowerShellEditorServices.Services; using System; using System.Globalization; using System.Management.Automation.Host; @@ -11,16 +12,20 @@ internal class EditorServicesConsolePSHost : PSHost, IHostSupportsInteractiveSes { private readonly ILogger _logger; + private readonly PowerShellExecutionService _executionService; + private Runspace _pushedRunspace; public EditorServicesConsolePSHost( ILoggerFactory loggerFactory, + PowerShellExecutionService executionService, string name, Version version, PSHost internalHost, ConsoleReadLine readline) { _logger = loggerFactory.CreateLogger(); + _executionService = executionService; _pushedRunspace = null; Name = name; Version = version; @@ -45,12 +50,12 @@ public EditorServicesConsolePSHost( public override void EnterNestedPrompt() { - throw new NotImplementedException(); + _executionService.EnterNestedPrompt(); } public override void ExitNestedPrompt() { - throw new NotImplementedException(); + _executionService.ExitNestedPrompt(); } public override void NotifyBeginApplication() diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs index 224335693..a976ad6a9 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -3,7 +3,7 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using System; -using System.Collections.ObjectModel; +using System.Collections.Generic; using System.Linq; using System.Management.Automation; using System.Text; @@ -16,11 +16,13 @@ internal class PowerShellConsoleService : IDisposable { public static PowerShellConsoleService CreateAndStart( ILoggerFactory loggerFactory, - PowerShellExecutionService executionService) + PowerShellExecutionService executionService, + PowerShellEventService eventService) { return new PowerShellConsoleService( loggerFactory, executionService, + eventService, executionService.EngineIntrinsics, executionService.EditorServicesHost, executionService.ReadLine, @@ -31,6 +33,8 @@ public static PowerShellConsoleService CreateAndStart( private readonly PowerShellExecutionService _executionService; + private readonly PowerShellEventService _eventService; + private readonly EngineIntrinsics _engineIntrinsics; private readonly EditorServicesConsolePSHost _editorServicesHost; @@ -50,6 +54,7 @@ public static PowerShellConsoleService CreateAndStart( private PowerShellConsoleService( ILoggerFactory loggerFactory, PowerShellExecutionService executionService, + PowerShellEventService eventService, EngineIntrinsics engineIntrinsics, EditorServicesConsolePSHost editorServicesHost, ConsoleReadLine readLine, @@ -57,6 +62,7 @@ private PowerShellConsoleService( { _logger = loggerFactory.CreateLogger(); _executionService = executionService; + _eventService = eventService; _engineIntrinsics = engineIntrinsics; _editorServicesHost = editorServicesHost; _readLine = readLine; @@ -65,17 +71,20 @@ private PowerShellConsoleService( public void Dispose() { - System.Console.CancelKeyPress -= HandleConsoleCancellation; + System.Console.CancelKeyPress -= OnCancelKeyPress; + _eventService.PromptFramePushed -= OnPromptFramePushed; } public void StartRepl() { _replLoopCancellationSource = new CancellationTokenSource(); - System.Console.CancelKeyPress += HandleConsoleCancellation; + System.Console.CancelKeyPress += OnCancelKeyPress; System.Console.OutputEncoding = Encoding.UTF8; _psrlProxy.OverrideReadKey(ReadKey); _consoleLoopThread = Task.Run(RunReplLoopAsync, _replLoopCancellationSource.Token); _executionService.RegisterConsoleService(this); + _eventService.PromptFramePushed += OnPromptFramePushed; + _eventService.PromptFramePopped += OnPromptFramePopped; } public void CancelCurrentPrompt() @@ -135,7 +144,7 @@ private async Task RunReplLoopAsync() } } - private Task> GetPromptOutputAsync() + private Task> GetPromptOutputAsync() { var promptCommand = new PSCommand().AddCommand("prompt"); @@ -167,9 +176,19 @@ private Task InvokeInputAsync(string input) return _executionService.ExecutePSCommandAsync(command, executionOptions, _currentCommandCancellationSource.Token); } - private void HandleConsoleCancellation(object sender, ConsoleCancelEventArgs args) + private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) + { + CancelCurrentPrompt(); + } + + public void OnPromptFramePushed(object sender, PromptFramePushedArgs args) + { + Task.Run(RunReplLoopAsync); + } + + public void OnPromptFramePopped(object sender, PromptFramePoppedArgs args) { - _currentCommandCancellationSource.Cancel(); + CancelCurrentPrompt(); } private ConsoleKeyInfo ReadKey(bool intercept) diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellEventService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellEventService.cs new file mode 100644 index 000000000..1c1aecd4f --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellEventService.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Security.Cryptography; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell +{ + [Flags] + internal enum PromptFrameType + { + Normal = 0, + NestedPrompt = 1, + Debug = 2, + Remote = 4, + } + + internal class PromptFramePushedArgs + { + public PromptFramePushedArgs(PromptFrameType frameType) + { + FrameType = frameType; + } + + public PromptFrameType FrameType { get; } + } + + internal class PromptFramePoppedArgs + { + public PromptFramePoppedArgs(PromptFrameType frameType) + { + FrameType = frameType; + } + + public PromptFrameType FrameType { get; } + } + + internal class DebuggerResumedArgs + { + public DebuggerResumedArgs(DebuggerResumeAction? resumeAction) + { + ResumeAction = resumeAction; + } + + DebuggerResumeAction? ResumeAction { get; } + } + + internal class PowerShellEventService + { + private readonly Stack _frameStack; + + public PowerShellEventService() + { + _frameStack = new Stack(); + } + + public void PushFrame(Runspace runspace, PromptFrameType frameType) + { + UnregisterCurrentRunspace(); + + runspace.Debugger.DebuggerStop += OnDebuggerStopped; + runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; + _frameStack.Push(new PowerShellContextFrame + { + Runspace = runspace, + FrameType = frameType, + }); + + PromptFramePushed?.Invoke(this, new PromptFramePushedArgs(frameType)); + } + + public void PopFrame() + { + PromptFrameType frameType = PopAndDisposeCurrentRunspace(); + + if (_frameStack.Count > 0) + { + PowerShellContextFrame currentFrame = _frameStack.Peek(); + currentFrame.Runspace.Debugger.DebuggerStop += OnDebuggerStopped; + currentFrame.Runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; + } + + PromptFramePopped?.Invoke(this, new PromptFramePoppedArgs(frameType)); + } + + public void ProcessDebuggerCommandResults(DebuggerCommandResults results) + { + DebuggerResumed?.Invoke(this, new DebuggerResumedArgs(results.ResumeAction)); + } + + public void Dispose() + { + while (_frameStack.Count > 0) + { + PopAndDisposeCurrentRunspace(); + } + } + + public event Action PromptFramePushed; + + public event Action PromptFramePopped; + + public event Action DebuggerStopped; + + public event Action DebuggerResumed; + + public event Action BreakpointUpdated; + + private void OnDebuggerStopped(object sender, DebuggerStopEventArgs args) + { + DebuggerStopped?.Invoke(this, args); + } + + private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs args) + { + BreakpointUpdated?.Invoke(this, args); + } + + private void UnregisterCurrentRunspace() + { + if (_frameStack.Count > 0) + { + PowerShellContextFrame frame = _frameStack.Peek(); + UnregisterRunspace(frame.Runspace); + } + } + + private PromptFrameType PopAndDisposeCurrentRunspace() + { + PowerShellContextFrame frame = _frameStack.Pop(); + UnregisterRunspace(frame.Runspace); + frame.Runspace.Dispose(); + return frame.FrameType; + } + + private void UnregisterRunspace(Runspace runspace) + { + if (runspace.Debugger != null) + { + runspace.Debugger.DebuggerStop -= OnDebuggerStopped; + runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; + } + } + + private struct PowerShellContextFrame + { + public Runspace Runspace { get; set; } + + public PromptFrameType FrameType { get; set; } + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index f9683c7d3..1636a33bc 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -5,10 +5,10 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.IO; using System.Management.Automation; using System.Management.Automation.Host; @@ -36,10 +36,14 @@ internal class PowerShellExecutionService : IDisposable public static PowerShellExecutionService CreateAndStart( ILoggerFactory loggerFactory, + ILanguageServer languageServer, + PowerShellEventService eventService, HostStartupInfo hostInfo) { var executionService = new PowerShellExecutionService( loggerFactory, + languageServer, + eventService, hostInfo.Name, hostInfo.Version, hostInfo.LanguageMode, @@ -60,6 +64,10 @@ public static PowerShellExecutionService CreateAndStart( private readonly ILogger _logger; + private readonly ILanguageServer _languageServer; + + private readonly PowerShellEventService _eventService; + private readonly string _hostName; private readonly Version _hostVersion; @@ -72,18 +80,24 @@ public static PowerShellExecutionService CreateAndStart( private readonly IReadOnlyList _additionalModulesToLoad; + private readonly Stack _pwshStack; + private Thread _pipelineThread; private CancellationTokenSource _currentExecutionCancellationSource; - private SMA.PowerShell _pwsh; + private SMA.PowerShell _currentPwsh; private PowerShellConsoleService _consoleService; private bool _taskRunning; + private bool _exitNestedPrompt; + private PowerShellExecutionService( ILoggerFactory loggerFactory, + ILanguageServer languageServer, + PowerShellEventService eventService, string hostName, Version hostVersion, PSLanguageMode languageMode, @@ -93,6 +107,8 @@ private PowerShellExecutionService( { _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(); + _languageServer = languageServer; + _eventService = eventService; _stopThreadCancellationSource = new CancellationTokenSource(); _executionQueue = new BlockingCollection(); _hostName = hostName; @@ -101,6 +117,7 @@ private PowerShellExecutionService( _internalHost = internalHost; _profilePaths = profilePaths; _additionalModulesToLoad = additionalModules; + _pwshStack = new Stack(); } public EngineIntrinsics EngineIntrinsics { get; private set; } @@ -116,7 +133,7 @@ public Task ExecuteDelegateAsync( string representation, CancellationToken cancellationToken) { - TResult appliedFunc(CancellationToken cancellationToken) => func(_pwsh, cancellationToken); + TResult appliedFunc(CancellationToken cancellationToken) => func(_currentPwsh, cancellationToken); return ExecuteDelegateAsync(appliedFunc, representation, cancellationToken); } @@ -125,7 +142,7 @@ public Task ExecuteDelegateAsync( string representation, CancellationToken cancellationToken) { - void appliedAction(CancellationToken cancellationToken) => action(_pwsh, cancellationToken); + void appliedAction(CancellationToken cancellationToken) => action(_currentPwsh, cancellationToken); return ExecuteDelegateAsync(appliedAction, representation, cancellationToken); } @@ -145,12 +162,12 @@ public Task ExecuteDelegateAsync( return QueueTask(new SynchronousDelegateTask(_logger, action, representation, cancellationToken)); } - public Task> ExecutePSCommandAsync( + public Task> ExecutePSCommandAsync( PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) { - Task> result = QueueTask(new SynchronousPowerShellTask(_logger, _pwsh, EditorServicesHost, psCommand, executionOptions, cancellationToken)); + Task> result = QueueTask(new SynchronousPowerShellTask(_logger, _eventService, _currentPwsh, EditorServicesHost, psCommand, executionOptions, cancellationToken)); if (executionOptions.InterruptCommandPrompt) { @@ -184,10 +201,48 @@ public void RegisterConsoleService(PowerShellConsoleService consoleService) _consoleService = consoleService; } + public void EnterNestedPrompt() + { + _currentPwsh = _currentPwsh.CreateNestedPowerShell(); + _pwshStack.Push(_currentPwsh); + _eventService.PushFrame(_currentPwsh.Runspace, PromptFrameType.NestedPrompt); + + try + { + foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(_stopThreadCancellationSource.Token)) + { + RunTaskSynchronously(task); + + if (_exitNestedPrompt) + { + break; + } + } + } + catch (OperationCanceledException) + { + } + finally + { + _currentPwsh.Dispose(); + _currentPwsh = _pwshStack.Pop(); + _eventService.PopFrame(); + _exitNestedPrompt = false; + } + } + + public void ExitNestedPrompt() + { + _exitNestedPrompt = true; + } + public void Dispose() { Stop(); - _pwsh.Dispose(); + foreach (SMA.PowerShell pwsh in _pwshStack) + { + pwsh.Dispose(); + } } private Task QueueTask(SynchronousTask task) @@ -207,20 +262,23 @@ private void Start() private void Initialize() { - _pwsh = SMA.PowerShell.Create(); + _currentPwsh = SMA.PowerShell.Create(); + _pwshStack.Push(_currentPwsh); + + _eventService.PushFrame(_currentPwsh.Runspace, PromptFrameType.Normal); ReadLine = new ConsoleReadLine(); - EditorServicesHost = new EditorServicesConsolePSHost(_loggerFactory, _hostName, _hostVersion, _internalHost, ReadLine); + EditorServicesHost = new EditorServicesConsolePSHost(_loggerFactory, this, _hostName, _hostVersion, _internalHost, ReadLine); - _pwsh.Runspace = CreateRunspace(EditorServicesHost, _languageMode); - Runspace.DefaultRunspace = _pwsh.Runspace; - EditorServicesHost.RegisterRunspace(_pwsh.Runspace); + _currentPwsh.Runspace = CreateRunspace(EditorServicesHost, _languageMode); + Runspace.DefaultRunspace = _currentPwsh.Runspace; + EditorServicesHost.RegisterRunspace(_currentPwsh.Runspace); - var engineIntrinsics = (EngineIntrinsics)_pwsh.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); + var engineIntrinsics = (EngineIntrinsics)_currentPwsh.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); - PSReadLineProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, _pwsh); - PSReadLineProxy.OverrideIdleHandler(HandlePowerShellOnIdle); + PSReadLineProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, _currentPwsh); + PSReadLineProxy.OverrideIdleHandler(OnPowerShellIdle); ReadLine.RegisterExecutionDependencies(this, PSReadLineProxy); if (VersionUtils.IsWindows) @@ -247,7 +305,7 @@ private void RunConsumerLoop() try { - foreach (ISynchronousTask synchronousTask in _executionQueue.GetConsumingEnumerable()) + foreach (ISynchronousTask synchronousTask in _executionQueue.GetConsumingEnumerable(_stopThreadCancellationSource.Token)) { RunTaskSynchronously(synchronousTask); } @@ -258,9 +316,9 @@ private void RunConsumerLoop() } } - private void HandlePowerShellOnIdle() + private void OnPowerShellIdle() { - while (_pwsh.InvocationStateInfo.State == PSInvocationState.Completed + while (_currentPwsh.InvocationStateInfo.State == PSInvocationState.Completed && _executionQueue.TryTake(out ISynchronousTask task)) { RunTaskSynchronously(task); @@ -294,7 +352,7 @@ private void SetExecutionPolicy() { // We want to get the list hierarchy of execution policies // Calling the cmdlet is the simplest way to do that - IReadOnlyList policies = _pwsh + IReadOnlyList policies = _currentPwsh .AddCommand("Microsoft.PowerShell.Security\\Get-ExecutionPolicy") .AddParameter("-List") .InvokeAndClear(); @@ -337,7 +395,7 @@ private void SetExecutionPolicy() _logger.LogTrace("Setting execution policy to {Policy}", policyToSet); try { - _pwsh.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") + _currentPwsh.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") .AddParameter("Scope", ExecutionPolicyScope.Process) .AddParameter("ExecutionPolicy", policyToSet) .AddParameter("Force") @@ -358,7 +416,7 @@ private void LoadProfiles() AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.CurrentUserAllHosts), _profilePaths.CurrentUserAllHosts); AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.CurrentUserCurrentHost), _profilePaths.CurrentUserCurrentHost); - _pwsh.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); + _currentPwsh.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); } private void AddProfileMemberAndLoadIfExists(PSObject profileVariable, string profileName, string profilePath) @@ -367,13 +425,15 @@ private void AddProfileMemberAndLoadIfExists(PSObject profileVariable, string pr if (File.Exists(profilePath)) { - _pwsh.AddScript(profilePath, useLocalScope: false).AddOutputCommand().InvokeAndClear(); + _currentPwsh.AddScript(profilePath, useLocalScope: false) + .AddOutputCommand() + .InvokeAndClear(); } } private void ImportModule(string moduleNameOrPath) { - _pwsh.AddCommand("Microsoft.PowerShell.Core\\Import-Module") + _currentPwsh.AddCommand("Microsoft.PowerShell.Core\\Import-Module") .AddParameter("-Name", moduleNameOrPath) .InvokeAndClear(); } diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs index 822a60fd5..96f5fec83 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs @@ -8,6 +8,7 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Utility; using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Management.Automation; @@ -95,7 +96,7 @@ public async Task ImportPlasterIfInstalledAsync() .AddParameter("ModuleInfo", (PSModuleInfo)moduleObject.ImmediateBaseObject) .AddParameter("PassThru"); - Collection importResult = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); + IReadOnlyList importResult = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); this.isPlasterLoaded = importResult.Any(); string loadedQualifier = @@ -134,7 +135,7 @@ public async Task GetAvailableTemplatesAsync( psCommand.AddParameter("IncludeModules"); } - Collection templateObjects = await _executionService.ExecutePSCommandAsync( + IReadOnlyList templateObjects = await _executionService.ExecutePSCommandAsync( psCommand, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs index b64c39f46..c267f3cf7 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs @@ -146,7 +146,7 @@ public static async Task GetCommandSynopsisAsync( .AddParameter("Online", false) .AddParameter("ErrorAction", "Ignore"); - Collection results = await executionService.ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); + IReadOnlyList results = await executionService.ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); PSObject helpObject = results.FirstOrDefault(); // Extract the synopsis string from the object From cb16210bd568c91d8ec6d21316d8ab965d5f1e80 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 8 Jul 2020 09:57:57 -0700 Subject: [PATCH 013/176] Move to PSContext --- .../Server/PsesDebugServer.cs | 11 +- .../Server/PsesServiceCollectionExtensions.cs | 5 +- .../PowerShell/Execution/PowerShellContext.cs | 175 ++++++++++++++++++ .../Execution/SynchronousPowerShellTask.cs | 16 +- .../PowerShell/PowerShellConsoleService.cs | 20 +- .../PowerShell/PowerShellEventService.cs | 152 --------------- .../PowerShell/PowerShellExecutionService.cs | 104 ++++++----- 7 files changed, 248 insertions(+), 235 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/PowerShellEventService.cs diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 76acc7adc..51a558d53 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -11,6 +11,7 @@ using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.DebugAdapter.Protocol; using OmniSharp.Extensions.DebugAdapter.Protocol.Serialization; @@ -84,12 +85,12 @@ public async Task StartAsync() // This is only needed for Temp sessions who only have a debug server. if (_usePSReadLine && _useTempSession && Interlocked.Exchange(ref s_hasRunPsrlStaticCtor, 1) == 0) { - var command = new PSCommand() - .AddCommand(s_lazyInvokeReadLineConstructorCmdletInfo.Value); - // This must be run synchronously to ensure debugging works - _powerShellContextService - .ExecuteCommandAsync(command, sendOutputToHost: true, sendErrorToHost: true) + _executionService + .ExecuteDelegateAsync((cancellationToken) => + { + // Is this needed now that we do things the cool way?? + }, "PSRL static constructor execution", CancellationToken.None) .GetAwaiter() .GetResult(); } diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index 805f3bce3..d559842a1 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -22,18 +22,15 @@ public static IServiceCollection AddPsesLanguageServices( return collection.AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() .AddSingleton( (provider) => PowerShellExecutionService.CreateAndStart( provider.GetService(), provider.GetService(), - provider.GetService(), hostStartupInfo)) .AddSingleton( (provider) => PowerShellConsoleService.CreateAndStart( provider.GetService(), - provider.GetService(), - provider.GetService())) + provider.GetService())) .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs new file mode 100644 index 000000000..e6edecac8 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs @@ -0,0 +1,175 @@ +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Host; +using System.Management.Automation.Runspaces; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + [Flags] + internal enum PromptFrameType + { + Normal = 0, + NestedPrompt = 1, + Debug = 2, + Remote = 4, + } + + internal class PromptFramePushedArgs + { + public PromptFramePushedArgs(PromptFrameType frameType) + { + FrameType = frameType; + } + + public PromptFrameType FrameType { get; } + } + + internal class PromptFramePoppedArgs + { + public PromptFramePoppedArgs(PromptFrameType frameType) + { + FrameType = frameType; + } + + public PromptFrameType FrameType { get; } + } + + internal class DebuggerResumedArgs + { + public DebuggerResumedArgs(DebuggerResumeAction? resumeAction) + { + ResumeAction = resumeAction; + } + + DebuggerResumeAction? ResumeAction { get; } + } + + + internal class PowerShellContext : IDisposable + { + public static PowerShellContext Create(EditorServicesConsolePSHost psHost, PSLanguageMode languageMode) + { + InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" + ? InitialSessionState.CreateDefault() + : InitialSessionState.CreateDefault2(); + + iss.LanguageMode = languageMode; + + Runspace runspace = RunspaceFactory.CreateRunspace(psHost, iss); + + runspace.SetApartmentStateToSta(); + runspace.ThreadOptions = PSThreadOptions.ReuseThread; + + runspace.Open(); + + var context = new PowerShellContext(); + context.PushPowerShell(runspace); + + Runspace.DefaultRunspace = runspace; + psHost.RegisterRunspace(runspace); + + return context; + } + + private readonly Stack _frameStack; + + private PowerShellContext() + { + _frameStack = new Stack(); + } + + public SMA.PowerShell CurrentPowerShell + { + get => _frameStack.Count > 0 ? _frameStack.Peek().PowerShell : null; + } + + public event Action PromptFramePushed; + + public event Action PromptFramePopped; + + public event Action DebuggerStopped; + + public event Action DebuggerResumed; + + public event Action BreakpointUpdated; + + public void ProcessDebuggerResult(DebuggerCommandResults result) + { + DebuggerResumed?.Invoke(this, new DebuggerResumedArgs(result.ResumeAction)); + } + + public void PushNestedPowerShell() + { + var pwsh = CurrentPowerShell.CreateNestedPowerShell(); + pwsh.Runspace.ThreadOptions = PSThreadOptions.ReuseThread; + + PushFrame(new ContextFrame(pwsh, PromptFrameType.NestedPrompt)); + } + + public void PopPowerShell() + { + PopFrame(); + } + + public void Dispose() + { + while (_frameStack.Count > 0) + { + _frameStack.Pop().PowerShell.Dispose(); + } + } + + private void PushPowerShell(Runspace runspace) + { + var pwsh = SMA.PowerShell.Create(); + pwsh.Runspace = runspace; + + PushFrame(new ContextFrame(pwsh, PromptFrameType.Normal)); + } + + private void PushFrame(ContextFrame frame) + { + _frameStack.Push(frame); + frame.PowerShell.Runspace.Debugger.DebuggerStop += OnDebuggerStopped; + frame.PowerShell.Runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; + PromptFramePushed?.Invoke(this, new PromptFramePushedArgs(frame.FrameType)); + } + + private void PopFrame() + { + ContextFrame frame = _frameStack.Pop(); + frame.PowerShell.Runspace.Debugger.DebuggerStop -= OnDebuggerStopped; + frame.PowerShell.Runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; + PromptFramePopped?.Invoke(this, new PromptFramePoppedArgs(frame.FrameType)); + frame.PowerShell.Dispose(); + } + + private void OnDebuggerStopped(object sender, DebuggerStopEventArgs args) + { + DebuggerStopped?.Invoke(this, args); + } + + private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs args) + { + BreakpointUpdated?.Invoke(this, args); + } + + private class ContextFrame + { + public ContextFrame(SMA.PowerShell powerShell, PromptFrameType frameType) + { + PowerShell = powerShell; + FrameType = frameType; + } + + public SMA.PowerShell PowerShell { get; } + + public PromptFrameType FrameType { get; } + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index d8b53f4e8..b4437e7a8 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -15,9 +15,7 @@ internal class SynchronousPowerShellTask : SynchronousTask : SynchronousTask Run(CancellationToken cancellationToken) { + _pwsh = _pwshContext.CurrentPowerShell; + cancellationToken.Register(Cancel); if (_executionOptions.WriteInputToHost) @@ -118,7 +118,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT { var outputCollection = new PSDataCollection(); DebuggerCommandResults debuggerResult = _pwsh.Runspace.Debugger.ProcessCommand(_psCommand, outputCollection); - _eventService.ProcessDebuggerCommandResults(debuggerResult); + _pwshContext.ProcessDebuggerResult(debuggerResult); if (typeof(TResult) == typeof(PSObject)) { diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs index a976ad6a9..746805efe 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -16,14 +16,11 @@ internal class PowerShellConsoleService : IDisposable { public static PowerShellConsoleService CreateAndStart( ILoggerFactory loggerFactory, - PowerShellExecutionService executionService, - PowerShellEventService eventService) + PowerShellExecutionService executionService) { return new PowerShellConsoleService( loggerFactory, executionService, - eventService, - executionService.EngineIntrinsics, executionService.EditorServicesHost, executionService.ReadLine, executionService.PSReadLineProxy); @@ -33,10 +30,6 @@ public static PowerShellConsoleService CreateAndStart( private readonly PowerShellExecutionService _executionService; - private readonly PowerShellEventService _eventService; - - private readonly EngineIntrinsics _engineIntrinsics; - private readonly EditorServicesConsolePSHost _editorServicesHost; private readonly ConsoleReadLine _readLine; @@ -54,16 +47,12 @@ public static PowerShellConsoleService CreateAndStart( private PowerShellConsoleService( ILoggerFactory loggerFactory, PowerShellExecutionService executionService, - PowerShellEventService eventService, - EngineIntrinsics engineIntrinsics, EditorServicesConsolePSHost editorServicesHost, ConsoleReadLine readLine, PSReadLineProxy psrlProxy) { _logger = loggerFactory.CreateLogger(); _executionService = executionService; - _eventService = eventService; - _engineIntrinsics = engineIntrinsics; _editorServicesHost = editorServicesHost; _readLine = readLine; _psrlProxy = psrlProxy; @@ -72,7 +61,8 @@ private PowerShellConsoleService( public void Dispose() { System.Console.CancelKeyPress -= OnCancelKeyPress; - _eventService.PromptFramePushed -= OnPromptFramePushed; + _executionService.PromptFramePushed -= OnPromptFramePushed; + _executionService.PromptFramePopped -= OnPromptFramePopped; } public void StartRepl() @@ -83,8 +73,8 @@ public void StartRepl() _psrlProxy.OverrideReadKey(ReadKey); _consoleLoopThread = Task.Run(RunReplLoopAsync, _replLoopCancellationSource.Token); _executionService.RegisterConsoleService(this); - _eventService.PromptFramePushed += OnPromptFramePushed; - _eventService.PromptFramePopped += OnPromptFramePopped; + _executionService.PromptFramePushed += OnPromptFramePushed; + _executionService.PromptFramePopped += OnPromptFramePopped; } public void CancelCurrentPrompt() diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellEventService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellEventService.cs deleted file mode 100644 index 1c1aecd4f..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellEventService.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Security.Cryptography; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell -{ - [Flags] - internal enum PromptFrameType - { - Normal = 0, - NestedPrompt = 1, - Debug = 2, - Remote = 4, - } - - internal class PromptFramePushedArgs - { - public PromptFramePushedArgs(PromptFrameType frameType) - { - FrameType = frameType; - } - - public PromptFrameType FrameType { get; } - } - - internal class PromptFramePoppedArgs - { - public PromptFramePoppedArgs(PromptFrameType frameType) - { - FrameType = frameType; - } - - public PromptFrameType FrameType { get; } - } - - internal class DebuggerResumedArgs - { - public DebuggerResumedArgs(DebuggerResumeAction? resumeAction) - { - ResumeAction = resumeAction; - } - - DebuggerResumeAction? ResumeAction { get; } - } - - internal class PowerShellEventService - { - private readonly Stack _frameStack; - - public PowerShellEventService() - { - _frameStack = new Stack(); - } - - public void PushFrame(Runspace runspace, PromptFrameType frameType) - { - UnregisterCurrentRunspace(); - - runspace.Debugger.DebuggerStop += OnDebuggerStopped; - runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; - _frameStack.Push(new PowerShellContextFrame - { - Runspace = runspace, - FrameType = frameType, - }); - - PromptFramePushed?.Invoke(this, new PromptFramePushedArgs(frameType)); - } - - public void PopFrame() - { - PromptFrameType frameType = PopAndDisposeCurrentRunspace(); - - if (_frameStack.Count > 0) - { - PowerShellContextFrame currentFrame = _frameStack.Peek(); - currentFrame.Runspace.Debugger.DebuggerStop += OnDebuggerStopped; - currentFrame.Runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; - } - - PromptFramePopped?.Invoke(this, new PromptFramePoppedArgs(frameType)); - } - - public void ProcessDebuggerCommandResults(DebuggerCommandResults results) - { - DebuggerResumed?.Invoke(this, new DebuggerResumedArgs(results.ResumeAction)); - } - - public void Dispose() - { - while (_frameStack.Count > 0) - { - PopAndDisposeCurrentRunspace(); - } - } - - public event Action PromptFramePushed; - - public event Action PromptFramePopped; - - public event Action DebuggerStopped; - - public event Action DebuggerResumed; - - public event Action BreakpointUpdated; - - private void OnDebuggerStopped(object sender, DebuggerStopEventArgs args) - { - DebuggerStopped?.Invoke(this, args); - } - - private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs args) - { - BreakpointUpdated?.Invoke(this, args); - } - - private void UnregisterCurrentRunspace() - { - if (_frameStack.Count > 0) - { - PowerShellContextFrame frame = _frameStack.Peek(); - UnregisterRunspace(frame.Runspace); - } - } - - private PromptFrameType PopAndDisposeCurrentRunspace() - { - PowerShellContextFrame frame = _frameStack.Pop(); - UnregisterRunspace(frame.Runspace); - frame.Runspace.Dispose(); - return frame.FrameType; - } - - private void UnregisterRunspace(Runspace runspace) - { - if (runspace.Debugger != null) - { - runspace.Debugger.DebuggerStop -= OnDebuggerStopped; - runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; - } - } - - private struct PowerShellContextFrame - { - public Runspace Runspace { get; set; } - - public PromptFrameType FrameType { get; set; } - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 1636a33bc..39dbe877c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -37,13 +37,11 @@ internal class PowerShellExecutionService : IDisposable public static PowerShellExecutionService CreateAndStart( ILoggerFactory loggerFactory, ILanguageServer languageServer, - PowerShellEventService eventService, HostStartupInfo hostInfo) { var executionService = new PowerShellExecutionService( loggerFactory, languageServer, - eventService, hostInfo.Name, hostInfo.Version, hostInfo.LanguageMode, @@ -66,8 +64,6 @@ public static PowerShellExecutionService CreateAndStart( private readonly ILanguageServer _languageServer; - private readonly PowerShellEventService _eventService; - private readonly string _hostName; private readonly Version _hostVersion; @@ -80,13 +76,11 @@ public static PowerShellExecutionService CreateAndStart( private readonly IReadOnlyList _additionalModulesToLoad; - private readonly Stack _pwshStack; - private Thread _pipelineThread; private CancellationTokenSource _currentExecutionCancellationSource; - private SMA.PowerShell _currentPwsh; + private Execution.PowerShellContext _pwshContext; private PowerShellConsoleService _consoleService; @@ -97,7 +91,6 @@ public static PowerShellExecutionService CreateAndStart( private PowerShellExecutionService( ILoggerFactory loggerFactory, ILanguageServer languageServer, - PowerShellEventService eventService, string hostName, Version hostVersion, PSLanguageMode languageMode, @@ -108,7 +101,6 @@ private PowerShellExecutionService( _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(); _languageServer = languageServer; - _eventService = eventService; _stopThreadCancellationSource = new CancellationTokenSource(); _executionQueue = new BlockingCollection(); _hostName = hostName; @@ -117,7 +109,6 @@ private PowerShellExecutionService( _internalHost = internalHost; _profilePaths = profilePaths; _additionalModulesToLoad = additionalModules; - _pwshStack = new Stack(); } public EngineIntrinsics EngineIntrinsics { get; private set; } @@ -128,12 +119,22 @@ private PowerShellExecutionService( public ConsoleReadLine ReadLine { get; private set; } + public event Action PromptFramePushed; + + public event Action PromptFramePopped; + + public event Action DebuggerStopped; + + public event Action DebuggerResumed; + + public event Action BreakpointUpdated; + public Task ExecuteDelegateAsync( Func func, string representation, CancellationToken cancellationToken) { - TResult appliedFunc(CancellationToken cancellationToken) => func(_currentPwsh, cancellationToken); + TResult appliedFunc(CancellationToken cancellationToken) => func(_pwshContext.CurrentPowerShell, cancellationToken); return ExecuteDelegateAsync(appliedFunc, representation, cancellationToken); } @@ -142,7 +143,7 @@ public Task ExecuteDelegateAsync( string representation, CancellationToken cancellationToken) { - void appliedAction(CancellationToken cancellationToken) => action(_currentPwsh, cancellationToken); + void appliedAction(CancellationToken cancellationToken) => action(_pwshContext.CurrentPowerShell, cancellationToken); return ExecuteDelegateAsync(appliedAction, representation, cancellationToken); } @@ -167,7 +168,7 @@ public Task> ExecutePSCommandAsync( PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) { - Task> result = QueueTask(new SynchronousPowerShellTask(_logger, _eventService, _currentPwsh, EditorServicesHost, psCommand, executionOptions, cancellationToken)); + Task> result = QueueTask(new SynchronousPowerShellTask(_logger, _pwshContext, EditorServicesHost, psCommand, executionOptions, cancellationToken)); if (executionOptions.InterruptCommandPrompt) { @@ -203,9 +204,7 @@ public void RegisterConsoleService(PowerShellConsoleService consoleService) public void EnterNestedPrompt() { - _currentPwsh = _currentPwsh.CreateNestedPowerShell(); - _pwshStack.Push(_currentPwsh); - _eventService.PushFrame(_currentPwsh.Runspace, PromptFrameType.NestedPrompt); + _pwshContext.PushNestedPowerShell(); try { @@ -224,9 +223,7 @@ public void EnterNestedPrompt() } finally { - _currentPwsh.Dispose(); - _currentPwsh = _pwshStack.Pop(); - _eventService.PopFrame(); + _pwshContext.PopPowerShell(); _exitNestedPrompt = false; } } @@ -239,10 +236,7 @@ public void ExitNestedPrompt() public void Dispose() { Stop(); - foreach (SMA.PowerShell pwsh in _pwshStack) - { - pwsh.Dispose(); - } + _pwshContext.Dispose(); } private Task QueueTask(SynchronousTask task) @@ -262,22 +256,15 @@ private void Start() private void Initialize() { - _currentPwsh = SMA.PowerShell.Create(); - _pwshStack.Push(_currentPwsh); - - _eventService.PushFrame(_currentPwsh.Runspace, PromptFrameType.Normal); - ReadLine = new ConsoleReadLine(); EditorServicesHost = new EditorServicesConsolePSHost(_loggerFactory, this, _hostName, _hostVersion, _internalHost, ReadLine); - _currentPwsh.Runspace = CreateRunspace(EditorServicesHost, _languageMode); - Runspace.DefaultRunspace = _currentPwsh.Runspace; - EditorServicesHost.RegisterRunspace(_currentPwsh.Runspace); + SetPowerShellContext(EditorServicesHost, _languageMode); - var engineIntrinsics = (EngineIntrinsics)_currentPwsh.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); + EngineIntrinsics = (EngineIntrinsics)_pwshContext.CurrentPowerShell.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); - PSReadLineProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, _currentPwsh); + PSReadLineProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, _pwshContext.CurrentPowerShell); PSReadLineProxy.OverrideIdleHandler(OnPowerShellIdle); ReadLine.RegisterExecutionDependencies(this, PSReadLineProxy); @@ -318,7 +305,7 @@ private void RunConsumerLoop() private void OnPowerShellIdle() { - while (_currentPwsh.InvocationStateInfo.State == PSInvocationState.Completed + while (_pwshContext.CurrentPowerShell.InvocationStateInfo.State == PSInvocationState.Completed && _executionQueue.TryTake(out ISynchronousTask task)) { RunTaskSynchronously(task); @@ -352,7 +339,7 @@ private void SetExecutionPolicy() { // We want to get the list hierarchy of execution policies // Calling the cmdlet is the simplest way to do that - IReadOnlyList policies = _currentPwsh + IReadOnlyList policies = _pwshContext.CurrentPowerShell .AddCommand("Microsoft.PowerShell.Security\\Get-ExecutionPolicy") .AddParameter("-List") .InvokeAndClear(); @@ -395,7 +382,7 @@ private void SetExecutionPolicy() _logger.LogTrace("Setting execution policy to {Policy}", policyToSet); try { - _currentPwsh.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") + _pwshContext.CurrentPowerShell.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") .AddParameter("Scope", ExecutionPolicyScope.Process) .AddParameter("ExecutionPolicy", policyToSet) .AddParameter("Force") @@ -416,7 +403,7 @@ private void LoadProfiles() AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.CurrentUserAllHosts), _profilePaths.CurrentUserAllHosts); AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.CurrentUserCurrentHost), _profilePaths.CurrentUserCurrentHost); - _currentPwsh.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); + _pwshContext.CurrentPowerShell.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); } private void AddProfileMemberAndLoadIfExists(PSObject profileVariable, string profileName, string profilePath) @@ -425,7 +412,7 @@ private void AddProfileMemberAndLoadIfExists(PSObject profileVariable, string pr if (File.Exists(profilePath)) { - _currentPwsh.AddScript(profilePath, useLocalScope: false) + _pwshContext.CurrentPowerShell.AddScript(profilePath, useLocalScope: false) .AddOutputCommand() .InvokeAndClear(); } @@ -433,29 +420,44 @@ private void AddProfileMemberAndLoadIfExists(PSObject profileVariable, string pr private void ImportModule(string moduleNameOrPath) { - _currentPwsh.AddCommand("Microsoft.PowerShell.Core\\Import-Module") + _pwshContext.CurrentPowerShell.AddCommand("Microsoft.PowerShell.Core\\Import-Module") .AddParameter("-Name", moduleNameOrPath) .InvokeAndClear(); } - private static Runspace CreateRunspace( - PSHost psHost, - PSLanguageMode languageMode) + private void SetPowerShellContext(EditorServicesConsolePSHost psHost, PSLanguageMode languageMode) { - InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" - ? InitialSessionState.CreateDefault() - : InitialSessionState.CreateDefault2(); + _pwshContext = Execution.PowerShellContext.Create(psHost, languageMode); + _pwshContext.PromptFramePushed += OnPromptFramePushed; + _pwshContext.PromptFramePopped += OnPromptFramePopped; + _pwshContext.DebuggerStopped += OnDebuggerStopped; + _pwshContext.DebuggerResumed += OnDebuggerResumed; + _pwshContext.BreakpointUpdated += OnBreakpointUpdated; + } - iss.LanguageMode = languageMode; + private void OnPromptFramePushed(object sender, PromptFramePushedArgs promptFramePushedArgs) + { + PromptFramePushed?.Invoke(this, promptFramePushedArgs); + } - Runspace runspace = RunspaceFactory.CreateRunspace(psHost, iss); + private void OnPromptFramePopped(object sender, PromptFramePoppedArgs promptFramePoppedArgs) + { + PromptFramePopped?.Invoke(this, promptFramePoppedArgs); + } - runspace.SetApartmentStateToSta(); - runspace.ThreadOptions = PSThreadOptions.ReuseThread; + private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) + { + DebuggerStopped?.Invoke(this, debuggerStopEventArgs); + } - runspace.Open(); + private void OnDebuggerResumed(object sender, DebuggerResumedArgs debuggerResumedArgs) + { + DebuggerResumed?.Invoke(this, debuggerResumedArgs); + } - return runspace; + private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) + { + BreakpointUpdated?.Invoke(this, breakpointUpdatedEventArgs); } } } From 81de70f7f64f7c0cebb37de088972e858747725c Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 8 Jul 2020 10:42:53 -0700 Subject: [PATCH 014/176] Make nested prompts work --- .../Services/PowerShell/Execution/PowerShellContext.cs | 8 +++++--- .../Services/PowerShell/PowerShellExecutionService.cs | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs index e6edecac8..2b2a101b6 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs @@ -63,7 +63,7 @@ public static PowerShellContext Create(EditorServicesConsolePSHost psHost, PSLan Runspace runspace = RunspaceFactory.CreateRunspace(psHost, iss); runspace.SetApartmentStateToSta(); - runspace.ThreadOptions = PSThreadOptions.ReuseThread; + runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; runspace.Open(); @@ -105,8 +105,10 @@ public void ProcessDebuggerResult(DebuggerCommandResults result) public void PushNestedPowerShell() { - var pwsh = CurrentPowerShell.CreateNestedPowerShell(); - pwsh.Runspace.ThreadOptions = PSThreadOptions.ReuseThread; + // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild + // So we must use the RunspaceMode.CurrentRunspace option on PowerShell.Create() instead + var pwsh = SMA.PowerShell.Create(RunspaceMode.CurrentRunspace); + pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; PushFrame(new ContextFrame(pwsh, PromptFrameType.NestedPrompt)); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 39dbe877c..e5858c1a5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -12,7 +12,6 @@ using System.IO; using System.Management.Automation; using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -249,8 +248,9 @@ private void Start() { _pipelineThread = new Thread(RunConsumerLoop) { - Name = "PSES Execution Service Thread" + Name = "PSES Execution Service Thread", }; + _pipelineThread.SetApartmentState(ApartmentState.STA); _pipelineThread.Start(); } From dc73db6a93e84546f555c7995475533b018038fe Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 8 Jul 2020 16:28:05 -0700 Subject: [PATCH 015/176] Fix nesting issues with stacks --- .../PowerShell/Execution/PowerShellContext.cs | 56 +++++-- .../PowerShell/PowerShellConsoleService.cs | 147 ++++++++++++------ .../PowerShell/PowerShellExecutionService.cs | 79 ++++++---- 3 files changed, 200 insertions(+), 82 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs index 2b2a101b6..c94febb68 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs @@ -6,6 +6,7 @@ using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Runspaces; +using System.Threading; using SMA = System.Management.Automation; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution @@ -85,7 +86,12 @@ private PowerShellContext() public SMA.PowerShell CurrentPowerShell { - get => _frameStack.Count > 0 ? _frameStack.Peek().PowerShell : null; + get => _frameStack.Peek().PowerShell; + } + + public CancellationTokenSource CurrentCancellationSource + { + get => _frameStack.Peek().CancellationTokenSource; } public event Action PromptFramePushed; @@ -110,7 +116,7 @@ public void PushNestedPowerShell() var pwsh = SMA.PowerShell.Create(RunspaceMode.CurrentRunspace); pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; - PushFrame(new ContextFrame(pwsh, PromptFrameType.NestedPrompt)); + PushFrame(new ContextFrame(pwsh, PromptFrameType.NestedPrompt, new CancellationTokenSource())); } public void PopPowerShell() @@ -131,7 +137,7 @@ private void PushPowerShell(Runspace runspace) var pwsh = SMA.PowerShell.Create(); pwsh.Runspace = runspace; - PushFrame(new ContextFrame(pwsh, PromptFrameType.Normal)); + PushFrame(new ContextFrame(pwsh, PromptFrameType.Normal, new CancellationTokenSource())); } private void PushFrame(ContextFrame frame) @@ -145,10 +151,16 @@ private void PushFrame(ContextFrame frame) private void PopFrame() { ContextFrame frame = _frameStack.Pop(); - frame.PowerShell.Runspace.Debugger.DebuggerStop -= OnDebuggerStopped; - frame.PowerShell.Runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; - PromptFramePopped?.Invoke(this, new PromptFramePoppedArgs(frame.FrameType)); - frame.PowerShell.Dispose(); + try + { + frame.PowerShell.Runspace.Debugger.DebuggerStop -= OnDebuggerStopped; + frame.PowerShell.Runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; + PromptFramePopped?.Invoke(this, new PromptFramePoppedArgs(frame.FrameType)); + } + finally + { + frame.Dispose(); + } } private void OnDebuggerStopped(object sender, DebuggerStopEventArgs args) @@ -161,17 +173,43 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs args) BreakpointUpdated?.Invoke(this, args); } - private class ContextFrame + private class ContextFrame : IDisposable { - public ContextFrame(SMA.PowerShell powerShell, PromptFrameType frameType) + private bool disposedValue; + + public ContextFrame(SMA.PowerShell powerShell, PromptFrameType frameType, CancellationTokenSource cancellationTokenSource) { PowerShell = powerShell; FrameType = frameType; + CancellationTokenSource = cancellationTokenSource; } public SMA.PowerShell PowerShell { get; } public PromptFrameType FrameType { get; } + + public CancellationTokenSource CancellationTokenSource { get; } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + PowerShell.Dispose(); + CancellationTokenSource.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs index 746805efe..07df5035b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -26,6 +26,8 @@ public static PowerShellConsoleService CreateAndStart( executionService.PSReadLineProxy); } + private readonly object _stackLock = new object(); + private readonly ILogger _logger; private readonly PowerShellExecutionService _executionService; @@ -36,11 +38,9 @@ public static PowerShellConsoleService CreateAndStart( private readonly PSReadLineProxy _psrlProxy; - private Task _consoleLoopThread; - - private CancellationTokenSource _replLoopCancellationSource; + private readonly Stack _replLoopTaskStack; - private CancellationTokenSource _currentCommandCancellationSource; + private readonly Stack _currentCommandCancellationSourceStack; private bool _canCancel; @@ -52,6 +52,8 @@ private PowerShellConsoleService( PSReadLineProxy psrlProxy) { _logger = loggerFactory.CreateLogger(); + _replLoopTaskStack = new Stack(); + _currentCommandCancellationSourceStack = new Stack(); _executionService = executionService; _editorServicesHost = editorServicesHost; _readLine = readLine; @@ -60,6 +62,11 @@ private PowerShellConsoleService( public void Dispose() { + while (_replLoopTaskStack.Count > 0) + { + StopCurrentRepl(); + } + System.Console.CancelKeyPress -= OnCancelKeyPress; _executionService.PromptFramePushed -= OnPromptFramePushed; _executionService.PromptFramePopped -= OnPromptFramePopped; @@ -67,81 +74,104 @@ public void Dispose() public void StartRepl() { - _replLoopCancellationSource = new CancellationTokenSource(); System.Console.CancelKeyPress += OnCancelKeyPress; System.Console.OutputEncoding = Encoding.UTF8; _psrlProxy.OverrideReadKey(ReadKey); - _consoleLoopThread = Task.Run(RunReplLoopAsync, _replLoopCancellationSource.Token); _executionService.RegisterConsoleService(this); _executionService.PromptFramePushed += OnPromptFramePushed; _executionService.PromptFramePopped += OnPromptFramePopped; + PushNewReplTask(); } public void CancelCurrentPrompt() { if (_canCancel) { - _currentCommandCancellationSource?.Cancel(); + _currentCommandCancellationSourceStack.Peek().Cancel(); } } - public void Stop() + public void StopCurrentRepl() { - _replLoopCancellationSource.Cancel(); + ReplTask replTask; + lock (_stackLock) + { + replTask = _replLoopTaskStack.Pop(); + } + replTask.CancellationTokenSource.Cancel(); } private async Task RunReplLoopAsync() { - while (!_replLoopCancellationSource.IsCancellationRequested) + ReplTask replTask; + lock (_stackLock) { - _currentCommandCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_replLoopCancellationSource.Token); - _canCancel = true; - try + replTask = _replLoopTaskStack.Peek(); + } + + using (replTask.CancellationTokenSource) + { + while (!replTask.CancellationTokenSource.IsCancellationRequested) { - string promptString = (await GetPromptOutputAsync().ConfigureAwait(false)).FirstOrDefault() ?? "PS> "; + var currentCommandCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(replTask.CancellationTokenSource.Token); + _currentCommandCancellationSourceStack.Push(currentCommandCancellationSource); + _canCancel = true; + try + { - WritePrompt(promptString); + string promptString = (await GetPromptOutputAsync(currentCommandCancellationSource.Token).ConfigureAwait(false)).FirstOrDefault() ?? "PS> "; - string userInput = await InvokeReadLineAsync().ConfigureAwait(false); + if (currentCommandCancellationSource.IsCancellationRequested) + { + continue; + } - if (_currentCommandCancellationSource.IsCancellationRequested) + WritePrompt(promptString); + + string userInput = await InvokeReadLineAsync(currentCommandCancellationSource.Token).ConfigureAwait(false); + + if (currentCommandCancellationSource.IsCancellationRequested) + { + continue; + } + + await InvokeInputAsync(userInput, currentCommandCancellationSource.Token).ConfigureAwait(false); + + if (replTask.CancellationTokenSource.IsCancellationRequested) + { + break; + } + + if (currentCommandCancellationSource.IsCancellationRequested) + { + _editorServicesHost.UI.WriteLine(); + } + } + catch (OperationCanceledException) { - _editorServicesHost.UI.WriteLine(); continue; } + catch (Exception e) + { - await InvokeInputAsync(userInput).ConfigureAwait(false); - - if (_currentCommandCancellationSource.IsCancellationRequested) + } + finally { - _editorServicesHost.UI.WriteLine(); + _canCancel = false; + _currentCommandCancellationSourceStack.Pop().Dispose(); } } - catch (OperationCanceledException) - { - continue; - } - catch (Exception e) - { - - } - finally - { - _canCancel = false; - _currentCommandCancellationSource.Dispose(); - _currentCommandCancellationSource = null; - } } } - private Task> GetPromptOutputAsync() + private Task> GetPromptOutputAsync(CancellationToken cancellationToken) { var promptCommand = new PSCommand().AddCommand("prompt"); return _executionService.ExecutePSCommandAsync( promptCommand, new PowerShellExecutionOptions(), - CancellationToken.None); + cancellationToken); } private void WritePrompt(string promptString) @@ -149,12 +179,12 @@ private void WritePrompt(string promptString) _editorServicesHost.UI.Write(promptString); } - private Task InvokeReadLineAsync() + private Task InvokeReadLineAsync(CancellationToken cancellationToken) { - return _readLine.ReadCommandLineAsync(_currentCommandCancellationSource.Token); + return _readLine.ReadCommandLineAsync(cancellationToken); } - private Task InvokeInputAsync(string input) + private Task InvokeInputAsync(string input, CancellationToken cancellationToken) { var command = new PSCommand().AddScript(input); var executionOptions = new PowerShellExecutionOptions @@ -163,7 +193,7 @@ private Task InvokeInputAsync(string input) AddToHistory = true }; - return _executionService.ExecutePSCommandAsync(command, executionOptions, _currentCommandCancellationSource.Token); + return _executionService.ExecutePSCommandAsync(command, executionOptions, cancellationToken); } private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) @@ -171,19 +201,42 @@ private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) CancelCurrentPrompt(); } - public void OnPromptFramePushed(object sender, PromptFramePushedArgs args) + private void OnPromptFramePushed(object sender, PromptFramePushedArgs args) { - Task.Run(RunReplLoopAsync); + PushNewReplTask(); } - public void OnPromptFramePopped(object sender, PromptFramePoppedArgs args) + private void OnPromptFramePopped(object sender, PromptFramePoppedArgs args) { - CancelCurrentPrompt(); + StopCurrentRepl(); } private ConsoleKeyInfo ReadKey(bool intercept) { - return ConsoleProxy.SafeReadKey(intercept, _currentCommandCancellationSource?.Token ?? CancellationToken.None); + return ConsoleProxy.SafeReadKey(intercept, _currentCommandCancellationSourceStack.Peek().Token); + } + + private void PushNewReplTask() + { + var replLoopCancellationSource = new CancellationTokenSource(); + lock (_stackLock) + { + _replLoopTaskStack.Push(new ReplTask(Task.Run(RunReplLoopAsync, replLoopCancellationSource.Token), replLoopCancellationSource)); + } + } + + + private struct ReplTask + { + public ReplTask(Task loopTask, CancellationTokenSource cancellationTokenSource) + { + LoopTask = loopTask; + CancellationTokenSource = cancellationTokenSource; + } + + public Task LoopTask { get; } + + public CancellationTokenSource CancellationTokenSource { get; } } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index e5858c1a5..7bd82bb96 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -53,7 +53,7 @@ public static PowerShellExecutionService CreateAndStart( return executionService; } - private readonly CancellationTokenSource _stopThreadCancellationSource; + private readonly CancellationTokenSource _consumerThreadCancellationSource; private readonly BlockingCollection _executionQueue; @@ -77,6 +77,8 @@ public static PowerShellExecutionService CreateAndStart( private Thread _pipelineThread; + private CancellationTokenSource _loopCancellationSource; + private CancellationTokenSource _currentExecutionCancellationSource; private Execution.PowerShellContext _pwshContext; @@ -100,7 +102,7 @@ private PowerShellExecutionService( _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(); _languageServer = languageServer; - _stopThreadCancellationSource = new CancellationTokenSource(); + _consumerThreadCancellationSource = new CancellationTokenSource(); _executionQueue = new BlockingCollection(); _hostName = hostName; _hostVersion = hostVersion; @@ -184,7 +186,7 @@ public Task ExecutePSCommandAsync( public void Stop() { - _stopThreadCancellationSource.Cancel(); + _consumerThreadCancellationSource.Cancel(); _pipelineThread.Join(); } @@ -204,26 +206,13 @@ public void RegisterConsoleService(PowerShellConsoleService consoleService) public void EnterNestedPrompt() { _pwshContext.PushNestedPowerShell(); - try { - foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(_stopThreadCancellationSource.Token)) - { - RunTaskSynchronously(task); - - if (_exitNestedPrompt) - { - break; - } - } - } - catch (OperationCanceledException) - { + RunConsumerLoop(); } finally { _pwshContext.PopPowerShell(); - _exitNestedPrompt = false; } } @@ -246,7 +235,7 @@ private Task QueueTask(SynchronousTask task) private void Start() { - _pipelineThread = new Thread(RunConsumerLoop) + _pipelineThread = new Thread(RunTopLevelConsumerLoop) { Name = "PSES Execution Service Thread", }; @@ -286,29 +275,67 @@ private void Initialize() } } - private void RunConsumerLoop() + private void RunTopLevelConsumerLoop() { Initialize(); + RunConsumerLoop(); + } + + private void RunConsumerLoop() + { + _loopCancellationSource = CancellationTokenSource.CreateLinkedTokenSource( + _pwshContext.CurrentCancellationSource.Token, + _consumerThreadCancellationSource.Token); + try { - foreach (ISynchronousTask synchronousTask in _executionQueue.GetConsumingEnumerable(_stopThreadCancellationSource.Token)) + foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(_loopCancellationSource.Token)) { - RunTaskSynchronously(synchronousTask); + RunTaskSynchronously(task); + + if (_exitNestedPrompt) + { + break; + } } } catch (OperationCanceledException) { - // End nicely + // Catch cancellations to end nicely + } + finally + { + _exitNestedPrompt = false; } } private void OnPowerShellIdle() { - while (_pwshContext.CurrentPowerShell.InvocationStateInfo.State == PSInvocationState.Completed - && _executionQueue.TryTake(out ISynchronousTask task)) + if (_executionQueue.Count == 0) + { + return; + } + + _loopCancellationSource = CancellationTokenSource.CreateLinkedTokenSource( + _pwshContext.CurrentCancellationSource.Token, + _consumerThreadCancellationSource.Token); + + try + { + while (_pwshContext.CurrentPowerShell.InvocationStateInfo.State == PSInvocationState.Completed + && _executionQueue.TryTake(out ISynchronousTask task)) + { + RunTaskSynchronously(task); + } + } + catch (OperationCanceledException) + { + + } + finally { - RunTaskSynchronously(task); + _loopCancellationSource.Dispose(); } // TODO: Run nested pipeline here for engine event handling @@ -321,7 +348,7 @@ private void RunTaskSynchronously(ISynchronousTask task) return; } - using (_currentExecutionCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_stopThreadCancellationSource.Token)) + using (_currentExecutionCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_loopCancellationSource.Token)) { _taskRunning = true; try From 993a8c12dadfd0dd21aec56c0d102f44d0b740bf Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 14 Jul 2020 14:18:02 -0700 Subject: [PATCH 016/176] Fix handling of prompt cancellation --- .../PowerShell/Execution/PowerShellContext.cs | 7 ++ .../PowerShell/PowerShellConsoleService.cs | 82 +++++++++++++++---- .../PowerShell/PowerShellExecutionService.cs | 12 +-- 3 files changed, 78 insertions(+), 23 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs index c94febb68..2163df77f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs @@ -50,6 +50,13 @@ public DebuggerResumedArgs(DebuggerResumeAction? resumeAction) DebuggerResumeAction? ResumeAction { get; } } + internal class PromptCancellationRequestedArgs + { + } + + internal class NestedPromptExitedArgs + { + } internal class PowerShellContext : IDisposable { diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs index 07df5035b..f00252019 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -44,6 +44,10 @@ public static PowerShellConsoleService CreateAndStart( private bool _canCancel; + private ConsoleKeyInfo? _lastKey; + + private bool _exiting; + private PowerShellConsoleService( ILoggerFactory loggerFactory, PowerShellExecutionService executionService, @@ -58,6 +62,7 @@ private PowerShellConsoleService( _editorServicesHost = editorServicesHost; _readLine = readLine; _psrlProxy = psrlProxy; + _exiting = false; } public void Dispose() @@ -69,7 +74,8 @@ public void Dispose() System.Console.CancelKeyPress -= OnCancelKeyPress; _executionService.PromptFramePushed -= OnPromptFramePushed; - _executionService.PromptFramePopped -= OnPromptFramePopped; + _executionService.PromptCancellationRequested -= OnPromptCancellationRequested; + _executionService.NestedPromptExited -= OnNestedPromptExited; } public void StartRepl() @@ -77,15 +83,21 @@ public void StartRepl() System.Console.CancelKeyPress += OnCancelKeyPress; System.Console.OutputEncoding = Encoding.UTF8; _psrlProxy.OverrideReadKey(ReadKey); - _executionService.RegisterConsoleService(this); _executionService.PromptFramePushed += OnPromptFramePushed; - _executionService.PromptFramePopped += OnPromptFramePopped; + _executionService.PromptCancellationRequested += OnPromptCancellationRequested; + _executionService.NestedPromptExited += OnNestedPromptExited; PushNewReplTask(); } public void CancelCurrentPrompt() { - if (_canCancel) + bool canCancel = false; + lock (_stackLock) + { + canCancel = _replLoopTaskStack.Peek().CanCancel; + } + + if (canCancel) { _currentCommandCancellationSourceStack.Peek().Cancel(); } @@ -98,6 +110,7 @@ public void StopCurrentRepl() { replTask = _replLoopTaskStack.Pop(); } + replTask.CancellationTokenSource.Cancel(); } @@ -113,9 +126,9 @@ private async Task RunReplLoopAsync() { while (!replTask.CancellationTokenSource.IsCancellationRequested) { - var currentCommandCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(replTask.CancellationTokenSource.Token); + var currentCommandCancellationSource = new CancellationTokenSource(); _currentCommandCancellationSourceStack.Push(currentCommandCancellationSource); - _canCancel = true; + replTask.CanCancel = true; try { @@ -130,8 +143,13 @@ private async Task RunReplLoopAsync() string userInput = await InvokeReadLineAsync(currentCommandCancellationSource.Token).ConfigureAwait(false); - if (currentCommandCancellationSource.IsCancellationRequested) + if (string.IsNullOrEmpty(userInput)) { + if (currentCommandCancellationSource.IsCancellationRequested + || LastKeyWasCtrlC()) + { + _editorServicesHost.UI.WriteLine(); + } continue; } @@ -141,11 +159,6 @@ private async Task RunReplLoopAsync() { break; } - - if (currentCommandCancellationSource.IsCancellationRequested) - { - _editorServicesHost.UI.WriteLine(); - } } catch (OperationCanceledException) { @@ -157,11 +170,13 @@ private async Task RunReplLoopAsync() } finally { - _canCancel = false; + replTask.CanCancel = false; _currentCommandCancellationSourceStack.Pop().Dispose(); } } } + + _exiting = false; } private Task> GetPromptOutputAsync(CancellationToken cancellationToken) @@ -206,14 +221,39 @@ private void OnPromptFramePushed(object sender, PromptFramePushedArgs args) PushNewReplTask(); } - private void OnPromptFramePopped(object sender, PromptFramePoppedArgs args) + private void OnPromptCancellationRequested(object sender, PromptCancellationRequestedArgs args) { + CancelCurrentPrompt(); + } + + private void OnNestedPromptExited(object sender, NestedPromptExitedArgs args) + { + _exiting = true; StopCurrentRepl(); } + private void OnReplCanceled() + { + if (_exiting) + { + return; + } + + CancelCurrentPrompt(); + } + private ConsoleKeyInfo ReadKey(bool intercept) { - return ConsoleProxy.SafeReadKey(intercept, _currentCommandCancellationSourceStack.Peek().Token); + _lastKey = ConsoleProxy.SafeReadKey(intercept, _currentCommandCancellationSourceStack.Peek().Token); + return _lastKey.Value; + } + + private bool LastKeyWasCtrlC() + { + return _lastKey != null + && _lastKey.Value.Key == ConsoleKey.C + && (_lastKey.Value.Modifiers & ConsoleModifiers.Control) != 0 + && (_lastKey.Value.Modifiers & ConsoleModifiers.Alt) == 0; } private void PushNewReplTask() @@ -221,22 +261,30 @@ private void PushNewReplTask() var replLoopCancellationSource = new CancellationTokenSource(); lock (_stackLock) { - _replLoopTaskStack.Push(new ReplTask(Task.Run(RunReplLoopAsync, replLoopCancellationSource.Token), replLoopCancellationSource)); + replLoopCancellationSource.Token.Register(OnReplCanceled); + var replTask = new ReplTask(Task.Run(RunReplLoopAsync, replLoopCancellationSource.Token), replLoopCancellationSource); + _replLoopTaskStack.Push(replTask); } } - private struct ReplTask + private class ReplTask { public ReplTask(Task loopTask, CancellationTokenSource cancellationTokenSource) { LoopTask = loopTask; CancellationTokenSource = cancellationTokenSource; + Guid = Guid.NewGuid(); + CanCancel = false; } public Task LoopTask { get; } public CancellationTokenSource CancellationTokenSource { get; } + + public Guid Guid { get; } + + public bool CanCancel { get; set; } } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 7bd82bb96..d25f2bc2a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -130,6 +130,10 @@ private PowerShellExecutionService( public event Action BreakpointUpdated; + public event Action PromptCancellationRequested; + + public event Action NestedPromptExited; + public Task ExecuteDelegateAsync( Func func, string representation, @@ -173,7 +177,7 @@ public Task> ExecutePSCommandAsync( if (executionOptions.InterruptCommandPrompt) { - _consoleService?.CancelCurrentPrompt(); + PromptCancellationRequested?.Invoke(this, new PromptCancellationRequestedArgs()); } return result; @@ -198,11 +202,6 @@ public void CancelCurrentTask() } } - public void RegisterConsoleService(PowerShellConsoleService consoleService) - { - _consoleService = consoleService; - } - public void EnterNestedPrompt() { _pwshContext.PushNestedPowerShell(); @@ -218,6 +217,7 @@ public void EnterNestedPrompt() public void ExitNestedPrompt() { + NestedPromptExited?.Invoke(this, new NestedPromptExitedArgs()); _exitNestedPrompt = true; } From c3cabcd4904d0e1a0a7b8b9f6781f9c0de4923af Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 14 Jul 2020 15:52:04 -0700 Subject: [PATCH 017/176] Use concurrent datastructure for stacks --- .../PowerShell/PowerShellConsoleService.cs | 123 ++++++++++-------- 1 file changed, 71 insertions(+), 52 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs index f00252019..26a8d4ea1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -2,7 +2,9 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Utility; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Management.Automation; @@ -26,8 +28,6 @@ public static PowerShellConsoleService CreateAndStart( executionService.PSReadLineProxy); } - private readonly object _stackLock = new object(); - private readonly ILogger _logger; private readonly PowerShellExecutionService _executionService; @@ -38,11 +38,12 @@ public static PowerShellConsoleService CreateAndStart( private readonly PSReadLineProxy _psrlProxy; - private readonly Stack _replLoopTaskStack; - - private readonly Stack _currentCommandCancellationSourceStack; + private readonly ConcurrentStack _replLoopTaskStack; - private bool _canCancel; + // This is required because PSRL will keep prompting for keys as we push a new REPL task + // Keeping current command cancellations on their own stack simplifies access to the cancellation token + // for the REPL command that's currently running. + private readonly ConcurrentStack _commandCancellationStack; private ConsoleKeyInfo? _lastKey; @@ -56,8 +57,8 @@ private PowerShellConsoleService( PSReadLineProxy psrlProxy) { _logger = loggerFactory.CreateLogger(); - _replLoopTaskStack = new Stack(); - _currentCommandCancellationSourceStack = new Stack(); + _replLoopTaskStack = new ConcurrentStack(); + _commandCancellationStack = new ConcurrentStack(); _executionService = executionService; _editorServicesHost = editorServicesHost; _readLine = readLine; @@ -91,61 +92,55 @@ public void StartRepl() public void CancelCurrentPrompt() { - bool canCancel = false; - lock (_stackLock) - { - canCancel = _replLoopTaskStack.Peek().CanCancel; - } - - if (canCancel) + if (_commandCancellationStack.TryPeek(out CommandCancellation commandCancellation)) { - _currentCommandCancellationSourceStack.Peek().Cancel(); + commandCancellation.CancellationSource?.Cancel(); } } public void StopCurrentRepl() { - ReplTask replTask; - lock (_stackLock) + if (_replLoopTaskStack.TryPop(out ReplTask currentReplTask)) { - replTask = _replLoopTaskStack.Pop(); + currentReplTask.ReplCancellationSource.Cancel(); } - - replTask.CancellationTokenSource.Cancel(); } private async Task RunReplLoopAsync() { - ReplTask replTask; - lock (_stackLock) - { - replTask = _replLoopTaskStack.Peek(); - } + _replLoopTaskStack.TryPeek(out ReplTask replTask); - using (replTask.CancellationTokenSource) + try { - while (!replTask.CancellationTokenSource.IsCancellationRequested) + while (!replTask.ReplCancellationSource.IsCancellationRequested) { - var currentCommandCancellationSource = new CancellationTokenSource(); - _currentCommandCancellationSourceStack.Push(currentCommandCancellationSource); - replTask.CanCancel = true; + var currentCommandCancellation = new CommandCancellation(); + _commandCancellationStack.Push(currentCommandCancellation); + try { - string promptString = (await GetPromptOutputAsync(currentCommandCancellationSource.Token).ConfigureAwait(false)).FirstOrDefault() ?? "PS> "; + string promptString = (await GetPromptOutputAsync(currentCommandCancellation.CancellationSource.Token).ConfigureAwait(false)).FirstOrDefault() ?? "PS> "; - if (currentCommandCancellationSource.IsCancellationRequested) + if (currentCommandCancellation.CancellationSource.IsCancellationRequested) { continue; } WritePrompt(promptString); - string userInput = await InvokeReadLineAsync(currentCommandCancellationSource.Token).ConfigureAwait(false); + string userInput = await InvokeReadLineAsync(currentCommandCancellation.CancellationSource.Token).ConfigureAwait(false); + // If the user input was empty it's because: + // - the user provided no input + // - the readline task was canceled + // - CtrlC was sent to readline (which does not propagate a cancellation) + // + // In any event there's nothing to run in PowerShell, so we just loop back to the prompt again. + // However, we must distinguish the last two scenarios, since PSRL will print a new line in those cases if (string.IsNullOrEmpty(userInput)) { - if (currentCommandCancellationSource.IsCancellationRequested + if (currentCommandCancellation.CancellationSource.IsCancellationRequested || LastKeyWasCtrlC()) { _editorServicesHost.UI.WriteLine(); @@ -153,9 +148,9 @@ private async Task RunReplLoopAsync() continue; } - await InvokeInputAsync(userInput, currentCommandCancellationSource.Token).ConfigureAwait(false); + await InvokeInputAsync(userInput, currentCommandCancellation.CancellationSource.Token).ConfigureAwait(false); - if (replTask.CancellationTokenSource.IsCancellationRequested) + if (replTask.ReplCancellationSource.IsCancellationRequested) { break; } @@ -166,17 +161,22 @@ private async Task RunReplLoopAsync() } catch (Exception e) { - + // TODO: Do something here } finally { - replTask.CanCancel = false; - _currentCommandCancellationSourceStack.Pop().Dispose(); + _commandCancellationStack.TryPop(out CommandCancellation _); + currentCommandCancellation.CancellationSource.Dispose(); + currentCommandCancellation.CancellationSource = null; } } } + finally + { + _exiting = false; + replTask.ReplCancellationSource.Dispose(); + } - _exiting = false; } private Task> GetPromptOutputAsync(CancellationToken cancellationToken) @@ -234,6 +234,14 @@ private void OnNestedPromptExited(object sender, NestedPromptExitedArgs args) private void OnReplCanceled() { + // Ordinarily, when the REPL is canceled + // we want to propagate the cancellation to any currently running command. + // However, when the REPL is canceled by an 'exit' command, + // the currently running command is doing the cancellation. + // Not only would canceling it not make sense + // but trying to cancel it from its own thread will deadlock PowerShell. + // Instead we just let the command progress. + if (_exiting) { return; @@ -244,7 +252,14 @@ private void OnReplCanceled() private ConsoleKeyInfo ReadKey(bool intercept) { - _lastKey = ConsoleProxy.SafeReadKey(intercept, _currentCommandCancellationSourceStack.Peek().Token); + _commandCancellationStack.TryPeek(out CommandCancellation commandCancellation); + + // PSRL doesn't tell us when CtrlC was sent. + // So instead we keep track of the last key here. + // This isn't functionally required, + // but helps us determine when the prompt needs a newline added + + _lastKey = ConsoleProxy.SafeReadKey(intercept, commandCancellation.CancellationSource.Token); return _lastKey.Value; } @@ -259,12 +274,9 @@ private bool LastKeyWasCtrlC() private void PushNewReplTask() { var replLoopCancellationSource = new CancellationTokenSource(); - lock (_stackLock) - { - replLoopCancellationSource.Token.Register(OnReplCanceled); - var replTask = new ReplTask(Task.Run(RunReplLoopAsync, replLoopCancellationSource.Token), replLoopCancellationSource); - _replLoopTaskStack.Push(replTask); - } + replLoopCancellationSource.Token.Register(OnReplCanceled); + var replTask = new ReplTask(Task.Run(RunReplLoopAsync, replLoopCancellationSource.Token), replLoopCancellationSource); + _replLoopTaskStack.Push(replTask); } @@ -273,18 +285,25 @@ private class ReplTask public ReplTask(Task loopTask, CancellationTokenSource cancellationTokenSource) { LoopTask = loopTask; - CancellationTokenSource = cancellationTokenSource; + ReplCancellationSource = cancellationTokenSource; Guid = Guid.NewGuid(); - CanCancel = false; } public Task LoopTask { get; } - public CancellationTokenSource CancellationTokenSource { get; } + public CancellationTokenSource ReplCancellationSource { get; } public Guid Guid { get; } + } + + private class CommandCancellation + { + public CommandCancellation() + { + CancellationSource = new CancellationTokenSource(); + } - public bool CanCancel { get; set; } + public CancellationTokenSource CancellationSource { get; set; } } } } From 6f7c564e85036aec4a761a0fc8363f737c29b6d7 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 16 Jul 2020 20:31:52 -0700 Subject: [PATCH 018/176] Add simple debug REPL support --- .../PowerShell/Execution/PowerShellContext.cs | 7 +- .../Execution/SynchronousPowerShellTask.cs | 41 +++- .../PowerShell/PowerShellConsoleService.cs | 5 +- .../PowerShell/PowerShellExecutionService.cs | 219 ++++++++++++++---- .../Utility/PowerShellExtensions.cs | 21 +- 5 files changed, 238 insertions(+), 55 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs index 2163df77f..6195fd2c1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs @@ -47,7 +47,7 @@ public DebuggerResumedArgs(DebuggerResumeAction? resumeAction) ResumeAction = resumeAction; } - DebuggerResumeAction? ResumeAction { get; } + public DebuggerResumeAction? ResumeAction { get; } } internal class PromptCancellationRequestedArgs @@ -113,7 +113,10 @@ public CancellationTokenSource CurrentCancellationSource public void ProcessDebuggerResult(DebuggerCommandResults result) { - DebuggerResumed?.Invoke(this, new DebuggerResumedArgs(result.ResumeAction)); + if (result.ResumeAction != null) + { + DebuggerResumed?.Invoke(this, new DebuggerResumedArgs(result.ResumeAction)); + } } public void PushNestedPowerShell() diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index b4437e7a8..e5f09cc52 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -45,13 +45,13 @@ public override IReadOnlyList Run(CancellationToken cancellationToken) { _pwsh = _pwshContext.CurrentPowerShell; - cancellationToken.Register(Cancel); - if (_executionOptions.WriteInputToHost) { _psHost.UI.WriteLine(_psCommand.GetInvocationText()); } + _pwsh.Commands = _psCommand; + if (_pwsh.Runspace.Debugger.IsActive) { return ExecuteInDebugger(cancellationToken); @@ -67,13 +67,13 @@ public override string ToString() private IReadOnlyList ExecuteNormally(CancellationToken cancellationToken) { - _pwsh.Commands = _psCommand; - if (_executionOptions.WriteOutputToHost) { _pwsh.AddOutputCommand(); } + cancellationToken.Register(CancelNormalExecution); + Collection result = null; try { @@ -116,15 +116,40 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationToken) { + cancellationToken.Register(CancelDebugExecution); + var outputCollection = new PSDataCollection(); + + if (_executionOptions.WriteOutputToHost) + { + _pwsh.AddDebugOutputCommand(); + + // Use an inline delegate here, since otherwise we need a cast -- allocation < cast + outputCollection.DataAdded += (object sender, DataAddedEventArgs args) => + { + for (int i = args.Index; i < outputCollection.Count; i++) + { + _psHost.UI.WriteLine(outputCollection[i].ToString()); + } + }; + } + DebuggerCommandResults debuggerResult = _pwsh.Runspace.Debugger.ProcessCommand(_psCommand, outputCollection); _pwshContext.ProcessDebuggerResult(debuggerResult); + // Optimisation to save wasted computation if we're going to throw the output away anyway + if (_executionOptions.WriteOutputToHost) + { + return Array.Empty(); + } + + // If we've been asked for a PSObject, no need to allocate a new collection if (typeof(TResult) == typeof(PSObject)) { return (IReadOnlyList)outputCollection; } + // Otherwise, convert things over var results = new List(outputCollection.Count); foreach (PSObject outputResult in outputCollection) { @@ -133,13 +158,17 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT results.Add((TResult)result); } } - return results; } - private void Cancel() + private void CancelNormalExecution() { _pwsh.Stop(); } + + private void CancelDebugExecution() + { + _pwsh.Runspace.Debugger.StopProcessCommand(); + } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs index 26a8d4ea1..c7cb6e6f6 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -49,6 +49,8 @@ public static PowerShellConsoleService CreateAndStart( private bool _exiting; + private bool _debugging; + private PowerShellConsoleService( ILoggerFactory loggerFactory, PowerShellExecutionService executionService, @@ -64,6 +66,7 @@ private PowerShellConsoleService( _readLine = readLine; _psrlProxy = psrlProxy; _exiting = false; + _debugging = false; } public void Dispose() @@ -137,7 +140,7 @@ private async Task RunReplLoopAsync() // - CtrlC was sent to readline (which does not propagate a cancellation) // // In any event there's nothing to run in PowerShell, so we just loop back to the prompt again. - // However, we must distinguish the last two scenarios, since PSRL will print a new line in those cases + // However, we must distinguish the last two scenarios, since PSRL will not print a new line in those cases. if (string.IsNullOrEmpty(userInput)) { if (currentCommandCancellation.CancellationSource.IsCancellationRequested diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index d25f2bc2a..f7956cd45 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -12,6 +12,7 @@ using System.IO; using System.Management.Automation; using System.Management.Automation.Host; +using System.Management.Automation.Language; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -75,20 +76,20 @@ public static PowerShellExecutionService CreateAndStart( private readonly IReadOnlyList _additionalModulesToLoad; + private readonly DebuggingContext _debuggingContext; + private Thread _pipelineThread; - private CancellationTokenSource _loopCancellationSource; + private ConcurrentStack _loopCancellationStack; - private CancellationTokenSource _currentExecutionCancellationSource; + private ConcurrentStack _commandCancellationStack; private Execution.PowerShellContext _pwshContext; - private PowerShellConsoleService _consoleService; - - private bool _taskRunning; - private bool _exitNestedPrompt; + private DebuggerResumeAction? _debuggerResumeAction; + private PowerShellExecutionService( ILoggerFactory loggerFactory, ILanguageServer languageServer, @@ -103,7 +104,10 @@ private PowerShellExecutionService( _logger = loggerFactory.CreateLogger(); _languageServer = languageServer; _consumerThreadCancellationSource = new CancellationTokenSource(); + _loopCancellationStack = new ConcurrentStack(); + _commandCancellationStack = new ConcurrentStack(); _executionQueue = new BlockingCollection(); + _debuggingContext = new DebuggingContext(); _hostName = hostName; _hostVersion = hostVersion; _languageMode = languageMode; @@ -196,21 +200,36 @@ public void Stop() public void CancelCurrentTask() { - if (_taskRunning) + if (_commandCancellationStack.TryPeek(out CancellationTokenSource currentCommandCancellation)) { - _currentExecutionCancellationSource?.Cancel(); + currentCommandCancellation.Cancel(); } } public void EnterNestedPrompt() { _pwshContext.PushNestedPowerShell(); + var cancellationContext = LoopCancellationContext.EnterNew(this, _pwshContext.CurrentCancellationSource, _consumerThreadCancellationSource); try { - RunConsumerLoop(); + foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationContext.CancellationToken)) + { + RunTaskSynchronously(task, cancellationContext.CancellationToken); + + if (_exitNestedPrompt) + { + break; + } + } + } + catch (OperationCanceledException) + { + // Catch cancellations to end nicely } finally { + _exitNestedPrompt = false; + cancellationContext.Dispose(); _pwshContext.PopPowerShell(); } } @@ -279,25 +298,15 @@ private void RunTopLevelConsumerLoop() { Initialize(); - RunConsumerLoop(); - } - - private void RunConsumerLoop() - { - _loopCancellationSource = CancellationTokenSource.CreateLinkedTokenSource( - _pwshContext.CurrentCancellationSource.Token, - _consumerThreadCancellationSource.Token); - + var cancellationContext = LoopCancellationContext.EnterNew( + this, + _pwshContext.CurrentCancellationSource, + _consumerThreadCancellationSource); try { - foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(_loopCancellationSource.Token)) + foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationContext.CancellationToken)) { - RunTaskSynchronously(task); - - if (_exitNestedPrompt) - { - break; - } + RunTaskSynchronously(task, cancellationContext.CancellationToken); } } catch (OperationCanceledException) @@ -306,7 +315,7 @@ private void RunConsumerLoop() } finally { - _exitNestedPrompt = false; + cancellationContext.Dispose(); } } @@ -317,16 +326,17 @@ private void OnPowerShellIdle() return; } - _loopCancellationSource = CancellationTokenSource.CreateLinkedTokenSource( - _pwshContext.CurrentCancellationSource.Token, - _consumerThreadCancellationSource.Token); + var loopCancellationContext = LoopCancellationContext.EnterNew( + this, + _pwshContext.CurrentCancellationSource, + _consumerThreadCancellationSource); try { while (_pwshContext.CurrentPowerShell.InvocationStateInfo.State == PSInvocationState.Completed && _executionQueue.TryTake(out ISynchronousTask task)) { - RunTaskSynchronously(task); + RunTaskSynchronously(task, loopCancellationContext.CancellationToken); } } catch (OperationCanceledException) @@ -335,30 +345,22 @@ private void OnPowerShellIdle() } finally { - _loopCancellationSource.Dispose(); + loopCancellationContext.Dispose(); } // TODO: Run nested pipeline here for engine event handling } - private void RunTaskSynchronously(ISynchronousTask task) + private void RunTaskSynchronously(ISynchronousTask task, CancellationToken loopCancellationToken) { if (task.IsCanceled) { return; } - using (_currentExecutionCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_loopCancellationSource.Token)) + using (var cancellationContext = TaskCancellationContext.EnterNew(this, loopCancellationToken)) { - _taskRunning = true; - try - { - task.ExecuteSynchronously(_currentExecutionCancellationSource.Token); - } - finally - { - _taskRunning = false; - } + task.ExecuteSynchronously(cancellationContext.CancellationToken); } } @@ -474,11 +476,51 @@ private void OnPromptFramePopped(object sender, PromptFramePoppedArgs promptFram private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) { + _debuggingContext.OnDebuggerStop(sender, debuggerStopEventArgs); + + _pwshContext.PushNestedPowerShell(); + DebuggerStopped?.Invoke(this, debuggerStopEventArgs); + + var cancellationContext = LoopCancellationContext.EnterNew( + this, + _pwshContext.CurrentCancellationSource, + _consumerThreadCancellationSource); + + var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_debuggingContext.DebuggerResumeCancellationToken.Value, cancellationContext.CancellationToken); + + try + { + // Run commands, but cancelling our blocking wait if the debugger resumes + foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationSource.Token)) + { + // We don't want to cancel the current command when the debugger resumes, + // since that command will be resuming the debugger. + // Instead let it complete and check the cancellation afterward. + RunTaskSynchronously(task, cancellationContext.CancellationToken); + + if (cancellationSource.IsCancellationRequested) + { + break; + } + } + } + catch (OperationCanceledException) + { + // Catch cancellations to end nicely + } + finally + { + cancellationSource.Dispose(); + cancellationContext.Dispose(); + _pwshContext.PopPowerShell(); + } } private void OnDebuggerResumed(object sender, DebuggerResumedArgs debuggerResumedArgs) { + _debuggingContext.OnDebuggerResume(sender, debuggerResumedArgs); + DebuggerResumed?.Invoke(this, debuggerResumedArgs); } @@ -486,5 +528,98 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs break { BreakpointUpdated?.Invoke(this, breakpointUpdatedEventArgs); } + + private class DebuggingContext + { + private CancellationTokenSource _debuggerCancellationTokenSource; + + public CancellationToken? DebuggerResumeCancellationToken => _debuggerCancellationTokenSource?.Token; + + public DebuggerResumeAction? LastResumeAction { get; private set; } + + public void OnDebuggerStop(object sender, DebuggerStopEventArgs debuggerStopEventArgs) + { + _debuggerCancellationTokenSource = new CancellationTokenSource(); + } + + public void OnDebuggerResume(object sender, DebuggerResumedArgs debuggerResumedArgs) + { + LastResumeAction = debuggerResumedArgs.ResumeAction; + + if (_debuggerCancellationTokenSource != null) + { + try + { + _debuggerCancellationTokenSource.Cancel(); + } + finally + { + _debuggerCancellationTokenSource.Dispose(); + _debuggerCancellationTokenSource = null; + } + } + } + } + + private readonly struct LoopCancellationContext : IDisposable + { + public static LoopCancellationContext EnterNew( + PowerShellExecutionService executionService, + CancellationTokenSource cts1, + CancellationTokenSource cts2) + { + var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token); + executionService._loopCancellationStack.Push(cancellationTokenSource); + return new LoopCancellationContext(executionService._loopCancellationStack, cancellationTokenSource.Token); + } + + private readonly ConcurrentStack _loopCancellationStack; + + public readonly CancellationToken CancellationToken; + + private LoopCancellationContext( + ConcurrentStack loopCancellationStack, + CancellationToken cancellationToken) + { + _loopCancellationStack = loopCancellationStack; + CancellationToken = cancellationToken; + } + + public void Dispose() + { + if (_loopCancellationStack.TryPop(out CancellationTokenSource loopCancellation)) + { + loopCancellation.Dispose(); + } + } + } + + private readonly struct TaskCancellationContext : IDisposable + { + public static TaskCancellationContext EnterNew(PowerShellExecutionService executionService, CancellationToken loopCancellationToken) + { + var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(loopCancellationToken); + executionService._commandCancellationStack.Push(cancellationTokenSource); + return new TaskCancellationContext(executionService._commandCancellationStack, cancellationTokenSource.Token); + } + + private TaskCancellationContext(ConcurrentStack commandCancellationStack, CancellationToken cancellationToken) + { + _commandCancellationStack = commandCancellationStack; + CancellationToken = cancellationToken; + } + + private readonly ConcurrentStack _commandCancellationStack; + + public readonly CancellationToken CancellationToken; + + public void Dispose() + { + if (_commandCancellationStack.TryPop(out CancellationTokenSource taskCancellation)) + { + taskCancellation.Dispose(); + } + } + } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs index bd9d1b879..a96639aff 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs @@ -35,10 +35,15 @@ public static void InvokeAndClear(this SMA.PowerShell pwsh) public static SMA.PowerShell AddOutputCommand(this SMA.PowerShell pwsh) { - Command lastCommand = pwsh.Commands.Commands[pwsh.Commands.Commands.Count - 1]; - lastCommand.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output); - lastCommand.MergeMyResults(PipelineResultTypes.Information, PipelineResultTypes.Output); - return pwsh.AddCommand("Microsoft.Powershell.Core\\Out-Default", useLocalScope: true); + return pwsh.MergePipelineResults() + .AddCommand("Microsoft.Powershell.Core\\Out-Default", useLocalScope: true); + } + + public static SMA.PowerShell AddDebugOutputCommand(this SMA.PowerShell pwsh) + { + return pwsh.MergePipelineResults() + .AddCommand("Microsoft.Powershell.Core\\Out-String", useLocalScope: true) + .AddParameter("Stream"); } public static string GetErrorString(this SMA.PowerShell pwsh) @@ -78,5 +83,13 @@ private static StringBuilder AddErrorString(this StringBuilder sb, ErrorRecord e return sb; } + + private static SMA.PowerShell MergePipelineResults(this SMA.PowerShell pwsh) + { + Command lastCommand = pwsh.Commands.Commands[pwsh.Commands.Commands.Count - 1]; + lastCommand.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output); + lastCommand.MergeMyResults(PipelineResultTypes.Information, PipelineResultTypes.Output); + return pwsh; + } } } From 2f0822ddaed164945d0a1fa435e8df7d1e7a54da Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 21 Jul 2020 11:47:37 -0700 Subject: [PATCH 019/176] Fix out-string usage in debugger --- .../Execution/SynchronousPowerShellTask.cs | 25 ++++++++----------- .../PowerShell/PowerShellExecutionService.cs | 8 +++--- .../PowerShell/Utility/PSCommandExtensions.cs | 21 ++++++++++++++++ .../Utility/PowerShellExtensions.cs | 22 ++++++---------- 4 files changed, 44 insertions(+), 32 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index e5f09cc52..14a0d3f43 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -50,14 +50,9 @@ public override IReadOnlyList Run(CancellationToken cancellationToken) _psHost.UI.WriteLine(_psCommand.GetInvocationText()); } - _pwsh.Commands = _psCommand; - - if (_pwsh.Runspace.Debugger.IsActive) - { - return ExecuteInDebugger(cancellationToken); - } - - return ExecuteNormally(cancellationToken); + return _pwsh.Runspace.Debugger.IsActive + ? ExecuteInDebugger(cancellationToken) + : ExecuteNormally(cancellationToken); } public override string ToString() @@ -69,7 +64,7 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok { if (_executionOptions.WriteOutputToHost) { - _pwsh.AddOutputCommand(); + _psCommand.AddOutputCommand(); } cancellationToken.Register(CancelNormalExecution); @@ -77,7 +72,7 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok Collection result = null; try { - result = _pwsh.InvokeAndClear(); + result = _pwsh.InvokeCommand(_psCommand); if (_executionOptions.PropagateCancellationToCaller) { @@ -99,9 +94,11 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok throw; } - _pwsh.AddOutputCommand() - .AddParameter("InputObject", e.ErrorRecord.AsPSObject()) - .InvokeAndClear(); + var command = new PSCommand() + .AddOutputCommand() + .AddParameter("InputObject", e.ErrorRecord.AsPSObject()); + + _pwsh.InvokeCommand(command); } finally { @@ -122,7 +119,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT if (_executionOptions.WriteOutputToHost) { - _pwsh.AddDebugOutputCommand(); + _psCommand.AddDebugOutputCommand(); // Use an inline delegate here, since otherwise we need a cast -- allocation < cast outputCollection.DataAdded += (object sender, DataAddedEventArgs args) => diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index f7956cd45..b6540abd7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -441,9 +441,11 @@ private void AddProfileMemberAndLoadIfExists(PSObject profileVariable, string pr if (File.Exists(profilePath)) { - _pwshContext.CurrentPowerShell.AddScript(profilePath, useLocalScope: false) - .AddOutputCommand() - .InvokeAndClear(); + var psCommand = new PSCommand() + .AddScript(profilePath, useLocalScope: false) + .AddOutputCommand(); + + _pwshContext.CurrentPowerShell.InvokeCommand(psCommand); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs index 10df46bb3..2393c4fe4 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs @@ -6,6 +6,27 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility { internal static class PSCommandExtensions { + public static PSCommand AddOutputCommand(this PSCommand psCommand) + { + return psCommand.MergePipelineResults() + .AddCommand("Out-Default", useLocalScope: true); + } + + public static PSCommand AddDebugOutputCommand(this PSCommand psCommand) + { + return psCommand.MergePipelineResults() + .AddCommand("Out-String", useLocalScope: true) + .AddParameter("Stream"); + } + + public static PSCommand MergePipelineResults(this PSCommand psCommand) + { + Command lastCommand = psCommand.Commands[psCommand.Commands.Count - 1]; + lastCommand.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output); + lastCommand.MergeMyResults(PipelineResultTypes.Information, PipelineResultTypes.Output); + return psCommand; + } + public static string GetInvocationText(this PSCommand command) { Command lastCommand = command.Commands[0]; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs index a96639aff..558e8659d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.ObjectModel; +using System.ComponentModel; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Text; @@ -33,17 +34,16 @@ public static void InvokeAndClear(this SMA.PowerShell pwsh) } } - public static SMA.PowerShell AddOutputCommand(this SMA.PowerShell pwsh) + public static Collection InvokeCommand(this SMA.PowerShell pwsh, PSCommand psCommand) { - return pwsh.MergePipelineResults() - .AddCommand("Microsoft.Powershell.Core\\Out-Default", useLocalScope: true); + pwsh.Commands = psCommand; + return pwsh.InvokeAndClear(); } - public static SMA.PowerShell AddDebugOutputCommand(this SMA.PowerShell pwsh) + public static void InvokeCommand(this SMA.PowerShell pwsh, PSCommand psCommand) { - return pwsh.MergePipelineResults() - .AddCommand("Microsoft.Powershell.Core\\Out-String", useLocalScope: true) - .AddParameter("Stream"); + pwsh.Commands = psCommand; + pwsh.InvokeAndClear(); } public static string GetErrorString(this SMA.PowerShell pwsh) @@ -83,13 +83,5 @@ private static StringBuilder AddErrorString(this StringBuilder sb, ErrorRecord e return sb; } - - private static SMA.PowerShell MergePipelineResults(this SMA.PowerShell pwsh) - { - Command lastCommand = pwsh.Commands.Commands[pwsh.Commands.Commands.Count - 1]; - lastCommand.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output); - lastCommand.MergeMyResults(PipelineResultTypes.Information, PipelineResultTypes.Output); - return pwsh; - } } } From 66903b602aecee40cae5061c79b158461b1877e0 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 21 Jul 2020 16:02:44 -0700 Subject: [PATCH 020/176] Attempt to handle debug exit --- .../PowerShell/Execution/PowerShellExecutionOptions.cs | 2 ++ .../PowerShell/Execution/SynchronousPowerShellTask.cs | 2 +- .../Services/PowerShell/PowerShellConsoleService.cs | 3 ++- .../Services/PowerShell/PowerShellExecutionService.cs | 8 +++++++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs index b2c046f93..a96565679 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs @@ -11,5 +11,7 @@ public struct PowerShellExecutionOptions public bool PropagateCancellationToCaller { get; set; } public bool InterruptCommandPrompt { get; set; } + + public bool NoDebuggerExecution { get; set; } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 14a0d3f43..ee8a3134f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -50,7 +50,7 @@ public override IReadOnlyList Run(CancellationToken cancellationToken) _psHost.UI.WriteLine(_psCommand.GetInvocationText()); } - return _pwsh.Runspace.Debugger.IsActive + return !_executionOptions.NoDebuggerExecution && _pwsh.Runspace.Debugger.InBreakpoint ? ExecuteInDebugger(cancellationToken) : ExecuteNormally(cancellationToken); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs index c7cb6e6f6..f45db9956 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -85,6 +85,7 @@ public void Dispose() public void StartRepl() { System.Console.CancelKeyPress += OnCancelKeyPress; + System.Console.InputEncoding = Encoding.UTF8; System.Console.OutputEncoding = Encoding.UTF8; _psrlProxy.OverrideReadKey(ReadKey); _executionService.PromptFramePushed += OnPromptFramePushed; @@ -208,7 +209,7 @@ private Task InvokeInputAsync(string input, CancellationToken cancellationToken) var executionOptions = new PowerShellExecutionOptions { WriteOutputToHost = true, - AddToHistory = true + AddToHistory = true, }; return _executionService.ExecutePSCommandAsync(command, executionOptions, cancellationToken); diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index b6540abd7..4880d234e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -515,14 +515,20 @@ private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStop { cancellationSource.Dispose(); cancellationContext.Dispose(); + _exitNestedPrompt = false; _pwshContext.PopPowerShell(); } + + if (_debuggingContext.LastResumeAction == DebuggerResumeAction.Stop) + { + CancelCurrentTask(); + } } private void OnDebuggerResumed(object sender, DebuggerResumedArgs debuggerResumedArgs) { _debuggingContext.OnDebuggerResume(sender, debuggerResumedArgs); - + ExitNestedPrompt(); DebuggerResumed?.Invoke(this, debuggerResumedArgs); } From 05cb6b6b1c2775751995ca02d953af15936bcdc5 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 23 Jul 2020 10:51:02 -0700 Subject: [PATCH 021/176] Make debugger resume commands work --- .../PowerShell/PowerShellExecutionService.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 4880d234e..1e933f02f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -88,8 +88,6 @@ public static PowerShellExecutionService CreateAndStart( private bool _exitNestedPrompt; - private DebuggerResumeAction? _debuggerResumeAction; - private PowerShellExecutionService( ILoggerFactory loggerFactory, ILanguageServer languageServer, @@ -489,6 +487,8 @@ private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStop _pwshContext.CurrentCancellationSource, _consumerThreadCancellationSource); + // If the debugger is resumed while the execution queue listener is blocked on getting a new execution event, + // we must cancel the blocking call var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_debuggingContext.DebuggerResumeCancellationToken.Value, cancellationContext.CancellationToken); try @@ -501,8 +501,15 @@ private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStop // Instead let it complete and check the cancellation afterward. RunTaskSynchronously(task, cancellationContext.CancellationToken); + if (_debuggingContext.LastResumeAction != null) + { + debuggerStopEventArgs.ResumeAction = _debuggingContext.LastResumeAction.Value; + break; + } + if (cancellationSource.IsCancellationRequested) { + debuggerStopEventArgs.ResumeAction = DebuggerResumeAction.Stop; break; } } @@ -515,14 +522,10 @@ private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStop { cancellationSource.Dispose(); cancellationContext.Dispose(); + _debuggingContext.LastResumeAction = null; _exitNestedPrompt = false; _pwshContext.PopPowerShell(); } - - if (_debuggingContext.LastResumeAction == DebuggerResumeAction.Stop) - { - CancelCurrentTask(); - } } private void OnDebuggerResumed(object sender, DebuggerResumedArgs debuggerResumedArgs) @@ -543,7 +546,7 @@ private class DebuggingContext public CancellationToken? DebuggerResumeCancellationToken => _debuggerCancellationTokenSource?.Token; - public DebuggerResumeAction? LastResumeAction { get; private set; } + public DebuggerResumeAction? LastResumeAction { get; set; } public void OnDebuggerStop(object sender, DebuggerStopEventArgs debuggerStopEventArgs) { From 8950e7346b8f7c28b74f3a5fd0b8942fd974a354 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 24 Jul 2020 15:20:55 -0700 Subject: [PATCH 022/176] Change use of PS context and events to safely manage prompts --- .../PowerShell/Execution/PowerShellContext.cs | 96 ++++-- .../Host/EditorServicesConsolePSHost.cs | 37 +-- .../PowerShell/PowerShellConsoleService.cs | 65 ++-- .../PowerShell/PowerShellExecutionService.cs | 290 +++++++++--------- 4 files changed, 283 insertions(+), 205 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs index 6195fd2c1..479bd134c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs @@ -20,9 +20,9 @@ internal enum PromptFrameType Remote = 4, } - internal class PromptFramePushedArgs + internal class PowerShellPushedArgs { - public PromptFramePushedArgs(PromptFrameType frameType) + public PowerShellPushedArgs(PromptFrameType frameType) { FrameType = frameType; } @@ -30,9 +30,9 @@ public PromptFramePushedArgs(PromptFrameType frameType) public PromptFrameType FrameType { get; } } - internal class PromptFramePoppedArgs + internal class PowerShellPoppedArgs { - public PromptFramePoppedArgs(PromptFrameType frameType) + public PowerShellPoppedArgs(PromptFrameType frameType) { FrameType = frameType; } @@ -40,9 +40,9 @@ public PromptFramePoppedArgs(PromptFrameType frameType) public PromptFrameType FrameType { get; } } - internal class DebuggerResumedArgs + internal class DebuggerResumingArgs { - public DebuggerResumedArgs(DebuggerResumeAction? resumeAction) + public DebuggerResumingArgs(DebuggerResumeAction? resumeAction) { ResumeAction = resumeAction; } @@ -54,7 +54,7 @@ internal class PromptCancellationRequestedArgs { } - internal class NestedPromptExitedArgs + internal class NestedPromptExitingArgs { } @@ -76,10 +76,9 @@ public static PowerShellContext Create(EditorServicesConsolePSHost psHost, PSLan runspace.Open(); var context = new PowerShellContext(); - context.PushPowerShell(runspace); + context.PushInitialPowerShell(runspace); Runspace.DefaultRunspace = runspace; - psHost.RegisterRunspace(runspace); return context; } @@ -91,6 +90,8 @@ private PowerShellContext() _frameStack = new Stack(); } + public int PowerShellDepth => _frameStack.Count; + public SMA.PowerShell CurrentPowerShell { get => _frameStack.Peek().PowerShell; @@ -101,32 +102,57 @@ public CancellationTokenSource CurrentCancellationSource get => _frameStack.Peek().CancellationTokenSource; } - public event Action PromptFramePushed; + public bool IsExiting { get; private set; } + + public event Action PowerShellPushed; + + public event Action PowerShellPopped; - public event Action PromptFramePopped; + public event Action NestedPromptExiting; public event Action DebuggerStopped; - public event Action DebuggerResumed; + public event Action DebuggerResumed; public event Action BreakpointUpdated; + public void BeginExiting() + { + IsExiting = true; + NestedPromptExiting?.Invoke(this, new NestedPromptExitingArgs()); + } + public void ProcessDebuggerResult(DebuggerCommandResults result) { if (result.ResumeAction != null) { - DebuggerResumed?.Invoke(this, new DebuggerResumedArgs(result.ResumeAction)); + DebuggerResumed?.Invoke(this, new DebuggerResumingArgs(result.ResumeAction)); } } public void PushNestedPowerShell() { - // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild - // So we must use the RunspaceMode.CurrentRunspace option on PowerShell.Create() instead - var pwsh = SMA.PowerShell.Create(RunspaceMode.CurrentRunspace); - pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; + PushNestedPowerShell(PromptFrameType.Normal); + } - PushFrame(new ContextFrame(pwsh, PromptFrameType.NestedPrompt, new CancellationTokenSource())); + public void PushDebugPowerShell() + { + PushNestedPowerShell(PromptFrameType.Debug); + } + + public void PushPowerShell(Runspace runspace) + { + var pwsh = SMA.PowerShell.Create(); + pwsh.Runspace = runspace; + + PromptFrameType frameType = PromptFrameType.Normal; + + if (runspace.RunspaceIsRemote) + { + frameType |= PromptFrameType.Remote; + } + + PushFrame(new ContextFrame(pwsh, frameType, new CancellationTokenSource())); } public void PopPowerShell() @@ -134,6 +160,17 @@ public void PopPowerShell() PopFrame(); } + public bool TryPopPowerShell() + { + if (_frameStack.Count <= 1) + { + return false; + } + + PopFrame(); + return true; + } + public void Dispose() { while (_frameStack.Count > 0) @@ -142,7 +179,7 @@ public void Dispose() } } - private void PushPowerShell(Runspace runspace) + private void PushInitialPowerShell(Runspace runspace) { var pwsh = SMA.PowerShell.Create(); pwsh.Runspace = runspace; @@ -150,22 +187,39 @@ private void PushPowerShell(Runspace runspace) PushFrame(new ContextFrame(pwsh, PromptFrameType.Normal, new CancellationTokenSource())); } + private void PushNestedPowerShell(PromptFrameType frameType) + { + SMA.PowerShell pwsh = CreateNestedPowerShell(); + PromptFrameType newFrameType = _frameStack.Peek().FrameType | PromptFrameType.NestedPrompt | frameType; + PushFrame(new ContextFrame(pwsh, newFrameType, new CancellationTokenSource())); + } + + private SMA.PowerShell CreateNestedPowerShell() + { + // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild + // So we must use the RunspaceMode.CurrentRunspace option on PowerShell.Create() instead + var pwsh = SMA.PowerShell.Create(RunspaceMode.CurrentRunspace); + pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; + return pwsh; + } + private void PushFrame(ContextFrame frame) { _frameStack.Push(frame); frame.PowerShell.Runspace.Debugger.DebuggerStop += OnDebuggerStopped; frame.PowerShell.Runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; - PromptFramePushed?.Invoke(this, new PromptFramePushedArgs(frame.FrameType)); + PowerShellPushed?.Invoke(this, new PowerShellPushedArgs(frame.FrameType)); } private void PopFrame() { + IsExiting = false; ContextFrame frame = _frameStack.Pop(); try { frame.PowerShell.Runspace.Debugger.DebuggerStop -= OnDebuggerStopped; frame.PowerShell.Runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; - PromptFramePopped?.Invoke(this, new PromptFramePoppedArgs(frame.FrameType)); + PowerShellPopped?.Invoke(this, new PowerShellPoppedArgs(frame.FrameType)); } finally { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index 1c859f2f0..a0adad31c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; -using PowerShellEditorServices.Services; using System; using System.Globalization; using System.Management.Automation.Host; @@ -8,24 +7,24 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host { + using PowerShellContext = Execution.PowerShellContext; + internal class EditorServicesConsolePSHost : PSHost, IHostSupportsInteractiveSession { private readonly ILogger _logger; - private readonly PowerShellExecutionService _executionService; + private PowerShellContext _pwshContext; private Runspace _pushedRunspace; public EditorServicesConsolePSHost( ILoggerFactory loggerFactory, - PowerShellExecutionService executionService, string name, Version version, PSHost internalHost, ConsoleReadLine readline) { _logger = loggerFactory.CreateLogger(); - _executionService = executionService; _pushedRunspace = null; Name = name; Version = version; @@ -44,18 +43,18 @@ public EditorServicesConsolePSHost( public override Version Version { get; } - public bool IsRunspacePushed { get; private set; } + public Runspace Runspace => _pwshContext.CurrentPowerShell.Runspace; - public Runspace Runspace { get; private set; } + public bool IsRunspacePushed => _pwshContext.PowerShellDepth > 1; public override void EnterNestedPrompt() { - _executionService.EnterNestedPrompt(); + _pwshContext.PushNestedPowerShell(); } public override void ExitNestedPrompt() { - _executionService.ExitNestedPrompt(); + _pwshContext.BeginExiting(); } public override void NotifyBeginApplication() @@ -66,31 +65,25 @@ public override void NotifyEndApplication() { } - public void PopRunspace() + public void PushRunspace(Runspace runspace) { - Runspace = _pushedRunspace; - _pushedRunspace = null; - IsRunspacePushed = false; + _pwshContext.PushPowerShell(runspace); } - public void PushRunspace(Runspace runspace) + public void PopRunspace() { - _pushedRunspace = Runspace; - Runspace = runspace; - IsRunspacePushed = true; + // TODO: What if we're in a nested prompt in a remote/debug session? + _pwshContext.PopPowerShell(); } public override void SetShouldExit(int exitCode) { - if (IsRunspacePushed) - { - PopRunspace(); - } + _pwshContext.TryPopPowerShell(); } - public void RegisterRunspace(Runspace runspace) + internal void RegisterPowerShellContext(PowerShellContext pwshContext) { - Runspace = runspace; + _pwshContext = pwshContext; } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs index f45db9956..f1f4742a5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -69,6 +69,19 @@ private PowerShellConsoleService( _debugging = false; } + public void StartRepl() + { + System.Console.CancelKeyPress += OnCancelKeyPress; + System.Console.InputEncoding = Encoding.UTF8; + System.Console.OutputEncoding = Encoding.UTF8; + _psrlProxy.OverrideReadKey(ReadKey); + _executionService.PowerShellPushed += OnPromptFramePushed; + _executionService.PromptCancellationRequested += OnPromptCancellationRequested; + _executionService.NestedPromptExiting += OnNestedPromptExiting; + _executionService.DebuggerResuming += OnDebuggerResuming; + PushNewReplTask(); + } + public void Dispose() { while (_replLoopTaskStack.Count > 0) @@ -77,21 +90,10 @@ public void Dispose() } System.Console.CancelKeyPress -= OnCancelKeyPress; - _executionService.PromptFramePushed -= OnPromptFramePushed; + _executionService.PowerShellPushed -= OnPromptFramePushed; _executionService.PromptCancellationRequested -= OnPromptCancellationRequested; - _executionService.NestedPromptExited -= OnNestedPromptExited; - } - - public void StartRepl() - { - System.Console.CancelKeyPress += OnCancelKeyPress; - System.Console.InputEncoding = Encoding.UTF8; - System.Console.OutputEncoding = Encoding.UTF8; - _psrlProxy.OverrideReadKey(ReadKey); - _executionService.PromptFramePushed += OnPromptFramePushed; - _executionService.PromptCancellationRequested += OnPromptCancellationRequested; - _executionService.NestedPromptExited += OnNestedPromptExited; - PushNewReplTask(); + _executionService.NestedPromptExiting -= OnNestedPromptExiting; + _executionService.DebuggerResuming -= OnDebuggerResuming; } public void CancelCurrentPrompt() @@ -104,7 +106,7 @@ public void CancelCurrentPrompt() public void StopCurrentRepl() { - if (_replLoopTaskStack.TryPop(out ReplTask currentReplTask)) + if (_replLoopTaskStack.TryPeek(out ReplTask currentReplTask)) { currentReplTask.ReplCancellationSource.Cancel(); } @@ -178,6 +180,7 @@ private async Task RunReplLoopAsync() finally { _exiting = false; + _replLoopTaskStack.TryPop(out _); replTask.ReplCancellationSource.Dispose(); } @@ -220,7 +223,7 @@ private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) CancelCurrentPrompt(); } - private void OnPromptFramePushed(object sender, PromptFramePushedArgs args) + private void OnPromptFramePushed(object sender, PowerShellPushedArgs args) { PushNewReplTask(); } @@ -230,7 +233,13 @@ private void OnPromptCancellationRequested(object sender, PromptCancellationRequ CancelCurrentPrompt(); } - private void OnNestedPromptExited(object sender, NestedPromptExitedArgs args) + private void OnNestedPromptExiting(object sender, NestedPromptExitingArgs args) + { + _exiting = true; + StopCurrentRepl(); + } + + private void OnDebuggerResuming(object sender, DebuggerResumingArgs args) { _exiting = true; StopCurrentRepl(); @@ -277,23 +286,31 @@ private bool LastKeyWasCtrlC() private void PushNewReplTask() { - var replLoopCancellationSource = new CancellationTokenSource(); - replLoopCancellationSource.Token.Register(OnReplCanceled); - var replTask = new ReplTask(Task.Run(RunReplLoopAsync, replLoopCancellationSource.Token), replLoopCancellationSource); - _replLoopTaskStack.Push(replTask); + ReplTask.PushAndStart(_replLoopTaskStack, RunReplLoopAsync, OnReplCanceled); } private class ReplTask { - public ReplTask(Task loopTask, CancellationTokenSource cancellationTokenSource) + public static void PushAndStart( + ConcurrentStack replLoopTaskStack, + Func replLoopTaskFunc, + Action cancellationAction) + { + var replLoopCancellationSource = new CancellationTokenSource(); + replLoopCancellationSource.Token.Register(cancellationAction); + var replTask = new ReplTask(replLoopCancellationSource); + replLoopTaskStack.Push(replTask); + replTask.LoopTask = Task.Run(replLoopTaskFunc, replLoopCancellationSource.Token); + } + + public ReplTask(CancellationTokenSource cancellationTokenSource) { - LoopTask = loopTask; ReplCancellationSource = cancellationTokenSource; Guid = Guid.NewGuid(); } - public Task LoopTask { get; } + public Task LoopTask { get; private set; } public CancellationTokenSource ReplCancellationSource { get; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 1e933f02f..361bdd19d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -86,8 +86,6 @@ public static PowerShellExecutionService CreateAndStart( private Execution.PowerShellContext _pwshContext; - private bool _exitNestedPrompt; - private PowerShellExecutionService( ILoggerFactory loggerFactory, ILanguageServer languageServer, @@ -122,19 +120,19 @@ private PowerShellExecutionService( public ConsoleReadLine ReadLine { get; private set; } - public event Action PromptFramePushed; + public event Action PowerShellPushed; - public event Action PromptFramePopped; + public event Action PromptFramePopped; public event Action DebuggerStopped; - public event Action DebuggerResumed; + public event Action DebuggerResuming; public event Action BreakpointUpdated; public event Action PromptCancellationRequested; - public event Action NestedPromptExited; + public event Action NestedPromptExiting; public Task ExecuteDelegateAsync( Func func, @@ -204,40 +202,6 @@ public void CancelCurrentTask() } } - public void EnterNestedPrompt() - { - _pwshContext.PushNestedPowerShell(); - var cancellationContext = LoopCancellationContext.EnterNew(this, _pwshContext.CurrentCancellationSource, _consumerThreadCancellationSource); - try - { - foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationContext.CancellationToken)) - { - RunTaskSynchronously(task, cancellationContext.CancellationToken); - - if (_exitNestedPrompt) - { - break; - } - } - } - catch (OperationCanceledException) - { - // Catch cancellations to end nicely - } - finally - { - _exitNestedPrompt = false; - cancellationContext.Dispose(); - _pwshContext.PopPowerShell(); - } - } - - public void ExitNestedPrompt() - { - NestedPromptExited?.Invoke(this, new NestedPromptExitedArgs()); - _exitNestedPrompt = true; - } - public void Dispose() { Stop(); @@ -260,38 +224,6 @@ private void Start() _pipelineThread.Start(); } - private void Initialize() - { - ReadLine = new ConsoleReadLine(); - - EditorServicesHost = new EditorServicesConsolePSHost(_loggerFactory, this, _hostName, _hostVersion, _internalHost, ReadLine); - - SetPowerShellContext(EditorServicesHost, _languageMode); - - EngineIntrinsics = (EngineIntrinsics)_pwshContext.CurrentPowerShell.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); - - PSReadLineProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, _pwshContext.CurrentPowerShell); - PSReadLineProxy.OverrideIdleHandler(OnPowerShellIdle); - ReadLine.RegisterExecutionDependencies(this, PSReadLineProxy); - - if (VersionUtils.IsWindows) - { - SetExecutionPolicy(); - } - - LoadProfiles(); - - ImportModule(s_commandsModulePath); - - if (_additionalModulesToLoad != null && _additionalModulesToLoad.Count > 0) - { - foreach (string module in _additionalModulesToLoad) - { - ImportModule(module); - } - } - } - private void RunTopLevelConsumerLoop() { Initialize(); @@ -317,48 +249,53 @@ private void RunTopLevelConsumerLoop() } } - private void OnPowerShellIdle() + private void RunTaskSynchronously(ISynchronousTask task, CancellationToken loopCancellationToken) { - if (_executionQueue.Count == 0) + if (task.IsCanceled) { return; } - var loopCancellationContext = LoopCancellationContext.EnterNew( - this, - _pwshContext.CurrentCancellationSource, - _consumerThreadCancellationSource); - - try - { - while (_pwshContext.CurrentPowerShell.InvocationStateInfo.State == PSInvocationState.Completed - && _executionQueue.TryTake(out ISynchronousTask task)) - { - RunTaskSynchronously(task, loopCancellationContext.CancellationToken); - } - } - catch (OperationCanceledException) - { - - } - finally + using (var cancellationContext = TaskCancellationContext.EnterNew(this, loopCancellationToken)) { - loopCancellationContext.Dispose(); + task.ExecuteSynchronously(cancellationContext.CancellationToken); } - - // TODO: Run nested pipeline here for engine event handling } - private void RunTaskSynchronously(ISynchronousTask task, CancellationToken loopCancellationToken) + private void Initialize() { - if (task.IsCanceled) + ReadLine = new ConsoleReadLine(); + + EditorServicesHost = new EditorServicesConsolePSHost( + _loggerFactory, + _hostName, + _hostVersion, + _internalHost, + ReadLine); + + SetPowerShellContext(EditorServicesHost, _languageMode); + + EngineIntrinsics = (EngineIntrinsics)_pwshContext.CurrentPowerShell.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); + + PSReadLineProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, _pwshContext.CurrentPowerShell); + PSReadLineProxy.OverrideIdleHandler(OnPowerShellIdle); + ReadLine.RegisterExecutionDependencies(this, PSReadLineProxy); + + if (VersionUtils.IsWindows) { - return; + SetExecutionPolicy(); } - using (var cancellationContext = TaskCancellationContext.EnterNew(this, loopCancellationToken)) + LoadProfiles(); + + ImportModule(s_commandsModulePath); + + if (_additionalModulesToLoad != null && _additionalModulesToLoad.Count > 0) { - task.ExecuteSynchronously(cancellationContext.CancellationToken); + foreach (string module in _additionalModulesToLoad) + { + ImportModule(module); + } } } @@ -457,35 +394,38 @@ private void ImportModule(string moduleNameOrPath) private void SetPowerShellContext(EditorServicesConsolePSHost psHost, PSLanguageMode languageMode) { _pwshContext = Execution.PowerShellContext.Create(psHost, languageMode); - _pwshContext.PromptFramePushed += OnPromptFramePushed; - _pwshContext.PromptFramePopped += OnPromptFramePopped; + EditorServicesHost.RegisterPowerShellContext(_pwshContext); + _pwshContext.PowerShellPushed += OnPowerShellPushed; + _pwshContext.PowerShellPopped += OnPowerShellPopped; + _pwshContext.NestedPromptExiting += OnNestedPromptExiting; _pwshContext.DebuggerStopped += OnDebuggerStopped; - _pwshContext.DebuggerResumed += OnDebuggerResumed; + _pwshContext.DebuggerResumed += OnDebuggerResuming; _pwshContext.BreakpointUpdated += OnBreakpointUpdated; } - private void OnPromptFramePushed(object sender, PromptFramePushedArgs promptFramePushedArgs) + private void RunNestedLoop(in LoopCancellationContext cancellationContext) { - PromptFramePushed?.Invoke(this, promptFramePushedArgs); - } + try + { + foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationContext.CancellationToken)) + { + RunTaskSynchronously(task, cancellationContext.CancellationToken); - private void OnPromptFramePopped(object sender, PromptFramePoppedArgs promptFramePoppedArgs) - { - PromptFramePopped?.Invoke(this, promptFramePoppedArgs); + if (_pwshContext.IsExiting) + { + break; + } + } + } + catch (OperationCanceledException) + { + // Catch cancellations to end nicely + } } - private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) + private void RunDebugLoop(in LoopCancellationContext cancellationContext) { - _debuggingContext.OnDebuggerStop(sender, debuggerStopEventArgs); - - _pwshContext.PushNestedPowerShell(); - - DebuggerStopped?.Invoke(this, debuggerStopEventArgs); - - var cancellationContext = LoopCancellationContext.EnterNew( - this, - _pwshContext.CurrentCancellationSource, - _consumerThreadCancellationSource); + DebuggerStopped?.Invoke(this, _debuggingContext.LastStopEventArgs); // If the debugger is resumed while the execution queue listener is blocked on getting a new execution event, // we must cancel the blocking call @@ -501,15 +441,8 @@ private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStop // Instead let it complete and check the cancellation afterward. RunTaskSynchronously(task, cancellationContext.CancellationToken); - if (_debuggingContext.LastResumeAction != null) + if (_debuggingContext.ShouldResume) { - debuggerStopEventArgs.ResumeAction = _debuggingContext.LastResumeAction.Value; - break; - } - - if (cancellationSource.IsCancellationRequested) - { - debuggerStopEventArgs.ResumeAction = DebuggerResumeAction.Stop; break; } } @@ -521,18 +454,88 @@ private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStop finally { cancellationSource.Dispose(); - cancellationContext.Dispose(); - _debuggingContext.LastResumeAction = null; - _exitNestedPrompt = false; + } + } + + private void OnPowerShellIdle() + { + if (_executionQueue.Count == 0) + { + return; + } + + var loopCancellationContext = LoopCancellationContext.EnterNew( + this, + _pwshContext.CurrentCancellationSource, + _consumerThreadCancellationSource); + + try + { + while (_pwshContext.CurrentPowerShell.InvocationStateInfo.State == PSInvocationState.Completed + && _executionQueue.TryTake(out ISynchronousTask task)) + { + RunTaskSynchronously(task, loopCancellationContext.CancellationToken); + } + } + catch (OperationCanceledException) + { + + } + finally + { + loopCancellationContext.Dispose(); + } + + // TODO: Run nested pipeline here for engine event handling + } + + private void OnPowerShellPushed(object sender, PowerShellPushedArgs powerShellPushedArgs) + { + var cancellationContext = LoopCancellationContext.EnterNew( + this, + _pwshContext.CurrentCancellationSource, + _consumerThreadCancellationSource); + + PowerShellPushed?.Invoke(this, powerShellPushedArgs); + + try + { + if ((powerShellPushedArgs.FrameType & PromptFrameType.Debug) != 0) + { + RunDebugLoop(cancellationContext); + } + else + { + RunNestedLoop(cancellationContext); + } + } + finally + { _pwshContext.PopPowerShell(); + cancellationContext.Dispose(); } } - private void OnDebuggerResumed(object sender, DebuggerResumedArgs debuggerResumedArgs) + private void OnPowerShellPopped(object sender, PowerShellPoppedArgs promptFramePoppedArgs) + { + PromptFramePopped?.Invoke(this, promptFramePoppedArgs); + } + + private void OnNestedPromptExiting(object sender, NestedPromptExitingArgs nestedPromptExitingArgs) { - _debuggingContext.OnDebuggerResume(sender, debuggerResumedArgs); - ExitNestedPrompt(); - DebuggerResumed?.Invoke(this, debuggerResumedArgs); + NestedPromptExiting?.Invoke(this, nestedPromptExitingArgs); + } + + private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) + { + _debuggingContext.OnDebuggerStop(sender, debuggerStopEventArgs); + _pwshContext.PushDebugPowerShell(); + } + + private void OnDebuggerResuming(object sender, DebuggerResumingArgs debuggerResumedArgs) + { + _debuggingContext.OnDebuggerResuming(sender, debuggerResumedArgs); + DebuggerResuming?.Invoke(this, debuggerResumedArgs); } private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) @@ -546,16 +549,27 @@ private class DebuggingContext public CancellationToken? DebuggerResumeCancellationToken => _debuggerCancellationTokenSource?.Token; - public DebuggerResumeAction? LastResumeAction { get; set; } + public DebuggerStopEventArgs LastStopEventArgs { get; private set; } + + public bool ShouldResume { get; private set; } + + public void Reset() + { + LastStopEventArgs = null; + ShouldResume = false; + } public void OnDebuggerStop(object sender, DebuggerStopEventArgs debuggerStopEventArgs) { _debuggerCancellationTokenSource = new CancellationTokenSource(); + LastStopEventArgs = debuggerStopEventArgs; } - public void OnDebuggerResume(object sender, DebuggerResumedArgs debuggerResumedArgs) + public void OnDebuggerResuming(object sender, DebuggerResumingArgs debuggerResumedArgs) { - LastResumeAction = debuggerResumedArgs.ResumeAction; + ShouldResume = true; + + LastStopEventArgs.ResumeAction = debuggerResumedArgs.ResumeAction.Value; if (_debuggerCancellationTokenSource != null) { From c9fc9311210d3e21852c1a652b05d928ca2793ef Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 29 Jul 2020 13:57:46 -0700 Subject: [PATCH 023/176] Improve debug handling, add remoting support, improve idle processing --- .../PowerShell/Console/ConsoleReadLine.cs | 3 +- .../PowerShell/Execution/PowerShellContext.cs | 83 ++++++++++++++----- .../Host/EditorServicesConsolePSHost.cs | 7 +- .../PowerShell/PowerShellConsoleService.cs | 43 ++++++---- .../PowerShell/PowerShellExecutionService.cs | 63 ++++++++------ .../PowerShell/Utility/RunspaceExtensions.cs | 23 +++++ .../Utilities/CommandHelpers.cs | 6 ++ 7 files changed, 159 insertions(+), 69 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index 80cd0b93f..848af0ace 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -145,7 +145,8 @@ private Task ReadLineAsync(bool isCommandLine, CancellationToken cancell private string InvokePSReadLine(CancellationToken cancellationToken) { - return _psrlProxy.ReadLine(_executionService.EditorServicesHost.Runspace, _executionService.EngineIntrinsics, cancellationToken); + EngineIntrinsics engineIntrinsics = _executionService.EditorServicesHost.IsRunspacePushed ? null : _executionService.EngineIntrinsics; + return _psrlProxy.ReadLine(_executionService.EditorServicesHost.Runspace, engineIntrinsics, cancellationToken); } /// diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs index 479bd134c..49b26f3f7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs @@ -12,32 +12,33 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution { [Flags] - internal enum PromptFrameType + internal enum PowerShellFrameType { Normal = 0, NestedPrompt = 1, Debug = 2, Remote = 4, + NonInteractive = 8, } internal class PowerShellPushedArgs { - public PowerShellPushedArgs(PromptFrameType frameType) + public PowerShellPushedArgs(PowerShellFrameType frameType) { FrameType = frameType; } - public PromptFrameType FrameType { get; } + public PowerShellFrameType FrameType { get; } } internal class PowerShellPoppedArgs { - public PowerShellPoppedArgs(PromptFrameType frameType) + public PowerShellPoppedArgs(PowerShellFrameType frameType) { FrameType = frameType; } - public PromptFrameType FrameType { get; } + public PowerShellFrameType FrameType { get; } } internal class DebuggerResumingArgs @@ -54,7 +55,7 @@ internal class PromptCancellationRequestedArgs { } - internal class NestedPromptExitingArgs + internal class FrameExitingArgs { } @@ -108,7 +109,7 @@ public CancellationTokenSource CurrentCancellationSource public event Action PowerShellPopped; - public event Action NestedPromptExiting; + public event Action FrameExiting; public event Action DebuggerStopped; @@ -116,10 +117,15 @@ public CancellationTokenSource CurrentCancellationSource public event Action BreakpointUpdated; - public void BeginExiting() + public void SetShouldExit() { + if (_frameStack.Count <= 1) + { + return; + } + IsExiting = true; - NestedPromptExiting?.Invoke(this, new NestedPromptExitingArgs()); + FrameExiting?.Invoke(this, new FrameExitingArgs()); } public void ProcessDebuggerResult(DebuggerCommandResults result) @@ -127,17 +133,23 @@ public void ProcessDebuggerResult(DebuggerCommandResults result) if (result.ResumeAction != null) { DebuggerResumed?.Invoke(this, new DebuggerResumingArgs(result.ResumeAction)); + FrameExiting?.Invoke(this, new FrameExitingArgs()); } } + public void PushNonInteractivePowerShell() + { + PushNestedPowerShell(PowerShellFrameType.NonInteractive); + } + public void PushNestedPowerShell() { - PushNestedPowerShell(PromptFrameType.Normal); + PushNestedPowerShell(PowerShellFrameType.Normal); } public void PushDebugPowerShell() { - PushNestedPowerShell(PromptFrameType.Debug); + PushNestedPowerShell(PowerShellFrameType.Debug); } public void PushPowerShell(Runspace runspace) @@ -145,11 +157,11 @@ public void PushPowerShell(Runspace runspace) var pwsh = SMA.PowerShell.Create(); pwsh.Runspace = runspace; - PromptFrameType frameType = PromptFrameType.Normal; + PowerShellFrameType frameType = PowerShellFrameType.Normal; if (runspace.RunspaceIsRemote) { - frameType |= PromptFrameType.Remote; + frameType |= PowerShellFrameType.Remote; } PushFrame(new ContextFrame(pwsh, frameType, new CancellationTokenSource())); @@ -184,19 +196,28 @@ private void PushInitialPowerShell(Runspace runspace) var pwsh = SMA.PowerShell.Create(); pwsh.Runspace = runspace; - PushFrame(new ContextFrame(pwsh, PromptFrameType.Normal, new CancellationTokenSource())); + PushFrame(new ContextFrame(pwsh, PowerShellFrameType.Normal, new CancellationTokenSource())); } - private void PushNestedPowerShell(PromptFrameType frameType) + private void PushNestedPowerShell(PowerShellFrameType frameType) { SMA.PowerShell pwsh = CreateNestedPowerShell(); - PromptFrameType newFrameType = _frameStack.Peek().FrameType | PromptFrameType.NestedPrompt | frameType; + PowerShellFrameType newFrameType = _frameStack.Peek().FrameType | PowerShellFrameType.NestedPrompt | frameType; PushFrame(new ContextFrame(pwsh, newFrameType, new CancellationTokenSource())); } private SMA.PowerShell CreateNestedPowerShell() { + ContextFrame currentFrame = _frameStack.Peek(); + if ((currentFrame.FrameType & PowerShellFrameType.Remote) != 0) + { + var remotePwsh = SMA.PowerShell.Create(); + remotePwsh.Runspace = currentFrame.PowerShell.Runspace; + return remotePwsh; + } + // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild + // This means it throws due to the parent pipeline not running... // So we must use the RunspaceMode.CurrentRunspace option on PowerShell.Create() instead var pwsh = SMA.PowerShell.Create(RunspaceMode.CurrentRunspace); pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; @@ -205,9 +226,12 @@ private SMA.PowerShell CreateNestedPowerShell() private void PushFrame(ContextFrame frame) { + if (_frameStack.Count > 0) + { + RemoveRunspaceEventHandlers(CurrentPowerShell.Runspace); + } + AddRunspaceEventHandlers(frame.PowerShell.Runspace); _frameStack.Push(frame); - frame.PowerShell.Runspace.Debugger.DebuggerStop += OnDebuggerStopped; - frame.PowerShell.Runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; PowerShellPushed?.Invoke(this, new PowerShellPushedArgs(frame.FrameType)); } @@ -217,8 +241,11 @@ private void PopFrame() ContextFrame frame = _frameStack.Pop(); try { - frame.PowerShell.Runspace.Debugger.DebuggerStop -= OnDebuggerStopped; - frame.PowerShell.Runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; + RemoveRunspaceEventHandlers(frame.PowerShell.Runspace); + if (_frameStack.Count > 0) + { + AddRunspaceEventHandlers(CurrentPowerShell.Runspace); + } PowerShellPopped?.Invoke(this, new PowerShellPoppedArgs(frame.FrameType)); } finally @@ -227,6 +254,18 @@ private void PopFrame() } } + private void AddRunspaceEventHandlers(Runspace runspace) + { + runspace.Debugger.DebuggerStop += OnDebuggerStopped; + runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; + } + + private void RemoveRunspaceEventHandlers(Runspace runspace) + { + runspace.Debugger.DebuggerStop -= OnDebuggerStopped; + runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; + } + private void OnDebuggerStopped(object sender, DebuggerStopEventArgs args) { DebuggerStopped?.Invoke(this, args); @@ -241,7 +280,7 @@ private class ContextFrame : IDisposable { private bool disposedValue; - public ContextFrame(SMA.PowerShell powerShell, PromptFrameType frameType, CancellationTokenSource cancellationTokenSource) + public ContextFrame(SMA.PowerShell powerShell, PowerShellFrameType frameType, CancellationTokenSource cancellationTokenSource) { PowerShell = powerShell; FrameType = frameType; @@ -250,7 +289,7 @@ public ContextFrame(SMA.PowerShell powerShell, PromptFrameType frameType, Cancel public SMA.PowerShell PowerShell { get; } - public PromptFrameType FrameType { get; } + public PowerShellFrameType FrameType { get; } public CancellationTokenSource CancellationTokenSource { get; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index a0adad31c..ac615b6e3 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -54,7 +54,7 @@ public override void EnterNestedPrompt() public override void ExitNestedPrompt() { - _pwshContext.BeginExiting(); + _pwshContext.SetShouldExit(); } public override void NotifyBeginApplication() @@ -72,13 +72,12 @@ public void PushRunspace(Runspace runspace) public void PopRunspace() { - // TODO: What if we're in a nested prompt in a remote/debug session? - _pwshContext.PopPowerShell(); + _pwshContext.SetShouldExit(); } public override void SetShouldExit(int exitCode) { - _pwshContext.TryPopPowerShell(); + _pwshContext.SetShouldExit(); } internal void RegisterPowerShellContext(PowerShellContext pwshContext) diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs index f1f4742a5..2be48d9b0 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs @@ -2,12 +2,15 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -75,10 +78,9 @@ public void StartRepl() System.Console.InputEncoding = Encoding.UTF8; System.Console.OutputEncoding = Encoding.UTF8; _psrlProxy.OverrideReadKey(ReadKey); - _executionService.PowerShellPushed += OnPromptFramePushed; + _executionService.PowerShellPushed += OnPowerShellPushed; _executionService.PromptCancellationRequested += OnPromptCancellationRequested; - _executionService.NestedPromptExiting += OnNestedPromptExiting; - _executionService.DebuggerResuming += OnDebuggerResuming; + _executionService.FrameExiting += OnFrameExiting; PushNewReplTask(); } @@ -90,10 +92,9 @@ public void Dispose() } System.Console.CancelKeyPress -= OnCancelKeyPress; - _executionService.PowerShellPushed -= OnPromptFramePushed; + _executionService.PowerShellPushed -= OnPowerShellPushed; _executionService.PromptCancellationRequested -= OnPromptCancellationRequested; - _executionService.NestedPromptExiting -= OnNestedPromptExiting; - _executionService.DebuggerResuming -= OnDebuggerResuming; + _executionService.FrameExiting -= OnFrameExiting; } public void CancelCurrentPrompt() @@ -125,8 +126,7 @@ private async Task RunReplLoopAsync() try { - - string promptString = (await GetPromptOutputAsync(currentCommandCancellation.CancellationSource.Token).ConfigureAwait(false)).FirstOrDefault() ?? "PS> "; + string promptString = await GetPromptStringAsync(currentCommandCancellation.CancellationSource.Token).ConfigureAwait(false); if (currentCommandCancellation.CancellationSource.IsCancellationRequested) { @@ -186,6 +186,18 @@ private async Task RunReplLoopAsync() } + private async Task GetPromptStringAsync(CancellationToken cancellationToken) + { + string prompt = (await GetPromptOutputAsync(cancellationToken).ConfigureAwait(false)).FirstOrDefault() ?? "PS> "; + + if (_editorServicesHost.Runspace.RunspaceIsRemote) + { + prompt = _editorServicesHost.Runspace.GetRemotePrompt(prompt); + } + + return prompt; + } + private Task> GetPromptOutputAsync(CancellationToken cancellationToken) { var promptCommand = new PSCommand().AddCommand("prompt"); @@ -223,9 +235,12 @@ private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) CancelCurrentPrompt(); } - private void OnPromptFramePushed(object sender, PowerShellPushedArgs args) + private void OnPowerShellPushed(object sender, PowerShellPushedArgs args) { - PushNewReplTask(); + if ((args.FrameType & PowerShellFrameType.NonInteractive) == 0) + { + PushNewReplTask(); + } } private void OnPromptCancellationRequested(object sender, PromptCancellationRequestedArgs args) @@ -233,13 +248,7 @@ private void OnPromptCancellationRequested(object sender, PromptCancellationRequ CancelCurrentPrompt(); } - private void OnNestedPromptExiting(object sender, NestedPromptExitingArgs args) - { - _exiting = true; - StopCurrentRepl(); - } - - private void OnDebuggerResuming(object sender, DebuggerResumingArgs args) + private void OnFrameExiting(object sender, FrameExitingArgs args) { _exiting = true; StopCurrentRepl(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 361bdd19d..841b5cd69 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -86,6 +86,8 @@ public static PowerShellExecutionService CreateAndStart( private Execution.PowerShellContext _pwshContext; + private bool _runIdleLoop; + private PowerShellExecutionService( ILoggerFactory loggerFactory, ILanguageServer languageServer, @@ -120,6 +122,8 @@ private PowerShellExecutionService( public ConsoleReadLine ReadLine { get; private set; } + public bool IsCurrentlyRemote => EditorServicesHost.Runspace.RunspaceIsRemote; + public event Action PowerShellPushed; public event Action PromptFramePopped; @@ -132,7 +136,7 @@ private PowerShellExecutionService( public event Action PromptCancellationRequested; - public event Action NestedPromptExiting; + public event Action FrameExiting; public Task ExecuteDelegateAsync( Func func, @@ -205,6 +209,12 @@ public void CancelCurrentTask() public void Dispose() { Stop(); + _pwshContext.PowerShellPushed -= OnPowerShellPushed; + _pwshContext.PowerShellPopped -= OnPowerShellPopped; + _pwshContext.FrameExiting -= OnFrameExiting; + _pwshContext.DebuggerStopped -= OnDebuggerStopped; + _pwshContext.DebuggerResumed -= OnDebuggerResuming; + _pwshContext.BreakpointUpdated -= OnBreakpointUpdated; _pwshContext.Dispose(); } @@ -397,7 +407,7 @@ private void SetPowerShellContext(EditorServicesConsolePSHost psHost, PSLanguage EditorServicesHost.RegisterPowerShellContext(_pwshContext); _pwshContext.PowerShellPushed += OnPowerShellPushed; _pwshContext.PowerShellPopped += OnPowerShellPopped; - _pwshContext.NestedPromptExiting += OnNestedPromptExiting; + _pwshContext.FrameExiting += OnFrameExiting; _pwshContext.DebuggerStopped += OnDebuggerStopped; _pwshContext.DebuggerResumed += OnDebuggerResuming; _pwshContext.BreakpointUpdated += OnBreakpointUpdated; @@ -453,40 +463,37 @@ private void RunDebugLoop(in LoopCancellationContext cancellationContext) } finally { + _debuggingContext.Reset(); cancellationSource.Dispose(); } } - private void OnPowerShellIdle() + private void RunIdleLoop(in LoopCancellationContext cancellationContext) { - if (_executionQueue.Count == 0) - { - return; - } - - var loopCancellationContext = LoopCancellationContext.EnterNew( - this, - _pwshContext.CurrentCancellationSource, - _consumerThreadCancellationSource); - try { - while (_pwshContext.CurrentPowerShell.InvocationStateInfo.State == PSInvocationState.Completed - && _executionQueue.TryTake(out ISynchronousTask task)) + while (_executionQueue.TryTake(out ISynchronousTask task)) { - RunTaskSynchronously(task, loopCancellationContext.CancellationToken); + RunTaskSynchronously(task, cancellationContext.CancellationToken); } } catch (OperationCanceledException) { } - finally + + // TODO: Run nested pipeline here for engine event handling + } + + private void OnPowerShellIdle() + { + if (_executionQueue.Count == 0) { - loopCancellationContext.Dispose(); + return; } - // TODO: Run nested pipeline here for engine event handling + _runIdleLoop = true; + _pwshContext.PushNonInteractivePowerShell(); } private void OnPowerShellPushed(object sender, PowerShellPushedArgs powerShellPushedArgs) @@ -500,17 +507,23 @@ private void OnPowerShellPushed(object sender, PowerShellPushedArgs powerShellPu try { - if ((powerShellPushedArgs.FrameType & PromptFrameType.Debug) != 0) + if (_runIdleLoop) { - RunDebugLoop(cancellationContext); + RunIdleLoop(cancellationContext); + return; } - else + + if ((powerShellPushedArgs.FrameType & PowerShellFrameType.Debug) != 0) { - RunNestedLoop(cancellationContext); + RunDebugLoop(cancellationContext); + return; } + + RunNestedLoop(cancellationContext); } finally { + _runIdleLoop = false; _pwshContext.PopPowerShell(); cancellationContext.Dispose(); } @@ -521,9 +534,9 @@ private void OnPowerShellPopped(object sender, PowerShellPoppedArgs promptFrameP PromptFramePopped?.Invoke(this, promptFramePoppedArgs); } - private void OnNestedPromptExiting(object sender, NestedPromptExitingArgs nestedPromptExitingArgs) + private void OnFrameExiting(object sender, FrameExitingArgs nestedPromptExitingArgs) { - NestedPromptExiting?.Invoke(this, nestedPromptExitingArgs); + FrameExiting?.Invoke(this, nestedPromptExitingArgs); } private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs index 23f3bb12e..cffea54d3 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs @@ -1,5 +1,7 @@ using Microsoft.PowerShell.EditorServices.Utility; using System; +using System.Linq.Expressions; +using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Reflection; using System.Threading; @@ -10,6 +12,8 @@ internal static class RunspaceExtensions { private static readonly Action s_runspaceApartmentStateSetter; + private static readonly Func s_getRemotePromptFunc; + static RunspaceExtensions() { // PowerShell ApartmentState APIs aren't available in PSStandard, so we need to use reflection. @@ -19,11 +23,30 @@ static RunspaceExtensions() Delegate setter = Delegate.CreateDelegate(typeof(Action), firstArgument: null, method: setterInfo); s_runspaceApartmentStateSetter = (Action)setter; } + + MethodInfo getRemotePromptMethod = typeof(HostUtilities).GetMethod("GetRemotePrompt", BindingFlags.NonPublic | BindingFlags.Static); + ParameterExpression runspaceParam = Expression.Parameter(typeof(Runspace)); + ParameterExpression basePromptParam = Expression.Parameter(typeof(string)); + s_getRemotePromptFunc = Expression.Lambda>( + Expression.Call( + getRemotePromptMethod, + new Expression[] + { + Expression.Convert(runspaceParam, typeof(Runspace).Assembly.GetType("System.Management.Automation.RemoteRunspace")), + basePromptParam, + Expression.Constant(false), // configuredSession must be false + }), + new ParameterExpression[] { runspaceParam, basePromptParam }).Compile(); } public static void SetApartmentStateToSta(this Runspace runspace) { s_runspaceApartmentStateSetter?.Invoke(runspace, ApartmentState.STA); } + + public static string GetRemotePrompt(this Runspace runspace, string basePrompt) + { + return s_getRemotePromptFunc(runspace, basePrompt); + } } } diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs index c267f3cf7..db784d0e6 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs @@ -67,6 +67,12 @@ public static async Task GetCommandInfoAsync( string commandName, PowerShellExecutionService executionService) { + // This mechanism only works in-process + if (executionService.IsCurrentlyRemote) + { + return null; + } + Validate.IsNotNull(nameof(commandName), commandName); Validate.IsNotNull(nameof(executionService), executionService); From 09e53e1d30681b0532ac6d979380ccb926c84226 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 3 Aug 2020 15:27:30 -0700 Subject: [PATCH 024/176] Merge PS context into execution service, make REPL service a subcomponent --- ex.js | 13 + .../Server/PsesLanguageServer.cs | 2 - .../Server/PsesServiceCollectionExtensions.cs | 4 - .../PowerShell/Console/ConsoleReadLine.cs | 3 - .../ConsoleReplRunner.cs} | 84 +--- .../Execution/DebuggerResumingEventArgs.cs | 15 + .../PowerShell/Execution/PowerShellContext.cs | 318 --------------- .../Execution/PowerShellFrameType.cs | 14 + .../Execution/SynchronousDelegateTask.cs | 66 ++++ .../Execution/SynchronousPowerShellTask.cs | 10 +- .../Host/EditorServicesConsolePSHost.cs | 26 +- .../PowerShell/PowerShellExecutionService.cs | 366 +++++++++++++----- .../PowerShell/Utility/AsyncResetEvent.cs | 45 +++ 13 files changed, 461 insertions(+), 505 deletions(-) create mode 100644 ex.js rename src/PowerShellEditorServices/Services/PowerShell/{PowerShellConsoleService.cs => Console/ConsoleReplRunner.cs} (78%) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/DebuggerResumingEventArgs.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellFrameType.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Utility/AsyncResetEvent.cs diff --git a/ex.js b/ex.js new file mode 100644 index 000000000..c5bc30443 --- /dev/null +++ b/ex.js @@ -0,0 +1,13 @@ +function run(timeout, count) { + let i = 0; + function inner() { + if (i === count) { + return; + } + console.log(i); + i++; + setTimeout(inner, timeout); + } + inner(); +} + diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index ce2df7f4a..20d3e4694 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -118,8 +118,6 @@ public async Task StartAsync() IServiceProvider serviceProvider = languageServer.Services; - serviceProvider.GetService().StartRepl(); - var workspaceService = serviceProvider.GetService(); // Grab the workspace path from the parameters diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index d559842a1..93a36b37c 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -27,10 +27,6 @@ public static IServiceCollection AddPsesLanguageServices( provider.GetService(), provider.GetService(), hostStartupInfo)) - .AddSingleton( - (provider) => PowerShellConsoleService.CreateAndStart( - provider.GetService(), - provider.GetService())) .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index 848af0ace..161741a59 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -3,8 +3,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using System.Collections.ObjectModel; -using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -12,7 +10,6 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using System; using System.Collections.Generic; using System.Management.Automation; diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs similarity index 78% rename from src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs rename to src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs index 2be48d9b0..7645b379e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellConsoleService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs @@ -1,46 +1,25 @@ using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; -using Microsoft.PowerShell.EditorServices.Utility; +using PowerShellEditorServices.Services.PowerShell.Utility; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { - internal class PowerShellConsoleService : IDisposable + internal class ConsoleReplRunner : IDisposable { - public static PowerShellConsoleService CreateAndStart( - ILoggerFactory loggerFactory, - PowerShellExecutionService executionService) - { - return new PowerShellConsoleService( - loggerFactory, - executionService, - executionService.EditorServicesHost, - executionService.ReadLine, - executionService.PSReadLineProxy); - } - private readonly ILogger _logger; private readonly PowerShellExecutionService _executionService; - private readonly EditorServicesConsolePSHost _editorServicesHost; - - private readonly ConsoleReadLine _readLine; - - private readonly PSReadLineProxy _psrlProxy; - private readonly ConcurrentStack _replLoopTaskStack; // This is required because PSRL will keep prompting for keys as we push a new REPL task @@ -52,24 +31,15 @@ public static PowerShellConsoleService CreateAndStart( private bool _exiting; - private bool _debugging; - - private PowerShellConsoleService( + public ConsoleReplRunner( ILoggerFactory loggerFactory, - PowerShellExecutionService executionService, - EditorServicesConsolePSHost editorServicesHost, - ConsoleReadLine readLine, - PSReadLineProxy psrlProxy) + PowerShellExecutionService executionService) { - _logger = loggerFactory.CreateLogger(); + _logger = loggerFactory.CreateLogger(); _replLoopTaskStack = new ConcurrentStack(); _commandCancellationStack = new ConcurrentStack(); _executionService = executionService; - _editorServicesHost = editorServicesHost; - _readLine = readLine; - _psrlProxy = psrlProxy; _exiting = false; - _debugging = false; } public void StartRepl() @@ -77,10 +47,7 @@ public void StartRepl() System.Console.CancelKeyPress += OnCancelKeyPress; System.Console.InputEncoding = Encoding.UTF8; System.Console.OutputEncoding = Encoding.UTF8; - _psrlProxy.OverrideReadKey(ReadKey); - _executionService.PowerShellPushed += OnPowerShellPushed; - _executionService.PromptCancellationRequested += OnPromptCancellationRequested; - _executionService.FrameExiting += OnFrameExiting; + _executionService.PSReadLineProxy.OverrideReadKey(ReadKey); PushNewReplTask(); } @@ -92,9 +59,6 @@ public void Dispose() } System.Console.CancelKeyPress -= OnCancelKeyPress; - _executionService.PowerShellPushed -= OnPowerShellPushed; - _executionService.PromptCancellationRequested -= OnPromptCancellationRequested; - _executionService.FrameExiting -= OnFrameExiting; } public void CancelCurrentPrompt() @@ -149,7 +113,7 @@ private async Task RunReplLoopAsync() if (currentCommandCancellation.CancellationSource.IsCancellationRequested || LastKeyWasCtrlC()) { - _editorServicesHost.UI.WriteLine(); + _executionService.EditorServicesHost.UI.WriteLine(); } continue; } @@ -190,9 +154,9 @@ private async Task GetPromptStringAsync(CancellationToken cancellationTo { string prompt = (await GetPromptOutputAsync(cancellationToken).ConfigureAwait(false)).FirstOrDefault() ?? "PS> "; - if (_editorServicesHost.Runspace.RunspaceIsRemote) + if (_executionService.EditorServicesHost.Runspace.RunspaceIsRemote) { - prompt = _editorServicesHost.Runspace.GetRemotePrompt(prompt); + prompt = _executionService.EditorServicesHost.Runspace.GetRemotePrompt(prompt); } return prompt; @@ -210,12 +174,12 @@ private Task> GetPromptOutputAsync(CancellationToken cance private void WritePrompt(string promptString) { - _editorServicesHost.UI.Write(promptString); + _executionService.EditorServicesHost.UI.Write(promptString); } private Task InvokeReadLineAsync(CancellationToken cancellationToken) { - return _readLine.ReadCommandLineAsync(cancellationToken); + return _executionService.ReadLine.ReadCommandLineAsync(cancellationToken); } private Task InvokeInputAsync(string input, CancellationToken cancellationToken) @@ -230,30 +194,17 @@ private Task InvokeInputAsync(string input, CancellationToken cancellationToken) return _executionService.ExecutePSCommandAsync(command, executionOptions, cancellationToken); } - private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) - { - CancelCurrentPrompt(); - } - - private void OnPowerShellPushed(object sender, PowerShellPushedArgs args) + public void SetReplPop() { - if ((args.FrameType & PowerShellFrameType.NonInteractive) == 0) - { - PushNewReplTask(); - } + _exiting = true; + StopCurrentRepl(); } - private void OnPromptCancellationRequested(object sender, PromptCancellationRequestedArgs args) + private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) { CancelCurrentPrompt(); } - private void OnFrameExiting(object sender, FrameExitingArgs args) - { - _exiting = true; - StopCurrentRepl(); - } - private void OnReplCanceled() { // Ordinarily, when the REPL is canceled @@ -293,12 +244,11 @@ private bool LastKeyWasCtrlC() && (_lastKey.Value.Modifiers & ConsoleModifiers.Alt) == 0; } - private void PushNewReplTask() + public void PushNewReplTask() { ReplTask.PushAndStart(_replLoopTaskStack, RunReplLoopAsync, OnReplCanceled); } - private class ReplTask { public static void PushAndStart( diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/DebuggerResumingEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/DebuggerResumingEventArgs.cs new file mode 100644 index 000000000..c04adb031 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/DebuggerResumingEventArgs.cs @@ -0,0 +1,15 @@ +using System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + internal class DebuggerResumingEventArgs + { + public DebuggerResumingEventArgs(DebuggerResumeAction resumeAction) + { + ResumeAction = resumeAction; + } + + public DebuggerResumeAction ResumeAction { get; } + + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs deleted file mode 100644 index 49b26f3f7..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellContext.cs +++ /dev/null @@ -1,318 +0,0 @@ -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; -using System.Threading; -using SMA = System.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution -{ - [Flags] - internal enum PowerShellFrameType - { - Normal = 0, - NestedPrompt = 1, - Debug = 2, - Remote = 4, - NonInteractive = 8, - } - - internal class PowerShellPushedArgs - { - public PowerShellPushedArgs(PowerShellFrameType frameType) - { - FrameType = frameType; - } - - public PowerShellFrameType FrameType { get; } - } - - internal class PowerShellPoppedArgs - { - public PowerShellPoppedArgs(PowerShellFrameType frameType) - { - FrameType = frameType; - } - - public PowerShellFrameType FrameType { get; } - } - - internal class DebuggerResumingArgs - { - public DebuggerResumingArgs(DebuggerResumeAction? resumeAction) - { - ResumeAction = resumeAction; - } - - public DebuggerResumeAction? ResumeAction { get; } - } - - internal class PromptCancellationRequestedArgs - { - } - - internal class FrameExitingArgs - { - } - - internal class PowerShellContext : IDisposable - { - public static PowerShellContext Create(EditorServicesConsolePSHost psHost, PSLanguageMode languageMode) - { - InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" - ? InitialSessionState.CreateDefault() - : InitialSessionState.CreateDefault2(); - - iss.LanguageMode = languageMode; - - Runspace runspace = RunspaceFactory.CreateRunspace(psHost, iss); - - runspace.SetApartmentStateToSta(); - runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; - - runspace.Open(); - - var context = new PowerShellContext(); - context.PushInitialPowerShell(runspace); - - Runspace.DefaultRunspace = runspace; - - return context; - } - - private readonly Stack _frameStack; - - private PowerShellContext() - { - _frameStack = new Stack(); - } - - public int PowerShellDepth => _frameStack.Count; - - public SMA.PowerShell CurrentPowerShell - { - get => _frameStack.Peek().PowerShell; - } - - public CancellationTokenSource CurrentCancellationSource - { - get => _frameStack.Peek().CancellationTokenSource; - } - - public bool IsExiting { get; private set; } - - public event Action PowerShellPushed; - - public event Action PowerShellPopped; - - public event Action FrameExiting; - - public event Action DebuggerStopped; - - public event Action DebuggerResumed; - - public event Action BreakpointUpdated; - - public void SetShouldExit() - { - if (_frameStack.Count <= 1) - { - return; - } - - IsExiting = true; - FrameExiting?.Invoke(this, new FrameExitingArgs()); - } - - public void ProcessDebuggerResult(DebuggerCommandResults result) - { - if (result.ResumeAction != null) - { - DebuggerResumed?.Invoke(this, new DebuggerResumingArgs(result.ResumeAction)); - FrameExiting?.Invoke(this, new FrameExitingArgs()); - } - } - - public void PushNonInteractivePowerShell() - { - PushNestedPowerShell(PowerShellFrameType.NonInteractive); - } - - public void PushNestedPowerShell() - { - PushNestedPowerShell(PowerShellFrameType.Normal); - } - - public void PushDebugPowerShell() - { - PushNestedPowerShell(PowerShellFrameType.Debug); - } - - public void PushPowerShell(Runspace runspace) - { - var pwsh = SMA.PowerShell.Create(); - pwsh.Runspace = runspace; - - PowerShellFrameType frameType = PowerShellFrameType.Normal; - - if (runspace.RunspaceIsRemote) - { - frameType |= PowerShellFrameType.Remote; - } - - PushFrame(new ContextFrame(pwsh, frameType, new CancellationTokenSource())); - } - - public void PopPowerShell() - { - PopFrame(); - } - - public bool TryPopPowerShell() - { - if (_frameStack.Count <= 1) - { - return false; - } - - PopFrame(); - return true; - } - - public void Dispose() - { - while (_frameStack.Count > 0) - { - _frameStack.Pop().PowerShell.Dispose(); - } - } - - private void PushInitialPowerShell(Runspace runspace) - { - var pwsh = SMA.PowerShell.Create(); - pwsh.Runspace = runspace; - - PushFrame(new ContextFrame(pwsh, PowerShellFrameType.Normal, new CancellationTokenSource())); - } - - private void PushNestedPowerShell(PowerShellFrameType frameType) - { - SMA.PowerShell pwsh = CreateNestedPowerShell(); - PowerShellFrameType newFrameType = _frameStack.Peek().FrameType | PowerShellFrameType.NestedPrompt | frameType; - PushFrame(new ContextFrame(pwsh, newFrameType, new CancellationTokenSource())); - } - - private SMA.PowerShell CreateNestedPowerShell() - { - ContextFrame currentFrame = _frameStack.Peek(); - if ((currentFrame.FrameType & PowerShellFrameType.Remote) != 0) - { - var remotePwsh = SMA.PowerShell.Create(); - remotePwsh.Runspace = currentFrame.PowerShell.Runspace; - return remotePwsh; - } - - // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild - // This means it throws due to the parent pipeline not running... - // So we must use the RunspaceMode.CurrentRunspace option on PowerShell.Create() instead - var pwsh = SMA.PowerShell.Create(RunspaceMode.CurrentRunspace); - pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; - return pwsh; - } - - private void PushFrame(ContextFrame frame) - { - if (_frameStack.Count > 0) - { - RemoveRunspaceEventHandlers(CurrentPowerShell.Runspace); - } - AddRunspaceEventHandlers(frame.PowerShell.Runspace); - _frameStack.Push(frame); - PowerShellPushed?.Invoke(this, new PowerShellPushedArgs(frame.FrameType)); - } - - private void PopFrame() - { - IsExiting = false; - ContextFrame frame = _frameStack.Pop(); - try - { - RemoveRunspaceEventHandlers(frame.PowerShell.Runspace); - if (_frameStack.Count > 0) - { - AddRunspaceEventHandlers(CurrentPowerShell.Runspace); - } - PowerShellPopped?.Invoke(this, new PowerShellPoppedArgs(frame.FrameType)); - } - finally - { - frame.Dispose(); - } - } - - private void AddRunspaceEventHandlers(Runspace runspace) - { - runspace.Debugger.DebuggerStop += OnDebuggerStopped; - runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; - } - - private void RemoveRunspaceEventHandlers(Runspace runspace) - { - runspace.Debugger.DebuggerStop -= OnDebuggerStopped; - runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; - } - - private void OnDebuggerStopped(object sender, DebuggerStopEventArgs args) - { - DebuggerStopped?.Invoke(this, args); - } - - private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs args) - { - BreakpointUpdated?.Invoke(this, args); - } - - private class ContextFrame : IDisposable - { - private bool disposedValue; - - public ContextFrame(SMA.PowerShell powerShell, PowerShellFrameType frameType, CancellationTokenSource cancellationTokenSource) - { - PowerShell = powerShell; - FrameType = frameType; - CancellationTokenSource = cancellationTokenSource; - } - - public SMA.PowerShell PowerShell { get; } - - public PowerShellFrameType FrameType { get; } - - public CancellationTokenSource CancellationTokenSource { get; } - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - PowerShell.Dispose(); - CancellationTokenSource.Dispose(); - } - - disposedValue = true; - } - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellFrameType.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellFrameType.cs new file mode 100644 index 000000000..cc308eaac --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellFrameType.cs @@ -0,0 +1,14 @@ +using System; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + [Flags] + internal enum PowerShellFrameType + { + Normal = 0x0, + Nested = 0x1, + Debug = 0x2, + Remote = 0x4, + NonInteractive = 0x8, + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs index d6d61be6a..5748bc7ec 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Logging; using System; using System.Threading; +using SMA = System.Management.Automation; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution { @@ -60,4 +61,69 @@ public override string ToString() return _representation; } } + + internal class SynchronousPSDelegateTask : SynchronousTask + { + private readonly Action _action; + + private readonly string _representation; + + private readonly PowerShellExecutionService.PowerShellRunspaceContext _psRunspaceContext; + + public SynchronousPSDelegateTask( + ILogger logger, + PowerShellExecutionService.PowerShellRunspaceContext psRunspaceContext, + Action action, + string representation, + CancellationToken cancellationToken) + : base(logger, cancellationToken) + { + _psRunspaceContext = psRunspaceContext; + _action = action; + _representation = representation; + } + + public override object Run(CancellationToken cancellationToken) + { + _action(_psRunspaceContext.CurrentPowerShell, cancellationToken); + return null; + } + + public override string ToString() + { + return _representation; + } + } + + internal class SynchronousPSDelegateTask : SynchronousTask + { + private readonly Func _func; + + private readonly string _representation; + + private readonly PowerShellExecutionService.PowerShellRunspaceContext _psRunspaceContext; + + public SynchronousPSDelegateTask( + ILogger logger, + PowerShellExecutionService.PowerShellRunspaceContext psRunspaceContext, + Func func, + string representation, + CancellationToken cancellationToken) + : base(logger, cancellationToken) + { + _psRunspaceContext = psRunspaceContext; + _func = func; + _representation = representation; + } + + public override TResult Run(CancellationToken cancellationToken) + { + return _func(_psRunspaceContext.CurrentPowerShell, cancellationToken); + } + + public override string ToString() + { + return _representation; + } + } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index ee8a3134f..318d5e681 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -15,7 +15,7 @@ internal class SynchronousPowerShellTask : SynchronousTask : SynchronousTask Run(CancellationToken cancellationToken) { - _pwsh = _pwshContext.CurrentPowerShell; + _pwsh = _psRunspaceContext.CurrentPowerShell; if (_executionOptions.WriteInputToHost) { @@ -132,7 +132,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT } DebuggerCommandResults debuggerResult = _pwsh.Runspace.Debugger.ProcessCommand(_psCommand, outputCollection); - _pwshContext.ProcessDebuggerResult(debuggerResult); + _psRunspaceContext.ProcessDebuggerResult(debuggerResult); // Optimisation to save wasted computation if we're going to throw the output away anyway if (_executionOptions.WriteOutputToHost) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index ac615b6e3..58a90510f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -4,18 +4,15 @@ using System.Globalization; using System.Management.Automation.Host; using System.Management.Automation.Runspaces; +using static Microsoft.PowerShell.EditorServices.Services.PowerShell.PowerShellExecutionService; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host { - using PowerShellContext = Execution.PowerShellContext; - internal class EditorServicesConsolePSHost : PSHost, IHostSupportsInteractiveSession { private readonly ILogger _logger; - private PowerShellContext _pwshContext; - - private Runspace _pushedRunspace; + private PowerShellRunspaceContext _psRunspaceContext; public EditorServicesConsolePSHost( ILoggerFactory loggerFactory, @@ -25,7 +22,6 @@ public EditorServicesConsolePSHost( ConsoleReadLine readline) { _logger = loggerFactory.CreateLogger(); - _pushedRunspace = null; Name = name; Version = version; UI = new EditorServicesConsolePSHostUserInterface(loggerFactory, readline, internalHost.UI); @@ -43,18 +39,18 @@ public EditorServicesConsolePSHost( public override Version Version { get; } - public Runspace Runspace => _pwshContext.CurrentPowerShell.Runspace; + public Runspace Runspace => _psRunspaceContext.Runspace; - public bool IsRunspacePushed => _pwshContext.PowerShellDepth > 1; + public bool IsRunspacePushed => _psRunspaceContext.IsRunspacePushed; public override void EnterNestedPrompt() { - _pwshContext.PushNestedPowerShell(); + _psRunspaceContext.PushNestedPowerShell(); } public override void ExitNestedPrompt() { - _pwshContext.SetShouldExit(); + _psRunspaceContext.SetShouldExit(); } public override void NotifyBeginApplication() @@ -67,22 +63,22 @@ public override void NotifyEndApplication() public void PushRunspace(Runspace runspace) { - _pwshContext.PushPowerShell(runspace); + _psRunspaceContext.PushPowerShell(runspace); } public void PopRunspace() { - _pwshContext.SetShouldExit(); + _psRunspaceContext.SetShouldExit(); } public override void SetShouldExit(int exitCode) { - _pwshContext.SetShouldExit(); + _psRunspaceContext.SetShouldExit(); } - internal void RegisterPowerShellContext(PowerShellContext pwshContext) + internal void RegisterPowerShellContext(PowerShellExecutionService.PowerShellRunspaceContext psRunspaceContext) { - _pwshContext = pwshContext; + _psRunspaceContext = psRunspaceContext; } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 841b5cd69..122b362cc 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -5,6 +5,7 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using Microsoft.PowerShell.EditorServices.Utility; +using Newtonsoft.Json.Bson; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using System; using System.Collections.Concurrent; @@ -13,6 +14,7 @@ using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Language; +using System.Management.Automation.Runspaces; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -64,6 +66,8 @@ public static PowerShellExecutionService CreateAndStart( private readonly ILanguageServer _languageServer; + private readonly ConsoleReplRunner _consoleRepl; + private readonly string _hostName; private readonly Version _hostVersion; @@ -78,16 +82,18 @@ public static PowerShellExecutionService CreateAndStart( private readonly DebuggingContext _debuggingContext; - private Thread _pipelineThread; + private readonly Stack _psFrameStack; - private ConcurrentStack _loopCancellationStack; + private Thread _pipelineThread; - private ConcurrentStack _commandCancellationStack; + private readonly ConcurrentStack _loopCancellationStack; - private Execution.PowerShellContext _pwshContext; + private readonly ConcurrentStack _commandCancellationStack; private bool _runIdleLoop; + private bool _isExiting; + private PowerShellExecutionService( ILoggerFactory loggerFactory, ILanguageServer languageServer, @@ -101,11 +107,13 @@ private PowerShellExecutionService( _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(); _languageServer = languageServer; + _consoleRepl = new ConsoleReplRunner(_loggerFactory, this); _consumerThreadCancellationSource = new CancellationTokenSource(); _loopCancellationStack = new ConcurrentStack(); _commandCancellationStack = new ConcurrentStack(); _executionQueue = new BlockingCollection(); _debuggingContext = new DebuggingContext(); + _psFrameStack = new Stack(); _hostName = hostName; _hostVersion = hostVersion; _languageMode = languageMode; @@ -122,29 +130,24 @@ private PowerShellExecutionService( public ConsoleReadLine ReadLine { get; private set; } - public bool IsCurrentlyRemote => EditorServicesHost.Runspace.RunspaceIsRemote; + internal SMA.PowerShell CurrentPowerShell => _psFrameStack.Peek().PowerShell; - public event Action PowerShellPushed; + internal CancellationTokenSource CurrentPowerShellCancellationSource => _psFrameStack.Peek().CancellationTokenSource; - public event Action PromptFramePopped; + public bool IsCurrentlyRemote => EditorServicesHost.Runspace.RunspaceIsRemote; public event Action DebuggerStopped; - public event Action DebuggerResuming; + public event Action DebuggerResuming; public event Action BreakpointUpdated; - public event Action PromptCancellationRequested; - - public event Action FrameExiting; - public Task ExecuteDelegateAsync( Func func, string representation, CancellationToken cancellationToken) { - TResult appliedFunc(CancellationToken cancellationToken) => func(_pwshContext.CurrentPowerShell, cancellationToken); - return ExecuteDelegateAsync(appliedFunc, representation, cancellationToken); + return QueueTask(new SynchronousPSDelegateTask(_logger, new PowerShellRunspaceContext(this), func, representation, cancellationToken)); } public Task ExecuteDelegateAsync( @@ -152,8 +155,7 @@ public Task ExecuteDelegateAsync( string representation, CancellationToken cancellationToken) { - void appliedAction(CancellationToken cancellationToken) => action(_pwshContext.CurrentPowerShell, cancellationToken); - return ExecuteDelegateAsync(appliedAction, representation, cancellationToken); + return QueueTask(new SynchronousPSDelegateTask(_logger, new PowerShellRunspaceContext(this), action, representation, cancellationToken)); } public Task ExecuteDelegateAsync( @@ -177,11 +179,17 @@ public Task> ExecutePSCommandAsync( PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) { - Task> result = QueueTask(new SynchronousPowerShellTask(_logger, _pwshContext, EditorServicesHost, psCommand, executionOptions, cancellationToken)); + Task> result = QueueTask(new SynchronousPowerShellTask( + _logger, + new PowerShellRunspaceContext(this), + EditorServicesHost, + psCommand, + executionOptions, + cancellationToken)); if (executionOptions.InterruptCommandPrompt) { - PromptCancellationRequested?.Invoke(this, new PromptCancellationRequestedArgs()); + _consoleRepl.CancelCurrentPrompt(); } return result; @@ -209,13 +217,10 @@ public void CancelCurrentTask() public void Dispose() { Stop(); - _pwshContext.PowerShellPushed -= OnPowerShellPushed; - _pwshContext.PowerShellPopped -= OnPowerShellPopped; - _pwshContext.FrameExiting -= OnFrameExiting; - _pwshContext.DebuggerStopped -= OnDebuggerStopped; - _pwshContext.DebuggerResumed -= OnDebuggerResuming; - _pwshContext.BreakpointUpdated -= OnBreakpointUpdated; - _pwshContext.Dispose(); + while (_psFrameStack.Count > 0) + { + PopFrame(); + } } private Task QueueTask(SynchronousTask task) @@ -238,9 +243,11 @@ private void RunTopLevelConsumerLoop() { Initialize(); + _consoleRepl.StartRepl(); + var cancellationContext = LoopCancellationContext.EnterNew( this, - _pwshContext.CurrentCancellationSource, + CurrentPowerShellCancellationSource, _consumerThreadCancellationSource); try { @@ -283,11 +290,13 @@ private void Initialize() _internalHost, ReadLine); - SetPowerShellContext(EditorServicesHost, _languageMode); + PushInitialRunspace(EditorServicesHost, _languageMode); + + EditorServicesHost.RegisterPowerShellContext(new PowerShellRunspaceContext(this)); - EngineIntrinsics = (EngineIntrinsics)_pwshContext.CurrentPowerShell.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); + EngineIntrinsics = (EngineIntrinsics)CurrentPowerShell.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); - PSReadLineProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, _pwshContext.CurrentPowerShell); + PSReadLineProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, CurrentPowerShell); PSReadLineProxy.OverrideIdleHandler(OnPowerShellIdle); ReadLine.RegisterExecutionDependencies(this, PSReadLineProxy); @@ -313,7 +322,7 @@ private void SetExecutionPolicy() { // We want to get the list hierarchy of execution policies // Calling the cmdlet is the simplest way to do that - IReadOnlyList policies = _pwshContext.CurrentPowerShell + IReadOnlyList policies = CurrentPowerShell .AddCommand("Microsoft.PowerShell.Security\\Get-ExecutionPolicy") .AddParameter("-List") .InvokeAndClear(); @@ -356,7 +365,7 @@ private void SetExecutionPolicy() _logger.LogTrace("Setting execution policy to {Policy}", policyToSet); try { - _pwshContext.CurrentPowerShell.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") + CurrentPowerShell.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") .AddParameter("Scope", ExecutionPolicyScope.Process) .AddParameter("ExecutionPolicy", policyToSet) .AddParameter("Force") @@ -377,7 +386,7 @@ private void LoadProfiles() AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.CurrentUserAllHosts), _profilePaths.CurrentUserAllHosts); AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.CurrentUserCurrentHost), _profilePaths.CurrentUserCurrentHost); - _pwshContext.CurrentPowerShell.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); + CurrentPowerShell.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); } private void AddProfileMemberAndLoadIfExists(PSObject profileVariable, string profileName, string profilePath) @@ -390,27 +399,39 @@ private void AddProfileMemberAndLoadIfExists(PSObject profileVariable, string pr .AddScript(profilePath, useLocalScope: false) .AddOutputCommand(); - _pwshContext.CurrentPowerShell.InvokeCommand(psCommand); + CurrentPowerShell.InvokeCommand(psCommand); } } private void ImportModule(string moduleNameOrPath) { - _pwshContext.CurrentPowerShell.AddCommand("Microsoft.PowerShell.Core\\Import-Module") + CurrentPowerShell.AddCommand("Microsoft.PowerShell.Core\\Import-Module") .AddParameter("-Name", moduleNameOrPath) .InvokeAndClear(); } - private void SetPowerShellContext(EditorServicesConsolePSHost psHost, PSLanguageMode languageMode) + private void PushInitialRunspace(EditorServicesConsolePSHost psHost, PSLanguageMode languageMode) { - _pwshContext = Execution.PowerShellContext.Create(psHost, languageMode); - EditorServicesHost.RegisterPowerShellContext(_pwshContext); - _pwshContext.PowerShellPushed += OnPowerShellPushed; - _pwshContext.PowerShellPopped += OnPowerShellPopped; - _pwshContext.FrameExiting += OnFrameExiting; - _pwshContext.DebuggerStopped += OnDebuggerStopped; - _pwshContext.DebuggerResumed += OnDebuggerResuming; - _pwshContext.BreakpointUpdated += OnBreakpointUpdated; + InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" + ? InitialSessionState.CreateDefault() + : InitialSessionState.CreateDefault2(); + + iss.LanguageMode = languageMode; + + Runspace runspace = RunspaceFactory.CreateRunspace(psHost, iss); + + runspace.SetApartmentStateToSta(); + runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; + + runspace.Open(); + + AddRunspaceEventHandlers(runspace); + + var pwsh = SMA.PowerShell.Create(); + pwsh.Runspace = runspace; + _psFrameStack.Push(new PowerShellContextFrame(pwsh, PowerShellFrameType.Normal, new CancellationTokenSource())); + + Runspace.DefaultRunspace = runspace; } private void RunNestedLoop(in LoopCancellationContext cancellationContext) @@ -421,7 +442,7 @@ private void RunNestedLoop(in LoopCancellationContext cancellationContext) { RunTaskSynchronously(task, cancellationContext.CancellationToken); - if (_pwshContext.IsExiting) + if (_isExiting) { break; } @@ -435,14 +456,14 @@ private void RunNestedLoop(in LoopCancellationContext cancellationContext) private void RunDebugLoop(in LoopCancellationContext cancellationContext) { - DebuggerStopped?.Invoke(this, _debuggingContext.LastStopEventArgs); - // If the debugger is resumed while the execution queue listener is blocked on getting a new execution event, // we must cancel the blocking call - var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_debuggingContext.DebuggerResumeCancellationToken.Value, cancellationContext.CancellationToken); + var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_debuggingContext.DebuggerResumeCancellationToken, cancellationContext.CancellationToken); try { + DebuggerStopped?.Invoke(this, _debuggingContext.LastStopEventArgs); + // Run commands, but cancelling our blocking wait if the debugger resumes foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationSource.Token)) { @@ -451,7 +472,7 @@ private void RunDebugLoop(in LoopCancellationContext cancellationContext) // Instead let it complete and check the cancellation afterward. RunTaskSynchronously(task, cancellationContext.CancellationToken); - if (_debuggingContext.ShouldResume) + if (cancellationSource.Token.IsCancellationRequested) { break; } @@ -463,7 +484,7 @@ private void RunDebugLoop(in LoopCancellationContext cancellationContext) } finally { - _debuggingContext.Reset(); + _debuggingContext.ResetCurrentStopContext(); cancellationSource.Dispose(); } } @@ -493,18 +514,113 @@ private void OnPowerShellIdle() } _runIdleLoop = true; - _pwshContext.PushNonInteractivePowerShell(); + PushNonInteractivePowerShell(); } - private void OnPowerShellPushed(object sender, PowerShellPushedArgs powerShellPushedArgs) + private void PushNestedPowerShell(PowerShellFrameType frameType) + { + SMA.PowerShell pwsh = CreateNestedPowerShell(); + PowerShellFrameType newFrameType = _psFrameStack.Peek().FrameType | PowerShellFrameType.Nested | frameType; + PushFrame(new PowerShellContextFrame(pwsh, newFrameType, new CancellationTokenSource())); + } + + private SMA.PowerShell CreateNestedPowerShell() + { + PowerShellContextFrame currentFrame = _psFrameStack.Peek(); + if ((currentFrame.FrameType & PowerShellFrameType.Remote) != 0) + { + var remotePwsh = SMA.PowerShell.Create(); + remotePwsh.Runspace = currentFrame.PowerShell.Runspace; + return remotePwsh; + } + + // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild + // This means it throws due to the parent pipeline not running... + // So we must use the RunspaceMode.CurrentRunspace option on PowerShell.Create() instead + var pwsh = SMA.PowerShell.Create(RunspaceMode.CurrentRunspace); + pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; + return pwsh; + } + + + private void PushNonInteractivePowerShell() + { + PushNestedPowerShell(PowerShellFrameType.NonInteractive); + } + + private void PushNestedPowerShell() + { + PushNestedPowerShell(PowerShellFrameType.Normal); + } + + private void PushDebugPowerShell() + { + PushNestedPowerShell(PowerShellFrameType.Debug); + } + + private void PushPowerShell(Runspace runspace) + { + var pwsh = SMA.PowerShell.Create(); + pwsh.Runspace = runspace; + + PowerShellFrameType frameType = PowerShellFrameType.Normal; + + if (runspace.RunspaceIsRemote) + { + frameType |= PowerShellFrameType.Remote; + } + + PushFrame(new PowerShellContextFrame(pwsh, frameType, new CancellationTokenSource())); + } + + private void PushFrame(PowerShellContextFrame frame) + { + if (_psFrameStack.Count > 0) + { + RemoveRunspaceEventHandlers(CurrentPowerShell.Runspace); + } + AddRunspaceEventHandlers(frame.PowerShell.Runspace); + _psFrameStack.Push(frame); + RunPowerShellLoop(frame.FrameType); + } + + private void PopFrame() + { + _isExiting = false; + PowerShellContextFrame frame = _psFrameStack.Pop(); + try + { + RemoveRunspaceEventHandlers(frame.PowerShell.Runspace); + if (_psFrameStack.Count > 0) + { + AddRunspaceEventHandlers(CurrentPowerShell.Runspace); + } + } + finally + { + frame.Dispose(); + } + } + + private void AddRunspaceEventHandlers(Runspace runspace) + { + runspace.Debugger.DebuggerStop += OnDebuggerStopped; + runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; + } + + private void RemoveRunspaceEventHandlers(Runspace runspace) + { + runspace.Debugger.DebuggerStop -= OnDebuggerStopped; + runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; + } + + private void RunPowerShellLoop(PowerShellFrameType powerShellFrameType) { var cancellationContext = LoopCancellationContext.EnterNew( this, - _pwshContext.CurrentCancellationSource, + CurrentPowerShellCancellationSource, _consumerThreadCancellationSource); - PowerShellPushed?.Invoke(this, powerShellPushedArgs); - try { if (_runIdleLoop) @@ -513,7 +629,9 @@ private void OnPowerShellPushed(object sender, PowerShellPushedArgs powerShellPu return; } - if ((powerShellPushedArgs.FrameType & PowerShellFrameType.Debug) != 0) + _consoleRepl.PushNewReplTask(); + + if ((powerShellFrameType & PowerShellFrameType.Debug) != 0) { RunDebugLoop(cancellationContext); return; @@ -524,31 +642,22 @@ private void OnPowerShellPushed(object sender, PowerShellPushedArgs powerShellPu finally { _runIdleLoop = false; - _pwshContext.PopPowerShell(); + PopFrame(); cancellationContext.Dispose(); } } - private void OnPowerShellPopped(object sender, PowerShellPoppedArgs promptFramePoppedArgs) - { - PromptFramePopped?.Invoke(this, promptFramePoppedArgs); - } - - private void OnFrameExiting(object sender, FrameExitingArgs nestedPromptExitingArgs) - { - FrameExiting?.Invoke(this, nestedPromptExitingArgs); - } - private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) { _debuggingContext.OnDebuggerStop(sender, debuggerStopEventArgs); - _pwshContext.PushDebugPowerShell(); + PushDebugPowerShell(); } - private void OnDebuggerResuming(object sender, DebuggerResumingArgs debuggerResumedArgs) + private void SetDebuggerResuming(DebuggerResumeAction resumeAction) { - _debuggingContext.OnDebuggerResuming(sender, debuggerResumedArgs); - DebuggerResuming?.Invoke(this, debuggerResumedArgs); + _consoleRepl.SetReplPop(); + _debuggingContext.SetDebuggerResuming(resumeAction); + DebuggerResuming?.Invoke(this, new DebuggerResumingEventArgs(resumeAction)); } private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) @@ -556,20 +665,70 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs break BreakpointUpdated?.Invoke(this, breakpointUpdatedEventArgs); } + internal struct PowerShellRunspaceContext + { + private readonly PowerShellExecutionService _executionService; + + public PowerShellRunspaceContext(PowerShellExecutionService executionService) + { + _executionService = executionService; + } + + public Runspace Runspace => _executionService.CurrentPowerShell.Runspace; + + public bool IsRunspacePushed => _executionService._psFrameStack.Count > 1; + + public SMA.PowerShell CurrentPowerShell => _executionService.CurrentPowerShell; + + public void SetShouldExit() + { + if (_executionService._psFrameStack.Count <= 1) + { + return; + } + + _executionService._isExiting = true; + + if ((_executionService._psFrameStack.Peek().FrameType & PowerShellFrameType.NonInteractive) == 0) + { + _executionService._consoleRepl.SetReplPop(); + } + } + + public void ProcessDebuggerResult(DebuggerCommandResults debuggerResult) + { + if (debuggerResult.ResumeAction != null) + { + _executionService.SetDebuggerResuming(debuggerResult.ResumeAction.Value); + } + } + + public void PushNestedPowerShell() + { + _executionService.PushNestedPowerShell(); + } + + public void PushPowerShell(Runspace runspace) + { + _executionService.PushPowerShell(runspace); + } + } + private class DebuggingContext { private CancellationTokenSource _debuggerCancellationTokenSource; - public CancellationToken? DebuggerResumeCancellationToken => _debuggerCancellationTokenSource?.Token; + public CancellationToken DebuggerResumeCancellationToken => _debuggerCancellationTokenSource.Token; public DebuggerStopEventArgs LastStopEventArgs { get; private set; } - public bool ShouldResume { get; private set; } + public bool HasStopped => _debuggerCancellationTokenSource != null; - public void Reset() + public void ResetCurrentStopContext() { LastStopEventArgs = null; - ShouldResume = false; + _debuggerCancellationTokenSource.Dispose(); + _debuggerCancellationTokenSource = null; } public void OnDebuggerStop(object sender, DebuggerStopEventArgs debuggerStopEventArgs) @@ -578,24 +737,10 @@ public void OnDebuggerStop(object sender, DebuggerStopEventArgs debuggerStopEven LastStopEventArgs = debuggerStopEventArgs; } - public void OnDebuggerResuming(object sender, DebuggerResumingArgs debuggerResumedArgs) + public void SetDebuggerResuming(DebuggerResumeAction resumeAction) { - ShouldResume = true; - - LastStopEventArgs.ResumeAction = debuggerResumedArgs.ResumeAction.Value; - - if (_debuggerCancellationTokenSource != null) - { - try - { - _debuggerCancellationTokenSource.Cancel(); - } - finally - { - _debuggerCancellationTokenSource.Dispose(); - _debuggerCancellationTokenSource = null; - } - } + LastStopEventArgs.ResumeAction = resumeAction; + _debuggerCancellationTokenSource.Cancel(); } } @@ -659,5 +804,44 @@ public void Dispose() } } } + + private class PowerShellContextFrame : IDisposable + { + private bool disposedValue; + + public PowerShellContextFrame(SMA.PowerShell powerShell, PowerShellFrameType frameType, CancellationTokenSource cancellationTokenSource) + { + PowerShell = powerShell; + FrameType = frameType; + CancellationTokenSource = cancellationTokenSource; + } + + public SMA.PowerShell PowerShell { get; } + + public PowerShellFrameType FrameType { get; } + + public CancellationTokenSource CancellationTokenSource { get; } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + PowerShell.Dispose(); + CancellationTokenSource.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/AsyncResetEvent.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/AsyncResetEvent.cs new file mode 100644 index 000000000..fc2d7ec4f --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/AsyncResetEvent.cs @@ -0,0 +1,45 @@ +using Microsoft.PowerShell.EditorServices.Utility; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace PowerShellEditorServices.Services.PowerShell.Utility +{ + internal class AsyncResetEvent + { + private TaskCompletionSource _taskCompletionSource; + + public AsyncResetEvent() + { + _taskCompletionSource = null; + } + + public bool IsBlocking => _taskCompletionSource != null; + + public void SetBlock() + { + Interlocked.CompareExchange(ref _taskCompletionSource, new TaskCompletionSource(), null); + } + + public void Unblock() + { + TaskCompletionSource taskCompletionSource = Interlocked.Exchange(ref _taskCompletionSource, null); + if (taskCompletionSource != null) + { + taskCompletionSource.TrySetResult(true); + } + } + + public Task WaitAsync() + { + if (_taskCompletionSource == null) + { + return Task.CompletedTask; + } + + return _taskCompletionSource.Task; + } + } +} From 3821b75fc1d7ab345fc1fef3817988180b69c4b9 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 4 Aug 2020 16:24:18 -0700 Subject: [PATCH 025/176] Implement remote debugger waiting --- .../Execution/SynchronousPowerShellTask.cs | 59 ++++++++++++++++++- .../PowerShell/PowerShellExecutionService.cs | 2 + .../Utility/PowerShellExtensions.cs | 44 ++++++++++++++ 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 318d5e681..94126e1fe 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -79,7 +79,11 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok cancellationToken.ThrowIfCancellationRequested(); } } - catch (Exception e) when (e is PipelineStoppedException || e is PSRemotingDataStructureException) + catch (PipelineStoppedException) + { + throw new OperationCanceledException(); + } + catch (PSRemotingDataStructureException e) { string message = $"Pipeline stopped while executing command:{Environment.NewLine}{Environment.NewLine}{e}"; Logger.LogError(message); @@ -131,7 +135,58 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT }; } - DebuggerCommandResults debuggerResult = _pwsh.Runspace.Debugger.ProcessCommand(_psCommand, outputCollection); + DebuggerCommandResults debuggerResult = null; + try + { + debuggerResult = _pwsh.Runspace.Debugger.ProcessCommand(_psCommand, outputCollection); + + if (_executionOptions.PropagateCancellationToCaller) + { + cancellationToken.ThrowIfCancellationRequested(); + } + } + catch (PipelineStoppedException) + { + throw new OperationCanceledException(); + } + catch (PSRemotingDataStructureException e) + { + string message = $"Pipeline stopped while executing command:{Environment.NewLine}{Environment.NewLine}{e}"; + Logger.LogError(message); + throw new ExecutionCanceledException(message, e); + } + catch (RuntimeException e) + { + Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}"); + + if (!_executionOptions.WriteOutputToHost) + { + throw; + } + + var errorOutputCollection = new PSDataCollection(); + errorOutputCollection.DataAdded += (object sender, DataAddedEventArgs args) => + { + for (int i = args.Index; i < outputCollection.Count; i++) + { + _psHost.UI.WriteLine(outputCollection[i].ToString()); + } + }; + + var command = new PSCommand() + .AddDebugOutputCommand() + .AddParameter("InputObject", e.ErrorRecord.AsPSObject()); + + _pwsh.Runspace.Debugger.ProcessCommand(command, errorOutputCollection); + } + finally + { + if (_pwsh.HadErrors) + { + _pwsh.Streams.Error.Clear(); + } + } + _psRunspaceContext.ProcessDebuggerResult(debuggerResult); // Optimisation to save wasted computation if we're going to throw the output away anyway diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 122b362cc..f432eedb9 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -649,8 +649,10 @@ private void RunPowerShellLoop(PowerShellFrameType powerShellFrameType) private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) { + CurrentPowerShell.WaitForRemoteOutputIfNeeded(); _debuggingContext.OnDebuggerStop(sender, debuggerStopEventArgs); PushDebugPowerShell(); + CurrentPowerShell.ResumeRemoteOutputIfNeeded(); } private void SetDebuggerResuming(DebuggerResumeAction resumeAction) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs index 558e8659d..423285024 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs @@ -3,6 +3,8 @@ using System.ComponentModel; using System.Management.Automation; using System.Management.Automation.Runspaces; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using SMA = System.Management.Automation; @@ -10,6 +12,27 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility { internal static class PowerShellExtensions { + private static readonly Action s_waitForServicingComplete; + + private static readonly Action s_suspendIncomingData; + + private static readonly Action s_resumeIncomingData; + + static PowerShellExtensions() + { + s_waitForServicingComplete = (Action)Delegate.CreateDelegate( + typeof(Action), + typeof(SMA.PowerShell).GetMethod("WaitForServicingComplete", BindingFlags.Instance | BindingFlags.NonPublic)); + + s_suspendIncomingData = (Action)Delegate.CreateDelegate( + typeof(Action), + typeof(SMA.PowerShell).GetMethod("SuspendIncomingData", BindingFlags.Instance | BindingFlags.NonPublic)); + + s_resumeIncomingData = (Action)Delegate.CreateDelegate( + typeof(Action), + typeof(SMA.PowerShell).GetMethod("ResumeIncomingData", BindingFlags.Instance | BindingFlags.NonPublic)); + } + public static Collection InvokeAndClear(this SMA.PowerShell pwsh) { try @@ -46,6 +69,27 @@ public static void InvokeCommand(this SMA.PowerShell pwsh, PSCommand psCommand) pwsh.InvokeAndClear(); } + public static void WaitForRemoteOutputIfNeeded(this SMA.PowerShell pwsh) + { + if (!pwsh.Runspace.RunspaceIsRemote) + { + return; + } + + s_waitForServicingComplete(pwsh); + s_suspendIncomingData(pwsh); + } + + public static void ResumeRemoteOutputIfNeeded(this SMA.PowerShell pwsh) + { + if (!pwsh.Runspace.RunspaceIsRemote) + { + return; + } + + s_resumeIncomingData(pwsh); + } + public static string GetErrorString(this SMA.PowerShell pwsh) { var sb = new StringBuilder(capacity: 1024) From 7839f68ef36f3c39439dbc1dcdd974f49dc6d1d9 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 5 Aug 2020 14:47:46 -0700 Subject: [PATCH 026/176] Improve cancellation handling and runspace cleanup --- .../PowerShell/Console/ConsoleReplRunner.cs | 6 +- .../Execution/SynchronousPowerShellTask.cs | 28 ++------ .../PowerShell/PowerShellExecutionService.cs | 71 ++++++++++++++++--- .../PowerShell/Utility/RunspaceExtensions.cs | 16 +++++ 4 files changed, 88 insertions(+), 33 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs index 7645b379e..c680cce93 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs @@ -131,7 +131,9 @@ private async Task RunReplLoopAsync() } catch (Exception e) { - // TODO: Do something here + _executionService.EditorServicesHost.UI.WriteErrorLine($"An error occurred while running the REPL loop:{Environment.NewLine}{e}"); + _logger.LogError(e, "An error occurred while running the REPL loop"); + break; } finally { @@ -202,6 +204,8 @@ public void SetReplPop() private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) { + // We don't want to terminate the process + args.Cancel = true; CancelCurrentPrompt(); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 94126e1fe..69492c5e7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -73,22 +73,12 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok try { result = _pwsh.InvokeCommand(_psCommand); - - if (_executionOptions.PropagateCancellationToCaller) - { - cancellationToken.ThrowIfCancellationRequested(); - } + cancellationToken.ThrowIfCancellationRequested(); } - catch (PipelineStoppedException) + catch (Exception e) when (cancellationToken.IsCancellationRequested || e is PipelineStoppedException || e is PSRemotingDataStructureException) { throw new OperationCanceledException(); } - catch (PSRemotingDataStructureException e) - { - string message = $"Pipeline stopped while executing command:{Environment.NewLine}{Environment.NewLine}{e}"; - Logger.LogError(message); - throw new ExecutionCanceledException(message, e); - } catch (RuntimeException e) { Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}"); @@ -139,22 +129,12 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT try { debuggerResult = _pwsh.Runspace.Debugger.ProcessCommand(_psCommand, outputCollection); - - if (_executionOptions.PropagateCancellationToCaller) - { - cancellationToken.ThrowIfCancellationRequested(); - } + cancellationToken.ThrowIfCancellationRequested(); } - catch (PipelineStoppedException) + catch (Exception e) when (cancellationToken.IsCancellationRequested || e is PipelineStoppedException || e is PSRemotingDataStructureException) { throw new OperationCanceledException(); } - catch (PSRemotingDataStructureException e) - { - string message = $"Pipeline stopped while executing command:{Environment.NewLine}{Environment.NewLine}{e}"; - Logger.LogError(message); - throw new ExecutionCanceledException(message, e); - } catch (RuntimeException e) { Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}"); diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index f432eedb9..9ce7aa9db 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; @@ -90,6 +91,8 @@ public static PowerShellExecutionService CreateAndStart( private readonly ConcurrentStack _commandCancellationStack; + private readonly ReaderWriterLockSlim _taskProcessingLock; + private bool _runIdleLoop; private bool _isExiting; @@ -114,6 +117,7 @@ private PowerShellExecutionService( _executionQueue = new BlockingCollection(); _debuggingContext = new DebuggingContext(); _psFrameStack = new Stack(); + _taskProcessingLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); _hostName = hostName; _hostVersion = hostVersion; _languageMode = languageMode; @@ -243,8 +247,6 @@ private void RunTopLevelConsumerLoop() { Initialize(); - _consoleRepl.StartRepl(); - var cancellationContext = LoopCancellationContext.EnterNew( this, CurrentPowerShellCancellationSource, @@ -316,6 +318,8 @@ private void Initialize() ImportModule(module); } } + + _consoleRepl.StartRepl(); } private void SetExecutionPolicy() @@ -542,7 +546,6 @@ private SMA.PowerShell CreateNestedPowerShell() return pwsh; } - private void PushNonInteractivePowerShell() { PushNestedPowerShell(PowerShellFrameType.NonInteractive); @@ -606,12 +609,14 @@ private void AddRunspaceEventHandlers(Runspace runspace) { runspace.Debugger.DebuggerStop += OnDebuggerStopped; runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; + runspace.StateChanged += OnRunspaceStateChanged; } private void RemoveRunspaceEventHandlers(Runspace runspace) { runspace.Debugger.DebuggerStop -= OnDebuggerStopped; runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; + runspace.StateChanged -= OnRunspaceStateChanged; } private void RunPowerShellLoop(PowerShellFrameType powerShellFrameType) @@ -667,6 +672,54 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs break BreakpointUpdated?.Invoke(this, breakpointUpdatedEventArgs); } + private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) + { + if (!runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) + { + PopOrReinitializeRunspace(); + } + } + + private void PopOrReinitializeRunspace() + { + _consoleRepl.SetReplPop(); + CancelCurrentTask(); + + RunspaceStateInfo oldRunspaceState = CurrentPowerShell.Runspace.RunspaceStateInfo; + _taskProcessingLock.EnterWriteLock(); + try + { + while (_psFrameStack.Count > 0 + && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) + { + PopFrame(); + } + + if (_psFrameStack.Count == 0) + { + // If our main runspace was corrupted, + // we must re-initialize our state. + // TODO: Use runspace.ResetRunspaceState() here instead + Initialize(); + + _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." + + " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs."); + EditorServicesHost.UI.WriteErrorLine("The main runspace encountered an error and has been reinitialized. See the PowerShell extension logs for more details."); + } + else + { + _logger.LogError($"Current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was popped."); + EditorServicesHost.UI.WriteErrorLine($"The current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}'." + + " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior." + + " The session is now returning to the previous runspace."); + } + } + finally + { + _taskProcessingLock.ExitWriteLock(); + } + } + internal struct PowerShellRunspaceContext { private readonly PowerShellExecutionService _executionService; @@ -783,24 +836,26 @@ public void Dispose() { public static TaskCancellationContext EnterNew(PowerShellExecutionService executionService, CancellationToken loopCancellationToken) { + executionService._taskProcessingLock.EnterReadLock(); var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(loopCancellationToken); executionService._commandCancellationStack.Push(cancellationTokenSource); - return new TaskCancellationContext(executionService._commandCancellationStack, cancellationTokenSource.Token); + return new TaskCancellationContext(executionService, cancellationTokenSource.Token); } - private TaskCancellationContext(ConcurrentStack commandCancellationStack, CancellationToken cancellationToken) + private TaskCancellationContext(PowerShellExecutionService executionService, CancellationToken cancellationToken) { - _commandCancellationStack = commandCancellationStack; + _executionService = executionService; CancellationToken = cancellationToken; } - private readonly ConcurrentStack _commandCancellationStack; + private readonly PowerShellExecutionService _executionService; public readonly CancellationToken CancellationToken; public void Dispose() { - if (_commandCancellationStack.TryPop(out CancellationTokenSource taskCancellation)) + _executionService._taskProcessingLock.ExitReadLock(); + if (_executionService._commandCancellationStack.TryPop(out CancellationTokenSource taskCancellation)) { taskCancellation.Dispose(); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs index cffea54d3..9ead3ac26 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs @@ -48,5 +48,21 @@ public static string GetRemotePrompt(this Runspace runspace, string basePrompt) { return s_getRemotePromptFunc(runspace, basePrompt); } + + public static bool IsUsable(this RunspaceStateInfo runspaceStateInfo) + { + switch (runspaceStateInfo.State) + { + case RunspaceState.Broken: + case RunspaceState.Closed: + case RunspaceState.Closing: + case RunspaceState.Disconnecting: + case RunspaceState.Disconnected: + return false; + + default: + return true; + } + } } } From 58f5d99be8dc39845f22f87c5e812ae75c0e1318 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 5 Aug 2020 15:50:24 -0700 Subject: [PATCH 027/176] Deal with debug abort in WinPS --- .../Execution/SynchronousPowerShellTask.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 69492c5e7..4e90ff91a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -133,6 +133,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT } catch (Exception e) when (cancellationToken.IsCancellationRequested || e is PipelineStoppedException || e is PSRemotingDataStructureException) { + StopDebuggerIfRemoteDebugSessionFailed(); throw new OperationCanceledException(); } catch (RuntimeException e) @@ -193,6 +194,30 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT return results; } + private void StopDebuggerIfRemoteDebugSessionFailed() + { + // When remoting to Windows PowerShell, + // command cancellation may cancel the remote debug session in a way that the local debug session doesn't detect. + // Instead we have to query the remote directly + if (_pwsh.Runspace.RunspaceIsRemote) + { + var assessDebuggerCommand = new PSCommand().AddScript("$Host.Runspace.Debugger.InBreakpoint"); + + var outputCollection = new PSDataCollection(); + _pwsh.Runspace.Debugger.ProcessCommand(assessDebuggerCommand, outputCollection); + + foreach (PSObject output in outputCollection) + { + if (object.Equals(output?.BaseObject, false)) + { + _psRunspaceContext.ProcessDebuggerResult(new DebuggerCommandResults(DebuggerResumeAction.Stop, evaluatedByDebugger: true)); + _logger.LogWarning("Cancelling debug session due to remote command cancellation causing the end of remote debugging session"); + _psHost.UI.WriteWarningLine("Debug session aborted by command cancellation. This is a known issue in the Windows PowerShell 5.1 remoting system."); + } + } + } + } + private void CancelNormalExecution() { _pwsh.Stop(); From ecf1ed030daefa1a32ba8694afdd01fd2038a366 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 10 Aug 2020 16:03:14 -0700 Subject: [PATCH 028/176] Temp refactor --- .../DebugAdapter/DebugEventHandlerService.cs | 2 +- .../Services/DebugAdapter/DebugService.cs | 194 ++++--- .../Handlers/LaunchAndAttachHandler.cs | 2 +- .../PowerShell/Context/IPowerShellContext.cs | 22 + .../PowerShell/Context/PowerShellContext.cs | 47 ++ .../Context/PowerShellContextFrame.cs | 46 ++ .../PowerShellFrameType.cs | 2 +- .../DebuggerResumingEventArgs.cs | 2 +- .../Debugging/IPowerShellDebugContext.cs | 32 ++ .../Execution/PipelineThreadRunner.cs | 467 +++++++++++++++++ .../PowerShell/PowerShellExecutionService.cs | 480 +----------------- .../PowerShell/Runspace/IRunspaceContext.cs | 12 + .../PowerShell/Runspace/RunspaceOrigin.cs | 28 + .../PowerShell/Utility/CancellationContext.cs | 47 ++ .../RemoteFileManagerService.cs | 6 +- .../Session/RunspaceDetails.cs | 21 - 16 files changed, 802 insertions(+), 608 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Context/IPowerShellContext.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContext.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs rename src/PowerShellEditorServices/Services/PowerShell/{Execution => Context}/PowerShellFrameType.cs (95%) rename src/PowerShellEditorServices/Services/PowerShell/{Execution => Debugging}/DebuggerResumingEventArgs.cs (96%) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadRunner.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceOrigin.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs index e645a4c5d..5f2712c46 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs @@ -91,7 +91,7 @@ private void PowerShellContext_RunspaceChanged(object sender, RunspaceChangedEve { if (_debugStateService.WaitingForAttach && e.ChangeAction == RunspaceChangeAction.Enter && - e.NewRunspace.Context == RunspaceContext.DebuggedRunspace) + e.NewRunspace.Context == RunspaceOrigin.DebuggedRunspace) { // Sends the InitializedEvent so that the debugger will continue // sending configuration requests diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index dd69ce9b1..e76f52e48 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -11,9 +11,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services @@ -29,10 +32,14 @@ internal class DebugService private const string PsesGlobalVariableNamePrefix = "__psEditorServices_"; private const string TemporaryScriptFileName = "Script Listing.ps1"; - private readonly ILogger logger; - private readonly PowerShellContextService powerShellContext; + private readonly BreakpointDetails[] s_emptyBreakpointDetailsArray = Array.Empty(); + + private readonly ILogger _logger; + private readonly PowerShellExecutionService _executionService; private readonly BreakpointService _breakpointService; - private RemoteFileManagerService remoteFileManager; + private readonly RemoteFileManagerService remoteFileManager; + + private readonly EditorServicesConsolePSHost _psesHost; private int nextVariableId; private string temporaryScriptListingPath; @@ -57,7 +64,7 @@ internal class DebugService /// Gets a boolean that indicates whether the debugger is currently /// stopped at a breakpoint. /// - public bool IsDebuggerStopped => this.powerShellContext.IsDebuggerStopped; + public bool IsDebuggerStopped => _executionService.IsDebuggerStopped; /// /// Gets the current DebuggerStoppedEventArgs when the debugger @@ -94,20 +101,21 @@ internal class DebugService //// /// An ILogger implementation used for writing log messages. public DebugService( - PowerShellContextService powerShellContext, + PowerShellExecutionService executionService, RemoteFileManagerService remoteFileManager, BreakpointService breakpointService, + EditorServicesConsolePSHost psesHost, ILoggerFactory factory) { - Validate.IsNotNull(nameof(powerShellContext), powerShellContext); + Validate.IsNotNull(nameof(executionService), executionService); - this.logger = factory.CreateLogger(); - this.powerShellContext = powerShellContext; + this._logger = factory.CreateLogger(); + _executionService = executionService; _breakpointService = breakpointService; - this.powerShellContext.DebuggerStop += this.OnDebuggerStopAsync; - this.powerShellContext.DebuggerResumed += this.OnDebuggerResumed; - - this.powerShellContext.BreakpointUpdated += this.OnBreakpointUpdated; + _psesHost = psesHost; + _executionService.DebuggerStopped += this.OnDebuggerStopAsync; + _executionService.DebuggerResuming += this.OnDebuggerResuming; + _executionService.BreakpointUpdated += this.OnBreakpointUpdated; this.remoteFileManager = remoteFileManager; @@ -141,12 +149,12 @@ public async Task SetLineBreakpointsAsync( string scriptPath = scriptFile.FilePath; // Make sure we're using the remote script path - if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote && - this.remoteFileManager != null) + if (_psesHost.Runspace.RunspaceIsRemote + && this.remoteFileManager != null) { if (!this.remoteFileManager.IsUnderRemoteTempPath(scriptPath)) { - this.logger.LogTrace( + this._logger.LogTrace( $"Could not set breakpoints for local path '{scriptPath}' in a remote session."); return Array.Empty(); @@ -163,7 +171,7 @@ public async Task SetLineBreakpointsAsync( this.temporaryScriptListingPath != null && this.temporaryScriptListingPath.Equals(scriptPath, StringComparison.CurrentCultureIgnoreCase)) { - this.logger.LogTrace( + this._logger.LogTrace( $"Could not set breakpoint on temporary script listing path '{scriptPath}'."); return Array.Empty(); @@ -185,7 +193,7 @@ public async Task SetLineBreakpointsAsync( } return await dscBreakpoints.SetLineBreakpointsAsync( - this.powerShellContext, + _executionService, escapedScriptPath, breakpoints).ConfigureAwait(false); } @@ -288,14 +296,14 @@ public VariableDetailsBase[] GetVariables(int variableReferenceId) { if ((variableReferenceId < 0) || (variableReferenceId >= this.variables.Count)) { - logger.LogWarning($"Received request for variableReferenceId {variableReferenceId} that is out of range of valid indices."); + _logger.LogWarning($"Received request for variableReferenceId {variableReferenceId} that is out of range of valid indices."); return Array.Empty(); } VariableDetailsBase parentVariable = this.variables[variableReferenceId]; if (parentVariable.IsExpandable) { - childVariables = parentVariable.GetChildren(this.logger); + childVariables = parentVariable.GetChildren(this._logger); foreach (var child in childVariables) { // Only add child if it hasn't already been added. @@ -394,7 +402,7 @@ public async Task SetVariableAsync(int variableContainerReferenceId, str Validate.IsNotNull(nameof(name), name); Validate.IsNotNull(nameof(value), value); - this.logger.LogTrace($"SetVariableRequest for '{name}' to value string (pre-quote processing): '{value}'"); + this._logger.LogTrace($"SetVariableRequest for '{name}' to value string (pre-quote processing): '{value}'"); // An empty or whitespace only value is not a valid expression for SetVariable. if (value.Trim().Length == 0) @@ -403,26 +411,13 @@ public async Task SetVariableAsync(int variableContainerReferenceId, str } // Evaluate the expression to get back a PowerShell object from the expression string. - PSCommand psCommand = new PSCommand(); - psCommand.AddScript(value); - var errorMessages = new StringBuilder(); - var results = - await this.powerShellContext.ExecuteCommandAsync( - psCommand, - errorMessages, - false, - false).ConfigureAwait(false); - - // Check if PowerShell's evaluation of the expression resulted in an error. - object psobject = results.FirstOrDefault(); - if ((psobject == null) && (errorMessages.Length > 0)) - { - throw new InvalidPowerShellExpressionException(errorMessages.ToString()); - } + // This may throw, in which case the exception is propagated to the caller + PSCommand evaluateExpressionCommand = new PSCommand().AddScript(value); + object expressionResult = (await _executionService.ExecutePSCommandAsync(evaluateExpressionCommand, new PowerShellExecutionOptions(), CancellationToken.None)).FirstOrDefault(); // If PowerShellContext.ExecuteCommand returns an ErrorRecord as output, the expression failed evaluation. // Ideally we would have a separate means from communicating error records apart from normal output. - if (psobject is ErrorRecord errorRecord) + if (expressionResult is ErrorRecord errorRecord) { throw new InvalidPowerShellExpressionException(errorRecord.ToString()); } @@ -473,14 +468,12 @@ await this.powerShellContext.ExecuteCommandAsync( } // Now that we have the scope, get the associated PSVariable object for the variable to be set. - psCommand.Commands.Clear(); - psCommand = new PSCommand(); - psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-Variable"); - psCommand.AddParameter("Name", name.TrimStart('$')); - psCommand.AddParameter("Scope", scope); - - IEnumerable result = await this.powerShellContext.ExecuteCommandAsync(psCommand, sendErrorToHost: false).ConfigureAwait(false); - PSVariable psVariable = result.FirstOrDefault(); + var getVariableCommand = new PSCommand() + .AddCommand(@"Microsoft.PowerShell.Utility\Get-Variable") + .AddParameter("Name", name.TrimStart('$')) + .AddParameter("Scope", scope); + + PSVariable psVariable = (await _executionService.ExecutePSCommandAsync(getVariableCommand, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false)).FirstOrDefault(); if (psVariable == null) { throw new Exception($"Failed to retrieve PSVariable object for '{name}' from scope '{scope}'."); @@ -491,47 +484,42 @@ await this.powerShellContext.ExecuteCommandAsync( // If it is not strongly typed, we simply assign the object directly to the PSVariable potentially changing its type. // Turns out ArgumentTypeConverterAttribute is not public. So we call the attribute through it's base class - // ArgumentTransformationAttribute. - var argTypeConverterAttr = - psVariable.Attributes - .OfType() - .FirstOrDefault(a => a.GetType().Name.Equals("ArgumentTypeConverterAttribute")); + ArgumentTransformationAttribute argTypeConverterAttr = null; + foreach (Attribute variableAttribute in psVariable.Attributes) + { + if (variableAttribute is ArgumentTransformationAttribute argTransformAttr + && argTransformAttr.GetType().Name.Equals("ArgumentTypeConverterAttribute")) + { + argTypeConverterAttr = argTransformAttr; + break; + } + } if (argTypeConverterAttr != null) { - // PSVariable is strongly typed. Need to apply the conversion/transform to the new value. - psCommand.Commands.Clear(); - psCommand = new PSCommand(); - psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-Variable"); - psCommand.AddParameter("Name", "ExecutionContext"); - psCommand.AddParameter("ValueOnly"); - - errorMessages.Clear(); + _logger.LogTrace($"Setting variable '{name}' using conversion to value: {expressionResult ?? ""}"); - var getExecContextResults = - await this.powerShellContext.ExecuteCommandAsync( - psCommand, - errorMessages, - sendErrorToHost: false).ConfigureAwait(false); - - EngineIntrinsics executionContext = getExecContextResults.OfType().FirstOrDefault(); + psVariable.Value = await _executionService.ExecuteDelegateAsync((pwsh, cancellationToken) => + { + var engineIntrinsics = (EngineIntrinsics)pwsh.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); - var msg = $"Setting variable '{name}' using conversion to value: {psobject ?? ""}"; - this.logger.LogTrace(msg); + // TODO: This is almost (but not quite) the same as LanguagePrimitives.Convert(), which does not require the pipeline thread. + // We should investigate changing it. + return argTypeConverterAttr.Transform(engineIntrinsics, expressionResult); - psVariable.Value = argTypeConverterAttr.Transform(executionContext, psobject); + }, "PS debugger argument converter", CancellationToken.None).ConfigureAwait(false); } else { // PSVariable is *not* strongly typed. In this case, whack the old value with the new value. - var msg = $"Setting variable '{name}' directly to value: {psobject ?? ""} - previous type was {psVariable.Value?.GetType().Name ?? ""}"; - this.logger.LogTrace(msg); - psVariable.Value = psobject; + _logger.LogTrace($"Setting variable '{name}' directly to value: {expressionResult ?? ""} - previous type was {psVariable.Value?.GetType().Name ?? ""}"); + psVariable.Value = expressionResult; } // Use the VariableDetails.ValueString functionality to get the string representation for client debugger. // This makes the returned string consistent with the strings normally displayed for variables in the debugger. var tempVariable = new VariableDetails(psVariable); - this.logger.LogTrace($"Set variable '{name}' to: {tempVariable.ValueString ?? ""}"); + _logger.LogTrace($"Set variable '{name}' to: {tempVariable.ValueString ?? ""}"); return tempVariable.ValueString; } @@ -551,29 +539,26 @@ public async Task EvaluateExpressionAsync( int stackFrameId, bool writeResultAsOutput) { - var results = - await this.powerShellContext.ExecuteScriptStringAsync( - expressionString, - false, - writeResultAsOutput).ConfigureAwait(false); + var command = new PSCommand().AddScript(expressionString); + IReadOnlyList results = await _executionService.ExecutePSCommandAsync( + command, + new PowerShellExecutionOptions { WriteOutputToHost = writeResultAsOutput }, + CancellationToken.None).ConfigureAwait(false); // Since this method should only be getting invoked in the debugger, // we can assume that Out-String will be getting used to format results // of command executions into string output. However, if null is returned // then return null so that no output gets displayed. - string outputString = - results != null && results.Any() ? - string.Join(Environment.NewLine, results) : - null; - - // If we've written the result as output, don't return a - // VariableDetails instance. - return - writeResultAsOutput ? - null : - new VariableDetails( - expressionString, - outputString); + if (writeResultAsOutput || results == null || results.Count == 0) + { + return null; + } + + // If we didn't write output, + // return a VariableDetails instance. + return new VariableDetails( + expressionString, + string.Join(Environment.NewLine, results)); } /// @@ -698,15 +683,16 @@ private async Task FetchVariableContainerAsync( string scope, VariableContainerDetails autoVariables) { - PSCommand psCommand = new PSCommand(); - psCommand.AddCommand("Get-Variable"); - psCommand.AddParameter("Scope", scope); + PSCommand psCommand = new PSCommand() + .AddCommand("Get-Variable") + .AddParameter("Scope", scope); - var scopeVariableContainer = - new VariableContainerDetails(this.nextVariableId++, "Scope: " + scope); + var scopeVariableContainer = new VariableContainerDetails(this.nextVariableId++, "Scope: " + scope); this.variables.Add(scopeVariableContainer); - var results = await this.powerShellContext.ExecuteCommandAsync(psCommand, sendErrorToHost: false).ConfigureAwait(false); + IReadOnlyList results = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None) + .ConfigureAwait(false); + if (results != null) { foreach (PSObject psVariableObject in results) @@ -754,7 +740,7 @@ private bool AddToAutoVariables(PSObject psvariable, string scope) optionsProperty.Value as string, out variableScope)) { - this.logger.LogWarning( + this._logger.LogWarning( $"Could not parse a variable's ScopedItemOptions value of '{optionsProperty.Value}'"); } } @@ -810,7 +796,7 @@ private async Task FetchStackFramesAsync(string scriptNameOverride) var callStackVarName = $"$global:{PsesGlobalVariableNamePrefix}CallStack"; psCommand.AddScript($"{callStackVarName} = Get-PSCallStack; {callStackVarName}"); - var results = await this.powerShellContext.ExecuteCommandAsync(psCommand).ConfigureAwait(false); + var results = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); var callStackFrames = results.ToArray(); @@ -893,9 +879,9 @@ internal async void OnDebuggerStopAsync(object sender, DebuggerStopEventArgs e) PSCommand command = new PSCommand(); command.AddScript($"list 1 {int.MaxValue}"); - IEnumerable scriptListingLines = - await this.powerShellContext.ExecuteCommandAsync( - command, false, false).ConfigureAwait(false); + IReadOnlyList scriptListingLines = + await _executionService.ExecutePSCommandAsync( + command, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); if (scriptListingLines != null) { @@ -922,7 +908,7 @@ await this.powerShellContext.ExecuteCommandAsync( } else { - this.logger.LogWarning($"Could not load script context"); + this._logger.LogWarning($"Could not load script context"); } } @@ -968,7 +954,7 @@ await this.remoteFileManager.FetchRemoteFileAsync( this.CurrentDebuggerStoppedEventArgs); } - private void OnDebuggerResumed(object sender, DebuggerResumeAction e) + private void OnDebuggerResuming(object sender, DebuggerResumingEventArgs debuggerResumingEventArgs) { this.CurrentDebuggerStoppedEventArgs = null; } @@ -999,7 +985,7 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) if (mappedPath == null) { - this.logger.LogError( + this._logger.LogError( $"Could not map remote path '{scriptPath}' to a local path."); return; diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index 4f0ee3f15..647c339c7 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -397,7 +397,7 @@ private async Task OnExecutionCompletedAsync(Task executeTask) if (_debugStateService.IsAttachSession) { // Pop the sessions - if (_powerShellContextService.CurrentRunspace.Context == RunspaceContext.EnteredProcess) + if (_powerShellContextService.CurrentRunspace.Context == RunspaceOrigin.EnteredProcess) { try { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/IPowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/IPowerShellContext.cs new file mode 100644 index 000000000..0579be804 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/IPowerShellContext.cs @@ -0,0 +1,22 @@ +using System; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context +{ + internal interface IPowerShellContext : IDisposable + { + SMA.PowerShell CurrentPowerShell { get; } + + bool IsRunspacePushed { get; } + + void SetShouldExit(int exitCode); + + void ProcessDebuggerResult(DebuggerCommandResults debuggerResult); + + void PushNestedPowerShell(); + + void PushPowerShell(Runspace runspaceToUse); + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContext.cs new file mode 100644 index 000000000..5f37ec3f7 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContext.cs @@ -0,0 +1,47 @@ +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using System; +using System.Collections.Generic; +using System.Management.Automation.Runspaces; +using System.Runtime.CompilerServices; +using System.Text; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context +{ + internal class PowerShellContext : IPowerShellContext + { + private readonly Stack _psFrameStack; + + public PowerShellContext() + { + _psFrameStack = new Stack(); + } + + public SMA.PowerShell CurrentPowerShell => _psFrameStack.Peek().PowerShell; + + private void PushFrame(PowerShellContextFrame frame) + { + if (_psFrameStack.Count > 0) + { + RemoveRunspaceEventHandlers(CurrentPowerShell.Runspace); + } + AddRunspaceEventHandlers(frame.PowerShell.Runspace); + _psFrameStack.Push(frame); + RunPowerShellLoop(frame.FrameType); + } + + private void AddRunspaceEventHandlers(Runspace runspace) + { + runspace.Debugger.DebuggerStop += OnDebuggerStopped; + runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; + runspace.StateChanged += OnRunspaceStateChanged; + } + + private void RemoveRunspaceEventHandlers(Runspace runspace) + { + runspace.Debugger.DebuggerStop -= OnDebuggerStopped; + runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; + runspace.StateChanged -= OnRunspaceStateChanged; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs new file mode 100644 index 000000000..68404e39f --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs @@ -0,0 +1,46 @@ +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using System; +using System.Threading; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context +{ + internal class PowerShellContextFrame : IDisposable + { + private bool disposedValue; + + public PowerShellContextFrame(SMA.PowerShell powerShell, PowerShellFrameType frameType, CancellationTokenSource cancellationTokenSource) + { + PowerShell = powerShell; + FrameType = frameType; + CancellationTokenSource = cancellationTokenSource; + } + + public SMA.PowerShell PowerShell { get; } + + public PowerShellFrameType FrameType { get; } + + public CancellationTokenSource CancellationTokenSource { get; } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + PowerShell.Dispose(); + CancellationTokenSource.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellFrameType.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellFrameType.cs similarity index 95% rename from src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellFrameType.cs rename to src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellFrameType.cs index cc308eaac..4b07fb164 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellFrameType.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellFrameType.cs @@ -1,6 +1,6 @@ using System; -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context { [Flags] internal enum PowerShellFrameType diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/DebuggerResumingEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DebuggerResumingEventArgs.cs similarity index 96% rename from src/PowerShellEditorServices/Services/PowerShell/Execution/DebuggerResumingEventArgs.cs rename to src/PowerShellEditorServices/Services/PowerShell/Debugging/DebuggerResumingEventArgs.cs index c04adb031..9c60c5d98 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/DebuggerResumingEventArgs.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DebuggerResumingEventArgs.cs @@ -1,6 +1,6 @@ using System.Management.Automation; -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging { internal class DebuggerResumingEventArgs { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs new file mode 100644 index 000000000..670aee0dd --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs @@ -0,0 +1,32 @@ +using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Text; +using System.Threading; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging +{ + internal interface IPowerShellDebugContext + { + bool IsStopped { get; } + + DscBreakpointCapability DscBreakpointCapability { get; } + + DebuggerStopEventArgs LastStopEventArgs { get; } + + CancellationToken OnResumeCancellationToken { get; } + + void Continue(); + + void StepOver(); + + void StepInto(); + + void StepOut(); + + void Break(); + + void Abort(); + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadRunner.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadRunner.cs new file mode 100644 index 000000000..7cb2e6a41 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadRunner.cs @@ -0,0 +1,467 @@ +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using Microsoft.PowerShell.EditorServices.Utility; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Host; +using System.Management.Automation.Runspaces; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + internal class PipelineThreadRunner + { + private static readonly string s_commandsModulePath = Path.GetFullPath( + Path.Combine( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + "../../Commands/PowerShellEditorServices.Commands.psd1")); + + private static readonly PropertyInfo s_shouldProcessInExecutionThreadProperty = + typeof(PSEventSubscriber) + .GetProperty( + "ShouldProcessInExecutionThread", + BindingFlags.Instance | BindingFlags.NonPublic); + + private readonly IPowerShellContext _pwshContext; + + private readonly BlockingCollection _executionQueue; + + private readonly CancellationTokenSource _consumerThreadCancellationSource; + + private readonly Thread _pipelineThread; + + private readonly ILoggerFactory _loggerFactory; + + private readonly ILogger _logger; + + private readonly ILanguageServer _languageServer; + + private readonly ConsoleReplRunner _consoleRepl; + + private readonly string _hostName; + + private readonly Version _hostVersion; + + private readonly PSLanguageMode _languageMode; + + private readonly PSHost _internalHost; + + private readonly ProfilePathInfo _profilePaths; + + private readonly IReadOnlyList _additionalModulesToLoad; + + private readonly IPowerShellDebugContext _debugContext; + + private readonly ConcurrentStack _loopCancellationStack; + + private readonly ConcurrentStack _commandCancellationStack; + + private readonly ReaderWriterLockSlim _taskProcessingLock; + + private bool _isExiting; + + private bool _runIdleLoop; + + public PipelineThreadRunner() + { + _pipelineThread = new Thread(RunTopLevelConsumerLoop) + { + Name = "PSES Execution Service Thread", + }; + _pipelineThread.SetApartmentState(ApartmentState.STA); + } + + public Task QueueTask(SynchronousTask synchronousTask) + { + _executionQueue.Add(synchronousTask); + return synchronousTask.Task; + } + public void Start() + { + _pipelineThread.Start(); + } + + public void Stop() + { + _consumerThreadCancellationSource.Cancel(); + _pipelineThread.Join(); + } + + public void CancelCurrentTask() + { + if (_commandCancellationStack.TryPeek(out CancellationTokenSource currentCommandCancellation)) + { + currentCommandCancellation.Cancel(); + } + } + + public void Dispose() + { + Stop(); + _pwshContext.Dispose(); + } + + + private void RunTopLevelConsumerLoop() + { + Initialize(); + + var cancellationContext = LoopCancellationContext.EnterNew( + this, + CurrentPowerShellCancellationSource, + _consumerThreadCancellationSource); + try + { + foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationContext.CancellationToken)) + { + RunTaskSynchronously(task, cancellationContext.CancellationToken); + } + } + catch (OperationCanceledException) + { + // Catch cancellations to end nicely + } + finally + { + cancellationContext.Dispose(); + } + } + + private void RunNestedLoop(in LoopCancellationContext cancellationContext) + { + try + { + foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationContext.CancellationToken)) + { + RunTaskSynchronously(task, cancellationContext.CancellationToken); + + if (_isExiting) + { + break; + } + } + } + catch (OperationCanceledException) + { + // Catch cancellations to end nicely + } + } + + private void RunDebugLoop(in LoopCancellationContext cancellationContext) + { + // If the debugger is resumed while the execution queue listener is blocked on getting a new execution event, + // we must cancel the blocking call + var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_debugContext.DebuggerResumeCancellationToken, cancellationContext.CancellationToken); + + try + { + DebuggerStopped?.Invoke(this, _debugContext.LastStopEventArgs); + + // Run commands, but cancelling our blocking wait if the debugger resumes + foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationSource.Token)) + { + // We don't want to cancel the current command when the debugger resumes, + // since that command will be resuming the debugger. + // Instead let it complete and check the cancellation afterward. + RunTaskSynchronously(task, cancellationContext.CancellationToken); + + if (cancellationSource.Token.IsCancellationRequested) + { + break; + } + } + } + catch (OperationCanceledException) + { + // Catch cancellations to end nicely + } + finally + { + _debugContext.ResetCurrentStopContext(); + cancellationSource.Dispose(); + } + } + + private void RunIdleLoop(in LoopCancellationContext cancellationContext) + { + try + { + while (_executionQueue.TryTake(out ISynchronousTask task)) + { + RunTaskSynchronously(task, cancellationContext.CancellationToken); + } + } + catch (OperationCanceledException) + { + + } + + // TODO: Run nested pipeline here for engine event handling + } + + + private void RunTaskSynchronously(ISynchronousTask task, CancellationToken loopCancellationToken) + { + if (task.IsCanceled) + { + return; + } + + using (var cancellationContext = TaskCancellationContext.EnterNew(this, loopCancellationToken)) + { + task.ExecuteSynchronously(cancellationContext.CancellationToken); + } + } + + private void Initialize() + { + ReadLine = new ConsoleReadLine(); + + EditorServicesHost = new EditorServicesConsolePSHost( + _loggerFactory, + _hostName, + _hostVersion, + _internalHost, + ReadLine); + + PushInitialRunspace(EditorServicesHost, _languageMode); + + EditorServicesHost.RegisterPowerShellContext(new PowerShellRunspaceContext(this)); + + EngineIntrinsics = (EngineIntrinsics)CurrentPowerShell.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); + + PSReadLineProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, CurrentPowerShell); + PSReadLineProxy.OverrideIdleHandler(OnPowerShellIdle); + ReadLine.RegisterExecutionDependencies(this, PSReadLineProxy); + + if (VersionUtils.IsWindows) + { + SetExecutionPolicy(); + } + + LoadProfiles(); + + ImportModule(s_commandsModulePath); + + if (_additionalModulesToLoad != null && _additionalModulesToLoad.Count > 0) + { + foreach (string module in _additionalModulesToLoad) + { + ImportModule(module); + } + } + + _consoleRepl.StartRepl(); + } + + private void SetExecutionPolicy() + { + // We want to get the list hierarchy of execution policies + // Calling the cmdlet is the simplest way to do that + IReadOnlyList policies = _pwshContext.CurrentPowerShell + .AddCommand("Microsoft.PowerShell.Security\\Get-ExecutionPolicy") + .AddParameter("-List") + .InvokeAndClear(); + + // The policies come out in the following order: + // - MachinePolicy + // - UserPolicy + // - Process + // - CurrentUser + // - LocalMachine + // We want to ignore policy settings, since we'll already have those anyway. + // Then we need to look at the CurrentUser setting, and then the LocalMachine setting. + // + // Get-ExecutionPolicy -List emits PSObjects with Scope and ExecutionPolicy note properties + // set to expected values, so we must sift through those. + + ExecutionPolicy policyToSet = ExecutionPolicy.Bypass; + var currentUserPolicy = (ExecutionPolicy)policies[policies.Count - 2].Members["ExecutionPolicy"].Value; + if (currentUserPolicy != ExecutionPolicy.Undefined) + { + policyToSet = currentUserPolicy; + } + else + { + var localMachinePolicy = (ExecutionPolicy)policies[policies.Count - 1].Members["ExecutionPolicy"].Value; + if (localMachinePolicy != ExecutionPolicy.Undefined) + { + policyToSet = localMachinePolicy; + } + } + + // If there's nothing to do, save ourselves a PowerShell invocation + if (policyToSet == ExecutionPolicy.Bypass) + { + _logger.LogTrace("Execution policy already set to Bypass. Skipping execution policy set"); + return; + } + + // Finally set the inherited execution policy + _logger.LogTrace("Setting execution policy to {Policy}", policyToSet); + try + { + _pwshContext.CurrentPowerShell.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") + .AddParameter("Scope", ExecutionPolicyScope.Process) + .AddParameter("ExecutionPolicy", policyToSet) + .AddParameter("Force") + .InvokeAndClear(); + } + catch (CmdletInvocationException e) + { + _logger.LogError(e, "Error occurred calling 'Set-ExecutionPolicy -Scope Process -ExecutionPolicy {Policy} -Force'", policyToSet); + } + } + + private void LoadProfiles() + { + var profileVariable = new PSObject(); + + AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.AllUsersAllHosts), _profilePaths.AllUsersAllHosts); + AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.AllUsersCurrentHost), _profilePaths.AllUsersCurrentHost); + AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.CurrentUserAllHosts), _profilePaths.CurrentUserAllHosts); + AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.CurrentUserCurrentHost), _profilePaths.CurrentUserCurrentHost); + + _pwshContext.CurrentPowerShell.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); + } + + private void AddProfileMemberAndLoadIfExists(PSObject profileVariable, string profileName, string profilePath) + { + profileVariable.Members.Add(new PSNoteProperty(profileName, profilePath)); + + if (File.Exists(profilePath)) + { + var psCommand = new PSCommand() + .AddScript(profilePath, useLocalScope: false) + .AddOutputCommand(); + + _pwshContext.CurrentPowerShell.InvokeCommand(psCommand); + } + } + + private void ImportModule(string moduleNameOrPath) + { + _pwshContext.CurrentPowerShell.AddCommand("Microsoft.PowerShell.Core\\Import-Module") + .AddParameter("-Name", moduleNameOrPath) + .InvokeAndClear(); + } + + private void PushInitialRunspace(EditorServicesConsolePSHost psHost, PSLanguageMode languageMode) + { + InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" + ? InitialSessionState.CreateDefault() + : InitialSessionState.CreateDefault2(); + + iss.LanguageMode = languageMode; + + Runspace runspace = RunspaceFactory.CreateRunspace(psHost, iss); + + runspace.SetApartmentStateToSta(); + runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; + + runspace.Open(); + + AddRunspaceEventHandlers(runspace); + + var pwsh = SMA.PowerShell.Create(); + pwsh.Runspace = runspace; + _psFrameStack.Push(new PowerShellContextFrame(pwsh, PowerShellFrameType.Normal, new CancellationTokenSource())); + + Runspace.DefaultRunspace = runspace; + } + + private void OnPowerShellIdle() + { + if (_executionQueue.Count == 0) + { + return; + } + + _runIdleLoop = true; + PushNonInteractivePowerShell(); + } + + private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) + { + IsDebuggerStopped = true; + CurrentPowerShell.WaitForRemoteOutputIfNeeded(); + _debugContext.OnDebuggerStop(sender, debuggerStopEventArgs); + PushDebugPowerShell(); + CurrentPowerShell.ResumeRemoteOutputIfNeeded(); + IsDebuggerStopped = false; + } + + private void SetDebuggerResuming(DebuggerResumeAction resumeAction) + { + _consoleRepl.SetReplPop(); + _debugContext.SetDebuggerResuming(resumeAction); + DebuggerResuming?.Invoke(this, new DebuggerResumingEventArgs(resumeAction)); + } + + private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) + { + BreakpointUpdated?.Invoke(this, breakpointUpdatedEventArgs); + } + + private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) + { + if (!runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) + { + PopOrReinitializeRunspace(); + } + } + + private void PopOrReinitializeRunspace() + { + _consoleRepl.SetReplPop(); + CancelCurrentTask(); + + RunspaceStateInfo oldRunspaceState = CurrentPowerShell.Runspace.RunspaceStateInfo; + _taskProcessingLock.EnterWriteLock(); + try + { + while (_psFrameStack.Count > 0 + && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) + { + PopFrame(); + } + + if (_psFrameStack.Count == 0) + { + // If our main runspace was corrupted, + // we must re-initialize our state. + // TODO: Use runspace.ResetRunspaceState() here instead + Initialize(); + + _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." + + " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs."); + EditorServicesHost.UI.WriteErrorLine("The main runspace encountered an error and has been reinitialized. See the PowerShell extension logs for more details."); + } + else + { + _logger.LogError($"Current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was popped."); + EditorServicesHost.UI.WriteErrorLine($"The current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}'." + + " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior." + + " The session is now returning to the previous runspace."); + } + } + finally + { + _taskProcessingLock.ExitWriteLock(); + } + } + + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 9ce7aa9db..5ee8818e6 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -2,6 +2,7 @@ using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; @@ -23,19 +24,8 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell { - internal class PowerShellExecutionService : IDisposable + internal partial class PowerShellExecutionService : IDisposable { - private static readonly string s_commandsModulePath = Path.GetFullPath( - Path.Combine( - Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), - "../../Commands/PowerShellEditorServices.Commands.psd1")); - - private static readonly PropertyInfo s_shouldProcessInExecutionThreadProperty = - typeof(PSEventSubscriber) - .GetProperty( - "ShouldProcessInExecutionThread", - BindingFlags.Instance | BindingFlags.NonPublic); - public static PowerShellExecutionService CreateAndStart( ILoggerFactory loggerFactory, @@ -57,42 +47,10 @@ public static PowerShellExecutionService CreateAndStart( return executionService; } - private readonly CancellationTokenSource _consumerThreadCancellationSource; - private readonly BlockingCollection _executionQueue; - private readonly ILoggerFactory _loggerFactory; - - private readonly ILogger _logger; - - private readonly ILanguageServer _languageServer; - - private readonly ConsoleReplRunner _consoleRepl; - - private readonly string _hostName; - - private readonly Version _hostVersion; - - private readonly PSLanguageMode _languageMode; - - private readonly PSHost _internalHost; - - private readonly ProfilePathInfo _profilePaths; - - private readonly IReadOnlyList _additionalModulesToLoad; - - private readonly DebuggingContext _debuggingContext; - - private readonly Stack _psFrameStack; - private Thread _pipelineThread; - private readonly ConcurrentStack _loopCancellationStack; - - private readonly ConcurrentStack _commandCancellationStack; - - private readonly ReaderWriterLockSlim _taskProcessingLock; - private bool _runIdleLoop; private bool _isExiting; @@ -140,6 +98,8 @@ private PowerShellExecutionService( public bool IsCurrentlyRemote => EditorServicesHost.Runspace.RunspaceIsRemote; + public bool IsDebuggerStopped { get; private set; } + public event Action DebuggerStopped; public event Action DebuggerResuming; @@ -204,323 +164,12 @@ public Task ExecutePSCommandAsync( PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) => ExecutePSCommandAsync(psCommand, executionOptions, cancellationToken); - public void Stop() - { - _consumerThreadCancellationSource.Cancel(); - _pipelineThread.Join(); - } - - public void CancelCurrentTask() - { - if (_commandCancellationStack.TryPeek(out CancellationTokenSource currentCommandCancellation)) - { - currentCommandCancellation.Cancel(); - } - } - - public void Dispose() - { - Stop(); - while (_psFrameStack.Count > 0) - { - PopFrame(); - } - } - private Task QueueTask(SynchronousTask task) { _executionQueue.Add(task); return task.Task; } - private void Start() - { - _pipelineThread = new Thread(RunTopLevelConsumerLoop) - { - Name = "PSES Execution Service Thread", - }; - _pipelineThread.SetApartmentState(ApartmentState.STA); - _pipelineThread.Start(); - } - - private void RunTopLevelConsumerLoop() - { - Initialize(); - - var cancellationContext = LoopCancellationContext.EnterNew( - this, - CurrentPowerShellCancellationSource, - _consumerThreadCancellationSource); - try - { - foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationContext.CancellationToken)) - { - RunTaskSynchronously(task, cancellationContext.CancellationToken); - } - } - catch (OperationCanceledException) - { - // Catch cancellations to end nicely - } - finally - { - cancellationContext.Dispose(); - } - } - - private void RunTaskSynchronously(ISynchronousTask task, CancellationToken loopCancellationToken) - { - if (task.IsCanceled) - { - return; - } - - using (var cancellationContext = TaskCancellationContext.EnterNew(this, loopCancellationToken)) - { - task.ExecuteSynchronously(cancellationContext.CancellationToken); - } - } - - private void Initialize() - { - ReadLine = new ConsoleReadLine(); - - EditorServicesHost = new EditorServicesConsolePSHost( - _loggerFactory, - _hostName, - _hostVersion, - _internalHost, - ReadLine); - - PushInitialRunspace(EditorServicesHost, _languageMode); - - EditorServicesHost.RegisterPowerShellContext(new PowerShellRunspaceContext(this)); - - EngineIntrinsics = (EngineIntrinsics)CurrentPowerShell.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); - - PSReadLineProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, CurrentPowerShell); - PSReadLineProxy.OverrideIdleHandler(OnPowerShellIdle); - ReadLine.RegisterExecutionDependencies(this, PSReadLineProxy); - - if (VersionUtils.IsWindows) - { - SetExecutionPolicy(); - } - - LoadProfiles(); - - ImportModule(s_commandsModulePath); - - if (_additionalModulesToLoad != null && _additionalModulesToLoad.Count > 0) - { - foreach (string module in _additionalModulesToLoad) - { - ImportModule(module); - } - } - - _consoleRepl.StartRepl(); - } - - private void SetExecutionPolicy() - { - // We want to get the list hierarchy of execution policies - // Calling the cmdlet is the simplest way to do that - IReadOnlyList policies = CurrentPowerShell - .AddCommand("Microsoft.PowerShell.Security\\Get-ExecutionPolicy") - .AddParameter("-List") - .InvokeAndClear(); - - // The policies come out in the following order: - // - MachinePolicy - // - UserPolicy - // - Process - // - CurrentUser - // - LocalMachine - // We want to ignore policy settings, since we'll already have those anyway. - // Then we need to look at the CurrentUser setting, and then the LocalMachine setting. - // - // Get-ExecutionPolicy -List emits PSObjects with Scope and ExecutionPolicy note properties - // set to expected values, so we must sift through those. - - ExecutionPolicy policyToSet = ExecutionPolicy.Bypass; - var currentUserPolicy = (ExecutionPolicy)policies[policies.Count - 2].Members["ExecutionPolicy"].Value; - if (currentUserPolicy != ExecutionPolicy.Undefined) - { - policyToSet = currentUserPolicy; - } - else - { - var localMachinePolicy = (ExecutionPolicy)policies[policies.Count - 1].Members["ExecutionPolicy"].Value; - if (localMachinePolicy != ExecutionPolicy.Undefined) - { - policyToSet = localMachinePolicy; - } - } - - // If there's nothing to do, save ourselves a PowerShell invocation - if (policyToSet == ExecutionPolicy.Bypass) - { - _logger.LogTrace("Execution policy already set to Bypass. Skipping execution policy set"); - return; - } - - // Finally set the inherited execution policy - _logger.LogTrace("Setting execution policy to {Policy}", policyToSet); - try - { - CurrentPowerShell.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") - .AddParameter("Scope", ExecutionPolicyScope.Process) - .AddParameter("ExecutionPolicy", policyToSet) - .AddParameter("Force") - .InvokeAndClear(); - } - catch (CmdletInvocationException e) - { - _logger.LogError(e, "Error occurred calling 'Set-ExecutionPolicy -Scope Process -ExecutionPolicy {Policy} -Force'", policyToSet); - } - } - - private void LoadProfiles() - { - var profileVariable = new PSObject(); - - AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.AllUsersAllHosts), _profilePaths.AllUsersAllHosts); - AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.AllUsersCurrentHost), _profilePaths.AllUsersCurrentHost); - AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.CurrentUserAllHosts), _profilePaths.CurrentUserAllHosts); - AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.CurrentUserCurrentHost), _profilePaths.CurrentUserCurrentHost); - - CurrentPowerShell.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); - } - - private void AddProfileMemberAndLoadIfExists(PSObject profileVariable, string profileName, string profilePath) - { - profileVariable.Members.Add(new PSNoteProperty(profileName, profilePath)); - - if (File.Exists(profilePath)) - { - var psCommand = new PSCommand() - .AddScript(profilePath, useLocalScope: false) - .AddOutputCommand(); - - CurrentPowerShell.InvokeCommand(psCommand); - } - } - - private void ImportModule(string moduleNameOrPath) - { - CurrentPowerShell.AddCommand("Microsoft.PowerShell.Core\\Import-Module") - .AddParameter("-Name", moduleNameOrPath) - .InvokeAndClear(); - } - - private void PushInitialRunspace(EditorServicesConsolePSHost psHost, PSLanguageMode languageMode) - { - InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" - ? InitialSessionState.CreateDefault() - : InitialSessionState.CreateDefault2(); - - iss.LanguageMode = languageMode; - - Runspace runspace = RunspaceFactory.CreateRunspace(psHost, iss); - - runspace.SetApartmentStateToSta(); - runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; - - runspace.Open(); - - AddRunspaceEventHandlers(runspace); - - var pwsh = SMA.PowerShell.Create(); - pwsh.Runspace = runspace; - _psFrameStack.Push(new PowerShellContextFrame(pwsh, PowerShellFrameType.Normal, new CancellationTokenSource())); - - Runspace.DefaultRunspace = runspace; - } - - private void RunNestedLoop(in LoopCancellationContext cancellationContext) - { - try - { - foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationContext.CancellationToken)) - { - RunTaskSynchronously(task, cancellationContext.CancellationToken); - - if (_isExiting) - { - break; - } - } - } - catch (OperationCanceledException) - { - // Catch cancellations to end nicely - } - } - - private void RunDebugLoop(in LoopCancellationContext cancellationContext) - { - // If the debugger is resumed while the execution queue listener is blocked on getting a new execution event, - // we must cancel the blocking call - var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_debuggingContext.DebuggerResumeCancellationToken, cancellationContext.CancellationToken); - - try - { - DebuggerStopped?.Invoke(this, _debuggingContext.LastStopEventArgs); - - // Run commands, but cancelling our blocking wait if the debugger resumes - foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationSource.Token)) - { - // We don't want to cancel the current command when the debugger resumes, - // since that command will be resuming the debugger. - // Instead let it complete and check the cancellation afterward. - RunTaskSynchronously(task, cancellationContext.CancellationToken); - - if (cancellationSource.Token.IsCancellationRequested) - { - break; - } - } - } - catch (OperationCanceledException) - { - // Catch cancellations to end nicely - } - finally - { - _debuggingContext.ResetCurrentStopContext(); - cancellationSource.Dispose(); - } - } - - private void RunIdleLoop(in LoopCancellationContext cancellationContext) - { - try - { - while (_executionQueue.TryTake(out ISynchronousTask task)) - { - RunTaskSynchronously(task, cancellationContext.CancellationToken); - } - } - catch (OperationCanceledException) - { - - } - - // TODO: Run nested pipeline here for engine event handling - } - - private void OnPowerShellIdle() - { - if (_executionQueue.Count == 0) - { - return; - } - - _runIdleLoop = true; - PushNonInteractivePowerShell(); - } - private void PushNestedPowerShell(PowerShellFrameType frameType) { SMA.PowerShell pwsh = CreateNestedPowerShell(); @@ -605,20 +254,6 @@ private void PopFrame() } } - private void AddRunspaceEventHandlers(Runspace runspace) - { - runspace.Debugger.DebuggerStop += OnDebuggerStopped; - runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; - runspace.StateChanged += OnRunspaceStateChanged; - } - - private void RemoveRunspaceEventHandlers(Runspace runspace) - { - runspace.Debugger.DebuggerStop -= OnDebuggerStopped; - runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; - runspace.StateChanged -= OnRunspaceStateChanged; - } - private void RunPowerShellLoop(PowerShellFrameType powerShellFrameType) { var cancellationContext = LoopCancellationContext.EnterNew( @@ -652,74 +287,6 @@ private void RunPowerShellLoop(PowerShellFrameType powerShellFrameType) } } - private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) - { - CurrentPowerShell.WaitForRemoteOutputIfNeeded(); - _debuggingContext.OnDebuggerStop(sender, debuggerStopEventArgs); - PushDebugPowerShell(); - CurrentPowerShell.ResumeRemoteOutputIfNeeded(); - } - - private void SetDebuggerResuming(DebuggerResumeAction resumeAction) - { - _consoleRepl.SetReplPop(); - _debuggingContext.SetDebuggerResuming(resumeAction); - DebuggerResuming?.Invoke(this, new DebuggerResumingEventArgs(resumeAction)); - } - - private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) - { - BreakpointUpdated?.Invoke(this, breakpointUpdatedEventArgs); - } - - private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) - { - if (!runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) - { - PopOrReinitializeRunspace(); - } - } - - private void PopOrReinitializeRunspace() - { - _consoleRepl.SetReplPop(); - CancelCurrentTask(); - - RunspaceStateInfo oldRunspaceState = CurrentPowerShell.Runspace.RunspaceStateInfo; - _taskProcessingLock.EnterWriteLock(); - try - { - while (_psFrameStack.Count > 0 - && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) - { - PopFrame(); - } - - if (_psFrameStack.Count == 0) - { - // If our main runspace was corrupted, - // we must re-initialize our state. - // TODO: Use runspace.ResetRunspaceState() here instead - Initialize(); - - _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." - + " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs."); - EditorServicesHost.UI.WriteErrorLine("The main runspace encountered an error and has been reinitialized. See the PowerShell extension logs for more details."); - } - else - { - _logger.LogError($"Current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was popped."); - EditorServicesHost.UI.WriteErrorLine($"The current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}'." - + " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior." - + " The session is now returning to the previous runspace."); - } - } - finally - { - _taskProcessingLock.ExitWriteLock(); - } - } - internal struct PowerShellRunspaceContext { private readonly PowerShellExecutionService _executionService; @@ -861,44 +428,5 @@ public void Dispose() } } } - - private class PowerShellContextFrame : IDisposable - { - private bool disposedValue; - - public PowerShellContextFrame(SMA.PowerShell powerShell, PowerShellFrameType frameType, CancellationTokenSource cancellationTokenSource) - { - PowerShell = powerShell; - FrameType = frameType; - CancellationTokenSource = cancellationTokenSource; - } - - public SMA.PowerShell PowerShell { get; } - - public PowerShellFrameType FrameType { get; } - - public CancellationTokenSource CancellationTokenSource { get; } - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - PowerShell.Dispose(); - CancellationTokenSource.Dispose(); - } - - disposedValue = true; - } - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs new file mode 100644 index 000000000..065de5e1f --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs @@ -0,0 +1,12 @@ + +using System.Management.Automation.Runspaces; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + internal interface IRunspaceContext + { + bool IsRemote { get; } + + RunspaceOrigin RunspaceOrigin { get; } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceOrigin.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceOrigin.cs new file mode 100644 index 000000000..72ef60308 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceOrigin.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell +{ + /// + /// Specifies the context in which the runspace was encountered. + /// + internal enum RunspaceOrigin + { + /// + /// The original runspace in a local or remote session. + /// + Original, + + /// + /// A runspace in a process that was entered with Enter-PSHostProcess. + /// + EnteredProcess, + + /// + /// A runspace that is being debugged with Debug-Runspace. + /// + DebuggedRunspace + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs new file mode 100644 index 000000000..ad2b12d7c --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace PowerShellEditorServices.Services.PowerShell.Utility +{ + internal struct CancellationContext : IDisposable + { + public static CancellationContext Enter(ConcurrentStack cancellationStack, params CancellationToken[] linkedTokens) + { + var linkedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(linkedTokens); + return Enter(cancellationStack, linkedCancellationSource); + } + + public static CancellationContext Enter(ConcurrentStack cancellationStack, CancellationToken token1, CancellationToken token2) + { + var linkedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(token1, token2); + return Enter(cancellationStack, linkedCancellationSource); + } + + private static CancellationContext Enter(ConcurrentStack cancellationStack, CancellationTokenSource linkedCancellationSource) + { + cancellationStack.Push(linkedCancellationSource); + return new CancellationContext(cancellationStack, linkedCancellationSource.Token); + } + + private readonly ConcurrentStack _cancellationStack; + + private CancellationContext(ConcurrentStack cancellationStack, CancellationToken currentCancellationToken) + { + _cancellationStack = cancellationStack; + CancellationToken = currentCancellationToken; + } + + public readonly CancellationToken CancellationToken; + + public void Dispose() + { + if (_cancellationStack.TryPop(out CancellationTokenSource contextCancellationTokenSource)) + { + contextCancellationTokenSource.Dispose(); + } + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs index 887cc2351..bb4886ebd 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs @@ -618,7 +618,7 @@ private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) private void RegisterPSEditFunction(RunspaceDetails runspaceDetails) { if (runspaceDetails.Location == RunspaceLocation.Remote && - runspaceDetails.Context == RunspaceContext.Original) + runspaceDetails.Context == RunspaceOrigin.Original) { try { @@ -629,7 +629,7 @@ private void RegisterPSEditFunction(RunspaceDetails runspaceDetails) .AddScript(CreatePSEditFunctionScript) .AddParameter("PSEditModule", PSEditModule); - if (runspaceDetails.Context == RunspaceContext.DebuggedRunspace) + if (runspaceDetails.Context == RunspaceOrigin.DebuggedRunspace) { _executionService.ExecutePSCommandAsync(createCommand, new PowerShellExecutionOptions(), CancellationToken.None).GetAwaiter().GetResult(); } @@ -653,7 +653,7 @@ private void RegisterPSEditFunction(RunspaceDetails runspaceDetails) private void RemovePSEditFunction(RunspaceDetails runspaceDetails) { if (runspaceDetails.Location == RunspaceLocation.Remote && - runspaceDetails.Context == RunspaceContext.Original) + runspaceDetails.Context == RunspaceOrigin.Original) { try { diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceDetails.cs index f5aa8d4b0..c744567b8 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceDetails.cs @@ -26,27 +26,6 @@ internal enum RunspaceLocation Remote } - /// - /// Specifies the context in which the runspace was encountered. - /// - internal enum RunspaceContext - { - /// - /// The original runspace in a local or remote session. - /// - Original, - - /// - /// A runspace in a process that was entered with Enter-PSHostProcess. - /// - EnteredProcess, - - /// - /// A runspace that is being debugged with Debug-Runspace. - /// - DebuggedRunspace - } - /// /// Provides details about a runspace being used in the current /// editing session. From 2fc95169a8888001e39151524e9d302d1f216368 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 20 Aug 2020 09:53:22 -0700 Subject: [PATCH 029/176] Migrate runspace pieces --- .../Extensions/Api/EditorContextService.cs | 2 +- .../Api/EditorExtensionServiceProvider.cs | 1 + .../Extensions/Api/EditorUIService.cs | 2 +- .../Extensions/Api/ExtensionCommandService.cs | 1 + .../Extensions/EditorFileRanges.cs | 2 +- .../Extensions/EditorObject.cs | 2 +- .../Hosting/EditorServicesServerFactory.cs | 1 + .../PowerShellEditorServices.csproj | 4 + .../Server/PsesLanguageServer.cs | 2 + .../Server/PsesServiceCollectionExtensions.cs | 4 +- .../DebugAdapter/DebugEventHandlerService.cs | 10 +- .../Services/DebugAdapter/DebugService.cs | 72 ++- .../Debugging/DebuggerStoppedEventArgs.cs | 17 +- .../Handlers/ConfigurationDoneHandler.cs | 12 +- .../Handlers/DebugEvaluateHandler.cs | 20 +- .../Handlers/DisconnectHandler.cs | 23 +- .../Handlers/LaunchAndAttachHandler.cs | 136 +++-- .../Console => Extension}/ChoiceDetails.cs | 2 +- .../EditorOperationsService.cs | 5 +- .../ExtensionService.cs | 3 +- .../IInvokeExtensionCommandHandler.cs | 2 +- .../Handlers/InvokeExtensionCommandHandler.cs | 2 +- .../Host => Extension}/PromptEvents.cs | 2 +- .../PowerShell/Console/ConsoleReadLine.cs | 8 +- .../PowerShell/Console/ConsoleReplRunner.cs | 22 +- .../PowerShell/Console/PSReadLineProxy.cs | 3 +- .../PowerShell/Context/IPowerShellContext.cs | 19 +- .../PowerShell/Context/PowerShellContext.cs | 419 +++++++++++++++- .../Context}/PowerShellVersionDetails.cs | 29 +- .../Debugging/DscBreakpointCapability.cs | 168 +++++++ .../Debugging/IPowerShellDebugContext.cs | 13 +- .../Debugging/PowerShellDebugContext.cs | 120 +++++ .../Execution/PipelineThreadExecutor.cs | 280 +++++++++++ .../Execution/PipelineThreadRunner.cs | 467 ------------------ .../Execution/SynchronousDelegateTask.cs | 17 +- .../Execution/SynchronousPowerShellTask.cs | 20 +- .../Handlers/EvaluateHandler.cs | 0 .../Handlers/ExpandAliasHandler.cs | 0 .../Handlers/GetCommandHandler.cs | 0 .../Handlers/GetCommentHelpHandler.cs | 0 .../Handlers/GetVersionHandler.cs | 2 +- .../Handlers/IEvaluateHandler.cs | 0 .../Handlers/IGetCommentHelpHandler.cs | 0 .../Handlers/IGetPSHostProcessesHandler.cs | 0 .../Handlers/IGetRunspaceHandler.cs | 0 .../Handlers/IGetVersionHandler.cs | 4 +- .../PSHostProcessAndRunspaceHandlers.cs | 0 .../Handlers/ShowHelpHandler.cs | 0 .../Host/EditorServicesConsolePSHost.cs | 25 +- .../PowerShell/PowerShellExecutionService.cs | 363 ++------------ .../PowerShell/Runspace/IRunspaceContext.cs | 12 - .../PowerShell/Runspace/IRunspaceInfo.cs | 28 ++ .../Runspace/RunspaceChangedEventArgs.cs | 67 +++ .../PowerShell/Runspace/RunspaceInfo.cs | 33 ++ .../PowerShell/Runspace/RunspaceOrigin.cs | 11 +- .../Runspace}/SessionDetails.cs | 17 +- .../PowerShell/Utility/AsyncResetEvent.cs | 45 -- .../PowerShell/Utility/CancellationContext.cs | 42 +- .../Utility}/CommandHelpers.cs | 7 +- .../PowerShell/Utility/RunspaceExtensions.cs | 3 +- .../Capabilities/DscBreakpointCapability.cs | 20 +- .../Services/Symbols/SymbolDetails.cs | 2 +- .../Services/Symbols/SymbolsService.cs | 2 +- .../Handlers/ITemplateHandlers.cs | 2 +- .../Handlers/TemplateHandlers.cs | 2 +- .../TemplateService.cs | 6 +- .../Handlers/CompletionHandler.cs | 2 +- .../RemoteFileManagerService.cs | 121 ++--- .../Utility/AsyncLock.cs | 125 ----- .../Utility/AsyncQueue.cs | 221 --------- .../Utility/PathUtils.cs | 6 + 71 files changed, 1552 insertions(+), 1528 deletions(-) rename src/PowerShellEditorServices/Services/{PowerShellContext/Console => Extension}/ChoiceDetails.cs (98%) rename src/PowerShellEditorServices/Services/{PowerShellContext => Extension}/EditorOperationsService.cs (98%) rename src/PowerShellEditorServices/Services/{PowerShellContext => Extension}/ExtensionService.cs (98%) rename src/PowerShellEditorServices/Services/{PowerShellContext => Extension}/Handlers/IInvokeExtensionCommandHandler.cs (93%) rename src/PowerShellEditorServices/Services/{PowerShellContext => Extension}/Handlers/InvokeExtensionCommandHandler.cs (96%) rename src/PowerShellEditorServices/Services/{PowerShellContext/Session/Host => Extension}/PromptEvents.cs (93%) rename src/PowerShellEditorServices/Services/{PowerShellContext/Session => PowerShell/Context}/PowerShellVersionDetails.cs (81%) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadRunner.cs rename src/PowerShellEditorServices/Services/{PowerShellContext => PowerShell}/Handlers/EvaluateHandler.cs (100%) rename src/PowerShellEditorServices/Services/{PowerShellContext => PowerShell}/Handlers/ExpandAliasHandler.cs (100%) rename src/PowerShellEditorServices/Services/{PowerShellContext => PowerShell}/Handlers/GetCommandHandler.cs (100%) rename src/PowerShellEditorServices/Services/{PowerShellContext => PowerShell}/Handlers/GetCommentHelpHandler.cs (100%) rename src/PowerShellEditorServices/Services/{PowerShellContext => PowerShell}/Handlers/GetVersionHandler.cs (98%) rename src/PowerShellEditorServices/Services/{PowerShellContext => PowerShell}/Handlers/IEvaluateHandler.cs (100%) rename src/PowerShellEditorServices/Services/{PowerShellContext => PowerShell}/Handlers/IGetCommentHelpHandler.cs (100%) rename src/PowerShellEditorServices/Services/{PowerShellContext => PowerShell}/Handlers/IGetPSHostProcessesHandler.cs (100%) rename src/PowerShellEditorServices/Services/{PowerShellContext => PowerShell}/Handlers/IGetRunspaceHandler.cs (100%) rename src/PowerShellEditorServices/Services/{PowerShellContext => PowerShell}/Handlers/IGetVersionHandler.cs (91%) rename src/PowerShellEditorServices/Services/{PowerShellContext => PowerShell}/Handlers/PSHostProcessAndRunspaceHandlers.cs (100%) rename src/PowerShellEditorServices/Services/{PowerShellContext => PowerShell}/Handlers/ShowHelpHandler.cs (100%) delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceChangedEventArgs.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs rename src/PowerShellEditorServices/Services/{PowerShellContext/Session => PowerShell/Runspace}/SessionDetails.cs (80%) delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Utility/AsyncResetEvent.cs rename src/PowerShellEditorServices/Services/{PowerShellContext/Utilities => PowerShell/Utility}/CommandHelpers.cs (97%) rename src/PowerShellEditorServices/Services/{PowerShellContext => Template}/Handlers/ITemplateHandlers.cs (97%) rename src/PowerShellEditorServices/Services/{PowerShellContext => Template}/Handlers/TemplateHandlers.cs (97%) rename src/PowerShellEditorServices/Services/{PowerShellContext => Template}/TemplateService.cs (96%) rename src/PowerShellEditorServices/Services/{PowerShellContext => Workspace}/RemoteFileManagerService.cs (88%) delete mode 100644 src/PowerShellEditorServices/Utility/AsyncLock.cs delete mode 100644 src/PowerShellEditorServices/Utility/AsyncQueue.cs diff --git a/src/PowerShellEditorServices/Extensions/Api/EditorContextService.cs b/src/PowerShellEditorServices/Extensions/Api/EditorContextService.cs index 2b880c805..9229f7071 100644 --- a/src/PowerShellEditorServices/Extensions/Api/EditorContextService.cs +++ b/src/PowerShellEditorServices/Extensions/Api/EditorContextService.cs @@ -4,7 +4,7 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Handlers; +using Microsoft.PowerShell.EditorServices.Services.Extension; using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace Microsoft.PowerShell.EditorServices.Extensions.Services diff --git a/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs b/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs index 8d25db01d..f59ff7a61 100644 --- a/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs +++ b/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs @@ -6,6 +6,7 @@ using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Extension; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.LanguageServer.Protocol.Server; diff --git a/src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs b/src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs index f83dd7d20..8fd462e31 100644 --- a/src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs +++ b/src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.Extension; using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace Microsoft.PowerShell.EditorServices.Extensions.Services diff --git a/src/PowerShellEditorServices/Extensions/Api/ExtensionCommandService.cs b/src/PowerShellEditorServices/Extensions/Api/ExtensionCommandService.cs index 229dc0e9b..55c7c693d 100644 --- a/src/PowerShellEditorServices/Extensions/Api/ExtensionCommandService.cs +++ b/src/PowerShellEditorServices/Extensions/Api/ExtensionCommandService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Extension; using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/src/PowerShellEditorServices/Extensions/EditorFileRanges.cs b/src/PowerShellEditorServices/Extensions/EditorFileRanges.cs index f3d84e963..c7a7d5b38 100644 --- a/src/PowerShellEditorServices/Extensions/EditorFileRanges.cs +++ b/src/PowerShellEditorServices/Extensions/EditorFileRanges.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.PowerShell.EditorServices.Handlers; +using Microsoft.PowerShell.EditorServices.Services.Extension; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using System; diff --git a/src/PowerShellEditorServices/Extensions/EditorObject.cs b/src/PowerShellEditorServices/Extensions/EditorObject.cs index b88f41f84..68747ac27 100644 --- a/src/PowerShellEditorServices/Extensions/EditorObject.cs +++ b/src/PowerShellEditorServices/Extensions/EditorObject.cs @@ -5,7 +5,7 @@ using System.Reflection; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Extensions.Services; -using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Extension; namespace Microsoft.PowerShell.EditorServices.Extensions { diff --git a/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs b/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs index 82fe5214a..48c10f1ee 100644 --- a/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs +++ b/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs @@ -12,6 +12,7 @@ using Serilog; using Serilog.Events; using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using Microsoft.PowerShell.EditorServices.Services.Extension; #if DEBUG using Serilog.Debugging; diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 04af52eaf..4f5225819 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -43,5 +43,9 @@ + + + + diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index 20d3e4694..6e8fe1ef7 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -10,8 +10,10 @@ using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Extension; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.Template; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using OmniSharp.Extensions.LanguageServer.Server; using Serilog; diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index 93a36b37c..b9b2d7f3c 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -8,7 +8,9 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.Extension; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.Template; using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace Microsoft.PowerShell.EditorServices.Server @@ -30,7 +32,7 @@ public static IServiceCollection AddPsesLanguageServices( .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton( + .AddSingleton( (provider) => { var extensionService = new ExtensionService( diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs index 5f2712c46..3c72fa824 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; using Microsoft.PowerShell.EditorServices.Services.PowerShell; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.DebugAdapter.Protocol.Events; using OmniSharp.Extensions.DebugAdapter.Protocol.Server; @@ -36,7 +36,7 @@ public DebugEventHandlerService( internal void RegisterEventHandlers() { - //_powerShellContextService.RunspaceChanged += PowerShellContext_RunspaceChanged; + _executionService.RunspaceChanged += ExecutionService_RunspaceChanged; _debugService.BreakpointUpdated += DebugService_BreakpointUpdated; _debugService.DebuggerStopped += DebugService_DebuggerStopped; //_powerShellContextService.DebuggerResumed += PowerShellContext_DebuggerResumed; @@ -44,7 +44,7 @@ internal void RegisterEventHandlers() internal void UnregisterEventHandlers() { - //_powerShellContextService.RunspaceChanged -= PowerShellContext_RunspaceChanged; + _executionService.RunspaceChanged -= ExecutionService_RunspaceChanged; _debugService.BreakpointUpdated -= DebugService_BreakpointUpdated; _debugService.DebuggerStopped -= DebugService_DebuggerStopped; //_powerShellContextService.DebuggerResumed -= PowerShellContext_DebuggerResumed; @@ -87,11 +87,11 @@ e.OriginalEvent.Breakpoints[0] is CommandBreakpoint }); } - private void PowerShellContext_RunspaceChanged(object sender, RunspaceChangedEventArgs e) + private void ExecutionService_RunspaceChanged(object sender, RunspaceChangedEventArgs e) { if (_debugStateService.WaitingForAttach && e.ChangeAction == RunspaceChangeAction.Enter && - e.NewRunspace.Context == RunspaceOrigin.DebuggedRunspace) + e.NewRunspace.RunspaceOrigin == RunspaceOrigin.DebuggedRunspace) { // Sends the InitializedEvent so that the debugger will continue // sending configuration requests diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index e76f52e48..ad6efe64f 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -7,9 +7,9 @@ using System.Management.Automation; using System.Management.Automation.Language; using System.Reflection; -using System.Text; -using System.Threading; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; +using System.Threading; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Services.TextDocument; @@ -17,7 +17,8 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using Microsoft.PowerShell.EditorServices.Utility; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; namespace Microsoft.PowerShell.EditorServices.Services { @@ -64,7 +65,7 @@ internal class DebugService /// Gets a boolean that indicates whether the debugger is currently /// stopped at a breakpoint. /// - public bool IsDebuggerStopped => _executionService.IsDebuggerStopped; + public bool IsDebuggerStopped => _executionService.DebugContext.IsStopped; /// /// Gets the current DebuggerStoppedEventArgs when the debugger @@ -113,9 +114,9 @@ public DebugService( _executionService = executionService; _breakpointService = breakpointService; _psesHost = psesHost; - _executionService.DebuggerStopped += this.OnDebuggerStopAsync; - _executionService.DebuggerResuming += this.OnDebuggerResuming; - _executionService.BreakpointUpdated += this.OnBreakpointUpdated; + _executionService.DebugContext.DebuggerStopped += this.OnDebuggerStopAsync; + _executionService.DebugContext.DebuggerResuming += this.OnDebuggerResuming; + _executionService.DebugContext.BreakpointUpdated += this.OnBreakpointUpdated; this.remoteFileManager = remoteFileManager; @@ -142,10 +143,7 @@ public async Task SetLineBreakpointsAsync( BreakpointDetails[] breakpoints, bool clearExisting = true) { - var dscBreakpoints = - this.powerShellContext - .CurrentRunspace - .GetCapability(); + DscBreakpointCapability dscBreakpoints = _executionService.CurrentRunspace.DscBreakpointCapability; string scriptPath = scriptFile.FilePath; // Make sure we're using the remote script path @@ -163,7 +161,7 @@ public async Task SetLineBreakpointsAsync( string mappedPath = this.remoteFileManager.GetMappedPath( scriptPath, - this.powerShellContext.CurrentRunspace); + _executionService.CurrentRunspace); scriptPath = mappedPath; } @@ -179,8 +177,7 @@ public async Task SetLineBreakpointsAsync( // Fix for issue #123 - file paths that contain wildcard chars [ and ] need to // quoted and have those wildcard chars escaped. - string escapedScriptPath = - PowerShellContextService.WildcardEscapePath(scriptPath); + string escapedScriptPath = PathUtils.WildcardEscape(scriptPath); if (dscBreakpoints == null || !dscBreakpoints.IsDscResourcePath(escapedScriptPath)) { @@ -231,8 +228,7 @@ await _breakpointService.RemoveBreakpointsAsync( /// public void Continue() { - this.powerShellContext.ResumeDebugger( - DebuggerResumeAction.Continue); + _executionService.DebugContext.Continue(); } /// @@ -240,8 +236,7 @@ public void Continue() /// public void StepOver() { - this.powerShellContext.ResumeDebugger( - DebuggerResumeAction.StepOver); + _executionService.DebugContext.StepOver(); } /// @@ -249,8 +244,7 @@ public void StepOver() /// public void StepIn() { - this.powerShellContext.ResumeDebugger( - DebuggerResumeAction.StepInto); + _executionService.DebugContext.StepInto(); } /// @@ -258,8 +252,7 @@ public void StepIn() /// public void StepOut() { - this.powerShellContext.ResumeDebugger( - DebuggerResumeAction.StepOut); + _executionService.DebugContext.StepOut(); } /// @@ -269,8 +262,7 @@ public void StepOut() /// public void Break() { - // Break execution in the debugger - this.powerShellContext.BreakExecution(); + _executionService.DebugContext.BreakExecution(); } /// @@ -279,7 +271,7 @@ public void Break() /// public void Abort() { - this.powerShellContext.AbortExecution(shouldAbortDebugSession: true); + _executionService.DebugContext.Abort(); } /// @@ -816,7 +808,7 @@ private async Task FetchStackFramesAsync(string scriptNameOverride) // When debugging, this is the best way I can find to get what is likely the workspace root. // This is controlled by the "cwd:" setting in the launch config. - string workspaceRootPath = this.powerShellContext.InitialWorkingDirectory; + string workspaceRootPath = _executionService.PowerShellContext.InitialWorkingDirectory; this.stackFrameDetails[i] = StackFrameDetails.Create(callStackFrames[i], autoVariables, localVariables, workspaceRootPath); @@ -827,14 +819,14 @@ private async Task FetchStackFramesAsync(string scriptNameOverride) { this.stackFrameDetails[i].ScriptPath = scriptNameOverride; } - else if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote && - this.remoteFileManager != null && - !string.Equals(stackFrameScriptPath, StackFrameDetails.NoFileScriptPath)) + else if (_executionService.CurrentRunspace.IsRemote() + && this.remoteFileManager != null + && !string.Equals(stackFrameScriptPath, StackFrameDetails.NoFileScriptPath)) { this.stackFrameDetails[i].ScriptPath = this.remoteFileManager.GetMappedPath( stackFrameScriptPath, - this.powerShellContext.CurrentRunspace); + _executionService.CurrentRunspace); } } } @@ -896,9 +888,9 @@ await _executionService.ExecutePSCommandAsync( this.temporaryScriptListingPath = this.remoteFileManager.CreateTemporaryFile( - $"[{this.powerShellContext.CurrentRunspace.SessionDetails.ComputerName}] {TemporaryScriptFileName}", + $"[{_executionService.CurrentRunspace.SessionDetails.ComputerName}] {TemporaryScriptFileName}", scriptListing, - this.powerShellContext.CurrentRunspace); + _executionService.CurrentRunspace); localScriptPath = this.temporaryScriptListingPath @@ -918,14 +910,14 @@ await this.FetchStackFramesAndVariablesAsync( // If this is a remote connection and the debugger stopped at a line // in a script file, get the file contents - if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote && - this.remoteFileManager != null && - !noScriptName) + if (_executionService.CurrentRunspace.IsRemote() + && this.remoteFileManager != null + && !noScriptName) { localScriptPath = await this.remoteFileManager.FetchRemoteFileAsync( e.InvocationInfo.ScriptName, - powerShellContext.CurrentRunspace).ConfigureAwait(false); + _executionService.CurrentRunspace).ConfigureAwait(false); } if (this.stackFrameDetails.Length > 0) @@ -945,7 +937,7 @@ await this.remoteFileManager.FetchRemoteFileAsync( this.CurrentDebuggerStoppedEventArgs = new DebuggerStoppedEventArgs( e, - this.powerShellContext.CurrentRunspace, + _executionService.CurrentRunspace, localScriptPath); // Notify the host that the debugger is stopped @@ -975,13 +967,13 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) { // TODO: This could be either a path or a script block! string scriptPath = lineBreakpoint.Script; - if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote && - this.remoteFileManager != null) + if (_executionService.CurrentRunspace.IsRemote() + && this.remoteFileManager != null) { string mappedPath = this.remoteFileManager.GetMappedPath( scriptPath, - this.powerShellContext.CurrentRunspace); + _executionService.CurrentRunspace); if (mappedPath == null) { diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs index a79564a97..d0be19023 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs @@ -1,7 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Utility; using System.Management.Automation; @@ -26,7 +27,7 @@ internal class DebuggerStoppedEventArgs /// public bool IsRemoteSession { - get { return this.RunspaceDetails.Location == RunspaceLocation.Remote; } + get => RunspaceInfo.IsRemote(); } /// @@ -37,7 +38,7 @@ public bool IsRemoteSession /// /// Gets the RunspaceDetails for the current runspace. /// - public RunspaceDetails RunspaceDetails { get; private set; } + public IRunspaceInfo RunspaceInfo { get; private set; } /// /// Gets the line number at which the debugger stopped execution. @@ -77,8 +78,8 @@ public int ColumnNumber /// The RunspaceDetails of the runspace which raised this event. public DebuggerStoppedEventArgs( DebuggerStopEventArgs originalEvent, - RunspaceDetails runspaceDetails) - : this(originalEvent, runspaceDetails, null) + IRunspaceInfo runspaceInfo) + : this(originalEvent, runspaceInfo, null) { } @@ -90,11 +91,11 @@ public DebuggerStoppedEventArgs( /// The local path of the remote script being debugged. public DebuggerStoppedEventArgs( DebuggerStopEventArgs originalEvent, - RunspaceDetails runspaceDetails, + IRunspaceInfo runspaceInfo, string localScriptPath) { Validate.IsNotNull(nameof(originalEvent), originalEvent); - Validate.IsNotNull(nameof(runspaceDetails), runspaceDetails); + Validate.IsNotNull(nameof(runspaceInfo), runspaceInfo); if (!string.IsNullOrEmpty(localScriptPath)) { @@ -107,7 +108,7 @@ public DebuggerStoppedEventArgs( } this.OriginalEvent = originalEvent; - this.RunspaceDetails = runspaceDetails; + this.RunspaceInfo = runspaceInfo; } #endregion diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 0cf270d8b..ef4ec48f5 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -10,7 +10,6 @@ using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using OmniSharp.Extensions.DebugAdapter.Protocol.Events; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; @@ -59,16 +58,9 @@ public Task Handle(ConfigurationDoneArguments request if (!string.IsNullOrEmpty(_debugStateService.ScriptToLaunch)) { - if (false)//_powerShellContextService.SessionState == PowerShellContextState.Ready) - { - // Configuration is done, launch the script - var nonAwaitedTask = LaunchScriptAsync(_debugStateService.ScriptToLaunch) + // TODO: ContinueWith on this task so that any errors can be handled + LaunchScriptAsync(_debugStateService.ScriptToLaunch) .ConfigureAwait(continueOnCapturedContext: false); - } - else - { - _logger.LogTrace("configurationDone request called after script was already launched, skipping it."); - } } if (_debugStateService.IsInteractiveDebugSession) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs index d4f100188..e9a4e88bd 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs @@ -2,27 +2,30 @@ // Licensed under the MIT License. using System; +using System.Management.Automation; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; namespace Microsoft.PowerShell.EditorServices.Handlers { internal class DebugEvaluateHandler : IEvaluateHandler { private readonly ILogger _logger; - private readonly PowerShellContextService _powerShellContextService; + private readonly PowerShellExecutionService _executionService; private readonly DebugService _debugService; public DebugEvaluateHandler( ILoggerFactory factory, - PowerShellContextService powerShellContextService, + PowerShellExecutionService executionService, DebugService debugService) { _logger = factory.CreateLogger(); - _powerShellContextService = powerShellContextService; + _executionService = executionService; _debugService = debugService; } @@ -39,10 +42,11 @@ public async Task Handle(EvaluateRequestArguments request, if (isFromRepl) { - var notAwaited = - _powerShellContextService - .ExecuteScriptStringAsync(request.Expression, false, true) - .ConfigureAwait(false); + // TODO: Await this or handle errors from it + _executionService.ExecutePSCommandAsync( + new PSCommand().AddScript(request.Expression), + new PowerShellExecutionOptions { WriteOutputToHost = true }, + CancellationToken.None); } else { @@ -50,7 +54,7 @@ public async Task Handle(EvaluateRequestArguments request, // VS Code might send this request after the debugger // has been resumed, return an empty result in this case. - if (_powerShellContextService.IsDebuggerStopped) + if (_executionService.DebugContext.IsStopped) { // First check to see if the watch expression refers to a naked variable reference. result = diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs index 248aff048..fb9e8c943 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs @@ -2,14 +2,15 @@ // Licensed under the MIT License. using System; +using System.Management.Automation; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Server; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShell; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; namespace Microsoft.PowerShell.EditorServices.Handlers @@ -45,22 +46,27 @@ public async Task Handle(DisconnectArguments request, Cancel if (_debugStateService.ExecutionCompleted == false) { _debugStateService.ExecutionCompleted = true; - //_executionService.CancelCurrentTask(); + _executionService.CancelCurrentTask(); if (_debugStateService.IsInteractiveDebugSession && _debugStateService.IsAttachSession) { // Pop the sessions - /* - if (_powerShellContextService.CurrentRunspace.Context == RunspaceContext.EnteredProcess) + if (_executionService.CurrentRunspace.RunspaceOrigin == RunspaceOrigin.EnteredProcess) { try { - await _powerShellContextService.ExecuteScriptStringAsync("Exit-PSHostProcess").ConfigureAwait(false); + await _executionService.ExecutePSCommandAsync( + new PSCommand().AddCommand("Exit-PSHostProcess"), + new PowerShellExecutionOptions(), + CancellationToken.None).ConfigureAwait(false); if (_debugStateService.IsRemoteAttach && - _powerShellContextService.CurrentRunspace.Location == RunspaceLocation.Remote) + _executionService.CurrentRunspace.IsRemote()) { - await _powerShellContextService.ExecuteScriptStringAsync("Exit-PSSession").ConfigureAwait(false); + await _executionService.ExecutePSCommandAsync( + new PSCommand().AddCommand("Exit-PSSession"), + new PowerShellExecutionOptions(), + CancellationToken.None).ConfigureAwait(false); } } catch (Exception e) @@ -68,7 +74,6 @@ public async Task Handle(DisconnectArguments request, Cancel _logger.LogException("Caught exception while popping attached process after debugging", e); } } - */ } _debugService.IsClientAttached = false; diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index 647c339c7..09e497595 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -11,13 +11,15 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using OmniSharp.Extensions.DebugAdapter.Protocol.Events; using OmniSharp.Extensions.JsonRpc; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; using OmniSharp.Extensions.DebugAdapter.Protocol.Server; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; namespace Microsoft.PowerShell.EditorServices.Handlers { @@ -94,16 +96,14 @@ internal class LaunchAndAttachHandler : ILaunchHandler(); @@ -111,9 +111,9 @@ public LaunchAndAttachHandler( _breakpointService = breakpointService; _debugEventHandlerService = debugEventHandlerService; _debugService = debugService; + _executionService = executionService; _debugStateService = debugStateService; _debugStateService.ServerStarted = new TaskCompletionSource(); - _powerShellContextService = powerShellContextService; _remoteFileManagerService = remoteFileManagerService; } @@ -122,7 +122,7 @@ public async Task Handle(PsesLaunchRequestArguments request, Can _debugEventHandlerService.RegisterEventHandlers(); // Determine whether or not the working directory should be set in the PowerShellContext. - if ((_powerShellContextService.CurrentRunspace.Location == RunspaceLocation.Local) && + if (!_executionService.CurrentRunspace.IsRemote() && !_debugService.IsDebuggerStopped) { // Get the working directory that was passed via the debug config @@ -160,7 +160,8 @@ public async Task Handle(PsesLaunchRequestArguments request, Can // the working dir should not be changed. if (!string.IsNullOrEmpty(workingDir)) { - await _powerShellContextService.SetWorkingDirectoryAsync(workingDir, isPathAlreadyEscaped: false).ConfigureAwait(false); + var setDirCommand = new PSCommand().AddCommand("Set-Location").AddParameter("LiteralPath", workingDir); + await _executionService.ExecutePSCommandAsync(setDirCommand, new PowerShellExecutionOptions(), cancellationToken); } _logger.LogTrace("Working dir " + (string.IsNullOrEmpty(workingDir) ? "not set." : $"set to '{workingDir}'")); @@ -189,13 +190,13 @@ public async Task Handle(PsesLaunchRequestArguments request, Can // If the current session is remote, map the script path to the remote // machine if necessary - if (_debugStateService.ScriptToLaunch != null && - _powerShellContextService.CurrentRunspace.Location == RunspaceLocation.Remote) + if (_debugStateService.ScriptToLaunch != null + && _executionService.CurrentRunspace.IsRemote()) { _debugStateService.ScriptToLaunch = _remoteFileManagerService.GetMappedPath( _debugStateService.ScriptToLaunch, - _powerShellContextService.CurrentRunspace); + _executionService.CurrentRunspace); } // If no script is being launched, mark this as an interactive @@ -219,7 +220,7 @@ public async Task Handle(PsesAttachRequestArguments request, Can bool customPipeNameIsSet = !string.IsNullOrEmpty(request.CustomPipeName) && request.CustomPipeName != "undefined"; PowerShellVersionDetails runspaceVersion = - _powerShellContextService.CurrentRunspace.PowerShellVersion; + _executionService.CurrentRunspace.PowerShellVersionDetails; // If there are no host processes to attach to or the user cancels selection, we get a null for the process id. // This is not an error, just a request to stop the original "attach to" request. @@ -241,18 +242,24 @@ public async Task Handle(PsesAttachRequestArguments request, Can { throw new RpcErrorException(0, $"Remote sessions are only available with PowerShell 4 and higher (current session is {runspaceVersion.Version})."); } - else if (_powerShellContextService.CurrentRunspace.Location == RunspaceLocation.Remote) + else if (_executionService.CurrentRunspace.IsRemote()) { throw new RpcErrorException(0, "Cannot attach to a process in a remote session when already in a remote session."); } - await _powerShellContextService.ExecuteScriptStringAsync( - $"Enter-PSSession -ComputerName \"{request.ComputerName}\"", - errorMessages).ConfigureAwait(false); + var enterPSSessionCommand = new PSCommand() + .AddCommand("Enter-PSSession") + .AddParameter("ComputerName", request.ComputerName); - if (errorMessages.Length > 0) + try + { + await _executionService.ExecutePSCommandAsync(enterPSSessionCommand, new PowerShellExecutionOptions(), cancellationToken).ConfigureAwait(false); + } + catch (Exception e) { - throw new RpcErrorException(0, $"Could not establish remote session to computer '{request.ComputerName}'"); + string msg = $"Could not establish remote session to computer '{request.ComputerName}'"; + _logger.LogError(e, msg); + throw new RpcErrorException(0, msg); } _debugStateService.IsRemoteAttach = true; @@ -265,13 +272,19 @@ await _powerShellContextService.ExecuteScriptStringAsync( throw new RpcErrorException(0, $"Attaching to a process is only available with PowerShell 5 and higher (current session is {runspaceVersion.Version})."); } - await _powerShellContextService.ExecuteScriptStringAsync( - $"Enter-PSHostProcess -Id {processId}", - errorMessages).ConfigureAwait(false); + var enterPSHostProcessCommand = new PSCommand() + .AddCommand("Enter-PSHostProcess") + .AddParameter("Id", processId); - if (errorMessages.Length > 0) + try + { + await _executionService.ExecutePSCommandAsync(enterPSHostProcessCommand, new PowerShellExecutionOptions(), cancellationToken).ConfigureAwait(false); + } + catch (Exception e) { - throw new RpcErrorException(0, $"Could not attach to process '{processId}'"); + string msg = $"Could not attach to process '{processId}'"; + _logger.LogError(e, msg); + throw new RpcErrorException(0, msg); } } else if (customPipeNameIsSet) @@ -281,9 +294,20 @@ await _powerShellContextService.ExecuteScriptStringAsync( throw new RpcErrorException(0, $"Attaching to a process with CustomPipeName is only available with PowerShell 6.2 and higher (current session is {runspaceVersion.Version})."); } - await _powerShellContextService.ExecuteScriptStringAsync( - $"Enter-PSHostProcess -CustomPipeName {request.CustomPipeName}", - errorMessages).ConfigureAwait(false); + var enterPSHostProcessCommand = new PSCommand() + .AddCommand("Enter-PSHostProcess") + .AddParameter("CustomPipeName", request.CustomPipeName); + + try + { + await _executionService.ExecutePSCommandAsync(enterPSHostProcessCommand, new PowerShellExecutionOptions(), cancellationToken); + } + catch (Exception e) + { + string msg = $"Could not attach to process with CustomPipeName: '{request.CustomPipeName}'"; + _logger.LogError(e, msg); + throw new RpcErrorException(0, msg); + } if (errorMessages.Length > 0) { @@ -303,22 +327,26 @@ await _powerShellContextService.ExecuteScriptStringAsync( // InitializedEvent will be sent as soon as the RunspaceChanged // event gets fired with the attached runspace. - string debugRunspaceCmd; + var debugRunspaceCmd = new PSCommand().AddCommand("Debug-Runspace"); if (request.RunspaceName != null) { - IEnumerable ids = await _powerShellContextService.ExecuteCommandAsync( - new PSCommand() - .AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace") + var getRunspaceIdCommand = new PSCommand() + .AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace") .AddParameter("Name", request.RunspaceName) - .AddCommand("Microsoft.PowerShell.Utility\\Select-Object") - .AddParameter("ExpandProperty", "Id"), cancellationToken: cancellationToken).ConfigureAwait(false); + .AddCommand("Microsoft.PowerShell.Utility\\Select-Object") + .AddParameter("ExpandProperty", "Id"); + IEnumerable ids = await _executionService.ExecutePSCommandAsync(getRunspaceIdCommand, new PowerShellExecutionOptions(), cancellationToken).ConfigureAwait(false); foreach (var id in ids) { _debugStateService.RunspaceId = id; break; + + // TODO: If we don't end up setting this, we should throw } - debugRunspaceCmd = $"\nDebug-Runspace -Name '{request.RunspaceName}'"; + + // TODO: We have the ID, why not just use that? + debugRunspaceCmd.AddParameter("Name", request.RunspaceName); } else if (request.RunspaceId != null) { @@ -332,21 +360,21 @@ await _powerShellContextService.ExecuteScriptStringAsync( _debugStateService.RunspaceId = runspaceId; - debugRunspaceCmd = $"\nDebug-Runspace -Id {runspaceId}"; + debugRunspaceCmd.AddParameter("Id", runspaceId); } else { _debugStateService.RunspaceId = 1; - debugRunspaceCmd = "\nDebug-Runspace -Id 1"; + debugRunspaceCmd.AddParameter("Id", 1); } // Clear any existing breakpoints before proceeding await _breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(continueOnCapturedContext: false); _debugStateService.WaitingForAttach = true; - Task nonAwaitedTask = _powerShellContextService - .ExecuteScriptStringAsync(debugRunspaceCmd) + Task nonAwaitedTask = _executionService + .ExecutePSCommandAsync(debugRunspaceCmd, new PowerShellExecutionOptions(), CancellationToken.None) .ContinueWith(OnExecutionCompletedAsync); if (runspaceVersion.Version.Major >= 7) @@ -396,24 +424,24 @@ private async Task OnExecutionCompletedAsync(Task executeTask) if (_debugStateService.IsAttachSession) { - // Pop the sessions - if (_powerShellContextService.CurrentRunspace.Context == RunspaceOrigin.EnteredProcess) - { - try - { - await _powerShellContextService.ExecuteScriptStringAsync("Exit-PSHostProcess").ConfigureAwait(false); - - if (_debugStateService.IsRemoteAttach && - _powerShellContextService.CurrentRunspace.Location == RunspaceLocation.Remote) - { - await _powerShellContextService.ExecuteScriptStringAsync("Exit-PSSession").ConfigureAwait(false); - } - } - catch (Exception e) - { - _logger.LogException("Caught exception while popping attached process after debugging", e); - } - } + // Pop the sessions + if (_executionService.CurrentRunspace.RunspaceOrigin == RunspaceOrigin.EnteredProcess) + { + try + { + await _executionService.ExecutePSCommandAsync(new PSCommand().AddCommand("Exit-PSHostProcess"), new PowerShellExecutionOptions(), CancellationToken.None); + + if (_debugStateService.IsRemoteAttach && + _executionService.CurrentRunspace.IsRemote()) + { + await _executionService.ExecutePSCommandAsync(new PSCommand().AddCommand("Exit-PSSession"), new PowerShellExecutionOptions(), CancellationToken.None); + } + } + catch (Exception e) + { + _logger.LogException("Caught exception while popping attached process after debugging", e); + } + } } _debugService.IsClientAttached = false; diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoiceDetails.cs b/src/PowerShellEditorServices/Services/Extension/ChoiceDetails.cs similarity index 98% rename from src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoiceDetails.cs rename to src/PowerShellEditorServices/Services/Extension/ChoiceDetails.cs index 0efc14fbc..fe82210fd 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoiceDetails.cs +++ b/src/PowerShellEditorServices/Services/Extension/ChoiceDetails.cs @@ -4,7 +4,7 @@ using System; using System.Management.Automation.Host; -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext +namespace Microsoft.PowerShell.EditorServices.Services.Extension { /// /// Contains the details about a choice that should be displayed diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs similarity index 98% rename from src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs rename to src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs index c2922e182..96a317ffe 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs +++ b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.PowerShell.EditorServices.Extensions; -using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using OmniSharp.Extensions.LanguageServer.Protocol.Models; @@ -10,7 +9,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.PowerShell.EditorServices.Services +namespace Microsoft.PowerShell.EditorServices.Services.Extension { internal class EditorOperationsService : IEditorOperations { @@ -275,7 +274,7 @@ private bool TestHasLanguageServer(bool warnUser = true) if (warnUser) { - _executionService.EditorServicesHost.UI.WriteWarningLine( + _executionService.PowerShellContext.EditorServicesPSHost.UI.WriteWarningLine( "Editor operations are not supported in temporary consoles. Re-run the command in the main PowerShell Intergrated Console."); } diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs similarity index 98% rename from src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs rename to src/PowerShellEditorServices/Services/Extension/ExtensionService.cs index 112cbaa11..b79a28d96 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs @@ -9,11 +9,10 @@ using Microsoft.PowerShell.EditorServices.Extensions; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.LanguageServer.Protocol.Server; -namespace Microsoft.PowerShell.EditorServices.Services +namespace Microsoft.PowerShell.EditorServices.Services.Extension { /// /// Provides a high-level service which enables PowerShell scripts diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IInvokeExtensionCommandHandler.cs b/src/PowerShellEditorServices/Services/Extension/Handlers/IInvokeExtensionCommandHandler.cs similarity index 93% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IInvokeExtensionCommandHandler.cs rename to src/PowerShellEditorServices/Services/Extension/Handlers/IInvokeExtensionCommandHandler.cs index b6d4fb411..325b74073 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IInvokeExtensionCommandHandler.cs +++ b/src/PowerShellEditorServices/Services/Extension/Handlers/IInvokeExtensionCommandHandler.cs @@ -5,7 +5,7 @@ using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -namespace Microsoft.PowerShell.EditorServices.Handlers +namespace Microsoft.PowerShell.EditorServices.Services.Extension { [Serial, Method("powerShell/invokeExtensionCommand")] internal interface IInvokeExtensionCommandHandler : IJsonRpcNotificationHandler { } diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/InvokeExtensionCommandHandler.cs b/src/PowerShellEditorServices/Services/Extension/Handlers/InvokeExtensionCommandHandler.cs similarity index 96% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/InvokeExtensionCommandHandler.cs rename to src/PowerShellEditorServices/Services/Extension/Handlers/InvokeExtensionCommandHandler.cs index b533787c4..ae65bfae9 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/InvokeExtensionCommandHandler.cs +++ b/src/PowerShellEditorServices/Services/Extension/Handlers/InvokeExtensionCommandHandler.cs @@ -8,7 +8,7 @@ using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Extensions; -namespace Microsoft.PowerShell.EditorServices.Handlers +namespace Microsoft.PowerShell.EditorServices.Services.Extension { internal class InvokeExtensionCommandHandler : IInvokeExtensionCommandHandler { diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/PromptEvents.cs b/src/PowerShellEditorServices/Services/Extension/PromptEvents.cs similarity index 93% rename from src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/PromptEvents.cs rename to src/PowerShellEditorServices/Services/Extension/PromptEvents.cs index 3a4314ca0..9b59e0ba6 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/PromptEvents.cs +++ b/src/PowerShellEditorServices/Services/Extension/PromptEvents.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext +namespace Microsoft.PowerShell.EditorServices.Services.Extension { internal class ShowChoicePromptRequest { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index 161741a59..2bbb9cabf 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -22,6 +22,8 @@ internal class ConsoleReadLine private PowerShellExecutionService _executionService; + private EngineIntrinsics _engineIntrinsics; + #region Constructors #endregion @@ -30,9 +32,11 @@ internal class ConsoleReadLine public void RegisterExecutionDependencies( PowerShellExecutionService executionService, + EngineIntrinsics engineIntrinsics, PSReadLineProxy psrlProxy) { _executionService = executionService; + _engineIntrinsics = engineIntrinsics; _psrlProxy = psrlProxy; } @@ -142,8 +146,8 @@ private Task ReadLineAsync(bool isCommandLine, CancellationToken cancell private string InvokePSReadLine(CancellationToken cancellationToken) { - EngineIntrinsics engineIntrinsics = _executionService.EditorServicesHost.IsRunspacePushed ? null : _executionService.EngineIntrinsics; - return _psrlProxy.ReadLine(_executionService.EditorServicesHost.Runspace, engineIntrinsics, cancellationToken); + EngineIntrinsics engineIntrinsics = _executionService.PowerShellContext.IsRunspacePushed ? null : _engineIntrinsics; + return _psrlProxy.ReadLine(_executionService.CurrentRunspace.Runspace, engineIntrinsics, cancellationToken); } /// diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs index c680cce93..2bb14f88c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs @@ -1,8 +1,7 @@ using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; -using PowerShellEditorServices.Services.PowerShell.Utility; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -14,10 +13,14 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; + internal class ConsoleReplRunner : IDisposable { private readonly ILogger _logger; + private PowerShellContext _pwshContext; + private readonly PowerShellExecutionService _executionService; private readonly ConcurrentStack _replLoopTaskStack; @@ -33,6 +36,7 @@ internal class ConsoleReplRunner : IDisposable public ConsoleReplRunner( ILoggerFactory loggerFactory, + PowerShellContext pwshContext, PowerShellExecutionService executionService) { _logger = loggerFactory.CreateLogger(); @@ -47,7 +51,7 @@ public void StartRepl() System.Console.CancelKeyPress += OnCancelKeyPress; System.Console.InputEncoding = Encoding.UTF8; System.Console.OutputEncoding = Encoding.UTF8; - _executionService.PSReadLineProxy.OverrideReadKey(ReadKey); + _pwshContext.PSReadLineProxy.OverrideReadKey(ReadKey); PushNewReplTask(); } @@ -113,7 +117,7 @@ private async Task RunReplLoopAsync() if (currentCommandCancellation.CancellationSource.IsCancellationRequested || LastKeyWasCtrlC()) { - _executionService.EditorServicesHost.UI.WriteLine(); + _pwshContext.EditorServicesHost.UI.WriteLine(); } continue; } @@ -131,7 +135,7 @@ private async Task RunReplLoopAsync() } catch (Exception e) { - _executionService.EditorServicesHost.UI.WriteErrorLine($"An error occurred while running the REPL loop:{Environment.NewLine}{e}"); + _pwshContext.EditorServicesHost.UI.WriteErrorLine($"An error occurred while running the REPL loop:{Environment.NewLine}{e}"); _logger.LogError(e, "An error occurred while running the REPL loop"); break; } @@ -156,9 +160,9 @@ private async Task GetPromptStringAsync(CancellationToken cancellationTo { string prompt = (await GetPromptOutputAsync(cancellationToken).ConfigureAwait(false)).FirstOrDefault() ?? "PS> "; - if (_executionService.EditorServicesHost.Runspace.RunspaceIsRemote) + if (_pwshContext.EditorServicesHost.Runspace.RunspaceIsRemote) { - prompt = _executionService.EditorServicesHost.Runspace.GetRemotePrompt(prompt); + prompt = _pwshContext.EditorServicesHost.Runspace.GetRemotePrompt(prompt); } return prompt; @@ -176,12 +180,12 @@ private Task> GetPromptOutputAsync(CancellationToken cance private void WritePrompt(string promptString) { - _executionService.EditorServicesHost.UI.Write(promptString); + _pwshContext.EditorServicesHost.UI.Write(promptString); } private Task InvokeReadLineAsync(CancellationToken cancellationToken) { - return _executionService.ReadLine.ReadCommandLineAsync(cancellationToken); + return _pwshContext.ReadLine.ReadCommandLineAsync(cancellationToken); } private Task InvokeInputAsync(string input, CancellationToken cancellationToken) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs index c2db8d33f..fc41e928b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Management.Automation; -using System.Management.Automation.Runspaces; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; @@ -17,6 +16,8 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { + using System.Management.Automation.Runspaces; + internal class PSReadLineProxy { private const string FieldMemberType = "field"; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/IPowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/IPowerShellContext.cs index 0579be804..08b871e3a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/IPowerShellContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/IPowerShellContext.cs @@ -1,22 +1,21 @@ -using System; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using System; using System.Management.Automation; -using System.Management.Automation.Runspaces; +using System.Threading; using SMA = System.Management.Automation; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context { + using System.Management.Automation.Runspaces; + internal interface IPowerShellContext : IDisposable { - SMA.PowerShell CurrentPowerShell { get; } - - bool IsRunspacePushed { get; } + CancellationTokenSource CurrentCancellationSource { get; } - void SetShouldExit(int exitCode); + EditorServicesConsolePSHost EditorServicesPSHost { get; } - void ProcessDebuggerResult(DebuggerCommandResults debuggerResult); - - void PushNestedPowerShell(); + bool IsRunspacePushed { get; } - void PushPowerShell(Runspace runspaceToUse); + string InitialWorkingDirectory { get; } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContext.cs index 5f37ec3f7..fdad69567 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContext.cs @@ -1,33 +1,251 @@ -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Generic; -using System.Management.Automation.Runspaces; -using System.Runtime.CompilerServices; -using System.Text; +using System.IO; +using System.Management.Automation; +using System.Reflection; +using System.Threading; using SMA = System.Management.Automation; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context { + using OmniSharp.Extensions.LanguageServer.Protocol.Server; + using System.Management.Automation.Runspaces; + internal class PowerShellContext : IPowerShellContext { + private static readonly string s_commandsModulePath = Path.GetFullPath( + Path.Combine( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + "../../Commands/PowerShellEditorServices.Commands.psd1")); + + private readonly ILoggerFactory _loggerFactory; + + private readonly ILogger _logger; + private readonly Stack _psFrameStack; - public PowerShellContext() + private readonly HostStartupInfo _hostInfo; + + private readonly PowerShellExecutionService _executionService; + + private readonly ConsoleReplRunner _consoleReplRunner; + + private readonly PipelineThreadExecutor _pipelineExecutor; + + public PowerShellContext( + ILoggerFactory loggerFactory, + HostStartupInfo hostInfo, + ILanguageServer languageServer, + PowerShellExecutionService executionService, + PipelineThreadExecutor pipelineExecutor) { + _loggerFactory = loggerFactory; + _logger = loggerFactory.CreateLogger(); + _hostInfo = hostInfo; + _executionService = executionService; + _pipelineExecutor = pipelineExecutor; _psFrameStack = new Stack(); + DebugContext = new PowerShellDebugContext(languageServer, this, _consoleReplRunner); + + if (hostInfo.ConsoleReplEnabled) + { + _consoleReplRunner = new ConsoleReplRunner(_loggerFactory, executionService); + } } - public SMA.PowerShell CurrentPowerShell => _psFrameStack.Peek().PowerShell; + public SMA.PowerShell CurrentPowerShell => CurrentFrame.PowerShell; + + public Runspace CurrentRunspace => CurrentPowerShell.Runspace; + + public PowerShellContextFrame CurrentFrame => _psFrameStack.Peek(); + + public ConsoleReadLine ReadLine { get; private set; } + + public PSReadLineProxy PSReadLineProxy { get; private set; } + + public EditorServicesConsolePSHost EditorServicesHost { get; private set; } + + public EngineIntrinsics EngineIntrinsics { get; private set; } + + public PowerShellDebugContext DebugContext { get; } + + public RunspaceInfo CurrentRunspaceInfo { get; } + + public RunspaceInfo RunspaceContext { get; } + + public CancellationTokenSource CurrentCancellationSource => throw new NotImplementedException(); + + public EditorServicesConsolePSHost EditorServicesPSHost => throw new NotImplementedException(); + + public bool IsRunspacePushed => throw new NotImplementedException(); + + public string InitialWorkingDirectory { get; private set; } + + public void PushInitialPowerShell() + { + ReadLine = new ConsoleReadLine(); + + EditorServicesHost = new EditorServicesConsolePSHost( + _loggerFactory, + _hostInfo.Name, + _hostInfo.Version, + _hostInfo.PSHost, + ReadLine); + + Runspace runspace = CreateInitialRunspace(); + + var pwsh = SMA.PowerShell.Create(); + pwsh.Runspace = runspace; + _psFrameStack.Push(new PowerShellContextFrame(pwsh, PowerShellFrameType.Normal, new CancellationTokenSource())); + + EditorServicesHost.RegisterPowerShellContext(this); + + EngineIntrinsics = (EngineIntrinsics)CurrentFrame.PowerShell.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); + + PSReadLineProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, CurrentPowerShell); + PSReadLineProxy.OverrideIdleHandler(_pipelineExecutor.OnPowerShellIdle); + ReadLine.RegisterExecutionDependencies(_executionService, EngineIntrinsics, PSReadLineProxy); + + if (VersionUtils.IsWindows) + { + SetExecutionPolicy(); + } + + LoadProfiles(); + + ImportModule(s_commandsModulePath); + + if (_hostInfo.AdditionalModules != null && _hostInfo.AdditionalModules.Count > 0) + { + foreach (string module in _hostInfo.AdditionalModules) + { + ImportModule(module); + } + } + + _consoleReplRunner?.StartRepl(); + } + + public void SetShouldExit(int? exitCode) + { + if (_psFrameStack.Count <= 1) + { + return; + } + + _pipelineExecutor.IsExiting = true; + + if ((CurrentFrame.FrameType & PowerShellFrameType.NonInteractive) == 0) + { + _consoleReplRunner?.SetReplPop(); + } + } + + public void ProcessDebuggerResult(DebuggerCommandResults debuggerResult) + { + if (debuggerResult.ResumeAction != null) + { + DebugContext.RaiseDebuggerResumingEvent(new DebuggerResumingEventArgs(debuggerResult.ResumeAction.Value)); + } + } + + public void PushNestedPowerShell() + { + PushNestedPowerShell(PowerShellFrameType.Normal); + } + + public void PushPowerShell(Runspace runspaceToUse) + { + var pwsh = SMA.PowerShell.Create(); + pwsh.Runspace = runspaceToUse; + + PowerShellFrameType frameType = PowerShellFrameType.Normal; + + if (runspaceToUse.RunspaceIsRemote) + { + frameType |= PowerShellFrameType.Remote; + } + + PushFrame(new PowerShellContextFrame(pwsh, frameType, new CancellationTokenSource())); + } + + public void PopFrame() + { + _pipelineExecutor.IsExiting = false; + PowerShellContextFrame frame = _psFrameStack.Pop(); + try + { + RemoveRunspaceEventHandlers(frame.PowerShell.Runspace); + if (_psFrameStack.Count > 0) + { + AddRunspaceEventHandlers(CurrentPowerShell.Runspace); + } + } + finally + { + frame.Dispose(); + } + } + + public void Dispose() + { + _consoleReplRunner?.Dispose(); + _pipelineExecutor.Dispose(); + } private void PushFrame(PowerShellContextFrame frame) { if (_psFrameStack.Count > 0) { - RemoveRunspaceEventHandlers(CurrentPowerShell.Runspace); + RemoveRunspaceEventHandlers(CurrentFrame.PowerShell.Runspace); } AddRunspaceEventHandlers(frame.PowerShell.Runspace); _psFrameStack.Push(frame); - RunPowerShellLoop(frame.FrameType); + _pipelineExecutor.RunPowerShellLoop(frame.FrameType); + } + + private void PushNestedPowerShell(PowerShellFrameType frameType) + { + SMA.PowerShell pwsh = CreateNestedPowerShell(); + PowerShellFrameType newFrameType = _psFrameStack.Peek().FrameType | PowerShellFrameType.Nested | frameType; + PushFrame(new PowerShellContextFrame(pwsh, newFrameType, new CancellationTokenSource())); + } + + private SMA.PowerShell CreateNestedPowerShell() + { + PowerShellContextFrame currentFrame = _psFrameStack.Peek(); + if ((currentFrame.FrameType & PowerShellFrameType.Remote) != 0) + { + var remotePwsh = SMA.PowerShell.Create(); + remotePwsh.Runspace = currentFrame.PowerShell.Runspace; + return remotePwsh; + } + + // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild + // This means it throws due to the parent pipeline not running... + // So we must use the RunspaceMode.CurrentRunspace option on PowerShell.Create() instead + var pwsh = SMA.PowerShell.Create(RunspaceMode.CurrentRunspace); + pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; + return pwsh; + } + + public void PushNonInteractivePowerShell() + { + PushNestedPowerShell(PowerShellFrameType.NonInteractive); + } + + private void PushDebugPowerShell() + { + PushNestedPowerShell(PowerShellFrameType.Debug); } private void AddRunspaceEventHandlers(Runspace runspace) @@ -43,5 +261,190 @@ private void RemoveRunspaceEventHandlers(Runspace runspace) runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; runspace.StateChanged -= OnRunspaceStateChanged; } + + #region Initial Runspace Setup + + private Runspace CreateInitialRunspace() + { + InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" + ? InitialSessionState.CreateDefault() + : InitialSessionState.CreateDefault2(); + + iss.LanguageMode = _hostInfo.LanguageMode; + + Runspace runspace = RunspaceFactory.CreateRunspace(EditorServicesHost, iss); + + runspace.SetApartmentStateToSta(); + runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; + + runspace.Open(); + + AddRunspaceEventHandlers(runspace); + + Runspace.DefaultRunspace = runspace; + + return runspace; + } + + private void SetExecutionPolicy() + { + // We want to get the list hierarchy of execution policies + // Calling the cmdlet is the simplest way to do that + IReadOnlyList policies = CurrentPowerShell + .AddCommand("Microsoft.PowerShell.Security\\Get-ExecutionPolicy") + .AddParameter("-List") + .InvokeAndClear(); + + // The policies come out in the following order: + // - MachinePolicy + // - UserPolicy + // - Process + // - CurrentUser + // - LocalMachine + // We want to ignore policy settings, since we'll already have those anyway. + // Then we need to look at the CurrentUser setting, and then the LocalMachine setting. + // + // Get-ExecutionPolicy -List emits PSObjects with Scope and ExecutionPolicy note properties + // set to expected values, so we must sift through those. + + ExecutionPolicy policyToSet = ExecutionPolicy.Bypass; + var currentUserPolicy = (ExecutionPolicy)policies[policies.Count - 2].Members["ExecutionPolicy"].Value; + if (currentUserPolicy != ExecutionPolicy.Undefined) + { + policyToSet = currentUserPolicy; + } + else + { + var localMachinePolicy = (ExecutionPolicy)policies[policies.Count - 1].Members["ExecutionPolicy"].Value; + if (localMachinePolicy != ExecutionPolicy.Undefined) + { + policyToSet = localMachinePolicy; + } + } + + // If there's nothing to do, save ourselves a PowerShell invocation + if (policyToSet == ExecutionPolicy.Bypass) + { + _logger.LogTrace("Execution policy already set to Bypass. Skipping execution policy set"); + return; + } + + // Finally set the inherited execution policy + _logger.LogTrace("Setting execution policy to {Policy}", policyToSet); + try + { + CurrentPowerShell.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") + .AddParameter("Scope", ExecutionPolicyScope.Process) + .AddParameter("ExecutionPolicy", policyToSet) + .AddParameter("Force") + .InvokeAndClear(); + } + catch (CmdletInvocationException e) + { + _logger.LogError(e, "Error occurred calling 'Set-ExecutionPolicy -Scope Process -ExecutionPolicy {Policy} -Force'", policyToSet); + } + } + + private void LoadProfiles() + { + var profileVariable = new PSObject(); + + ProfilePathInfo profilePaths = _hostInfo.ProfilePaths; + + AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.AllUsersAllHosts), profilePaths.AllUsersAllHosts); + AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.AllUsersCurrentHost), profilePaths.AllUsersCurrentHost); + AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.CurrentUserAllHosts), profilePaths.CurrentUserAllHosts); + AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.CurrentUserCurrentHost), profilePaths.CurrentUserCurrentHost); + + CurrentPowerShell.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); + } + + private void AddProfileMemberAndLoadIfExists(PSObject profileVariable, string profileName, string profilePath) + { + profileVariable.Members.Add(new PSNoteProperty(profileName, profilePath)); + + if (File.Exists(profilePath)) + { + var psCommand = new PSCommand() + .AddScript(profilePath, useLocalScope: false) + .AddOutputCommand(); + + CurrentPowerShell.InvokeCommand(psCommand); + } + } + + private void ImportModule(string moduleNameOrPath) + { + CurrentPowerShell.AddCommand("Microsoft.PowerShell.Core\\Import-Module") + .AddParameter("-Name", moduleNameOrPath) + .InvokeAndClear(); + } + + #endregion /* Initial Runspace Setup */ + + private void PopOrReinitializeRunspace() + { + _consoleReplRunner?.SetReplPop(); + _pipelineExecutor.CancelCurrentTask(); + + RunspaceStateInfo oldRunspaceState = CurrentPowerShell.Runspace.RunspaceStateInfo; + using (_pipelineExecutor.TakeTaskWriterLock()) + { + while (_psFrameStack.Count > 0 + && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) + { + PopFrame(); + } + + if (_psFrameStack.Count == 0) + { + // If our main runspace was corrupted, + // we must re-initialize our state. + // TODO: Use runspace.ResetRunspaceState() here instead + PushInitialPowerShell(); + + _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." + + " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs."); + EditorServicesHost.UI.WriteErrorLine("The main runspace encountered an error and has been reinitialized. See the PowerShell extension logs for more details."); + } + else + { + _logger.LogError($"Current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was popped."); + EditorServicesHost.UI.WriteErrorLine($"The current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}'." + + " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior." + + " The session is now returning to the previous runspace."); + } + } + } + + private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) + { + DebugContext.SetDebuggerStopped(debuggerStopEventArgs); + try + { + CurrentPowerShell.WaitForRemoteOutputIfNeeded(); + DebugContext.LastStopEventArgs = debuggerStopEventArgs; + PushDebugPowerShell(); + CurrentPowerShell.ResumeRemoteOutputIfNeeded(); + } + finally + { + DebugContext.SetDebuggerResumed(); + } + } + + private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) + { + DebugContext.HandleBreakpointUpdated(breakpointUpdatedEventArgs); + } + + private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) + { + if (!runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) + { + PopOrReinitializeRunspace(); + } + } + } } diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellVersionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs similarity index 81% rename from src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellVersionDetails.cs rename to src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs index 8a91f75bc..a9d828173 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellVersionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs @@ -2,11 +2,16 @@ // Licensed under the MIT License. using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using System; using System.Collections; +using System.Linq; +using System.Management.Automation; using System.Management.Automation.Runspaces; +using System.Threading; +using System.Threading.Tasks; -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context { /// /// Defines the possible enumeration values for the PowerShell process architecture. @@ -90,7 +95,7 @@ public PowerShellVersionDetails( /// The runspace for which version details will be gathered. /// An ILogger implementation used for writing log messages. /// A new PowerShellVersionDetails instance. - public static PowerShellVersionDetails GetVersionDetails(Runspace runspace, ILogger logger) + public static async Task GetVersionDetailsAsync(ILogger logger, PowerShellExecutionService executionService, CancellationToken cancellationToken) { Version powerShellVersion = new Version(5, 0); string versionString = null; @@ -99,7 +104,15 @@ public static PowerShellVersionDetails GetVersionDetails(Runspace runspace, ILog try { - var psVersionTable = PowerShellContextService.ExecuteScriptAndGetItem("$PSVersionTable", runspace, useLocalScope: true); + var psVersionTableCommand = new PSCommand().AddScript("$PSVersionTable", useLocalScope: true); + + Hashtable psVersionTable = (await executionService.ExecutePSCommandAsync( + psVersionTableCommand, + new PowerShellExecutionOptions(), + cancellationToken) + .ConfigureAwait(false)) + .FirstOrDefault(); + if (psVersionTable != null) { var edition = psVersionTable["PSEdition"] as string; @@ -132,7 +145,15 @@ public static PowerShellVersionDetails GetVersionDetails(Runspace runspace, ILog versionString = powerShellVersion.ToString(); } - var arch = PowerShellContextService.ExecuteScriptAndGetItem("$env:PROCESSOR_ARCHITECTURE", runspace, useLocalScope: true); + var procArchCommand = new PSCommand().AddScript("$env:PROCESSOR_ARCHITECTURE", useLocalScope: true); + + string arch = (await executionService.ExecutePSCommandAsync( + procArchCommand, + new PowerShellExecutionOptions(), + cancellationToken) + .ConfigureAwait(false)) + .FirstOrDefault(); + if (arch != null) { if (string.Equals(arch, "AMD64", StringComparison.CurrentCultureIgnoreCase)) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs new file mode 100644 index 000000000..8bf2bfeda --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -0,0 +1,168 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Utility; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using System.Threading; +using SMA = System.Management.Automation; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging +{ + internal class DscBreakpointCapability + { + private string[] dscResourceRootPaths = Array.Empty(); + + private Dictionary breakpointsPerFile = + new Dictionary(); + + public async Task SetLineBreakpointsAsync( + PowerShellExecutionService executionService, + string scriptPath, + BreakpointDetails[] breakpoints) + { + List resultBreakpointDetails = + new List(); + + // We always get the latest array of breakpoint line numbers + // so store that for future use + if (breakpoints.Length > 0) + { + // Set the breakpoints for this scriptPath + this.breakpointsPerFile[scriptPath] = + breakpoints.Select(b => b.LineNumber).ToArray(); + } + else + { + // No more breakpoints for this scriptPath, remove it + this.breakpointsPerFile.Remove(scriptPath); + } + + string hashtableString = + string.Join( + ", ", + this.breakpointsPerFile + .Select(file => $"@{{Path=\"{file.Key}\";Line=@({string.Join(",", file.Value)})}}")); + + // Run Enable-DscDebug as a script because running it as a PSCommand + // causes an error which states that the Breakpoint parameter has not + // been passed. + var dscCommand = new PSCommand().AddScript( + hashtableString.Length > 0 + ? $"Enable-DscDebug -Breakpoint {hashtableString}" + : "Disable-DscDebug"); + + await executionService.ExecutePSCommandAsync( + dscCommand, + new PowerShellExecutionOptions(), + CancellationToken.None); + + // Verify all the breakpoints and return them + foreach (var breakpoint in breakpoints) + { + breakpoint.Verified = true; + } + + return breakpoints.ToArray(); + } + + public bool IsDscResourcePath(string scriptPath) + { + return dscResourceRootPaths.Any( + dscResourceRootPath => + scriptPath.StartsWith( + dscResourceRootPath, + StringComparison.CurrentCultureIgnoreCase)); + } + + public static async Task GetDscCapabilityAsync( + PowerShellExecutionService executionService, + ILogger logger, + CancellationToken cancellationToken) + { + // DSC support is enabled only for Windows PowerShell. + if ((executionService.CurrentRunspace.PowerShellVersionDetails.Version.Major >= 6) && + (executionService.CurrentRunspace.RunspaceOrigin != RunspaceOrigin.DebuggedRunspace)) + { + return null; + } + + Func getDscBreakpointCapabilityFunc = (pwsh, cancellationToken) => + { + PSModuleInfo dscModule = null; + try + { + dscModule = pwsh.AddCommand("Import-Module") + .AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1") + .AddParameter("PassThru") + .AddParameter("ErrorAction", "Ignore") + .InvokeAndClear() + .FirstOrDefault(); + } + catch (RuntimeException e) + { + logger.LogException("Could not load the DSC module!", e); + } + + if (dscModule == null) + { + logger.LogTrace($"Side-by-side DSC module was not found."); + return null; + } + + logger.LogTrace("Side-by-side DSC module found, gathering DSC resource paths..."); + + // The module was loaded, add the breakpoint capability + var capability = new DscBreakpointCapability(); + + pwsh.AddCommand("Microsoft.PowerShell.Utility\\Write-Host") + .AddArgument("Gathering DSC resource paths, this may take a while...") + .InvokeAndClear(); + + Collection resourcePaths = null; + try + { + // Get the list of DSC resource paths + resourcePaths = pwsh.AddCommand("Get-DscResource") + .AddCommand("Select-Object") + .AddParameter("ExpandProperty", "ParentPath") + .InvokeAndClear(); + } + catch (CmdletInvocationException e) + { + logger.LogException("Get-DscResource failed!", e); + } + + if (resourcePaths == null) + { + logger.LogTrace($"No DSC resources found."); + return null; + } + + capability.dscResourceRootPaths = resourcePaths.ToArray(); + + logger.LogTrace($"DSC resources found: {resourcePaths.Count}"); + + return capability; + }; + + return await executionService.ExecuteDelegateAsync( + getDscBreakpointCapabilityFunc, + nameof(getDscBreakpointCapabilityFunc), + cancellationToken); + + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs index 670aee0dd..ba591ac5d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs @@ -1,8 +1,5 @@ -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; -using System; -using System.Collections.Generic; +using System; using System.Management.Automation; -using System.Text; using System.Threading; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging @@ -17,6 +14,12 @@ internal interface IPowerShellDebugContext CancellationToken OnResumeCancellationToken { get; } + public event Action DebuggerStopped; + + public event Action DebuggerResuming; + + public event Action BreakpointUpdated; + void Continue(); void StepOver(); @@ -25,7 +28,7 @@ internal interface IPowerShellDebugContext void StepOut(); - void Break(); + void BreakExecution(); void Abort(); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs new file mode 100644 index 000000000..c1a637971 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -0,0 +1,120 @@ +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; +using System; +using System.Management.Automation; +using System.Threading; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging +{ + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; + using OmniSharp.Extensions.LanguageServer.Protocol.Server; + + internal class PowerShellDebugContext : IPowerShellDebugContext + { + private readonly ILanguageServer _languageServer; + + private readonly PowerShellContext _pwshContext; + + private readonly ConsoleReplRunner _consoleRepl; + + public PowerShellDebugContext( + ILanguageServer languageServer, + PowerShellContext pwshContext, + ConsoleReplRunner consoleReplRunner) + { + _languageServer = languageServer; + _pwshContext = pwshContext; + _consoleRepl = consoleReplRunner; + } + + private CancellationTokenSource _debugLoopCancellationSource; + + public bool IsStopped { get; private set; } + + public DscBreakpointCapability DscBreakpointCapability => throw new NotImplementedException(); + + public DebuggerStopEventArgs LastStopEventArgs { get; set; } + + public CancellationToken OnResumeCancellationToken => _debugLoopCancellationSource.Token; + + public event Action DebuggerStopped; + public event Action DebuggerResuming; + public event Action BreakpointUpdated; + + public void Abort() + { + SetDebugResuming(DebuggerResumeAction.Stop); + } + + public void BreakExecution() + { + _pwshContext.CurrentRunspace.Debugger.SetDebuggerStepMode(enabled: true); + } + + public void Continue() + { + SetDebugResuming(DebuggerResumeAction.Continue); + } + + public void StepInto() + { + SetDebugResuming(DebuggerResumeAction.StepInto); + } + + public void StepOut() + { + SetDebugResuming(DebuggerResumeAction.StepOut); + } + + public void StepOver() + { + SetDebugResuming(DebuggerResumeAction.StepOver); + } + + public void SetDebugResuming(DebuggerResumeAction debuggerResumeAction) + { + _consoleRepl?.SetReplPop(); + LastStopEventArgs.ResumeAction = debuggerResumeAction; + _debugLoopCancellationSource.Cancel(); + } + + public void EnterDebugLoop(CancellationToken loopCancellationToken) + { + _debugLoopCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(loopCancellationToken); + RaiseDebuggerStoppedEvent(); + + } + + public void ExitDebugLoop() + { + _debugLoopCancellationSource.Dispose(); + _debugLoopCancellationSource = null; + } + + public void SetDebuggerStopped(DebuggerStopEventArgs debuggerStopEventArgs) + { + IsStopped = true; + LastStopEventArgs = debuggerStopEventArgs; + } + + public void SetDebuggerResumed() + { + IsStopped = false; + } + + private void RaiseDebuggerStoppedEvent() + { + // TODO: Send language server message to start debugger + DebuggerStopped?.Invoke(this, LastStopEventArgs); + } + + public void RaiseDebuggerResumingEvent(DebuggerResumingEventArgs debuggerResumingEventArgs) + { + DebuggerResuming?.Invoke(this, debuggerResumingEventArgs); + } + + public void HandleBreakpointUpdated(BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) + { + BreakpointUpdated?.Invoke(this, breakpointUpdatedEventArgs); + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs new file mode 100644 index 000000000..456825ac0 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs @@ -0,0 +1,280 @@ +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Host; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + using System.Management.Automation.Runspaces; + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; + + internal class PipelineThreadExecutor + { + private static readonly PropertyInfo s_shouldProcessInExecutionThreadProperty = + typeof(PSEventSubscriber) + .GetProperty( + "ShouldProcessInExecutionThread", + BindingFlags.Instance | BindingFlags.NonPublic); + + private readonly ILoggerFactory _loggerFactory; + + private readonly ILogger _logger; + + private readonly HostStartupInfo _hostInfo; + + private readonly BlockingCollection _executionQueue; + + private readonly CancellationTokenSource _consumerThreadCancellationSource; + + private readonly Thread _pipelineThread; + + private readonly CancellationContext _loopCancellationContext; + + private readonly CancellationContext _commandCancellationContext; + + private readonly ReaderWriterLockSlim _taskProcessingLock; + + private PowerShellContext _pwshContext; + + private PowerShellDebugContext _debugContext; + + private ConsoleReplRunner _consoleRepl; + + private bool _runIdleLoop; + + public PipelineThreadExecutor( + ILoggerFactory loggerFactory, + HostStartupInfo hostInfo) + { + _logger = loggerFactory.CreateLogger(); + _hostInfo = hostInfo; + + _pipelineThread = new Thread(Run) + { + Name = "PSES Execution Service Thread", + }; + _pipelineThread.SetApartmentState(ApartmentState.STA); + } + + public bool IsExiting { get; set; } + + public Task QueueTask(SynchronousTask synchronousTask) + { + _executionQueue.Add(synchronousTask); + return synchronousTask.Task; + } + public void Start(PowerShellContext pwshContext, ConsoleReplRunner consoleReplRunner) + { + _pwshContext = pwshContext; + _consoleRepl = consoleReplRunner; + _pipelineThread.Start(); + } + + public void Stop() + { + _consumerThreadCancellationSource.Cancel(); + _pipelineThread.Join(); + } + + public void CancelCurrentTask() + { + _commandCancellationContext.CancelCurrentTask(); + } + + public void Dispose() + { + Stop(); + _pwshContext.Dispose(); + } + + public IDisposable TakeTaskWriterLock() + { + return TaskProcessingWriterLockLifetime.TakeLock(_taskProcessingLock); + } + + private void Run() + { + RunTopLevelConsumerLoop(); + } + + public void RunPowerShellLoop(PowerShellFrameType powerShellFrameType) + { + using (CancellationScope cancellationScope = _loopCancellationContext.EnterScope(_pwshContext.CurrentCancellationSource.Token, _consumerThreadCancellationSource.Token)) + { + try + { + if (_runIdleLoop) + { + RunIdleLoop(cancellationScope); + return; + } + + _consoleRepl?.PushNewReplTask(); + + if ((powerShellFrameType & PowerShellFrameType.Debug) != 0) + { + RunDebugLoop(cancellationScope); + return; + } + + RunNestedLoop(cancellationScope); + } + finally + { + _runIdleLoop = false; + _pwshContext.PopFrame(); + } + } + } + + private void RunTopLevelConsumerLoop() + { + using (CancellationScope cancellationScope = _loopCancellationContext.EnterScope(_pwshContext.CurrentCancellationSource.Token, _consumerThreadCancellationSource.Token)) + { + try + { + foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationScope.CancellationToken)) + { + RunTaskSynchronously(task, cancellationScope.CancellationToken); + } + } + catch (OperationCanceledException) + { + // Catch cancellations to end nicely + } + } + } + + private void RunNestedLoop(in CancellationScope cancellationScope) + { + try + { + foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationScope.CancellationToken)) + { + RunTaskSynchronously(task, cancellationScope.CancellationToken); + + if (IsExiting) + { + break; + } + } + } + catch (OperationCanceledException) + { + // Catch cancellations to end nicely + } + } + + private void RunDebugLoop(in CancellationScope cancellationScope) + { + _debugContext.EnterDebugLoop(cancellationScope.CancellationToken); + try + { + // Run commands, but cancelling our blocking wait if the debugger resumes + foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(_debugContext.OnResumeCancellationToken)) + { + // We don't want to cancel the current command when the debugger resumes, + // since that command will be resuming the debugger. + // Instead let it complete and check the cancellation afterward. + RunTaskSynchronously(task, cancellationScope.CancellationToken); + + if (_debugContext.OnResumeCancellationToken.IsCancellationRequested) + { + break; + } + } + } + catch (OperationCanceledException) + { + // Catch cancellations to end nicely + } + finally + { + _debugContext.ExitDebugLoop(); + } + } + + private void RunIdleLoop(in CancellationScope cancellationScope) + { + try + { + while (_executionQueue.TryTake(out ISynchronousTask task)) + { + RunTaskSynchronously(task, cancellationScope.CancellationToken); + } + } + catch (OperationCanceledException) + { + + } + + // TODO: Run nested pipeline here for engine event handling + } + + private void RunTaskSynchronously(ISynchronousTask task, CancellationToken loopCancellationToken) + { + if (task.IsCanceled) + { + return; + } + + using (CancellationScope commandCancellationScope = _commandCancellationContext.EnterScope(loopCancellationToken)) + { + _taskProcessingLock.EnterReadLock(); + try + { + task.ExecuteSynchronously(commandCancellationScope.CancellationToken); + } + finally + { + _taskProcessingLock.ExitReadLock(); + } + } + } + + public void OnPowerShellIdle() + { + if (_executionQueue.Count == 0) + { + return; + } + + _runIdleLoop = true; + _pwshContext.PushNonInteractivePowerShell(); + } + + private struct TaskProcessingWriterLockLifetime : IDisposable + { + private readonly ReaderWriterLockSlim _rwLock; + + public static TaskProcessingWriterLockLifetime TakeLock(ReaderWriterLockSlim rwLock) + { + rwLock.EnterWriteLock(); + return new TaskProcessingWriterLockLifetime(rwLock); + } + + private TaskProcessingWriterLockLifetime(ReaderWriterLockSlim rwLock) + { + _rwLock = rwLock; + } + + public void Dispose() + { + _rwLock.ExitWriteLock(); + } + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadRunner.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadRunner.cs deleted file mode 100644 index 7cb2e6a41..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadRunner.cs +++ /dev/null @@ -1,467 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Hosting; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; -using Microsoft.PowerShell.EditorServices.Utility; -using OmniSharp.Extensions.LanguageServer.Protocol.Server; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using SMA = System.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution -{ - internal class PipelineThreadRunner - { - private static readonly string s_commandsModulePath = Path.GetFullPath( - Path.Combine( - Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), - "../../Commands/PowerShellEditorServices.Commands.psd1")); - - private static readonly PropertyInfo s_shouldProcessInExecutionThreadProperty = - typeof(PSEventSubscriber) - .GetProperty( - "ShouldProcessInExecutionThread", - BindingFlags.Instance | BindingFlags.NonPublic); - - private readonly IPowerShellContext _pwshContext; - - private readonly BlockingCollection _executionQueue; - - private readonly CancellationTokenSource _consumerThreadCancellationSource; - - private readonly Thread _pipelineThread; - - private readonly ILoggerFactory _loggerFactory; - - private readonly ILogger _logger; - - private readonly ILanguageServer _languageServer; - - private readonly ConsoleReplRunner _consoleRepl; - - private readonly string _hostName; - - private readonly Version _hostVersion; - - private readonly PSLanguageMode _languageMode; - - private readonly PSHost _internalHost; - - private readonly ProfilePathInfo _profilePaths; - - private readonly IReadOnlyList _additionalModulesToLoad; - - private readonly IPowerShellDebugContext _debugContext; - - private readonly ConcurrentStack _loopCancellationStack; - - private readonly ConcurrentStack _commandCancellationStack; - - private readonly ReaderWriterLockSlim _taskProcessingLock; - - private bool _isExiting; - - private bool _runIdleLoop; - - public PipelineThreadRunner() - { - _pipelineThread = new Thread(RunTopLevelConsumerLoop) - { - Name = "PSES Execution Service Thread", - }; - _pipelineThread.SetApartmentState(ApartmentState.STA); - } - - public Task QueueTask(SynchronousTask synchronousTask) - { - _executionQueue.Add(synchronousTask); - return synchronousTask.Task; - } - public void Start() - { - _pipelineThread.Start(); - } - - public void Stop() - { - _consumerThreadCancellationSource.Cancel(); - _pipelineThread.Join(); - } - - public void CancelCurrentTask() - { - if (_commandCancellationStack.TryPeek(out CancellationTokenSource currentCommandCancellation)) - { - currentCommandCancellation.Cancel(); - } - } - - public void Dispose() - { - Stop(); - _pwshContext.Dispose(); - } - - - private void RunTopLevelConsumerLoop() - { - Initialize(); - - var cancellationContext = LoopCancellationContext.EnterNew( - this, - CurrentPowerShellCancellationSource, - _consumerThreadCancellationSource); - try - { - foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationContext.CancellationToken)) - { - RunTaskSynchronously(task, cancellationContext.CancellationToken); - } - } - catch (OperationCanceledException) - { - // Catch cancellations to end nicely - } - finally - { - cancellationContext.Dispose(); - } - } - - private void RunNestedLoop(in LoopCancellationContext cancellationContext) - { - try - { - foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationContext.CancellationToken)) - { - RunTaskSynchronously(task, cancellationContext.CancellationToken); - - if (_isExiting) - { - break; - } - } - } - catch (OperationCanceledException) - { - // Catch cancellations to end nicely - } - } - - private void RunDebugLoop(in LoopCancellationContext cancellationContext) - { - // If the debugger is resumed while the execution queue listener is blocked on getting a new execution event, - // we must cancel the blocking call - var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_debugContext.DebuggerResumeCancellationToken, cancellationContext.CancellationToken); - - try - { - DebuggerStopped?.Invoke(this, _debugContext.LastStopEventArgs); - - // Run commands, but cancelling our blocking wait if the debugger resumes - foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationSource.Token)) - { - // We don't want to cancel the current command when the debugger resumes, - // since that command will be resuming the debugger. - // Instead let it complete and check the cancellation afterward. - RunTaskSynchronously(task, cancellationContext.CancellationToken); - - if (cancellationSource.Token.IsCancellationRequested) - { - break; - } - } - } - catch (OperationCanceledException) - { - // Catch cancellations to end nicely - } - finally - { - _debugContext.ResetCurrentStopContext(); - cancellationSource.Dispose(); - } - } - - private void RunIdleLoop(in LoopCancellationContext cancellationContext) - { - try - { - while (_executionQueue.TryTake(out ISynchronousTask task)) - { - RunTaskSynchronously(task, cancellationContext.CancellationToken); - } - } - catch (OperationCanceledException) - { - - } - - // TODO: Run nested pipeline here for engine event handling - } - - - private void RunTaskSynchronously(ISynchronousTask task, CancellationToken loopCancellationToken) - { - if (task.IsCanceled) - { - return; - } - - using (var cancellationContext = TaskCancellationContext.EnterNew(this, loopCancellationToken)) - { - task.ExecuteSynchronously(cancellationContext.CancellationToken); - } - } - - private void Initialize() - { - ReadLine = new ConsoleReadLine(); - - EditorServicesHost = new EditorServicesConsolePSHost( - _loggerFactory, - _hostName, - _hostVersion, - _internalHost, - ReadLine); - - PushInitialRunspace(EditorServicesHost, _languageMode); - - EditorServicesHost.RegisterPowerShellContext(new PowerShellRunspaceContext(this)); - - EngineIntrinsics = (EngineIntrinsics)CurrentPowerShell.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); - - PSReadLineProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, CurrentPowerShell); - PSReadLineProxy.OverrideIdleHandler(OnPowerShellIdle); - ReadLine.RegisterExecutionDependencies(this, PSReadLineProxy); - - if (VersionUtils.IsWindows) - { - SetExecutionPolicy(); - } - - LoadProfiles(); - - ImportModule(s_commandsModulePath); - - if (_additionalModulesToLoad != null && _additionalModulesToLoad.Count > 0) - { - foreach (string module in _additionalModulesToLoad) - { - ImportModule(module); - } - } - - _consoleRepl.StartRepl(); - } - - private void SetExecutionPolicy() - { - // We want to get the list hierarchy of execution policies - // Calling the cmdlet is the simplest way to do that - IReadOnlyList policies = _pwshContext.CurrentPowerShell - .AddCommand("Microsoft.PowerShell.Security\\Get-ExecutionPolicy") - .AddParameter("-List") - .InvokeAndClear(); - - // The policies come out in the following order: - // - MachinePolicy - // - UserPolicy - // - Process - // - CurrentUser - // - LocalMachine - // We want to ignore policy settings, since we'll already have those anyway. - // Then we need to look at the CurrentUser setting, and then the LocalMachine setting. - // - // Get-ExecutionPolicy -List emits PSObjects with Scope and ExecutionPolicy note properties - // set to expected values, so we must sift through those. - - ExecutionPolicy policyToSet = ExecutionPolicy.Bypass; - var currentUserPolicy = (ExecutionPolicy)policies[policies.Count - 2].Members["ExecutionPolicy"].Value; - if (currentUserPolicy != ExecutionPolicy.Undefined) - { - policyToSet = currentUserPolicy; - } - else - { - var localMachinePolicy = (ExecutionPolicy)policies[policies.Count - 1].Members["ExecutionPolicy"].Value; - if (localMachinePolicy != ExecutionPolicy.Undefined) - { - policyToSet = localMachinePolicy; - } - } - - // If there's nothing to do, save ourselves a PowerShell invocation - if (policyToSet == ExecutionPolicy.Bypass) - { - _logger.LogTrace("Execution policy already set to Bypass. Skipping execution policy set"); - return; - } - - // Finally set the inherited execution policy - _logger.LogTrace("Setting execution policy to {Policy}", policyToSet); - try - { - _pwshContext.CurrentPowerShell.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") - .AddParameter("Scope", ExecutionPolicyScope.Process) - .AddParameter("ExecutionPolicy", policyToSet) - .AddParameter("Force") - .InvokeAndClear(); - } - catch (CmdletInvocationException e) - { - _logger.LogError(e, "Error occurred calling 'Set-ExecutionPolicy -Scope Process -ExecutionPolicy {Policy} -Force'", policyToSet); - } - } - - private void LoadProfiles() - { - var profileVariable = new PSObject(); - - AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.AllUsersAllHosts), _profilePaths.AllUsersAllHosts); - AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.AllUsersCurrentHost), _profilePaths.AllUsersCurrentHost); - AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.CurrentUserAllHosts), _profilePaths.CurrentUserAllHosts); - AddProfileMemberAndLoadIfExists(profileVariable, nameof(_profilePaths.CurrentUserCurrentHost), _profilePaths.CurrentUserCurrentHost); - - _pwshContext.CurrentPowerShell.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); - } - - private void AddProfileMemberAndLoadIfExists(PSObject profileVariable, string profileName, string profilePath) - { - profileVariable.Members.Add(new PSNoteProperty(profileName, profilePath)); - - if (File.Exists(profilePath)) - { - var psCommand = new PSCommand() - .AddScript(profilePath, useLocalScope: false) - .AddOutputCommand(); - - _pwshContext.CurrentPowerShell.InvokeCommand(psCommand); - } - } - - private void ImportModule(string moduleNameOrPath) - { - _pwshContext.CurrentPowerShell.AddCommand("Microsoft.PowerShell.Core\\Import-Module") - .AddParameter("-Name", moduleNameOrPath) - .InvokeAndClear(); - } - - private void PushInitialRunspace(EditorServicesConsolePSHost psHost, PSLanguageMode languageMode) - { - InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" - ? InitialSessionState.CreateDefault() - : InitialSessionState.CreateDefault2(); - - iss.LanguageMode = languageMode; - - Runspace runspace = RunspaceFactory.CreateRunspace(psHost, iss); - - runspace.SetApartmentStateToSta(); - runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; - - runspace.Open(); - - AddRunspaceEventHandlers(runspace); - - var pwsh = SMA.PowerShell.Create(); - pwsh.Runspace = runspace; - _psFrameStack.Push(new PowerShellContextFrame(pwsh, PowerShellFrameType.Normal, new CancellationTokenSource())); - - Runspace.DefaultRunspace = runspace; - } - - private void OnPowerShellIdle() - { - if (_executionQueue.Count == 0) - { - return; - } - - _runIdleLoop = true; - PushNonInteractivePowerShell(); - } - - private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) - { - IsDebuggerStopped = true; - CurrentPowerShell.WaitForRemoteOutputIfNeeded(); - _debugContext.OnDebuggerStop(sender, debuggerStopEventArgs); - PushDebugPowerShell(); - CurrentPowerShell.ResumeRemoteOutputIfNeeded(); - IsDebuggerStopped = false; - } - - private void SetDebuggerResuming(DebuggerResumeAction resumeAction) - { - _consoleRepl.SetReplPop(); - _debugContext.SetDebuggerResuming(resumeAction); - DebuggerResuming?.Invoke(this, new DebuggerResumingEventArgs(resumeAction)); - } - - private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) - { - BreakpointUpdated?.Invoke(this, breakpointUpdatedEventArgs); - } - - private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) - { - if (!runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) - { - PopOrReinitializeRunspace(); - } - } - - private void PopOrReinitializeRunspace() - { - _consoleRepl.SetReplPop(); - CancelCurrentTask(); - - RunspaceStateInfo oldRunspaceState = CurrentPowerShell.Runspace.RunspaceStateInfo; - _taskProcessingLock.EnterWriteLock(); - try - { - while (_psFrameStack.Count > 0 - && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) - { - PopFrame(); - } - - if (_psFrameStack.Count == 0) - { - // If our main runspace was corrupted, - // we must re-initialize our state. - // TODO: Use runspace.ResetRunspaceState() here instead - Initialize(); - - _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." - + " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs."); - EditorServicesHost.UI.WriteErrorLine("The main runspace encountered an error and has been reinitialized. See the PowerShell extension logs for more details."); - } - else - { - _logger.LogError($"Current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was popped."); - EditorServicesHost.UI.WriteErrorLine($"The current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}'." - + " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior." - + " The session is now returning to the previous runspace."); - } - } - finally - { - _taskProcessingLock.ExitWriteLock(); - } - } - - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs index 5748bc7ec..7a0bce5bc 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using System; using System.Threading; using SMA = System.Management.Automation; @@ -68,24 +69,24 @@ internal class SynchronousPSDelegateTask : SynchronousTask private readonly string _representation; - private readonly PowerShellExecutionService.PowerShellRunspaceContext _psRunspaceContext; + private readonly PowerShellContext _pwshContext; public SynchronousPSDelegateTask( ILogger logger, - PowerShellExecutionService.PowerShellRunspaceContext psRunspaceContext, + PowerShellContext pwshContext, Action action, string representation, CancellationToken cancellationToken) : base(logger, cancellationToken) { - _psRunspaceContext = psRunspaceContext; + _pwshContext = pwshContext; _action = action; _representation = representation; } public override object Run(CancellationToken cancellationToken) { - _action(_psRunspaceContext.CurrentPowerShell, cancellationToken); + _action(_pwshContext.CurrentPowerShell, cancellationToken); return null; } @@ -101,24 +102,24 @@ internal class SynchronousPSDelegateTask : SynchronousTask private readonly string _representation; - private readonly PowerShellExecutionService.PowerShellRunspaceContext _psRunspaceContext; + private readonly PowerShellContext _pwshContext; public SynchronousPSDelegateTask( ILogger logger, - PowerShellExecutionService.PowerShellRunspaceContext psRunspaceContext, + PowerShellContext pwshContext, Func func, string representation, CancellationToken cancellationToken) : base(logger, cancellationToken) { - _psRunspaceContext = psRunspaceContext; + _pwshContext = pwshContext; _func = func; _representation = representation; } public override TResult Run(CancellationToken cancellationToken) { - return _func(_psRunspaceContext.CurrentPowerShell, cancellationToken); + return _func(_pwshContext.CurrentPowerShell, cancellationToken); } public override string ToString() diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 4e90ff91a..3d38a791e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using System; using System.Collections.Generic; @@ -15,35 +16,34 @@ internal class SynchronousPowerShellTask : SynchronousTask Run(CancellationToken cancellationToken) { - _pwsh = _psRunspaceContext.CurrentPowerShell; + _pwsh = _pwshContext.CurrentPowerShell; + _psHost = _pwshContext.EditorServicesPSHost; if (_executionOptions.WriteInputToHost) { @@ -168,7 +168,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT } } - _psRunspaceContext.ProcessDebuggerResult(debuggerResult); + _pwshContext.ProcessDebuggerResult(debuggerResult); // Optimisation to save wasted computation if we're going to throw the output away anyway if (_executionOptions.WriteOutputToHost) @@ -210,7 +210,7 @@ private void StopDebuggerIfRemoteDebugSessionFailed() { if (object.Equals(output?.BaseObject, false)) { - _psRunspaceContext.ProcessDebuggerResult(new DebuggerCommandResults(DebuggerResumeAction.Stop, evaluatedByDebugger: true)); + _pwshContext.ProcessDebuggerResult(new DebuggerCommandResults(DebuggerResumeAction.Stop, evaluatedByDebugger: true)); _logger.LogWarning("Cancelling debug session due to remote command cancellation causing the end of remote debugging session"); _psHost.UI.WriteWarningLine("Debug session aborted by command cancellation. This is a known issue in the Windows PowerShell 5.1 remoting system."); } diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/EvaluateHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs similarity index 100% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/EvaluateHandler.cs rename to src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ExpandAliasHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs similarity index 100% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ExpandAliasHandler.cs rename to src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommandHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs similarity index 100% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommandHandler.cs rename to src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommentHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommentHelpHandler.cs similarity index 100% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommentHelpHandler.cs rename to src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommentHelpHandler.cs diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs similarity index 98% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetVersionHandler.cs rename to src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs index 44f541b18..1d582b469 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs @@ -90,7 +90,7 @@ private async Task CheckPackageManagement() _logger.LogDebug("Old version of PackageManagement detected."); - if (_executionService.EditorServicesHost.Runspace.SessionStateProxy.LanguageMode != PSLanguageMode.FullLanguage) + if (_executionService.CurrentRunspace.Runspace.SessionStateProxy.LanguageMode != PSLanguageMode.FullLanguage) { _languageServer.Window.ShowWarning("You have an older version of PackageManagement known to cause issues with the PowerShell extension. Please run the following command in a new Windows PowerShell session and then restart the PowerShell extension: `Install-Module PackageManagement -Force -AllowClobber -MinimumVersion 1.4.6`"); return; diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IEvaluateHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/IEvaluateHandler.cs similarity index 100% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IEvaluateHandler.cs rename to src/PowerShellEditorServices/Services/PowerShell/Handlers/IEvaluateHandler.cs diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetCommentHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetCommentHelpHandler.cs similarity index 100% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetCommentHelpHandler.cs rename to src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetCommentHelpHandler.cs diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetPSHostProcessesHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetPSHostProcessesHandler.cs similarity index 100% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetPSHostProcessesHandler.cs rename to src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetPSHostProcessesHandler.cs diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetRunspaceHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetRunspaceHandler.cs similarity index 100% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetRunspaceHandler.cs rename to src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetRunspaceHandler.cs diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetVersionHandler.cs similarity index 91% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetVersionHandler.cs rename to src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetVersionHandler.cs index afd07b9a5..d727950b3 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/IGetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetVersionHandler.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using MediatR; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using OmniSharp.Extensions.JsonRpc; -namespace Microsoft.PowerShell.EditorServices.Handlers +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell { [Serial, Method("powerShell/getVersion")] internal interface IGetVersionHandler : IJsonRpcRequestHandler { } diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs similarity index 100% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs rename to src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ShowHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs similarity index 100% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ShowHelpHandler.cs rename to src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index 58a90510f..76739bef1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -1,18 +1,19 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using System; using System.Globalization; using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; -using static Microsoft.PowerShell.EditorServices.Services.PowerShell.PowerShellExecutionService; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host { + using System.Management.Automation.Runspaces; + internal class EditorServicesConsolePSHost : PSHost, IHostSupportsInteractiveSession { private readonly ILogger _logger; - private PowerShellRunspaceContext _psRunspaceContext; + private PowerShellContext _psContext; public EditorServicesConsolePSHost( ILoggerFactory loggerFactory, @@ -39,18 +40,18 @@ public EditorServicesConsolePSHost( public override Version Version { get; } - public Runspace Runspace => _psRunspaceContext.Runspace; + public Runspace Runspace => _psContext.CurrentRunspace; - public bool IsRunspacePushed => _psRunspaceContext.IsRunspacePushed; + public bool IsRunspacePushed => _psContext.IsRunspacePushed; public override void EnterNestedPrompt() { - _psRunspaceContext.PushNestedPowerShell(); + _psContext.PushNestedPowerShell(); } public override void ExitNestedPrompt() { - _psRunspaceContext.SetShouldExit(); + _psContext.SetShouldExit(exitCode: null); } public override void NotifyBeginApplication() @@ -63,22 +64,22 @@ public override void NotifyEndApplication() public void PushRunspace(Runspace runspace) { - _psRunspaceContext.PushPowerShell(runspace); + _psContext.PushPowerShell(runspace); } public void PopRunspace() { - _psRunspaceContext.SetShouldExit(); + _psContext.SetShouldExit(exitCode: null); } public override void SetShouldExit(int exitCode) { - _psRunspaceContext.SetShouldExit(); + _psContext.SetShouldExit(exitCode); } - internal void RegisterPowerShellContext(PowerShellExecutionService.PowerShellRunspaceContext psRunspaceContext) + internal void RegisterPowerShellContext(PowerShellContext psContext) { - _psRunspaceContext = psRunspaceContext; + _psContext = psContext; } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 5ee8818e6..7903d8b66 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -1,32 +1,22 @@ using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; -using Microsoft.PowerShell.EditorServices.Utility; -using Newtonsoft.Json.Bson; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.IO; using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Language; -using System.Management.Automation.Runspaces; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using SMA = System.Management.Automation; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell { - internal partial class PowerShellExecutionService : IDisposable + internal class PowerShellExecutionService : IDisposable { - public static PowerShellExecutionService CreateAndStart( ILoggerFactory loggerFactory, ILanguageServer languageServer, @@ -34,84 +24,53 @@ public static PowerShellExecutionService CreateAndStart( { var executionService = new PowerShellExecutionService( loggerFactory, - languageServer, - hostInfo.Name, - hostInfo.Version, - hostInfo.LanguageMode, - hostInfo.PSHost, - hostInfo.ProfilePaths, - hostInfo.AdditionalModules); + hostInfo, + languageServer); - executionService.Start(); + executionService._pipelineExecutor.Start(executionService._pwshContext, executionService._consoleRepl); + executionService._consoleRepl?.StartRepl(); return executionService; } - private readonly BlockingCollection _executionQueue; + private readonly ILogger _logger; - private Thread _pipelineThread; + private readonly PowerShellContext _pwshContext; - private bool _runIdleLoop; + private readonly PipelineThreadExecutor _pipelineExecutor; - private bool _isExiting; + private readonly ConsoleReplRunner _consoleRepl; private PowerShellExecutionService( ILoggerFactory loggerFactory, - ILanguageServer languageServer, - string hostName, - Version hostVersion, - PSLanguageMode languageMode, - PSHost internalHost, - ProfilePathInfo profilePaths, - IReadOnlyList additionalModules) + HostStartupInfo hostInfo, + ILanguageServer languageServer) { - _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(); - _languageServer = languageServer; - _consoleRepl = new ConsoleReplRunner(_loggerFactory, this); - _consumerThreadCancellationSource = new CancellationTokenSource(); - _loopCancellationStack = new ConcurrentStack(); - _commandCancellationStack = new ConcurrentStack(); - _executionQueue = new BlockingCollection(); - _debuggingContext = new DebuggingContext(); - _psFrameStack = new Stack(); - _taskProcessingLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - _hostName = hostName; - _hostVersion = hostVersion; - _languageMode = languageMode; - _internalHost = internalHost; - _profilePaths = profilePaths; - _additionalModulesToLoad = additionalModules; - } - - public EngineIntrinsics EngineIntrinsics { get; private set; } - - public EditorServicesConsolePSHost EditorServicesHost { get; private set; } - - public PSReadLineProxy PSReadLineProxy { get; private set; } - - public ConsoleReadLine ReadLine { get; private set; } - - internal SMA.PowerShell CurrentPowerShell => _psFrameStack.Peek().PowerShell; + _pipelineExecutor = new PipelineThreadExecutor(loggerFactory, hostInfo); + _pwshContext = new PowerShellContext(loggerFactory, hostInfo, languageServer, this, _pipelineExecutor); - internal CancellationTokenSource CurrentPowerShellCancellationSource => _psFrameStack.Peek().CancellationTokenSource; - - public bool IsCurrentlyRemote => EditorServicesHost.Runspace.RunspaceIsRemote; + // TODO: Fix this + if (hostInfo.ConsoleReplEnabled) + { + _consoleRepl = new ConsoleReplRunner(loggerFactory, _pwshContext, this); + } + } - public bool IsDebuggerStopped { get; private set; } + public IPowerShellDebugContext DebugContext => _pwshContext.DebugContext; - public event Action DebuggerStopped; + public IRunspaceInfo CurrentRunspace => _pwshContext.RunspaceContext; - public event Action DebuggerResuming; + public IPowerShellContext PowerShellContext => _pwshContext; - public event Action BreakpointUpdated; + public Action RunspaceChanged; public Task ExecuteDelegateAsync( Func func, string representation, CancellationToken cancellationToken) { - return QueueTask(new SynchronousPSDelegateTask(_logger, new PowerShellRunspaceContext(this), func, representation, cancellationToken)); + return QueueTask(new SynchronousPSDelegateTask(_logger, _pwshContext, func, representation, cancellationToken)); } public Task ExecuteDelegateAsync( @@ -119,7 +78,7 @@ public Task ExecuteDelegateAsync( string representation, CancellationToken cancellationToken) { - return QueueTask(new SynchronousPSDelegateTask(_logger, new PowerShellRunspaceContext(this), action, representation, cancellationToken)); + return QueueTask(new SynchronousPSDelegateTask(_logger, _pwshContext, action, representation, cancellationToken)); } public Task ExecuteDelegateAsync( @@ -145,8 +104,7 @@ public Task> ExecutePSCommandAsync( { Task> result = QueueTask(new SynchronousPowerShellTask( _logger, - new PowerShellRunspaceContext(this), - EditorServicesHost, + _pwshContext, psCommand, executionOptions, cancellationToken)); @@ -164,269 +122,18 @@ public Task ExecutePSCommandAsync( PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) => ExecutePSCommandAsync(psCommand, executionOptions, cancellationToken); - private Task QueueTask(SynchronousTask task) + public void CancelCurrentTask() { - _executionQueue.Add(task); - return task.Task; + _pipelineExecutor.CancelCurrentTask(); } - private void PushNestedPowerShell(PowerShellFrameType frameType) + public void Dispose() { - SMA.PowerShell pwsh = CreateNestedPowerShell(); - PowerShellFrameType newFrameType = _psFrameStack.Peek().FrameType | PowerShellFrameType.Nested | frameType; - PushFrame(new PowerShellContextFrame(pwsh, newFrameType, new CancellationTokenSource())); + _pipelineExecutor.Dispose(); + _consoleRepl.Dispose(); + _pwshContext.Dispose(); } - private SMA.PowerShell CreateNestedPowerShell() - { - PowerShellContextFrame currentFrame = _psFrameStack.Peek(); - if ((currentFrame.FrameType & PowerShellFrameType.Remote) != 0) - { - var remotePwsh = SMA.PowerShell.Create(); - remotePwsh.Runspace = currentFrame.PowerShell.Runspace; - return remotePwsh; - } - - // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild - // This means it throws due to the parent pipeline not running... - // So we must use the RunspaceMode.CurrentRunspace option on PowerShell.Create() instead - var pwsh = SMA.PowerShell.Create(RunspaceMode.CurrentRunspace); - pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; - return pwsh; - } - - private void PushNonInteractivePowerShell() - { - PushNestedPowerShell(PowerShellFrameType.NonInteractive); - } - - private void PushNestedPowerShell() - { - PushNestedPowerShell(PowerShellFrameType.Normal); - } - - private void PushDebugPowerShell() - { - PushNestedPowerShell(PowerShellFrameType.Debug); - } - - private void PushPowerShell(Runspace runspace) - { - var pwsh = SMA.PowerShell.Create(); - pwsh.Runspace = runspace; - - PowerShellFrameType frameType = PowerShellFrameType.Normal; - - if (runspace.RunspaceIsRemote) - { - frameType |= PowerShellFrameType.Remote; - } - - PushFrame(new PowerShellContextFrame(pwsh, frameType, new CancellationTokenSource())); - } - - private void PushFrame(PowerShellContextFrame frame) - { - if (_psFrameStack.Count > 0) - { - RemoveRunspaceEventHandlers(CurrentPowerShell.Runspace); - } - AddRunspaceEventHandlers(frame.PowerShell.Runspace); - _psFrameStack.Push(frame); - RunPowerShellLoop(frame.FrameType); - } - - private void PopFrame() - { - _isExiting = false; - PowerShellContextFrame frame = _psFrameStack.Pop(); - try - { - RemoveRunspaceEventHandlers(frame.PowerShell.Runspace); - if (_psFrameStack.Count > 0) - { - AddRunspaceEventHandlers(CurrentPowerShell.Runspace); - } - } - finally - { - frame.Dispose(); - } - } - - private void RunPowerShellLoop(PowerShellFrameType powerShellFrameType) - { - var cancellationContext = LoopCancellationContext.EnterNew( - this, - CurrentPowerShellCancellationSource, - _consumerThreadCancellationSource); - - try - { - if (_runIdleLoop) - { - RunIdleLoop(cancellationContext); - return; - } - - _consoleRepl.PushNewReplTask(); - - if ((powerShellFrameType & PowerShellFrameType.Debug) != 0) - { - RunDebugLoop(cancellationContext); - return; - } - - RunNestedLoop(cancellationContext); - } - finally - { - _runIdleLoop = false; - PopFrame(); - cancellationContext.Dispose(); - } - } - - internal struct PowerShellRunspaceContext - { - private readonly PowerShellExecutionService _executionService; - - public PowerShellRunspaceContext(PowerShellExecutionService executionService) - { - _executionService = executionService; - } - - public Runspace Runspace => _executionService.CurrentPowerShell.Runspace; - - public bool IsRunspacePushed => _executionService._psFrameStack.Count > 1; - - public SMA.PowerShell CurrentPowerShell => _executionService.CurrentPowerShell; - - public void SetShouldExit() - { - if (_executionService._psFrameStack.Count <= 1) - { - return; - } - - _executionService._isExiting = true; - - if ((_executionService._psFrameStack.Peek().FrameType & PowerShellFrameType.NonInteractive) == 0) - { - _executionService._consoleRepl.SetReplPop(); - } - } - - public void ProcessDebuggerResult(DebuggerCommandResults debuggerResult) - { - if (debuggerResult.ResumeAction != null) - { - _executionService.SetDebuggerResuming(debuggerResult.ResumeAction.Value); - } - } - - public void PushNestedPowerShell() - { - _executionService.PushNestedPowerShell(); - } - - public void PushPowerShell(Runspace runspace) - { - _executionService.PushPowerShell(runspace); - } - } - - private class DebuggingContext - { - private CancellationTokenSource _debuggerCancellationTokenSource; - - public CancellationToken DebuggerResumeCancellationToken => _debuggerCancellationTokenSource.Token; - - public DebuggerStopEventArgs LastStopEventArgs { get; private set; } - - public bool HasStopped => _debuggerCancellationTokenSource != null; - - public void ResetCurrentStopContext() - { - LastStopEventArgs = null; - _debuggerCancellationTokenSource.Dispose(); - _debuggerCancellationTokenSource = null; - } - - public void OnDebuggerStop(object sender, DebuggerStopEventArgs debuggerStopEventArgs) - { - _debuggerCancellationTokenSource = new CancellationTokenSource(); - LastStopEventArgs = debuggerStopEventArgs; - } - - public void SetDebuggerResuming(DebuggerResumeAction resumeAction) - { - LastStopEventArgs.ResumeAction = resumeAction; - _debuggerCancellationTokenSource.Cancel(); - } - } - - private readonly struct LoopCancellationContext : IDisposable - { - public static LoopCancellationContext EnterNew( - PowerShellExecutionService executionService, - CancellationTokenSource cts1, - CancellationTokenSource cts2) - { - var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token); - executionService._loopCancellationStack.Push(cancellationTokenSource); - return new LoopCancellationContext(executionService._loopCancellationStack, cancellationTokenSource.Token); - } - - private readonly ConcurrentStack _loopCancellationStack; - - public readonly CancellationToken CancellationToken; - - private LoopCancellationContext( - ConcurrentStack loopCancellationStack, - CancellationToken cancellationToken) - { - _loopCancellationStack = loopCancellationStack; - CancellationToken = cancellationToken; - } - - public void Dispose() - { - if (_loopCancellationStack.TryPop(out CancellationTokenSource loopCancellation)) - { - loopCancellation.Dispose(); - } - } - } - - private readonly struct TaskCancellationContext : IDisposable - { - public static TaskCancellationContext EnterNew(PowerShellExecutionService executionService, CancellationToken loopCancellationToken) - { - executionService._taskProcessingLock.EnterReadLock(); - var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(loopCancellationToken); - executionService._commandCancellationStack.Push(cancellationTokenSource); - return new TaskCancellationContext(executionService, cancellationTokenSource.Token); - } - - private TaskCancellationContext(PowerShellExecutionService executionService, CancellationToken cancellationToken) - { - _executionService = executionService; - CancellationToken = cancellationToken; - } - - private readonly PowerShellExecutionService _executionService; - - public readonly CancellationToken CancellationToken; - - public void Dispose() - { - _executionService._taskProcessingLock.ExitReadLock(); - if (_executionService._commandCancellationStack.TryPop(out CancellationTokenSource taskCancellation)) - { - taskCancellation.Dispose(); - } - } - } + private Task QueueTask(SynchronousTask task) => _pipelineExecutor.QueueTask(task); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs deleted file mode 100644 index 065de5e1f..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs +++ /dev/null @@ -1,12 +0,0 @@ - -using System.Management.Automation.Runspaces; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution -{ - internal interface IRunspaceContext - { - bool IsRemote { get; } - - RunspaceOrigin RunspaceOrigin { get; } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs new file mode 100644 index 000000000..f01751c70 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs @@ -0,0 +1,28 @@ + +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; +using SMA = System.Management.Automation.Runspaces; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace +{ + internal interface IRunspaceInfo + { + RunspaceOrigin RunspaceOrigin { get; } + + PowerShellVersionDetails PowerShellVersionDetails { get; } + + SessionDetails SessionDetails { get; } + + SMA.Runspace Runspace { get; } + + DscBreakpointCapability DscBreakpointCapability { get; } + } + + internal static class RunspaceInfoExtensions + { + public static bool IsRemote(this IRunspaceInfo runspaceInfo) + { + return runspaceInfo.RunspaceOrigin != RunspaceOrigin.Local; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceChangedEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceChangedEventArgs.cs new file mode 100644 index 000000000..04d590c33 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceChangedEventArgs.cs @@ -0,0 +1,67 @@ +using Microsoft.PowerShell.EditorServices.Utility; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace +{ + /// + /// Defines the set of actions that will cause the runspace to be changed. + /// + internal enum RunspaceChangeAction + { + /// + /// The runspace change was caused by entering a new session. + /// + Enter, + + /// + /// The runspace change was caused by exiting the current session. + /// + Exit, + + /// + /// The runspace change was caused by shutting down the service. + /// + Shutdown + } + + /// + /// Provides arguments for the PowerShellContext.RunspaceChanged event. + /// + internal class RunspaceChangedEventArgs + { + /// + /// Creates a new instance of the RunspaceChangedEventArgs class. + /// + /// The action which caused the runspace to change. + /// The previously active runspace. + /// The newly active runspace. + public RunspaceChangedEventArgs( + RunspaceChangeAction changeAction, + IRunspaceInfo previousRunspace, + IRunspaceInfo newRunspace) + { + Validate.IsNotNull(nameof(previousRunspace), previousRunspace); + + this.ChangeAction = changeAction; + this.PreviousRunspace = previousRunspace; + this.NewRunspace = newRunspace; + } + + /// + /// Gets the RunspaceChangeAction which caused this event. + /// + public RunspaceChangeAction ChangeAction { get; } + + /// + /// Gets a RunspaceDetails object describing the previous runspace. + /// + public IRunspaceInfo PreviousRunspace { get; } + + /// + /// Gets a RunspaceDetails object describing the new runspace. + /// + public IRunspaceInfo NewRunspace { get; } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs new file mode 100644 index 000000000..57503320a --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs @@ -0,0 +1,33 @@ +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; +using SMA = System.Management.Automation.Runspaces; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace +{ + internal class RunspaceInfo : IRunspaceInfo + { + public RunspaceInfo( + SMA.Runspace runspace, + RunspaceOrigin origin, + PowerShellVersionDetails powerShellVersionDetails, + SessionDetails sessionDetails, + DscBreakpointCapability dscBreakpointCapability) + { + Runspace = runspace; + RunspaceOrigin = origin; + SessionDetails = sessionDetails; + PowerShellVersionDetails = powerShellVersionDetails; + DscBreakpointCapability = dscBreakpointCapability; + } + + public RunspaceOrigin RunspaceOrigin { get; } + + public PowerShellVersionDetails PowerShellVersionDetails { get; } + + public SessionDetails SessionDetails { get; } + + public SMA.Runspace Runspace { get; } + + public DscBreakpointCapability DscBreakpointCapability { get; } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceOrigin.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceOrigin.cs index 72ef60308..b91faf020 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceOrigin.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceOrigin.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace { /// /// Specifies the context in which the runspace was encountered. @@ -11,9 +11,14 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell internal enum RunspaceOrigin { /// - /// The original runspace in a local or remote session. + /// The original runspace in a local session. /// - Original, + Local, + + /// + /// A remote runspace entered through Enter-PSSession. + /// + PSSession, /// /// A runspace in a process that was entered with Enter-PSHostProcess. diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/SessionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs similarity index 80% rename from src/PowerShellEditorServices/Services/PowerShellContext/Session/SessionDetails.cs rename to src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs index 0f00d2a28..a58a4b6bb 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/SessionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs @@ -6,7 +6,7 @@ using System.Collections; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace { /// /// Provides details about the current PowerShell session. @@ -34,15 +34,14 @@ internal class SessionDetails /// PSCommand returned by GetDetailsCommand. /// /// - public SessionDetails(PSObject detailsObject) + protected SessionDetails( + int processId, + string computerName, + Guid? instanceId) { - Validate.IsNotNull(nameof(detailsObject), detailsObject); - - Hashtable innerHashtable = detailsObject.BaseObject as Hashtable; - - this.ProcessId = (int)innerHashtable["processId"] as int?; - this.ComputerName = innerHashtable["computerName"] as string; - this.InstanceId = innerHashtable["instanceId"] as Guid?; + ProcessId = processId; + ComputerName = computerName; + InstanceId = instanceId; } /// diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/AsyncResetEvent.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/AsyncResetEvent.cs deleted file mode 100644 index fc2d7ec4f..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/AsyncResetEvent.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace PowerShellEditorServices.Services.PowerShell.Utility -{ - internal class AsyncResetEvent - { - private TaskCompletionSource _taskCompletionSource; - - public AsyncResetEvent() - { - _taskCompletionSource = null; - } - - public bool IsBlocking => _taskCompletionSource != null; - - public void SetBlock() - { - Interlocked.CompareExchange(ref _taskCompletionSource, new TaskCompletionSource(), null); - } - - public void Unblock() - { - TaskCompletionSource taskCompletionSource = Interlocked.Exchange(ref _taskCompletionSource, null); - if (taskCompletionSource != null) - { - taskCompletionSource.TrySetResult(true); - } - } - - public Task WaitAsync() - { - if (_taskCompletionSource == null) - { - return Task.CompletedTask; - } - - return _taskCompletionSource.Task; - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs index ad2b12d7c..b9d59c7ff 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs @@ -1,34 +1,48 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Text; using System.Threading; -namespace PowerShellEditorServices.Services.PowerShell.Utility +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility { - internal struct CancellationContext : IDisposable + internal class CancellationContext { - public static CancellationContext Enter(ConcurrentStack cancellationStack, params CancellationToken[] linkedTokens) + private readonly ConcurrentStack _cancellationSourceStack; + + public CancellationContext() + { + _cancellationSourceStack = new ConcurrentStack(); + } + + public CancellationScope EnterScope(params CancellationToken[] linkedTokens) { - var linkedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(linkedTokens); - return Enter(cancellationStack, linkedCancellationSource); + return EnterScope(CancellationTokenSource.CreateLinkedTokenSource(linkedTokens)); } - public static CancellationContext Enter(ConcurrentStack cancellationStack, CancellationToken token1, CancellationToken token2) + public CancellationScope EnterScope(CancellationToken linkedToken1, CancellationToken linkedToken2) { - var linkedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(token1, token2); - return Enter(cancellationStack, linkedCancellationSource); + return EnterScope(CancellationTokenSource.CreateLinkedTokenSource(linkedToken1, linkedToken2)); } - private static CancellationContext Enter(ConcurrentStack cancellationStack, CancellationTokenSource linkedCancellationSource) + public void CancelCurrentTask() { - cancellationStack.Push(linkedCancellationSource); - return new CancellationContext(cancellationStack, linkedCancellationSource.Token); + if (_cancellationSourceStack.TryPeek(out CancellationTokenSource currentCancellationSource)) + { + currentCancellationSource.Cancel(); + } } + private CancellationScope EnterScope(CancellationTokenSource cancellationFrameSource) + { + _cancellationSourceStack.Push(cancellationFrameSource); + return new CancellationScope(_cancellationSourceStack, cancellationFrameSource.Token); + } + } + + internal struct CancellationScope : IDisposable + { private readonly ConcurrentStack _cancellationStack; - private CancellationContext(ConcurrentStack cancellationStack, CancellationToken currentCancellationToken) + internal CancellationScope(ConcurrentStack cancellationStack, CancellationToken currentCancellationToken) { _cancellationStack = cancellationStack; CancellationToken = currentCancellationToken; diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs similarity index 97% rename from src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs rename to src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs index db784d0e6..8e8c61546 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Utilities/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs @@ -3,16 +3,15 @@ using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using System.Management.Automation; using System.Threading; using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Utility; -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility { /// /// Provides utility methods for working with PowerShell commands. @@ -68,7 +67,7 @@ public static async Task GetCommandInfoAsync( PowerShellExecutionService executionService) { // This mechanism only works in-process - if (executionService.IsCurrentlyRemote) + if (executionService.CurrentRunspace.IsRemote()) { return null; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs index 9ead3ac26..c65176b98 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs @@ -2,12 +2,13 @@ using System; using System.Linq.Expressions; using System.Management.Automation; -using System.Management.Automation.Runspaces; using System.Reflection; using System.Threading; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility { + using System.Management.Automation.Runspaces; + internal static class RunspaceExtensions { private static readonly Action s_runspaceApartmentStateSetter; diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Capabilities/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Capabilities/DscBreakpointCapability.cs index 09f795cc4..354cb8b60 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Capabilities/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Capabilities/DscBreakpointCapability.cs @@ -3,19 +3,19 @@ using System.Linq; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Utility; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation; + namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { - using Microsoft.Extensions.Logging; - using Microsoft.PowerShell.EditorServices.Logging; - using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; - using Microsoft.PowerShell.EditorServices.Utility; - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Management.Automation; - - internal class DscBreakpointCapability : IRunspaceCapability + internal class DscBreakpointCapability { private string[] dscResourceRootPaths = Array.Empty(); diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs index fbc6949ea..985489b68 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs @@ -5,7 +5,7 @@ using System.Management.Automation; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Services.PowerShell; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; namespace Microsoft.PowerShell.EditorServices.Services.Symbols { diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 871cd1bbe..ef7568c1f 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -16,7 +16,7 @@ using Microsoft.PowerShell.EditorServices.CodeLenses; using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Utility; diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ITemplateHandlers.cs b/src/PowerShellEditorServices/Services/Template/Handlers/ITemplateHandlers.cs similarity index 97% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ITemplateHandlers.cs rename to src/PowerShellEditorServices/Services/Template/Handlers/ITemplateHandlers.cs index 0f4a04742..88ae40789 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ITemplateHandlers.cs +++ b/src/PowerShellEditorServices/Services/Template/Handlers/ITemplateHandlers.cs @@ -4,7 +4,7 @@ using MediatR; using OmniSharp.Extensions.JsonRpc; -namespace Microsoft.PowerShell.EditorServices.Handlers +namespace Microsoft.PowerShell.EditorServices.Services.Template { [Serial] [Method("powerShell/getProjectTemplates", Direction.ClientToServer)] diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/TemplateHandlers.cs b/src/PowerShellEditorServices/Services/Template/Handlers/TemplateHandlers.cs similarity index 97% rename from src/PowerShellEditorServices/Services/PowerShellContext/Handlers/TemplateHandlers.cs rename to src/PowerShellEditorServices/Services/Template/Handlers/TemplateHandlers.cs index 1270daf84..58b4e6a78 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Handlers/TemplateHandlers.cs +++ b/src/PowerShellEditorServices/Services/Template/Handlers/TemplateHandlers.cs @@ -8,7 +8,7 @@ using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services; -namespace Microsoft.PowerShell.EditorServices.Handlers +namespace Microsoft.PowerShell.EditorServices.Services.Template { internal class TemplateHandlers : IGetProjectTemplatesHandler, INewProjectFromTemplateHandler { diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs b/src/PowerShellEditorServices/Services/Template/TemplateService.cs similarity index 96% rename from src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs rename to src/PowerShellEditorServices/Services/Template/TemplateService.cs index 96f5fec83..33847a292 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/TemplateService.cs +++ b/src/PowerShellEditorServices/Services/Template/TemplateService.cs @@ -2,20 +2,16 @@ // Licensed under the MIT License. using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; -using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using System.Management.Automation; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.PowerShell.EditorServices.Services +namespace Microsoft.PowerShell.EditorServices.Services.Template { /// /// Provides a service for listing PowerShell project templates and creating diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs index e110d89eb..804efaba6 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShell; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Utility; diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs similarity index 88% rename from src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs rename to src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs index bb4886ebd..d2854c7ad 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/RemoteFileManagerService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs @@ -4,9 +4,10 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Extensions; using Microsoft.PowerShell.EditorServices.Logging; +using Microsoft.PowerShell.EditorServices.Services.Extension; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Generic; @@ -283,7 +284,7 @@ public RemoteFileManagerService( /// /// The remote file path to be opened. /// - /// + /// /// The runspace from which where the remote file will be fetched. /// /// @@ -291,7 +292,7 @@ public RemoteFileManagerService( /// public async Task FetchRemoteFileAsync( string remoteFilePath, - RunspaceDetails runspaceDetails) + IRunspaceInfo runspaceInfo) { string localFilePath = null; @@ -299,8 +300,8 @@ public async Task FetchRemoteFileAsync( { try { - RemotePathMappings pathMappings = this.GetPathMappings(runspaceDetails); - localFilePath = this.GetMappedPath(remoteFilePath, runspaceDetails); + RemotePathMappings pathMappings = this.GetPathMappings(runspaceInfo); + localFilePath = this.GetMappedPath(remoteFilePath, runspaceInfo); if (!pathMappings.IsRemotePathOpened(remoteFilePath)) { @@ -308,11 +309,19 @@ public async Task FetchRemoteFileAsync( if (!File.Exists(localFilePath)) { // Load the file contents from the remote machine and create the buffer - PSCommand command = new PSCommand(); - command.AddCommand("Microsoft.PowerShell.Management\\Get-Content"); - command.AddParameter("Path", remoteFilePath); - command.AddParameter("Raw"); - command.AddParameter("Encoding", "Byte"); + PSCommand command = new PSCommand() + .AddCommand("Microsoft.PowerShell.Management\\Get-Content") + .AddParameter("Path", remoteFilePath) + .AddParameter("Raw"); + + if (string.Equals(runspaceInfo.PowerShellVersionDetails.Edition, "Core")) + { + command.AddParameter("AsByteStream"); + } + else + { + command.AddParameter("Encoding", "Byte"); + } byte[] fileContent = (await this._executionService.ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false)) @@ -403,11 +412,11 @@ await _executionService.ExecutePSCommandAsync( /// /// The contents of the file to be created. /// - /// + /// /// The runspace for which the temporary file relates. /// /// The full temporary path of the file if successful, null otherwise. - public string CreateTemporaryFile(string fileName, string fileContents, RunspaceDetails runspaceDetails) + public string CreateTemporaryFile(string fileName, string fileContents, IRunspaceInfo runspaceInfo) { string temporaryFilePath = Path.Combine(this.processTempPath, fileName); @@ -415,7 +424,7 @@ public string CreateTemporaryFile(string fileName, string fileContents, Runspace { File.WriteAllText(temporaryFilePath, fileContents); - RemotePathMappings pathMappings = this.GetPathMappings(runspaceDetails); + RemotePathMappings pathMappings = this.GetPathMappings(runspaceInfo); pathMappings.AddOpenedLocalPath(temporaryFilePath); } catch (IOException e) @@ -442,7 +451,7 @@ public string CreateTemporaryFile(string fileName, string fileContents, Runspace /// The mapped file path. public string GetMappedPath( string filePath, - RunspaceDetails runspaceDetails) + IRunspaceInfo runspaceDetails) { RemotePathMappings remotePathMappings = this.GetPathMappings(runspaceDetails); return remotePathMappings.GetMappedPath(filePath); @@ -470,9 +479,9 @@ public bool IsUnderRemoteTempPath(string filePath) private string StoreRemoteFile( string remoteFilePath, byte[] fileContent, - RunspaceDetails runspaceDetails) + IRunspaceInfo runspaceInfo) { - RemotePathMappings pathMappings = this.GetPathMappings(runspaceDetails); + RemotePathMappings pathMappings = this.GetPathMappings(runspaceInfo); string localFilePath = pathMappings.GetMappedPath(remoteFilePath); RemoteFileManagerService.StoreRemoteFile( @@ -492,14 +501,14 @@ private static void StoreRemoteFile( pathMappings.AddOpenedLocalPath(localFilePath); } - private RemotePathMappings GetPathMappings(RunspaceDetails runspaceDetails) + private RemotePathMappings GetPathMappings(IRunspaceInfo runspaceInfo) { RemotePathMappings remotePathMappings = null; - string computerName = runspaceDetails.SessionDetails.ComputerName; + string computerName = runspaceInfo.SessionDetails.ComputerName; if (!this.filesPerComputer.TryGetValue(computerName, out remotePathMappings)) { - remotePathMappings = new RemotePathMappings(runspaceDetails, this); + remotePathMappings = new RemotePathMappings(runspaceInfo, this); this.filesPerComputer.Add(computerName, remotePathMappings); } @@ -511,38 +520,35 @@ private async void HandleRunspaceChangedAsync(object sender, RunspaceChangedEven if (e.ChangeAction == RunspaceChangeAction.Enter) { this.RegisterPSEditFunction(e.NewRunspace); + return; } - else + + // Close any remote files that were opened + if (e.PreviousRunspace.IsRemote() && + (e.ChangeAction == RunspaceChangeAction.Shutdown || + !string.Equals( + e.NewRunspace.SessionDetails.ComputerName, + e.PreviousRunspace.SessionDetails.ComputerName, + StringComparison.CurrentCultureIgnoreCase))) { - // Close any remote files that were opened - if (e.PreviousRunspace.Location == RunspaceLocation.Remote && - (e.ChangeAction == RunspaceChangeAction.Shutdown || - !string.Equals( - e.NewRunspace.SessionDetails.ComputerName, - e.PreviousRunspace.SessionDetails.ComputerName, - StringComparison.CurrentCultureIgnoreCase))) + RemotePathMappings remotePathMappings; + if (this.filesPerComputer.TryGetValue(e.PreviousRunspace.SessionDetails.ComputerName, out remotePathMappings)) { - RemotePathMappings remotePathMappings; - if (this.filesPerComputer.TryGetValue(e.PreviousRunspace.SessionDetails.ComputerName, out remotePathMappings)) + foreach (string remotePath in remotePathMappings.OpenedPaths) { - foreach (string remotePath in remotePathMappings.OpenedPaths) - { - await (this.editorOperations?.CloseFileAsync(remotePath)).ConfigureAwait(false); - } + await (this.editorOperations?.CloseFileAsync(remotePath)).ConfigureAwait(false); } } + } - if (e.PreviousRunspace != null) - { - this.RemovePSEditFunction(e.PreviousRunspace); - } + if (e.PreviousRunspace != null) + { + this.RemovePSEditFunction(e.PreviousRunspace); } } private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) { - return; - if (string.Equals(RemoteSessionOpenFile, args.SourceIdentifier, StringComparison.CurrentCultureIgnoreCase)) { try @@ -553,7 +559,7 @@ private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) string remoteFilePath = args.SourceArgs[0] as string; // Is this a local process runspace? Treat as a local file - if (false) //_executionService.CurrentRunspace.Location == RunspaceLocation.Local) + if (_executionService.CurrentRunspace.RunspaceOrigin == RunspaceOrigin.Local) { localFilePath = remoteFilePath; } @@ -586,8 +592,7 @@ private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) this.StoreRemoteFile( remoteFilePath, fileContent, - (RunspaceDetails)null); - //this.powerShellContext.CurrentRunspace); + _executionService.CurrentRunspace); } else { @@ -615,21 +620,20 @@ private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) } } - private void RegisterPSEditFunction(RunspaceDetails runspaceDetails) + private void RegisterPSEditFunction(IRunspaceInfo runspaceInfo) { - if (runspaceDetails.Location == RunspaceLocation.Remote && - runspaceDetails.Context == RunspaceOrigin.Original) + if (runspaceInfo.IsRemote()) { try { - runspaceDetails.Runspace.Events.ReceivedEvents.PSEventReceived += HandlePSEventReceivedAsync; + runspaceInfo.Runspace.Events.ReceivedEvents.PSEventReceived += HandlePSEventReceivedAsync; PSCommand createCommand = new PSCommand(); createCommand .AddScript(CreatePSEditFunctionScript) .AddParameter("PSEditModule", PSEditModule); - if (runspaceDetails.Context == RunspaceOrigin.DebuggedRunspace) + if (runspaceInfo.RunspaceOrigin == RunspaceOrigin.DebuggedRunspace) { _executionService.ExecutePSCommandAsync(createCommand, new PowerShellExecutionOptions(), CancellationToken.None).GetAwaiter().GetResult(); } @@ -637,7 +641,7 @@ private void RegisterPSEditFunction(RunspaceDetails runspaceDetails) { using (var powerShell = System.Management.Automation.PowerShell.Create()) { - powerShell.Runspace = runspaceDetails.Runspace; + powerShell.Runspace = runspaceInfo.Runspace; powerShell.Commands = createCommand; powerShell.Invoke(); } @@ -650,23 +654,22 @@ private void RegisterPSEditFunction(RunspaceDetails runspaceDetails) } } - private void RemovePSEditFunction(RunspaceDetails runspaceDetails) + private void RemovePSEditFunction(IRunspaceInfo runspaceInfo) { - if (runspaceDetails.Location == RunspaceLocation.Remote && - runspaceDetails.Context == RunspaceOrigin.Original) + if (runspaceInfo.RunspaceOrigin == RunspaceOrigin.PSSession) { try { - if (runspaceDetails.Runspace.Events != null) + if (runspaceInfo.Runspace.Events != null) { - runspaceDetails.Runspace.Events.ReceivedEvents.PSEventReceived -= HandlePSEventReceivedAsync; + runspaceInfo.Runspace.Events.ReceivedEvents.PSEventReceived -= HandlePSEventReceivedAsync; } - if (runspaceDetails.Runspace.RunspaceStateInfo.State == RunspaceState.Opened) + if (runspaceInfo.Runspace.RunspaceStateInfo.State == RunspaceState.Opened) { using (var powerShell = System.Management.Automation.PowerShell.Create()) { - powerShell.Runspace = runspaceDetails.Runspace; + powerShell.Runspace = runspaceInfo.Runspace; powerShell.Commands.AddScript(RemovePSEditFunctionScript); powerShell.Invoke(); } @@ -703,7 +706,7 @@ private void TryDeleteTemporaryPath() private class RemotePathMappings { - private RunspaceDetails runspaceDetails; + private IRunspaceInfo runspaceInfo; private RemoteFileManagerService remoteFileManager; private HashSet openedPaths = new HashSet(); private Dictionary pathMappings = new Dictionary(); @@ -714,10 +717,10 @@ public IEnumerable OpenedPaths } public RemotePathMappings( - RunspaceDetails runspaceDetails, + IRunspaceInfo runspaceInfo, RemoteFileManagerService remoteFileManager) { - this.runspaceDetails = runspaceDetails; + this.runspaceInfo = runspaceInfo; this.remoteFileManager = remoteFileManager; } @@ -750,7 +753,7 @@ public string GetMappedPath(string filePath) mappedPath = this.MapRemotePathToLocal( filePath, - runspaceDetails.SessionDetails.ComputerName); + runspaceInfo.SessionDetails.ComputerName); this.AddPathMapping(filePath, mappedPath); } diff --git a/src/PowerShellEditorServices/Utility/AsyncLock.cs b/src/PowerShellEditorServices/Utility/AsyncLock.cs deleted file mode 100644 index 7e39eb6bb..000000000 --- a/src/PowerShellEditorServices/Utility/AsyncLock.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// Provides a simple wrapper over a SemaphoreSlim to allow - /// synchronization locking inside of async calls. Cannot be - /// used recursively. - /// - internal class AsyncLock - { - #region Fields - - private Task lockReleaseTask; - private SemaphoreSlim lockSemaphore = new SemaphoreSlim(1, 1); - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the AsyncLock class. - /// - public AsyncLock() - { - this.lockReleaseTask = - Task.FromResult( - (IDisposable)new LockReleaser(this)); - } - - #endregion - - #region Public Methods - - /// - /// Locks - /// - /// A task which has an IDisposable - public Task LockAsync() - { - return this.LockAsync(CancellationToken.None); - } - - /// - /// Obtains or waits for a lock which can be used to synchronize - /// access to a resource. The wait may be cancelled with the - /// given CancellationToken. - /// - /// - /// A CancellationToken which can be used to cancel the lock. - /// - /// - public Task LockAsync(CancellationToken cancellationToken) - { - Task waitTask = lockSemaphore.WaitAsync(cancellationToken); - - return waitTask.IsCompleted ? - this.lockReleaseTask : - waitTask.ContinueWith( - (t, releaser) => - { - return (IDisposable)releaser; - }, - this.lockReleaseTask.Result, - cancellationToken, - TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.Default); - } - - /// - /// Obtains or waits for a lock which can be used to synchronize - /// access to a resource. - /// - /// - public IDisposable Lock() - { - return Lock(CancellationToken.None); - } - - /// - /// Obtains or waits for a lock which can be used to synchronize - /// access to a resource. The wait may be cancelled with the - /// given CancellationToken. - /// - /// - /// A CancellationToken which can be used to cancel the lock. - /// - /// - public IDisposable Lock(CancellationToken cancellationToken) - { - lockSemaphore.Wait(cancellationToken); - return this.lockReleaseTask.Result; - } - - #endregion - - #region Private Classes - - /// - /// Provides an IDisposable wrapper around an AsyncLock so - /// that it can easily be used inside of a 'using' block. - /// - private class LockReleaser : IDisposable - { - private AsyncLock lockToRelease; - - internal LockReleaser(AsyncLock lockToRelease) - { - this.lockToRelease = lockToRelease; - } - - public void Dispose() - { - this.lockToRelease.lockSemaphore.Release(); - } - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Utility/AsyncQueue.cs b/src/PowerShellEditorServices/Utility/AsyncQueue.cs deleted file mode 100644 index 3c710f753..000000000 --- a/src/PowerShellEditorServices/Utility/AsyncQueue.cs +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Utility -{ - /// - /// Provides a synchronized queue which can be used from within async - /// operations. This is primarily used for producer/consumer scenarios. - /// - /// The type of item contained in the queue. - internal class AsyncQueue - { - #region Private Fields - - private AsyncLock queueLock = new AsyncLock(); - private Queue itemQueue; - private Queue> requestQueue; - - #endregion - - #region Properties - - /// - /// Returns true if the queue is currently empty. - /// - public bool IsEmpty { get; private set; } - - #endregion - - #region Constructors - - /// - /// Initializes an empty instance of the AsyncQueue class. - /// - public AsyncQueue() : this(Enumerable.Empty()) - { - } - - /// - /// Initializes an instance of the AsyncQueue class, pre-populated - /// with the given collection of items. - /// - /// - /// An IEnumerable containing the initial items with which the queue will - /// be populated. - /// - public AsyncQueue(IEnumerable initialItems) - { - this.itemQueue = new Queue(initialItems); - this.requestQueue = new Queue>(); - } - - #endregion - - #region Public Methods - - /// - /// Enqueues an item onto the end of the queue. - /// - /// The item to be added to the queue. - /// - /// A Task which can be awaited until the synchronized enqueue - /// operation completes. - /// - public async Task EnqueueAsync(T item) - { - using (await queueLock.LockAsync().ConfigureAwait(false)) - { - TaskCompletionSource requestTaskSource = null; - - // Are any requests waiting? - while (this.requestQueue.Count > 0) - { - // Is the next request cancelled already? - requestTaskSource = this.requestQueue.Dequeue(); - if (!requestTaskSource.Task.IsCanceled) - { - // Dispatch the item - requestTaskSource.SetResult(item); - return; - } - } - - // No more requests waiting, queue the item for a later request - this.itemQueue.Enqueue(item); - this.IsEmpty = false; - } - } - - /// - /// Enqueues an item onto the end of the queue. - /// - /// The item to be added to the queue. - public void Enqueue(T item) - { - using (queueLock.Lock()) - { - while (this.requestQueue.Count > 0) - { - var requestTaskSource = this.requestQueue.Dequeue(); - if (requestTaskSource.Task.IsCanceled) - { - continue; - } - - requestTaskSource.SetResult(item); - return; - } - } - - this.itemQueue.Enqueue(item); - this.IsEmpty = false; - } - - /// - /// Dequeues an item from the queue or waits asynchronously - /// until an item is available. - /// - /// - /// A Task which can be awaited until a value can be dequeued. - /// - public Task DequeueAsync() - { - return this.DequeueAsync(CancellationToken.None); - } - - /// - /// Dequeues an item from the queue or waits asynchronously - /// until an item is available. The wait can be cancelled - /// using the given CancellationToken. - /// - /// - /// A CancellationToken with which a dequeue wait can be cancelled. - /// - /// - /// A Task which can be awaited until a value can be dequeued. - /// - public async Task DequeueAsync(CancellationToken cancellationToken) - { - Task requestTask; - - using (await queueLock.LockAsync(cancellationToken).ConfigureAwait(false)) - { - if (this.itemQueue.Count > 0) - { - // Items are waiting to be taken so take one immediately - T item = this.itemQueue.Dequeue(); - this.IsEmpty = this.itemQueue.Count == 0; - - return item; - } - else - { - // Queue the request for the next item - var requestTaskSource = new TaskCompletionSource(); - this.requestQueue.Enqueue(requestTaskSource); - - // Register the wait task for cancel notifications - cancellationToken.Register( - () => requestTaskSource.TrySetCanceled()); - - requestTask = requestTaskSource.Task; - } - } - - // Wait for the request task to complete outside of the lock - return await requestTask.ConfigureAwait(false); - } - - /// - /// Dequeues an item from the queue or waits asynchronously - /// until an item is available. - /// - /// - public T Dequeue() - { - return Dequeue(CancellationToken.None); - } - - /// - /// Dequeues an item from the queue or waits asynchronously - /// until an item is available. The wait can be cancelled - /// using the given CancellationToken. - /// - /// - /// A CancellationToken with which a dequeue wait can be cancelled. - /// - /// - public T Dequeue(CancellationToken cancellationToken) - { - TaskCompletionSource requestTask; - using (queueLock.Lock(cancellationToken)) - { - if (this.itemQueue.Count > 0) - { - T item = this.itemQueue.Dequeue(); - this.IsEmpty = this.itemQueue.Count == 0; - - return item; - } - - requestTask = new TaskCompletionSource(); - this.requestQueue.Enqueue(requestTask); - - if (cancellationToken.CanBeCanceled) - { - cancellationToken.Register(() => requestTask.TrySetCanceled()); - } - } - - return requestTask.Task.GetAwaiter().GetResult(); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Utility/PathUtils.cs b/src/PowerShellEditorServices/Utility/PathUtils.cs index e7ce11e6d..f3f9d45ae 100644 --- a/src/PowerShellEditorServices/Utility/PathUtils.cs +++ b/src/PowerShellEditorServices/Utility/PathUtils.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.IO; +using System.Management.Automation; using System.Runtime.InteropServices; namespace Microsoft.PowerShell.EditorServices.Utility @@ -35,5 +36,10 @@ public static string NormalizePathSeparators(string path) { return string.IsNullOrWhiteSpace(path) ? path : path.Replace(AlternatePathSeparator, DefaultPathSeparator); } + + public static string WildcardEscape(string path) + { + return WildcardPattern.Escape(path); + } } } From 0987a4f92a75ab1fb7f06e6c745bd0ec0c56fe84 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 26 Aug 2020 17:28:42 -0700 Subject: [PATCH 030/176] Bring code up to compilation --- .../Server/PsesServiceCollectionExtensions.cs | 15 +- .../Services/DebugAdapter/DebugService.cs | 50 +- .../Debugging/DebuggerStoppedEventArgs.cs | 2 +- .../Handlers/DebugEvaluateHandler.cs | 6 +- .../Handlers/DisconnectHandler.cs | 9 +- .../Handlers/LaunchAndAttachHandler.cs | 28 +- .../Extension/EditorOperationsService.cs | 6 +- .../PowerShell/Console/ConsoleReadLine.cs | 50 +- .../PowerShell/Console/ConsoleReplRunner.cs | 27 +- .../Services/PowerShell/Console/IReadLine.cs | 24 + .../PowerShell/Console/ReadLineProvider.cs | 27 ++ .../PowerShell/Context/IPowerShellContext.cs | 21 - .../PowerShell/Context/PowerShellContext.cs | 450 ------------------ .../Context/PowerShellContextFrame.cs | 21 +- .../Context/PowerShellVersionDetails.cs | 25 +- .../Debugging/DscBreakpointCapability.cs | 8 +- .../Debugging/IPowerShellDebugContext.cs | 5 +- .../Debugging/PowerShellDebugContext.cs | 43 +- .../Execution/PipelineThreadExecutor.cs | 45 +- .../Execution/SynchronousDelegateTask.cs | 17 +- .../Execution/SynchronousPowerShellTask.cs | 26 +- .../PowerShell/Handlers/GetVersionHandler.cs | 7 +- .../Host/EditorServicesConsolePSHost.cs | 301 +++++++++++- ...ditorServicesConsolePSHostUserInterface.cs | 10 +- .../PowerShell/Host/HostStartOptions.cs | 10 + .../Services/PowerShell/Host/IReadLine.cs | 10 - .../PowerShell/Host/PowerShellFactory.cs | 120 +++++ .../PowerShell/PowerShellExecutionService.cs | 60 +-- .../PowerShell/Runspace/IRunspaceContext.cs | 8 + .../PowerShell/Runspace/IRunspaceInfo.cs | 14 +- .../PowerShell/Runspace/RunspaceInfo.cs | 72 ++- .../PowerShell/Runspace/SessionDetails.cs | 57 ++- .../PowerShell/Utility/CommandHelpers.cs | 3 +- .../Utility/PowerShellExtensions.cs | 143 +++++- .../Services/Symbols/SymbolDetails.cs | 3 + .../Services/Symbols/SymbolsService.cs | 8 + .../Handlers/CompletionHandler.cs | 5 + .../Handlers/ConfigurationHandler.cs | 61 +-- .../Workspace/RemoteFileManagerService.cs | 58 +-- 39 files changed, 1028 insertions(+), 827 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Console/ReadLineProvider.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Context/IPowerShellContext.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContext.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/IReadLine.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/PowerShellFactory.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index b9b2d7f3c..c8b35cddc 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -10,6 +10,9 @@ using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.Extension; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Services.Template; using OmniSharp.Extensions.LanguageServer.Protocol.Server; @@ -23,12 +26,18 @@ public static IServiceCollection AddPsesLanguageServices( { return collection.AddSingleton() .AddSingleton() - .AddSingleton() - .AddSingleton( - (provider) => PowerShellExecutionService.CreateAndStart( + .AddSingleton( + (provider) => EditorServicesConsolePSHost.Create( provider.GetService(), provider.GetService(), hostStartupInfo)) + .AddSingleton( + (provider) => provider.GetService()) + .AddSingleton( + (provider) => provider.GetService().ExecutionService) + .AddSingleton() + .AddSingleton( + (provider) => provider.GetService().DebugContext) .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index ad6efe64f..ee1a2bd04 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -42,6 +42,8 @@ internal class DebugService private readonly EditorServicesConsolePSHost _psesHost; + private readonly IPowerShellDebugContext _debugContext; + private int nextVariableId; private string temporaryScriptListingPath; private List variables; @@ -65,7 +67,7 @@ internal class DebugService /// Gets a boolean that indicates whether the debugger is currently /// stopped at a breakpoint. /// - public bool IsDebuggerStopped => _executionService.DebugContext.IsStopped; + public bool IsDebuggerStopped => _debugContext.IsStopped; /// /// Gets the current DebuggerStoppedEventArgs when the debugger @@ -103,6 +105,7 @@ internal class DebugService /// An ILogger implementation used for writing log messages. public DebugService( PowerShellExecutionService executionService, + IPowerShellDebugContext debugContext, RemoteFileManagerService remoteFileManager, BreakpointService breakpointService, EditorServicesConsolePSHost psesHost, @@ -114,9 +117,10 @@ public DebugService( _executionService = executionService; _breakpointService = breakpointService; _psesHost = psesHost; - _executionService.DebugContext.DebuggerStopped += this.OnDebuggerStopAsync; - _executionService.DebugContext.DebuggerResuming += this.OnDebuggerResuming; - _executionService.DebugContext.BreakpointUpdated += this.OnBreakpointUpdated; + _debugContext = debugContext; + _debugContext.DebuggerStopped += OnDebuggerStopAsync; + _debugContext.DebuggerResuming += OnDebuggerResuming; + _debugContext.BreakpointUpdated += OnBreakpointUpdated; this.remoteFileManager = remoteFileManager; @@ -143,11 +147,11 @@ public async Task SetLineBreakpointsAsync( BreakpointDetails[] breakpoints, bool clearExisting = true) { - DscBreakpointCapability dscBreakpoints = _executionService.CurrentRunspace.DscBreakpointCapability; + DscBreakpointCapability dscBreakpoints = await _debugContext.GetDscBreakpointCapabilityAsync(CancellationToken.None); string scriptPath = scriptFile.FilePath; // Make sure we're using the remote script path - if (_psesHost.Runspace.RunspaceIsRemote + if (_psesHost.CurrentRunspace.IsOnRemoteMachine && this.remoteFileManager != null) { if (!this.remoteFileManager.IsUnderRemoteTempPath(scriptPath)) @@ -161,7 +165,7 @@ public async Task SetLineBreakpointsAsync( string mappedPath = this.remoteFileManager.GetMappedPath( scriptPath, - _executionService.CurrentRunspace); + _psesHost.CurrentRunspace); scriptPath = mappedPath; } @@ -228,7 +232,7 @@ await _breakpointService.RemoveBreakpointsAsync( /// public void Continue() { - _executionService.DebugContext.Continue(); + _debugContext.Continue(); } /// @@ -236,7 +240,7 @@ public void Continue() /// public void StepOver() { - _executionService.DebugContext.StepOver(); + _debugContext.StepOver(); } /// @@ -244,7 +248,7 @@ public void StepOver() /// public void StepIn() { - _executionService.DebugContext.StepInto(); + _debugContext.StepInto(); } /// @@ -252,7 +256,7 @@ public void StepIn() /// public void StepOut() { - _executionService.DebugContext.StepOut(); + _debugContext.StepOut(); } /// @@ -262,7 +266,7 @@ public void StepOut() /// public void Break() { - _executionService.DebugContext.BreakExecution(); + _debugContext.BreakExecution(); } /// @@ -271,7 +275,7 @@ public void Break() /// public void Abort() { - _executionService.DebugContext.Abort(); + _debugContext.Abort(); } /// @@ -808,7 +812,7 @@ private async Task FetchStackFramesAsync(string scriptNameOverride) // When debugging, this is the best way I can find to get what is likely the workspace root. // This is controlled by the "cwd:" setting in the launch config. - string workspaceRootPath = _executionService.PowerShellContext.InitialWorkingDirectory; + string workspaceRootPath = _psesHost.InitialWorkingDirectory; this.stackFrameDetails[i] = StackFrameDetails.Create(callStackFrames[i], autoVariables, localVariables, workspaceRootPath); @@ -819,14 +823,14 @@ private async Task FetchStackFramesAsync(string scriptNameOverride) { this.stackFrameDetails[i].ScriptPath = scriptNameOverride; } - else if (_executionService.CurrentRunspace.IsRemote() + else if (_psesHost.CurrentRunspace.IsOnRemoteMachine && this.remoteFileManager != null && !string.Equals(stackFrameScriptPath, StackFrameDetails.NoFileScriptPath)) { this.stackFrameDetails[i].ScriptPath = this.remoteFileManager.GetMappedPath( stackFrameScriptPath, - _executionService.CurrentRunspace); + _psesHost.CurrentRunspace); } } } @@ -888,9 +892,9 @@ await _executionService.ExecutePSCommandAsync( this.temporaryScriptListingPath = this.remoteFileManager.CreateTemporaryFile( - $"[{_executionService.CurrentRunspace.SessionDetails.ComputerName}] {TemporaryScriptFileName}", + $"[{_psesHost.CurrentRunspace.SessionDetails.ComputerName}] {TemporaryScriptFileName}", scriptListing, - _executionService.CurrentRunspace); + _psesHost.CurrentRunspace); localScriptPath = this.temporaryScriptListingPath @@ -910,14 +914,14 @@ await this.FetchStackFramesAndVariablesAsync( // If this is a remote connection and the debugger stopped at a line // in a script file, get the file contents - if (_executionService.CurrentRunspace.IsRemote() + if (_psesHost.CurrentRunspace.IsOnRemoteMachine && this.remoteFileManager != null && !noScriptName) { localScriptPath = await this.remoteFileManager.FetchRemoteFileAsync( e.InvocationInfo.ScriptName, - _executionService.CurrentRunspace).ConfigureAwait(false); + _psesHost.CurrentRunspace).ConfigureAwait(false); } if (this.stackFrameDetails.Length > 0) @@ -937,7 +941,7 @@ await this.remoteFileManager.FetchRemoteFileAsync( this.CurrentDebuggerStoppedEventArgs = new DebuggerStoppedEventArgs( e, - _executionService.CurrentRunspace, + _psesHost.CurrentRunspace, localScriptPath); // Notify the host that the debugger is stopped @@ -967,13 +971,13 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) { // TODO: This could be either a path or a script block! string scriptPath = lineBreakpoint.Script; - if (_executionService.CurrentRunspace.IsRemote() + if (_psesHost.CurrentRunspace.IsOnRemoteMachine && this.remoteFileManager != null) { string mappedPath = this.remoteFileManager.GetMappedPath( scriptPath, - _executionService.CurrentRunspace); + _psesHost.CurrentRunspace); if (mappedPath == null) { diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs index d0be19023..87dfd860f 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs @@ -27,7 +27,7 @@ internal class DebuggerStoppedEventArgs /// public bool IsRemoteSession { - get => RunspaceInfo.IsRemote(); + get => RunspaceInfo.RunspaceOrigin != RunspaceOrigin.Local; } /// diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs index e9a4e88bd..6f606927e 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs @@ -9,6 +9,7 @@ using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; namespace Microsoft.PowerShell.EditorServices.Handlers @@ -16,15 +17,18 @@ namespace Microsoft.PowerShell.EditorServices.Handlers internal class DebugEvaluateHandler : IEvaluateHandler { private readonly ILogger _logger; + private readonly IPowerShellDebugContext _debugContext; private readonly PowerShellExecutionService _executionService; private readonly DebugService _debugService; public DebugEvaluateHandler( ILoggerFactory factory, + IPowerShellDebugContext debugContext, PowerShellExecutionService executionService, DebugService debugService) { _logger = factory.CreateLogger(); + _debugContext = debugContext; _executionService = executionService; _debugService = debugService; } @@ -54,7 +58,7 @@ public async Task Handle(EvaluateRequestArguments request, // VS Code might send this request after the debugger // has been resumed, return an empty result in this case. - if (_executionService.DebugContext.IsStopped) + if (_debugContext.IsStopped) { // First check to see if the watch expression refers to a naked variable reference. result = diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs index fb9e8c943..b24abe10f 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs @@ -6,10 +6,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Server; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; @@ -23,10 +25,12 @@ internal class DisconnectHandler : IDisconnectHandler private readonly DebugStateService _debugStateService; private readonly DebugEventHandlerService _debugEventHandlerService; private readonly PsesDebugServer _psesDebugServer; + private readonly IRunspaceContext _runspaceContext; public DisconnectHandler( ILoggerFactory factory, PsesDebugServer psesDebugServer, + IRunspaceContext runspaceContext, PowerShellExecutionService executionService, DebugService debugService, DebugStateService debugStateService, @@ -34,6 +38,7 @@ public DisconnectHandler( { _logger = factory.CreateLogger(); _psesDebugServer = psesDebugServer; + _runspaceContext = runspaceContext; _executionService = executionService; _debugService = debugService; _debugStateService = debugStateService; @@ -51,7 +56,7 @@ public async Task Handle(DisconnectArguments request, Cancel if (_debugStateService.IsInteractiveDebugSession && _debugStateService.IsAttachSession) { // Pop the sessions - if (_executionService.CurrentRunspace.RunspaceOrigin == RunspaceOrigin.EnteredProcess) + if (_runspaceContext.CurrentRunspace.RunspaceOrigin == RunspaceOrigin.EnteredProcess) { try { @@ -61,7 +66,7 @@ await _executionService.ExecutePSCommandAsync( CancellationToken.None).ConfigureAwait(false); if (_debugStateService.IsRemoteAttach && - _executionService.CurrentRunspace.IsRemote()) + _runspaceContext.CurrentRunspace.RunspaceOrigin == RunspaceOrigin.EnteredProcess) { await _executionService.ExecutePSCommandAsync( new PSCommand().AddCommand("Exit-PSSession"), diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index 09e497595..bf1e4df39 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -20,6 +20,7 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; namespace Microsoft.PowerShell.EditorServices.Handlers { @@ -90,6 +91,7 @@ internal class LaunchAndAttachHandler : ILaunchHandler _logger; private readonly BreakpointService _breakpointService; private readonly DebugService _debugService; + private readonly IRunspaceContext _runspaceContext; private readonly PowerShellExecutionService _executionService; private readonly DebugStateService _debugStateService; private readonly DebugEventHandlerService _debugEventHandlerService; @@ -102,6 +104,7 @@ public LaunchAndAttachHandler( BreakpointService breakpointService, DebugEventHandlerService debugEventHandlerService, DebugService debugService, + IRunspaceContext runspaceContext, PowerShellExecutionService executionService, DebugStateService debugStateService, RemoteFileManagerService remoteFileManagerService) @@ -111,6 +114,7 @@ public LaunchAndAttachHandler( _breakpointService = breakpointService; _debugEventHandlerService = debugEventHandlerService; _debugService = debugService; + _runspaceContext = runspaceContext; _executionService = executionService; _debugStateService = debugStateService; _debugStateService.ServerStarted = new TaskCompletionSource(); @@ -122,8 +126,8 @@ public async Task Handle(PsesLaunchRequestArguments request, Can _debugEventHandlerService.RegisterEventHandlers(); // Determine whether or not the working directory should be set in the PowerShellContext. - if (!_executionService.CurrentRunspace.IsRemote() && - !_debugService.IsDebuggerStopped) + if (_runspaceContext.CurrentRunspace.RunspaceOrigin == RunspaceOrigin.Local + && !_debugService.IsDebuggerStopped) { // Get the working directory that was passed via the debug config // (either via launch.json or generated via no-config debug). @@ -191,12 +195,12 @@ public async Task Handle(PsesLaunchRequestArguments request, Can // If the current session is remote, map the script path to the remote // machine if necessary if (_debugStateService.ScriptToLaunch != null - && _executionService.CurrentRunspace.IsRemote()) + && _runspaceContext.CurrentRunspace.IsOnRemoteMachine) { _debugStateService.ScriptToLaunch = _remoteFileManagerService.GetMappedPath( _debugStateService.ScriptToLaunch, - _executionService.CurrentRunspace); + _runspaceContext.CurrentRunspace); } // If no script is being launched, mark this as an interactive @@ -219,8 +223,7 @@ public async Task Handle(PsesAttachRequestArguments request, Can bool processIdIsSet = !string.IsNullOrEmpty(request.ProcessId) && request.ProcessId != "undefined"; bool customPipeNameIsSet = !string.IsNullOrEmpty(request.CustomPipeName) && request.CustomPipeName != "undefined"; - PowerShellVersionDetails runspaceVersion = - _executionService.CurrentRunspace.PowerShellVersionDetails; + PowerShellVersionDetails runspaceVersion = _runspaceContext.CurrentRunspace.PowerShellVersionDetails; // If there are no host processes to attach to or the user cancels selection, we get a null for the process id. // This is not an error, just a request to stop the original "attach to" request. @@ -234,15 +237,13 @@ public async Task Handle(PsesAttachRequestArguments request, Can throw new RpcErrorException(0, "User aborted attach to PowerShell host process."); } - StringBuilder errorMessages = new StringBuilder(); - if (request.ComputerName != null) { if (runspaceVersion.Version.Major < 4) { throw new RpcErrorException(0, $"Remote sessions are only available with PowerShell 4 and higher (current session is {runspaceVersion.Version})."); } - else if (_executionService.CurrentRunspace.IsRemote()) + else if (_runspaceContext.CurrentRunspace.RunspaceOrigin != RunspaceOrigin.Local) { throw new RpcErrorException(0, "Cannot attach to a process in a remote session when already in a remote session."); } @@ -308,11 +309,6 @@ public async Task Handle(PsesAttachRequestArguments request, Can _logger.LogError(e, msg); throw new RpcErrorException(0, msg); } - - if (errorMessages.Length > 0) - { - throw new RpcErrorException(0, $"Could not attach to process with CustomPipeName: '{request.CustomPipeName}'"); - } } else if (request.ProcessId != "current") { @@ -425,14 +421,14 @@ private async Task OnExecutionCompletedAsync(Task executeTask) if (_debugStateService.IsAttachSession) { // Pop the sessions - if (_executionService.CurrentRunspace.RunspaceOrigin == RunspaceOrigin.EnteredProcess) + if (_runspaceContext.CurrentRunspace.RunspaceOrigin == RunspaceOrigin.EnteredProcess) { try { await _executionService.ExecutePSCommandAsync(new PSCommand().AddCommand("Exit-PSHostProcess"), new PowerShellExecutionOptions(), CancellationToken.None); if (_debugStateService.IsRemoteAttach && - _executionService.CurrentRunspace.IsRemote()) + _runspaceContext.CurrentRunspace.RunspaceOrigin != RunspaceOrigin.Local) { await _executionService.ExecutePSCommandAsync(new PSCommand().AddCommand("Exit-PSSession"), new PowerShellExecutionOptions(), CancellationToken.None); } diff --git a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs index 96a317ffe..3df728ba1 100644 --- a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs +++ b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs @@ -3,6 +3,7 @@ using Microsoft.PowerShell.EditorServices.Extensions; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; @@ -15,6 +16,7 @@ internal class EditorOperationsService : IEditorOperations { private const bool DefaultPreviewSetting = true; + private readonly EditorServicesConsolePSHost _psesHost; private readonly WorkspaceService _workspaceService; private readonly ILanguageServerFacade _languageServer; @@ -22,10 +24,12 @@ internal class EditorOperationsService : IEditorOperations private readonly PowerShellExecutionService _executionService; public EditorOperationsService( + EditorServicesConsolePSHost psesHost, WorkspaceService workspaceService, PowerShellExecutionService executionService, ILanguageServerFacade languageServer) { + _psesHost = psesHost; _workspaceService = workspaceService; _executionService = executionService; _languageServer = languageServer; @@ -274,7 +278,7 @@ private bool TestHasLanguageServer(bool warnUser = true) if (warnUser) { - _executionService.PowerShellContext.EditorServicesPSHost.UI.WriteWarningLine( + _psesHost.UI.WriteWarningLine( "Editor operations are not supported in temporary consoles. Re-run the command in the main PowerShell Intergrated Console."); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index 2bbb9cabf..14e6610bf 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -10,34 +10,58 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using System; using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Language; + using System.Runtime.CompilerServices; using System.Security; - internal class ConsoleReadLine + internal class ConsoleReadLine : IReadLine { - private PSReadLineProxy _psrlProxy; + private readonly PSReadLineProxy _psrlProxy; - private PowerShellExecutionService _executionService; + private readonly EditorServicesConsolePSHost _psesHost; - private EngineIntrinsics _engineIntrinsics; + private readonly PowerShellExecutionService _executionService; + + private readonly EngineIntrinsics _engineIntrinsics; #region Constructors + public ConsoleReadLine( + PSReadLineProxy psrlProxy, + EditorServicesConsolePSHost psesHost, + PowerShellExecutionService executionService, + EngineIntrinsics engineIntrinsics) + { + _psrlProxy = psrlProxy; + _psesHost = psesHost; + _executionService = executionService; + _engineIntrinsics = engineIntrinsics; + } + #endregion #region Public Methods - public void RegisterExecutionDependencies( - PowerShellExecutionService executionService, - EngineIntrinsics engineIntrinsics, - PSReadLineProxy psrlProxy) + public Task ReadLineAsync(CancellationToken cancellationToken) => ReadLineAsync(isCommandLine: true, cancellationToken); + + public string ReadLine() => ReadLineAsync(CancellationToken.None).GetAwaiter().GetResult(); + + public SecureString ReadSecureLine() => ReadSecureLineAsync(CancellationToken.None).GetAwaiter().GetResult(); + + public bool TryOverrideReadKey(Func readKeyFunc) { - _executionService = executionService; - _engineIntrinsics = engineIntrinsics; - _psrlProxy = psrlProxy; + _psrlProxy.OverrideReadKey(readKeyFunc); + return true; + } + + public bool TryOverrideIdleHandler(Action idleHandler) + { + _psrlProxy.OverrideIdleHandler(idleHandler); + return true; } public Task ReadCommandLineAsync(CancellationToken cancellationToken) @@ -146,8 +170,8 @@ private Task ReadLineAsync(bool isCommandLine, CancellationToken cancell private string InvokePSReadLine(CancellationToken cancellationToken) { - EngineIntrinsics engineIntrinsics = _executionService.PowerShellContext.IsRunspacePushed ? null : _engineIntrinsics; - return _psrlProxy.ReadLine(_executionService.CurrentRunspace.Runspace, engineIntrinsics, cancellationToken); + EngineIntrinsics engineIntrinsics = _psesHost.IsRunspacePushed ? null : _engineIntrinsics; + return _psrlProxy.ReadLine(_psesHost.Runspace, engineIntrinsics, cancellationToken); } /// diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs index 2bb14f88c..65ef92d78 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs @@ -13,13 +13,14 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; internal class ConsoleReplRunner : IDisposable { private readonly ILogger _logger; - private PowerShellContext _pwshContext; + private readonly EditorServicesConsolePSHost _psesHost; private readonly PowerShellExecutionService _executionService; @@ -30,18 +31,23 @@ internal class ConsoleReplRunner : IDisposable // for the REPL command that's currently running. private readonly ConcurrentStack _commandCancellationStack; + private readonly IReadLineProvider _readLineProvider; + private ConsoleKeyInfo? _lastKey; private bool _exiting; public ConsoleReplRunner( ILoggerFactory loggerFactory, - PowerShellContext pwshContext, + EditorServicesConsolePSHost psesHost, + IReadLineProvider readLineProvider, PowerShellExecutionService executionService) { _logger = loggerFactory.CreateLogger(); _replLoopTaskStack = new ConcurrentStack(); _commandCancellationStack = new ConcurrentStack(); + _psesHost = psesHost; + _readLineProvider = readLineProvider; _executionService = executionService; _exiting = false; } @@ -51,8 +57,9 @@ public void StartRepl() System.Console.CancelKeyPress += OnCancelKeyPress; System.Console.InputEncoding = Encoding.UTF8; System.Console.OutputEncoding = Encoding.UTF8; - _pwshContext.PSReadLineProxy.OverrideReadKey(ReadKey); + _readLineProvider.ReadLine.TryOverrideReadKey(ReadKey); PushNewReplTask(); + _logger.LogInformation("REPL started"); } public void Dispose() @@ -117,7 +124,7 @@ private async Task RunReplLoopAsync() if (currentCommandCancellation.CancellationSource.IsCancellationRequested || LastKeyWasCtrlC()) { - _pwshContext.EditorServicesHost.UI.WriteLine(); + _psesHost.UI.WriteLine(); } continue; } @@ -135,7 +142,7 @@ private async Task RunReplLoopAsync() } catch (Exception e) { - _pwshContext.EditorServicesHost.UI.WriteErrorLine($"An error occurred while running the REPL loop:{Environment.NewLine}{e}"); + _psesHost.UI.WriteErrorLine($"An error occurred while running the REPL loop:{Environment.NewLine}{e}"); _logger.LogError(e, "An error occurred while running the REPL loop"); break; } @@ -160,9 +167,9 @@ private async Task GetPromptStringAsync(CancellationToken cancellationTo { string prompt = (await GetPromptOutputAsync(cancellationToken).ConfigureAwait(false)).FirstOrDefault() ?? "PS> "; - if (_pwshContext.EditorServicesHost.Runspace.RunspaceIsRemote) + if (_psesHost.CurrentRunspace.RunspaceOrigin != RunspaceOrigin.Local) { - prompt = _pwshContext.EditorServicesHost.Runspace.GetRemotePrompt(prompt); + prompt = _psesHost.Runspace.GetRemotePrompt(prompt); } return prompt; @@ -180,12 +187,12 @@ private Task> GetPromptOutputAsync(CancellationToken cance private void WritePrompt(string promptString) { - _pwshContext.EditorServicesHost.UI.Write(promptString); + _psesHost.UI.Write(promptString); } private Task InvokeReadLineAsync(CancellationToken cancellationToken) { - return _pwshContext.ReadLine.ReadCommandLineAsync(cancellationToken); + return _readLineProvider.ReadLine.ReadLineAsync(cancellationToken); } private Task InvokeInputAsync(string input, CancellationToken cancellationToken) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs new file mode 100644 index 000000000..50eaf7e34 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Security; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console +{ + internal interface IReadLine + { + Task ReadLineAsync(CancellationToken cancellationToken); + + Task ReadSecureLineAsync(CancellationToken cancellationToken); + + string ReadLine(); + + SecureString ReadSecureLine(); + + bool TryOverrideReadKey(Func readKeyOverride); + + bool TryOverrideIdleHandler(Action idleHandler); + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ReadLineProvider.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ReadLineProvider.cs new file mode 100644 index 000000000..312da2b05 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ReadLineProvider.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.Logging; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console +{ + internal interface IReadLineProvider + { + IReadLine ReadLine { get; } + } + + internal class ReadLineProvider : IReadLineProvider + { + private readonly ILogger _logger; + + public ReadLineProvider(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public IReadLine ReadLine { get; private set; } + + public void OverrideReadLine(IReadLine readLine) + { + _logger.LogInformation($"ReadLine overridden with '{readLine.GetType()}'"); + ReadLine = readLine; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/IPowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/IPowerShellContext.cs deleted file mode 100644 index 08b871e3a..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/IPowerShellContext.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using System; -using System.Management.Automation; -using System.Threading; -using SMA = System.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context -{ - using System.Management.Automation.Runspaces; - - internal interface IPowerShellContext : IDisposable - { - CancellationTokenSource CurrentCancellationSource { get; } - - EditorServicesConsolePSHost EditorServicesPSHost { get; } - - bool IsRunspacePushed { get; } - - string InitialWorkingDirectory { get; } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContext.cs deleted file mode 100644 index fdad69567..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContext.cs +++ /dev/null @@ -1,450 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Hosting; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.IO; -using System.Management.Automation; -using System.Reflection; -using System.Threading; -using SMA = System.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context -{ - using OmniSharp.Extensions.LanguageServer.Protocol.Server; - using System.Management.Automation.Runspaces; - - internal class PowerShellContext : IPowerShellContext - { - private static readonly string s_commandsModulePath = Path.GetFullPath( - Path.Combine( - Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), - "../../Commands/PowerShellEditorServices.Commands.psd1")); - - private readonly ILoggerFactory _loggerFactory; - - private readonly ILogger _logger; - - private readonly Stack _psFrameStack; - - private readonly HostStartupInfo _hostInfo; - - private readonly PowerShellExecutionService _executionService; - - private readonly ConsoleReplRunner _consoleReplRunner; - - private readonly PipelineThreadExecutor _pipelineExecutor; - - public PowerShellContext( - ILoggerFactory loggerFactory, - HostStartupInfo hostInfo, - ILanguageServer languageServer, - PowerShellExecutionService executionService, - PipelineThreadExecutor pipelineExecutor) - { - _loggerFactory = loggerFactory; - _logger = loggerFactory.CreateLogger(); - _hostInfo = hostInfo; - _executionService = executionService; - _pipelineExecutor = pipelineExecutor; - _psFrameStack = new Stack(); - DebugContext = new PowerShellDebugContext(languageServer, this, _consoleReplRunner); - - if (hostInfo.ConsoleReplEnabled) - { - _consoleReplRunner = new ConsoleReplRunner(_loggerFactory, executionService); - } - } - - public SMA.PowerShell CurrentPowerShell => CurrentFrame.PowerShell; - - public Runspace CurrentRunspace => CurrentPowerShell.Runspace; - - public PowerShellContextFrame CurrentFrame => _psFrameStack.Peek(); - - public ConsoleReadLine ReadLine { get; private set; } - - public PSReadLineProxy PSReadLineProxy { get; private set; } - - public EditorServicesConsolePSHost EditorServicesHost { get; private set; } - - public EngineIntrinsics EngineIntrinsics { get; private set; } - - public PowerShellDebugContext DebugContext { get; } - - public RunspaceInfo CurrentRunspaceInfo { get; } - - public RunspaceInfo RunspaceContext { get; } - - public CancellationTokenSource CurrentCancellationSource => throw new NotImplementedException(); - - public EditorServicesConsolePSHost EditorServicesPSHost => throw new NotImplementedException(); - - public bool IsRunspacePushed => throw new NotImplementedException(); - - public string InitialWorkingDirectory { get; private set; } - - public void PushInitialPowerShell() - { - ReadLine = new ConsoleReadLine(); - - EditorServicesHost = new EditorServicesConsolePSHost( - _loggerFactory, - _hostInfo.Name, - _hostInfo.Version, - _hostInfo.PSHost, - ReadLine); - - Runspace runspace = CreateInitialRunspace(); - - var pwsh = SMA.PowerShell.Create(); - pwsh.Runspace = runspace; - _psFrameStack.Push(new PowerShellContextFrame(pwsh, PowerShellFrameType.Normal, new CancellationTokenSource())); - - EditorServicesHost.RegisterPowerShellContext(this); - - EngineIntrinsics = (EngineIntrinsics)CurrentFrame.PowerShell.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); - - PSReadLineProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, CurrentPowerShell); - PSReadLineProxy.OverrideIdleHandler(_pipelineExecutor.OnPowerShellIdle); - ReadLine.RegisterExecutionDependencies(_executionService, EngineIntrinsics, PSReadLineProxy); - - if (VersionUtils.IsWindows) - { - SetExecutionPolicy(); - } - - LoadProfiles(); - - ImportModule(s_commandsModulePath); - - if (_hostInfo.AdditionalModules != null && _hostInfo.AdditionalModules.Count > 0) - { - foreach (string module in _hostInfo.AdditionalModules) - { - ImportModule(module); - } - } - - _consoleReplRunner?.StartRepl(); - } - - public void SetShouldExit(int? exitCode) - { - if (_psFrameStack.Count <= 1) - { - return; - } - - _pipelineExecutor.IsExiting = true; - - if ((CurrentFrame.FrameType & PowerShellFrameType.NonInteractive) == 0) - { - _consoleReplRunner?.SetReplPop(); - } - } - - public void ProcessDebuggerResult(DebuggerCommandResults debuggerResult) - { - if (debuggerResult.ResumeAction != null) - { - DebugContext.RaiseDebuggerResumingEvent(new DebuggerResumingEventArgs(debuggerResult.ResumeAction.Value)); - } - } - - public void PushNestedPowerShell() - { - PushNestedPowerShell(PowerShellFrameType.Normal); - } - - public void PushPowerShell(Runspace runspaceToUse) - { - var pwsh = SMA.PowerShell.Create(); - pwsh.Runspace = runspaceToUse; - - PowerShellFrameType frameType = PowerShellFrameType.Normal; - - if (runspaceToUse.RunspaceIsRemote) - { - frameType |= PowerShellFrameType.Remote; - } - - PushFrame(new PowerShellContextFrame(pwsh, frameType, new CancellationTokenSource())); - } - - public void PopFrame() - { - _pipelineExecutor.IsExiting = false; - PowerShellContextFrame frame = _psFrameStack.Pop(); - try - { - RemoveRunspaceEventHandlers(frame.PowerShell.Runspace); - if (_psFrameStack.Count > 0) - { - AddRunspaceEventHandlers(CurrentPowerShell.Runspace); - } - } - finally - { - frame.Dispose(); - } - } - - public void Dispose() - { - _consoleReplRunner?.Dispose(); - _pipelineExecutor.Dispose(); - } - - private void PushFrame(PowerShellContextFrame frame) - { - if (_psFrameStack.Count > 0) - { - RemoveRunspaceEventHandlers(CurrentFrame.PowerShell.Runspace); - } - AddRunspaceEventHandlers(frame.PowerShell.Runspace); - _psFrameStack.Push(frame); - _pipelineExecutor.RunPowerShellLoop(frame.FrameType); - } - - private void PushNestedPowerShell(PowerShellFrameType frameType) - { - SMA.PowerShell pwsh = CreateNestedPowerShell(); - PowerShellFrameType newFrameType = _psFrameStack.Peek().FrameType | PowerShellFrameType.Nested | frameType; - PushFrame(new PowerShellContextFrame(pwsh, newFrameType, new CancellationTokenSource())); - } - - private SMA.PowerShell CreateNestedPowerShell() - { - PowerShellContextFrame currentFrame = _psFrameStack.Peek(); - if ((currentFrame.FrameType & PowerShellFrameType.Remote) != 0) - { - var remotePwsh = SMA.PowerShell.Create(); - remotePwsh.Runspace = currentFrame.PowerShell.Runspace; - return remotePwsh; - } - - // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild - // This means it throws due to the parent pipeline not running... - // So we must use the RunspaceMode.CurrentRunspace option on PowerShell.Create() instead - var pwsh = SMA.PowerShell.Create(RunspaceMode.CurrentRunspace); - pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; - return pwsh; - } - - public void PushNonInteractivePowerShell() - { - PushNestedPowerShell(PowerShellFrameType.NonInteractive); - } - - private void PushDebugPowerShell() - { - PushNestedPowerShell(PowerShellFrameType.Debug); - } - - private void AddRunspaceEventHandlers(Runspace runspace) - { - runspace.Debugger.DebuggerStop += OnDebuggerStopped; - runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; - runspace.StateChanged += OnRunspaceStateChanged; - } - - private void RemoveRunspaceEventHandlers(Runspace runspace) - { - runspace.Debugger.DebuggerStop -= OnDebuggerStopped; - runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; - runspace.StateChanged -= OnRunspaceStateChanged; - } - - #region Initial Runspace Setup - - private Runspace CreateInitialRunspace() - { - InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" - ? InitialSessionState.CreateDefault() - : InitialSessionState.CreateDefault2(); - - iss.LanguageMode = _hostInfo.LanguageMode; - - Runspace runspace = RunspaceFactory.CreateRunspace(EditorServicesHost, iss); - - runspace.SetApartmentStateToSta(); - runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; - - runspace.Open(); - - AddRunspaceEventHandlers(runspace); - - Runspace.DefaultRunspace = runspace; - - return runspace; - } - - private void SetExecutionPolicy() - { - // We want to get the list hierarchy of execution policies - // Calling the cmdlet is the simplest way to do that - IReadOnlyList policies = CurrentPowerShell - .AddCommand("Microsoft.PowerShell.Security\\Get-ExecutionPolicy") - .AddParameter("-List") - .InvokeAndClear(); - - // The policies come out in the following order: - // - MachinePolicy - // - UserPolicy - // - Process - // - CurrentUser - // - LocalMachine - // We want to ignore policy settings, since we'll already have those anyway. - // Then we need to look at the CurrentUser setting, and then the LocalMachine setting. - // - // Get-ExecutionPolicy -List emits PSObjects with Scope and ExecutionPolicy note properties - // set to expected values, so we must sift through those. - - ExecutionPolicy policyToSet = ExecutionPolicy.Bypass; - var currentUserPolicy = (ExecutionPolicy)policies[policies.Count - 2].Members["ExecutionPolicy"].Value; - if (currentUserPolicy != ExecutionPolicy.Undefined) - { - policyToSet = currentUserPolicy; - } - else - { - var localMachinePolicy = (ExecutionPolicy)policies[policies.Count - 1].Members["ExecutionPolicy"].Value; - if (localMachinePolicy != ExecutionPolicy.Undefined) - { - policyToSet = localMachinePolicy; - } - } - - // If there's nothing to do, save ourselves a PowerShell invocation - if (policyToSet == ExecutionPolicy.Bypass) - { - _logger.LogTrace("Execution policy already set to Bypass. Skipping execution policy set"); - return; - } - - // Finally set the inherited execution policy - _logger.LogTrace("Setting execution policy to {Policy}", policyToSet); - try - { - CurrentPowerShell.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") - .AddParameter("Scope", ExecutionPolicyScope.Process) - .AddParameter("ExecutionPolicy", policyToSet) - .AddParameter("Force") - .InvokeAndClear(); - } - catch (CmdletInvocationException e) - { - _logger.LogError(e, "Error occurred calling 'Set-ExecutionPolicy -Scope Process -ExecutionPolicy {Policy} -Force'", policyToSet); - } - } - - private void LoadProfiles() - { - var profileVariable = new PSObject(); - - ProfilePathInfo profilePaths = _hostInfo.ProfilePaths; - - AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.AllUsersAllHosts), profilePaths.AllUsersAllHosts); - AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.AllUsersCurrentHost), profilePaths.AllUsersCurrentHost); - AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.CurrentUserAllHosts), profilePaths.CurrentUserAllHosts); - AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.CurrentUserCurrentHost), profilePaths.CurrentUserCurrentHost); - - CurrentPowerShell.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); - } - - private void AddProfileMemberAndLoadIfExists(PSObject profileVariable, string profileName, string profilePath) - { - profileVariable.Members.Add(new PSNoteProperty(profileName, profilePath)); - - if (File.Exists(profilePath)) - { - var psCommand = new PSCommand() - .AddScript(profilePath, useLocalScope: false) - .AddOutputCommand(); - - CurrentPowerShell.InvokeCommand(psCommand); - } - } - - private void ImportModule(string moduleNameOrPath) - { - CurrentPowerShell.AddCommand("Microsoft.PowerShell.Core\\Import-Module") - .AddParameter("-Name", moduleNameOrPath) - .InvokeAndClear(); - } - - #endregion /* Initial Runspace Setup */ - - private void PopOrReinitializeRunspace() - { - _consoleReplRunner?.SetReplPop(); - _pipelineExecutor.CancelCurrentTask(); - - RunspaceStateInfo oldRunspaceState = CurrentPowerShell.Runspace.RunspaceStateInfo; - using (_pipelineExecutor.TakeTaskWriterLock()) - { - while (_psFrameStack.Count > 0 - && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) - { - PopFrame(); - } - - if (_psFrameStack.Count == 0) - { - // If our main runspace was corrupted, - // we must re-initialize our state. - // TODO: Use runspace.ResetRunspaceState() here instead - PushInitialPowerShell(); - - _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." - + " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs."); - EditorServicesHost.UI.WriteErrorLine("The main runspace encountered an error and has been reinitialized. See the PowerShell extension logs for more details."); - } - else - { - _logger.LogError($"Current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was popped."); - EditorServicesHost.UI.WriteErrorLine($"The current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}'." - + " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior." - + " The session is now returning to the previous runspace."); - } - } - } - - private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) - { - DebugContext.SetDebuggerStopped(debuggerStopEventArgs); - try - { - CurrentPowerShell.WaitForRemoteOutputIfNeeded(); - DebugContext.LastStopEventArgs = debuggerStopEventArgs; - PushDebugPowerShell(); - CurrentPowerShell.ResumeRemoteOutputIfNeeded(); - } - finally - { - DebugContext.SetDebuggerResumed(); - } - } - - private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) - { - DebugContext.HandleBreakpointUpdated(breakpointUpdatedEventArgs); - } - - private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) - { - if (!runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) - { - PopOrReinitializeRunspace(); - } - } - - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs index 68404e39f..0feb0241c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs @@ -1,4 +1,5 @@ -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using System; using System.Threading; using SMA = System.Management.Automation; @@ -7,17 +8,31 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context { internal class PowerShellContextFrame : IDisposable { + public static PowerShellContextFrame CreateForPowerShellInstance( + ILogger logger, + SMA.PowerShell pwsh, + RunspaceOrigin runspaceOrigin, + PowerShellFrameType frameType, + string localComputerName) + { + var runspaceInfo = RunspaceInfo.CreateFromPowerShell(logger, pwsh, runspaceOrigin, localComputerName); + return new PowerShellContextFrame(pwsh, runspaceInfo, frameType); + } + private bool disposedValue; - public PowerShellContextFrame(SMA.PowerShell powerShell, PowerShellFrameType frameType, CancellationTokenSource cancellationTokenSource) + public PowerShellContextFrame(SMA.PowerShell powerShell, RunspaceInfo runspaceInfo, PowerShellFrameType frameType) { PowerShell = powerShell; + RunspaceInfo = runspaceInfo; FrameType = frameType; - CancellationTokenSource = cancellationTokenSource; + CancellationTokenSource = new CancellationTokenSource(); } public SMA.PowerShell PowerShell { get; } + public RunspaceInfo RunspaceInfo { get; } + public PowerShellFrameType FrameType { get; } public CancellationTokenSource CancellationTokenSource { get; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs index a9d828173..2b4d2afc1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs @@ -6,13 +6,13 @@ using System; using System.Collections; using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Threading; using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context { + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; + using System.Management.Automation; + /// /// Defines the possible enumeration values for the PowerShell process architecture. /// @@ -92,10 +92,9 @@ public PowerShellVersionDetails( /// /// Gets the PowerShell version details for the given runspace. /// - /// The runspace for which version details will be gathered. /// An ILogger implementation used for writing log messages. /// A new PowerShellVersionDetails instance. - public static async Task GetVersionDetailsAsync(ILogger logger, PowerShellExecutionService executionService, CancellationToken cancellationToken) + public static PowerShellVersionDetails GetVersionDetails(ILogger logger, PowerShell pwsh) { Version powerShellVersion = new Version(5, 0); string versionString = null; @@ -106,11 +105,9 @@ public static async Task GetVersionDetailsAsync(ILogge { var psVersionTableCommand = new PSCommand().AddScript("$PSVersionTable", useLocalScope: true); - Hashtable psVersionTable = (await executionService.ExecutePSCommandAsync( - psVersionTableCommand, - new PowerShellExecutionOptions(), - cancellationToken) - .ConfigureAwait(false)) + Hashtable psVersionTable = pwsh + .AddScript("$PSVersionTable", useLocalScope: true) + .InvokeAndClear() .FirstOrDefault(); if (psVersionTable != null) @@ -147,11 +144,9 @@ public static async Task GetVersionDetailsAsync(ILogge var procArchCommand = new PSCommand().AddScript("$env:PROCESSOR_ARCHITECTURE", useLocalScope: true); - string arch = (await executionService.ExecutePSCommandAsync( - procArchCommand, - new PowerShellExecutionOptions(), - cancellationToken) - .ConfigureAwait(false)) + string arch = pwsh + .AddScript("$env:PROCESSOR_ARCHITECTURE", useLocalScope: true) + .InvokeAndClear() .FirstOrDefault(); if (arch != null) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index 8bf2bfeda..4208311c5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -18,6 +18,7 @@ using SMA = System.Management.Automation; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging { @@ -88,13 +89,14 @@ public bool IsDscResourcePath(string scriptPath) } public static async Task GetDscCapabilityAsync( - PowerShellExecutionService executionService, ILogger logger, + IRunspaceInfo currentRunspace, + PowerShellExecutionService executionService, CancellationToken cancellationToken) { // DSC support is enabled only for Windows PowerShell. - if ((executionService.CurrentRunspace.PowerShellVersionDetails.Version.Major >= 6) && - (executionService.CurrentRunspace.RunspaceOrigin != RunspaceOrigin.DebuggedRunspace)) + if ((currentRunspace.PowerShellVersionDetails.Version.Major >= 6) && + (currentRunspace.RunspaceOrigin != RunspaceOrigin.DebuggedRunspace)) { return null; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs index ba591ac5d..7e5284bcb 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs @@ -1,6 +1,7 @@ using System; using System.Management.Automation; using System.Threading; +using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging { @@ -8,8 +9,6 @@ internal interface IPowerShellDebugContext { bool IsStopped { get; } - DscBreakpointCapability DscBreakpointCapability { get; } - DebuggerStopEventArgs LastStopEventArgs { get; } CancellationToken OnResumeCancellationToken { get; } @@ -31,5 +30,7 @@ internal interface IPowerShellDebugContext void BreakExecution(); void Abort(); + + Task GetDscBreakpointCapabilityAsync(CancellationToken cancellationToken); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index c1a637971..8bc9c0c25 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -5,24 +5,30 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging { - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; + using Microsoft.Extensions.Logging; + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using OmniSharp.Extensions.LanguageServer.Protocol.Server; + using System.Threading.Tasks; internal class PowerShellDebugContext : IPowerShellDebugContext { + private readonly ILogger _logger; + private readonly ILanguageServer _languageServer; - private readonly PowerShellContext _pwshContext; + private readonly EditorServicesConsolePSHost _psesHost; private readonly ConsoleReplRunner _consoleRepl; public PowerShellDebugContext( + ILoggerFactory loggerFactory, ILanguageServer languageServer, - PowerShellContext pwshContext, + EditorServicesConsolePSHost psesHost, ConsoleReplRunner consoleReplRunner) { + _logger = loggerFactory.CreateLogger(); _languageServer = languageServer; - _pwshContext = pwshContext; + _psesHost = psesHost; _consoleRepl = consoleReplRunner; } @@ -32,7 +38,7 @@ public PowerShellDebugContext( public DscBreakpointCapability DscBreakpointCapability => throw new NotImplementedException(); - public DebuggerStopEventArgs LastStopEventArgs { get; set; } + public DebuggerStopEventArgs LastStopEventArgs { get; private set; } public CancellationToken OnResumeCancellationToken => _debugLoopCancellationSource.Token; @@ -40,6 +46,11 @@ public PowerShellDebugContext( public event Action DebuggerResuming; public event Action BreakpointUpdated; + public Task GetDscBreakpointCapabilityAsync(CancellationToken cancellationToken) + { + return _psesHost.CurrentRunspace.GetDscBreakpointCapabilityAsync(_logger, _psesHost.ExecutionService, cancellationToken); + } + public void Abort() { SetDebugResuming(DebuggerResumeAction.Stop); @@ -47,7 +58,7 @@ public void Abort() public void BreakExecution() { - _pwshContext.CurrentRunspace.Debugger.SetDebuggerStepMode(enabled: true); + _psesHost.Runspace.Debugger.SetDebuggerStepMode(enabled: true); } public void Continue() @@ -101,20 +112,28 @@ public void SetDebuggerResumed() IsStopped = false; } + public void ProcessDebuggerResult(DebuggerCommandResults debuggerResult) + { + if (debuggerResult.ResumeAction != null) + { + RaiseDebuggerResumingEvent(new DebuggerResumingEventArgs(debuggerResult.ResumeAction.Value)); + } + } + + public void HandleBreakpointUpdated(BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) + { + BreakpointUpdated?.Invoke(this, breakpointUpdatedEventArgs); + } + private void RaiseDebuggerStoppedEvent() { // TODO: Send language server message to start debugger DebuggerStopped?.Invoke(this, LastStopEventArgs); } - public void RaiseDebuggerResumingEvent(DebuggerResumingEventArgs debuggerResumingEventArgs) + private void RaiseDebuggerResumingEvent(DebuggerResumingEventArgs debuggerResumingEventArgs) { DebuggerResuming?.Invoke(this, debuggerResumingEventArgs); } - - public void HandleBreakpointUpdated(BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) - { - BreakpointUpdated?.Invoke(this, breakpointUpdatedEventArgs); - } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs index 456825ac0..7fd4e382a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs @@ -1,25 +1,19 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; -using OmniSharp.Extensions.LanguageServer.Protocol.Server; using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Management.Automation; -using System.Management.Automation.Host; using System.Reflection; using System.Threading; using System.Threading.Tasks; -using SMA = System.Management.Automation; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution { - using System.Management.Automation.Runspaces; - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; internal class PipelineThreadExecutor { @@ -33,6 +27,12 @@ internal class PipelineThreadExecutor private readonly ILogger _logger; + private readonly EditorServicesConsolePSHost _psesHost; + + private readonly PowerShellDebugContext _debugContext; + + private readonly IReadLineProvider _readLineProvider; + private readonly HostStartupInfo _hostInfo; private readonly BlockingCollection _executionQueue; @@ -47,20 +47,19 @@ internal class PipelineThreadExecutor private readonly ReaderWriterLockSlim _taskProcessingLock; - private PowerShellContext _pwshContext; - - private PowerShellDebugContext _debugContext; - - private ConsoleReplRunner _consoleRepl; - private bool _runIdleLoop; public PipelineThreadExecutor( ILoggerFactory loggerFactory, - HostStartupInfo hostInfo) + HostStartupInfo hostInfo, + EditorServicesConsolePSHost psesHost, + IReadLineProvider readLineProvider) { _logger = loggerFactory.CreateLogger(); _hostInfo = hostInfo; + _psesHost = psesHost; + _debugContext = psesHost.DebugContext; + _readLineProvider = readLineProvider; _pipelineThread = new Thread(Run) { @@ -76,10 +75,11 @@ public Task QueueTask(SynchronousTask synchronousTask _executionQueue.Add(synchronousTask); return synchronousTask.Task; } - public void Start(PowerShellContext pwshContext, ConsoleReplRunner consoleReplRunner) + public void Start() { - _pwshContext = pwshContext; - _consoleRepl = consoleReplRunner; + // We need to override the idle handler here, + // since readline will be overridden by this point + _readLineProvider.ReadLine.TryOverrideIdleHandler(OnPowerShellIdle); _pipelineThread.Start(); } @@ -97,7 +97,6 @@ public void CancelCurrentTask() public void Dispose() { Stop(); - _pwshContext.Dispose(); } public IDisposable TakeTaskWriterLock() @@ -112,7 +111,7 @@ private void Run() public void RunPowerShellLoop(PowerShellFrameType powerShellFrameType) { - using (CancellationScope cancellationScope = _loopCancellationContext.EnterScope(_pwshContext.CurrentCancellationSource.Token, _consumerThreadCancellationSource.Token)) + using (CancellationScope cancellationScope = _loopCancellationContext.EnterScope(_psesHost.CurrentCancellationSource.Token, _consumerThreadCancellationSource.Token)) { try { @@ -122,7 +121,7 @@ public void RunPowerShellLoop(PowerShellFrameType powerShellFrameType) return; } - _consoleRepl?.PushNewReplTask(); + _psesHost.PushNewReplTask(); if ((powerShellFrameType & PowerShellFrameType.Debug) != 0) { @@ -135,14 +134,14 @@ public void RunPowerShellLoop(PowerShellFrameType powerShellFrameType) finally { _runIdleLoop = false; - _pwshContext.PopFrame(); + _psesHost.PopPowerShell(); } } } private void RunTopLevelConsumerLoop() { - using (CancellationScope cancellationScope = _loopCancellationContext.EnterScope(_pwshContext.CurrentCancellationSource.Token, _consumerThreadCancellationSource.Token)) + using (CancellationScope cancellationScope = _loopCancellationContext.EnterScope(_psesHost.CurrentCancellationSource.Token, _consumerThreadCancellationSource.Token)) { try { @@ -253,7 +252,7 @@ public void OnPowerShellIdle() } _runIdleLoop = true; - _pwshContext.PushNonInteractivePowerShell(); + _psesHost.PushNonInteractivePowerShell(); } private struct TaskProcessingWriterLockLifetime : IDisposable diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs index 7a0bce5bc..305850a96 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using System; using System.Threading; using SMA = System.Management.Automation; @@ -69,24 +70,24 @@ internal class SynchronousPSDelegateTask : SynchronousTask private readonly string _representation; - private readonly PowerShellContext _pwshContext; + private readonly EditorServicesConsolePSHost _psesHost; public SynchronousPSDelegateTask( ILogger logger, - PowerShellContext pwshContext, + EditorServicesConsolePSHost psesHost, Action action, string representation, CancellationToken cancellationToken) : base(logger, cancellationToken) { - _pwshContext = pwshContext; + _psesHost = psesHost; _action = action; _representation = representation; } public override object Run(CancellationToken cancellationToken) { - _action(_pwshContext.CurrentPowerShell, cancellationToken); + _action(_psesHost.CurrentPowerShell, cancellationToken); return null; } @@ -102,24 +103,24 @@ internal class SynchronousPSDelegateTask : SynchronousTask private readonly string _representation; - private readonly PowerShellContext _pwshContext; + private readonly EditorServicesConsolePSHost _psesHost; public SynchronousPSDelegateTask( ILogger logger, - PowerShellContext pwshContext, + EditorServicesConsolePSHost psesHost, Func func, string representation, CancellationToken cancellationToken) : base(logger, cancellationToken) { - _pwshContext = pwshContext; + _psesHost = psesHost; _func = func; _representation = representation; } public override TResult Run(CancellationToken cancellationToken) { - return _func(_pwshContext.CurrentPowerShell, cancellationToken); + return _func(_psesHost.CurrentPowerShell, cancellationToken); } public override string ToString() diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 3d38a791e..c45cfe421 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -1,11 +1,10 @@ using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation; -using System.Management.Automation.Host; using System.Management.Automation.Remoting; using System.Threading; using SMA = System.Management.Automation; @@ -16,7 +15,7 @@ internal class SynchronousPowerShellTask : SynchronousTask : SynchronousTask Run(CancellationToken cancellationToken) { - _pwsh = _pwshContext.CurrentPowerShell; - _psHost = _pwshContext.EditorServicesPSHost; + _pwsh = _psesHost.CurrentPowerShell; if (_executionOptions.WriteInputToHost) { - _psHost.UI.WriteLine(_psCommand.GetInvocationText()); + _psesHost.UI.WriteLine(_psCommand.GetInvocationText()); } return !_executionOptions.NoDebuggerExecution && _pwsh.Runspace.Debugger.InBreakpoint @@ -120,7 +116,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT { for (int i = args.Index; i < outputCollection.Count; i++) { - _psHost.UI.WriteLine(outputCollection[i].ToString()); + _psesHost.UI.WriteLine(outputCollection[i].ToString()); } }; } @@ -150,7 +146,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT { for (int i = args.Index; i < outputCollection.Count; i++) { - _psHost.UI.WriteLine(outputCollection[i].ToString()); + _psesHost.UI.WriteLine(outputCollection[i].ToString()); } }; @@ -168,7 +164,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT } } - _pwshContext.ProcessDebuggerResult(debuggerResult); + _psesHost.DebugContext.ProcessDebuggerResult(debuggerResult); // Optimisation to save wasted computation if we're going to throw the output away anyway if (_executionOptions.WriteOutputToHost) @@ -210,9 +206,9 @@ private void StopDebuggerIfRemoteDebugSessionFailed() { if (object.Equals(output?.BaseObject, false)) { - _pwshContext.ProcessDebuggerResult(new DebuggerCommandResults(DebuggerResumeAction.Stop, evaluatedByDebugger: true)); + _psesHost.DebugContext.ProcessDebuggerResult(new DebuggerCommandResults(DebuggerResumeAction.Stop, evaluatedByDebugger: true)); _logger.LogWarning("Cancelling debug session due to remote command cancellation causing the end of remote debugging session"); - _psHost.UI.WriteWarningLine("Debug session aborted by command cancellation. This is a known issue in the Windows PowerShell 5.1 remoting system."); + _psesHost.UI.WriteWarningLine("Debug session aborted by command cancellation. This is a known issue in the Windows PowerShell 5.1 remoting system."); } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs index 1d582b469..ac38f09d5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs @@ -11,6 +11,8 @@ using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; @@ -23,17 +25,20 @@ internal class GetVersionHandler : IGetVersionHandler private static readonly Version s_desiredPackageManagementVersion = new Version(1, 4, 6); private readonly ILogger _logger; + private IRunspaceContext _runspaceContext; private readonly PowerShellExecutionService _executionService; private readonly ILanguageServerFacade _languageServer; private readonly ConfigurationService _configurationService; public GetVersionHandler( ILoggerFactory factory, + IRunspaceContext runspaceContext, PowerShellExecutionService executionService, ILanguageServerFacade languageServer, ConfigurationService configurationService) { _logger = factory.CreateLogger(); + _runspaceContext = runspaceContext; _executionService = executionService; _languageServer = languageServer; _configurationService = configurationService; @@ -90,7 +95,7 @@ private async Task CheckPackageManagement() _logger.LogDebug("Old version of PackageManagement detected."); - if (_executionService.CurrentRunspace.Runspace.SessionStateProxy.LanguageMode != PSLanguageMode.FullLanguage) + if (_runspaceContext.CurrentRunspace.Runspace.SessionStateProxy.LanguageMode != PSLanguageMode.FullLanguage) { _languageServer.Window.ShowWarning("You have an older version of PackageManagement known to cause issues with the PowerShell extension. Please run the following command in a new Windows PowerShell session and then restart the PowerShell extension: `Install-Module PackageManagement -Force -AllowClobber -MinimumVersion 1.4.6`"); return; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index 76739bef1..df2f79788 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -1,36 +1,82 @@ using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; using System; +using System.Collections.Generic; using System.Globalization; +using System.Management.Automation; using System.Management.Automation.Host; +using System.Threading; +using System.Threading.Tasks; +using SMA = System.Management.Automation; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host { using System.Management.Automation.Runspaces; - internal class EditorServicesConsolePSHost : PSHost, IHostSupportsInteractiveSession + internal class EditorServicesConsolePSHost : PSHost, IHostSupportsInteractiveSession, IRunspaceContext { + public static EditorServicesConsolePSHost Create( + ILoggerFactory loggerFactory, + ILanguageServer languageServer, + HostStartupInfo hostInfo) + { + var host = new EditorServicesConsolePSHost(loggerFactory, languageServer, hostInfo); + host.PushInitialPowerShell(); + return host; + } + private readonly ILogger _logger; - private PowerShellContext _psContext; + private readonly Stack _psFrameStack; + + private readonly PowerShellFactory _psFactory; + + private readonly ConsoleReplRunner _consoleReplRunner; + + private readonly PipelineThreadExecutor _pipelineExecutor; + + private readonly HostStartupInfo _hostInfo; + + private readonly ReadLineProvider _readLineProvider; + + private string _localComputerName; + + private int _hostStarted = 0; public EditorServicesConsolePSHost( ILoggerFactory loggerFactory, - string name, - Version version, - PSHost internalHost, - ConsoleReadLine readline) + ILanguageServer languageServer, + HostStartupInfo hostInfo) { _logger = loggerFactory.CreateLogger(); - Name = name; - Version = version; - UI = new EditorServicesConsolePSHostUserInterface(loggerFactory, readline, internalHost.UI); + _psFrameStack = new Stack(); + _psFactory = new PowerShellFactory(loggerFactory, this); + _hostInfo = hostInfo; + Name = hostInfo.Name; + Version = hostInfo.Version; + + _readLineProvider = new ReadLineProvider(loggerFactory); + _pipelineExecutor = new PipelineThreadExecutor(loggerFactory, hostInfo, this, _readLineProvider); + ExecutionService = new PowerShellExecutionService(loggerFactory, this, _pipelineExecutor); + DebugContext = new PowerShellDebugContext(loggerFactory, languageServer, this, _consoleReplRunner); + UI = new EditorServicesConsolePSHostUserInterface(loggerFactory, _readLineProvider, hostInfo.PSHost.UI); + + if (hostInfo.ConsoleReplEnabled) + { + _consoleReplRunner = new ConsoleReplRunner(loggerFactory, this, _readLineProvider, ExecutionService); + } } - public override CultureInfo CurrentCulture => CultureInfo.CurrentCulture; + public override CultureInfo CurrentCulture => _hostInfo.PSHost.CurrentCulture; - public override CultureInfo CurrentUICulture => CultureInfo.CurrentUICulture; + public override CultureInfo CurrentUICulture => _hostInfo.PSHost.CurrentUICulture; public override Guid InstanceId { get; } = Guid.NewGuid(); @@ -40,46 +86,257 @@ public EditorServicesConsolePSHost( public override Version Version { get; } - public Runspace Runspace => _psContext.CurrentRunspace; + public bool IsRunspacePushed { get; private set; } + + internal bool IsRunning => _hostStarted != 0; + + public Runspace Runspace => CurrentPowerShell.Runspace; + + internal string InitialWorkingDirectory { get; private set; } + + internal PowerShellExecutionService ExecutionService { get; } + + internal PowerShellDebugContext DebugContext { get; } + + internal SMA.PowerShell CurrentPowerShell => CurrentFrame.PowerShell; + + internal RunspaceInfo CurrentRunspace => CurrentFrame.RunspaceInfo; + + IRunspaceInfo IRunspaceContext.CurrentRunspace => CurrentRunspace; + + internal CancellationTokenSource CurrentCancellationSource => CurrentFrame.CancellationTokenSource; - public bool IsRunspacePushed => _psContext.IsRunspacePushed; + private PowerShellContextFrame CurrentFrame => _psFrameStack.Peek(); public override void EnterNestedPrompt() { - _psContext.PushNestedPowerShell(); + PushPowerShell(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested); } public override void ExitNestedPrompt() { - _psContext.SetShouldExit(exitCode: null); + SetExit(); } public override void NotifyBeginApplication() { + // TODO: Work out what to do here } public override void NotifyEndApplication() { + // TODO: Work out what to do here } - public void PushRunspace(Runspace runspace) + public void PopRunspace() { - _psContext.PushPowerShell(runspace); + IsRunspacePushed = false; + SetExit(); } - public void PopRunspace() + public void PushRunspace(Runspace runspace) { - _psContext.SetShouldExit(exitCode: null); + IsRunspacePushed = true; + PushPowerShell(_psFactory.CreatePowerShellForRunspace(runspace), PowerShellFrameType.Remote); } public override void SetShouldExit(int exitCode) { - _psContext.SetShouldExit(exitCode); + SetExit(); } - internal void RegisterPowerShellContext(PowerShellContext psContext) + private void PushInitialPowerShell() { - _psContext = psContext; + SMA.PowerShell pwsh = _psFactory.CreateInitialPowerShell(_hostInfo, _readLineProvider); + var runspaceInfo = RunspaceInfo.CreateFromLocalPowerShell(_logger, pwsh); + _localComputerName = runspaceInfo.SessionDetails.ComputerName; + PushPowerShell(new PowerShellContextFrame(pwsh, runspaceInfo, PowerShellFrameType.Normal)); } + + internal void PushNonInteractivePowerShell() + { + PushPowerShell(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested | PowerShellFrameType.NonInteractive); + } + + internal void CancelCurrentPrompt() + { + _consoleReplRunner?.CancelCurrentPrompt(); + } + + internal void PushNewReplTask() + { + _consoleReplRunner?.PushNewReplTask(); + } + + internal Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cancellationToken) + { + InitialWorkingDirectory = path; + + return ExecutionService.ExecutePSCommandAsync( + new PSCommand().AddCommand("Set-Location").AddParameter("LiteralPath", path), + new PowerShellExecutionOptions(), + cancellationToken); + } + + public async Task StartAsync(HostStartOptions hostStartOptions, CancellationToken cancellationToken) + { + _logger.LogInformation("Host starting"); + if (Interlocked.Exchange(ref _hostStarted, 1) != 0) + { + _logger.LogDebug("Host start requested after already started"); + return; + } + + _pipelineExecutor.Start(); + + if (hostStartOptions.LoadProfiles) + { + await ExecutionService.ExecuteDelegateAsync((pwsh, delegateCancellation) => + { + pwsh.LoadProfiles(_hostInfo.ProfilePaths); + }, "LoadProfiles", cancellationToken).ConfigureAwait(false); + + _logger.LogInformation("Profiles loaded"); + } + + if (hostStartOptions.InitialWorkingDirectory != null) + { + await SetInitialWorkingDirectoryAsync(hostStartOptions.InitialWorkingDirectory, CancellationToken.None).ConfigureAwait(false); + } + + _consoleReplRunner?.StartRepl(); + } + + private void SetExit() + { + if (_psFrameStack.Count <= 1) + { + return; + } + + _pipelineExecutor.IsExiting = true; + + if ((CurrentFrame.FrameType & PowerShellFrameType.NonInteractive) == 0) + { + _consoleReplRunner?.SetReplPop(); + } + } + + private void PushPowerShell(SMA.PowerShell pwsh, PowerShellFrameType frameType) + { + // TODO: Improve runspace origin detection here + RunspaceOrigin runspaceOrigin = pwsh.Runspace.RunspaceIsRemote ? RunspaceOrigin.EnteredProcess : RunspaceOrigin.Local; + var runspaceInfo = RunspaceInfo.CreateFromPowerShell(_logger, pwsh, runspaceOrigin, _localComputerName); + PushPowerShell(new PowerShellContextFrame(pwsh, runspaceInfo, frameType)); + } + + private void PushPowerShell(PowerShellContextFrame frame) + { + if (_psFrameStack.Count > 0) + { + RemoveRunspaceEventHandlers(CurrentFrame.PowerShell.Runspace); + } + AddRunspaceEventHandlers(frame.PowerShell.Runspace); + + _psFrameStack.Push(frame); + + _pipelineExecutor.RunPowerShellLoop(frame.FrameType); + } + + internal void PopPowerShell() + { + _pipelineExecutor.IsExiting = false; + PowerShellContextFrame frame = _psFrameStack.Pop(); + try + { + RemoveRunspaceEventHandlers(frame.PowerShell.Runspace); + if (_psFrameStack.Count > 0) + { + AddRunspaceEventHandlers(CurrentPowerShell.Runspace); + } + } + finally + { + frame.Dispose(); + } + } + + private void AddRunspaceEventHandlers(Runspace runspace) + { + runspace.Debugger.DebuggerStop += OnDebuggerStopped; + runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; + runspace.StateChanged += OnRunspaceStateChanged; + } + + private void RemoveRunspaceEventHandlers(Runspace runspace) + { + runspace.Debugger.DebuggerStop -= OnDebuggerStopped; + runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; + runspace.StateChanged -= OnRunspaceStateChanged; + } + + private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) + { + DebugContext.SetDebuggerStopped(debuggerStopEventArgs); + try + { + CurrentPowerShell.WaitForRemoteOutputIfNeeded(); + PushPowerShell(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Debug | PowerShellFrameType.Nested); + CurrentPowerShell.ResumeRemoteOutputIfNeeded(); + } + finally + { + DebugContext.SetDebuggerResumed(); + } + } + + private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) + { + DebugContext.HandleBreakpointUpdated(breakpointUpdatedEventArgs); + } + + private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) + { + if (!runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) + { + PopOrReinitializeRunspace(); + } + } + + private void PopOrReinitializeRunspace() + { + _consoleReplRunner?.SetReplPop(); + _pipelineExecutor.CancelCurrentTask(); + + RunspaceStateInfo oldRunspaceState = CurrentPowerShell.Runspace.RunspaceStateInfo; + using (_pipelineExecutor.TakeTaskWriterLock()) + { + while (_psFrameStack.Count > 0 + && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) + { + PopPowerShell(); + } + + if (_psFrameStack.Count == 0) + { + // If our main runspace was corrupted, + // we must re-initialize our state. + // TODO: Use runspace.ResetRunspaceState() here instead + PushInitialPowerShell(); + + _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." + + " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs."); + UI.WriteErrorLine("The main runspace encountered an error and has been reinitialized. See the PowerShell extension logs for more details."); + } + else + { + _logger.LogError($"Current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was popped."); + UI.WriteErrorLine($"The current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}'." + + " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior." + + " The session is now returning to the previous runspace."); + } + } + } + } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs index 55f864dcc..781af6733 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs @@ -15,7 +15,7 @@ internal class EditorServicesConsolePSHostUserInterface : PSHostUserInterface { private readonly ILogger _logger; - private readonly ConsoleReadLine _readLine; + private readonly IReadLineProvider _readLineProvider; private readonly PSHostUserInterface _underlyingHostUI; @@ -23,11 +23,11 @@ internal class EditorServicesConsolePSHostUserInterface : PSHostUserInterface public EditorServicesConsolePSHostUserInterface( ILoggerFactory loggerFactory, - ConsoleReadLine readLine, + IReadLineProvider readLineProvider, PSHostUserInterface underlyingHostUI) { _logger = loggerFactory.CreateLogger(); - _readLine = readLine; + _readLineProvider = readLineProvider; _underlyingHostUI = underlyingHostUI; RawUI = new EditorServicesConsolePSHostRawUserInterface(loggerFactory, underlyingHostUI.RawUI); @@ -84,12 +84,12 @@ public override PSCredential PromptForCredential(string caption, string message, public override string ReadLine() { - return _readLine.ReadCommandLineAsync(CancellationToken.None).GetAwaiter().GetResult(); + return _readLineProvider.ReadLine.ReadLineAsync(CancellationToken.None).GetAwaiter().GetResult(); } public override SecureString ReadLineAsSecureString() { - return _readLine.ReadSecureLineAsync(CancellationToken.None).GetAwaiter().GetResult(); + return _readLineProvider.ReadLine.ReadSecureLineAsync(CancellationToken.None).GetAwaiter().GetResult(); } public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs new file mode 100644 index 000000000..7c71d446e --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs @@ -0,0 +1,10 @@ + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host +{ + internal struct HostStartOptions + { + public bool LoadProfiles { get; set; } + + public string InitialWorkingDirectory { get; set; } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/IReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/IReadLine.cs deleted file mode 100644 index 47cd57919..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/IReadLine.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host -{ - internal interface IReadLine - { - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PowerShellFactory.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PowerShellFactory.cs new file mode 100644 index 000000000..5e7924fc5 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PowerShellFactory.cs @@ -0,0 +1,120 @@ +using System; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host +{ + using Microsoft.Extensions.Logging; + using Microsoft.PowerShell.EditorServices.Hosting; + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; + using Microsoft.PowerShell.EditorServices.Utility; + using System.Collections.Generic; + using System.IO; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using System.Reflection; + + internal class PowerShellFactory + { + private static readonly string s_commandsModulePath = Path.GetFullPath( + Path.Combine( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + "../../Commands/PowerShellEditorServices.Commands.psd1")); + + private readonly ILoggerFactory _loggerFactory; + + private readonly ILogger _logger; + + private readonly EditorServicesConsolePSHost _psesHost; + + public PowerShellFactory( + ILoggerFactory loggerFactory, + EditorServicesConsolePSHost psHost) + { + _loggerFactory = loggerFactory; + _logger = loggerFactory.CreateLogger(); + _psesHost = psHost; + } + + public PowerShell CreateNestedPowerShell(RunspaceInfo currentRunspace) + { + if (currentRunspace.RunspaceOrigin != RunspaceOrigin.Local) + { + var remotePwsh = PowerShell.Create(); + remotePwsh.Runspace = currentRunspace.Runspace; + return remotePwsh; + } + + // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild + // This means it throws due to the parent pipeline not running... + // So we must use the RunspaceMode.CurrentRunspace option on PowerShell.Create() instead + var pwsh = PowerShell.Create(RunspaceMode.CurrentRunspace); + pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; + return pwsh; + } + + public PowerShell CreatePowerShellForRunspace(Runspace runspace) + { + var pwsh = PowerShell.Create(); + pwsh.Runspace = runspace; + return pwsh; + } + + public PowerShell CreateInitialPowerShell( + HostStartupInfo hostStartupInfo, + ReadLineProvider readLineProvider) + { + Runspace runspace = CreateInitialRunspace(hostStartupInfo.LanguageMode); + + var pwsh = PowerShell.Create(); + pwsh.Runspace = runspace; + + var engineIntrinsics = (EngineIntrinsics)runspace.SessionStateProxy.GetVariable("ExecutionContext"); + + if (hostStartupInfo.ConsoleReplEnabled && !hostStartupInfo.UsesLegacyReadLine) + { + var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, pwsh); + var readLine = new ConsoleReadLine(psrlProxy, _psesHost, _psesHost.ExecutionService, engineIntrinsics); + readLineProvider.OverrideReadLine(readLine); + } + + if (VersionUtils.IsWindows) + { + pwsh.SetCorrectExecutionPolicy(_logger); + } + + pwsh.ImportModule(s_commandsModulePath); + + if (hostStartupInfo.AdditionalModules != null && hostStartupInfo.AdditionalModules.Count > 0) + { + foreach (string module in hostStartupInfo.AdditionalModules) + { + pwsh.ImportModule(module); + } + } + + return pwsh; + } + + private Runspace CreateInitialRunspace(PSLanguageMode languageMode) + { + InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" + ? InitialSessionState.CreateDefault() + : InitialSessionState.CreateDefault2(); + + iss.LanguageMode = languageMode; + + Runspace runspace = RunspaceFactory.CreateRunspace(_psesHost, iss); + + runspace.SetApartmentStateToSta(); + runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; + + runspace.Open(); + + Runspace.DefaultRunspace = runspace; + + return runspace; + } + + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 7903d8b66..888a1ccd8 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -4,6 +4,7 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using System; @@ -15,54 +16,24 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell { - internal class PowerShellExecutionService : IDisposable + internal class PowerShellExecutionService { - public static PowerShellExecutionService CreateAndStart( - ILoggerFactory loggerFactory, - ILanguageServer languageServer, - HostStartupInfo hostInfo) - { - var executionService = new PowerShellExecutionService( - loggerFactory, - hostInfo, - languageServer); - - executionService._pipelineExecutor.Start(executionService._pwshContext, executionService._consoleRepl); - executionService._consoleRepl?.StartRepl(); - - return executionService; - } - private readonly ILogger _logger; - private readonly PowerShellContext _pwshContext; + private readonly EditorServicesConsolePSHost _psesHost; private readonly PipelineThreadExecutor _pipelineExecutor; - private readonly ConsoleReplRunner _consoleRepl; - - private PowerShellExecutionService( + public PowerShellExecutionService( ILoggerFactory loggerFactory, - HostStartupInfo hostInfo, - ILanguageServer languageServer) + EditorServicesConsolePSHost psesHost, + PipelineThreadExecutor pipelineExecutor) { _logger = loggerFactory.CreateLogger(); - _pipelineExecutor = new PipelineThreadExecutor(loggerFactory, hostInfo); - _pwshContext = new PowerShellContext(loggerFactory, hostInfo, languageServer, this, _pipelineExecutor); - - // TODO: Fix this - if (hostInfo.ConsoleReplEnabled) - { - _consoleRepl = new ConsoleReplRunner(loggerFactory, _pwshContext, this); - } + _psesHost = psesHost; + _pipelineExecutor = pipelineExecutor; } - public IPowerShellDebugContext DebugContext => _pwshContext.DebugContext; - - public IRunspaceInfo CurrentRunspace => _pwshContext.RunspaceContext; - - public IPowerShellContext PowerShellContext => _pwshContext; - public Action RunspaceChanged; public Task ExecuteDelegateAsync( @@ -70,7 +41,7 @@ public Task ExecuteDelegateAsync( string representation, CancellationToken cancellationToken) { - return QueueTask(new SynchronousPSDelegateTask(_logger, _pwshContext, func, representation, cancellationToken)); + return QueueTask(new SynchronousPSDelegateTask(_logger, _psesHost, func, representation, cancellationToken)); } public Task ExecuteDelegateAsync( @@ -78,7 +49,7 @@ public Task ExecuteDelegateAsync( string representation, CancellationToken cancellationToken) { - return QueueTask(new SynchronousPSDelegateTask(_logger, _pwshContext, action, representation, cancellationToken)); + return QueueTask(new SynchronousPSDelegateTask(_logger, _psesHost, action, representation, cancellationToken)); } public Task ExecuteDelegateAsync( @@ -104,14 +75,14 @@ public Task> ExecutePSCommandAsync( { Task> result = QueueTask(new SynchronousPowerShellTask( _logger, - _pwshContext, + _psesHost, psCommand, executionOptions, cancellationToken)); if (executionOptions.InterruptCommandPrompt) { - _consoleRepl.CancelCurrentPrompt(); + _psesHost.CancelCurrentPrompt(); } return result; @@ -127,13 +98,6 @@ public void CancelCurrentTask() _pipelineExecutor.CancelCurrentTask(); } - public void Dispose() - { - _pipelineExecutor.Dispose(); - _consoleRepl.Dispose(); - _pwshContext.Dispose(); - } - private Task QueueTask(SynchronousTask task) => _pipelineExecutor.QueueTask(task); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs new file mode 100644 index 000000000..f8f77f707 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs @@ -0,0 +1,8 @@ + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace +{ + internal interface IRunspaceContext + { + IRunspaceInfo CurrentRunspace { get; } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs index f01751c70..8f44e3e5d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs @@ -1,6 +1,6 @@  using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; +using System.Threading.Tasks; using SMA = System.Management.Automation.Runspaces; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace @@ -9,20 +9,12 @@ internal interface IRunspaceInfo { RunspaceOrigin RunspaceOrigin { get; } + bool IsOnRemoteMachine { get; } + PowerShellVersionDetails PowerShellVersionDetails { get; } SessionDetails SessionDetails { get; } SMA.Runspace Runspace { get; } - - DscBreakpointCapability DscBreakpointCapability { get; } - } - - internal static class RunspaceInfoExtensions - { - public static bool IsRemote(this IRunspaceInfo runspaceInfo) - { - return runspaceInfo.RunspaceOrigin != RunspaceOrigin.Local; - } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs index 57503320a..f59d799f6 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs @@ -1,23 +1,66 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; -using SMA = System.Management.Automation.Runspaces; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace { + using System.Management.Automation.Runspaces; + using System.Management.Automation; + using Microsoft.Extensions.Logging; + using System.Threading.Tasks; + using System.Threading; + using System; + internal class RunspaceInfo : IRunspaceInfo { + public static RunspaceInfo CreateFromLocalPowerShell( + ILogger logger, + PowerShell pwsh) + { + var psVersionDetails = PowerShellVersionDetails.GetVersionDetails(logger, pwsh); + var sessionDetails = SessionDetails.GetFromPowerShell(pwsh); + + return new RunspaceInfo( + pwsh.Runspace, + RunspaceOrigin.Local, + psVersionDetails, + sessionDetails, + isRemote: false); + } + + public static RunspaceInfo CreateFromPowerShell( + ILogger logger, + PowerShell pwsh, + RunspaceOrigin runspaceOrigin, + string localComputerName) + { + var psVersionDetails = PowerShellVersionDetails.GetVersionDetails(logger, pwsh); + var sessionDetails = SessionDetails.GetFromPowerShell(pwsh); + + bool isOnLocalMachine = string.Equals(sessionDetails.ComputerName, localComputerName, StringComparison.OrdinalIgnoreCase) + || string.Equals(sessionDetails.ComputerName, "localhost", StringComparison.OrdinalIgnoreCase); + + return new RunspaceInfo( + pwsh.Runspace, + runspaceOrigin, + psVersionDetails, + sessionDetails, + isRemote: !isOnLocalMachine); + } + + private DscBreakpointCapability _dscBreakpointCapability; + public RunspaceInfo( - SMA.Runspace runspace, + Runspace runspace, RunspaceOrigin origin, PowerShellVersionDetails powerShellVersionDetails, SessionDetails sessionDetails, - DscBreakpointCapability dscBreakpointCapability) + bool isRemote) { Runspace = runspace; RunspaceOrigin = origin; SessionDetails = sessionDetails; PowerShellVersionDetails = powerShellVersionDetails; - DscBreakpointCapability = dscBreakpointCapability; + IsOnRemoteMachine = isRemote; } public RunspaceOrigin RunspaceOrigin { get; } @@ -26,8 +69,25 @@ public RunspaceInfo( public SessionDetails SessionDetails { get; } - public SMA.Runspace Runspace { get; } + public Runspace Runspace { get; } + + public bool IsOnRemoteMachine { get; } - public DscBreakpointCapability DscBreakpointCapability { get; } + public async Task GetDscBreakpointCapabilityAsync( + ILogger logger, + PowerShellExecutionService executionService, + CancellationToken cancellationToken) + { + if (_dscBreakpointCapability == null) + { + _dscBreakpointCapability = await DscBreakpointCapability.GetDscCapabilityAsync( + logger, + this, + executionService, + cancellationToken); + } + + return _dscBreakpointCapability; + } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs index a58a4b6bb..733f31d9a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs @@ -8,25 +8,38 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace { + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; + using System.Linq; + using System.Management.Automation; + /// /// Provides details about the current PowerShell session. /// internal class SessionDetails { - /// - /// Gets the process ID of the current process. - /// - public int? ProcessId { get; private set; } + private const string Property_ComputerName = "computerName"; + private const string Property_ProcessId = "processId"; + private const string Property_InstanceId = "instanceId"; /// - /// Gets the name of the current computer. + /// Gets the PSCommand that gathers details from the + /// current session. /// - public string ComputerName { get; private set; } + /// A PSCommand used to gather session details. + public static SessionDetails GetFromPowerShell(PowerShell pwsh) + { + Hashtable detailsObject = pwsh + .AddScript( + $"@{{ '{Property_ComputerName}' = if ([Environment]::MachineName) {{[Environment]::MachineName}} else {{'localhost'}}; '{Property_ProcessId}' = $PID; '{Property_InstanceId}' = $host.InstanceId }}", + useLocalScope: true) + .InvokeAndClear() + .FirstOrDefault(); - /// - /// Gets the current PSHost instance ID. - /// - public Guid? InstanceId { get; private set; } + return new SessionDetails( + (int)detailsObject[Property_ProcessId], + (string)detailsObject[Property_ComputerName], + (Guid?)detailsObject[Property_InstanceId]); + } /// /// Creates an instance of SessionDetails using the information @@ -34,7 +47,7 @@ internal class SessionDetails /// PSCommand returned by GetDetailsCommand. /// /// - protected SessionDetails( + public SessionDetails( int processId, string computerName, Guid? instanceId) @@ -45,18 +58,18 @@ protected SessionDetails( } /// - /// Gets the PSCommand that gathers details from the - /// current session. + /// Gets the process ID of the current process. /// - /// A PSCommand used to gather session details. - public static PSCommand GetDetailsCommand() - { - PSCommand infoCommand = new PSCommand(); - infoCommand.AddScript( - "@{ 'computerName' = if ([Environment]::MachineName) {[Environment]::MachineName} else {'localhost'}; 'processId' = $PID; 'instanceId' = $host.InstanceId }", - useLocalScope: true); + public int? ProcessId { get; } - return infoCommand; - } + /// + /// Gets the name of the current computer. + /// + public string ComputerName { get; } + + /// + /// Gets the current PSHost instance ID. + /// + public Guid? InstanceId { get; } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs index 8e8c61546..c415420d2 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs @@ -64,10 +64,11 @@ internal static class CommandHelpers /// A CommandInfo object with details about the specified command. public static async Task GetCommandInfoAsync( string commandName, + IRunspaceInfo currentRunspace, PowerShellExecutionService executionService) { // This mechanism only works in-process - if (executionService.CurrentRunspace.IsRemote()) + if (currentRunspace.RunspaceOrigin != RunspaceOrigin.Local) { return null; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs index 423285024..89c54cbe7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs @@ -1,39 +1,41 @@ using System; using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Management.Automation; -using System.Management.Automation.Runspaces; using System.Reflection; -using System.Runtime.CompilerServices; using System.Text; -using SMA = System.Management.Automation; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility { + using Microsoft.Extensions.Logging; + using Microsoft.PowerShell.EditorServices.Hosting; + using System.Collections.Generic; + using System.IO; + using System.Management.Automation; + using System.Runtime.CompilerServices; + internal static class PowerShellExtensions { - private static readonly Action s_waitForServicingComplete; + private static readonly Action s_waitForServicingComplete; - private static readonly Action s_suspendIncomingData; + private static readonly Action s_suspendIncomingData; - private static readonly Action s_resumeIncomingData; + private static readonly Action s_resumeIncomingData; static PowerShellExtensions() { - s_waitForServicingComplete = (Action)Delegate.CreateDelegate( - typeof(Action), - typeof(SMA.PowerShell).GetMethod("WaitForServicingComplete", BindingFlags.Instance | BindingFlags.NonPublic)); + s_waitForServicingComplete = (Action)Delegate.CreateDelegate( + typeof(Action), + typeof(PowerShell).GetMethod("WaitForServicingComplete", BindingFlags.Instance | BindingFlags.NonPublic)); - s_suspendIncomingData = (Action)Delegate.CreateDelegate( - typeof(Action), - typeof(SMA.PowerShell).GetMethod("SuspendIncomingData", BindingFlags.Instance | BindingFlags.NonPublic)); + s_suspendIncomingData = (Action)Delegate.CreateDelegate( + typeof(Action), + typeof(PowerShell).GetMethod("SuspendIncomingData", BindingFlags.Instance | BindingFlags.NonPublic)); - s_resumeIncomingData = (Action)Delegate.CreateDelegate( - typeof(Action), - typeof(SMA.PowerShell).GetMethod("ResumeIncomingData", BindingFlags.Instance | BindingFlags.NonPublic)); + s_resumeIncomingData = (Action)Delegate.CreateDelegate( + typeof(Action), + typeof(PowerShell).GetMethod("ResumeIncomingData", BindingFlags.Instance | BindingFlags.NonPublic)); } - public static Collection InvokeAndClear(this SMA.PowerShell pwsh) + public static Collection InvokeAndClear(this PowerShell pwsh) { try { @@ -45,7 +47,7 @@ public static Collection InvokeAndClear(this SMA.PowerShell pw } } - public static void InvokeAndClear(this SMA.PowerShell pwsh) + public static void InvokeAndClear(this PowerShell pwsh) { try { @@ -57,19 +59,19 @@ public static void InvokeAndClear(this SMA.PowerShell pwsh) } } - public static Collection InvokeCommand(this SMA.PowerShell pwsh, PSCommand psCommand) + public static Collection InvokeCommand(this PowerShell pwsh, PSCommand psCommand) { pwsh.Commands = psCommand; return pwsh.InvokeAndClear(); } - public static void InvokeCommand(this SMA.PowerShell pwsh, PSCommand psCommand) + public static void InvokeCommand(this PowerShell pwsh, PSCommand psCommand) { pwsh.Commands = psCommand; pwsh.InvokeAndClear(); } - public static void WaitForRemoteOutputIfNeeded(this SMA.PowerShell pwsh) + public static void WaitForRemoteOutputIfNeeded(this PowerShell pwsh) { if (!pwsh.Runspace.RunspaceIsRemote) { @@ -80,7 +82,7 @@ public static void WaitForRemoteOutputIfNeeded(this SMA.PowerShell pwsh) s_suspendIncomingData(pwsh); } - public static void ResumeRemoteOutputIfNeeded(this SMA.PowerShell pwsh) + public static void ResumeRemoteOutputIfNeeded(this PowerShell pwsh) { if (!pwsh.Runspace.RunspaceIsRemote) { @@ -90,7 +92,86 @@ public static void ResumeRemoteOutputIfNeeded(this SMA.PowerShell pwsh) s_resumeIncomingData(pwsh); } - public static string GetErrorString(this SMA.PowerShell pwsh) + public static void SetCorrectExecutionPolicy(this PowerShell pwsh, ILogger logger) + { + // We want to get the list hierarchy of execution policies + // Calling the cmdlet is the simplest way to do that + IReadOnlyList policies = pwsh + .AddCommand("Microsoft.PowerShell.Security\\Get-ExecutionPolicy") + .AddParameter("-List") + .InvokeAndClear(); + + // The policies come out in the following order: + // - MachinePolicy + // - UserPolicy + // - Process + // - CurrentUser + // - LocalMachine + // We want to ignore policy settings, since we'll already have those anyway. + // Then we need to look at the CurrentUser setting, and then the LocalMachine setting. + // + // Get-ExecutionPolicy -List emits PSObjects with Scope and ExecutionPolicy note properties + // set to expected values, so we must sift through those. + + ExecutionPolicy policyToSet = ExecutionPolicy.Bypass; + var currentUserPolicy = (ExecutionPolicy)policies[policies.Count - 2].Members["ExecutionPolicy"].Value; + if (currentUserPolicy != ExecutionPolicy.Undefined) + { + policyToSet = currentUserPolicy; + } + else + { + var localMachinePolicy = (ExecutionPolicy)policies[policies.Count - 1].Members["ExecutionPolicy"].Value; + if (localMachinePolicy != ExecutionPolicy.Undefined) + { + policyToSet = localMachinePolicy; + } + } + + // If there's nothing to do, save ourselves a PowerShell invocation + if (policyToSet == ExecutionPolicy.Bypass) + { + logger.LogTrace("Execution policy already set to Bypass. Skipping execution policy set"); + return; + } + + // Finally set the inherited execution policy + logger.LogTrace("Setting execution policy to {Policy}", policyToSet); + try + { + pwsh.AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") + .AddParameter("Scope", ExecutionPolicyScope.Process) + .AddParameter("ExecutionPolicy", policyToSet) + .AddParameter("Force") + .InvokeAndClear(); + } + catch (CmdletInvocationException e) + { + logger.LogError(e, "Error occurred calling 'Set-ExecutionPolicy -Scope Process -ExecutionPolicy {Policy} -Force'", policyToSet); + } + } + + public static void LoadProfiles(this PowerShell pwsh, ProfilePathInfo profilePaths) + { + var profileVariable = new PSObject(); + + pwsh.AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.AllUsersAllHosts), profilePaths.AllUsersAllHosts); + pwsh.AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.AllUsersCurrentHost), profilePaths.AllUsersCurrentHost); + pwsh.AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.CurrentUserAllHosts), profilePaths.CurrentUserAllHosts); + pwsh.AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.CurrentUserCurrentHost), profilePaths.CurrentUserCurrentHost); + + pwsh.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); + } + + public static void ImportModule(this PowerShell pwsh, string moduleNameOrPath) + { + pwsh.AddCommand("Microsoft.PowerShell.Core\\Import-Module") + .AddParameter("-Name", moduleNameOrPath) + .InvokeAndClear(); + } + + + public static string GetErrorString(this PowerShell pwsh) { var sb = new StringBuilder(capacity: 1024) .Append("Execution of the following command(s) completed with errors:") @@ -108,6 +189,20 @@ public static string GetErrorString(this SMA.PowerShell pwsh) return sb.ToString(); } + private static void AddProfileMemberAndLoadIfExists(this PowerShell pwsh, PSObject profileVariable, string profileName, string profilePath) + { + profileVariable.Members.Add(new PSNoteProperty(profileName, profilePath)); + + if (File.Exists(profilePath)) + { + var psCommand = new PSCommand() + .AddScript(profilePath, useLocalScope: false) + .AddOutputCommand(); + + pwsh.InvokeCommand(psCommand); + } + } + private static StringBuilder AddErrorString(this StringBuilder sb, ErrorRecord error, int errorIndex) { sb.Append("Error #").Append(errorIndex).Append(':').AppendLine() diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs index 985489b68..c3ad77e22 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs @@ -5,6 +5,7 @@ using System.Management.Automation; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; namespace Microsoft.PowerShell.EditorServices.Services.Symbols @@ -39,6 +40,7 @@ internal class SymbolDetails internal static async Task CreateAsync( SymbolReference symbolReference, + IRunspaceInfo currentRunspace, PowerShellExecutionService executionService) { SymbolDetails symbolDetails = new SymbolDetails @@ -51,6 +53,7 @@ internal static async Task CreateAsync( case SymbolType.Function: CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( symbolReference.SymbolName, + currentRunspace, executionService).ConfigureAwait(false); if (commandInfo != null) diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index ef7568c1f..0d3922294 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -16,6 +16,8 @@ using Microsoft.PowerShell.EditorServices.CodeLenses; using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Services.TextDocument; @@ -32,6 +34,7 @@ internal class SymbolsService #region Private Fields private readonly ILogger _logger; + private readonly IRunspaceContext _runspaceContext; private readonly PowerShellExecutionService _executionService; private readonly WorkspaceService _workspaceService; @@ -49,11 +52,13 @@ internal class SymbolsService /// An ILoggerFactory implementation used for writing log messages. public SymbolsService( ILoggerFactory factory, + IRunspaceContext runspaceContext, PowerShellExecutionService executionService, WorkspaceService workspaceService, ConfigurationService configurationService) { _logger = factory.CreateLogger(); + _runspaceContext = runspaceContext; _executionService = executionService; _workspaceService = workspaceService; @@ -320,6 +325,7 @@ public async Task FindSymbolDetailsAtLocationAsync( symbolReference.FilePath = scriptFile.FilePath; SymbolDetails symbolDetails = await SymbolDetails.CreateAsync( symbolReference, + _runspaceContext.CurrentRunspace, _executionService).ConfigureAwait(false); return symbolDetails; @@ -355,6 +361,7 @@ public async Task FindParameterSetsInFileAsync( CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( foundSymbol.SymbolName, + _runspaceContext.CurrentRunspace, _executionService).ConfigureAwait(false); if (commandInfo == null) @@ -471,6 +478,7 @@ public async Task GetDefinitionOfSymbolAsync( CommandInfo cmdInfo = await CommandHelpers.GetCommandInfoAsync( foundSymbol.SymbolName, + _runspaceContext.CurrentRunspace, _executionService).ConfigureAwait(false); foundDefinition = diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs index 804efaba6..ca8f56eab 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Services.TextDocument; @@ -25,6 +26,7 @@ internal class PsesCompletionHandler : ICompletionHandler, ICompletionResolveHan { const int DefaultWaitTimeoutMilliseconds = 5000; private readonly ILogger _logger; + private readonly IRunspaceContext _runspaceContext; private readonly PowerShellExecutionService _executionService; private readonly WorkspaceService _workspaceService; private CompletionResults _mostRecentCompletions; @@ -37,10 +39,12 @@ internal class PsesCompletionHandler : ICompletionHandler, ICompletionResolveHan public PsesCompletionHandler( ILoggerFactory factory, + IRunspaceContext runspaceContext, PowerShellExecutionService executionService, WorkspaceService workspaceService) { _logger = factory.CreateLogger(); + _runspaceContext = runspaceContext; _executionService = executionService; _workspaceService = workspaceService; } @@ -109,6 +113,7 @@ public async Task Handle(CompletionItem request, CancellationTok CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( request.Label, + _runspaceContext.CurrentRunspace, _executionService).ConfigureAwait(false); if (commandInfo != null) diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index 612a830ab..e4bad4f17 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -19,6 +19,7 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; using System.IO; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; namespace Microsoft.PowerShell.EditorServices.Handlers @@ -28,8 +29,7 @@ internal class PsesConfigurationHandler : DidChangeConfigurationHandlerBase private readonly ILogger _logger; private readonly WorkspaceService _workspaceService; private readonly ConfigurationService _configurationService; - private readonly PowerShellContextService _powerShellContextService; - private readonly PowerShellExecutionService _executionService; + private readonly EditorServicesConsolePSHost _psesHost; private readonly ILanguageServerFacade _languageServer; private DidChangeConfigurationCapability _capability; private bool _profilesLoaded; @@ -41,16 +41,14 @@ public PsesConfigurationHandler( WorkspaceService workspaceService, AnalysisService analysisService, ConfigurationService configurationService, - PowerShellContextService powerShellContextService, - PowerShellExecutionService executionService, - ILanguageServerFacade languageServer) + ILanguageServerFacade languageServer, + EditorServicesConsolePSHost psesHost) { _logger = factory.CreateLogger(); _workspaceService = workspaceService; _configurationService = configurationService; - _powerShellContextService = powerShellContextService; - _executionService = executionService; _languageServer = languageServer; + _psesHost = psesHost; ConfigurationUpdated += analysisService.OnConfigurationUpdated; } @@ -84,18 +82,21 @@ public override async Task Handle(DidChangeConfigurationParams request, Ca && Directory.Exists(_configurationService.CurrentSettings.Cwd)) { this._logger.LogTrace($"Setting CWD (from config) to {_configurationService.CurrentSettings.Cwd}"); - await _powerShellContextService.SetWorkingDirectoryAsync( + await _psesHost.SetInitialWorkingDirectoryAsync( _configurationService.CurrentSettings.Cwd, - isPathAlreadyEscaped: false).ConfigureAwait(false); + CancellationToken.None).ConfigureAwait(false); - } else if (_workspaceService.WorkspacePath != null + } + else if (_workspaceService.WorkspacePath != null && Directory.Exists(_workspaceService.WorkspacePath)) { this._logger.LogTrace($"Setting CWD (from workspace) to {_workspaceService.WorkspacePath}"); - await _powerShellContextService.SetWorkingDirectoryAsync( + await _psesHost.SetInitialWorkingDirectoryAsync( _workspaceService.WorkspacePath, - isPathAlreadyEscaped: false).ConfigureAwait(false); - } else { + CancellationToken.None).ConfigureAwait(false); + } + else + { this._logger.LogTrace("Tried to set CWD but in bad state"); } @@ -106,23 +107,27 @@ await _powerShellContextService.SetWorkingDirectoryAsync( // - Profile loading is configured, AND // - Profiles haven't been loaded before, OR // - The profile loading configuration just changed - if (_configurationService.CurrentSettings.EnableProfileLoading - && (!this._profilesLoaded || !profileLoadingPreviouslyEnabled)) - { - this._logger.LogTrace("Loading profiles..."); - //await _executionService.LoadHostProfilesAsync().ConfigureAwait(false); - this._profilesLoaded = true; - this._logger.LogTrace("Loaded!"); - } + bool loadProfiles = _configurationService.CurrentSettings.EnableProfileLoading + && (!_profilesLoaded || !profileLoadingPreviouslyEnabled); - // Wait until after profiles are loaded (or not, if that's the - // case) before starting the interactive console. - if (!this._consoleReplStarted) + if (!_psesHost.IsRunning) { - // Start the interactive terminal - this._logger.LogTrace("Starting command loop"); - //_executionService.ConsoleReader.StartCommandLoop(); - this._consoleReplStarted = true; + _logger.LogTrace("Starting command loop"); + + if (loadProfiles) + { + _logger.LogTrace("Loading profiles..."); + } + + await _psesHost.StartAsync(new HostStartOptions + { + LoadProfiles = loadProfiles, + }, CancellationToken.None).ConfigureAwait(false); + + _consoleReplStarted = true; + _profilesLoaded = loadProfiles; + + _logger.LogTrace("Loaded!"); } // Run any events subscribed to configuration updates diff --git a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs index d2854c7ad..b4a5a578b 100644 --- a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs @@ -8,7 +8,6 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; -using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Generic; using System.Diagnostics; @@ -33,6 +32,7 @@ internal class RemoteFileManagerService private ILogger logger; private string remoteFilesPath; private string processTempPath; + private readonly IRunspaceContext _runspaceContext; private readonly PowerShellExecutionService _executionService; private IEditorOperations editorOperations; @@ -251,10 +251,12 @@ function New-EditorFile { /// public RemoteFileManagerService( ILoggerFactory factory, + IRunspaceContext runspaceContext, PowerShellExecutionService executionService, EditorOperationsService editorOperations) { this.logger = factory.CreateLogger(); + _runspaceContext = runspaceContext; _executionService = executionService; //this.powerShellContext.RunspaceChanged += HandleRunspaceChangedAsync; @@ -524,7 +526,7 @@ private async void HandleRunspaceChangedAsync(object sender, RunspaceChangedEven } // Close any remote files that were opened - if (e.PreviousRunspace.IsRemote() && + if (e.PreviousRunspace.IsOnRemoteMachine && (e.ChangeAction == RunspaceChangeAction.Shutdown || !string.Equals( e.NewRunspace.SessionDetails.ComputerName, @@ -559,7 +561,7 @@ private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) string remoteFilePath = args.SourceArgs[0] as string; // Is this a local process runspace? Treat as a local file - if (_executionService.CurrentRunspace.RunspaceOrigin == RunspaceOrigin.Local) + if (!_runspaceContext.CurrentRunspace.IsOnRemoteMachine) { localFilePath = remoteFilePath; } @@ -592,7 +594,7 @@ private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) this.StoreRemoteFile( remoteFilePath, fileContent, - _executionService.CurrentRunspace); + _runspaceContext.CurrentRunspace); } else { @@ -622,36 +624,38 @@ private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) private void RegisterPSEditFunction(IRunspaceInfo runspaceInfo) { - if (runspaceInfo.IsRemote()) + if (!runspaceInfo.IsOnRemoteMachine) { - try - { - runspaceInfo.Runspace.Events.ReceivedEvents.PSEventReceived += HandlePSEventReceivedAsync; + return; + } - PSCommand createCommand = new PSCommand(); - createCommand - .AddScript(CreatePSEditFunctionScript) - .AddParameter("PSEditModule", PSEditModule); + try + { + runspaceInfo.Runspace.Events.ReceivedEvents.PSEventReceived += HandlePSEventReceivedAsync; - if (runspaceInfo.RunspaceOrigin == RunspaceOrigin.DebuggedRunspace) - { - _executionService.ExecutePSCommandAsync(createCommand, new PowerShellExecutionOptions(), CancellationToken.None).GetAwaiter().GetResult(); - } - else - { - using (var powerShell = System.Management.Automation.PowerShell.Create()) - { - powerShell.Runspace = runspaceInfo.Runspace; - powerShell.Commands = createCommand; - powerShell.Invoke(); - } - } + PSCommand createCommand = new PSCommand(); + createCommand + .AddScript(CreatePSEditFunctionScript) + .AddParameter("PSEditModule", PSEditModule); + + if (runspaceInfo.RunspaceOrigin == RunspaceOrigin.DebuggedRunspace) + { + _executionService.ExecutePSCommandAsync(createCommand, new PowerShellExecutionOptions(), CancellationToken.None).GetAwaiter().GetResult(); } - catch (RemoteException e) + else { - this.logger.LogException("Could not create psedit function.", e); + using (var powerShell = System.Management.Automation.PowerShell.Create()) + { + powerShell.Runspace = runspaceInfo.Runspace; + powerShell.Commands = createCommand; + powerShell.Invoke(); + } } } + catch (RemoteException e) + { + this.logger.LogException("Could not create psedit function.", e); + } } private void RemovePSEditFunction(IRunspaceInfo runspaceInfo) From cd8933e2fe71365b068d00086789284883409ba9 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 28 Aug 2020 10:25:55 -0700 Subject: [PATCH 031/176] Fix initialization --- .../Server/PsesServiceCollectionExtensions.cs | 19 ++++++++----------- .../Execution/PipelineThreadExecutor.cs | 1 + .../Host/EditorServicesConsolePSHost.cs | 12 +----------- 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index c8b35cddc..e14bebeae 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -24,13 +24,11 @@ public static IServiceCollection AddPsesLanguageServices( this IServiceCollection collection, HostStartupInfo hostStartupInfo) { - return collection.AddSingleton() + return collection + .AddSingleton(hostStartupInfo) + .AddSingleton() .AddSingleton() - .AddSingleton( - (provider) => EditorServicesConsolePSHost.Create( - provider.GetService(), - provider.GetService(), - hostStartupInfo)) + .AddSingleton() .AddSingleton( (provider) => provider.GetService()) .AddSingleton( @@ -41,16 +39,15 @@ public static IServiceCollection AddPsesLanguageServices( .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton( - (provider) => + .AddSingleton(async (provider) => { var extensionService = new ExtensionService( provider.GetService(), provider.GetService()); - extensionService.InitializeAsync( + await extensionService.InitializeAsync( serviceProvider: provider, - editorOperations: provider.GetService()) - .Wait(); + editorOperations: provider.GetService()).ConfigureAwait(false); + return extensionService; }) .AddSingleton(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs index 7fd4e382a..39e0c83e1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs @@ -106,6 +106,7 @@ public IDisposable TakeTaskWriterLock() private void Run() { + _psesHost.PushInitialPowerShell(); RunTopLevelConsumerLoop(); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index df2f79788..cf9b79b16 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -22,16 +22,6 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host internal class EditorServicesConsolePSHost : PSHost, IHostSupportsInteractiveSession, IRunspaceContext { - public static EditorServicesConsolePSHost Create( - ILoggerFactory loggerFactory, - ILanguageServer languageServer, - HostStartupInfo hostInfo) - { - var host = new EditorServicesConsolePSHost(loggerFactory, languageServer, hostInfo); - host.PushInitialPowerShell(); - return host; - } - private readonly ILogger _logger; private readonly Stack _psFrameStack; @@ -145,7 +135,7 @@ public override void SetShouldExit(int exitCode) SetExit(); } - private void PushInitialPowerShell() + public void PushInitialPowerShell() { SMA.PowerShell pwsh = _psFactory.CreateInitialPowerShell(_hostInfo, _readLineProvider); var runspaceInfo = RunspaceInfo.CreateFromLocalPowerShell(_logger, pwsh); From 4a821965643672caaf13dc5e0f9e95c98b802f6c Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 28 Aug 2020 12:16:24 -0700 Subject: [PATCH 032/176] Finally get the REPL and completions back --- .../Server/PsesLanguageServer.cs | 14 +------ .../Server/PsesServiceCollectionExtensions.cs | 13 ++++--- .../Services/Extension/ExtensionService.cs | 39 +++++++++++-------- .../Execution/PipelineThreadExecutor.cs | 13 +++++-- .../Host/EditorServicesConsolePSHost.cs | 22 ++++++----- .../Handlers/ConfigurationHandler.cs | 20 +++++++++- 6 files changed, 72 insertions(+), 49 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index 6e8fe1ef7..5b756f545 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -114,7 +114,7 @@ public async Task StartAsync() // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#initialize .OnInitialize( // TODO: Either fix or ignore "method lacks 'await'" warning. - async (languageServer, request, cancellationToken) => + (languageServer, request, cancellationToken) => { Log.Logger.Debug("Initializing OmniSharp Language Server"); @@ -138,17 +138,7 @@ public async Task StartAsync() } } - // Set the working directory of the PowerShell session to the workspace path - if (workspaceService.WorkspacePath != null - && Directory.Exists(workspaceService.WorkspacePath)) - { - await serviceProvider.GetService() - .ExecutePSCommandAsync( - new PSCommand().AddCommand("Set-Location").AddParameter("-LiteralPath", workspaceService.WorkspacePath), - new PowerShellExecutionOptions(), - cancellationToken) - .ConfigureAwait(false); - } + return Task.CompletedTask; }); }).ConfigureAwait(false); diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index e14bebeae..431e27e2b 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -39,14 +39,15 @@ public static IServiceCollection AddPsesLanguageServices( .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(async (provider) => + .AddSingleton((provider) => { var extensionService = new ExtensionService( - provider.GetService(), - provider.GetService()); - await extensionService.InitializeAsync( - serviceProvider: provider, - editorOperations: provider.GetService()).ConfigureAwait(false); + provider.GetService(), + provider, + provider.GetService(), + provider.GetService()); + + extensionService.InitializeAsync().GetAwaiter().GetResult(); return extensionService; }) diff --git a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs index b79a28d96..5c7c27037 100644 --- a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs @@ -56,11 +56,29 @@ internal sealed class ExtensionService /// Creates a new instance of the ExtensionService which uses the provided /// PowerShellContext for loading and executing extension code. /// - /// A PowerShellContext used to execute extension code. - internal ExtensionService(PowerShellExecutionService executionService, ILanguageServerFacade languageServer) + /// The PSES language server instance. + /// Services for dependency injection into the editor object. + /// Options object to configure the editor. + /// PowerShell execution service to run PowerShell execution requests. + internal ExtensionService( + ILanguageServerFacade languageServer, + IServiceProvider serviceProvider, + IEditorOperations editorOperations, + PowerShellExecutionService executionService) { ExecutionService = executionService; _languageServer = languageServer; + + EditorObject = + new EditorObject( + serviceProvider, + this, + editorOperations); + + // Attach to ExtensionService events + CommandAdded += ExtensionService_ExtensionAddedAsync; + CommandUpdated += ExtensionService_ExtensionUpdatedAsync; + CommandRemoved += ExtensionService_ExtensionRemovedAsync; } #endregion @@ -73,23 +91,10 @@ internal ExtensionService(PowerShellExecutionService executionService, ILanguage /// /// An IEditorOperations implementation. /// A Task that can be awaited for completion. - internal async Task InitializeAsync( - IServiceProvider serviceProvider, - IEditorOperations editorOperations) + internal async Task InitializeAsync() { - // Attach to ExtensionService events - this.CommandAdded += ExtensionService_ExtensionAddedAsync; - this.CommandUpdated += ExtensionService_ExtensionUpdatedAsync; - this.CommandRemoved += ExtensionService_ExtensionRemovedAsync; - - this.EditorObject = - new EditorObject( - serviceProvider, - this, - editorOperations); - // Assign the new EditorObject to be the static instance available to binary APIs - this.EditorObject.SetAsStaticInstance(); + EditorObject.SetAsStaticInstance(); // Register the editor object in the runspace await ExecutionService.ExecuteDelegateAsync((pwsh, cancellationToken) => diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs index 39e0c83e1..613b03332 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs @@ -60,6 +60,11 @@ public PipelineThreadExecutor( _psesHost = psesHost; _debugContext = psesHost.DebugContext; _readLineProvider = readLineProvider; + _consumerThreadCancellationSource = new CancellationTokenSource(); + _executionQueue = new BlockingCollection(); + _loopCancellationContext = new CancellationContext(); + _commandCancellationContext = new CancellationContext(); + _taskProcessingLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); _pipelineThread = new Thread(Run) { @@ -75,11 +80,9 @@ public Task QueueTask(SynchronousTask synchronousTask _executionQueue.Add(synchronousTask); return synchronousTask.Task; } + public void Start() { - // We need to override the idle handler here, - // since readline will be overridden by this point - _readLineProvider.ReadLine.TryOverrideIdleHandler(OnPowerShellIdle); _pipelineThread.Start(); } @@ -107,6 +110,10 @@ public IDisposable TakeTaskWriterLock() private void Run() { _psesHost.PushInitialPowerShell(); + // We need to override the idle handler here, + // since readline will be overridden when the initial Powershell runspace is instantiated above + _readLineProvider.ReadLine.TryOverrideIdleHandler(OnPowerShellIdle); + _psesHost.PushNewReplTask(); RunTopLevelConsumerLoop(); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index cf9b79b16..40cee7010 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -100,7 +100,7 @@ public EditorServicesConsolePSHost( public override void EnterNestedPrompt() { - PushPowerShell(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested); + PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested); } public override void ExitNestedPrompt() @@ -127,7 +127,7 @@ public void PopRunspace() public void PushRunspace(Runspace runspace) { IsRunspacePushed = true; - PushPowerShell(_psFactory.CreatePowerShellForRunspace(runspace), PowerShellFrameType.Remote); + PushPowerShellAndRunLoop(_psFactory.CreatePowerShellForRunspace(runspace), PowerShellFrameType.Remote); } public override void SetShouldExit(int exitCode) @@ -145,7 +145,7 @@ public void PushInitialPowerShell() internal void PushNonInteractivePowerShell() { - PushPowerShell(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested | PowerShellFrameType.NonInteractive); + PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested | PowerShellFrameType.NonInteractive); } internal void CancelCurrentPrompt() @@ -193,8 +193,6 @@ await ExecutionService.ExecuteDelegateAsync((pwsh, delegateCancellation) => { await SetInitialWorkingDirectoryAsync(hostStartOptions.InitialWorkingDirectory, CancellationToken.None).ConfigureAwait(false); } - - _consoleReplRunner?.StartRepl(); } private void SetExit() @@ -212,12 +210,18 @@ private void SetExit() } } - private void PushPowerShell(SMA.PowerShell pwsh, PowerShellFrameType frameType) + private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType) { // TODO: Improve runspace origin detection here RunspaceOrigin runspaceOrigin = pwsh.Runspace.RunspaceIsRemote ? RunspaceOrigin.EnteredProcess : RunspaceOrigin.Local; var runspaceInfo = RunspaceInfo.CreateFromPowerShell(_logger, pwsh, runspaceOrigin, _localComputerName); - PushPowerShell(new PowerShellContextFrame(pwsh, runspaceInfo, frameType)); + PushPowerShellAndRunLoop(new PowerShellContextFrame(pwsh, runspaceInfo, frameType)); + } + + private void PushPowerShellAndRunLoop(PowerShellContextFrame frame) + { + PushPowerShell(frame); + _pipelineExecutor.RunPowerShellLoop(frame.FrameType); } private void PushPowerShell(PowerShellContextFrame frame) @@ -229,8 +233,6 @@ private void PushPowerShell(PowerShellContextFrame frame) AddRunspaceEventHandlers(frame.PowerShell.Runspace); _psFrameStack.Push(frame); - - _pipelineExecutor.RunPowerShellLoop(frame.FrameType); } internal void PopPowerShell() @@ -271,7 +273,7 @@ private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStop try { CurrentPowerShell.WaitForRemoteOutputIfNeeded(); - PushPowerShell(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Debug | PowerShellFrameType.Nested); + PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Debug | PowerShellFrameType.Nested); CurrentPowerShell.ResumeRemoteOutputIfNeeded(); } finally diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index e4bad4f17..ed788c940 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -20,6 +20,7 @@ using System.IO; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.Extension; namespace Microsoft.PowerShell.EditorServices.Handlers @@ -29,11 +30,13 @@ internal class PsesConfigurationHandler : DidChangeConfigurationHandlerBase private readonly ILogger _logger; private readonly WorkspaceService _workspaceService; private readonly ConfigurationService _configurationService; + private readonly ExtensionService _extensionService; private readonly EditorServicesConsolePSHost _psesHost; private readonly ILanguageServerFacade _languageServer; private DidChangeConfigurationCapability _capability; private bool _profilesLoaded; private bool _consoleReplStarted; + private bool _extensionServiceInitialized; private bool _cwdSet; public PsesConfigurationHandler( @@ -42,12 +45,14 @@ public PsesConfigurationHandler( AnalysisService analysisService, ConfigurationService configurationService, ILanguageServerFacade languageServer, + ExtensionService extensionService, EditorServicesConsolePSHost psesHost) { _logger = factory.CreateLogger(); _workspaceService = workspaceService; _configurationService = configurationService; _languageServer = languageServer; + _extensionService = extensionService; _psesHost = psesHost; ConfigurationUpdated += analysisService.OnConfigurationUpdated; @@ -76,6 +81,14 @@ public override async Task Handle(DidChangeConfigurationParams request, Ca _workspaceService.WorkspacePath, _logger); + if (!_psesHost.IsRunning) + { + await _psesHost.StartAsync(new HostStartOptions + { + LoadProfiles = _configurationService.CurrentSettings.EnableProfileLoading, + }, CancellationToken.None).ConfigureAwait(false); + } + if (!this._cwdSet) { if (!string.IsNullOrEmpty(_configurationService.CurrentSettings.Cwd) @@ -130,9 +143,14 @@ await _psesHost.StartAsync(new HostStartOptions _logger.LogTrace("Loaded!"); } + if (!_extensionServiceInitialized) + { + await _extensionService.InitializeAsync(); + } + // Run any events subscribed to configuration updates this._logger.LogTrace("Running configuration update event handlers"); - ConfigurationUpdated(this, _configurationService.CurrentSettings); + ConfigurationUpdated?.Invoke(this, _configurationService.CurrentSettings); // Convert the editor file glob patterns into an array for the Workspace // Both the files.exclude and search.exclude hash tables look like (glob-text, is-enabled): From 0c0a0193960a3213c5f47ce65892df02ffe67751 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 28 Aug 2020 12:29:19 -0700 Subject: [PATCH 033/176] Fix cancellation --- .../Services/PowerShell/Execution/PipelineThreadExecutor.cs | 2 +- .../Services/PowerShell/Host/EditorServicesConsolePSHost.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs index 613b03332..a811e3a4c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs @@ -113,7 +113,7 @@ private void Run() // We need to override the idle handler here, // since readline will be overridden when the initial Powershell runspace is instantiated above _readLineProvider.ReadLine.TryOverrideIdleHandler(OnPowerShellIdle); - _psesHost.PushNewReplTask(); + _psesHost.StartRepl(); RunTopLevelConsumerLoop(); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index 40cee7010..60944c425 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -153,6 +153,11 @@ internal void CancelCurrentPrompt() _consoleReplRunner?.CancelCurrentPrompt(); } + internal void StartRepl() + { + _consoleReplRunner?.StartRepl(); + } + internal void PushNewReplTask() { _consoleReplRunner?.PushNewReplTask(); From 2aab375fcc94d742610ff08fa4469b96448689cc Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 28 Aug 2020 13:14:18 -0700 Subject: [PATCH 034/176] Get debugging to work again --- .../PowerShell/Console/ConsoleReplRunner.cs | 1 - .../Debugging/PowerShellDebugContext.cs | 1 + .../Execution/PipelineThreadExecutor.cs | 11 +++---- .../Host/EditorServicesConsolePSHost.cs | 29 +++++++++++++++++-- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs index 65ef92d78..a226a80b5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using System; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index 8bc9c0c25..2c26d8129 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -116,6 +116,7 @@ public void ProcessDebuggerResult(DebuggerCommandResults debuggerResult) { if (debuggerResult.ResumeAction != null) { + SetDebugResuming(debuggerResult.ResumeAction.Value); RaiseDebuggerResumingEvent(new DebuggerResumingEventArgs(debuggerResult.ResumeAction.Value)); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs index a811e3a4c..a3b694482 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs @@ -29,8 +29,6 @@ internal class PipelineThreadExecutor private readonly EditorServicesConsolePSHost _psesHost; - private readonly PowerShellDebugContext _debugContext; - private readonly IReadLineProvider _readLineProvider; private readonly HostStartupInfo _hostInfo; @@ -58,7 +56,6 @@ public PipelineThreadExecutor( _logger = loggerFactory.CreateLogger(); _hostInfo = hostInfo; _psesHost = psesHost; - _debugContext = psesHost.DebugContext; _readLineProvider = readLineProvider; _consumerThreadCancellationSource = new CancellationTokenSource(); _executionQueue = new BlockingCollection(); @@ -187,18 +184,18 @@ private void RunNestedLoop(in CancellationScope cancellationScope) private void RunDebugLoop(in CancellationScope cancellationScope) { - _debugContext.EnterDebugLoop(cancellationScope.CancellationToken); + _psesHost.DebugContext.EnterDebugLoop(cancellationScope.CancellationToken); try { // Run commands, but cancelling our blocking wait if the debugger resumes - foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(_debugContext.OnResumeCancellationToken)) + foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(_psesHost.DebugContext.OnResumeCancellationToken)) { // We don't want to cancel the current command when the debugger resumes, // since that command will be resuming the debugger. // Instead let it complete and check the cancellation afterward. RunTaskSynchronously(task, cancellationScope.CancellationToken); - if (_debugContext.OnResumeCancellationToken.IsCancellationRequested) + if (_psesHost.DebugContext.OnResumeCancellationToken.IsCancellationRequested) { break; } @@ -210,7 +207,7 @@ private void RunDebugLoop(in CancellationScope cancellationScope) } finally { - _debugContext.ExitDebugLoop(); + _psesHost.DebugContext.ExitDebugLoop(); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index 60944c425..2d57d8661 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -36,6 +36,8 @@ internal class EditorServicesConsolePSHost : PSHost, IHostSupportsInteractiveSes private readonly ReadLineProvider _readLineProvider; + private readonly Stack> _runspacesInUse; + private string _localComputerName; private int _hostStarted = 0; @@ -48,6 +50,7 @@ public EditorServicesConsolePSHost( _logger = loggerFactory.CreateLogger(); _psFrameStack = new Stack(); _psFactory = new PowerShellFactory(loggerFactory, this); + _runspacesInUse = new Stack>(); _hostInfo = hostInfo; Name = hostInfo.Name; Version = hostInfo.Version; @@ -55,13 +58,14 @@ public EditorServicesConsolePSHost( _readLineProvider = new ReadLineProvider(loggerFactory); _pipelineExecutor = new PipelineThreadExecutor(loggerFactory, hostInfo, this, _readLineProvider); ExecutionService = new PowerShellExecutionService(loggerFactory, this, _pipelineExecutor); - DebugContext = new PowerShellDebugContext(loggerFactory, languageServer, this, _consoleReplRunner); UI = new EditorServicesConsolePSHostUserInterface(loggerFactory, _readLineProvider, hostInfo.PSHost.UI); if (hostInfo.ConsoleReplEnabled) { _consoleReplRunner = new ConsoleReplRunner(loggerFactory, this, _readLineProvider, ExecutionService); } + + DebugContext = new PowerShellDebugContext(loggerFactory, languageServer, this, _consoleReplRunner); } public override CultureInfo CurrentCulture => _hostInfo.PSHost.CurrentCulture; @@ -217,9 +221,28 @@ private void SetExit() private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType) { + RunspaceInfo runspaceInfo = null; + if (_runspacesInUse.Count > 0) + { + // This is more than just an optimization. + // When debugging, we cannot execute PowerShell directly to get this information; + // trying to do so will block on the command that called us, deadlocking execution. + // Instead, since we are reusing the runspace, we reuse that runspace's info as well. + KeyValuePair currentRunspace = _runspacesInUse.Peek(); + if (currentRunspace.Key == pwsh.Runspace) + { + runspaceInfo = currentRunspace.Value; + } + } + + if (runspaceInfo is null) + { + RunspaceOrigin runspaceOrigin = pwsh.Runspace.RunspaceIsRemote ? RunspaceOrigin.EnteredProcess : RunspaceOrigin.Local; + runspaceInfo = RunspaceInfo.CreateFromPowerShell(_logger, pwsh, runspaceOrigin, _localComputerName); + _runspacesInUse.Push(new KeyValuePair(pwsh.Runspace, runspaceInfo)); + } + // TODO: Improve runspace origin detection here - RunspaceOrigin runspaceOrigin = pwsh.Runspace.RunspaceIsRemote ? RunspaceOrigin.EnteredProcess : RunspaceOrigin.Local; - var runspaceInfo = RunspaceInfo.CreateFromPowerShell(_logger, pwsh, runspaceOrigin, _localComputerName); PushPowerShellAndRunLoop(new PowerShellContextFrame(pwsh, runspaceInfo, frameType)); } From e7bbe6f4749b0f42e255378bb14d50a48a6a91c1 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 15 Apr 2021 01:11:43 -0700 Subject: [PATCH 035/176] Fix rebase issues --- .../Server/PsesServiceCollectionExtensions.cs | 2 +- .../PowerShell/Console/PSReadLineProxy.cs | 15 +-------------- .../Debugging/PowerShellDebugContext.cs | 6 +++--- .../Host/EditorServicesConsolePSHost.cs | 2 +- 4 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index 431e27e2b..8bd94c0a3 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -47,7 +47,7 @@ public static IServiceCollection AddPsesLanguageServices( provider.GetService(), provider.GetService()); - extensionService.InitializeAsync().GetAwaiter().GetResult(); + extensionService.InitializeAsync(); return extensionService; }) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs index fc41e928b..806471c21 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs @@ -62,7 +62,7 @@ internal class PSReadLineProxy param() end {{ $module = Get-Module -ListAvailable PSReadLine | - Where-Object {{ $_.Version -ge '2.0.2' }} | + Where-Object {{ $_.Version -ge '2.2.1' }} | Sort-Object -Descending Version | Select-Object -First 1 if (-not $module) {{ @@ -104,11 +104,6 @@ public PSReadLineProxy( new[] { typeof(Runspace), typeof(EngineIntrinsics), typeof(CancellationToken) }) ?.CreateDelegate(typeof(Func)); - ForcePSEventHandling = (Action)psConsoleReadLine.GetMethod( - ForcePSEventHandlingMethodName, - BindingFlags.Static | BindingFlags.NonPublic) - ?.CreateDelegate(typeof(Action)); - AddToHistory = (Action)psConsoleReadLine.GetMethod( AddToHistoryMethodName, s_addToHistoryTypes) @@ -156,14 +151,6 @@ public PSReadLineProxy( AddToHistoryMethodName, _logger); } - - if (ForcePSEventHandling == null) - { - throw NewInvalidPSReadLineVersionException( - MethodMemberType, - ForcePSEventHandlingMethodName, - _logger); - } } internal Action AddToHistory { get; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index 2c26d8129..655cc715b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -14,7 +14,7 @@ internal class PowerShellDebugContext : IPowerShellDebugContext { private readonly ILogger _logger; - private readonly ILanguageServer _languageServer; + private readonly ILanguageServerFacade _languageServer; private readonly EditorServicesConsolePSHost _psesHost; @@ -22,7 +22,7 @@ internal class PowerShellDebugContext : IPowerShellDebugContext public PowerShellDebugContext( ILoggerFactory loggerFactory, - ILanguageServer languageServer, + ILanguageServerFacade languageServer, EditorServicesConsolePSHost psesHost, ConsoleReplRunner consoleReplRunner) { @@ -33,7 +33,7 @@ public PowerShellDebugContext( } private CancellationTokenSource _debugLoopCancellationSource; - + public bool IsStopped { get; private set; } public DscBreakpointCapability DscBreakpointCapability => throw new NotImplementedException(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index 2d57d8661..19cb815f2 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -44,7 +44,7 @@ internal class EditorServicesConsolePSHost : PSHost, IHostSupportsInteractiveSes public EditorServicesConsolePSHost( ILoggerFactory loggerFactory, - ILanguageServer languageServer, + ILanguageServerFacade languageServer, HostStartupInfo hostInfo) { _logger = loggerFactory.CreateLogger(); From b3cd5af0fb1b95e26bcae50a35a9257feacb2685 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 10:48:22 -0700 Subject: [PATCH 036/176] Fix incorrect namespace --- .../Services/PowerShell/Console/ColorConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs index e7ebee377..da885c41d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace PowerShellEditorServices.Services.PowerShell.Console +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { internal class ColorConfiguration { From c07be475ec207dc2a1afea62829abeb38b5edd07 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 10:53:05 -0700 Subject: [PATCH 037/176] Remove defunct comment --- src/PowerShellEditorServices/Server/PsesLanguageServer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index 5b756f545..2014414c4 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -113,7 +113,6 @@ public async Task StartAsync() // _Initialize_ request: // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#initialize .OnInitialize( - // TODO: Either fix or ignore "method lacks 'await'" warning. (languageServer, request, cancellationToken) => { Log.Logger.Debug("Initializing OmniSharp Language Server"); From bdb8a7df10ff403730330b97e7f2cf4904d3c4f0 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 10:54:21 -0700 Subject: [PATCH 038/176] Add comment about extension service --- .../Server/PsesServiceCollectionExtensions.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index 8bd94c0a3..f6df2e83c 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -47,6 +47,11 @@ public static IServiceCollection AddPsesLanguageServices( provider.GetService(), provider.GetService()); + // This is where we create the $psEditor variable + // so that when the console is ready, it will be available + // TODO: Improve the sequencing here so that: + // - The variable is guaranteed to be initialized when the console first appears + // - Any errors that occur are handled rather than lost by the unawaited task extensionService.InitializeAsync(); return extensionService; From f8a9db8aa6e5763277cb8d6c86037cd028558011 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 10:54:27 -0700 Subject: [PATCH 039/176] Remove defunct code --- .../Services/Symbols/Vistors/AstOperations.cs | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index d9c4e1ddd..e70eb4272 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -84,44 +84,8 @@ public static async Task GetCompletionsAsync( cursorPosition.LineNumber, cursorPosition.ColumnNumber)); - /* - if (!powerShellContext.IsAvailable) - { - return null; - } - */ - var stopwatch = new Stopwatch(); - // If the current runspace is out of process we can use - // CommandCompletion.CompleteInput because PSReadLine won't be taking up the - // main runspace. - /* - if (powerShellContext.IsCurrentRunspaceOutOfProcess()) - { - using (RunspaceHandle runspaceHandle = await powerShellContext.GetRunspaceHandleAsync(cancellationToken).ConfigureAwait(false)) - using (System.Management.Automation.PowerShell powerShell = System.Management.Automation.PowerShell.Create()) - { - powerShell.Runspace = runspaceHandle.Runspace; - stopwatch.Start(); - try - { - return CommandCompletion.CompleteInput( - scriptAst, - currentTokens, - cursorPosition, - options: null, - powershell: powerShell); - } - finally - { - stopwatch.Stop(); - logger.LogTrace($"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); - } - } - } - */ - CommandCompletion commandCompletion = null; await executionService.ExecuteDelegateAsync((pwsh, cancellationToken) => { From beecda057081572d05555be12c77a7ba607d1a15 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 10:59:15 -0700 Subject: [PATCH 040/176] Use correct wildcard escape method --- .../Services/DebugAdapter/DebugService.cs | 3 +- .../Utility/PathUtils.cs | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index ee1a2bd04..798c13215 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -18,7 +18,6 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; namespace Microsoft.PowerShell.EditorServices.Services { @@ -181,7 +180,7 @@ public async Task SetLineBreakpointsAsync( // Fix for issue #123 - file paths that contain wildcard chars [ and ] need to // quoted and have those wildcard chars escaped. - string escapedScriptPath = PathUtils.WildcardEscape(scriptPath); + string escapedScriptPath = PathUtils.WildcardEscapePath(scriptPath); if (dscBreakpoints == null || !dscBreakpoints.IsDscResourcePath(escapedScriptPath)) { diff --git a/src/PowerShellEditorServices/Utility/PathUtils.cs b/src/PowerShellEditorServices/Utility/PathUtils.cs index f3f9d45ae..cdfed6df1 100644 --- a/src/PowerShellEditorServices/Utility/PathUtils.cs +++ b/src/PowerShellEditorServices/Utility/PathUtils.cs @@ -4,6 +4,7 @@ using System.IO; using System.Management.Automation; using System.Runtime.InteropServices; +using System.Text; namespace Microsoft.PowerShell.EditorServices.Utility { @@ -41,5 +42,44 @@ public static string WildcardEscape(string path) { return WildcardPattern.Escape(path); } + + /// + /// Return the given path with all PowerShell globbing characters escaped, + /// plus optionally the whitespace. + /// + /// The path to process. + /// Specify True to escape spaces in the path, otherwise False. + /// The path with [ and ] escaped. + internal static string WildcardEscapePath(string path, bool escapeSpaces = false) + { + var sb = new StringBuilder(); + for (int i = 0; i < path.Length; i++) + { + char curr = path[i]; + switch (curr) + { + // Escape '[', ']', '?' and '*' with '`' + case '[': + case ']': + case '*': + case '?': + case '`': + sb.Append('`').Append(curr); + break; + + default: + // Escape whitespace if required + if (escapeSpaces && char.IsWhiteSpace(curr)) + { + sb.Append('`').Append(curr); + break; + } + sb.Append(curr); + break; + } + } + + return sb.ToString(); + } } } From e2bc29305883d869d9035875be6d326b7b5c53e1 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 11:01:34 -0700 Subject: [PATCH 041/176] Remove unused value --- .../Services/PowerShell/Context/PowerShellVersionDetails.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs index 2b4d2afc1..64d848b2c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs @@ -103,8 +103,6 @@ public static PowerShellVersionDetails GetVersionDetails(ILogger logger, PowerSh try { - var psVersionTableCommand = new PSCommand().AddScript("$PSVersionTable", useLocalScope: true); - Hashtable psVersionTable = pwsh .AddScript("$PSVersionTable", useLocalScope: true) .InvokeAndClear() From 4b01b8fbcaf832c61c383ddbce069ce7bb3642d9 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 11:46:31 -0700 Subject: [PATCH 042/176] Add comment about debugger implementation --- .../Debugging/PowerShellDebugContext.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index 655cc715b..d5ed45e89 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -10,6 +10,29 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging using OmniSharp.Extensions.LanguageServer.Protocol.Server; using System.Threading.Tasks; + /// + /// Handles the state of the PowerShell debugger. + /// + /// + /// + /// Debugging through a PowerShell Host is implemented by registering a handler + /// for the event. + /// Registering that handler causes debug actions in PowerShell like Set-PSBreakpoint + /// and Wait-Debugger to drop into the debugger and trigger the handler. + /// The handler is passed a mutable object + /// and the debugger stop lasts for the duration of the handler call. + /// The handler sets the property + /// when after it returns, the PowerShell debugger uses that as the direction on how to proceed. + /// + /// + /// When we handle the event, + /// we drop into a nested debug prompt and execute things in the debugger with , + /// which enables debugger commands like l, c, s, etc. + /// saves the event args object in its state, + /// and when one of the debugger commands is used, the result returned is used to set + /// on the saved event args object so that when the event handler returns, the PowerShell debugger takes the correct action. + /// + /// internal class PowerShellDebugContext : IPowerShellDebugContext { private readonly ILogger _logger; From 906aebaa56c4def357c4f3b2a70577890788e6a8 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 11:57:09 -0700 Subject: [PATCH 043/176] Add comment about Debugger.ProcessCommand() --- .../PowerShell/Execution/SynchronousPowerShellTask.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index c45cfe421..f03a7ea4a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -107,6 +107,9 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT var outputCollection = new PSDataCollection(); + // Out-Default doesn't work as needed in the debugger + // Instead we add Out-String to the command and collect results in a PSDataCollection + // and use the event handler to print output to the UI as its added to that collection if (_executionOptions.WriteOutputToHost) { _psCommand.AddDebugOutputCommand(); @@ -124,6 +127,10 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT DebuggerCommandResults debuggerResult = null; try { + // In the PowerShell debugger, extra debugger commands are made available, like "l", "s", "c", etc. + // Executing those commands produces a result that needs to be set on the debugger stop event args. + // So we use the Debugger.ProcessCommand() API to properly execute commands in the debugger + // and then call DebugContext.ProcessDebuggerResult() later to handle the command appropriately debuggerResult = _pwsh.Runspace.Debugger.ProcessCommand(_psCommand, outputCollection); cancellationToken.ThrowIfCancellationRequested(); } From a246c6fc33f1561ae9529f94c96f6fb6f3638dcb Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 12:12:04 -0700 Subject: [PATCH 044/176] Add comments around exception handling --- .../PowerShell/Execution/SynchronousPowerShellTask.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index f03a7ea4a..ac00df574 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -71,10 +71,13 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok result = _pwsh.InvokeCommand(_psCommand); cancellationToken.ThrowIfCancellationRequested(); } + // Test if we've been cancelled. If we're remoting, PSRemotingDataStructureException effectively means the pipeline was stopped. catch (Exception e) when (cancellationToken.IsCancellationRequested || e is PipelineStoppedException || e is PSRemotingDataStructureException) { throw new OperationCanceledException(); } + // We only catch RuntimeExceptions here in case writing errors to output was requested + // Other errors are bubbled up to the caller catch (RuntimeException e) { Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}"); @@ -134,11 +137,14 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT debuggerResult = _pwsh.Runspace.Debugger.ProcessCommand(_psCommand, outputCollection); cancellationToken.ThrowIfCancellationRequested(); } + // Test if we've been cancelled. If we're remoting, PSRemotingDataStructureException effectively means the pipeline was stopped. catch (Exception e) when (cancellationToken.IsCancellationRequested || e is PipelineStoppedException || e is PSRemotingDataStructureException) { StopDebuggerIfRemoteDebugSessionFailed(); throw new OperationCanceledException(); } + // We only catch RuntimeExceptions here in case writing errors to output was requested + // Other errors are bubbled up to the caller catch (RuntimeException e) { Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}"); From 8acff1594042cb0f4a287d645305103510a91bc3 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 12:12:45 -0700 Subject: [PATCH 045/176] Remove unused exception --- .../Execution/ExecutionCanceledException.cs | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionCanceledException.cs diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionCanceledException.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionCanceledException.cs deleted file mode 100644 index 16fb5cf65..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionCanceledException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution -{ - public class ExecutionCanceledException : Exception - { - public ExecutionCanceledException(string message, Exception innerException) - : base(message, innerException) - { - } - } -} From 7b1c7004afa64c92d8f0b4274685fa023bfb1641 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 12:16:33 -0700 Subject: [PATCH 046/176] Add comments to EvaluateHandler --- .../Services/PowerShell/Handlers/EvaluateHandler.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs index 457d1e8ae..4fce47658 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs @@ -10,6 +10,10 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { + /// + /// Handler for a custom request type for evaluating PowerShell. + /// This is generally for F8 support, to allow execution of a highlighted code snippet in the console as if it were copy-pasted. + /// internal class EvaluateHandler : IEvaluateHandler { private readonly ILogger _logger; @@ -25,6 +29,9 @@ public EvaluateHandler( public Task Handle(EvaluateRequestArguments request, CancellationToken cancellationToken) { + // TODO: Understand why we currently handle this asynchronously and why we return a dummy result value + // instead of awaiting the execution and returing a real result of some kind + _executionService.ExecutePSCommandAsync( new PSCommand().AddScript(request.Expression), new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true, InterruptCommandPrompt = true }, From 54dce85fa16d8048557986e6dc29c4e979ae1fc6 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 12:29:36 -0700 Subject: [PATCH 047/176] Fix SessionDetails comment --- .../Services/PowerShell/Runspace/SessionDetails.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs index 733f31d9a..f21f521bd 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs @@ -22,10 +22,9 @@ internal class SessionDetails private const string Property_InstanceId = "instanceId"; /// - /// Gets the PSCommand that gathers details from the - /// current session. + /// Runs a PowerShell command to gather details about the current session. /// - /// A PSCommand used to gather session details. + /// A data object containing details about the PowerShell session. public static SessionDetails GetFromPowerShell(PowerShell pwsh) { Hashtable detailsObject = pwsh From 41399b7d574ad2867ae7284c2dbd5a2811d2dcc1 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 12:47:08 -0700 Subject: [PATCH 048/176] Add CancellationContext comment --- .../PowerShell/Utility/CancellationContext.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs index b9d59c7ff..c7ef8c541 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs @@ -4,6 +4,23 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility { + /// + /// Encapsulates the scoping logic for cancellation tokens. + /// As PowerShell commands nest, this class maintains a stack of cancellation scopes + /// that allow each scope of logic to be cancelled at its own level. + /// Implicitly handles the merging and cleanup of cancellation token sources. + /// + /// + /// The class + /// and the struct + /// are intended to be used with a using block so you can do this: + /// + /// using (CancellationScope cancellationScope = _cancellationContext.EnterScope(_globalCancellationSource.CancellationToken, localCancellationToken)) + /// { + /// ExecuteCommandAsync(command, cancellationScope.CancellationToken); + /// } + /// + /// internal class CancellationContext { private readonly ConcurrentStack _cancellationSourceStack; From ecd51d2036effeeafd307e208750b5c20247a757 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 12:48:37 -0700 Subject: [PATCH 049/176] Use FirstOrDefault() --- .../Services/PowerShell/Utility/CommandHelpers.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs index c415420d2..da41cf885 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs @@ -98,12 +98,7 @@ public static async Task GetCommandInfoAsync( .AddArgument(commandName) .AddParameter("ErrorAction", "Ignore"); - CommandInfo commandInfo = null; - foreach (CommandInfo result in await executionService.ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false)) - { - commandInfo = result; - break; - } + CommandInfo commandInfo = (await executionService.ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false)).FirstOrDefault(); // Only cache CmdletInfos since they're exposed in binaries they are likely to not change throughout the session. if (commandInfo?.CommandType == CommandTypes.Cmdlet) From 4c2cd6f3c54b6aa14baa37fe065a88a26abba1bf Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 14:05:57 -0700 Subject: [PATCH 050/176] Add explanation to ErrorRecordExtensions --- .../Services/PowerShell/Utility/ErrorRecordExtensions.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs index a7e820ae7..06853da3f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs @@ -24,11 +24,14 @@ static ErrorRecordExtensions() var errorObjectParameter = Expression.Parameter(typeof(PSObject)); + // Generates a call like: + // $errorPSObject.WriteStream = [System.Management.Automation.WriteStreamType]::Error + // So that error record PSObjects will be rendered in the console properly s_setWriteStreamProperty = Expression.Lambda>( Expression.Call( errorObjectParameter, writeStreamProperty.GetSetMethod(), - Expression.Constant(errorStreamType)), + Expression.Constant(errorStreamType)), errorObjectParameter) .Compile(); } From 315baf236d021aba375c6ce79b6235c8759f28bd Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 14:16:34 -0700 Subject: [PATCH 051/176] Add comments to PSCommandExtensions --- .../PowerShell/Utility/PSCommandExtensions.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs index 2393c4fe4..33bb20280 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs @@ -21,22 +21,26 @@ public static PSCommand AddDebugOutputCommand(this PSCommand psCommand) public static PSCommand MergePipelineResults(this PSCommand psCommand) { + // We need to do merge errors and output before rendering with an Out- cmdlet Command lastCommand = psCommand.Commands[psCommand.Commands.Count - 1]; lastCommand.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output); lastCommand.MergeMyResults(PipelineResultTypes.Information, PipelineResultTypes.Output); return psCommand; } + /// + /// Get a representation of the PSCommand, for logging purposes. + /// public static string GetInvocationText(this PSCommand command) { - Command lastCommand = command.Commands[0]; + Command currentCommand = command.Commands[0]; var sb = new StringBuilder().AddCommandText(command.Commands[0]); for (int i = 1; i < command.Commands.Count; i++) { - sb.Append(lastCommand.IsEndOfStatement ? "; " : " | "); - lastCommand = command.Commands[i]; - sb.AddCommandText(lastCommand); + sb.Append(currentCommand.IsEndOfStatement ? "; " : " | "); + currentCommand = command.Commands[i]; + sb.AddCommandText(currentCommand); } return sb.ToString(); From 3be5e75c1e459a6813c6da53e52a5e4fb3a0aadb Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 14:27:20 -0700 Subject: [PATCH 052/176] Add code ref to ErrorRecordExtensions --- .../Services/PowerShell/Utility/ErrorRecordExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs index 06853da3f..da8d4cf2d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs @@ -27,6 +27,7 @@ static ErrorRecordExtensions() // Generates a call like: // $errorPSObject.WriteStream = [System.Management.Automation.WriteStreamType]::Error // So that error record PSObjects will be rendered in the console properly + // See https://github.com/PowerShell/PowerShell/blob/946341b2ebe6a61f081f4c9143668dc7be1f9119/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs#L2088-L2091 s_setWriteStreamProperty = Expression.Lambda>( Expression.Call( errorObjectParameter, From 122cae47a933140c9affafbe4bbc44fe1318de50 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 14:27:34 -0700 Subject: [PATCH 053/176] Add comment to PowerShellExtensions --- .../Services/PowerShell/Utility/PowerShellExtensions.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs index 89c54cbe7..2309e5d16 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs @@ -71,6 +71,9 @@ public static void InvokeCommand(this PowerShell pwsh, PSCommand psCommand) pwsh.InvokeAndClear(); } + /// + /// When running a remote session, waits for remote processing and output to complete. + /// public static void WaitForRemoteOutputIfNeeded(this PowerShell pwsh) { if (!pwsh.Runspace.RunspaceIsRemote) @@ -78,6 +81,11 @@ public static void WaitForRemoteOutputIfNeeded(this PowerShell pwsh) return; } + // These methods are required when running commands remotely. + // Remote rendering from command output is done asynchronously. + // So to ensure we wait for output to be rendered, + // we need these methods to wait for rendering. + // PowerShell does this in its own implementation: https://github.com/PowerShell/PowerShell/blob/883ca98dd74ea13b3d8c0dd62d301963a40483d6/src/System.Management.Automation/engine/debugger/debugger.cs#L4628-L4652 s_waitForServicingComplete(pwsh); s_suspendIncomingData(pwsh); } From 12bec6af30dd50152255eabe5e055fa38bb24992 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 14:29:38 -0700 Subject: [PATCH 054/176] Simplify $PROFILE code --- .../PowerShell/Utility/PowerShellExtensions.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs index 2309e5d16..fc4d30ad6 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs @@ -163,10 +163,10 @@ public static void LoadProfiles(this PowerShell pwsh, ProfilePathInfo profilePat { var profileVariable = new PSObject(); - pwsh.AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.AllUsersAllHosts), profilePaths.AllUsersAllHosts); - pwsh.AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.AllUsersCurrentHost), profilePaths.AllUsersCurrentHost); - pwsh.AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.CurrentUserAllHosts), profilePaths.CurrentUserAllHosts); - pwsh.AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.CurrentUserCurrentHost), profilePaths.CurrentUserCurrentHost); + pwsh.AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.AllUsersAllHosts), profilePaths.AllUsersAllHosts) + .AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.AllUsersCurrentHost), profilePaths.AllUsersCurrentHost) + .AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.CurrentUserAllHosts), profilePaths.CurrentUserAllHosts) + .AddProfileMemberAndLoadIfExists(profileVariable, nameof(profilePaths.CurrentUserCurrentHost), profilePaths.CurrentUserCurrentHost); pwsh.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable); } @@ -197,7 +197,7 @@ public static string GetErrorString(this PowerShell pwsh) return sb.ToString(); } - private static void AddProfileMemberAndLoadIfExists(this PowerShell pwsh, PSObject profileVariable, string profileName, string profilePath) + private static PowerShell AddProfileMemberAndLoadIfExists(this PowerShell pwsh, PSObject profileVariable, string profileName, string profilePath) { profileVariable.Members.Add(new PSNoteProperty(profileName, profilePath)); @@ -209,6 +209,8 @@ private static void AddProfileMemberAndLoadIfExists(this PowerShell pwsh, PSObje pwsh.InvokeCommand(psCommand); } + + return pwsh; } private static StringBuilder AddErrorString(this StringBuilder sb, ErrorRecord error, int errorIndex) From 1c5f5f25c9dc1ffe43987f89a215f9b7b4e6a127 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 14:37:41 -0700 Subject: [PATCH 055/176] Document runspace extension method --- .../PowerShell/Utility/RunspaceExtensions.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs index c65176b98..f91ac11a8 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs @@ -18,12 +18,9 @@ internal static class RunspaceExtensions static RunspaceExtensions() { // PowerShell ApartmentState APIs aren't available in PSStandard, so we need to use reflection. - if (!VersionUtils.IsNetCore || VersionUtils.IsPS7OrGreater) - { - MethodInfo setterInfo = typeof(Runspace).GetProperty("ApartmentState").GetSetMethod(); - Delegate setter = Delegate.CreateDelegate(typeof(Action), firstArgument: null, method: setterInfo); - s_runspaceApartmentStateSetter = (Action)setter; - } + MethodInfo setterInfo = typeof(Runspace).GetProperty("ApartmentState").GetSetMethod(); + Delegate setter = Delegate.CreateDelegate(typeof(Action), firstArgument: null, method: setterInfo); + s_runspaceApartmentStateSetter = (Action)setter; MethodInfo getRemotePromptMethod = typeof(HostUtilities).GetMethod("GetRemotePrompt", BindingFlags.NonPublic | BindingFlags.Static); ParameterExpression runspaceParam = Expression.Parameter(typeof(Runspace)); @@ -45,6 +42,13 @@ public static void SetApartmentStateToSta(this Runspace runspace) s_runspaceApartmentStateSetter?.Invoke(runspace, ApartmentState.STA); } + /// + /// Augment a given prompt string with a remote decoration. + /// This is an internal method on Runspace in PowerShell that we reuse via reflection. + /// + /// The runspace the prompt is for. + /// The base prompt to decorate. + /// A prompt string decorated with remote connection details. public static string GetRemotePrompt(this Runspace runspace, string basePrompt) { return s_getRemotePromptFunc(runspace, basePrompt); From b9bd30e2d79e55c2e9c7bd264ed7b2945488d000 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 30 Sep 2021 14:58:21 -0700 Subject: [PATCH 056/176] Fix profiling loading logic in config handler --- .../Handlers/ConfigurationHandler.cs | 56 ++++++++----------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index ed788c940..026252cd2 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -17,8 +17,6 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Server; using OmniSharp.Extensions.LanguageServer.Protocol.Window; using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; -using System.IO; -using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.Extension; @@ -81,14 +79,35 @@ public override async Task Handle(DidChangeConfigurationParams request, Ca _workspaceService.WorkspacePath, _logger); + // We need to load the profiles if: + // - Profile loading is configured, AND + // - Profiles haven't been loaded before, OR + // - The profile loading configuration just changed + bool loadProfiles = _configurationService.CurrentSettings.EnableProfileLoading + && (!_profilesLoaded || !profileLoadingPreviouslyEnabled); + if (!_psesHost.IsRunning) { - await _psesHost.StartAsync(new HostStartOptions + _logger.LogTrace("Starting command loop"); + + if (loadProfiles) { - LoadProfiles = _configurationService.CurrentSettings.EnableProfileLoading, - }, CancellationToken.None).ConfigureAwait(false); + _logger.LogTrace("Loading profiles..."); + } + + await _psesHost.StartAsync(new HostStartOptions(), CancellationToken.None).ConfigureAwait(false); + + _consoleReplStarted = true; + + if (loadProfiles) + { + _profilesLoaded = true; + _logger.LogTrace("Loaded!"); + } } + // TODO: Load profiles when the host is already running + if (!this._cwdSet) { if (!string.IsNullOrEmpty(_configurationService.CurrentSettings.Cwd) @@ -116,33 +135,6 @@ await _psesHost.SetInitialWorkingDirectoryAsync( this._cwdSet = true; } - // We need to load the profiles if: - // - Profile loading is configured, AND - // - Profiles haven't been loaded before, OR - // - The profile loading configuration just changed - bool loadProfiles = _configurationService.CurrentSettings.EnableProfileLoading - && (!_profilesLoaded || !profileLoadingPreviouslyEnabled); - - if (!_psesHost.IsRunning) - { - _logger.LogTrace("Starting command loop"); - - if (loadProfiles) - { - _logger.LogTrace("Loading profiles..."); - } - - await _psesHost.StartAsync(new HostStartOptions - { - LoadProfiles = loadProfiles, - }, CancellationToken.None).ConfigureAwait(false); - - _consoleReplStarted = true; - _profilesLoaded = loadProfiles; - - _logger.LogTrace("Loaded!"); - } - if (!_extensionServiceInitialized) { await _extensionService.InitializeAsync(); From 122c2dd9e043414e36d1ba5d6daae73ae412ee63 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 10 May 2021 12:21:00 -0700 Subject: [PATCH 057/176] Remove extraneous JS file --- ex.js | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 ex.js diff --git a/ex.js b/ex.js deleted file mode 100644 index c5bc30443..000000000 --- a/ex.js +++ /dev/null @@ -1,13 +0,0 @@ -function run(timeout, count) { - let i = 0; - function inner() { - if (i === count) { - return; - } - console.log(i); - i++; - setTimeout(inner, timeout); - } - inner(); -} - From ada5528458436e4b0ac548e6359cf091de7ccc98 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 20 Apr 2021 11:40:48 -0700 Subject: [PATCH 058/176] Debugging dependency injection --- .../Internal/EditorServicesRunner.cs | 2 +- .../Server/PsesDebugServer.cs | 25 ++++++++++++------- .../Server/PsesServiceCollectionExtensions.cs | 3 ++- .../Services/Extension/ExtensionService.cs | 17 +++++++++---- .../Debugging/PowerShellDebugContext.cs | 11 +++++--- 5 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs index 0d2a50ac1..896a3d09f 100644 --- a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs +++ b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs @@ -130,7 +130,7 @@ private async Task CreateEditorServicesAndRunUntilShutdown() _logger.Log(PsesLogLevel.Diagnostic, "Creating/running editor services"); bool creatingLanguageServer = _config.LanguageServiceTransport != null; - bool creatingDebugServer = false;// _config.DebugServiceTransport != null; + bool creatingDebugServer = _config.DebugServiceTransport != null; bool isTempDebugSession = creatingDebugServer && !creatingLanguageServer; // Set up information required to instantiate servers diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 51a558d53..da7ae3cb9 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -11,12 +11,9 @@ using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShell; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; -using Microsoft.PowerShell.EditorServices.Utility; -using OmniSharp.Extensions.DebugAdapter.Protocol; -using OmniSharp.Extensions.DebugAdapter.Protocol.Serialization; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using OmniSharp.Extensions.DebugAdapter.Server; -using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Server; namespace Microsoft.PowerShell.EditorServices.Server @@ -47,6 +44,8 @@ internal class PsesDebugServer : IDisposable private PowerShellExecutionService _executionService; + private EditorServicesConsolePSHost _psesHost; + protected readonly ILoggerFactory _loggerFactory; public PsesDebugServer( @@ -78,6 +77,9 @@ public async Task StartAsync() { // We need to let the PowerShell Context Service know that we are in a debug session // so that it doesn't send the powerShell/startDebugger message. + _psesHost = ServiceProvider.GetService(); + _psesHost.DebugContext.IsDebugServerActive = true; + _executionService = ServiceProvider.GetService(); /* @@ -99,10 +101,14 @@ public async Task StartAsync() options .WithInput(_inputStream) .WithOutput(_outputStream) - .WithServices(serviceCollection => serviceCollection - .AddLogging() - .AddOptions() - .AddPsesDebugServices(ServiceProvider, this, _useTempSession)) + .WithServices(serviceCollection => { + serviceCollection + .AddLogging() + .AddOptions() + .AddPsesDebugServices(ServiceProvider, this, _useTempSession); + + Console.WriteLine("Services configured"); + }) // TODO: Consider replacing all WithHandler with AddSingleton .WithHandler() .WithHandler() @@ -141,6 +147,7 @@ public async Task StartAsync() public void Dispose() { // TODO: If the debugger has stopped, should we clear the breakpoints? + _psesHost.DebugContext.IsDebugServerActive = false; _debugAdapterServer.Dispose(); _inputStream.Dispose(); _outputStream.Dispose(); diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index f6df2e83c..2cfa0ed41 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -65,7 +65,8 @@ public static IServiceCollection AddPsesDebugServices( PsesDebugServer psesDebugServer, bool useTempSession) { - return collection.AddSingleton(languageServiceProvider.GetService()) + return collection + .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(psesDebugServer) diff --git a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs index 5c7c27037..c4d092e6d 100644 --- a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs @@ -27,6 +27,8 @@ internal sealed class ExtensionService private readonly ILanguageServerFacade _languageServer; + private int _initialized = 0; + #endregion #region Properties @@ -91,16 +93,21 @@ internal ExtensionService( /// /// An IEditorOperations implementation. /// A Task that can be awaited for completion. - internal async Task InitializeAsync() + internal Task InitializeAsync() { + if (Interlocked.Exchange(ref _initialized, 1) != 0) + { + return Task.CompletedTask; + } + // Assign the new EditorObject to be the static instance available to binary APIs EditorObject.SetAsStaticInstance(); // Register the editor object in the runspace - await ExecutionService.ExecuteDelegateAsync((pwsh, cancellationToken) => - { - pwsh.Runspace.SessionStateProxy.PSVariable.Set("psEditor", EditorObject); - }, representation: "Set PSEditor", CancellationToken.None); + return ExecutionService.ExecuteDelegateAsync((pwsh, cancellationToken) => + { + pwsh.Runspace.SessionStateProxy.PSVariable.Set("psEditor", EditorObject); + }, representation: "Set PSEditor", CancellationToken.None); } /// diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index d5ed45e89..e84b5407d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -43,6 +43,8 @@ internal class PowerShellDebugContext : IPowerShellDebugContext private readonly ConsoleReplRunner _consoleRepl; + private CancellationTokenSource _debugLoopCancellationSource; + public PowerShellDebugContext( ILoggerFactory loggerFactory, ILanguageServerFacade languageServer, @@ -55,11 +57,9 @@ public PowerShellDebugContext( _consoleRepl = consoleReplRunner; } - private CancellationTokenSource _debugLoopCancellationSource; - public bool IsStopped { get; private set; } - public DscBreakpointCapability DscBreakpointCapability => throw new NotImplementedException(); + public bool IsDebugServerActive { get; set; } public DebuggerStopEventArgs LastStopEventArgs { get; private set; } @@ -152,6 +152,11 @@ public void HandleBreakpointUpdated(BreakpointUpdatedEventArgs breakpointUpdated private void RaiseDebuggerStoppedEvent() { // TODO: Send language server message to start debugger + if (!IsDebugServerActive) + { + _languageServer.SendNotification("powerShell/startDebugger"); + } + DebuggerStopped?.Invoke(this, LastStopEventArgs); } From 0229fc251cd31429ce4305fb9cf5dea0aa8989e9 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 20 Apr 2021 22:38:07 -0700 Subject: [PATCH 059/176] Get UI debugger running --- .../Server/PsesDebugServer.cs | 7 +-- .../Server/PsesServiceCollectionExtensions.cs | 3 + .../DebugAdapter/DebugStateService.cs | 2 +- .../Handlers/ConfigurationDoneHandler.cs | 61 ++++++++++++++++++- .../Handlers/LaunchAndAttachHandler.cs | 6 +- .../Execution/SynchronousPowerShellTask.cs | 5 +- 6 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index da7ae3cb9..e1749446e 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -101,14 +101,11 @@ public async Task StartAsync() options .WithInput(_inputStream) .WithOutput(_outputStream) - .WithServices(serviceCollection => { + .WithServices(serviceCollection => serviceCollection .AddLogging() .AddOptions() - .AddPsesDebugServices(ServiceProvider, this, _useTempSession); - - Console.WriteLine("Services configured"); - }) + .AddPsesDebugServices(ServiceProvider, this, _useTempSession)) // TODO: Consider replacing all WithHandler with AddSingleton .WithHandler() .WithHandler() diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index 2cfa0ed41..29281c1b2 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -66,6 +66,9 @@ public static IServiceCollection AddPsesDebugServices( bool useTempSession) { return collection + .AddSingleton(languageServiceProvider.GetService()) + .AddSingleton(languageServiceProvider.GetService()) + .AddSingleton(languageServiceProvider.GetService().DebugContext) .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(languageServiceProvider.GetService()) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugStateService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugStateService.cs index 2f03dd091..c12e89db0 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugStateService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugStateService.cs @@ -13,7 +13,7 @@ internal class DebugStateService internal bool NoDebug { get; set; } - internal string Arguments { get; set; } + internal string[] Arguments { get; set; } internal bool IsRemoteAttach { get; set; } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index ef4ec48f5..39a29bbb1 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Language; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -118,11 +120,66 @@ await _executionService } else { - //await _powerShellContextService - // .ExecuteScriptWithArgsAsync(scriptToLaunch, _debugStateService.Arguments, writeInputToHost: true).ConfigureAwait(false); + await _executionService + .ExecutePSCommandAsync( + BuildPSCommandFromArguments(scriptToLaunch, _debugStateService.Arguments), + new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true }, + CancellationToken.None) + .ConfigureAwait(false); } _debugAdapterServer.SendNotification(EventNames.Terminated); } + + private PSCommand BuildPSCommandFromArguments(string command, IReadOnlyList arguments) + { + if (arguments is null + || arguments.Count == 0) + { + return new PSCommand().AddCommand(command); + } + + // We are forced to use a hack here so that we can reuse PowerShell's parameter binding + var sb = new StringBuilder() + .Append("& '") + .Append(command.Replace("'", "''")) + .Append("'"); + + foreach (string arg in arguments) + { + sb.Append(' '); + + if (ArgumentNeedsEscaping(arg)) + { + sb.Append('\'').Append(arg.Replace("'", "''")).Append('\''); + } + else + { + sb.Append(arg); + } + } + + return new PSCommand().AddScript(sb.ToString()); + } + + private bool ArgumentNeedsEscaping(string argument) + { + foreach (char c in argument) + { + switch (c) + { + case '\'': + case '"': + case '|': + case '&': + case ';': + case ':': + case char w when char.IsWhiteSpace(w): + return true; + } + } + + return false; + } } } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index bf1e4df39..1596d1c66 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -172,17 +172,15 @@ public async Task Handle(PsesLaunchRequestArguments request, Can } // Prepare arguments to the script - if specified - string arguments = null; if (request.Args?.Length > 0) { - arguments = string.Join(" ", request.Args); - _logger.LogTrace("Script arguments are: " + arguments); + _logger.LogTrace($"Script arguments are: {string.Join(" ", request.Args)}"); } // Store the launch parameters so that they can be used later _debugStateService.NoDebug = request.NoDebug; _debugStateService.ScriptToLaunch = request.Script; - _debugStateService.Arguments = arguments; + _debugStateService.Arguments = request.Args; _debugStateService.IsUsingTempIntegratedConsole = request.CreateTemporaryIntegratedConsole; if (request.CreateTemporaryIntegratedConsole diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index ac00df574..a74c98b6f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -186,9 +186,10 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT } // If we've been asked for a PSObject, no need to allocate a new collection - if (typeof(TResult) == typeof(PSObject)) + if (typeof(TResult) == typeof(PSObject) + && outputCollection is IReadOnlyList resultCollection) { - return (IReadOnlyList)outputCollection; + return resultCollection; } // Otherwise, convert things over From e2f64ec91cae7c987ebf866f61822cd2acdb8e53 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 10 May 2021 14:28:11 -0700 Subject: [PATCH 060/176] Resolve commented-out code --- .../DebugAdapter/DebugEventHandlerService.cs | 53 +++++++++++-------- .../Handlers/ConfigurationDoneHandler.cs | 9 +++- .../PowerShell/Handlers/GetVersionHandler.cs | 2 + .../Workspace/RemoteFileManagerService.cs | 8 +-- 4 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs index 3c72fa824..a41191370 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs @@ -3,8 +3,10 @@ using System.Management.Automation; using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.DebugAdapter.Protocol.Events; @@ -20,18 +22,22 @@ internal class DebugEventHandlerService private readonly DebugStateService _debugStateService; private readonly IDebugAdapterServerFacade _debugAdapterServer; + private readonly IPowerShellDebugContext _debugContext; + public DebugEventHandlerService( ILoggerFactory factory, PowerShellExecutionService executionService, DebugService debugService, DebugStateService debugStateService, - IDebugAdapterServerFacade debugAdapterServer) + IDebugAdapterServerFacade debugAdapterServer, + IPowerShellDebugContext debugContext) { _logger = factory.CreateLogger(); _executionService = executionService; _debugService = debugService; _debugStateService = debugStateService; _debugAdapterServer = debugAdapterServer; + _debugContext = debugContext; } internal void RegisterEventHandlers() @@ -89,28 +95,33 @@ e.OriginalEvent.Breakpoints[0] is CommandBreakpoint private void ExecutionService_RunspaceChanged(object sender, RunspaceChangedEventArgs e) { - if (_debugStateService.WaitingForAttach && - e.ChangeAction == RunspaceChangeAction.Enter && - e.NewRunspace.RunspaceOrigin == RunspaceOrigin.DebuggedRunspace) - { - // Sends the InitializedEvent so that the debugger will continue - // sending configuration requests - _debugStateService.WaitingForAttach = false; - _debugStateService.ServerStarted.SetResult(true); - } - else if ( - e.ChangeAction == RunspaceChangeAction.Exit && false) - // _powerShellContextService.IsDebuggerStopped) + switch (e.ChangeAction) { - // Exited the session while the debugger is stopped, - // send a ContinuedEvent so that the client changes the - // UI to appear to be running again - _debugAdapterServer.SendNotification(EventNames.Continued, - new ContinuedEvent + case RunspaceChangeAction.Enter: + if (_debugStateService.WaitingForAttach + && e.NewRunspace.RunspaceOrigin == RunspaceOrigin.DebuggedRunspace) + { + // Sends the InitializedEvent so that the debugger will continue + // sending configuration requests + _debugStateService.WaitingForAttach = false; + _debugStateService.ServerStarted.SetResult(true); + } + return; + + case RunspaceChangeAction.Exit: + if (_debugContext.IsStopped) { - ThreadId = 1, - AllThreadsContinued = true - }); + // Exited the session while the debugger is stopped, + // send a ContinuedEvent so that the client changes the + // UI to appear to be running again + _debugAdapterServer.SendNotification( + EventNames.Continued, + new ContinuedEvent + { + ThreadId = ThreadsHandler.PipelineThread.Id, + }); + } + return; } } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 39a29bbb1..574762d80 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -11,6 +11,7 @@ using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using OmniSharp.Extensions.DebugAdapter.Protocol.Events; @@ -29,6 +30,8 @@ internal class ConfigurationDoneHandler : IConfigurationDoneHandler private readonly PowerShellExecutionService _executionService; private readonly WorkspaceService _workspaceService; + private readonly IPowerShellDebugContext _debugContext; + public ConfigurationDoneHandler( ILoggerFactory loggerFactory, IDebugAdapterServerFacade debugAdapterServer, @@ -36,7 +39,8 @@ public ConfigurationDoneHandler( DebugStateService debugStateService, DebugEventHandlerService debugEventHandlerService, PowerShellExecutionService executionService, - WorkspaceService workspaceService) + WorkspaceService workspaceService, + IPowerShellDebugContext debugContext) { _logger = loggerFactory.CreateLogger(); _debugAdapterServer = debugAdapterServer; @@ -45,6 +49,7 @@ public ConfigurationDoneHandler( _debugEventHandlerService = debugEventHandlerService; _executionService = executionService; _workspaceService = workspaceService; + _debugContext = debugContext; } public Task Handle(ConfigurationDoneArguments request, CancellationToken cancellationToken) @@ -79,7 +84,7 @@ public Task Handle(ConfigurationDoneArguments request { // If this is an interactive session and there's a pending breakpoint that has not been propagated through // the debug service, fire the debug service's OnDebuggerStop event. - //_debugService.OnDebuggerStopAsync(null, _powerShellContextService.CurrentDebuggerStopEventArgs); + _debugService.OnDebuggerStopAsync(null, _debugContext.LastStopEventArgs); } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs index ac38f09d5..1f7c18218 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs @@ -129,6 +129,8 @@ await _executionService.ExecutePSCommandAsync( new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true }, CancellationToken.None).ConfigureAwait(false); + // TODO: Error handling here + /* if (errors.Length == 0) { diff --git a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs index b4a5a578b..b9da23597 100644 --- a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs @@ -258,7 +258,7 @@ public RemoteFileManagerService( this.logger = factory.CreateLogger(); _runspaceContext = runspaceContext; _executionService = executionService; - //this.powerShellContext.RunspaceChanged += HandleRunspaceChangedAsync; + _executionService.RunspaceChanged += HandleRunspaceChangedAsync; this.editorOperations = editorOperations; @@ -272,6 +272,7 @@ public RemoteFileManagerService( // Delete existing temporary file cache path if it already exists this.TryDeleteTemporaryPath(); + // TODO: Do this somewhere other than the constructor and make it async // Register the psedit function in the current runspace //this.RegisterPSEditFunction(this.powerShellContext.CurrentRunspace); } @@ -366,7 +367,7 @@ public async Task SaveRemoteFileAsync(string localFilePath) string remoteFilePath = this.GetMappedPath( localFilePath, - null); //_startupService.EditorServicesHost.Runspace); + _runspaceContext.CurrentRunspace); this.logger.LogTrace( $"Saving remote file {remoteFilePath} (local path: {localFilePath})"); @@ -633,8 +634,7 @@ private void RegisterPSEditFunction(IRunspaceInfo runspaceInfo) { runspaceInfo.Runspace.Events.ReceivedEvents.PSEventReceived += HandlePSEventReceivedAsync; - PSCommand createCommand = new PSCommand(); - createCommand + PSCommand createCommand = new PSCommand() .AddScript(CreatePSEditFunctionScript) .AddParameter("PSEditModule", PSEditModule); From 24c03d2dd2bbe4069c068aa1a814a2671197bc27 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 25 May 2021 15:19:19 -0700 Subject: [PATCH 061/176] Improve queue implementation --- .../Execution/PipelineThreadExecutor.cs | 104 ++++++++------- .../PowerShell/Execution/SynchronousTask.cs | 4 +- .../Host/EditorServicesConsolePSHost.cs | 13 +- .../PowerShell/PowerShellExecutionService.cs | 31 +++-- .../ConcurrentBlockablePriorityQueue.cs | 125 ++++++++++++++++++ 5 files changed, 209 insertions(+), 68 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Utility/ConcurrentBlockablePriorityQueue.cs diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs index a3b694482..dab052b80 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs @@ -11,6 +11,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using PowerShellEditorServices.Services.PowerShell.Utility; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using System.Runtime.CompilerServices; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution { @@ -33,7 +36,7 @@ internal class PipelineThreadExecutor private readonly HostStartupInfo _hostInfo; - private readonly BlockingCollection _executionQueue; + private readonly ConcurrentBlockablePriorityQueue _executionQueue; private readonly CancellationTokenSource _consumerThreadCancellationSource; @@ -43,7 +46,7 @@ internal class PipelineThreadExecutor private readonly CancellationContext _commandCancellationContext; - private readonly ReaderWriterLockSlim _taskProcessingLock; + private readonly ManualResetEventSlim _taskProcessingAllowed; private bool _runIdleLoop; @@ -58,10 +61,10 @@ public PipelineThreadExecutor( _psesHost = psesHost; _readLineProvider = readLineProvider; _consumerThreadCancellationSource = new CancellationTokenSource(); - _executionQueue = new BlockingCollection(); + _executionQueue = new ConcurrentBlockablePriorityQueue(); _loopCancellationContext = new CancellationContext(); _commandCancellationContext = new CancellationContext(); - _taskProcessingLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + _taskProcessingAllowed = new ManualResetEventSlim(initialState: true); _pipelineThread = new Thread(Run) { @@ -72,12 +75,42 @@ public PipelineThreadExecutor( public bool IsExiting { get; set; } - public Task QueueTask(SynchronousTask synchronousTask) + public Task RunTaskAsync(SynchronousTask synchronousTask) { - _executionQueue.Add(synchronousTask); + _executionQueue.Enqueue(synchronousTask); return synchronousTask.Task; } + public Task RunTaskNextAsync(SynchronousTask synchronousTask) + { + _executionQueue.EnqueueNext(synchronousTask); + return synchronousTask.Task; + } + + public Task CancelCurrentAndRunTaskNowAsync(SynchronousTask synchronousTask) + { + // We need to ensure that we don't: + // - Add this command to the queue and immediately cancel it + // - Allow a consumer to dequeue and run another command after cancellation and before we add this command + // + // To ensure that, we need the following sequence: + // - Stop queue consumption progressing + // - Cancel any current processing + // - Add our task to the front of the queue + // - Recommence processing + + using (_executionQueue.BlockConsumers()) + { + CancelCurrentTask(); + + // NOTE: + // This must not be awaited + // We only need to block consumers while we add to the queue + // Awaiting this will deadlock, since the runner can't progress while we block consumers + return RunTaskNextAsync(synchronousTask); + } + } + public void Start() { _pipelineThread.Start(); @@ -99,11 +132,6 @@ public void Dispose() Stop(); } - public IDisposable TakeTaskWriterLock() - { - return TaskProcessingWriterLockLifetime.TakeLock(_taskProcessingLock); - } - private void Run() { _psesHost.PushInitialPowerShell(); @@ -150,9 +178,9 @@ private void RunTopLevelConsumerLoop() { try { - foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationScope.CancellationToken)) + while (true) { - RunTaskSynchronously(task, cancellationScope.CancellationToken); + RunNextTaskSynchronously(cancellationScope.CancellationToken); } } catch (OperationCanceledException) @@ -166,9 +194,9 @@ private void RunNestedLoop(in CancellationScope cancellationScope) { try { - foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(cancellationScope.CancellationToken)) + while (true) { - RunTaskSynchronously(task, cancellationScope.CancellationToken); + RunNextTaskSynchronously(cancellationScope.CancellationToken); if (IsExiting) { @@ -188,8 +216,10 @@ private void RunDebugLoop(in CancellationScope cancellationScope) try { // Run commands, but cancelling our blocking wait if the debugger resumes - foreach (ISynchronousTask task in _executionQueue.GetConsumingEnumerable(_psesHost.DebugContext.OnResumeCancellationToken)) + while (true) { + ISynchronousTask task = _executionQueue.Take(_psesHost.DebugContext.OnResumeCancellationToken); + // We don't want to cancel the current command when the debugger resumes, // since that command will be resuming the debugger. // Instead let it complete and check the cancellation afterward. @@ -215,17 +245,24 @@ private void RunIdleLoop(in CancellationScope cancellationScope) { try { - while (_executionQueue.TryTake(out ISynchronousTask task)) + while (!cancellationScope.CancellationToken.IsCancellationRequested + && _executionQueue.TryTake(out ISynchronousTask task)) { RunTaskSynchronously(task, cancellationScope.CancellationToken); } + + // TODO: Handle engine events here using a nested pipeline } catch (OperationCanceledException) { } + } - // TODO: Run nested pipeline here for engine event handling + private void RunNextTaskSynchronously(CancellationToken loopCancellationToken) + { + ISynchronousTask task = _executionQueue.Take(loopCancellationToken); + RunTaskSynchronously(task, loopCancellationToken); } private void RunTaskSynchronously(ISynchronousTask task, CancellationToken loopCancellationToken) @@ -237,15 +274,7 @@ private void RunTaskSynchronously(ISynchronousTask task, CancellationToken loopC using (CancellationScope commandCancellationScope = _commandCancellationContext.EnterScope(loopCancellationToken)) { - _taskProcessingLock.EnterReadLock(); - try - { - task.ExecuteSynchronously(commandCancellationScope.CancellationToken); - } - finally - { - _taskProcessingLock.ExitReadLock(); - } + task.ExecuteSynchronously(commandCancellationScope.CancellationToken); } } @@ -259,26 +288,5 @@ public void OnPowerShellIdle() _runIdleLoop = true; _psesHost.PushNonInteractivePowerShell(); } - - private struct TaskProcessingWriterLockLifetime : IDisposable - { - private readonly ReaderWriterLockSlim _rwLock; - - public static TaskProcessingWriterLockLifetime TakeLock(ReaderWriterLockSlim rwLock) - { - rwLock.EnterWriteLock(); - return new TaskProcessingWriterLockLifetime(rwLock); - } - - private TaskProcessingWriterLockLifetime(ReaderWriterLockSlim rwLock) - { - _rwLock = rwLock; - } - - public void Dispose() - { - _rwLock.ExitWriteLock(); - } - } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs index 24c451bc1..eccbfc9e4 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs @@ -20,7 +20,9 @@ internal abstract class SynchronousTask : ISynchronousTask private bool _executionCanceled; - protected SynchronousTask(ILogger logger, CancellationToken cancellationToken) + protected SynchronousTask( + ILogger logger, + CancellationToken cancellationToken) { Logger = logger; _taskCompletionSource = new TaskCompletionSource(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index 19cb815f2..d6ee40813 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -319,17 +319,18 @@ private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspa { if (!runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) { - PopOrReinitializeRunspace(); + PopOrReinitializeRunspaceAsync(); } } - private void PopOrReinitializeRunspace() + private Task PopOrReinitializeRunspaceAsync() { _consoleReplRunner?.SetReplPop(); - _pipelineExecutor.CancelCurrentTask(); - RunspaceStateInfo oldRunspaceState = CurrentPowerShell.Runspace.RunspaceStateInfo; - using (_pipelineExecutor.TakeTaskWriterLock()) + + // Rather than try to lock the PowerShell executor while we alter its state, + // we simply run this on its thread, guaranteeing that no other action can occur + return _pipelineExecutor.RunTaskNextAsync(new SynchronousDelegateTask(_logger, (cancellationToken) => { while (_psFrameStack.Count > 0 && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) @@ -355,7 +356,7 @@ private void PopOrReinitializeRunspace() + " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior." + " The session is now returning to the previous runspace."); } - } + }, nameof(PopOrReinitializeRunspaceAsync), CancellationToken.None)); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 888a1ccd8..aa4c338fb 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -41,7 +41,7 @@ public Task ExecuteDelegateAsync( string representation, CancellationToken cancellationToken) { - return QueueTask(new SynchronousPSDelegateTask(_logger, _psesHost, func, representation, cancellationToken)); + return RunTaskAsync(new SynchronousPSDelegateTask(_logger, _psesHost, func, representation, cancellationToken)); } public Task ExecuteDelegateAsync( @@ -49,7 +49,7 @@ public Task ExecuteDelegateAsync( string representation, CancellationToken cancellationToken) { - return QueueTask(new SynchronousPSDelegateTask(_logger, _psesHost, action, representation, cancellationToken)); + return RunTaskAsync(new SynchronousPSDelegateTask(_logger, _psesHost, action, representation, cancellationToken)); } public Task ExecuteDelegateAsync( @@ -57,7 +57,7 @@ public Task ExecuteDelegateAsync( string representation, CancellationToken cancellationToken) { - return QueueTask(new SynchronousDelegateTask(_logger, func, representation, cancellationToken)); + return RunTaskAsync(new SynchronousDelegateTask(_logger, func, representation, cancellationToken)); } public Task ExecuteDelegateAsync( @@ -65,7 +65,7 @@ public Task ExecuteDelegateAsync( string representation, CancellationToken cancellationToken) { - return QueueTask(new SynchronousDelegateTask(_logger, action, representation, cancellationToken)); + return RunTaskAsync(new SynchronousDelegateTask(_logger, action, representation, cancellationToken)); } public Task> ExecutePSCommandAsync( @@ -73,19 +73,22 @@ public Task> ExecutePSCommandAsync( PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) { - Task> result = QueueTask(new SynchronousPowerShellTask( + if (executionOptions.InterruptCommandPrompt) + { + return CancelCurrentAndRunTaskNowAsync(new SynchronousPowerShellTask( + _logger, + _psesHost, + psCommand, + executionOptions, + cancellationToken)); + } + + return RunTaskAsync(new SynchronousPowerShellTask( _logger, _psesHost, psCommand, executionOptions, cancellationToken)); - - if (executionOptions.InterruptCommandPrompt) - { - _psesHost.CancelCurrentPrompt(); - } - - return result; } public Task ExecutePSCommandAsync( @@ -98,6 +101,8 @@ public void CancelCurrentTask() _pipelineExecutor.CancelCurrentTask(); } - private Task QueueTask(SynchronousTask task) => _pipelineExecutor.QueueTask(task); + private Task RunTaskAsync(SynchronousTask task) => _pipelineExecutor.RunTaskAsync(task); + + private Task CancelCurrentAndRunTaskNowAsync(SynchronousTask task) => _pipelineExecutor.CancelCurrentAndRunTaskNowAsync(task); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/ConcurrentBlockablePriorityQueue.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/ConcurrentBlockablePriorityQueue.cs new file mode 100644 index 000000000..a6e923cc9 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/ConcurrentBlockablePriorityQueue.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace PowerShellEditorServices.Services.PowerShell.Utility +{ + internal class ConcurrentBlockablePriorityQueue + { + private readonly object _priorityLock; + + private readonly ManualResetEventSlim _progressAllowedEvent; + + private readonly LinkedList _priorityItems; + + private readonly BlockingCollection _queue; + + public ConcurrentBlockablePriorityQueue() + { + _priorityLock = new object(); + _progressAllowedEvent = new ManualResetEventSlim(); + _priorityItems = new LinkedList(); + _queue = new BlockingCollection(); + } + + public int Count + { + get + { + lock (_priorityLock) + { + return _priorityItems.Count + _queue.Count; + } + } + } + + public void Enqueue(T item) + { + _queue.Add(item); + } + + public void EnqueuePriority(T item) + { + lock (_priorityLock) + { + _priorityItems.AddLast(item); + } + } + + public void EnqueueNext(T item) + { + lock (_priorityLock) + { + _priorityItems.AddFirst(item); + } + } + + public T Take(CancellationToken cancellationToken) + { + _progressAllowedEvent.Wait(); + + lock (_priorityLock) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (_priorityItems.Count > 0) + { + T item = _priorityItems.First.Value; + _priorityItems.RemoveFirst(); + return item; + } + } + + return _queue.Take(cancellationToken); + } + + public bool TryTake(out T item) + { + if (!_progressAllowedEvent.IsSet) + { + item = default; + return false; + } + + lock (_priorityLock) + { + if (_priorityItems.Count > 0) + { + item = _priorityItems.First.Value; + _priorityItems.RemoveFirst(); + return true; + } + } + + return _queue.TryTake(out item); + } + + public IDisposable BlockConsumers() + { + return PriorityQueueBlockLifetime.StartBlock(_progressAllowedEvent); + } + + private class PriorityQueueBlockLifetime : IDisposable + { + public static PriorityQueueBlockLifetime StartBlock(ManualResetEventSlim blockEvent) + { + blockEvent.Reset(); + return new PriorityQueueBlockLifetime(blockEvent); + } + + private readonly ManualResetEventSlim _blockEvent; + + private PriorityQueueBlockLifetime(ManualResetEventSlim blockEvent) + { + _blockEvent = blockEvent; + } + + public void Dispose() + { + _blockEvent.Set(); + } + } + } +} From abf91219cc85d616d3b502891b8d941bcf1a19d4 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 10 May 2021 15:13:11 -0700 Subject: [PATCH 062/176] Add async error handling code --- .../Handlers/ConfigurationDoneHandler.cs | 4 +-- .../Handlers/DebugEvaluateHandler.cs | 4 +-- .../Utility/AsyncUtils.cs | 34 +++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 574762d80..f566f8c45 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -14,6 +14,7 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.DebugAdapter.Protocol.Events; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; using OmniSharp.Extensions.DebugAdapter.Protocol.Server; @@ -65,9 +66,8 @@ public Task Handle(ConfigurationDoneArguments request if (!string.IsNullOrEmpty(_debugStateService.ScriptToLaunch)) { - // TODO: ContinueWith on this task so that any errors can be handled LaunchScriptAsync(_debugStateService.ScriptToLaunch) - .ConfigureAwait(continueOnCapturedContext: false); + .HandleErrorsAsync(_logger); } if (_debugStateService.IsInteractiveDebugSession) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs index 6f606927e..48507dd32 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs @@ -11,6 +11,7 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Handlers { @@ -46,11 +47,10 @@ public async Task Handle(EvaluateRequestArguments request, if (isFromRepl) { - // TODO: Await this or handle errors from it _executionService.ExecutePSCommandAsync( new PSCommand().AddScript(request.Expression), new PowerShellExecutionOptions { WriteOutputToHost = true }, - CancellationToken.None); + CancellationToken.None).HandleErrorsAsync(_logger); } else { diff --git a/src/PowerShellEditorServices/Utility/AsyncUtils.cs b/src/PowerShellEditorServices/Utility/AsyncUtils.cs index dd16a0afd..2fdff670d 100644 --- a/src/PowerShellEditorServices/Utility/AsyncUtils.cs +++ b/src/PowerShellEditorServices/Utility/AsyncUtils.cs @@ -1,7 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; +using System.Runtime.CompilerServices; using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; namespace Microsoft.PowerShell.EditorServices.Utility { @@ -19,5 +23,35 @@ internal static SemaphoreSlim CreateSimpleLockingSemaphore() { return new SemaphoreSlim(initialCount: 1, maxCount: 1); } + + internal static Task HandleErrorsAsync( + this Task task, + ILogger logger, + [CallerMemberName] string callerName = null, + [CallerFilePath] string callerSourceFile = null, + [CallerLineNumber] int callerLineNumber = -1) + { + return task.IsCompleted && !(task.IsFaulted || task.IsCanceled) + ? task + : LogTaskErrors(task, logger, callerName, callerSourceFile, callerLineNumber); + } + + private static async Task LogTaskErrors(Task task, ILogger logger, string callerName, string callerSourceFile, int callerLineNumber) + { + try + { + await task.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + logger.LogDebug($"Task canceled in '{callerName}' in file '{callerSourceFile}' line {callerLineNumber}"); + throw; + } + catch (Exception e) + { + logger.LogError(e, $"Exception thrown running task in '{callerName}' in file '{callerSourceFile}' line {callerLineNumber}"); + throw; + } + } } } From 225eae7bfe5b7f3ca157017ed91b57da89a3678f Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 21 Jun 2021 15:24:42 -0700 Subject: [PATCH 063/176] Rework execution queue to allow correct cancellation --- .../DebugAdapter/BreakpointService.cs | 10 +- .../Services/DebugAdapter/DebugService.cs | 33 +++-- .../Handlers/ConfigurationDoneHandler.cs | 10 +- .../Handlers/DebugEvaluateHandler.cs | 4 +- .../Handlers/DisconnectHandler.cs | 2 - .../Handlers/LaunchAndAttachHandler.cs | 16 +-- .../Services/Extension/ExtensionService.cs | 13 +- .../PowerShell/Console/ConsoleReadLine.cs | 3 +- .../PowerShell/Console/ConsoleReplRunner.cs | 3 +- .../Debugging/DscBreakpointCapability.cs | 18 ++- .../BlockableConcurrentPriorityQueue.cs | 61 +++++++++ .../Execution/ConcurrentPriorityQueue.cs | 52 ++++++++ .../PowerShell/Execution/ExecutionOptions.cs | 48 +++++++ .../Execution/PipelineThreadExecutor.cs | 89 +++++++------ .../Execution/PowerShellExecutionOptions.cs | 17 --- .../Execution/SynchronousDelegateTask.cs | 34 +++-- .../Execution/SynchronousPowerShellTask.cs | 34 +++-- .../PowerShell/Execution/SynchronousTask.cs | 4 + .../PowerShell/Handlers/EvaluateHandler.cs | 4 +- .../PowerShell/Handlers/ExpandAliasHandler.cs | 2 +- .../PowerShell/Handlers/GetCommandHandler.cs | 2 +- .../PowerShell/Handlers/GetVersionHandler.cs | 6 +- .../PSHostProcessAndRunspaceHandlers.cs | 2 +- .../PowerShell/Handlers/ShowHelpHandler.cs | 2 +- .../Host/EditorServicesConsolePSHost.cs | 70 +++++----- .../PowerShell/PowerShellExecutionService.cs | 50 +++---- .../PowerShell/Utility/CancellationContext.cs | 8 ++ .../PowerShell/Utility/CommandHelpers.cs | 4 +- .../ConcurrentBlockablePriorityQueue.cs | 125 ------------------ .../Utility/PowerShellExtensions.cs | 17 +-- .../Services/Symbols/Vistors/AstOperations.cs | 25 ++-- .../Services/Template/TemplateService.cs | 9 +- .../Workspace/RemoteFileManagerService.cs | 5 +- .../Utility/IsExternalInit.cs | 7 + 34 files changed, 440 insertions(+), 349 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/BlockableConcurrentPriorityQueue.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/ConcurrentPriorityQueue.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Utility/ConcurrentBlockablePriorityQueue.cs create mode 100644 src/PowerShellEditorServices/Utility/IsExternalInit.cs diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs index 01419754d..96b1a6786 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs @@ -54,7 +54,7 @@ public async Task> GetBreakpointsAsync() // Legacy behavior PSCommand psCommand = new PSCommand(); psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint"); - IEnumerable breakpoints = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None); + IEnumerable breakpoints = await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None); return breakpoints.ToList(); } @@ -140,7 +140,7 @@ public async Task> SetBreakpointsAsync(string esc if (psCommand != null) { IEnumerable setBreakpoints = - await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None); + await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None); configuredBreakpoints.AddRange( setBreakpoints.Select((breakpoint) => BreakpointDetails.Create(breakpoint)) ); @@ -218,7 +218,7 @@ public async Task> SetCommandBreakpoints(I if (psCommand != null) { IEnumerable setBreakpoints = - await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None); + await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None); configuredBreakpoints.AddRange( setBreakpoints.Select(CommandBreakpointDetails.Create)); } @@ -263,7 +263,7 @@ public async Task RemoveAllBreakpointsAsync(string scriptPath = null) psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Remove-PSBreakpoint"); - await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); + await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None).ConfigureAwait(false); } catch (Exception e) { @@ -309,7 +309,7 @@ public async Task RemoveBreakpointsAsync(IEnumerable breakpoints) psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Remove-PSBreakpoint"); psCommand.AddParameter("Id", breakpoints.Select(b => b.Id).ToArray()); - await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None); + await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None); } } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 798c13215..9db53c00e 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -408,7 +408,7 @@ public async Task SetVariableAsync(int variableContainerReferenceId, str // Evaluate the expression to get back a PowerShell object from the expression string. // This may throw, in which case the exception is propagated to the caller PSCommand evaluateExpressionCommand = new PSCommand().AddScript(value); - object expressionResult = (await _executionService.ExecutePSCommandAsync(evaluateExpressionCommand, new PowerShellExecutionOptions(), CancellationToken.None)).FirstOrDefault(); + object expressionResult = (await _executionService.ExecutePSCommandAsync(evaluateExpressionCommand, CancellationToken.None)).FirstOrDefault(); // If PowerShellContext.ExecuteCommand returns an ErrorRecord as output, the expression failed evaluation. // Ideally we would have a separate means from communicating error records apart from normal output. @@ -468,7 +468,7 @@ public async Task SetVariableAsync(int variableContainerReferenceId, str .AddParameter("Name", name.TrimStart('$')) .AddParameter("Scope", scope); - PSVariable psVariable = (await _executionService.ExecutePSCommandAsync(getVariableCommand, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false)).FirstOrDefault(); + PSVariable psVariable = (await _executionService.ExecutePSCommandAsync(getVariableCommand, CancellationToken.None).ConfigureAwait(false)).FirstOrDefault(); if (psVariable == null) { throw new Exception($"Failed to retrieve PSVariable object for '{name}' from scope '{scope}'."); @@ -494,15 +494,20 @@ public async Task SetVariableAsync(int variableContainerReferenceId, str { _logger.LogTrace($"Setting variable '{name}' using conversion to value: {expressionResult ?? ""}"); - psVariable.Value = await _executionService.ExecuteDelegateAsync((pwsh, cancellationToken) => - { - var engineIntrinsics = (EngineIntrinsics)pwsh.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); + psVariable.Value = await _executionService.ExecuteDelegateAsync( + "PS debugger argument converter", + ExecutionOptions.Default, + CancellationToken.None, + (pwsh, cancellationToken) => + { + var engineIntrinsics = (EngineIntrinsics)pwsh.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); + + // TODO: This is almost (but not quite) the same as LanguagePrimitives.Convert(), which does not require the pipeline thread. + // We should investigate changing it. + return argTypeConverterAttr.Transform(engineIntrinsics, expressionResult); - // TODO: This is almost (but not quite) the same as LanguagePrimitives.Convert(), which does not require the pipeline thread. - // We should investigate changing it. - return argTypeConverterAttr.Transform(engineIntrinsics, expressionResult); + }).ConfigureAwait(false); - }, "PS debugger argument converter", CancellationToken.None).ConfigureAwait(false); } else { @@ -537,8 +542,8 @@ public async Task EvaluateExpressionAsync( var command = new PSCommand().AddScript(expressionString); IReadOnlyList results = await _executionService.ExecutePSCommandAsync( command, - new PowerShellExecutionOptions { WriteOutputToHost = writeResultAsOutput }, - CancellationToken.None).ConfigureAwait(false); + CancellationToken.None, + new PowerShellExecutionOptions { WriteOutputToHost = true }).ConfigureAwait(false); // Since this method should only be getting invoked in the debugger, // we can assume that Out-String will be getting used to format results @@ -685,7 +690,7 @@ private async Task FetchVariableContainerAsync( var scopeVariableContainer = new VariableContainerDetails(this.nextVariableId++, "Scope: " + scope); this.variables.Add(scopeVariableContainer); - IReadOnlyList results = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None) + IReadOnlyList results = await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None) .ConfigureAwait(false); if (results != null) @@ -791,7 +796,7 @@ private async Task FetchStackFramesAsync(string scriptNameOverride) var callStackVarName = $"$global:{PsesGlobalVariableNamePrefix}CallStack"; psCommand.AddScript($"{callStackVarName} = Get-PSCallStack; {callStackVarName}"); - var results = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); + var results = await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None).ConfigureAwait(false); var callStackFrames = results.ToArray(); @@ -876,7 +881,7 @@ internal async void OnDebuggerStopAsync(object sender, DebuggerStopEventArgs e) IReadOnlyList scriptListingLines = await _executionService.ExecutePSCommandAsync( - command, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); + command, CancellationToken.None).ConfigureAwait(false); if (scriptListingLines != null) { diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index f566f8c45..04f4cc47e 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -110,7 +110,7 @@ private async Task LaunchScriptAsync(string scriptToLaunch) // This seems to be the simplest way to invoke a script block (which contains breakpoint information) via the PowerShell API. var cmd = new PSCommand().AddScript(". $args[0]").AddArgument(ast.GetScriptBlock()); await _executionService - .ExecutePSCommandAsync(cmd, new PowerShellExecutionOptions { WriteOutputToHost = true }, CancellationToken.None) + .ExecutePSCommandAsync(cmd, CancellationToken.None, new PowerShellExecutionOptions { WriteOutputToHost = true }) .ConfigureAwait(false); } else @@ -118,8 +118,8 @@ await _executionService await _executionService .ExecutePSCommandAsync( new PSCommand().AddScript(untitledScript.Contents), - new PowerShellExecutionOptions { WriteOutputToHost = true }, - CancellationToken.None) + CancellationToken.None, + new PowerShellExecutionOptions { WriteOutputToHost = true}) .ConfigureAwait(false); } } @@ -128,8 +128,8 @@ await _executionService await _executionService .ExecutePSCommandAsync( BuildPSCommandFromArguments(scriptToLaunch, _debugStateService.Arguments), - new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true }, - CancellationToken.None) + CancellationToken.None, + new PowerShellExecutionOptions { WriteOutputToHost = true, WriteInputToHost = true, AddToHistory = true }) .ConfigureAwait(false); } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs index 48507dd32..e5304225e 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs @@ -49,8 +49,8 @@ public async Task Handle(EvaluateRequestArguments request, { _executionService.ExecutePSCommandAsync( new PSCommand().AddScript(request.Expression), - new PowerShellExecutionOptions { WriteOutputToHost = true }, - CancellationToken.None).HandleErrorsAsync(_logger); + CancellationToken.None, + new PowerShellExecutionOptions { WriteOutputToHost = true }).HandleErrorsAsync(_logger); } else { diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs index b24abe10f..28e812d6a 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs @@ -62,7 +62,6 @@ public async Task Handle(DisconnectArguments request, Cancel { await _executionService.ExecutePSCommandAsync( new PSCommand().AddCommand("Exit-PSHostProcess"), - new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); if (_debugStateService.IsRemoteAttach && @@ -70,7 +69,6 @@ await _executionService.ExecutePSCommandAsync( { await _executionService.ExecutePSCommandAsync( new PSCommand().AddCommand("Exit-PSSession"), - new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); } } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index 1596d1c66..6e208a223 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -165,7 +165,7 @@ public async Task Handle(PsesLaunchRequestArguments request, Can if (!string.IsNullOrEmpty(workingDir)) { var setDirCommand = new PSCommand().AddCommand("Set-Location").AddParameter("LiteralPath", workingDir); - await _executionService.ExecutePSCommandAsync(setDirCommand, new PowerShellExecutionOptions(), cancellationToken); + await _executionService.ExecutePSCommandAsync(setDirCommand, cancellationToken); } _logger.LogTrace("Working dir " + (string.IsNullOrEmpty(workingDir) ? "not set." : $"set to '{workingDir}'")); @@ -252,7 +252,7 @@ public async Task Handle(PsesAttachRequestArguments request, Can try { - await _executionService.ExecutePSCommandAsync(enterPSSessionCommand, new PowerShellExecutionOptions(), cancellationToken).ConfigureAwait(false); + await _executionService.ExecutePSCommandAsync(enterPSSessionCommand, cancellationToken).ConfigureAwait(false); } catch (Exception e) { @@ -277,7 +277,7 @@ public async Task Handle(PsesAttachRequestArguments request, Can try { - await _executionService.ExecutePSCommandAsync(enterPSHostProcessCommand, new PowerShellExecutionOptions(), cancellationToken).ConfigureAwait(false); + await _executionService.ExecutePSCommandAsync(enterPSHostProcessCommand, cancellationToken).ConfigureAwait(false); } catch (Exception e) { @@ -299,7 +299,7 @@ public async Task Handle(PsesAttachRequestArguments request, Can try { - await _executionService.ExecutePSCommandAsync(enterPSHostProcessCommand, new PowerShellExecutionOptions(), cancellationToken); + await _executionService.ExecutePSCommandAsync(enterPSHostProcessCommand, cancellationToken); } catch (Exception e) { @@ -330,7 +330,7 @@ public async Task Handle(PsesAttachRequestArguments request, Can .AddCommand("Microsoft.PowerShell.Utility\\Select-Object") .AddParameter("ExpandProperty", "Id"); - IEnumerable ids = await _executionService.ExecutePSCommandAsync(getRunspaceIdCommand, new PowerShellExecutionOptions(), cancellationToken).ConfigureAwait(false); + IEnumerable ids = await _executionService.ExecutePSCommandAsync(getRunspaceIdCommand, cancellationToken).ConfigureAwait(false); foreach (var id in ids) { _debugStateService.RunspaceId = id; @@ -368,7 +368,7 @@ public async Task Handle(PsesAttachRequestArguments request, Can _debugStateService.WaitingForAttach = true; Task nonAwaitedTask = _executionService - .ExecutePSCommandAsync(debugRunspaceCmd, new PowerShellExecutionOptions(), CancellationToken.None) + .ExecutePSCommandAsync(debugRunspaceCmd, CancellationToken.None) .ContinueWith(OnExecutionCompletedAsync); if (runspaceVersion.Version.Major >= 7) @@ -423,12 +423,12 @@ private async Task OnExecutionCompletedAsync(Task executeTask) { try { - await _executionService.ExecutePSCommandAsync(new PSCommand().AddCommand("Exit-PSHostProcess"), new PowerShellExecutionOptions(), CancellationToken.None); + await _executionService.ExecutePSCommandAsync(new PSCommand().AddCommand("Exit-PSHostProcess"), CancellationToken.None); if (_debugStateService.IsRemoteAttach && _runspaceContext.CurrentRunspace.RunspaceOrigin != RunspaceOrigin.Local) { - await _executionService.ExecutePSCommandAsync(new PSCommand().AddCommand("Exit-PSSession"), new PowerShellExecutionOptions(), CancellationToken.None); + await _executionService.ExecutePSCommandAsync(new PSCommand().AddCommand("Exit-PSSession"), CancellationToken.None); } } catch (Exception e) diff --git a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs index c4d092e6d..01966203b 100644 --- a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs @@ -104,10 +104,14 @@ internal Task InitializeAsync() EditorObject.SetAsStaticInstance(); // Register the editor object in the runspace - return ExecutionService.ExecuteDelegateAsync((pwsh, cancellationToken) => + return ExecutionService.ExecuteDelegateAsync( + "Create $psEditorObject", + ExecutionOptions.Default, + CancellationToken.None, + (pwsh, cancellationToken) => { pwsh.Runspace.SessionStateProxy.PSVariable.Set("psEditor", EditorObject); - }, representation: "Set PSEditor", CancellationToken.None); + }); } /// @@ -128,8 +132,9 @@ public async Task InvokeCommandAsync(string commandName, EditorContext editorCon await ExecutionService.ExecutePSCommandAsync( executeCommand, - new PowerShellExecutionOptions { WriteOutputToHost = !editorCommand.SuppressOutput, }, - CancellationToken.None).ConfigureAwait(false); + CancellationToken.None, + new PowerShellExecutionOptions { WriteOutputToHost = !editorCommand.SuppressOutput }) + .ConfigureAwait(false); } else { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index 14e6610bf..54f469e27 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -165,7 +165,7 @@ private static Task ReadKeyAsync(CancellationToken cancellationT private Task ReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) { - return _executionService.ExecuteDelegateAsync(InvokePSReadLine, representation: "ReadLine", cancellationToken); + return _executionService.ExecuteDelegateAsync(representation: "ReadLine", new ExecutionOptions { MustRunInForeground = true }, cancellationToken, InvokePSReadLine); } private string InvokePSReadLine(CancellationToken cancellationToken) @@ -366,7 +366,6 @@ internal async Task InvokeLegacyReadLineAsync(bool isCommandLine, Cancel currentHistory = await _executionService.ExecutePSCommandAsync( command, - new PowerShellExecutionOptions(), cancellationToken).ConfigureAwait(false); if (currentHistory != null) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs index a226a80b5..0f3900b1f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs @@ -180,7 +180,6 @@ private Task> GetPromptOutputAsync(CancellationToken cance return _executionService.ExecutePSCommandAsync( promptCommand, - new PowerShellExecutionOptions(), cancellationToken); } @@ -203,7 +202,7 @@ private Task InvokeInputAsync(string input, CancellationToken cancellationToken) AddToHistory = true, }; - return _executionService.ExecutePSCommandAsync(command, executionOptions, cancellationToken); + return _executionService.ExecutePSCommandAsync(command, cancellationToken, executionOptions); } public void SetReplPop() diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index 4208311c5..e4d40fdfb 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -67,7 +67,6 @@ public async Task SetLineBreakpointsAsync( await executionService.ExecutePSCommandAsync( dscCommand, - new PowerShellExecutionOptions(), CancellationToken.None); // Verify all the breakpoints and return them @@ -103,6 +102,12 @@ public static async Task GetDscCapabilityAsync( Func getDscBreakpointCapabilityFunc = (pwsh, cancellationToken) => { + var invocationSettings = new PSInvocationSettings + { + AddToHistory = false, + ErrorActionPreference = ActionPreference.Stop + }; + PSModuleInfo dscModule = null; try { @@ -110,7 +115,7 @@ public static async Task GetDscCapabilityAsync( .AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1") .AddParameter("PassThru") .AddParameter("ErrorAction", "Ignore") - .InvokeAndClear() + .InvokeAndClear(invocationSettings) .FirstOrDefault(); } catch (RuntimeException e) @@ -131,7 +136,7 @@ public static async Task GetDscCapabilityAsync( pwsh.AddCommand("Microsoft.PowerShell.Utility\\Write-Host") .AddArgument("Gathering DSC resource paths, this may take a while...") - .InvokeAndClear(); + .InvokeAndClear(invocationSettings); Collection resourcePaths = null; try @@ -140,7 +145,7 @@ public static async Task GetDscCapabilityAsync( resourcePaths = pwsh.AddCommand("Get-DscResource") .AddCommand("Select-Object") .AddParameter("ExpandProperty", "ParentPath") - .InvokeAndClear(); + .InvokeAndClear(invocationSettings); } catch (CmdletInvocationException e) { @@ -161,9 +166,10 @@ public static async Task GetDscCapabilityAsync( }; return await executionService.ExecuteDelegateAsync( - getDscBreakpointCapabilityFunc, nameof(getDscBreakpointCapabilityFunc), - cancellationToken); + ExecutionOptions.Default, + cancellationToken, + getDscBreakpointCapabilityFunc); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockableConcurrentPriorityQueue.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockableConcurrentPriorityQueue.cs new file mode 100644 index 000000000..e0e4425c0 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockableConcurrentPriorityQueue.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading; + +namespace PowerShellEditorServices.Services.PowerShell.Utility +{ + internal class BlockableConcurrentPriorityQueue : ConcurrentPriorityQueue + { + private readonly ManualResetEventSlim _progressAllowedEvent; + + public BlockableConcurrentPriorityQueue() + : base() + { + // Start the reset event in the set state, meaning not blocked + _progressAllowedEvent = new ManualResetEventSlim(initialState: true); + } + + public IDisposable BlockConsumers() + { + return PriorityQueueBlockLifetime.StartBlocking(_progressAllowedEvent); + } + + public override T Take(CancellationToken cancellationToken) + { + _progressAllowedEvent.Wait(); + + return base.Take(cancellationToken); + } + + public override bool TryTake(out T item) + { + if (!_progressAllowedEvent.IsSet) + { + item = default; + return false; + } + + return base.TryTake(out item); + } + + private class PriorityQueueBlockLifetime : IDisposable + { + public static PriorityQueueBlockLifetime StartBlocking(ManualResetEventSlim blockEvent) + { + blockEvent.Reset(); + return new PriorityQueueBlockLifetime(blockEvent); + } + + private readonly ManualResetEventSlim _blockEvent; + + private PriorityQueueBlockLifetime(ManualResetEventSlim blockEvent) + { + _blockEvent = blockEvent; + } + + public void Dispose() + { + _blockEvent.Set(); + } + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/ConcurrentPriorityQueue.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/ConcurrentPriorityQueue.cs new file mode 100644 index 000000000..a526c4b97 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/ConcurrentPriorityQueue.cs @@ -0,0 +1,52 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace PowerShellEditorServices.Services.PowerShell.Utility +{ + internal class ConcurrentPriorityQueue + { + private readonly ConcurrentStack _priorityItems; + + private readonly BlockingCollection _queue; + + public ConcurrentPriorityQueue() + { + _priorityItems = new ConcurrentStack(); + _queue = new BlockingCollection(); + } + + public int Count => _priorityItems.Count + _queue.Count; + + public void Append(T item) + { + _queue.Add(item); + } + + public void Prepend(T item) + { + _priorityItems.Push(item); + } + + public virtual T Take(CancellationToken cancellationToken) + { + if (_priorityItems.TryPop(out T item)) + { + return item; + } + + return _queue.Take(cancellationToken); + } + + public virtual bool TryTake(out T item) + { + if (_priorityItems.TryPop(out item)) + { + return true; + } + + return _queue.TryTake(out item); + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs new file mode 100644 index 000000000..e37fff59c --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs @@ -0,0 +1,48 @@ +using System; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + public enum ExecutionPriority + { + Normal, + Next, + } + + public record ExecutionOptions + { + public static ExecutionOptions Default = new() + { + Priority = ExecutionPriority.Normal, + MustRunInForeground = false, + InterruptCurrentForeground = false, + }; + + public ExecutionPriority Priority { get; init; } + + public bool MustRunInForeground { get; init; } + + public bool InterruptCurrentForeground { get; init; } + } + + public record PowerShellExecutionOptions : ExecutionOptions + { + public static new PowerShellExecutionOptions Default = new() + { + Priority = ExecutionPriority.Normal, + MustRunInForeground = false, + InterruptCurrentForeground = false, + WriteOutputToHost = false, + WriteInputToHost = false, + WriteErrorsToHost = false, + AddToHistory = false, + }; + + public bool WriteOutputToHost { get; init; } + + public bool WriteInputToHost { get; init; } + + public bool WriteErrorsToHost { get; init; } + + public bool AddToHistory { get; init; } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs index dab052b80..0f0195791 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs @@ -1,19 +1,15 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using System; -using System.Collections.Concurrent; using System.Management.Automation; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using PowerShellEditorServices.Services.PowerShell.Utility; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using System.Runtime.CompilerServices; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution { @@ -36,7 +32,9 @@ internal class PipelineThreadExecutor private readonly HostStartupInfo _hostInfo; - private readonly ConcurrentBlockablePriorityQueue _executionQueue; + private readonly BlockableConcurrentPriorityQueue _foregroundExecutionQueue; + + private readonly ConcurrentPriorityQueue _backgroundExecutionQueue; private readonly CancellationTokenSource _consumerThreadCancellationSource; @@ -61,7 +59,8 @@ public PipelineThreadExecutor( _psesHost = psesHost; _readLineProvider = readLineProvider; _consumerThreadCancellationSource = new CancellationTokenSource(); - _executionQueue = new ConcurrentBlockablePriorityQueue(); + _foregroundExecutionQueue = new BlockableConcurrentPriorityQueue(); + _backgroundExecutionQueue = new ConcurrentPriorityQueue(); _loopCancellationContext = new CancellationContext(); _commandCancellationContext = new CancellationContext(); _taskProcessingAllowed = new ManualResetEventSlim(initialState: true); @@ -77,38 +76,27 @@ public PipelineThreadExecutor( public Task RunTaskAsync(SynchronousTask synchronousTask) { - _executionQueue.Enqueue(synchronousTask); - return synchronousTask.Task; - } - - public Task RunTaskNextAsync(SynchronousTask synchronousTask) - { - _executionQueue.EnqueueNext(synchronousTask); - return synchronousTask.Task; - } + if (synchronousTask.ExecutionOptions.InterruptCurrentForeground) + { + return CancelCurrentAndRunTaskNowAsync(synchronousTask); + } - public Task CancelCurrentAndRunTaskNowAsync(SynchronousTask synchronousTask) - { - // We need to ensure that we don't: - // - Add this command to the queue and immediately cancel it - // - Allow a consumer to dequeue and run another command after cancellation and before we add this command - // - // To ensure that, we need the following sequence: - // - Stop queue consumption progressing - // - Cancel any current processing - // - Add our task to the front of the queue - // - Recommence processing + ConcurrentPriorityQueue executionQueue = synchronousTask.ExecutionOptions.MustRunInForeground + ? _foregroundExecutionQueue + : _backgroundExecutionQueue; - using (_executionQueue.BlockConsumers()) + switch (synchronousTask.ExecutionOptions.Priority) { - CancelCurrentTask(); + case ExecutionPriority.Next: + executionQueue.Prepend(synchronousTask); + break; - // NOTE: - // This must not be awaited - // We only need to block consumers while we add to the queue - // Awaiting this will deadlock, since the runner can't progress while we block consumers - return RunTaskNextAsync(synchronousTask); + case ExecutionPriority.Normal: + executionQueue.Append(synchronousTask); + break; } + + return synchronousTask.Task; } public void Start() @@ -132,6 +120,27 @@ public void Dispose() Stop(); } + private Task CancelCurrentAndRunTaskNowAsync(SynchronousTask synchronousTask) + { + // We need to ensure that we don't: + // - Add this command to the queue and immediately cancel it + // - Allow a consumer to dequeue and run another command after cancellation and before we add this command + // + // To ensure that, we need the following sequence: + // - Stop queue consumption progressing + // - Cancel any current processing + // - Add our task to the front of the queue + // - Recommence processing + + using (_foregroundExecutionQueue.BlockConsumers()) + { + _commandCancellationContext.CancelCurrentTaskStack(); + + _foregroundExecutionQueue.Prepend(synchronousTask); + return synchronousTask.Task; + } + } + private void Run() { _psesHost.PushInitialPowerShell(); @@ -180,7 +189,7 @@ private void RunTopLevelConsumerLoop() { while (true) { - RunNextTaskSynchronously(cancellationScope.CancellationToken); + RunNextForegroundTaskSynchronously(cancellationScope.CancellationToken); } } catch (OperationCanceledException) @@ -196,7 +205,7 @@ private void RunNestedLoop(in CancellationScope cancellationScope) { while (true) { - RunNextTaskSynchronously(cancellationScope.CancellationToken); + RunNextForegroundTaskSynchronously(cancellationScope.CancellationToken); if (IsExiting) { @@ -218,7 +227,7 @@ private void RunDebugLoop(in CancellationScope cancellationScope) // Run commands, but cancelling our blocking wait if the debugger resumes while (true) { - ISynchronousTask task = _executionQueue.Take(_psesHost.DebugContext.OnResumeCancellationToken); + ISynchronousTask task = _foregroundExecutionQueue.Take(_psesHost.DebugContext.OnResumeCancellationToken); // We don't want to cancel the current command when the debugger resumes, // since that command will be resuming the debugger. @@ -246,7 +255,7 @@ private void RunIdleLoop(in CancellationScope cancellationScope) try { while (!cancellationScope.CancellationToken.IsCancellationRequested - && _executionQueue.TryTake(out ISynchronousTask task)) + && _backgroundExecutionQueue.TryTake(out ISynchronousTask task)) { RunTaskSynchronously(task, cancellationScope.CancellationToken); } @@ -259,9 +268,9 @@ private void RunIdleLoop(in CancellationScope cancellationScope) } } - private void RunNextTaskSynchronously(CancellationToken loopCancellationToken) + private void RunNextForegroundTaskSynchronously(CancellationToken loopCancellationToken) { - ISynchronousTask task = _executionQueue.Take(loopCancellationToken); + ISynchronousTask task = _foregroundExecutionQueue.Take(loopCancellationToken); RunTaskSynchronously(task, loopCancellationToken); } @@ -280,7 +289,7 @@ private void RunTaskSynchronously(ISynchronousTask task, CancellationToken loopC public void OnPowerShellIdle() { - if (_executionQueue.Count == 0) + if (_backgroundExecutionQueue.Count == 0) { return; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs deleted file mode 100644 index a96565679..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutionOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution -{ - public struct PowerShellExecutionOptions - { - public bool WriteOutputToHost { get; set; } - - public bool AddToHistory { get; set; } - - public bool WriteInputToHost { get; set; } - - public bool PropagateCancellationToCaller { get; set; } - - public bool InterruptCommandPrompt { get; set; } - - public bool NoDebuggerExecution { get; set; } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs index 305850a96..cee944250 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs @@ -15,15 +15,19 @@ internal class SynchronousDelegateTask : SynchronousTask public SynchronousDelegateTask( ILogger logger, - Action action, string representation, - CancellationToken cancellationToken) + ExecutionOptions executionOptions, + CancellationToken cancellationToken, + Action action) : base(logger, cancellationToken) { - _action = action; + ExecutionOptions = executionOptions; _representation = representation; + _action = action; } + public override ExecutionOptions ExecutionOptions { get; } + public override object Run(CancellationToken cancellationToken) { _action(cancellationToken); @@ -44,15 +48,19 @@ internal class SynchronousDelegateTask : SynchronousTask public SynchronousDelegateTask( ILogger logger, - Func func, string representation, - CancellationToken cancellationToken) + ExecutionOptions executionOptions, + CancellationToken cancellationToken, + Func func) : base(logger, cancellationToken) { _func = func; _representation = representation; + ExecutionOptions = executionOptions; } + public override ExecutionOptions ExecutionOptions { get; } + public override TResult Run(CancellationToken cancellationToken) { return _func(cancellationToken); @@ -75,16 +83,20 @@ internal class SynchronousPSDelegateTask : SynchronousTask public SynchronousPSDelegateTask( ILogger logger, EditorServicesConsolePSHost psesHost, - Action action, string representation, - CancellationToken cancellationToken) + ExecutionOptions executionOptions, + CancellationToken cancellationToken, + Action action) : base(logger, cancellationToken) { _psesHost = psesHost; _action = action; _representation = representation; + ExecutionOptions = executionOptions; } + public override ExecutionOptions ExecutionOptions { get; } + public override object Run(CancellationToken cancellationToken) { _action(_psesHost.CurrentPowerShell, cancellationToken); @@ -108,16 +120,20 @@ internal class SynchronousPSDelegateTask : SynchronousTask public SynchronousPSDelegateTask( ILogger logger, EditorServicesConsolePSHost psesHost, - Func func, string representation, - CancellationToken cancellationToken) + ExecutionOptions executionOptions, + CancellationToken cancellationToken, + Func func) : base(logger, cancellationToken) { _psesHost = psesHost; _func = func; _representation = representation; + ExecutionOptions = executionOptions; } + public override ExecutionOptions ExecutionOptions { get; } + public override TResult Run(CancellationToken cancellationToken) { return _func(_psesHost.CurrentPowerShell, cancellationToken); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index a74c98b6f..d402b7aee 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -19,8 +19,6 @@ internal class SynchronousPowerShellTask : SynchronousTask PowerShellExecutionOptions; + public override IReadOnlyList Run(CancellationToken cancellationToken) { _pwsh = _psesHost.CurrentPowerShell; - if (_executionOptions.WriteInputToHost) + if (PowerShellExecutionOptions.WriteInputToHost) { _psesHost.UI.WriteLine(_psCommand.GetInvocationText()); } - return !_executionOptions.NoDebuggerExecution && _pwsh.Runspace.Debugger.InBreakpoint + return _pwsh.Runspace.Debugger.InBreakpoint ? ExecuteInDebugger(cancellationToken) : ExecuteNormally(cancellationToken); } @@ -58,7 +60,7 @@ public override string ToString() private IReadOnlyList ExecuteNormally(CancellationToken cancellationToken) { - if (_executionOptions.WriteOutputToHost) + if (PowerShellExecutionOptions.WriteOutputToHost) { _psCommand.AddOutputCommand(); } @@ -68,7 +70,17 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok Collection result = null; try { - result = _pwsh.InvokeCommand(_psCommand); + var invocationSettings = new PSInvocationSettings + { + AddToHistory = PowerShellExecutionOptions.AddToHistory, + }; + + if (!PowerShellExecutionOptions.WriteErrorsToHost) + { + invocationSettings.ErrorActionPreference = ActionPreference.Stop; + } + + result = _pwsh.InvokeCommand(_psCommand, invocationSettings); cancellationToken.ThrowIfCancellationRequested(); } // Test if we've been cancelled. If we're remoting, PSRemotingDataStructureException effectively means the pipeline was stopped. @@ -82,7 +94,7 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok { Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}"); - if (!_executionOptions.WriteOutputToHost) + if (!PowerShellExecutionOptions.WriteErrorsToHost) { throw; } @@ -113,7 +125,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT // Out-Default doesn't work as needed in the debugger // Instead we add Out-String to the command and collect results in a PSDataCollection // and use the event handler to print output to the UI as its added to that collection - if (_executionOptions.WriteOutputToHost) + if (PowerShellExecutionOptions.WriteOutputToHost) { _psCommand.AddDebugOutputCommand(); @@ -149,7 +161,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT { Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}"); - if (!_executionOptions.WriteOutputToHost) + if (!PowerShellExecutionOptions.WriteErrorsToHost) { throw; } @@ -180,7 +192,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT _psesHost.DebugContext.ProcessDebuggerResult(debuggerResult); // Optimisation to save wasted computation if we're going to throw the output away anyway - if (_executionOptions.WriteOutputToHost) + if (PowerShellExecutionOptions.WriteOutputToHost) { return Array.Empty(); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs index eccbfc9e4..b1a22c4a9 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs @@ -10,6 +10,8 @@ internal interface ISynchronousTask bool IsCanceled { get; } void ExecuteSynchronously(CancellationToken threadCancellationToken); + + ExecutionOptions ExecutionOptions { get; } } internal abstract class SynchronousTask : ISynchronousTask @@ -36,6 +38,8 @@ protected SynchronousTask( public bool IsCanceled => _executionCanceled || _taskRequesterCancellationToken.IsCancellationRequested; + public abstract ExecutionOptions ExecutionOptions { get; } + public abstract TResult Run(CancellationToken cancellationToken); public abstract override string ToString(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs index 4fce47658..c2bcbb2f1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs @@ -34,8 +34,8 @@ public Task Handle(EvaluateRequestArguments request, Cance _executionService.ExecutePSCommandAsync( new PSCommand().AddScript(request.Expression), - new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true, InterruptCommandPrompt = true }, - CancellationToken.None); + CancellationToken.None, + new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true }); return Task.FromResult(new EvaluateResponseBody { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs index 9bc7c0c9f..0db866d85 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs @@ -71,7 +71,7 @@ function __Expand-Alias { .AddStatement() .AddCommand("__Expand-Alias") .AddArgument(request.Text); - var result = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), cancellationToken).ConfigureAwait(false); + var result = await _executionService.ExecutePSCommandAsync(psCommand, cancellationToken).ConfigureAwait(false); return new ExpandAliasResult { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs index bbf3b862f..42d930769 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs @@ -55,7 +55,7 @@ public async Task> Handle(GetCommandParams request, Cance .AddCommand("Microsoft.PowerShell.Utility\\Sort-Object") .AddParameter("Property", "Name"); - IEnumerable result = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), cancellationToken).ConfigureAwait(false); + IEnumerable result = await _executionService.ExecutePSCommandAsync(psCommand, cancellationToken).ConfigureAwait(false); var commandList = new List(); if (result != null) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs index 1f7c18218..21113ede8 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs @@ -85,7 +85,7 @@ private enum PowerShellProcessArchitecture private async Task CheckPackageManagement() { PSCommand getModule = new PSCommand().AddCommand("Get-Module").AddParameter("ListAvailable").AddParameter("Name", "PackageManagement"); - foreach (PSModuleInfo module in await _executionService.ExecutePSCommandAsync(getModule, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false)) + foreach (PSModuleInfo module in await _executionService.ExecutePSCommandAsync(getModule, CancellationToken.None).ConfigureAwait(false)) { // The user has a good enough version of PackageManagement if (module.Version >= s_desiredPackageManagementVersion) @@ -126,8 +126,8 @@ private async Task CheckPackageManagement() await _executionService.ExecutePSCommandAsync( command, - new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true }, - CancellationToken.None).ConfigureAwait(false); + CancellationToken.None, + new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true }).ConfigureAwait(false); // TODO: Error handling here diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs index 9745d96f0..e99839460 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs @@ -88,7 +88,7 @@ public async Task Handle(GetRunspaceParams request, Cancella { var psCommand = new PSCommand().AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace"); // returns (not deserialized) Runspaces. For simpler code, we use PSObject and rely on dynamic later. - runspaces = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), cancellationToken).ConfigureAwait(false); + runspaces = await _executionService.ExecutePSCommandAsync(psCommand, cancellationToken).ConfigureAwait(false); } var runspaceResponses = new List(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs index 0f8500f9d..54ef54ea0 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs @@ -74,7 +74,7 @@ public async Task Handle(ShowHelpParams request, CancellationToken cancell // TODO: Rather than print the help in the console, we should send the string back // to VSCode to display in a help pop-up (or similar) - await _executionService.ExecutePSCommandAsync(checkHelpPSCommand, new PowerShellExecutionOptions { WriteOutputToHost = true }, cancellationToken).ConfigureAwait(false); + await _executionService.ExecutePSCommandAsync(checkHelpPSCommand, cancellationToken, new PowerShellExecutionOptions { WriteOutputToHost = true }).ConfigureAwait(false); return Unit.Value; } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index d6ee40813..1a35ec52e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -173,7 +173,6 @@ internal Task SetInitialWorkingDirectoryAsync(string path, CancellationToken can return ExecutionService.ExecutePSCommandAsync( new PSCommand().AddCommand("Set-Location").AddParameter("LiteralPath", path), - new PowerShellExecutionOptions(), cancellationToken); } @@ -190,10 +189,14 @@ public async Task StartAsync(HostStartOptions hostStartOptions, CancellationToke if (hostStartOptions.LoadProfiles) { - await ExecutionService.ExecuteDelegateAsync((pwsh, delegateCancellation) => - { - pwsh.LoadProfiles(_hostInfo.ProfilePaths); - }, "LoadProfiles", cancellationToken).ConfigureAwait(false); + await ExecutionService.ExecuteDelegateAsync( + "LoadProfiles", + ExecutionOptions.Default, + cancellationToken, + (pwsh, delegateCancellation) => + { + pwsh.LoadProfiles(_hostInfo.ProfilePaths); + }).ConfigureAwait(false); _logger.LogInformation("Profiles loaded"); } @@ -330,33 +333,38 @@ private Task PopOrReinitializeRunspaceAsync() // Rather than try to lock the PowerShell executor while we alter its state, // we simply run this on its thread, guaranteeing that no other action can occur - return _pipelineExecutor.RunTaskNextAsync(new SynchronousDelegateTask(_logger, (cancellationToken) => - { - while (_psFrameStack.Count > 0 - && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) - { - PopPowerShell(); - } - - if (_psFrameStack.Count == 0) - { - // If our main runspace was corrupted, - // we must re-initialize our state. - // TODO: Use runspace.ResetRunspaceState() here instead - PushInitialPowerShell(); - - _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." - + " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs."); - UI.WriteErrorLine("The main runspace encountered an error and has been reinitialized. See the PowerShell extension logs for more details."); - } - else + return _pipelineExecutor.RunTaskAsync(new SynchronousDelegateTask( + _logger, + nameof(PopOrReinitializeRunspaceAsync), + new ExecutionOptions { InterruptCurrentForeground = true }, + CancellationToken.None, + (cancellationToken) => { - _logger.LogError($"Current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was popped."); - UI.WriteErrorLine($"The current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}'." - + " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior." - + " The session is now returning to the previous runspace."); - } - }, nameof(PopOrReinitializeRunspaceAsync), CancellationToken.None)); + while (_psFrameStack.Count > 0 + && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) + { + PopPowerShell(); + } + + if (_psFrameStack.Count == 0) + { + // If our main runspace was corrupted, + // we must re-initialize our state. + // TODO: Use runspace.ResetRunspaceState() here instead + PushInitialPowerShell(); + + _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." + + " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs."); + UI.WriteErrorLine("The main runspace encountered an error and has been reinitialized. See the PowerShell extension logs for more details."); + } + else + { + _logger.LogError($"Current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was popped."); + UI.WriteErrorLine($"The current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}'." + + " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior." + + " The session is now returning to the previous runspace."); + } + })); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index aa4c338fb..cb1760469 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -37,64 +37,58 @@ public PowerShellExecutionService( public Action RunspaceChanged; public Task ExecuteDelegateAsync( - Func func, string representation, - CancellationToken cancellationToken) + ExecutionOptions executionOptions, + CancellationToken cancellationToken, + Func func) { - return RunTaskAsync(new SynchronousPSDelegateTask(_logger, _psesHost, func, representation, cancellationToken)); + return RunTaskAsync(new SynchronousPSDelegateTask(_logger, _psesHost, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, func)); } public Task ExecuteDelegateAsync( - Action action, string representation, - CancellationToken cancellationToken) + ExecutionOptions executionOptions, + CancellationToken cancellationToken, + Action action) { - return RunTaskAsync(new SynchronousPSDelegateTask(_logger, _psesHost, action, representation, cancellationToken)); + return RunTaskAsync(new SynchronousPSDelegateTask(_logger, _psesHost, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, action)); } public Task ExecuteDelegateAsync( - Func func, string representation, - CancellationToken cancellationToken) + ExecutionOptions executionOptions, + CancellationToken cancellationToken, + Func func) { - return RunTaskAsync(new SynchronousDelegateTask(_logger, func, representation, cancellationToken)); + return RunTaskAsync(new SynchronousDelegateTask(_logger, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, func)); } public Task ExecuteDelegateAsync( - Action action, string representation, - CancellationToken cancellationToken) + ExecutionOptions executionOptions, + CancellationToken cancellationToken, + Action action) { - return RunTaskAsync(new SynchronousDelegateTask(_logger, action, representation, cancellationToken)); + return RunTaskAsync(new SynchronousDelegateTask(_logger, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, action)); } public Task> ExecutePSCommandAsync( PSCommand psCommand, - PowerShellExecutionOptions executionOptions, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + PowerShellExecutionOptions executionOptions = null) { - if (executionOptions.InterruptCommandPrompt) - { - return CancelCurrentAndRunTaskNowAsync(new SynchronousPowerShellTask( - _logger, - _psesHost, - psCommand, - executionOptions, - cancellationToken)); - } - return RunTaskAsync(new SynchronousPowerShellTask( _logger, _psesHost, psCommand, - executionOptions, + executionOptions ?? PowerShellExecutionOptions.Default, cancellationToken)); } public Task ExecutePSCommandAsync( PSCommand psCommand, - PowerShellExecutionOptions executionOptions, - CancellationToken cancellationToken) => ExecutePSCommandAsync(psCommand, executionOptions, cancellationToken); + CancellationToken cancellationToken, + PowerShellExecutionOptions executionOptions = null) => ExecutePSCommandAsync(psCommand, cancellationToken, executionOptions); public void CancelCurrentTask() { @@ -102,7 +96,5 @@ public void CancelCurrentTask() } private Task RunTaskAsync(SynchronousTask task) => _pipelineExecutor.RunTaskAsync(task); - - private Task CancelCurrentAndRunTaskNowAsync(SynchronousTask task) => _pipelineExecutor.CancelCurrentAndRunTaskNowAsync(task); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs index c7ef8c541..103ad0ec0 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs @@ -48,6 +48,14 @@ public void CancelCurrentTask() } } + public void CancelCurrentTaskStack() + { + foreach (CancellationTokenSource cancellationSource in _cancellationSourceStack) + { + cancellationSource.Cancel(); + } + } + private CancellationScope EnterScope(CancellationTokenSource cancellationFrameSource) { _cancellationSourceStack.Push(cancellationFrameSource); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs index da41cf885..2c2e1207e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs @@ -98,7 +98,7 @@ public static async Task GetCommandInfoAsync( .AddArgument(commandName) .AddParameter("ErrorAction", "Ignore"); - CommandInfo commandInfo = (await executionService.ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false)).FirstOrDefault(); + CommandInfo commandInfo = (await executionService.ExecutePSCommandAsync(command, CancellationToken.None).ConfigureAwait(false)).FirstOrDefault(); // Only cache CmdletInfos since they're exposed in binaries they are likely to not change throughout the session. if (commandInfo?.CommandType == CommandTypes.Cmdlet) @@ -147,7 +147,7 @@ public static async Task GetCommandSynopsisAsync( .AddParameter("Online", false) .AddParameter("ErrorAction", "Ignore"); - IReadOnlyList results = await executionService.ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); + IReadOnlyList results = await executionService.ExecutePSCommandAsync(command, CancellationToken.None).ConfigureAwait(false); PSObject helpObject = results.FirstOrDefault(); // Extract the synopsis string from the object diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/ConcurrentBlockablePriorityQueue.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/ConcurrentBlockablePriorityQueue.cs deleted file mode 100644 index a6e923cc9..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/ConcurrentBlockablePriorityQueue.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Text; -using System.Threading; - -namespace PowerShellEditorServices.Services.PowerShell.Utility -{ - internal class ConcurrentBlockablePriorityQueue - { - private readonly object _priorityLock; - - private readonly ManualResetEventSlim _progressAllowedEvent; - - private readonly LinkedList _priorityItems; - - private readonly BlockingCollection _queue; - - public ConcurrentBlockablePriorityQueue() - { - _priorityLock = new object(); - _progressAllowedEvent = new ManualResetEventSlim(); - _priorityItems = new LinkedList(); - _queue = new BlockingCollection(); - } - - public int Count - { - get - { - lock (_priorityLock) - { - return _priorityItems.Count + _queue.Count; - } - } - } - - public void Enqueue(T item) - { - _queue.Add(item); - } - - public void EnqueuePriority(T item) - { - lock (_priorityLock) - { - _priorityItems.AddLast(item); - } - } - - public void EnqueueNext(T item) - { - lock (_priorityLock) - { - _priorityItems.AddFirst(item); - } - } - - public T Take(CancellationToken cancellationToken) - { - _progressAllowedEvent.Wait(); - - lock (_priorityLock) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (_priorityItems.Count > 0) - { - T item = _priorityItems.First.Value; - _priorityItems.RemoveFirst(); - return item; - } - } - - return _queue.Take(cancellationToken); - } - - public bool TryTake(out T item) - { - if (!_progressAllowedEvent.IsSet) - { - item = default; - return false; - } - - lock (_priorityLock) - { - if (_priorityItems.Count > 0) - { - item = _priorityItems.First.Value; - _priorityItems.RemoveFirst(); - return true; - } - } - - return _queue.TryTake(out item); - } - - public IDisposable BlockConsumers() - { - return PriorityQueueBlockLifetime.StartBlock(_progressAllowedEvent); - } - - private class PriorityQueueBlockLifetime : IDisposable - { - public static PriorityQueueBlockLifetime StartBlock(ManualResetEventSlim blockEvent) - { - blockEvent.Reset(); - return new PriorityQueueBlockLifetime(blockEvent); - } - - private readonly ManualResetEventSlim _blockEvent; - - private PriorityQueueBlockLifetime(ManualResetEventSlim blockEvent) - { - _blockEvent = blockEvent; - } - - public void Dispose() - { - _blockEvent.Set(); - } - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs index fc4d30ad6..d868fdb8f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs @@ -11,6 +11,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility using System.IO; using System.Management.Automation; using System.Runtime.CompilerServices; + using UnixConsoleEcho; internal static class PowerShellExtensions { @@ -35,11 +36,11 @@ static PowerShellExtensions() typeof(PowerShell).GetMethod("ResumeIncomingData", BindingFlags.Instance | BindingFlags.NonPublic)); } - public static Collection InvokeAndClear(this PowerShell pwsh) + public static Collection InvokeAndClear(this PowerShell pwsh, PSInvocationSettings invocationSettings = null) { try { - return pwsh.Invoke(); + return pwsh.Invoke(input: null, invocationSettings); } finally { @@ -47,11 +48,11 @@ public static Collection InvokeAndClear(this PowerShell pwsh) } } - public static void InvokeAndClear(this PowerShell pwsh) + public static void InvokeAndClear(this PowerShell pwsh, PSInvocationSettings invocationSettings = null) { try { - pwsh.Invoke(); + pwsh.Invoke(input: null, invocationSettings); } finally { @@ -59,16 +60,16 @@ public static void InvokeAndClear(this PowerShell pwsh) } } - public static Collection InvokeCommand(this PowerShell pwsh, PSCommand psCommand) + public static Collection InvokeCommand(this PowerShell pwsh, PSCommand psCommand, PSInvocationSettings invocationSettings = null) { pwsh.Commands = psCommand; - return pwsh.InvokeAndClear(); + return pwsh.InvokeAndClear(invocationSettings); } - public static void InvokeCommand(this PowerShell pwsh, PSCommand psCommand) + public static void InvokeCommand(this PowerShell pwsh, PSCommand psCommand, PSInvocationSettings invocationSettings = null) { pwsh.Commands = psCommand; - pwsh.InvokeAndClear(); + pwsh.InvokeAndClear(invocationSettings); } /// diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index e70eb4272..f67fcf65a 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -14,6 +14,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.Symbols @@ -87,16 +88,20 @@ public static async Task GetCompletionsAsync( var stopwatch = new Stopwatch(); CommandCompletion commandCompletion = null; - await executionService.ExecuteDelegateAsync((pwsh, cancellationToken) => - { - stopwatch.Start(); - commandCompletion = CommandCompletion.CompleteInput( - scriptAst, - currentTokens, - cursorPosition, - options: null, - powershell: pwsh); - }, representation: "CompleteInput", cancellationToken); + await executionService.ExecuteDelegateAsync( + representation: "CompleteInput", + new ExecutionOptions { Priority = ExecutionPriority.Next }, + cancellationToken, + (pwsh, cancellationToken) => + { + stopwatch.Start(); + commandCompletion = CommandCompletion.CompleteInput( + scriptAst, + currentTokens, + cursorPosition, + options: null, + powershell: pwsh); + }); stopwatch.Stop(); logger.LogTrace($"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); diff --git a/src/PowerShellEditorServices/Services/Template/TemplateService.cs b/src/PowerShellEditorServices/Services/Template/TemplateService.cs index 33847a292..f817e9f2f 100644 --- a/src/PowerShellEditorServices/Services/Template/TemplateService.cs +++ b/src/PowerShellEditorServices/Services/Template/TemplateService.cs @@ -72,7 +72,7 @@ public async Task ImportPlasterIfInstalledAsync() this._logger.LogTrace("Checking if Plaster is installed..."); - PSObject moduleObject = (await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false)).First(); + PSObject moduleObject = (await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None).ConfigureAwait(false)).First(); this.isPlasterInstalled = moduleObject != null; string installedQualifier = @@ -92,7 +92,7 @@ public async Task ImportPlasterIfInstalledAsync() .AddParameter("ModuleInfo", (PSModuleInfo)moduleObject.ImmediateBaseObject) .AddParameter("PassThru"); - IReadOnlyList importResult = await _executionService.ExecutePSCommandAsync(psCommand, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); + IReadOnlyList importResult = await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None).ConfigureAwait(false); this.isPlasterLoaded = importResult.Any(); string loadedQualifier = @@ -133,7 +133,6 @@ public async Task GetAvailableTemplatesAsync( IReadOnlyList templateObjects = await _executionService.ExecutePSCommandAsync( psCommand, - new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); this._logger.LogTrace($"Found {templateObjects.Count()} Plaster templates"); @@ -166,8 +165,8 @@ public async Task CreateFromTemplateAsync( await _executionService.ExecutePSCommandAsync( command, - new PowerShellExecutionOptions { WriteOutputToHost = true, InterruptCommandPrompt = true }, - CancellationToken.None).ConfigureAwait(false); + CancellationToken.None, + new PowerShellExecutionOptions { WriteOutputToHost = true, InterruptCurrentForeground = true }).ConfigureAwait(false); // If any errors were written out, creation was not successful return true; diff --git a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs index b9da23597..0ffe84698 100644 --- a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs @@ -327,7 +327,7 @@ public async Task FetchRemoteFileAsync( } byte[] fileContent = - (await this._executionService.ExecutePSCommandAsync(command, new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false)) + (await this._executionService.ExecutePSCommandAsync(command, CancellationToken.None).ConfigureAwait(false)) .FirstOrDefault(); if (fileContent != null) @@ -394,7 +394,6 @@ public async Task SaveRemoteFileAsync(string localFilePath) await _executionService.ExecutePSCommandAsync( saveCommand, - new PowerShellExecutionOptions(), CancellationToken.None).ConfigureAwait(false); /* @@ -640,7 +639,7 @@ private void RegisterPSEditFunction(IRunspaceInfo runspaceInfo) if (runspaceInfo.RunspaceOrigin == RunspaceOrigin.DebuggedRunspace) { - _executionService.ExecutePSCommandAsync(createCommand, new PowerShellExecutionOptions(), CancellationToken.None).GetAwaiter().GetResult(); + _executionService.ExecutePSCommandAsync(createCommand, CancellationToken.None).GetAwaiter().GetResult(); } else { diff --git a/src/PowerShellEditorServices/Utility/IsExternalInit.cs b/src/PowerShellEditorServices/Utility/IsExternalInit.cs new file mode 100644 index 000000000..66a88a4ce --- /dev/null +++ b/src/PowerShellEditorServices/Utility/IsExternalInit.cs @@ -0,0 +1,7 @@ +using System.ComponentModel; + +namespace System.Runtime.CompilerServices +{ + [EditorBrowsable(EditorBrowsableState.Never)] + internal class IsExternalInit{} +} From 8c7477e85939688e1931682f9fa3b937a87238df Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 28 Jun 2021 15:24:21 -0700 Subject: [PATCH 064/176] TEMP: Need to fix cancellation from PSRL scope --- .../Handlers/ConfigurationDoneHandler.cs | 14 ++- .../BlockableConcurrentPriorityQueue.cs | 61 ------------ .../Execution/BlockingConcurrentDeque.cs | 93 +++++++++++++++++++ .../Execution/ConcurrentPriorityQueue.cs | 52 ----------- .../PowerShell/Execution/ExecutionOptions.cs | 6 ++ .../Execution/PipelineThreadExecutor.cs | 36 ++++--- .../Host/EditorServicesConsolePSHost.cs | 2 +- .../PowerShell/Utility/CancellationContext.cs | 2 +- 8 files changed, 128 insertions(+), 138 deletions(-) delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/BlockableConcurrentPriorityQueue.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/ConcurrentPriorityQueue.cs diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 04f4cc47e..8b5cf6452 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -23,6 +23,14 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { internal class ConfigurationDoneHandler : IConfigurationDoneHandler { + private readonly PowerShellExecutionOptions s_debuggerExecutionOptions = new() + { + MustRunInForeground = true, + WriteInputToHost = true, + WriteOutputToHost = true, + AddToHistory = true, + }; + private readonly ILogger _logger; private readonly IDebugAdapterServerFacade _debugAdapterServer; private readonly DebugService _debugService; @@ -110,7 +118,7 @@ private async Task LaunchScriptAsync(string scriptToLaunch) // This seems to be the simplest way to invoke a script block (which contains breakpoint information) via the PowerShell API. var cmd = new PSCommand().AddScript(". $args[0]").AddArgument(ast.GetScriptBlock()); await _executionService - .ExecutePSCommandAsync(cmd, CancellationToken.None, new PowerShellExecutionOptions { WriteOutputToHost = true }) + .ExecutePSCommandAsync(cmd, CancellationToken.None, s_debuggerExecutionOptions) .ConfigureAwait(false); } else @@ -119,7 +127,7 @@ await _executionService .ExecutePSCommandAsync( new PSCommand().AddScript(untitledScript.Contents), CancellationToken.None, - new PowerShellExecutionOptions { WriteOutputToHost = true}) + s_debuggerExecutionOptions) .ConfigureAwait(false); } } @@ -129,7 +137,7 @@ await _executionService .ExecutePSCommandAsync( BuildPSCommandFromArguments(scriptToLaunch, _debugStateService.Arguments), CancellationToken.None, - new PowerShellExecutionOptions { WriteOutputToHost = true, WriteInputToHost = true, AddToHistory = true }) + s_debuggerExecutionOptions) .ConfigureAwait(false); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockableConcurrentPriorityQueue.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockableConcurrentPriorityQueue.cs deleted file mode 100644 index e0e4425c0..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockableConcurrentPriorityQueue.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Threading; - -namespace PowerShellEditorServices.Services.PowerShell.Utility -{ - internal class BlockableConcurrentPriorityQueue : ConcurrentPriorityQueue - { - private readonly ManualResetEventSlim _progressAllowedEvent; - - public BlockableConcurrentPriorityQueue() - : base() - { - // Start the reset event in the set state, meaning not blocked - _progressAllowedEvent = new ManualResetEventSlim(initialState: true); - } - - public IDisposable BlockConsumers() - { - return PriorityQueueBlockLifetime.StartBlocking(_progressAllowedEvent); - } - - public override T Take(CancellationToken cancellationToken) - { - _progressAllowedEvent.Wait(); - - return base.Take(cancellationToken); - } - - public override bool TryTake(out T item) - { - if (!_progressAllowedEvent.IsSet) - { - item = default; - return false; - } - - return base.TryTake(out item); - } - - private class PriorityQueueBlockLifetime : IDisposable - { - public static PriorityQueueBlockLifetime StartBlocking(ManualResetEventSlim blockEvent) - { - blockEvent.Reset(); - return new PriorityQueueBlockLifetime(blockEvent); - } - - private readonly ManualResetEventSlim _blockEvent; - - private PriorityQueueBlockLifetime(ManualResetEventSlim blockEvent) - { - _blockEvent = blockEvent; - } - - public void Dispose() - { - _blockEvent.Set(); - } - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs new file mode 100644 index 000000000..56c9b9806 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + /// + /// Implements a concurrent deque that supplies: + /// - Non-blocking prepend and append operations + /// - Blocking and non-blocking take calls + /// - The ability to block consumers, so that can also guarantee the state of the consumer + /// + /// The type of item held by this collection. + /// + /// The prepend/append semantics of this class depend on the implementation semantics of + /// and its overloads checking the supplied array in order. + /// This behavior is unlikely to change and ensuring its correctness at our layer is likely to be costly. + /// See https://stackoverflow.com/q/26472251. + /// + internal class BlockingConcurrentDeque + { + private readonly ManualResetEventSlim _blockConsumersEvent; + + private readonly BlockingCollection[] _queues; + + public BlockingConcurrentDeque() + { + // Initialize in the "set" state, meaning unblocked + _blockConsumersEvent = new ManualResetEventSlim(initialState: true); + + _queues = new[] + { + // The high priority section is FIFO so that "prepend" always puts elements first + new BlockingCollection(new ConcurrentStack()), + new BlockingCollection(new ConcurrentQueue()), + }; + } + + public int Count => _queues[0].Count + _queues[1].Count; + + public void Prepend(T item) + { + _queues[0].Add(item); + } + + public void Append(T item) + { + _queues[1].Add(item); + } + + public T Take(CancellationToken cancellationToken) + { + _blockConsumersEvent.Wait(cancellationToken); + BlockingCollection.TakeFromAny(_queues, out T result, cancellationToken); + return result; + } + + public bool TryTake(out T item) + { + if (!_blockConsumersEvent.IsSet) + { + item = default; + return false; + } + + return BlockingCollection.TakeFromAny(_queues, out item) != -1; + } + + public IDisposable BlockConsumers() => PriorityQueueBlockLifetime.StartBlocking(_blockConsumersEvent); + + private class PriorityQueueBlockLifetime : IDisposable + { + public static PriorityQueueBlockLifetime StartBlocking(ManualResetEventSlim blockEvent) + { + blockEvent.Reset(); + return new PriorityQueueBlockLifetime(blockEvent); + } + + private readonly ManualResetEventSlim _blockEvent; + + private PriorityQueueBlockLifetime(ManualResetEventSlim blockEvent) + { + _blockEvent = blockEvent; + } + + public void Dispose() + { + _blockEvent.Set(); + } + } + } +} + diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/ConcurrentPriorityQueue.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/ConcurrentPriorityQueue.cs deleted file mode 100644 index a526c4b97..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/ConcurrentPriorityQueue.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Text; -using System.Threading; - -namespace PowerShellEditorServices.Services.PowerShell.Utility -{ - internal class ConcurrentPriorityQueue - { - private readonly ConcurrentStack _priorityItems; - - private readonly BlockingCollection _queue; - - public ConcurrentPriorityQueue() - { - _priorityItems = new ConcurrentStack(); - _queue = new BlockingCollection(); - } - - public int Count => _priorityItems.Count + _queue.Count; - - public void Append(T item) - { - _queue.Add(item); - } - - public void Prepend(T item) - { - _priorityItems.Push(item); - } - - public virtual T Take(CancellationToken cancellationToken) - { - if (_priorityItems.TryPop(out T item)) - { - return item; - } - - return _queue.Take(cancellationToken); - } - - public virtual bool TryTake(out T item) - { - if (_priorityItems.TryPop(out item)) - { - return true; - } - - return _queue.TryTake(out item); - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs index e37fff59c..e21c3bd46 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs @@ -2,6 +2,12 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution { + public enum TaskKind + { + Foreground, + Background, + } + public enum ExecutionPriority { Normal, diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs index 0f0195791..ffe944c83 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; -using PowerShellEditorServices.Services.PowerShell.Utility; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution { @@ -32,9 +31,7 @@ internal class PipelineThreadExecutor private readonly HostStartupInfo _hostInfo; - private readonly BlockableConcurrentPriorityQueue _foregroundExecutionQueue; - - private readonly ConcurrentPriorityQueue _backgroundExecutionQueue; + private readonly BlockingConcurrentDeque _taskQueue; private readonly CancellationTokenSource _consumerThreadCancellationSource; @@ -59,8 +56,7 @@ public PipelineThreadExecutor( _psesHost = psesHost; _readLineProvider = readLineProvider; _consumerThreadCancellationSource = new CancellationTokenSource(); - _foregroundExecutionQueue = new BlockableConcurrentPriorityQueue(); - _backgroundExecutionQueue = new ConcurrentPriorityQueue(); + _taskQueue = new BlockingConcurrentDeque(); _loopCancellationContext = new CancellationContext(); _commandCancellationContext = new CancellationContext(); _taskProcessingAllowed = new ManualResetEventSlim(initialState: true); @@ -81,18 +77,14 @@ public Task RunTaskAsync(SynchronousTask synchronousT return CancelCurrentAndRunTaskNowAsync(synchronousTask); } - ConcurrentPriorityQueue executionQueue = synchronousTask.ExecutionOptions.MustRunInForeground - ? _foregroundExecutionQueue - : _backgroundExecutionQueue; - switch (synchronousTask.ExecutionOptions.Priority) { case ExecutionPriority.Next: - executionQueue.Prepend(synchronousTask); + _taskQueue.Prepend(synchronousTask); break; case ExecutionPriority.Normal: - executionQueue.Append(synchronousTask); + _taskQueue.Append(synchronousTask); break; } @@ -132,11 +124,10 @@ private Task CancelCurrentAndRunTaskNowAsync(SynchronousTask { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs index 103ad0ec0..60111b9a1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs @@ -63,7 +63,7 @@ private CancellationScope EnterScope(CancellationTokenSource cancellationFrameSo } } - internal struct CancellationScope : IDisposable + internal class CancellationScope : IDisposable { private readonly ConcurrentStack _cancellationStack; From e359563b3d4c3076228c4bd31170d61ccc0e3e38 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 20 Jul 2021 13:44:35 -0700 Subject: [PATCH 065/176] TEMP: Moving to monolithic host --- .../PowerShell/Console/ConsoleReadLine.cs | 5 + .../PowerShell/Console/PSReadLineProxy.cs | 2 +- .../Execution/PipelineThreadExecutor.cs | 12 +- .../Host/EditorServicesConsolePSHost.cs | 14 + .../Services/PowerShell/Host/InternalHost.cs | 324 ++++++++++++++++++ .../PowerShell/Host/PowerShellFactory.cs | 4 +- .../PowerShell/Utility/CancellationContext.cs | 62 ++-- 7 files changed, 394 insertions(+), 29 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index 54f469e27..1afd19521 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -13,6 +13,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using System; using System.Collections.Generic; + using System.Diagnostics; using System.Management.Automation; using System.Management.Automation.Language; using System.Runtime.CompilerServices; @@ -170,6 +171,10 @@ private Task ReadLineAsync(bool isCommandLine, CancellationToken cancell private string InvokePSReadLine(CancellationToken cancellationToken) { + cancellationToken.Register(() => + { + Debug.WriteLine("PSRL CANCELLED"); + }); EngineIntrinsics engineIntrinsics = _psesHost.IsRunspacePushed ? null : _engineIntrinsics; return _psrlProxy.ReadLine(_psesHost.Runspace, engineIntrinsics, cancellationToken); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs index 806471c21..d36706cb4 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs @@ -80,7 +80,7 @@ public static PSReadLineProxy LoadAndCreate( { Type psConsoleReadLineType = pwsh.AddScript(ReadLineInitScript).InvokeAndClear().FirstOrDefault(); - Type type = Type.GetType("Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2"); + Type type = Type.GetType("Microsoft.PowerShell.PSConsoleReadLine"); RuntimeHelpers.RunClassConstructor(type.TypeHandle); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs index ffe944c83..51cc2153e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using System.Collections.Concurrent; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution { @@ -41,8 +42,6 @@ internal class PipelineThreadExecutor private readonly CancellationContext _commandCancellationContext; - private readonly ManualResetEventSlim _taskProcessingAllowed; - private bool _runIdleLoop; public PipelineThreadExecutor( @@ -59,7 +58,6 @@ public PipelineThreadExecutor( _taskQueue = new BlockingConcurrentDeque(); _loopCancellationContext = new CancellationContext(); _commandCancellationContext = new CancellationContext(); - _taskProcessingAllowed = new ManualResetEventSlim(initialState: true); _pipelineThread = new Thread(Run) { @@ -144,7 +142,7 @@ private void Run() public void RunPowerShellLoop(PowerShellFrameType powerShellFrameType) { - using (CancellationScope cancellationScope = _loopCancellationContext.EnterScope(_psesHost.CurrentCancellationSource.Token, _consumerThreadCancellationSource.Token)) + using (CancellationScope cancellationScope = _loopCancellationContext.EnterScope(_runIdleLoop, _psesHost.CurrentCancellationSource.Token, _consumerThreadCancellationSource.Token)) { try { @@ -174,7 +172,7 @@ public void RunPowerShellLoop(PowerShellFrameType powerShellFrameType) private void RunTopLevelConsumerLoop() { - using (CancellationScope cancellationScope = _loopCancellationContext.EnterScope(_psesHost.CurrentCancellationSource.Token, _consumerThreadCancellationSource.Token)) + using (CancellationScope cancellationScope = _loopCancellationContext.EnterScope(isIdleScope: false, _psesHost.CurrentCancellationSource.Token, _consumerThreadCancellationSource.Token)) { try { @@ -251,6 +249,8 @@ private void RunIdleLoop(in CancellationScope cancellationScope) if (task.ExecutionOptions.MustRunInForeground) { _taskQueue.Prepend(task); + _loopCancellationContext.CancelIdleParentTask(); + _commandCancellationContext.CancelIdleParentTask(); break; } @@ -277,7 +277,7 @@ private void RunTaskSynchronously(ISynchronousTask task, CancellationToken loopC return; } - using (CancellationScope commandCancellationScope = _commandCancellationContext.EnterScope(loopCancellationToken)) + using (CancellationScope commandCancellationScope = _commandCancellationContext.EnterScope(_runIdleLoop, loopCancellationToken)) { task.ExecuteSynchronously(commandCancellationScope.CancellationToken); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index 9975ae0b8..6b46c7a29 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -38,6 +38,8 @@ internal class EditorServicesConsolePSHost : PSHost, IHostSupportsInteractiveSes private readonly Stack> _runspacesInUse; + private readonly Thread _topRunspaceThread; + private string _localComputerName; private int _hostStarted = 0; @@ -55,6 +57,8 @@ public EditorServicesConsolePSHost( Name = hostInfo.Name; Version = hostInfo.Version; + _topRunspaceThread = new Thread(Run); + _readLineProvider = new ReadLineProvider(loggerFactory); _pipelineExecutor = new PipelineThreadExecutor(loggerFactory, hostInfo, this, _readLineProvider); ExecutionService = new PowerShellExecutionService(loggerFactory, this, _pipelineExecutor); @@ -139,6 +143,16 @@ public override void SetShouldExit(int exitCode) SetExit(); } + internal void Start() + { + _topRunspaceThread.Start(); + } + + private void Run() + { + PushInitialPowerShell(); + } + public void PushInitialPowerShell() { SMA.PowerShell pwsh = _psFactory.CreateInitialPowerShell(_hostInfo, _readLineProvider); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs new file mode 100644 index 000000000..c6d3ca999 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs @@ -0,0 +1,324 @@ +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Management.Automation.Host; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host +{ + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; + using Microsoft.PowerShell.EditorServices.Utility; + using System.IO; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using System.Reflection; + + internal class InternalHost : PSHost, IHostSupportsInteractiveSession, IRunspaceContext + { + private static readonly string s_commandsModulePath = Path.GetFullPath( + Path.Combine( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + "../../Commands/PowerShellEditorServices.Commands.psd1")); + + private readonly ILoggerFactory _loggerFactory; + + private readonly ILogger _logger; + + private readonly ILanguageServerFacade _languageServer; + + private readonly HostStartupInfo _hostInfo; + + private readonly ReadLineProvider _readLineProvider; + + private readonly BlockingConcurrentDeque _taskQueue; + + private readonly Stack _psFrameStack; + + private readonly Stack<(Runspace, RunspaceInfo)> _runspaceStack; + + private readonly PowerShellFactory _psFactory; + + private readonly EditorServicesConsolePSHost _publicHost; + + private bool _shouldExit = false; + + private string _localComputerName; + + public InternalHost( + ILoggerFactory loggerFactory, + ILanguageServerFacade languageServer, + HostStartupInfo hostInfo, + EditorServicesConsolePSHost publicHost) + { + _loggerFactory = loggerFactory; + _logger = loggerFactory.CreateLogger(); + _languageServer = languageServer; + _hostInfo = hostInfo; + _publicHost = publicHost; + + _readLineProvider = new ReadLineProvider(loggerFactory); + _taskQueue = new BlockingConcurrentDeque(); + _psFrameStack = new Stack(); + _runspaceStack = new Stack<(Runspace, RunspaceInfo)>(); + _psFactory = new PowerShellFactory(loggerFactory, this); + + Name = hostInfo.Name; + Version = hostInfo.Version; + + UI = new EditorServicesConsolePSHostUserInterface(loggerFactory, _readLineProvider, hostInfo.PSHost.UI); + } + + public override CultureInfo CurrentCulture => _hostInfo.PSHost.CurrentCulture; + + public override CultureInfo CurrentUICulture => _hostInfo.PSHost.CurrentUICulture; + + public override Guid InstanceId { get; } = Guid.NewGuid(); + + public override string Name { get; } + + public override PSHostUserInterface UI { get; } + + public override Version Version { get; } + + public bool IsRunspacePushed { get; private set; } + + public Runspace Runspace => _runspaceStack.Peek().Item1; + + public RunspaceInfo CurrentRunspace => CurrentFrame.RunspaceInfo; + + IRunspaceInfo IRunspaceContext.CurrentRunspace => CurrentRunspace; + + private SMA.PowerShell CurrentPowerShell => CurrentFrame.PowerShell; + + private PowerShellContextFrame CurrentFrame => _psFrameStack.Peek(); + + public override void EnterNestedPrompt() + { + PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested); + } + + public override void ExitNestedPrompt() + { + SetExit(); + } + + public override void NotifyBeginApplication() + { + // TODO: Work out what to do here + } + + public override void NotifyEndApplication() + { + // TODO: Work out what to do here + } + + public void PopRunspace() + { + IsRunspacePushed = false; + SetExit(); + } + + public void PushRunspace(Runspace runspace) + { + IsRunspacePushed = true; + PushPowerShellAndRunLoop(_psFactory.CreatePowerShellForRunspace(runspace), PowerShellFrameType.Remote); + } + + public override void SetShouldExit(int exitCode) + { + // TODO: Handle exit code if needed + SetExit(); + } + + private void SetExit() + { + if (_psFrameStack.Count <= 1) + { + return; + } + + _shouldExit = true; + } + + private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType) + { + RunspaceInfo runspaceInfo = null; + if (_runspaceStack.Count > 0) + { + // This is more than just an optimization. + // When debugging, we cannot execute PowerShell directly to get this information; + // trying to do so will block on the command that called us, deadlocking execution. + // Instead, since we are reusing the runspace, we reuse that runspace's info as well. + (Runspace currentRunspace, RunspaceInfo currentRunspaceInfo) = _runspaceStack.Peek(); + if (currentRunspace == pwsh.Runspace) + { + runspaceInfo = currentRunspaceInfo; + } + } + + if (runspaceInfo is null) + { + RunspaceOrigin runspaceOrigin = pwsh.Runspace.RunspaceIsRemote ? RunspaceOrigin.EnteredProcess : RunspaceOrigin.Local; + runspaceInfo = RunspaceInfo.CreateFromPowerShell(_logger, pwsh, runspaceOrigin, _localComputerName); + _runspaceStack.Push((pwsh.Runspace, runspaceInfo)); + } + + // TODO: Improve runspace origin detection here + PushPowerShellAndRunLoop(new PowerShellContextFrame(pwsh, runspaceInfo, frameType)); + } + + private void PushPowerShellAndRunLoop(PowerShellContextFrame frame) + { + if (_psFrameStack.Count > 0) + { + RemoveRunspaceEventHandlers(CurrentFrame.PowerShell.Runspace); + } + + AddRunspaceEventHandlers(frame.PowerShell.Runspace); + + _psFrameStack.Push(frame); + + RunExecutionLoop(); + } + + private void RunExecutionLoop() + { + while (true) + { + + } + } + + private void AddRunspaceEventHandlers(Runspace runspace) + { + runspace.Debugger.DebuggerStop += OnDebuggerStopped; + runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; + runspace.StateChanged += OnRunspaceStateChanged; + } + + private void RemoveRunspaceEventHandlers(Runspace runspace) + { + runspace.Debugger.DebuggerStop -= OnDebuggerStopped; + runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; + runspace.StateChanged -= OnRunspaceStateChanged; + } + + private PowerShell CreateNestedPowerShell(RunspaceInfo currentRunspace) + { + if (currentRunspace.RunspaceOrigin != RunspaceOrigin.Local) + { + var remotePwsh = PowerShell.Create(); + remotePwsh.Runspace = currentRunspace.Runspace; + return remotePwsh; + } + + // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild + // This means it throws due to the parent pipeline not running... + // So we must use the RunspaceMode.CurrentRunspace option on PowerShell.Create() instead + var pwsh = PowerShell.Create(RunspaceMode.CurrentRunspace); + pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; + return pwsh; + } + + private PowerShell CreatePowerShellForRunspace(Runspace runspace) + { + var pwsh = PowerShell.Create(); + pwsh.Runspace = runspace; + return pwsh; + } + + public PowerShell CreateInitialPowerShell( + HostStartupInfo hostStartupInfo, + ReadLineProvider readLineProvider) + { + Runspace runspace = CreateInitialRunspace(hostStartupInfo.LanguageMode); + + var pwsh = PowerShell.Create(); + pwsh.Runspace = runspace; + + var engineIntrinsics = (EngineIntrinsics)runspace.SessionStateProxy.GetVariable("ExecutionContext"); + + if (hostStartupInfo.ConsoleReplEnabled && !hostStartupInfo.UsesLegacyReadLine) + { + var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, pwsh); + var readLine = new ConsoleReadLine(psrlProxy, this, engineIntrinsics); + readLineProvider.OverrideReadLine(readLine); + } + + if (VersionUtils.IsWindows) + { + pwsh.SetCorrectExecutionPolicy(_logger); + } + + pwsh.ImportModule(s_commandsModulePath); + + if (hostStartupInfo.AdditionalModules != null && hostStartupInfo.AdditionalModules.Count > 0) + { + foreach (string module in hostStartupInfo.AdditionalModules) + { + pwsh.ImportModule(module); + } + } + + return pwsh; + } + + private Runspace CreateInitialRunspace(PSLanguageMode languageMode) + { + InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" + ? InitialSessionState.CreateDefault() + : InitialSessionState.CreateDefault2(); + + iss.LanguageMode = languageMode; + + Runspace runspace = RunspaceFactory.CreateRunspace(_publicHost, iss); + + runspace.SetApartmentStateToSta(); + runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; + + runspace.Open(); + + Runspace.DefaultRunspace = runspace; + + return runspace; + } + + + private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) + { + DebugContext.SetDebuggerStopped(debuggerStopEventArgs); + try + { + CurrentPowerShell.WaitForRemoteOutputIfNeeded(); + PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Debug | PowerShellFrameType.Nested); + CurrentPowerShell.ResumeRemoteOutputIfNeeded(); + } + finally + { + DebugContext.SetDebuggerResumed(); + } + } + + private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) + { + DebugContext.HandleBreakpointUpdated(breakpointUpdatedEventArgs); + } + + private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) + { + if (!runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) + { + //PopOrReinitializeRunspaceAsync(); + } + } + + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PowerShellFactory.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PowerShellFactory.cs index 5e7924fc5..491775357 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PowerShellFactory.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PowerShellFactory.cs @@ -25,11 +25,11 @@ internal class PowerShellFactory private readonly ILogger _logger; - private readonly EditorServicesConsolePSHost _psesHost; + private readonly InternalHost _psesHost; public PowerShellFactory( ILoggerFactory loggerFactory, - EditorServicesConsolePSHost psHost) + InternalHost psHost) { _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs index 60111b9a1..69decae4e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs @@ -23,26 +23,26 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility /// internal class CancellationContext { - private readonly ConcurrentStack _cancellationSourceStack; + private readonly ConcurrentStack _cancellationSourceStack; public CancellationContext() { - _cancellationSourceStack = new ConcurrentStack(); + _cancellationSourceStack = new ConcurrentStack(); } - public CancellationScope EnterScope(params CancellationToken[] linkedTokens) + public CancellationScope EnterScope(bool isIdleScope, params CancellationToken[] linkedTokens) { - return EnterScope(CancellationTokenSource.CreateLinkedTokenSource(linkedTokens)); + return EnterScope(isIdleScope, CancellationTokenSource.CreateLinkedTokenSource(linkedTokens)); } - public CancellationScope EnterScope(CancellationToken linkedToken1, CancellationToken linkedToken2) + public CancellationScope EnterScope(bool isIdleScope, CancellationToken linkedToken1, CancellationToken linkedToken2) { - return EnterScope(CancellationTokenSource.CreateLinkedTokenSource(linkedToken1, linkedToken2)); + return EnterScope(isIdleScope, CancellationTokenSource.CreateLinkedTokenSource(linkedToken1, linkedToken2)); } public void CancelCurrentTask() { - if (_cancellationSourceStack.TryPeek(out CancellationTokenSource currentCancellationSource)) + if (_cancellationSourceStack.TryPeek(out CancellationScope currentCancellationSource)) { currentCancellationSource.Cancel(); } @@ -50,37 +50,59 @@ public void CancelCurrentTask() public void CancelCurrentTaskStack() { - foreach (CancellationTokenSource cancellationSource in _cancellationSourceStack) + foreach (CancellationScope scope in _cancellationSourceStack) { - cancellationSource.Cancel(); + scope.Cancel(); } } - private CancellationScope EnterScope(CancellationTokenSource cancellationFrameSource) + public void CancelIdleParentTask() { - _cancellationSourceStack.Push(cancellationFrameSource); - return new CancellationScope(_cancellationSourceStack, cancellationFrameSource.Token); + foreach (CancellationScope scope in _cancellationSourceStack) + { + scope.Cancel(); + + if (!scope.IsIdleScope) + { + break; + } + } + } + + private CancellationScope EnterScope(bool isIdleScope, CancellationTokenSource cancellationFrameSource) + { + var scope = new CancellationScope(_cancellationSourceStack, cancellationFrameSource, isIdleScope); + _cancellationSourceStack.Push(scope); + return scope; } } internal class CancellationScope : IDisposable { - private readonly ConcurrentStack _cancellationStack; + private readonly ConcurrentStack _cancellationStack; - internal CancellationScope(ConcurrentStack cancellationStack, CancellationToken currentCancellationToken) + private readonly CancellationTokenSource _cancellationSource; + + internal CancellationScope( + ConcurrentStack cancellationStack, + CancellationTokenSource frameCancellationSource, + bool isIdleScope) { _cancellationStack = cancellationStack; - CancellationToken = currentCancellationToken; + _cancellationSource = frameCancellationSource; + IsIdleScope = isIdleScope; } - public readonly CancellationToken CancellationToken; + public CancellationToken CancellationToken => _cancellationSource.Token; + + public void Cancel() => _cancellationSource.Cancel(); + + public bool IsIdleScope { get; } public void Dispose() { - if (_cancellationStack.TryPop(out CancellationTokenSource contextCancellationTokenSource)) - { - contextCancellationTokenSource.Dispose(); - } + _cancellationStack.TryPop(out CancellationScope _); + _cancellationSource.Cancel(); } } } From db428b1be556e3ead522d0634a25065d6bfcaff6 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 4 Aug 2021 16:22:30 -0700 Subject: [PATCH 066/176] WIP --- .../PowerShell/Console/ConsoleReadLine.cs | 67 +++---- .../Services/PowerShell/Console/IReadLine.cs | 8 +- .../Execution/ISynchronousExecutor.cs | 47 +++++ .../Execution/PowerShellExecutor.cs | 59 ++++++ .../PowerShell/Execution/SynchronousTask.cs | 47 ++++- .../Services/PowerShell/Host/InternalHost.cs | 175 +++++++++++++++++- 6 files changed, 344 insertions(+), 59 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/ISynchronousExecutor.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutor.cs diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index 1afd19521..e1484432a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -5,7 +5,6 @@ using System.Text; using System.Threading; -using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { @@ -16,30 +15,29 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console using System.Diagnostics; using System.Management.Automation; using System.Management.Automation.Language; - using System.Runtime.CompilerServices; using System.Security; internal class ConsoleReadLine : IReadLine { private readonly PSReadLineProxy _psrlProxy; - private readonly EditorServicesConsolePSHost _psesHost; - - private readonly PowerShellExecutionService _executionService; + private readonly InternalHost _psesHost; private readonly EngineIntrinsics _engineIntrinsics; + private ISynchronousExecutor _executor; + #region Constructors public ConsoleReadLine( PSReadLineProxy psrlProxy, - EditorServicesConsolePSHost psesHost, - PowerShellExecutionService executionService, + InternalHost psesHost, + ISynchronousExecutor executor, EngineIntrinsics engineIntrinsics) { _psrlProxy = psrlProxy; _psesHost = psesHost; - _executionService = executionService; + _executor = executor; _engineIntrinsics = engineIntrinsics; } @@ -47,11 +45,10 @@ public ConsoleReadLine( #region Public Methods - public Task ReadLineAsync(CancellationToken cancellationToken) => ReadLineAsync(isCommandLine: true, cancellationToken); - - public string ReadLine() => ReadLineAsync(CancellationToken.None).GetAwaiter().GetResult(); - - public SecureString ReadSecureLine() => ReadSecureLineAsync(CancellationToken.None).GetAwaiter().GetResult(); + public string ReadLine(CancellationToken cancellationToken) + { + return _executor.InvokeDelegate(representation: "ReadLine", new ExecutionOptions { MustRunInForeground = true }, InvokePSReadLine, cancellationToken); + } public bool TryOverrideReadKey(Func readKeyFunc) { @@ -65,23 +62,13 @@ public bool TryOverrideIdleHandler(Action idleHandler) return true; } - public Task ReadCommandLineAsync(CancellationToken cancellationToken) - { - return ReadLineAsync(true, cancellationToken); - } - - public Task ReadSimpleLineAsync(CancellationToken cancellationToken) - { - return ReadLineAsync(false, cancellationToken); - } - - public async Task ReadSecureLineAsync(CancellationToken cancellationToken) + public SecureString ReadSecureLine(CancellationToken cancellationToken) { SecureString secureString = new SecureString(); // TODO: Are these values used? - int initialPromptRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false); - int initialPromptCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken).ConfigureAwait(false); + int initialPromptRow = ConsoleProxy.GetCursorTop(cancellationToken); + int initialPromptCol = ConsoleProxy.GetCursorLeft(cancellationToken); int previousInputLength = 0; Console.TreatControlCAsInput = true; @@ -90,7 +77,7 @@ public async Task ReadSecureLineAsync(CancellationToken cancellati { while (!cancellationToken.IsCancellationRequested) { - ConsoleKeyInfo keyInfo = await ReadKeyAsync(cancellationToken).ConfigureAwait(false); + ConsoleKeyInfo keyInfo = ReadKey(cancellationToken); if ((int)keyInfo.Key == 3 || keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) @@ -128,8 +115,8 @@ public async Task ReadSecureLineAsync(CancellationToken cancellati } else if (previousInputLength > 0 && currentInputLength < previousInputLength) { - int row = await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false); - int col = await ConsoleProxy.GetCursorLeftAsync(cancellationToken).ConfigureAwait(false); + int row = ConsoleProxy.GetCursorTop(cancellationToken); + int col = ConsoleProxy.GetCursorLeft(cancellationToken); // Back up the cursor before clearing the character col--; @@ -159,14 +146,9 @@ public async Task ReadSecureLineAsync(CancellationToken cancellati #region Private Methods - private static Task ReadKeyAsync(CancellationToken cancellationToken) - { - return ConsoleProxy.ReadKeyAsync(intercept: true, cancellationToken); - } - - private Task ReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) + private static ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) { - return _executionService.ExecuteDelegateAsync(representation: "ReadLine", new ExecutionOptions { MustRunInForeground = true }, cancellationToken, InvokePSReadLine); + return ConsoleProxy.ReadKey(intercept: true, cancellationToken); } private string InvokePSReadLine(CancellationToken cancellationToken) @@ -194,7 +176,7 @@ private string InvokePSReadLine(CancellationToken cancellationToken) /// A task object representing the asynchronus operation. The Result property on /// the task object returns the user input string. /// - internal async Task InvokeLegacyReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) + internal string InvokeLegacyReadLine(bool isCommandLine, CancellationToken cancellationToken) { string inputAfterCompletion = null; CommandCompletion currentCompletion = null; @@ -204,8 +186,8 @@ internal async Task InvokeLegacyReadLineAsync(bool isCommandLine, Cancel StringBuilder inputLine = new StringBuilder(); - int initialCursorCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken).ConfigureAwait(false); - int initialCursorRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false); + int initialCursorCol = ConsoleProxy.GetCursorLeft(cancellationToken); + int initialCursorRow = ConsoleProxy.GetCursorTop(cancellationToken); // TODO: Are these used? int initialWindowLeft = Console.WindowLeft; @@ -219,7 +201,7 @@ internal async Task InvokeLegacyReadLineAsync(bool isCommandLine, Cancel { while (!cancellationToken.IsCancellationRequested) { - ConsoleKeyInfo keyInfo = await ReadKeyAsync(cancellationToken).ConfigureAwait(false); + ConsoleKeyInfo keyInfo = ReadKey(cancellationToken); // Do final position calculation after the key has been pressed // because the window could have been resized before then @@ -369,9 +351,10 @@ internal async Task InvokeLegacyReadLineAsync(bool isCommandLine, Cancel PSCommand command = new PSCommand().AddCommand("Get-History"); - currentHistory = await _executionService.ExecutePSCommandAsync( + currentHistory = _executor.InvokePSCommand( command, - cancellationToken).ConfigureAwait(false); + PowerShellExecutionOptions.Default, + cancellationToken); if (currentHistory != null) { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs index 50eaf7e34..3e1afb44c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs @@ -9,13 +9,9 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { internal interface IReadLine { - Task ReadLineAsync(CancellationToken cancellationToken); + string ReadLine(CancellationToken cancellationToken); - Task ReadSecureLineAsync(CancellationToken cancellationToken); - - string ReadLine(); - - SecureString ReadSecureLine(); + SecureString ReadSecureLine(CancellationToken cancellationToken); bool TryOverrideReadKey(Func readKeyOverride); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/ISynchronousExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/ISynchronousExecutor.cs new file mode 100644 index 000000000..231289fad --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/ISynchronousExecutor.cs @@ -0,0 +1,47 @@ +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using System; +using System.Collections.Generic; +using SMA = System.Management.Automation; +using System.Text; +using System.Threading; +using System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + internal interface ISynchronousExecutor + { + TResult InvokeDelegate( + string representation, + ExecutionOptions executionOptions, + Func func, + CancellationToken cancellationToken); + + void InvokeDelegate( + string representation, + ExecutionOptions executionOptions, + Action action, + CancellationToken cancellationToken); + + TResult InvokePSDelegate( + string representation, + ExecutionOptions executionOptions, + Func func, + CancellationToken cancellationToken); + + void InvokePSDelegate( + string representation, + ExecutionOptions executionOptions, + Action action, + CancellationToken cancellationToken); + + IReadOnlyList InvokePSCommand( + PSCommand psCommand, + PowerShellExecutionOptions executionOptions, + CancellationToken cancellationToken); + + void InvokePSCommand( + PSCommand psCommand, + PowerShellExecutionOptions executionOptions, + CancellationToken cancellationToken); + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutor.cs new file mode 100644 index 000000000..c3e246a6d --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutor.cs @@ -0,0 +1,59 @@ +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Threading; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +{ + internal class PowerShellExecutor : ISynchronousExecutor + { + private readonly ILogger _logger; + + private readonly EditorServicesConsolePSHost _host; + + public PowerShellExecutor( + ILoggerFactory loggerFactory, + EditorServicesConsolePSHost host) + { + _logger = loggerFactory.CreateLogger(); + _host = host; + } + + public TResult InvokeDelegate(string representation, ExecutionOptions executionOptions, Func func, CancellationToken cancellationToken) + { + var task = new SynchronousDelegateTask(_logger, representation, executionOptions, cancellationToken, func); + return task.ExecuteAndGetResult(cancellationToken); + } + + public void InvokeDelegate(string representation, ExecutionOptions executionOptions, Action action, CancellationToken cancellationToken) + { + var task = new SynchronousDelegateTask(_logger, representation, executionOptions, cancellationToken, action); + task.ExecuteAndGetResult(cancellationToken); + } + + public IReadOnlyList InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) + { + var task = new SynchronousPowerShellTask(_logger, _host, psCommand, executionOptions, cancellationToken); + return task.ExecuteAndGetResult(cancellationToken); + } + + public void InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) + => InvokePSCommand(psCommand, executionOptions, cancellationToken); + + public TResult InvokePSDelegate(string representation, ExecutionOptions executionOptions, Func func, CancellationToken cancellationToken) + { + var task = new SynchronousPSDelegateTask(_logger, _host, representation, executionOptions, cancellationToken, func); + return task.ExecuteAndGetResult(cancellationToken); + } + + public void InvokePSDelegate(string representation, ExecutionOptions executionOptions, Action action, CancellationToken cancellationToken) + { + var task = new SynchronousPSDelegateTask(_logger, _host, representation, executionOptions, cancellationToken, action); + task.ExecuteAndGetResult(cancellationToken); + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs index b1a22c4a9..555903d35 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using System; +using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; @@ -22,6 +23,10 @@ internal abstract class SynchronousTask : ISynchronousTask private bool _executionCanceled; + private TResult _result; + + private ExceptionDispatchInfo _exceptionInfo; + protected SynchronousTask( ILogger logger, CancellationToken cancellationToken) @@ -36,6 +41,24 @@ protected SynchronousTask( public Task Task => _taskCompletionSource.Task; + public TResult Result + { + get + { + if (_executionCanceled) + { + throw new OperationCanceledException(); + } + + if (_exceptionInfo is not null) + { + _exceptionInfo.Throw(); + } + + return _result; + } + } + public bool IsCanceled => _executionCanceled || _taskRequesterCancellationToken.IsCancellationRequested; public abstract ExecutionOptions ExecutionOptions { get; } @@ -57,8 +80,7 @@ public void ExecuteSynchronously(CancellationToken executorCancellationToken) try { TResult result = Run(cancellationSource.Token); - - _taskCompletionSource.SetResult(result); + SetResult(result); } catch (OperationCanceledException) { @@ -66,15 +88,34 @@ public void ExecuteSynchronously(CancellationToken executorCancellationToken) } catch (Exception e) { - _taskCompletionSource.SetException(e); + SetException(e); } } } + public TResult ExecuteAndGetResult(CancellationToken cancellationToken) + { + ExecuteSynchronously(cancellationToken); + return Result; + } + private void SetCanceled() { _executionCanceled = true; _taskCompletionSource.SetCanceled(); } + + private void SetException(Exception e) + { + // We use this to capture the original stack trace so that exceptions will be useful later + _exceptionInfo = ExceptionDispatchInfo.Capture(e); + _taskCompletionSource.SetException(e); + } + + private void SetResult(TResult result) + { + _result = result; + _taskCompletionSource.SetResult(result); + } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs index c6d3ca999..112241f4e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs @@ -20,9 +20,13 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Reflection; + using System.Text; + using System.Threading; internal class InternalHost : PSHost, IHostSupportsInteractiveSession, IRunspaceContext { + private const string DefaultPrompt = "PSIC> "; + private static readonly string s_commandsModulePath = Path.GetFullPath( Path.Combine( Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @@ -36,8 +40,6 @@ internal class InternalHost : PSHost, IHostSupportsInteractiveSession, IRunspace private readonly HostStartupInfo _hostInfo; - private readonly ReadLineProvider _readLineProvider; - private readonly BlockingConcurrentDeque _taskQueue; private readonly Stack _psFrameStack; @@ -48,10 +50,16 @@ internal class InternalHost : PSHost, IHostSupportsInteractiveSession, IRunspace private readonly EditorServicesConsolePSHost _publicHost; + private readonly ReadLineProvider _readLineProvider; + + private readonly PowerShellExecutor _executor; + private bool _shouldExit = false; private string _localComputerName; + private ConsoleKeyInfo? _lastKey; + public InternalHost( ILoggerFactory loggerFactory, ILanguageServerFacade languageServer, @@ -62,14 +70,14 @@ public InternalHost( _logger = loggerFactory.CreateLogger(); _languageServer = languageServer; _hostInfo = hostInfo; - _publicHost = publicHost; - _readLineProvider = new ReadLineProvider(loggerFactory); _taskQueue = new BlockingConcurrentDeque(); _psFrameStack = new Stack(); _runspaceStack = new Stack<(Runspace, RunspaceInfo)>(); _psFactory = new PowerShellFactory(loggerFactory, this); + _executor = new PowerShellExecutor(loggerFactory, publicHost); + PublicHost = publicHost; Name = hostInfo.Name; Version = hostInfo.Version; @@ -94,9 +102,11 @@ public InternalHost( public RunspaceInfo CurrentRunspace => CurrentFrame.RunspaceInfo; - IRunspaceInfo IRunspaceContext.CurrentRunspace => CurrentRunspace; + public SMA.PowerShell CurrentPowerShell => CurrentFrame.PowerShell; - private SMA.PowerShell CurrentPowerShell => CurrentFrame.PowerShell; + public EditorServicesConsolePSHost PublicHost { get; } + + IRunspaceInfo IRunspaceContext.CurrentRunspace => CurrentRunspace; private PowerShellContextFrame CurrentFrame => _psFrameStack.Peek(); @@ -138,6 +148,12 @@ public override void SetShouldExit(int exitCode) SetExit(); } + public void Start() + { + SMA.PowerShell pwsh = CreateInitialPowerShell(_hostInfo, _readLineProvider); + PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal); + } + private void SetExit() { if (_psFrameStack.Count <= 1) @@ -186,15 +202,124 @@ private void PushPowerShellAndRunLoop(PowerShellContextFrame frame) _psFrameStack.Push(frame); - RunExecutionLoop(); + try + { + RunExecutionLoop(); + } + finally + { + PopPowerShell(); + } + } + + private void PopPowerShell() + { + _shouldExit = false; + PowerShellContextFrame frame = _psFrameStack.Pop(); + try + { + RemoveRunspaceEventHandlers(frame.PowerShell.Runspace); + + if (_runspaceStack.Peek().Item1 != CurrentPowerShell.Runspace) + { + _runspaceStack.Pop(); + } + } + finally + { + frame.Dispose(); + } } private void RunExecutionLoop() { while (true) { + DoOneRepl(CancellationToken.None); + + if (_shouldExit) + { + break; + } + + while (_taskQueue.TryTake(out ISynchronousTask task)) + { + RunTaskSynchronously(task, CancellationToken.None); + } + } + } + + private void DoOneRepl(CancellationToken cancellationToken) + { + try + { + string prompt = GetPrompt(cancellationToken) ?? DefaultPrompt; + UI.Write(prompt); + string userInput = InvokeReadLine(cancellationToken); + + // If the user input was empty it's because: + // - the user provided no input + // - the readline task was canceled + // - CtrlC was sent to readline (which does not propagate a cancellation) + // + // In any event there's nothing to run in PowerShell, so we just loop back to the prompt again. + // However, we must distinguish the last two scenarios, since PSRL will not print a new line in those cases. + if (string.IsNullOrEmpty(userInput)) + { + if (cancellationToken.IsCancellationRequested + || LastKeyWasCtrlC()) + { + UI.WriteLine(); + } + return; + } + InvokeInput(userInput, cancellationToken); + } + catch (OperationCanceledException) + { + // Do nothing, since we were just cancelled } + catch (Exception e) + { + UI.WriteErrorLine($"An error occurred while running the REPL loop:{Environment.NewLine}{e}"); + _logger.LogError(e, "An error occurred while running the REPL loop"); + } + } + + private string GetPrompt(CancellationToken cancellationToken) + { + var command = new PSCommand().AddCommand("prompt"); + IReadOnlyList results = _executor.InvokePSCommand(command, PowerShellExecutionOptions.Default, cancellationToken); + return results.Count > 0 ? results[0] : null; + } + + private string InvokeReadLine(CancellationToken cancellationToken) + { + return _readLineProvider.ReadLine.ReadLine(cancellationToken); + } + + private void InvokeInput(string input, CancellationToken cancellationToken) + { + var command = new PSCommand().AddScript(input, useLocalScope: false); + _executor.InvokePSCommand(command, new PowerShellExecutionOptions { AddToHistory = true, WriteErrorsToHost = true, WriteOutputToHost = true }, cancellationToken); + } + + private void RunTaskSynchronously(ISynchronousTask task, CancellationToken cancellationToken) + { + if (task.IsCanceled) + { + return; + } + + task.ExecuteSynchronously(cancellationToken); + } + + private IReadOnlyList RunPSCommandSynchronously(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) + { + var task = new SynchronousPowerShellTask(_logger, _publicHost, psCommand, executionOptions, cancellationToken); + task.ExecuteSynchronously(cancellationToken); + return task.Result; } private void AddRunspaceEventHandlers(Runspace runspace) @@ -249,8 +374,14 @@ public PowerShell CreateInitialPowerShell( if (hostStartupInfo.ConsoleReplEnabled && !hostStartupInfo.UsesLegacyReadLine) { var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, pwsh); - var readLine = new ConsoleReadLine(psrlProxy, this, engineIntrinsics); + var readLine = new ConsoleReadLine(psrlProxy, this, _executor, engineIntrinsics); + _readLineProvider.ReadLine.TryOverrideReadKey(ReadKey); + readLine.TryOverrideReadKey(ReadKey); + readLine.TryOverrideIdleHandler(OnPowerShellIdle); readLineProvider.OverrideReadLine(readLine); + System.Console.CancelKeyPress += OnCancelKeyPress; + System.Console.InputEncoding = Encoding.UTF8; + System.Console.OutputEncoding = Encoding.UTF8; } if (VersionUtils.IsWindows) @@ -291,6 +422,34 @@ private Runspace CreateInitialRunspace(PSLanguageMode languageMode) return runspace; } + private void OnPowerShellIdle() + { + + } + + private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) + { + + } + + private ConsoleKeyInfo ReadKey(bool intercept) + { + // PSRL doesn't tell us when CtrlC was sent. + // So instead we keep track of the last key here. + // This isn't functionally required, + // but helps us determine when the prompt needs a newline added + + _lastKey = ConsoleProxy.SafeReadKey(intercept, CancellationToken.None); + return _lastKey.Value; + } + + private bool LastKeyWasCtrlC() + { + return _lastKey.HasValue + && _lastKey.Value.Key == ConsoleKey.C + && (_lastKey.Value.Modifiers & ConsoleModifiers.Control) != 0 + && (_lastKey.Value.Modifiers & ConsoleModifiers.Alt) != 0; + } private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) { From 3ba93fe80aea90d6dbc9ea8e3c58a15f83929f7f Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 13 Aug 2021 14:23:24 -0700 Subject: [PATCH 067/176] Move all basic functionality into a new synchronous internal host --- .../Server/PsesDebugServer.cs | 13 +- .../Server/PsesServiceCollectionExtensions.cs | 13 +- .../Services/DebugAdapter/DebugService.cs | 4 +- .../PowerShell/Console/ConsoleReadLine.cs | 8 +- .../PowerShell/Console/ConsoleReplRunner.cs | 4 +- .../Debugging/DscBreakpointCapability.cs | 5 +- .../Debugging/PowerShellDebugContext.cs | 13 +- .../Execution/ISynchronousExecutor.cs | 47 --- .../Execution/PipelineThreadExecutor.cs | 10 +- .../Execution/PowerShellExecutor.cs | 59 --- .../Execution/SynchronousDelegateTask.cs | 8 +- .../Execution/SynchronousPowerShellTask.cs | 4 +- .../PowerShell/Execution/SynchronousTask.cs | 5 + .../Host/EditorServicesConsolePSHost.cs | 375 +---------------- ...ditorServicesConsolePSHostUserInterface.cs | 4 +- .../Host/EditorServicesConsolePSHost_Old.cs | 387 ++++++++++++++++++ .../Services/PowerShell/Host/InternalHost.cs | 258 ++++++++++-- .../PowerShell/Host/PowerShellFactory.cs | 120 ------ .../PowerShell/PowerShellExecutionService.cs | 37 +- .../PowerShell/Runspace/RunspaceInfo.cs | 5 +- .../Handlers/ConfigurationHandler.cs | 4 +- 21 files changed, 687 insertions(+), 696 deletions(-) delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/ISynchronousExecutor.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutor.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost_Old.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/PowerShellFactory.cs diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index e1749446e..2e30187bb 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -11,6 +11,7 @@ using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using OmniSharp.Extensions.DebugAdapter.Server; @@ -42,9 +43,7 @@ internal class PsesDebugServer : IDisposable private DebugAdapterServer _debugAdapterServer; - private PowerShellExecutionService _executionService; - - private EditorServicesConsolePSHost _psesHost; + private PowerShellDebugContext _debugContext; protected readonly ILoggerFactory _loggerFactory; @@ -77,10 +76,8 @@ public async Task StartAsync() { // We need to let the PowerShell Context Service know that we are in a debug session // so that it doesn't send the powerShell/startDebugger message. - _psesHost = ServiceProvider.GetService(); - _psesHost.DebugContext.IsDebugServerActive = true; - - _executionService = ServiceProvider.GetService(); + _debugContext = ServiceProvider.GetService().DebugContext; + _debugContext.IsDebugServerActive = true; /* // Needed to make sure PSReadLine's static properties are initialized in the pipeline thread. @@ -144,7 +141,7 @@ public async Task StartAsync() public void Dispose() { // TODO: If the debugger has stopped, should we clear the breakpoints? - _psesHost.DebugContext.IsDebugServerActive = false; + _debugContext.IsDebugServerActive = false; _debugAdapterServer.Dispose(); _inputStream.Dispose(); _outputStream.Dispose(); diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index 29281c1b2..b89b6566d 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -28,14 +28,13 @@ public static IServiceCollection AddPsesLanguageServices( .AddSingleton(hostStartupInfo) .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton( - (provider) => provider.GetService()) - .AddSingleton( - (provider) => provider.GetService().ExecutionService) + (provider) => provider.GetService()) + .AddSingleton() .AddSingleton() .AddSingleton( - (provider) => provider.GetService().DebugContext) + (provider) => provider.GetService().DebugContext) .AddSingleton() .AddSingleton() .AddSingleton() @@ -67,8 +66,8 @@ public static IServiceCollection AddPsesDebugServices( { return collection .AddSingleton(languageServiceProvider.GetService()) - .AddSingleton(languageServiceProvider.GetService()) - .AddSingleton(languageServiceProvider.GetService().DebugContext) + .AddSingleton(languageServiceProvider.GetService()) + .AddSingleton(languageServiceProvider.GetService().DebugContext) .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(languageServiceProvider.GetService()) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 9db53c00e..27ce4338c 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -39,7 +39,7 @@ internal class DebugService private readonly BreakpointService _breakpointService; private readonly RemoteFileManagerService remoteFileManager; - private readonly EditorServicesConsolePSHost _psesHost; + private readonly InternalHost _psesHost; private readonly IPowerShellDebugContext _debugContext; @@ -107,7 +107,7 @@ public DebugService( IPowerShellDebugContext debugContext, RemoteFileManagerService remoteFileManager, BreakpointService breakpointService, - EditorServicesConsolePSHost psesHost, + InternalHost psesHost, ILoggerFactory factory) { Validate.IsNotNull(nameof(executionService), executionService); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index e1484432a..d0c798c74 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -25,19 +25,15 @@ internal class ConsoleReadLine : IReadLine private readonly EngineIntrinsics _engineIntrinsics; - private ISynchronousExecutor _executor; - #region Constructors public ConsoleReadLine( PSReadLineProxy psrlProxy, InternalHost psesHost, - ISynchronousExecutor executor, EngineIntrinsics engineIntrinsics) { _psrlProxy = psrlProxy; _psesHost = psesHost; - _executor = executor; _engineIntrinsics = engineIntrinsics; } @@ -47,7 +43,7 @@ public ConsoleReadLine( public string ReadLine(CancellationToken cancellationToken) { - return _executor.InvokeDelegate(representation: "ReadLine", new ExecutionOptions { MustRunInForeground = true }, InvokePSReadLine, cancellationToken); + return _psesHost.InvokeDelegate(representation: "ReadLine", new ExecutionOptions { MustRunInForeground = true }, InvokePSReadLine, cancellationToken); } public bool TryOverrideReadKey(Func readKeyFunc) @@ -351,7 +347,7 @@ internal string InvokeLegacyReadLine(bool isCommandLine, CancellationToken cance PSCommand command = new PSCommand().AddCommand("Get-History"); - currentHistory = _executor.InvokePSCommand( + currentHistory = _psesHost.InvokePSCommand( command, PowerShellExecutionOptions.Default, cancellationToken); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs index 0f3900b1f..e4e0dd3df 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +/* +using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using System; @@ -300,3 +301,4 @@ public CommandCancellation() } } } +*/ diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index e4d40fdfb..05b645d6d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -8,7 +8,6 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; -using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -90,7 +89,7 @@ public bool IsDscResourcePath(string scriptPath) public static async Task GetDscCapabilityAsync( ILogger logger, IRunspaceInfo currentRunspace, - PowerShellExecutionService executionService, + InternalHost psesHost, CancellationToken cancellationToken) { // DSC support is enabled only for Windows PowerShell. @@ -165,7 +164,7 @@ public static async Task GetDscCapabilityAsync( return capability; }; - return await executionService.ExecuteDelegateAsync( + return await psesHost.ExecuteDelegateAsync( nameof(getDscBreakpointCapabilityFunc), ExecutionOptions.Default, cancellationToken, diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index e84b5407d..8a7022968 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -39,22 +39,18 @@ internal class PowerShellDebugContext : IPowerShellDebugContext private readonly ILanguageServerFacade _languageServer; - private readonly EditorServicesConsolePSHost _psesHost; - - private readonly ConsoleReplRunner _consoleRepl; + private readonly InternalHost _psesHost; private CancellationTokenSource _debugLoopCancellationSource; public PowerShellDebugContext( ILoggerFactory loggerFactory, ILanguageServerFacade languageServer, - EditorServicesConsolePSHost psesHost, - ConsoleReplRunner consoleReplRunner) + InternalHost psesHost) { _logger = loggerFactory.CreateLogger(); _languageServer = languageServer; _psesHost = psesHost; - _consoleRepl = consoleReplRunner; } public bool IsStopped { get; private set; } @@ -71,7 +67,7 @@ public PowerShellDebugContext( public Task GetDscBreakpointCapabilityAsync(CancellationToken cancellationToken) { - return _psesHost.CurrentRunspace.GetDscBreakpointCapabilityAsync(_logger, _psesHost.ExecutionService, cancellationToken); + return _psesHost.CurrentRunspace.GetDscBreakpointCapabilityAsync(_logger, _psesHost, cancellationToken); } public void Abort() @@ -106,7 +102,7 @@ public void StepOver() public void SetDebugResuming(DebuggerResumeAction debuggerResumeAction) { - _consoleRepl?.SetReplPop(); + _psesHost.SetExit(); LastStopEventArgs.ResumeAction = debuggerResumeAction; _debugLoopCancellationSource.Cancel(); } @@ -115,7 +111,6 @@ public void EnterDebugLoop(CancellationToken loopCancellationToken) { _debugLoopCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(loopCancellationToken); RaiseDebuggerStoppedEvent(); - } public void ExitDebugLoop() diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/ISynchronousExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/ISynchronousExecutor.cs deleted file mode 100644 index 231289fad..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/ISynchronousExecutor.cs +++ /dev/null @@ -1,47 +0,0 @@ -using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using System; -using System.Collections.Generic; -using SMA = System.Management.Automation; -using System.Text; -using System.Threading; -using System.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution -{ - internal interface ISynchronousExecutor - { - TResult InvokeDelegate( - string representation, - ExecutionOptions executionOptions, - Func func, - CancellationToken cancellationToken); - - void InvokeDelegate( - string representation, - ExecutionOptions executionOptions, - Action action, - CancellationToken cancellationToken); - - TResult InvokePSDelegate( - string representation, - ExecutionOptions executionOptions, - Func func, - CancellationToken cancellationToken); - - void InvokePSDelegate( - string representation, - ExecutionOptions executionOptions, - Action action, - CancellationToken cancellationToken); - - IReadOnlyList InvokePSCommand( - PSCommand psCommand, - PowerShellExecutionOptions executionOptions, - CancellationToken cancellationToken); - - void InvokePSCommand( - PSCommand psCommand, - PowerShellExecutionOptions executionOptions, - CancellationToken cancellationToken); - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs index 51cc2153e..6ebf219af 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +/* +using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; @@ -26,7 +27,7 @@ internal class PipelineThreadExecutor private readonly ILogger _logger; - private readonly EditorServicesConsolePSHost _psesHost; + private readonly InternalHost _psesHost; private readonly IReadLineProvider _readLineProvider; @@ -36,7 +37,7 @@ internal class PipelineThreadExecutor private readonly CancellationTokenSource _consumerThreadCancellationSource; - private readonly Thread _pipelineThread; + private readonly Thread _pipelineThread; private readonly CancellationContext _loopCancellationContext; @@ -47,7 +48,7 @@ internal class PipelineThreadExecutor public PipelineThreadExecutor( ILoggerFactory loggerFactory, HostStartupInfo hostInfo, - EditorServicesConsolePSHost psesHost, + InternalHost psesHost, IReadLineProvider readLineProvider) { _logger = loggerFactory.CreateLogger(); @@ -295,3 +296,4 @@ public void OnPowerShellIdle() } } } +*/ diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutor.cs deleted file mode 100644 index c3e246a6d..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PowerShellExecutor.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Threading; -using SMA = System.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution -{ - internal class PowerShellExecutor : ISynchronousExecutor - { - private readonly ILogger _logger; - - private readonly EditorServicesConsolePSHost _host; - - public PowerShellExecutor( - ILoggerFactory loggerFactory, - EditorServicesConsolePSHost host) - { - _logger = loggerFactory.CreateLogger(); - _host = host; - } - - public TResult InvokeDelegate(string representation, ExecutionOptions executionOptions, Func func, CancellationToken cancellationToken) - { - var task = new SynchronousDelegateTask(_logger, representation, executionOptions, cancellationToken, func); - return task.ExecuteAndGetResult(cancellationToken); - } - - public void InvokeDelegate(string representation, ExecutionOptions executionOptions, Action action, CancellationToken cancellationToken) - { - var task = new SynchronousDelegateTask(_logger, representation, executionOptions, cancellationToken, action); - task.ExecuteAndGetResult(cancellationToken); - } - - public IReadOnlyList InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) - { - var task = new SynchronousPowerShellTask(_logger, _host, psCommand, executionOptions, cancellationToken); - return task.ExecuteAndGetResult(cancellationToken); - } - - public void InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) - => InvokePSCommand(psCommand, executionOptions, cancellationToken); - - public TResult InvokePSDelegate(string representation, ExecutionOptions executionOptions, Func func, CancellationToken cancellationToken) - { - var task = new SynchronousPSDelegateTask(_logger, _host, representation, executionOptions, cancellationToken, func); - return task.ExecuteAndGetResult(cancellationToken); - } - - public void InvokePSDelegate(string representation, ExecutionOptions executionOptions, Action action, CancellationToken cancellationToken) - { - var task = new SynchronousPSDelegateTask(_logger, _host, representation, executionOptions, cancellationToken, action); - task.ExecuteAndGetResult(cancellationToken); - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs index cee944250..4aad8035b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs @@ -78,11 +78,11 @@ internal class SynchronousPSDelegateTask : SynchronousTask private readonly string _representation; - private readonly EditorServicesConsolePSHost _psesHost; + private readonly InternalHost _psesHost; public SynchronousPSDelegateTask( ILogger logger, - EditorServicesConsolePSHost psesHost, + InternalHost psesHost, string representation, ExecutionOptions executionOptions, CancellationToken cancellationToken, @@ -115,11 +115,11 @@ internal class SynchronousPSDelegateTask : SynchronousTask private readonly string _representation; - private readonly EditorServicesConsolePSHost _psesHost; + private readonly InternalHost _psesHost; public SynchronousPSDelegateTask( ILogger logger, - EditorServicesConsolePSHost psesHost, + InternalHost psesHost, string representation, ExecutionOptions executionOptions, CancellationToken cancellationToken, diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index d402b7aee..b264cf364 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -15,7 +15,7 @@ internal class SynchronousPowerShellTask : SynchronousTask : SynchronousTask _psFrameStack; - - private readonly PowerShellFactory _psFactory; - - private readonly ConsoleReplRunner _consoleReplRunner; - - private readonly PipelineThreadExecutor _pipelineExecutor; - - private readonly HostStartupInfo _hostInfo; - - private readonly ReadLineProvider _readLineProvider; - - private readonly Stack> _runspacesInUse; - - private readonly Thread _topRunspaceThread; - - private string _localComputerName; - - private int _hostStarted = 0; + private readonly InternalHost _internalHost; public EditorServicesConsolePSHost( ILoggerFactory loggerFactory, ILanguageServerFacade languageServer, HostStartupInfo hostInfo) { - _logger = loggerFactory.CreateLogger(); - _psFrameStack = new Stack(); - _psFactory = new PowerShellFactory(loggerFactory, this); - _runspacesInUse = new Stack>(); - _hostInfo = hostInfo; - Name = hostInfo.Name; - Version = hostInfo.Version; - - _topRunspaceThread = new Thread(Run); - - _readLineProvider = new ReadLineProvider(loggerFactory); - _pipelineExecutor = new PipelineThreadExecutor(loggerFactory, hostInfo, this, _readLineProvider); - ExecutionService = new PowerShellExecutionService(loggerFactory, this, _pipelineExecutor); - UI = new EditorServicesConsolePSHostUserInterface(loggerFactory, _readLineProvider, hostInfo.PSHost.UI); - - if (hostInfo.ConsoleReplEnabled) - { - _consoleReplRunner = new ConsoleReplRunner(loggerFactory, this, _readLineProvider, ExecutionService); - } - - DebugContext = new PowerShellDebugContext(loggerFactory, languageServer, this, _consoleReplRunner); - } - - public override CultureInfo CurrentCulture => _hostInfo.PSHost.CurrentCulture; - - public override CultureInfo CurrentUICulture => _hostInfo.PSHost.CurrentUICulture; - - public override Guid InstanceId { get; } = Guid.NewGuid(); - - public override string Name { get; } - - public override PSHostUserInterface UI { get; } - - public override Version Version { get; } - - public bool IsRunspacePushed { get; private set; } - - internal bool IsRunning => _hostStarted != 0; - - public Runspace Runspace => CurrentPowerShell.Runspace; - - internal string InitialWorkingDirectory { get; private set; } - - internal PowerShellExecutionService ExecutionService { get; } - - internal PowerShellDebugContext DebugContext { get; } - - internal SMA.PowerShell CurrentPowerShell => CurrentFrame.PowerShell; - - internal RunspaceInfo CurrentRunspace => CurrentFrame.RunspaceInfo; - - IRunspaceInfo IRunspaceContext.CurrentRunspace => CurrentRunspace; - - internal CancellationTokenSource CurrentCancellationSource => CurrentFrame.CancellationTokenSource; - - private PowerShellContextFrame CurrentFrame => _psFrameStack.Peek(); - - public override void EnterNestedPrompt() - { - PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested); - } - - public override void ExitNestedPrompt() - { - SetExit(); - } - - public override void NotifyBeginApplication() - { - // TODO: Work out what to do here - } - - public override void NotifyEndApplication() - { - // TODO: Work out what to do here - } - - public void PopRunspace() - { - IsRunspacePushed = false; - SetExit(); - } - - public void PushRunspace(Runspace runspace) - { - IsRunspacePushed = true; - PushPowerShellAndRunLoop(_psFactory.CreatePowerShellForRunspace(runspace), PowerShellFrameType.Remote); - } - - public override void SetShouldExit(int exitCode) - { - SetExit(); - } - - internal void Start() - { - _topRunspaceThread.Start(); - } - - private void Run() - { - PushInitialPowerShell(); - } - - public void PushInitialPowerShell() - { - SMA.PowerShell pwsh = _psFactory.CreateInitialPowerShell(_hostInfo, _readLineProvider); - var runspaceInfo = RunspaceInfo.CreateFromLocalPowerShell(_logger, pwsh); - _localComputerName = runspaceInfo.SessionDetails.ComputerName; - PushPowerShell(new PowerShellContextFrame(pwsh, runspaceInfo, PowerShellFrameType.Normal)); - } - - internal void PushNonInteractivePowerShell() - { - PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested | PowerShellFrameType.NonInteractive); - } - - internal void CancelCurrentPrompt() - { - _consoleReplRunner?.CancelCurrentPrompt(); - } - - internal void StartRepl() - { - _consoleReplRunner?.StartRepl(); - } - - internal void PushNewReplTask() - { - _consoleReplRunner?.PushNewReplTask(); - } - - internal Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cancellationToken) - { - InitialWorkingDirectory = path; - - return ExecutionService.ExecutePSCommandAsync( - new PSCommand().AddCommand("Set-Location").AddParameter("LiteralPath", path), - cancellationToken); - } - - public async Task StartAsync(HostStartOptions hostStartOptions, CancellationToken cancellationToken) - { - _logger.LogInformation("Host starting"); - if (Interlocked.Exchange(ref _hostStarted, 1) != 0) - { - _logger.LogDebug("Host start requested after already started"); - return; - } - - _pipelineExecutor.Start(); - - if (hostStartOptions.LoadProfiles) - { - await ExecutionService.ExecuteDelegateAsync( - "LoadProfiles", - new PowerShellExecutionOptions { MustRunInForeground = true }, - cancellationToken, - (pwsh, delegateCancellation) => - { - pwsh.LoadProfiles(_hostInfo.ProfilePaths); - }).ConfigureAwait(false); - - _logger.LogInformation("Profiles loaded"); - } - - if (hostStartOptions.InitialWorkingDirectory != null) - { - await SetInitialWorkingDirectoryAsync(hostStartOptions.InitialWorkingDirectory, CancellationToken.None).ConfigureAwait(false); - } - } - - private void SetExit() - { - if (_psFrameStack.Count <= 1) - { - return; - } - - _pipelineExecutor.IsExiting = true; - - if ((CurrentFrame.FrameType & PowerShellFrameType.NonInteractive) == 0) - { - _consoleReplRunner?.SetReplPop(); - } + _internalHost = new InternalHost(loggerFactory, languageServer, hostInfo, this); } - private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType) - { - RunspaceInfo runspaceInfo = null; - if (_runspacesInUse.Count > 0) - { - // This is more than just an optimization. - // When debugging, we cannot execute PowerShell directly to get this information; - // trying to do so will block on the command that called us, deadlocking execution. - // Instead, since we are reusing the runspace, we reuse that runspace's info as well. - KeyValuePair currentRunspace = _runspacesInUse.Peek(); - if (currentRunspace.Key == pwsh.Runspace) - { - runspaceInfo = currentRunspace.Value; - } - } + public override CultureInfo CurrentCulture => _internalHost.CurrentCulture; - if (runspaceInfo is null) - { - RunspaceOrigin runspaceOrigin = pwsh.Runspace.RunspaceIsRemote ? RunspaceOrigin.EnteredProcess : RunspaceOrigin.Local; - runspaceInfo = RunspaceInfo.CreateFromPowerShell(_logger, pwsh, runspaceOrigin, _localComputerName); - _runspacesInUse.Push(new KeyValuePair(pwsh.Runspace, runspaceInfo)); - } + public override CultureInfo CurrentUICulture => _internalHost.CurrentUICulture; - // TODO: Improve runspace origin detection here - PushPowerShellAndRunLoop(new PowerShellContextFrame(pwsh, runspaceInfo, frameType)); - } + public override Guid InstanceId => _internalHost.InstanceId; - private void PushPowerShellAndRunLoop(PowerShellContextFrame frame) - { - PushPowerShell(frame); - _pipelineExecutor.RunPowerShellLoop(frame.FrameType); - } + public override string Name => _internalHost.Name; - private void PushPowerShell(PowerShellContextFrame frame) - { - if (_psFrameStack.Count > 0) - { - RemoveRunspaceEventHandlers(CurrentFrame.PowerShell.Runspace); - } - AddRunspaceEventHandlers(frame.PowerShell.Runspace); + public override PSHostUserInterface UI => _internalHost.UI; - _psFrameStack.Push(frame); - } + public override Version Version => _internalHost.Version; - internal void PopPowerShell() - { - _pipelineExecutor.IsExiting = false; - PowerShellContextFrame frame = _psFrameStack.Pop(); - try - { - RemoveRunspaceEventHandlers(frame.PowerShell.Runspace); - if (_psFrameStack.Count > 0) - { - AddRunspaceEventHandlers(CurrentPowerShell.Runspace); - } - } - finally - { - frame.Dispose(); - } - } + public bool IsRunspacePushed => _internalHost.IsRunspacePushed; - private void AddRunspaceEventHandlers(Runspace runspace) - { - runspace.Debugger.DebuggerStop += OnDebuggerStopped; - runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; - runspace.StateChanged += OnRunspaceStateChanged; - } - - private void RemoveRunspaceEventHandlers(Runspace runspace) - { - runspace.Debugger.DebuggerStop -= OnDebuggerStopped; - runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; - runspace.StateChanged -= OnRunspaceStateChanged; - } - - private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) - { - DebugContext.SetDebuggerStopped(debuggerStopEventArgs); - try - { - CurrentPowerShell.WaitForRemoteOutputIfNeeded(); - PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Debug | PowerShellFrameType.Nested); - CurrentPowerShell.ResumeRemoteOutputIfNeeded(); - } - finally - { - DebugContext.SetDebuggerResumed(); - } - } + public System.Management.Automation.Runspaces.Runspace Runspace => _internalHost.Runspace; - private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) - { - DebugContext.HandleBreakpointUpdated(breakpointUpdatedEventArgs); - } + public override void EnterNestedPrompt() => _internalHost.EnterNestedPrompt(); - private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) - { - if (!runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) - { - PopOrReinitializeRunspaceAsync(); - } - } + public override void ExitNestedPrompt() => _internalHost.ExitNestedPrompt(); - private Task PopOrReinitializeRunspaceAsync() - { - _consoleReplRunner?.SetReplPop(); - RunspaceStateInfo oldRunspaceState = CurrentPowerShell.Runspace.RunspaceStateInfo; + public override void NotifyBeginApplication() => _internalHost.NotifyBeginApplication(); - // Rather than try to lock the PowerShell executor while we alter its state, - // we simply run this on its thread, guaranteeing that no other action can occur - return _pipelineExecutor.RunTaskAsync(new SynchronousDelegateTask( - _logger, - nameof(PopOrReinitializeRunspaceAsync), - new ExecutionOptions { InterruptCurrentForeground = true }, - CancellationToken.None, - (cancellationToken) => - { - while (_psFrameStack.Count > 0 - && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) - { - PopPowerShell(); - } + public override void NotifyEndApplication() => _internalHost.NotifyEndApplication(); - if (_psFrameStack.Count == 0) - { - // If our main runspace was corrupted, - // we must re-initialize our state. - // TODO: Use runspace.ResetRunspaceState() here instead - PushInitialPowerShell(); + public void PopRunspace() => _internalHost.PopRunspace(); - _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." - + " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs."); - UI.WriteErrorLine("The main runspace encountered an error and has been reinitialized. See the PowerShell extension logs for more details."); - } - else - { - _logger.LogError($"Current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was popped."); - UI.WriteErrorLine($"The current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}'." - + " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior." - + " The session is now returning to the previous runspace."); - } - })); - } + public void PushRunspace(System.Management.Automation.Runspaces.Runspace runspace) => _internalHost.PushRunspace(runspace); + public override void SetShouldExit(int exitCode) => _internalHost.SetShouldExit(exitCode); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs index 781af6733..2f0646c5a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs @@ -84,12 +84,12 @@ public override PSCredential PromptForCredential(string caption, string message, public override string ReadLine() { - return _readLineProvider.ReadLine.ReadLineAsync(CancellationToken.None).GetAwaiter().GetResult(); + return _readLineProvider.ReadLine.ReadLine(CancellationToken.None); } public override SecureString ReadLineAsSecureString() { - return _readLineProvider.ReadLine.ReadSecureLineAsync(CancellationToken.None).GetAwaiter().GetResult(); + return _readLineProvider.ReadLine.ReadSecureLine(CancellationToken.None); } public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost_Old.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost_Old.cs new file mode 100644 index 000000000..eb35c695b --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost_Old.cs @@ -0,0 +1,387 @@ +/* +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Management.Automation; +using System.Management.Automation.Host; +using System.Threading; +using System.Threading.Tasks; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host +{ + using System.Management.Automation.Runspaces; + + internal class EditorServicesConsolePSHost_Old : PSHost, IHostSupportsInteractiveSession, IRunspaceContext + { + private readonly ILogger _logger; + + private readonly Stack _psFrameStack; + + private readonly PowerShellFactory _psFactory; + + private readonly ConsoleReplRunner _consoleReplRunner; + + private readonly PipelineThreadExecutor _pipelineExecutor; + + private readonly HostStartupInfo _hostInfo; + + private readonly ReadLineProvider _readLineProvider; + + private readonly Stack> _runspacesInUse; + + private readonly Thread _topRunspaceThread; + + private string _localComputerName; + + private int _hostStarted = 0; + + public EditorServicesConsolePSHost( + ILoggerFactory loggerFactory, + ILanguageServerFacade languageServer, + HostStartupInfo hostInfo) + { + _logger = loggerFactory.CreateLogger(); + _psFrameStack = new Stack(); + _psFactory = new PowerShellFactory(loggerFactory, this); + _runspacesInUse = new Stack>(); + _hostInfo = hostInfo; + Name = hostInfo.Name; + Version = hostInfo.Version; + + _topRunspaceThread = new Thread(Run); + + _readLineProvider = new ReadLineProvider(loggerFactory); + _pipelineExecutor = new PipelineThreadExecutor(loggerFactory, hostInfo, this, _readLineProvider); + ExecutionService = new PowerShellExecutionService(loggerFactory, this, _pipelineExecutor); + UI = new EditorServicesConsolePSHostUserInterface(loggerFactory, _readLineProvider, hostInfo.PSHost.UI); + + if (hostInfo.ConsoleReplEnabled) + { + _consoleReplRunner = new ConsoleReplRunner(loggerFactory, this, _readLineProvider, ExecutionService); + } + + DebugContext = new PowerShellDebugContext(loggerFactory, languageServer, this, _consoleReplRunner); + } + + public override CultureInfo CurrentCulture => _hostInfo.PSHost.CurrentCulture; + + public override CultureInfo CurrentUICulture => _hostInfo.PSHost.CurrentUICulture; + + public override Guid InstanceId { get; } = Guid.NewGuid(); + + public override string Name { get; } + + public override PSHostUserInterface UI { get; } + + public override Version Version { get; } + + public bool IsRunspacePushed { get; private set; } + + internal bool IsRunning => _hostStarted != 0; + + public Runspace Runspace => CurrentPowerShell.Runspace; + + internal string InitialWorkingDirectory { get; private set; } + + internal PowerShellExecutionService ExecutionService { get; } + + internal PowerShellDebugContext DebugContext { get; } + + internal SMA.PowerShell CurrentPowerShell => CurrentFrame.PowerShell; + + internal RunspaceInfo CurrentRunspace => CurrentFrame.RunspaceInfo; + + IRunspaceInfo IRunspaceContext.CurrentRunspace => CurrentRunspace; + + internal CancellationTokenSource CurrentCancellationSource => CurrentFrame.CancellationTokenSource; + + private PowerShellContextFrame CurrentFrame => _psFrameStack.Peek(); + + public override void EnterNestedPrompt() + { + PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested); + } + + public override void ExitNestedPrompt() + { + SetExit(); + } + + public override void NotifyBeginApplication() + { + // TODO: Work out what to do here + } + + public override void NotifyEndApplication() + { + // TODO: Work out what to do here + } + + public void PopRunspace() + { + IsRunspacePushed = false; + SetExit(); + } + + public void PushRunspace(Runspace runspace) + { + IsRunspacePushed = true; + PushPowerShellAndRunLoop(_psFactory.CreatePowerShellForRunspace(runspace), PowerShellFrameType.Remote); + } + + public override void SetShouldExit(int exitCode) + { + SetExit(); + } + + internal void Start() + { + _topRunspaceThread.Start(); + } + + private void Run() + { + PushInitialPowerShell(); + } + + public void PushInitialPowerShell() + { + SMA.PowerShell pwsh = _psFactory.CreateInitialPowerShell(_hostInfo, _readLineProvider); + var runspaceInfo = RunspaceInfo.CreateFromLocalPowerShell(_logger, pwsh); + _localComputerName = runspaceInfo.SessionDetails.ComputerName; + PushPowerShell(new PowerShellContextFrame(pwsh, runspaceInfo, PowerShellFrameType.Normal)); + } + + internal void PushNonInteractivePowerShell() + { + PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested | PowerShellFrameType.NonInteractive); + } + + internal void CancelCurrentPrompt() + { + _consoleReplRunner?.CancelCurrentPrompt(); + } + + internal void StartRepl() + { + _consoleReplRunner?.StartRepl(); + } + + internal void PushNewReplTask() + { + _consoleReplRunner?.PushNewReplTask(); + } + + internal Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cancellationToken) + { + InitialWorkingDirectory = path; + + return ExecutionService.ExecutePSCommandAsync( + new PSCommand().AddCommand("Set-Location").AddParameter("LiteralPath", path), + cancellationToken); + } + + public async Task StartAsync(HostStartOptions hostStartOptions, CancellationToken cancellationToken) + { + _logger.LogInformation("Host starting"); + if (Interlocked.Exchange(ref _hostStarted, 1) != 0) + { + _logger.LogDebug("Host start requested after already started"); + return; + } + + _pipelineExecutor.Start(); + + if (hostStartOptions.LoadProfiles) + { + await ExecutionService.ExecuteDelegateAsync( + "LoadProfiles", + new PowerShellExecutionOptions { MustRunInForeground = true }, + cancellationToken, + (pwsh, delegateCancellation) => + { + pwsh.LoadProfiles(_hostInfo.ProfilePaths); + }).ConfigureAwait(false); + + _logger.LogInformation("Profiles loaded"); + } + + if (hostStartOptions.InitialWorkingDirectory != null) + { + await SetInitialWorkingDirectoryAsync(hostStartOptions.InitialWorkingDirectory, CancellationToken.None).ConfigureAwait(false); + } + } + + private void SetExit() + { + if (_psFrameStack.Count <= 1) + { + return; + } + + _pipelineExecutor.IsExiting = true; + + if ((CurrentFrame.FrameType & PowerShellFrameType.NonInteractive) == 0) + { + _consoleReplRunner?.SetReplPop(); + } + } + + private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType) + { + RunspaceInfo runspaceInfo = null; + if (_runspacesInUse.Count > 0) + { + // This is more than just an optimization. + // When debugging, we cannot execute PowerShell directly to get this information; + // trying to do so will block on the command that called us, deadlocking execution. + // Instead, since we are reusing the runspace, we reuse that runspace's info as well. + KeyValuePair currentRunspace = _runspacesInUse.Peek(); + if (currentRunspace.Key == pwsh.Runspace) + { + runspaceInfo = currentRunspace.Value; + } + } + + if (runspaceInfo is null) + { + RunspaceOrigin runspaceOrigin = pwsh.Runspace.RunspaceIsRemote ? RunspaceOrigin.EnteredProcess : RunspaceOrigin.Local; + runspaceInfo = RunspaceInfo.CreateFromPowerShell(_logger, pwsh, runspaceOrigin, _localComputerName); + _runspacesInUse.Push(new KeyValuePair(pwsh.Runspace, runspaceInfo)); + } + + // TODO: Improve runspace origin detection here + PushPowerShellAndRunLoop(new PowerShellContextFrame(pwsh, runspaceInfo, frameType)); + } + + private void PushPowerShellAndRunLoop(PowerShellContextFrame frame) + { + PushPowerShell(frame); + _pipelineExecutor.RunPowerShellLoop(frame.FrameType); + } + + private void PushPowerShell(PowerShellContextFrame frame) + { + if (_psFrameStack.Count > 0) + { + RemoveRunspaceEventHandlers(CurrentFrame.PowerShell.Runspace); + } + AddRunspaceEventHandlers(frame.PowerShell.Runspace); + + _psFrameStack.Push(frame); + } + + internal void PopPowerShell() + { + _pipelineExecutor.IsExiting = false; + PowerShellContextFrame frame = _psFrameStack.Pop(); + try + { + RemoveRunspaceEventHandlers(frame.PowerShell.Runspace); + if (_psFrameStack.Count > 0) + { + AddRunspaceEventHandlers(CurrentPowerShell.Runspace); + } + } + finally + { + frame.Dispose(); + } + } + + private void AddRunspaceEventHandlers(Runspace runspace) + { + runspace.Debugger.DebuggerStop += OnDebuggerStopped; + runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; + runspace.StateChanged += OnRunspaceStateChanged; + } + + private void RemoveRunspaceEventHandlers(Runspace runspace) + { + runspace.Debugger.DebuggerStop -= OnDebuggerStopped; + runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; + runspace.StateChanged -= OnRunspaceStateChanged; + } + + private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) + { + DebugContext.SetDebuggerStopped(debuggerStopEventArgs); + try + { + CurrentPowerShell.WaitForRemoteOutputIfNeeded(); + PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Debug | PowerShellFrameType.Nested); + CurrentPowerShell.ResumeRemoteOutputIfNeeded(); + } + finally + { + DebugContext.SetDebuggerResumed(); + } + } + + private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) + { + DebugContext.HandleBreakpointUpdated(breakpointUpdatedEventArgs); + } + + private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) + { + if (!runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) + { + PopOrReinitializeRunspaceAsync(); + } + } + + private Task PopOrReinitializeRunspaceAsync() + { + _consoleReplRunner?.SetReplPop(); + RunspaceStateInfo oldRunspaceState = CurrentPowerShell.Runspace.RunspaceStateInfo; + + // Rather than try to lock the PowerShell executor while we alter its state, + // we simply run this on its thread, guaranteeing that no other action can occur + return _pipelineExecutor.RunTaskAsync(new SynchronousDelegateTask( + _logger, + nameof(PopOrReinitializeRunspaceAsync), + new ExecutionOptions { InterruptCurrentForeground = true }, + CancellationToken.None, + (cancellationToken) => + { + while (_psFrameStack.Count > 0 + && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) + { + PopPowerShell(); + } + + if (_psFrameStack.Count == 0) + { + // If our main runspace was corrupted, + // we must re-initialize our state. + // TODO: Use runspace.ResetRunspaceState() here instead + PushInitialPowerShell(); + + _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." + + " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs."); + UI.WriteErrorLine("The main runspace encountered an error and has been reinitialized. See the PowerShell extension logs for more details."); + } + else + { + _logger.LogError($"Current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was popped."); + UI.WriteErrorLine($"The current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}'." + + " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior." + + " The session is now returning to the previous runspace."); + } + })); + } + + } +} +*/ diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs index 112241f4e..b47caf5e4 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs @@ -22,6 +22,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host using System.Reflection; using System.Text; using System.Threading; + using System.Threading.Tasks; internal class InternalHost : PSHost, IHostSupportsInteractiveSession, IRunspaceContext { @@ -46,16 +47,16 @@ internal class InternalHost : PSHost, IHostSupportsInteractiveSession, IRunspace private readonly Stack<(Runspace, RunspaceInfo)> _runspaceStack; - private readonly PowerShellFactory _psFactory; - private readonly EditorServicesConsolePSHost _publicHost; private readonly ReadLineProvider _readLineProvider; - private readonly PowerShellExecutor _executor; + private readonly Thread _pipelineThread; private bool _shouldExit = false; + private int _isRunning = 0; + private string _localComputerName; private ConsoleKeyInfo? _lastKey; @@ -74,13 +75,17 @@ public InternalHost( _taskQueue = new BlockingConcurrentDeque(); _psFrameStack = new Stack(); _runspaceStack = new Stack<(Runspace, RunspaceInfo)>(); - _psFactory = new PowerShellFactory(loggerFactory, this); - _executor = new PowerShellExecutor(loggerFactory, publicHost); + + _pipelineThread = new Thread(Run) + { + Name = "PSES Pipeline Execution Thread", + }; PublicHost = publicHost; Name = hostInfo.Name; Version = hostInfo.Version; + DebugContext = new PowerShellDebugContext(loggerFactory, languageServer, this); UI = new EditorServicesConsolePSHostUserInterface(loggerFactory, _readLineProvider, hostInfo.PSHost.UI); } @@ -106,13 +111,19 @@ public InternalHost( public EditorServicesConsolePSHost PublicHost { get; } + public PowerShellDebugContext DebugContext { get; } + + public bool IsRunning => _isRunning != 0; + + public string InitialWorkingDirectory { get; private set; } + IRunspaceInfo IRunspaceContext.CurrentRunspace => CurrentRunspace; private PowerShellContextFrame CurrentFrame => _psFrameStack.Peek(); public override void EnterNestedPrompt() { - PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested); + PushPowerShellAndRunLoop(CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested); } public override void ExitNestedPrompt() @@ -139,7 +150,7 @@ public void PopRunspace() public void PushRunspace(Runspace runspace) { IsRunspacePushed = true; - PushPowerShellAndRunLoop(_psFactory.CreatePowerShellForRunspace(runspace), PowerShellFrameType.Remote); + PushPowerShellAndRunLoop(CreatePowerShellForRunspace(runspace), PowerShellFrameType.Remote); } public override void SetShouldExit(int exitCode) @@ -148,13 +159,38 @@ public override void SetShouldExit(int exitCode) SetExit(); } - public void Start() + public async Task StartAsync(HostStartOptions startOptions, CancellationToken cancellationToken) { - SMA.PowerShell pwsh = CreateInitialPowerShell(_hostInfo, _readLineProvider); - PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal); + _logger.LogInformation("Host starting"); + if (Interlocked.Exchange(ref _isRunning, 1) != 0) + { + _logger.LogDebug("Host start requested after already started"); + return; + } + + _pipelineThread.Start(); + + if (startOptions.LoadProfiles) + { + await ExecuteDelegateAsync( + "LoadProfiles", + new PowerShellExecutionOptions { MustRunInForeground = true }, + cancellationToken, + (pwsh, delegateCancellation) => + { + pwsh.LoadProfiles(_hostInfo.ProfilePaths); + }).ConfigureAwait(false); + + _logger.LogInformation("Profiles loaded"); + } + + if (startOptions.InitialWorkingDirectory is not null) + { + await SetInitialWorkingDirectoryAsync(startOptions.InitialWorkingDirectory, CancellationToken.None).ConfigureAwait(false); + } } - private void SetExit() + public void SetExit() { if (_psFrameStack.Count <= 1) { @@ -164,6 +200,130 @@ private void SetExit() _shouldExit = true; } + public Task InvokeTaskOnPipelineThreadAsync( + SynchronousTask task) + { + switch (task.ExecutionOptions.Priority) + { + case ExecutionPriority.Next: + _taskQueue.Prepend(task); + break; + + case ExecutionPriority.Normal: + _taskQueue.Append(task); + break; + } + + return task.Task; + } + + public void CancelCurrentTask() + { + + } + + public Task ExecuteDelegateAsync( + string representation, + ExecutionOptions executionOptions, + CancellationToken cancellationToken, + Func func) + { + return InvokeTaskOnPipelineThreadAsync(new SynchronousPSDelegateTask(_logger, this, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, func)); + } + + public Task ExecuteDelegateAsync( + string representation, + ExecutionOptions executionOptions, + CancellationToken cancellationToken, + Action action) + { + return InvokeTaskOnPipelineThreadAsync(new SynchronousPSDelegateTask(_logger, this, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, action)); + } + + public Task ExecuteDelegateAsync( + string representation, + ExecutionOptions executionOptions, + CancellationToken cancellationToken, + Func func) + { + return InvokeTaskOnPipelineThreadAsync(new SynchronousDelegateTask(_logger, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, func)); + } + + public Task ExecuteDelegateAsync( + string representation, + ExecutionOptions executionOptions, + CancellationToken cancellationToken, + Action action) + { + return InvokeTaskOnPipelineThreadAsync(new SynchronousDelegateTask(_logger, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, action)); + } + + public Task> ExecutePSCommandAsync( + PSCommand psCommand, + CancellationToken cancellationToken, + PowerShellExecutionOptions executionOptions = null) + { + return InvokeTaskOnPipelineThreadAsync(new SynchronousPowerShellTask( + _logger, + this, + psCommand, + executionOptions ?? PowerShellExecutionOptions.Default, + cancellationToken)); + } + + public Task ExecutePSCommandAsync( + PSCommand psCommand, + CancellationToken cancellationToken, + PowerShellExecutionOptions executionOptions = null) => ExecutePSCommandAsync(psCommand, cancellationToken, executionOptions); + + public TResult InvokeDelegate(string representation, ExecutionOptions executionOptions, Func func, CancellationToken cancellationToken) + { + var task = new SynchronousDelegateTask(_logger, representation, executionOptions, cancellationToken, func); + return task.ExecuteAndGetResult(cancellationToken); + } + + public void InvokeDelegate(string representation, ExecutionOptions executionOptions, Action action, CancellationToken cancellationToken) + { + var task = new SynchronousDelegateTask(_logger, representation, executionOptions, cancellationToken, action); + task.ExecuteAndGetResult(cancellationToken); + } + + public IReadOnlyList InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) + { + var task = new SynchronousPowerShellTask(_logger, this, psCommand, executionOptions, cancellationToken); + return task.ExecuteAndGetResult(cancellationToken); + } + + public void InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) + => InvokePSCommand(psCommand, executionOptions, cancellationToken); + + public TResult InvokePSDelegate(string representation, ExecutionOptions executionOptions, Func func, CancellationToken cancellationToken) + { + var task = new SynchronousPSDelegateTask(_logger, this, representation, executionOptions, cancellationToken, func); + return task.ExecuteAndGetResult(cancellationToken); + } + + public void InvokePSDelegate(string representation, ExecutionOptions executionOptions, Action action, CancellationToken cancellationToken) + { + var task = new SynchronousPSDelegateTask(_logger, this, representation, executionOptions, cancellationToken, action); + task.ExecuteAndGetResult(cancellationToken); + } + + public Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cancellationToken) + { + InitialWorkingDirectory = path; + + return ExecutePSCommandAsync( + new PSCommand().AddCommand("Set-Location").AddParameter("LiteralPath", path), + cancellationToken); + } + + private void Run() + { + SMA.PowerShell pwsh = CreateInitialPowerShell(_hostInfo, _readLineProvider); + PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal); + } + private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType) { RunspaceInfo runspaceInfo = null; @@ -233,7 +393,7 @@ private void PopPowerShell() private void RunExecutionLoop() { - while (true) + while (!_shouldExit) { DoOneRepl(CancellationToken.None); @@ -244,7 +404,7 @@ private void RunExecutionLoop() while (_taskQueue.TryTake(out ISynchronousTask task)) { - RunTaskSynchronously(task, CancellationToken.None); + task.ExecuteSynchronously(CancellationToken.None); } } } @@ -290,7 +450,7 @@ private void DoOneRepl(CancellationToken cancellationToken) private string GetPrompt(CancellationToken cancellationToken) { var command = new PSCommand().AddCommand("prompt"); - IReadOnlyList results = _executor.InvokePSCommand(command, PowerShellExecutionOptions.Default, cancellationToken); + IReadOnlyList results = InvokePSCommand(command, PowerShellExecutionOptions.Default, cancellationToken); return results.Count > 0 ? results[0] : null; } @@ -302,24 +462,7 @@ private string InvokeReadLine(CancellationToken cancellationToken) private void InvokeInput(string input, CancellationToken cancellationToken) { var command = new PSCommand().AddScript(input, useLocalScope: false); - _executor.InvokePSCommand(command, new PowerShellExecutionOptions { AddToHistory = true, WriteErrorsToHost = true, WriteOutputToHost = true }, cancellationToken); - } - - private void RunTaskSynchronously(ISynchronousTask task, CancellationToken cancellationToken) - { - if (task.IsCanceled) - { - return; - } - - task.ExecuteSynchronously(cancellationToken); - } - - private IReadOnlyList RunPSCommandSynchronously(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) - { - var task = new SynchronousPowerShellTask(_logger, _publicHost, psCommand, executionOptions, cancellationToken); - task.ExecuteSynchronously(cancellationToken); - return task.Result; + InvokePSCommand(command, new PowerShellExecutionOptions { AddToHistory = true, WriteErrorsToHost = true, WriteOutputToHost = true }, cancellationToken); } private void AddRunspaceEventHandlers(Runspace runspace) @@ -374,7 +517,7 @@ public PowerShell CreateInitialPowerShell( if (hostStartupInfo.ConsoleReplEnabled && !hostStartupInfo.UsesLegacyReadLine) { var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, pwsh); - var readLine = new ConsoleReadLine(psrlProxy, this, _executor, engineIntrinsics); + var readLine = new ConsoleReadLine(psrlProxy, this, engineIntrinsics); _readLineProvider.ReadLine.TryOverrideReadKey(ReadKey); readLine.TryOverrideReadKey(ReadKey); readLine.TryOverrideIdleHandler(OnPowerShellIdle); @@ -402,6 +545,7 @@ public PowerShell CreateInitialPowerShell( return pwsh; } + private Runspace CreateInitialRunspace(PSLanguageMode languageMode) { InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" @@ -424,7 +568,10 @@ private Runspace CreateInitialRunspace(PSLanguageMode languageMode) private void OnPowerShellIdle() { - + while (_taskQueue.TryTake(out ISynchronousTask task)) + { + task.ExecuteSynchronously(CancellationToken.None); + } } private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) @@ -457,7 +604,7 @@ private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStop try { CurrentPowerShell.WaitForRemoteOutputIfNeeded(); - PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Debug | PowerShellFrameType.Nested); + PushPowerShellAndRunLoop(CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Debug | PowerShellFrameType.Nested); CurrentPowerShell.ResumeRemoteOutputIfNeeded(); } finally @@ -479,5 +626,46 @@ private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspa } } + /* + private void PopOrReinitializeRunspace() + { + SetExit(); + RunspaceStateInfo oldRunspaceState = CurrentPowerShell.Runspace.RunspaceStateInfo; + + // Rather than try to lock the PowerShell executor while we alter its state, + // we simply run this on its thread, guaranteeing that no other action can occur + _executor.InvokeDelegate( + nameof(PopOrReinitializeRunspace), + new ExecutionOptions { InterruptCurrentForeground = true }, + (cancellationToken) => + { + while (_psFrameStack.Count > 0 + && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) + { + PopPowerShell(); + } + + if (_psFrameStack.Count == 0) + { + // If our main runspace was corrupted, + // we must re-initialize our state. + // TODO: Use runspace.ResetRunspaceState() here instead + PushInitialPowerShell(); + + _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." + + " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs."); + UI.WriteErrorLine("The main runspace encountered an error and has been reinitialized. See the PowerShell extension logs for more details."); + } + else + { + _logger.LogError($"Current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was popped."); + UI.WriteErrorLine($"The current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}'." + + " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior." + + " The session is now returning to the previous runspace."); + } + }, + CancellationToken.None); + } + */ } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PowerShellFactory.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PowerShellFactory.cs deleted file mode 100644 index 491775357..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PowerShellFactory.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host -{ - using Microsoft.Extensions.Logging; - using Microsoft.PowerShell.EditorServices.Hosting; - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; - using Microsoft.PowerShell.EditorServices.Utility; - using System.Collections.Generic; - using System.IO; - using System.Management.Automation; - using System.Management.Automation.Runspaces; - using System.Reflection; - - internal class PowerShellFactory - { - private static readonly string s_commandsModulePath = Path.GetFullPath( - Path.Combine( - Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), - "../../Commands/PowerShellEditorServices.Commands.psd1")); - - private readonly ILoggerFactory _loggerFactory; - - private readonly ILogger _logger; - - private readonly InternalHost _psesHost; - - public PowerShellFactory( - ILoggerFactory loggerFactory, - InternalHost psHost) - { - _loggerFactory = loggerFactory; - _logger = loggerFactory.CreateLogger(); - _psesHost = psHost; - } - - public PowerShell CreateNestedPowerShell(RunspaceInfo currentRunspace) - { - if (currentRunspace.RunspaceOrigin != RunspaceOrigin.Local) - { - var remotePwsh = PowerShell.Create(); - remotePwsh.Runspace = currentRunspace.Runspace; - return remotePwsh; - } - - // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild - // This means it throws due to the parent pipeline not running... - // So we must use the RunspaceMode.CurrentRunspace option on PowerShell.Create() instead - var pwsh = PowerShell.Create(RunspaceMode.CurrentRunspace); - pwsh.Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; - return pwsh; - } - - public PowerShell CreatePowerShellForRunspace(Runspace runspace) - { - var pwsh = PowerShell.Create(); - pwsh.Runspace = runspace; - return pwsh; - } - - public PowerShell CreateInitialPowerShell( - HostStartupInfo hostStartupInfo, - ReadLineProvider readLineProvider) - { - Runspace runspace = CreateInitialRunspace(hostStartupInfo.LanguageMode); - - var pwsh = PowerShell.Create(); - pwsh.Runspace = runspace; - - var engineIntrinsics = (EngineIntrinsics)runspace.SessionStateProxy.GetVariable("ExecutionContext"); - - if (hostStartupInfo.ConsoleReplEnabled && !hostStartupInfo.UsesLegacyReadLine) - { - var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, pwsh); - var readLine = new ConsoleReadLine(psrlProxy, _psesHost, _psesHost.ExecutionService, engineIntrinsics); - readLineProvider.OverrideReadLine(readLine); - } - - if (VersionUtils.IsWindows) - { - pwsh.SetCorrectExecutionPolicy(_logger); - } - - pwsh.ImportModule(s_commandsModulePath); - - if (hostStartupInfo.AdditionalModules != null && hostStartupInfo.AdditionalModules.Count > 0) - { - foreach (string module in hostStartupInfo.AdditionalModules) - { - pwsh.ImportModule(module); - } - } - - return pwsh; - } - - private Runspace CreateInitialRunspace(PSLanguageMode languageMode) - { - InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" - ? InitialSessionState.CreateDefault() - : InitialSessionState.CreateDefault2(); - - iss.LanguageMode = languageMode; - - Runspace runspace = RunspaceFactory.CreateRunspace(_psesHost, iss); - - runspace.SetApartmentStateToSta(); - runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; - - runspace.Open(); - - Runspace.DefaultRunspace = runspace; - - return runspace; - } - - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index cb1760469..43a37efb2 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -20,18 +20,14 @@ internal class PowerShellExecutionService { private readonly ILogger _logger; - private readonly EditorServicesConsolePSHost _psesHost; - - private readonly PipelineThreadExecutor _pipelineExecutor; + private readonly InternalHost _psesHost; public PowerShellExecutionService( ILoggerFactory loggerFactory, - EditorServicesConsolePSHost psesHost, - PipelineThreadExecutor pipelineExecutor) + InternalHost psesHost) { _logger = loggerFactory.CreateLogger(); _psesHost = psesHost; - _pipelineExecutor = pipelineExecutor; } public Action RunspaceChanged; @@ -41,49 +37,34 @@ public Task ExecuteDelegateAsync( ExecutionOptions executionOptions, CancellationToken cancellationToken, Func func) - { - return RunTaskAsync(new SynchronousPSDelegateTask(_logger, _psesHost, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, func)); - } + => _psesHost.ExecuteDelegateAsync(representation, executionOptions, cancellationToken, func); public Task ExecuteDelegateAsync( string representation, ExecutionOptions executionOptions, CancellationToken cancellationToken, Action action) - { - return RunTaskAsync(new SynchronousPSDelegateTask(_logger, _psesHost, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, action)); - } + => _psesHost.ExecuteDelegateAsync(representation, executionOptions, cancellationToken, action); public Task ExecuteDelegateAsync( string representation, ExecutionOptions executionOptions, CancellationToken cancellationToken, Func func) - { - return RunTaskAsync(new SynchronousDelegateTask(_logger, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, func)); - } + => _psesHost.ExecuteDelegateAsync(representation, executionOptions, cancellationToken, func); public Task ExecuteDelegateAsync( string representation, ExecutionOptions executionOptions, CancellationToken cancellationToken, Action action) - { - return RunTaskAsync(new SynchronousDelegateTask(_logger, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, action)); - } + => _psesHost.ExecuteDelegateAsync(representation, executionOptions, cancellationToken, action); public Task> ExecutePSCommandAsync( PSCommand psCommand, CancellationToken cancellationToken, PowerShellExecutionOptions executionOptions = null) - { - return RunTaskAsync(new SynchronousPowerShellTask( - _logger, - _psesHost, - psCommand, - executionOptions ?? PowerShellExecutionOptions.Default, - cancellationToken)); - } + => _psesHost.ExecutePSCommandAsync(psCommand, cancellationToken, executionOptions); public Task ExecutePSCommandAsync( PSCommand psCommand, @@ -92,9 +73,7 @@ public Task ExecutePSCommandAsync( public void CancelCurrentTask() { - _pipelineExecutor.CancelCurrentTask(); + _psesHost.CancelCurrentTask(); } - - private Task RunTaskAsync(SynchronousTask task) => _pipelineExecutor.RunTaskAsync(task); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs index f59d799f6..54ea94c37 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs @@ -9,6 +9,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace using System.Threading.Tasks; using System.Threading; using System; + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; internal class RunspaceInfo : IRunspaceInfo { @@ -75,7 +76,7 @@ public RunspaceInfo( public async Task GetDscBreakpointCapabilityAsync( ILogger logger, - PowerShellExecutionService executionService, + InternalHost psesHost, CancellationToken cancellationToken) { if (_dscBreakpointCapability == null) @@ -83,7 +84,7 @@ public async Task GetDscBreakpointCapabilityAsync( _dscBreakpointCapability = await DscBreakpointCapability.GetDscCapabilityAsync( logger, this, - executionService, + psesHost, cancellationToken); } diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index 026252cd2..196e196d1 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -29,7 +29,7 @@ internal class PsesConfigurationHandler : DidChangeConfigurationHandlerBase private readonly WorkspaceService _workspaceService; private readonly ConfigurationService _configurationService; private readonly ExtensionService _extensionService; - private readonly EditorServicesConsolePSHost _psesHost; + private readonly InternalHost _psesHost; private readonly ILanguageServerFacade _languageServer; private DidChangeConfigurationCapability _capability; private bool _profilesLoaded; @@ -44,7 +44,7 @@ public PsesConfigurationHandler( ConfigurationService configurationService, ILanguageServerFacade languageServer, ExtensionService extensionService, - EditorServicesConsolePSHost psesHost) + InternalHost psesHost) { _logger = factory.CreateLogger(); _workspaceService = workspaceService; From c4d901e00355c9fda00f595440de0a9d2d987c2c Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 24 Aug 2021 11:37:52 -0700 Subject: [PATCH 068/176] Restore host to simple working order --- .../Server/PsesServiceCollectionExtensions.cs | 1 - .../Services/DebugAdapter/BreakpointService.cs | 4 ++-- .../Services/Extension/EditorOperationsService.cs | 4 ++-- .../Services/PowerShell/Console/PSReadLineProxy.cs | 12 +++++++++--- .../PowerShell/Execution/BlockingConcurrentDeque.cs | 2 +- .../PowerShell/Host/EditorServicesConsolePSHost.cs | 8 +++----- .../Services/PowerShell/Host/InternalHost.cs | 13 ++++++------- 7 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index b89b6566d..7ab5a41f7 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -65,7 +65,6 @@ public static IServiceCollection AddPsesDebugServices( bool useTempSession) { return collection - .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(languageServiceProvider.GetService().DebugContext) .AddSingleton(languageServiceProvider.GetService()) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs index 96b1a6786..1fc80ffee 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs @@ -20,7 +20,7 @@ internal class BreakpointService { private readonly ILogger _logger; private readonly PowerShellExecutionService _executionService; - private readonly EditorServicesConsolePSHost _editorServicesHost; + private readonly InternalHost _editorServicesHost; private readonly DebugStateService _debugStateService; // TODO: This needs to be managed per nested session @@ -33,7 +33,7 @@ internal class BreakpointService public BreakpointService( ILoggerFactory factory, PowerShellExecutionService executionService, - EditorServicesConsolePSHost editorServicesHost, + InternalHost editorServicesHost, DebugStateService debugStateService) { _logger = factory.CreateLogger(); diff --git a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs index 3df728ba1..ef7ed5ae3 100644 --- a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs +++ b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs @@ -16,7 +16,7 @@ internal class EditorOperationsService : IEditorOperations { private const bool DefaultPreviewSetting = true; - private readonly EditorServicesConsolePSHost _psesHost; + private readonly InternalHost _psesHost; private readonly WorkspaceService _workspaceService; private readonly ILanguageServerFacade _languageServer; @@ -24,7 +24,7 @@ internal class EditorOperationsService : IEditorOperations private readonly PowerShellExecutionService _executionService; public EditorOperationsService( - EditorServicesConsolePSHost psesHost, + InternalHost psesHost, WorkspaceService workspaceService, PowerShellExecutionService executionService, ILanguageServerFacade languageServer) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs index d36706cb4..e960528a8 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs @@ -80,9 +80,7 @@ public static PSReadLineProxy LoadAndCreate( { Type psConsoleReadLineType = pwsh.AddScript(ReadLineInitScript).InvokeAndClear().FirstOrDefault(); - Type type = Type.GetType("Microsoft.PowerShell.PSConsoleReadLine"); - - RuntimeHelpers.RunClassConstructor(type.TypeHandle); + RuntimeHelpers.RunClassConstructor(psConsoleReadLineType.TypeHandle); return new PSReadLineProxy(loggerFactory, psConsoleReadLineType); } @@ -128,6 +126,14 @@ public PSReadLineProxy( _logger); } + if (_handleIdleOverrideField is null) + { + throw NewInvalidPSReadLineVersionException( + FieldMemberType, + HandleIdleOverrideName, + _logger); + } + if (ReadLine == null) { throw NewInvalidPSReadLineVersionException( diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs index 56c9b9806..b35bfbe4f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs @@ -63,7 +63,7 @@ public bool TryTake(out T item) return false; } - return BlockingCollection.TakeFromAny(_queues, out item) != -1; + return BlockingCollection.TryTakeFromAny(_queues, out item) >= 0; } public IDisposable BlockConsumers() => PriorityQueueBlockLifetime.StartBlocking(_blockConsumersEvent); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index 406e72088..87468d490 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -11,12 +11,10 @@ public class EditorServicesConsolePSHost : PSHost, IHostSupportsInteractiveSessi { private readonly InternalHost _internalHost; - public EditorServicesConsolePSHost( - ILoggerFactory loggerFactory, - ILanguageServerFacade languageServer, - HostStartupInfo hostInfo) + internal EditorServicesConsolePSHost( + InternalHost internalHost) { - _internalHost = new InternalHost(loggerFactory, languageServer, hostInfo, this); + _internalHost = internalHost; } public override CultureInfo CurrentCulture => _internalHost.CurrentCulture; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs index b47caf5e4..29dda1b8b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs @@ -47,8 +47,6 @@ internal class InternalHost : PSHost, IHostSupportsInteractiveSession, IRunspace private readonly Stack<(Runspace, RunspaceInfo)> _runspaceStack; - private readonly EditorServicesConsolePSHost _publicHost; - private readonly ReadLineProvider _readLineProvider; private readonly Thread _pipelineThread; @@ -64,14 +62,14 @@ internal class InternalHost : PSHost, IHostSupportsInteractiveSession, IRunspace public InternalHost( ILoggerFactory loggerFactory, ILanguageServerFacade languageServer, - HostStartupInfo hostInfo, - EditorServicesConsolePSHost publicHost) + HostStartupInfo hostInfo) { _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(); _languageServer = languageServer; _hostInfo = hostInfo; + _readLineProvider = new ReadLineProvider(loggerFactory); _taskQueue = new BlockingConcurrentDeque(); _psFrameStack = new Stack(); _runspaceStack = new Stack<(Runspace, RunspaceInfo)>(); @@ -81,7 +79,9 @@ public InternalHost( Name = "PSES Pipeline Execution Thread", }; - PublicHost = publicHost; + _pipelineThread.SetApartmentState(ApartmentState.STA); + + PublicHost = new EditorServicesConsolePSHost(this); Name = hostInfo.Name; Version = hostInfo.Version; @@ -518,7 +518,6 @@ public PowerShell CreateInitialPowerShell( { var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, pwsh); var readLine = new ConsoleReadLine(psrlProxy, this, engineIntrinsics); - _readLineProvider.ReadLine.TryOverrideReadKey(ReadKey); readLine.TryOverrideReadKey(ReadKey); readLine.TryOverrideIdleHandler(OnPowerShellIdle); readLineProvider.OverrideReadLine(readLine); @@ -554,7 +553,7 @@ private Runspace CreateInitialRunspace(PSLanguageMode languageMode) iss.LanguageMode = languageMode; - Runspace runspace = RunspaceFactory.CreateRunspace(_publicHost, iss); + Runspace runspace = RunspaceFactory.CreateRunspace(PublicHost, iss); runspace.SetApartmentStateToSta(); runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; From 992c6c7a16e487b3a6349f43f19c5eec2010c2df Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 30 Aug 2021 11:37:22 -0700 Subject: [PATCH 069/176] Add simple cancellation support --- .../Services/PowerShell/Host/InternalHost.cs | 36 ++++++++++++------- .../PowerShell/Utility/CancellationContext.cs | 15 ++++---- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs index 29dda1b8b..bc2d5adcb 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs @@ -47,6 +47,8 @@ internal class InternalHost : PSHost, IHostSupportsInteractiveSession, IRunspace private readonly Stack<(Runspace, RunspaceInfo)> _runspaceStack; + private readonly CancellationContext _cancellationContext; + private readonly ReadLineProvider _readLineProvider; private readonly Thread _pipelineThread; @@ -73,6 +75,7 @@ public InternalHost( _taskQueue = new BlockingConcurrentDeque(); _psFrameStack = new Stack(); _runspaceStack = new Stack<(Runspace, RunspaceInfo)>(); + _cancellationContext = new CancellationContext(); _pipelineThread = new Thread(Run) { @@ -219,7 +222,7 @@ public Task InvokeTaskOnPipelineThreadAsync( public void CancelCurrentTask() { - + _cancellationContext.CancelCurrentTask(); } public Task ExecuteDelegateAsync( @@ -395,16 +398,20 @@ private void RunExecutionLoop() { while (!_shouldExit) { - DoOneRepl(CancellationToken.None); - - if (_shouldExit) + using (CancellationScope cancellationScope = _cancellationContext.EnterScope(isIdleScope: false)) { - break; - } + DoOneRepl(cancellationScope.CancellationToken); - while (_taskQueue.TryTake(out ISynchronousTask task)) - { - task.ExecuteSynchronously(CancellationToken.None); + if (_shouldExit) + { + break; + } + + while (!cancellationScope.CancellationToken.IsCancellationRequested + && _taskQueue.TryTake(out ISynchronousTask task)) + { + task.ExecuteSynchronously(cancellationScope.CancellationToken); + } } } } @@ -544,7 +551,6 @@ public PowerShell CreateInitialPowerShell( return pwsh; } - private Runspace CreateInitialRunspace(PSLanguageMode languageMode) { InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" @@ -567,15 +573,19 @@ private Runspace CreateInitialRunspace(PSLanguageMode languageMode) private void OnPowerShellIdle() { - while (_taskQueue.TryTake(out ISynchronousTask task)) + using (CancellationScope cancellationScope = _cancellationContext.EnterScope(isIdleScope: true)) { - task.ExecuteSynchronously(CancellationToken.None); + while (!cancellationScope.CancellationToken.IsCancellationRequested + && _taskQueue.TryTake(out ISynchronousTask task)) + { + task.ExecuteSynchronously(cancellationScope.CancellationToken); + } } } private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) { - + _cancellationContext.CancelCurrentTask(); } private ConsoleKeyInfo ReadKey(bool intercept) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs index 69decae4e..2e020a8a6 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs @@ -30,14 +30,13 @@ public CancellationContext() _cancellationSourceStack = new ConcurrentStack(); } - public CancellationScope EnterScope(bool isIdleScope, params CancellationToken[] linkedTokens) + public CancellationScope EnterScope(bool isIdleScope) { - return EnterScope(isIdleScope, CancellationTokenSource.CreateLinkedTokenSource(linkedTokens)); - } + CancellationTokenSource newScopeCancellationSource = _cancellationSourceStack.TryPeek(out CancellationScope parentScope) + ? CancellationTokenSource.CreateLinkedTokenSource(parentScope.CancellationToken) + : new CancellationTokenSource(); - public CancellationScope EnterScope(bool isIdleScope, CancellationToken linkedToken1, CancellationToken linkedToken2) - { - return EnterScope(isIdleScope, CancellationTokenSource.CreateLinkedTokenSource(linkedToken1, linkedToken2)); + return EnterScope(isIdleScope, newScopeCancellationSource); } public void CancelCurrentTask() @@ -60,12 +59,12 @@ public void CancelIdleParentTask() { foreach (CancellationScope scope in _cancellationSourceStack) { - scope.Cancel(); - if (!scope.IsIdleScope) { break; } + + scope.Cancel(); } } From c72ba4dac0e7c23624070f6ee1af8623358bcbbe Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 30 Aug 2021 12:15:58 -0700 Subject: [PATCH 070/176] Ensure startup operations are performed before first prompt --- .../Services/PowerShell/Host/InternalHost.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs index bc2d5adcb..503ecf140 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs @@ -396,6 +396,16 @@ private void PopPowerShell() private void RunExecutionLoop() { + // If we're in the top level of the stack, + // make sure we execute any startup tasks first + if (_psFrameStack.Count == 1) + { + while (_taskQueue.TryTake(out ISynchronousTask task)) + { + task.ExecuteSynchronously(CancellationToken.None); + } + } + while (!_shouldExit) { using (CancellationScope cancellationScope = _cancellationContext.EnterScope(isIdleScope: false)) @@ -573,6 +583,11 @@ private Runspace CreateInitialRunspace(PSLanguageMode languageMode) private void OnPowerShellIdle() { + if (_taskQueue.Count == 0) + { + return; + } + using (CancellationScope cancellationScope = _cancellationContext.EnterScope(isIdleScope: true)) { while (!cancellationScope.CancellationToken.IsCancellationRequested From dc29f3e697abf9dbf8bab2b8ff577debfd6fee8d Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 30 Aug 2021 13:29:05 -0700 Subject: [PATCH 071/176] Fix dependency issue --- .../Server/PsesServiceCollectionExtensions.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index 7ab5a41f7..27db3cc5a 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -64,9 +64,12 @@ public static IServiceCollection AddPsesDebugServices( PsesDebugServer psesDebugServer, bool useTempSession) { + InternalHost internalHost = languageServiceProvider.GetService(); + return collection - .AddSingleton(languageServiceProvider.GetService()) - .AddSingleton(languageServiceProvider.GetService().DebugContext) + .AddSingleton(internalHost) + .AddSingleton(internalHost) + .AddSingleton(internalHost.DebugContext) .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(languageServiceProvider.GetService()) From 1f3b01520283b987138371f344714992760faf28 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 30 Aug 2021 13:29:14 -0700 Subject: [PATCH 072/176] Fix incorrect remote runspace labelling --- .../Services/PowerShell/Host/InternalHost.cs | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs index 503ecf140..6f6c884f4 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs @@ -324,12 +324,29 @@ public Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cance private void Run() { SMA.PowerShell pwsh = CreateInitialPowerShell(_hostInfo, _readLineProvider); - PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal); + RunspaceInfo localRunspaceInfo = RunspaceInfo.CreateFromLocalPowerShell(_logger, pwsh); + _runspaceStack.Push((pwsh.Runspace, localRunspaceInfo)); + PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal, localRunspaceInfo); } - private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType) + private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType, RunspaceInfo runspaceInfo = null) + { + // TODO: Improve runspace origin detection here + if (runspaceInfo is null) + { + runspaceInfo = GetRunspaceInfoForPowerShell(pwsh, out bool isNewRunspace); + + if (isNewRunspace) + { + _runspaceStack.Push((pwsh.Runspace, runspaceInfo)); + } + } + + PushPowerShellAndRunLoop(new PowerShellContextFrame(pwsh, runspaceInfo, frameType)); + } + + private RunspaceInfo GetRunspaceInfoForPowerShell(SMA.PowerShell pwsh, out bool isNewRunspace) { - RunspaceInfo runspaceInfo = null; if (_runspaceStack.Count > 0) { // This is more than just an optimization. @@ -339,19 +356,14 @@ private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType f (Runspace currentRunspace, RunspaceInfo currentRunspaceInfo) = _runspaceStack.Peek(); if (currentRunspace == pwsh.Runspace) { - runspaceInfo = currentRunspaceInfo; + isNewRunspace = false; + return currentRunspaceInfo; } } - if (runspaceInfo is null) - { - RunspaceOrigin runspaceOrigin = pwsh.Runspace.RunspaceIsRemote ? RunspaceOrigin.EnteredProcess : RunspaceOrigin.Local; - runspaceInfo = RunspaceInfo.CreateFromPowerShell(_logger, pwsh, runspaceOrigin, _localComputerName); - _runspaceStack.Push((pwsh.Runspace, runspaceInfo)); - } - - // TODO: Improve runspace origin detection here - PushPowerShellAndRunLoop(new PowerShellContextFrame(pwsh, runspaceInfo, frameType)); + RunspaceOrigin runspaceOrigin = pwsh.Runspace.RunspaceIsRemote ? RunspaceOrigin.EnteredProcess : RunspaceOrigin.Local; + isNewRunspace = true; + return RunspaceInfo.CreateFromPowerShell(_logger, pwsh, runspaceOrigin, _localComputerName); } private void PushPowerShellAndRunLoop(PowerShellContextFrame frame) From b3ad7350669cf925ee1e8a4a415055b3ed2dcc5d Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 30 Aug 2021 16:22:35 -0700 Subject: [PATCH 073/176] Begin to make the debugger work --- .../Debugging/IPowerShellDebugContext.cs | 2 - .../Debugging/PowerShellDebugContext.cs | 10 +---- .../Services/PowerShell/Host/InternalHost.cs | 43 ++++++++++++++++--- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs index 7e5284bcb..d0f9b1e18 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs @@ -11,8 +11,6 @@ internal interface IPowerShellDebugContext DebuggerStopEventArgs LastStopEventArgs { get; } - CancellationToken OnResumeCancellationToken { get; } - public event Action DebuggerStopped; public event Action DebuggerResuming; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index 8a7022968..1a6e56325 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -41,8 +41,6 @@ internal class PowerShellDebugContext : IPowerShellDebugContext private readonly InternalHost _psesHost; - private CancellationTokenSource _debugLoopCancellationSource; - public PowerShellDebugContext( ILoggerFactory loggerFactory, ILanguageServerFacade languageServer, @@ -59,8 +57,6 @@ public PowerShellDebugContext( public DebuggerStopEventArgs LastStopEventArgs { get; private set; } - public CancellationToken OnResumeCancellationToken => _debugLoopCancellationSource.Token; - public event Action DebuggerStopped; public event Action DebuggerResuming; public event Action BreakpointUpdated; @@ -104,19 +100,17 @@ public void SetDebugResuming(DebuggerResumeAction debuggerResumeAction) { _psesHost.SetExit(); LastStopEventArgs.ResumeAction = debuggerResumeAction; - _debugLoopCancellationSource.Cancel(); } + // This must be called AFTER the new PowerShell has been pushed public void EnterDebugLoop(CancellationToken loopCancellationToken) { - _debugLoopCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(loopCancellationToken); RaiseDebuggerStoppedEvent(); } + // This must be called BEFORE the debug PowerShell has been popped public void ExitDebugLoop() { - _debugLoopCancellationSource.Dispose(); - _debugLoopCancellationSource = null; } public void SetDebuggerStopped(DebuggerStopEventArgs debuggerStopEventArgs) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs index 6f6c884f4..c7bec9b36 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs @@ -379,7 +379,18 @@ private void PushPowerShellAndRunLoop(PowerShellContextFrame frame) try { - RunExecutionLoop(); + if (_psFrameStack.Count == 1) + { + RunTopLevelExecutionLoop(); + } + else if ((frame.FrameType & PowerShellFrameType.Debug) != 0) + { + RunDebugExecutionLoop(); + } + else + { + RunExecutionLoop(); + } } finally { @@ -393,11 +404,12 @@ private void PopPowerShell() PowerShellContextFrame frame = _psFrameStack.Pop(); try { - RemoveRunspaceEventHandlers(frame.PowerShell.Runspace); - + // If we're changing runspace, make sure we move the handlers over if (_runspaceStack.Peek().Item1 != CurrentPowerShell.Runspace) { - _runspaceStack.Pop(); + (Runspace parentRunspace, _) = _runspaceStack.Pop(); + RemoveRunspaceEventHandlers(frame.PowerShell.Runspace); + AddRunspaceEventHandlers(parentRunspace); } } finally @@ -406,10 +418,9 @@ private void PopPowerShell() } } - private void RunExecutionLoop() + private void RunTopLevelExecutionLoop() { - // If we're in the top level of the stack, - // make sure we execute any startup tasks first + // Make sure we execute any startup tasks first if (_psFrameStack.Count == 1) { while (_taskQueue.TryTake(out ISynchronousTask task)) @@ -418,6 +429,24 @@ private void RunExecutionLoop() } } + RunExecutionLoop(); + } + + private void RunDebugExecutionLoop() + { + try + { + DebugContext.EnterDebugLoop(CancellationToken.None); + RunExecutionLoop(); + } + finally + { + DebugContext.ExitDebugLoop(); + } + } + + private void RunExecutionLoop() + { while (!_shouldExit) { using (CancellationScope cancellationScope = _cancellationContext.EnterScope(isIdleScope: false)) From c58fc921cbdf839c91b83d1f04b5a1a5f55f3c96 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 1 Sep 2021 15:16:31 -0700 Subject: [PATCH 074/176] Fix recursive PSRL cancellation token issue in debugger --- .../PowerShell/Console/ConsoleReadLine.cs | 2 +- .../PowerShell/Console/PSReadLineProxy.cs | 9 ++--- .../Services/PowerShell/Host/InternalHost.cs | 35 +++++++++++++++++++ .../PowerShell/Utility/CancellationContext.cs | 9 +++-- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index d0c798c74..d1afa6020 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -154,7 +154,7 @@ private string InvokePSReadLine(CancellationToken cancellationToken) Debug.WriteLine("PSRL CANCELLED"); }); EngineIntrinsics engineIntrinsics = _psesHost.IsRunspacePushed ? null : _engineIntrinsics; - return _psrlProxy.ReadLine(_psesHost.Runspace, engineIntrinsics, cancellationToken); + return _psrlProxy.ReadLine(_psesHost.Runspace, engineIntrinsics, cancellationToken, /* lastExecutionStatus */ null); } /// diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs index e960528a8..f35728c70 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs @@ -16,6 +16,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { + using OmniSharp.Extensions.DebugAdapter.Protocol.Models; using System.Management.Automation.Runspaces; internal class PSReadLineProxy @@ -97,10 +98,10 @@ public PSReadLineProxy( { _logger = loggerFactory.CreateLogger(); - ReadLine = (Func)psConsoleReadLine.GetMethod( + ReadLine = (Func)psConsoleReadLine.GetMethod( ReadLineMethodName, - new[] { typeof(Runspace), typeof(EngineIntrinsics), typeof(CancellationToken) }) - ?.CreateDelegate(typeof(Func)); + new[] { typeof(Runspace), typeof(EngineIntrinsics), typeof(CancellationToken), typeof(bool?) }) + ?.CreateDelegate(typeof(Func)); AddToHistory = (Action)psConsoleReadLine.GetMethod( AddToHistoryMethodName, @@ -165,7 +166,7 @@ public PSReadLineProxy( internal Action ForcePSEventHandling { get; } - internal Func ReadLine { get; } + internal Func ReadLine { get; } internal void OverrideReadKey(Func readKeyFunc) { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs index c7bec9b36..58f633fbb 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs @@ -61,6 +61,8 @@ internal class InternalHost : PSHost, IHostSupportsInteractiveSession, IRunspace private ConsoleKeyInfo? _lastKey; + private bool _skipNextPrompt = false; + public InternalHost( ILoggerFactory loggerFactory, ILanguageServerFacade languageServer, @@ -206,6 +208,23 @@ public void SetExit() public Task InvokeTaskOnPipelineThreadAsync( SynchronousTask task) { + if (task.ExecutionOptions.InterruptCurrentForeground) + { + // When a task must displace the current foreground command, + // we must: + // - block the consumer thread from mutating the queue + // - cancel any running task on the consumer thread + // - place our task on the front of the queue + // - unblock the consumer thread + using (_taskQueue.BlockConsumers()) + { + CancelCurrentTask(); + _taskQueue.Prepend(task); + } + + return task.Task; + } + switch (task.ExecutionOptions.Priority) { case ExecutionPriority.Next: @@ -469,6 +488,12 @@ private void RunExecutionLoop() private void DoOneRepl(CancellationToken cancellationToken) { + if (_skipNextPrompt) + { + _skipNextPrompt = false; + return; + } + try { string prompt = GetPrompt(cancellationToken) ?? DefaultPrompt; @@ -634,6 +659,16 @@ private void OnPowerShellIdle() while (!cancellationScope.CancellationToken.IsCancellationRequested && _taskQueue.TryTake(out ISynchronousTask task)) { + if (task.ExecutionOptions.MustRunInForeground) + { + // If we have a task that is queued, but cannot be run under readline + // we place it back at the front of the queue, and cancel the readline task + _taskQueue.Prepend(task); + _skipNextPrompt = true; + _cancellationContext.CancelIdleParentTask(); + break; + } + task.ExecuteSynchronously(cancellationScope.CancellationToken); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs index 2e020a8a6..6789dd4ae 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs @@ -55,16 +55,21 @@ public void CancelCurrentTaskStack() } } + /// + /// Cancels the parent task of the idle task. + /// public void CancelIdleParentTask() { foreach (CancellationScope scope in _cancellationSourceStack) { + scope.Cancel(); + + // Note that this check is done *after* the cancellation because we want to cancel + // not just the idle task, but its parent as well if (!scope.IsIdleScope) { break; } - - scope.Cancel(); } } From a77c21f87e17f996eb2c1c7d0d0d4cd2145de997 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 2 Sep 2021 11:19:52 -0700 Subject: [PATCH 075/176] Enable proper debug UI interaction --- .../DebugAdapter/DebugEventHandlerService.cs | 10 +++++----- .../Debugging/DebuggerResumingEventArgs.cs | 12 ++---------- .../PowerShell/Debugging/PowerShellDebugContext.cs | 6 +++--- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs index a41191370..3dbcc9156 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs @@ -45,7 +45,7 @@ internal void RegisterEventHandlers() _executionService.RunspaceChanged += ExecutionService_RunspaceChanged; _debugService.BreakpointUpdated += DebugService_BreakpointUpdated; _debugService.DebuggerStopped += DebugService_DebuggerStopped; - //_powerShellContextService.DebuggerResumed += PowerShellContext_DebuggerResumed; + _debugContext.DebuggerResuming += PowerShellContext_DebuggerResuming; } internal void UnregisterEventHandlers() @@ -53,7 +53,7 @@ internal void UnregisterEventHandlers() _executionService.RunspaceChanged -= ExecutionService_RunspaceChanged; _debugService.BreakpointUpdated -= DebugService_BreakpointUpdated; _debugService.DebuggerStopped -= DebugService_DebuggerStopped; - //_powerShellContextService.DebuggerResumed -= PowerShellContext_DebuggerResumed; + _debugContext.DebuggerResuming -= PowerShellContext_DebuggerResuming; } #region Public methods @@ -125,13 +125,13 @@ private void ExecutionService_RunspaceChanged(object sender, RunspaceChangedEven } } - private void PowerShellContext_DebuggerResumed(object sender, DebuggerResumeAction e) + private void PowerShellContext_DebuggerResuming(object sender, DebuggerResumingEventArgs e) { _debugAdapterServer.SendNotification(EventNames.Continued, new ContinuedEvent { - ThreadId = 1, - AllThreadsContinued = true + AllThreadsContinued = true, + ThreadId = ThreadsHandler.PipelineThread.Id, }); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DebuggerResumingEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DebuggerResumingEventArgs.cs index 9c60c5d98..94ecaac73 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DebuggerResumingEventArgs.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DebuggerResumingEventArgs.cs @@ -2,14 +2,6 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging { - internal class DebuggerResumingEventArgs - { - public DebuggerResumingEventArgs(DebuggerResumeAction resumeAction) - { - ResumeAction = resumeAction; - } - - public DebuggerResumeAction ResumeAction { get; } - - } + internal record DebuggerResumingEventArgs( + DebuggerResumeAction ResumeAction); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index 1a6e56325..61d831080 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -1,5 +1,4 @@ -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; -using System; +using System; using System.Management.Automation; using System.Threading; @@ -100,6 +99,8 @@ public void SetDebugResuming(DebuggerResumeAction debuggerResumeAction) { _psesHost.SetExit(); LastStopEventArgs.ResumeAction = debuggerResumeAction; + // We need to tell whatever is happening right now in the debug prompt to wrap up so we can continue + _psesHost.CancelCurrentTask(); } // This must be called AFTER the new PowerShell has been pushed @@ -140,7 +141,6 @@ public void HandleBreakpointUpdated(BreakpointUpdatedEventArgs breakpointUpdated private void RaiseDebuggerStoppedEvent() { - // TODO: Send language server message to start debugger if (!IsDebugServerActive) { _languageServer.SendNotification("powerShell/startDebugger"); From 12d75927d18d79708833e9e69fe564144965c947 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 3 Sep 2021 10:10:20 -0700 Subject: [PATCH 076/176] Fix remote session connection --- .../Context/PowerShellContextFrame.cs | 7 +------ .../Services/PowerShell/Host/InternalHost.cs | 17 ++++++++++++----- .../PowerShell/Runspace/RunspaceInfo.cs | 9 ++++++++- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs index 0feb0241c..9e028463a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs @@ -11,11 +11,10 @@ internal class PowerShellContextFrame : IDisposable public static PowerShellContextFrame CreateForPowerShellInstance( ILogger logger, SMA.PowerShell pwsh, - RunspaceOrigin runspaceOrigin, PowerShellFrameType frameType, string localComputerName) { - var runspaceInfo = RunspaceInfo.CreateFromPowerShell(logger, pwsh, runspaceOrigin, localComputerName); + var runspaceInfo = RunspaceInfo.CreateFromPowerShell(logger, pwsh, localComputerName); return new PowerShellContextFrame(pwsh, runspaceInfo, frameType); } @@ -26,7 +25,6 @@ public PowerShellContextFrame(SMA.PowerShell powerShell, RunspaceInfo runspaceIn PowerShell = powerShell; RunspaceInfo = runspaceInfo; FrameType = frameType; - CancellationTokenSource = new CancellationTokenSource(); } public SMA.PowerShell PowerShell { get; } @@ -35,8 +33,6 @@ public PowerShellContextFrame(SMA.PowerShell powerShell, RunspaceInfo runspaceIn public PowerShellFrameType FrameType { get; } - public CancellationTokenSource CancellationTokenSource { get; } - protected virtual void Dispose(bool disposing) { if (!disposedValue) @@ -44,7 +40,6 @@ protected virtual void Dispose(bool disposing) if (disposing) { PowerShell.Dispose(); - CancellationTokenSource.Dispose(); } disposedValue = true; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs index 58f633fbb..e68a191c1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs @@ -344,6 +344,7 @@ private void Run() { SMA.PowerShell pwsh = CreateInitialPowerShell(_hostInfo, _readLineProvider); RunspaceInfo localRunspaceInfo = RunspaceInfo.CreateFromLocalPowerShell(_logger, pwsh); + _localComputerName = localRunspaceInfo.SessionDetails.ComputerName; _runspaceStack.Push((pwsh.Runspace, localRunspaceInfo)); PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal, localRunspaceInfo); } @@ -380,9 +381,8 @@ private RunspaceInfo GetRunspaceInfoForPowerShell(SMA.PowerShell pwsh, out bool } } - RunspaceOrigin runspaceOrigin = pwsh.Runspace.RunspaceIsRemote ? RunspaceOrigin.EnteredProcess : RunspaceOrigin.Local; isNewRunspace = true; - return RunspaceInfo.CreateFromPowerShell(_logger, pwsh, runspaceOrigin, _localComputerName); + return RunspaceInfo.CreateFromPowerShell(_logger, pwsh, _localComputerName); } private void PushPowerShellAndRunLoop(PowerShellContextFrame frame) @@ -496,7 +496,7 @@ private void DoOneRepl(CancellationToken cancellationToken) try { - string prompt = GetPrompt(cancellationToken) ?? DefaultPrompt; + string prompt = GetPrompt(cancellationToken); UI.Write(prompt); string userInput = InvokeReadLine(cancellationToken); @@ -534,7 +534,14 @@ private string GetPrompt(CancellationToken cancellationToken) { var command = new PSCommand().AddCommand("prompt"); IReadOnlyList results = InvokePSCommand(command, PowerShellExecutionOptions.Default, cancellationToken); - return results.Count > 0 ? results[0] : null; + string prompt = results.Count > 0 ? results[0] : DefaultPrompt; + + if (CurrentRunspace.RunspaceOrigin != RunspaceOrigin.Local) + { + prompt = Runspace.GetRemotePrompt(prompt); + } + + return prompt; } private string InvokeReadLine(CancellationToken cancellationToken) @@ -720,7 +727,7 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs break private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) { - if (!runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) + if (!_shouldExit && !runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) { //PopOrReinitializeRunspaceAsync(); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs index 54ea94c37..3a97442ed 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs @@ -31,7 +31,6 @@ public static RunspaceInfo CreateFromLocalPowerShell( public static RunspaceInfo CreateFromPowerShell( ILogger logger, PowerShell pwsh, - RunspaceOrigin runspaceOrigin, string localComputerName) { var psVersionDetails = PowerShellVersionDetails.GetVersionDetails(logger, pwsh); @@ -40,6 +39,14 @@ public static RunspaceInfo CreateFromPowerShell( bool isOnLocalMachine = string.Equals(sessionDetails.ComputerName, localComputerName, StringComparison.OrdinalIgnoreCase) || string.Equals(sessionDetails.ComputerName, "localhost", StringComparison.OrdinalIgnoreCase); + RunspaceOrigin runspaceOrigin = RunspaceOrigin.Local; + if (pwsh.Runspace.RunspaceIsRemote) + { + runspaceOrigin = pwsh.Runspace.ConnectionInfo is NamedPipeConnectionInfo + ? RunspaceOrigin.EnteredProcess + : RunspaceOrigin.PSSession; + } + return new RunspaceInfo( pwsh.Runspace, runspaceOrigin, From fb0a680cef113e57eb321b9edfe047324b1f585e Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 8 Sep 2021 16:47:32 -0700 Subject: [PATCH 077/176] Check the current runspace for debug API support --- .../Services/DebugAdapter/BreakpointService.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs index 1fc80ffee..62208c5f7 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs @@ -18,6 +18,8 @@ namespace Microsoft.PowerShell.EditorServices.Services { internal class BreakpointService { + private static readonly Version s_minimumBreakpointApiVersion = new Version(7, 0, 0, 0); + private readonly ILogger _logger; private readonly PowerShellExecutionService _executionService; private readonly InternalHost _editorServicesHost; @@ -44,7 +46,7 @@ public BreakpointService( public async Task> GetBreakpointsAsync() { - if (BreakpointApiUtils.SupportsBreakpointApis) + if (_editorServicesHost.CurrentRunspace.PowerShellVersionDetails.Version >= s_minimumBreakpointApiVersion) { return BreakpointApiUtils.GetBreakpoints( _editorServicesHost.Runspace.Debugger, @@ -60,7 +62,7 @@ public async Task> GetBreakpointsAsync() public async Task> SetBreakpointsAsync(string escapedScriptPath, IEnumerable breakpoints) { - if (BreakpointApiUtils.SupportsBreakpointApis) + if (_editorServicesHost.CurrentRunspace.PowerShellVersionDetails.Version >= s_minimumBreakpointApiVersion) { foreach (BreakpointDetails breakpointDetails in breakpoints) { @@ -151,7 +153,7 @@ public async Task> SetBreakpointsAsync(string esc public async Task> SetCommandBreakpoints(IEnumerable breakpoints) { - if (BreakpointApiUtils.SupportsBreakpointApis) + if (_editorServicesHost.CurrentRunspace.PowerShellVersionDetails.Version >= s_minimumBreakpointApiVersion) { foreach (CommandBreakpointDetails commandBreakpointDetails in breakpoints) { @@ -233,7 +235,7 @@ public async Task RemoveAllBreakpointsAsync(string scriptPath = null) { try { - if (BreakpointApiUtils.SupportsBreakpointApis) + if (_editorServicesHost.CurrentRunspace.PowerShellVersionDetails.Version >= s_minimumBreakpointApiVersion) { foreach (Breakpoint breakpoint in BreakpointApiUtils.GetBreakpoints( _editorServicesHost.Runspace.Debugger, @@ -273,7 +275,7 @@ public async Task RemoveAllBreakpointsAsync(string scriptPath = null) public async Task RemoveBreakpointsAsync(IEnumerable breakpoints) { - if (BreakpointApiUtils.SupportsBreakpointApis) + if (_editorServicesHost.CurrentRunspace.PowerShellVersionDetails.Version >= s_minimumBreakpointApiVersion) { foreach (Breakpoint breakpoint in breakpoints) { From ada9e66ef33cb3a5dcfc765c4855755db9d2c9f8 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 9 Sep 2021 16:44:29 -0700 Subject: [PATCH 078/176] Fix debug disconnect handling --- .../Services/DebugAdapter/Handlers/DisconnectHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs index 28e812d6a..1e5a7d0e0 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs @@ -48,10 +48,11 @@ public DisconnectHandler( public async Task Handle(DisconnectArguments request, CancellationToken cancellationToken) { _debugEventHandlerService.UnregisterEventHandlers(); - if (_debugStateService.ExecutionCompleted == false) + + if (!_debugStateService.ExecutionCompleted) { _debugStateService.ExecutionCompleted = true; - _executionService.CancelCurrentTask(); + _debugService.Abort(); if (_debugStateService.IsInteractiveDebugSession && _debugStateService.IsAttachSession) { @@ -78,7 +79,6 @@ await _executionService.ExecutePSCommandAsync( } } } - _debugService.IsClientAttached = false; } From 5a2bf53005e28a492f1a95a7e3f3eaf48f9f7cf1 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 14 Sep 2021 16:45:42 -0700 Subject: [PATCH 079/176] Make remote debugger function correctly --- src/PowerShellEditorServices/Server/PsesDebugServer.cs | 3 +++ .../Services/DebugAdapter/DebugService.cs | 5 +++++ .../Services/PowerShell/Debugging/PowerShellDebugContext.cs | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 2e30187bb..e7f2235ee 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -119,6 +119,9 @@ public async Task StartAsync() // The OnInitialize delegate gets run when we first receive the _Initialize_ request: // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize .OnInitialize(async (server, request, cancellationToken) => { + // Ensure the debugger mode is set correctly + _debugContext.EnableDebugMode(); + var breakpointService = server.GetService(); // Clear any existing breakpoints before proceeding await breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(false); diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 27ce4338c..80f7ea83f 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -697,6 +697,11 @@ private async Task FetchVariableContainerAsync( { foreach (PSObject psVariableObject in results) { + if (psVariableObject.Properties["Name"] is null) + { + continue; + } + var variableDetails = new VariableDetails(psVariableObject) { Id = this.nextVariableId++ }; this.variables.Add(variableDetails); scopeVariableContainer.Children.Add(variableDetails.Name, variableDetails); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index 61d831080..09472ff82 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -65,6 +65,11 @@ public Task GetDscBreakpointCapabilityAsync(Cancellatio return _psesHost.CurrentRunspace.GetDscBreakpointCapabilityAsync(_logger, _psesHost, cancellationToken); } + public void EnableDebugMode() + { + _psesHost.Runspace.Debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript); + } + public void Abort() { SetDebugResuming(DebuggerResumeAction.Stop); From b75e516f294e7885f1d367aca01d8bd62d98f5a1 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 14 Sep 2021 17:02:01 -0700 Subject: [PATCH 080/176] Remove dead code --- .../PowerShell/Console/ConsoleReplRunner.cs | 304 -------------- .../Host/EditorServicesConsolePSHost_Old.cs | 387 ------------------ 2 files changed, 691 deletions(-) delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost_Old.cs diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs deleted file mode 100644 index e4e0dd3df..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs +++ /dev/null @@ -1,304 +0,0 @@ -/* -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Management.Automation; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console -{ - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; - - internal class ConsoleReplRunner : IDisposable - { - private readonly ILogger _logger; - - private readonly EditorServicesConsolePSHost _psesHost; - - private readonly PowerShellExecutionService _executionService; - - private readonly ConcurrentStack _replLoopTaskStack; - - // This is required because PSRL will keep prompting for keys as we push a new REPL task - // Keeping current command cancellations on their own stack simplifies access to the cancellation token - // for the REPL command that's currently running. - private readonly ConcurrentStack _commandCancellationStack; - - private readonly IReadLineProvider _readLineProvider; - - private ConsoleKeyInfo? _lastKey; - - private bool _exiting; - - public ConsoleReplRunner( - ILoggerFactory loggerFactory, - EditorServicesConsolePSHost psesHost, - IReadLineProvider readLineProvider, - PowerShellExecutionService executionService) - { - _logger = loggerFactory.CreateLogger(); - _replLoopTaskStack = new ConcurrentStack(); - _commandCancellationStack = new ConcurrentStack(); - _psesHost = psesHost; - _readLineProvider = readLineProvider; - _executionService = executionService; - _exiting = false; - } - - public void StartRepl() - { - System.Console.CancelKeyPress += OnCancelKeyPress; - System.Console.InputEncoding = Encoding.UTF8; - System.Console.OutputEncoding = Encoding.UTF8; - _readLineProvider.ReadLine.TryOverrideReadKey(ReadKey); - PushNewReplTask(); - _logger.LogInformation("REPL started"); - } - - public void Dispose() - { - while (_replLoopTaskStack.Count > 0) - { - StopCurrentRepl(); - } - - System.Console.CancelKeyPress -= OnCancelKeyPress; - } - - public void CancelCurrentPrompt() - { - if (_commandCancellationStack.TryPeek(out CommandCancellation commandCancellation)) - { - commandCancellation.CancellationSource?.Cancel(); - } - } - - public void StopCurrentRepl() - { - if (_replLoopTaskStack.TryPeek(out ReplTask currentReplTask)) - { - currentReplTask.ReplCancellationSource.Cancel(); - } - } - - private async Task RunReplLoopAsync() - { - _replLoopTaskStack.TryPeek(out ReplTask replTask); - - try - { - while (!replTask.ReplCancellationSource.IsCancellationRequested) - { - var currentCommandCancellation = new CommandCancellation(); - _commandCancellationStack.Push(currentCommandCancellation); - - try - { - string promptString = await GetPromptStringAsync(currentCommandCancellation.CancellationSource.Token).ConfigureAwait(false); - - if (currentCommandCancellation.CancellationSource.IsCancellationRequested) - { - continue; - } - - WritePrompt(promptString); - - string userInput = await InvokeReadLineAsync(currentCommandCancellation.CancellationSource.Token).ConfigureAwait(false); - - // If the user input was empty it's because: - // - the user provided no input - // - the readline task was canceled - // - CtrlC was sent to readline (which does not propagate a cancellation) - // - // In any event there's nothing to run in PowerShell, so we just loop back to the prompt again. - // However, we must distinguish the last two scenarios, since PSRL will not print a new line in those cases. - if (string.IsNullOrEmpty(userInput)) - { - if (currentCommandCancellation.CancellationSource.IsCancellationRequested - || LastKeyWasCtrlC()) - { - _psesHost.UI.WriteLine(); - } - continue; - } - - await InvokeInputAsync(userInput, currentCommandCancellation.CancellationSource.Token).ConfigureAwait(false); - - if (replTask.ReplCancellationSource.IsCancellationRequested) - { - break; - } - } - catch (OperationCanceledException) - { - continue; - } - catch (Exception e) - { - _psesHost.UI.WriteErrorLine($"An error occurred while running the REPL loop:{Environment.NewLine}{e}"); - _logger.LogError(e, "An error occurred while running the REPL loop"); - break; - } - finally - { - _commandCancellationStack.TryPop(out CommandCancellation _); - currentCommandCancellation.CancellationSource.Dispose(); - currentCommandCancellation.CancellationSource = null; - } - } - } - finally - { - _exiting = false; - _replLoopTaskStack.TryPop(out _); - replTask.ReplCancellationSource.Dispose(); - } - - } - - private async Task GetPromptStringAsync(CancellationToken cancellationToken) - { - string prompt = (await GetPromptOutputAsync(cancellationToken).ConfigureAwait(false)).FirstOrDefault() ?? "PS> "; - - if (_psesHost.CurrentRunspace.RunspaceOrigin != RunspaceOrigin.Local) - { - prompt = _psesHost.Runspace.GetRemotePrompt(prompt); - } - - return prompt; - } - - private Task> GetPromptOutputAsync(CancellationToken cancellationToken) - { - var promptCommand = new PSCommand().AddCommand("prompt"); - - return _executionService.ExecutePSCommandAsync( - promptCommand, - cancellationToken); - } - - private void WritePrompt(string promptString) - { - _psesHost.UI.Write(promptString); - } - - private Task InvokeReadLineAsync(CancellationToken cancellationToken) - { - return _readLineProvider.ReadLine.ReadLineAsync(cancellationToken); - } - - private Task InvokeInputAsync(string input, CancellationToken cancellationToken) - { - var command = new PSCommand().AddScript(input); - var executionOptions = new PowerShellExecutionOptions - { - WriteOutputToHost = true, - AddToHistory = true, - }; - - return _executionService.ExecutePSCommandAsync(command, cancellationToken, executionOptions); - } - - public void SetReplPop() - { - _exiting = true; - StopCurrentRepl(); - } - - private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) - { - // We don't want to terminate the process - args.Cancel = true; - CancelCurrentPrompt(); - } - - private void OnReplCanceled() - { - // Ordinarily, when the REPL is canceled - // we want to propagate the cancellation to any currently running command. - // However, when the REPL is canceled by an 'exit' command, - // the currently running command is doing the cancellation. - // Not only would canceling it not make sense - // but trying to cancel it from its own thread will deadlock PowerShell. - // Instead we just let the command progress. - - if (_exiting) - { - return; - } - - CancelCurrentPrompt(); - } - - private ConsoleKeyInfo ReadKey(bool intercept) - { - _commandCancellationStack.TryPeek(out CommandCancellation commandCancellation); - - // PSRL doesn't tell us when CtrlC was sent. - // So instead we keep track of the last key here. - // This isn't functionally required, - // but helps us determine when the prompt needs a newline added - - _lastKey = ConsoleProxy.SafeReadKey(intercept, commandCancellation.CancellationSource.Token); - return _lastKey.Value; - } - - private bool LastKeyWasCtrlC() - { - return _lastKey != null - && _lastKey.Value.Key == ConsoleKey.C - && (_lastKey.Value.Modifiers & ConsoleModifiers.Control) != 0 - && (_lastKey.Value.Modifiers & ConsoleModifiers.Alt) == 0; - } - - public void PushNewReplTask() - { - ReplTask.PushAndStart(_replLoopTaskStack, RunReplLoopAsync, OnReplCanceled); - } - - private class ReplTask - { - public static void PushAndStart( - ConcurrentStack replLoopTaskStack, - Func replLoopTaskFunc, - Action cancellationAction) - { - var replLoopCancellationSource = new CancellationTokenSource(); - replLoopCancellationSource.Token.Register(cancellationAction); - var replTask = new ReplTask(replLoopCancellationSource); - replLoopTaskStack.Push(replTask); - replTask.LoopTask = Task.Run(replLoopTaskFunc, replLoopCancellationSource.Token); - } - - public ReplTask(CancellationTokenSource cancellationTokenSource) - { - ReplCancellationSource = cancellationTokenSource; - Guid = Guid.NewGuid(); - } - - public Task LoopTask { get; private set; } - - public CancellationTokenSource ReplCancellationSource { get; } - - public Guid Guid { get; } - } - - private class CommandCancellation - { - public CommandCancellation() - { - CancellationSource = new CancellationTokenSource(); - } - - public CancellationTokenSource CancellationSource { get; set; } - } - } -} -*/ diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost_Old.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost_Old.cs deleted file mode 100644 index eb35c695b..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost_Old.cs +++ /dev/null @@ -1,387 +0,0 @@ -/* -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Hosting; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; -using OmniSharp.Extensions.LanguageServer.Protocol.Server; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Threading; -using System.Threading.Tasks; -using SMA = System.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host -{ - using System.Management.Automation.Runspaces; - - internal class EditorServicesConsolePSHost_Old : PSHost, IHostSupportsInteractiveSession, IRunspaceContext - { - private readonly ILogger _logger; - - private readonly Stack _psFrameStack; - - private readonly PowerShellFactory _psFactory; - - private readonly ConsoleReplRunner _consoleReplRunner; - - private readonly PipelineThreadExecutor _pipelineExecutor; - - private readonly HostStartupInfo _hostInfo; - - private readonly ReadLineProvider _readLineProvider; - - private readonly Stack> _runspacesInUse; - - private readonly Thread _topRunspaceThread; - - private string _localComputerName; - - private int _hostStarted = 0; - - public EditorServicesConsolePSHost( - ILoggerFactory loggerFactory, - ILanguageServerFacade languageServer, - HostStartupInfo hostInfo) - { - _logger = loggerFactory.CreateLogger(); - _psFrameStack = new Stack(); - _psFactory = new PowerShellFactory(loggerFactory, this); - _runspacesInUse = new Stack>(); - _hostInfo = hostInfo; - Name = hostInfo.Name; - Version = hostInfo.Version; - - _topRunspaceThread = new Thread(Run); - - _readLineProvider = new ReadLineProvider(loggerFactory); - _pipelineExecutor = new PipelineThreadExecutor(loggerFactory, hostInfo, this, _readLineProvider); - ExecutionService = new PowerShellExecutionService(loggerFactory, this, _pipelineExecutor); - UI = new EditorServicesConsolePSHostUserInterface(loggerFactory, _readLineProvider, hostInfo.PSHost.UI); - - if (hostInfo.ConsoleReplEnabled) - { - _consoleReplRunner = new ConsoleReplRunner(loggerFactory, this, _readLineProvider, ExecutionService); - } - - DebugContext = new PowerShellDebugContext(loggerFactory, languageServer, this, _consoleReplRunner); - } - - public override CultureInfo CurrentCulture => _hostInfo.PSHost.CurrentCulture; - - public override CultureInfo CurrentUICulture => _hostInfo.PSHost.CurrentUICulture; - - public override Guid InstanceId { get; } = Guid.NewGuid(); - - public override string Name { get; } - - public override PSHostUserInterface UI { get; } - - public override Version Version { get; } - - public bool IsRunspacePushed { get; private set; } - - internal bool IsRunning => _hostStarted != 0; - - public Runspace Runspace => CurrentPowerShell.Runspace; - - internal string InitialWorkingDirectory { get; private set; } - - internal PowerShellExecutionService ExecutionService { get; } - - internal PowerShellDebugContext DebugContext { get; } - - internal SMA.PowerShell CurrentPowerShell => CurrentFrame.PowerShell; - - internal RunspaceInfo CurrentRunspace => CurrentFrame.RunspaceInfo; - - IRunspaceInfo IRunspaceContext.CurrentRunspace => CurrentRunspace; - - internal CancellationTokenSource CurrentCancellationSource => CurrentFrame.CancellationTokenSource; - - private PowerShellContextFrame CurrentFrame => _psFrameStack.Peek(); - - public override void EnterNestedPrompt() - { - PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested); - } - - public override void ExitNestedPrompt() - { - SetExit(); - } - - public override void NotifyBeginApplication() - { - // TODO: Work out what to do here - } - - public override void NotifyEndApplication() - { - // TODO: Work out what to do here - } - - public void PopRunspace() - { - IsRunspacePushed = false; - SetExit(); - } - - public void PushRunspace(Runspace runspace) - { - IsRunspacePushed = true; - PushPowerShellAndRunLoop(_psFactory.CreatePowerShellForRunspace(runspace), PowerShellFrameType.Remote); - } - - public override void SetShouldExit(int exitCode) - { - SetExit(); - } - - internal void Start() - { - _topRunspaceThread.Start(); - } - - private void Run() - { - PushInitialPowerShell(); - } - - public void PushInitialPowerShell() - { - SMA.PowerShell pwsh = _psFactory.CreateInitialPowerShell(_hostInfo, _readLineProvider); - var runspaceInfo = RunspaceInfo.CreateFromLocalPowerShell(_logger, pwsh); - _localComputerName = runspaceInfo.SessionDetails.ComputerName; - PushPowerShell(new PowerShellContextFrame(pwsh, runspaceInfo, PowerShellFrameType.Normal)); - } - - internal void PushNonInteractivePowerShell() - { - PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested | PowerShellFrameType.NonInteractive); - } - - internal void CancelCurrentPrompt() - { - _consoleReplRunner?.CancelCurrentPrompt(); - } - - internal void StartRepl() - { - _consoleReplRunner?.StartRepl(); - } - - internal void PushNewReplTask() - { - _consoleReplRunner?.PushNewReplTask(); - } - - internal Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cancellationToken) - { - InitialWorkingDirectory = path; - - return ExecutionService.ExecutePSCommandAsync( - new PSCommand().AddCommand("Set-Location").AddParameter("LiteralPath", path), - cancellationToken); - } - - public async Task StartAsync(HostStartOptions hostStartOptions, CancellationToken cancellationToken) - { - _logger.LogInformation("Host starting"); - if (Interlocked.Exchange(ref _hostStarted, 1) != 0) - { - _logger.LogDebug("Host start requested after already started"); - return; - } - - _pipelineExecutor.Start(); - - if (hostStartOptions.LoadProfiles) - { - await ExecutionService.ExecuteDelegateAsync( - "LoadProfiles", - new PowerShellExecutionOptions { MustRunInForeground = true }, - cancellationToken, - (pwsh, delegateCancellation) => - { - pwsh.LoadProfiles(_hostInfo.ProfilePaths); - }).ConfigureAwait(false); - - _logger.LogInformation("Profiles loaded"); - } - - if (hostStartOptions.InitialWorkingDirectory != null) - { - await SetInitialWorkingDirectoryAsync(hostStartOptions.InitialWorkingDirectory, CancellationToken.None).ConfigureAwait(false); - } - } - - private void SetExit() - { - if (_psFrameStack.Count <= 1) - { - return; - } - - _pipelineExecutor.IsExiting = true; - - if ((CurrentFrame.FrameType & PowerShellFrameType.NonInteractive) == 0) - { - _consoleReplRunner?.SetReplPop(); - } - } - - private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType) - { - RunspaceInfo runspaceInfo = null; - if (_runspacesInUse.Count > 0) - { - // This is more than just an optimization. - // When debugging, we cannot execute PowerShell directly to get this information; - // trying to do so will block on the command that called us, deadlocking execution. - // Instead, since we are reusing the runspace, we reuse that runspace's info as well. - KeyValuePair currentRunspace = _runspacesInUse.Peek(); - if (currentRunspace.Key == pwsh.Runspace) - { - runspaceInfo = currentRunspace.Value; - } - } - - if (runspaceInfo is null) - { - RunspaceOrigin runspaceOrigin = pwsh.Runspace.RunspaceIsRemote ? RunspaceOrigin.EnteredProcess : RunspaceOrigin.Local; - runspaceInfo = RunspaceInfo.CreateFromPowerShell(_logger, pwsh, runspaceOrigin, _localComputerName); - _runspacesInUse.Push(new KeyValuePair(pwsh.Runspace, runspaceInfo)); - } - - // TODO: Improve runspace origin detection here - PushPowerShellAndRunLoop(new PowerShellContextFrame(pwsh, runspaceInfo, frameType)); - } - - private void PushPowerShellAndRunLoop(PowerShellContextFrame frame) - { - PushPowerShell(frame); - _pipelineExecutor.RunPowerShellLoop(frame.FrameType); - } - - private void PushPowerShell(PowerShellContextFrame frame) - { - if (_psFrameStack.Count > 0) - { - RemoveRunspaceEventHandlers(CurrentFrame.PowerShell.Runspace); - } - AddRunspaceEventHandlers(frame.PowerShell.Runspace); - - _psFrameStack.Push(frame); - } - - internal void PopPowerShell() - { - _pipelineExecutor.IsExiting = false; - PowerShellContextFrame frame = _psFrameStack.Pop(); - try - { - RemoveRunspaceEventHandlers(frame.PowerShell.Runspace); - if (_psFrameStack.Count > 0) - { - AddRunspaceEventHandlers(CurrentPowerShell.Runspace); - } - } - finally - { - frame.Dispose(); - } - } - - private void AddRunspaceEventHandlers(Runspace runspace) - { - runspace.Debugger.DebuggerStop += OnDebuggerStopped; - runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; - runspace.StateChanged += OnRunspaceStateChanged; - } - - private void RemoveRunspaceEventHandlers(Runspace runspace) - { - runspace.Debugger.DebuggerStop -= OnDebuggerStopped; - runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; - runspace.StateChanged -= OnRunspaceStateChanged; - } - - private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) - { - DebugContext.SetDebuggerStopped(debuggerStopEventArgs); - try - { - CurrentPowerShell.WaitForRemoteOutputIfNeeded(); - PushPowerShellAndRunLoop(_psFactory.CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Debug | PowerShellFrameType.Nested); - CurrentPowerShell.ResumeRemoteOutputIfNeeded(); - } - finally - { - DebugContext.SetDebuggerResumed(); - } - } - - private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs breakpointUpdatedEventArgs) - { - DebugContext.HandleBreakpointUpdated(breakpointUpdatedEventArgs); - } - - private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) - { - if (!runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) - { - PopOrReinitializeRunspaceAsync(); - } - } - - private Task PopOrReinitializeRunspaceAsync() - { - _consoleReplRunner?.SetReplPop(); - RunspaceStateInfo oldRunspaceState = CurrentPowerShell.Runspace.RunspaceStateInfo; - - // Rather than try to lock the PowerShell executor while we alter its state, - // we simply run this on its thread, guaranteeing that no other action can occur - return _pipelineExecutor.RunTaskAsync(new SynchronousDelegateTask( - _logger, - nameof(PopOrReinitializeRunspaceAsync), - new ExecutionOptions { InterruptCurrentForeground = true }, - CancellationToken.None, - (cancellationToken) => - { - while (_psFrameStack.Count > 0 - && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) - { - PopPowerShell(); - } - - if (_psFrameStack.Count == 0) - { - // If our main runspace was corrupted, - // we must re-initialize our state. - // TODO: Use runspace.ResetRunspaceState() here instead - PushInitialPowerShell(); - - _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." - + " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs."); - UI.WriteErrorLine("The main runspace encountered an error and has been reinitialized. See the PowerShell extension logs for more details."); - } - else - { - _logger.LogError($"Current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was popped."); - UI.WriteErrorLine($"The current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}'." - + " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior." - + " The session is now returning to the previous runspace."); - } - })); - } - - } -} -*/ From 8a0fe1ffd8d12d1a6f2ce89e81a33bca1c3ce42b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 14 Sep 2021 17:03:36 -0700 Subject: [PATCH 081/176] Remove more dead code --- .../Execution/PipelineThreadExecutor.cs | 299 ------------------ 1 file changed, 299 deletions(-) delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs deleted file mode 100644 index 6ebf219af..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/PipelineThreadExecutor.cs +++ /dev/null @@ -1,299 +0,0 @@ -/* -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Hosting; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; -using System; -using System.Management.Automation; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; -using System.Collections.Concurrent; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution -{ - - internal class PipelineThreadExecutor - { - private static readonly PropertyInfo s_shouldProcessInExecutionThreadProperty = - typeof(PSEventSubscriber) - .GetProperty( - "ShouldProcessInExecutionThread", - BindingFlags.Instance | BindingFlags.NonPublic); - - private readonly ILoggerFactory _loggerFactory; - - private readonly ILogger _logger; - - private readonly InternalHost _psesHost; - - private readonly IReadLineProvider _readLineProvider; - - private readonly HostStartupInfo _hostInfo; - - private readonly BlockingConcurrentDeque _taskQueue; - - private readonly CancellationTokenSource _consumerThreadCancellationSource; - - private readonly Thread _pipelineThread; - - private readonly CancellationContext _loopCancellationContext; - - private readonly CancellationContext _commandCancellationContext; - - private bool _runIdleLoop; - - public PipelineThreadExecutor( - ILoggerFactory loggerFactory, - HostStartupInfo hostInfo, - InternalHost psesHost, - IReadLineProvider readLineProvider) - { - _logger = loggerFactory.CreateLogger(); - _hostInfo = hostInfo; - _psesHost = psesHost; - _readLineProvider = readLineProvider; - _consumerThreadCancellationSource = new CancellationTokenSource(); - _taskQueue = new BlockingConcurrentDeque(); - _loopCancellationContext = new CancellationContext(); - _commandCancellationContext = new CancellationContext(); - - _pipelineThread = new Thread(Run) - { - Name = "PSES Execution Service Thread", - }; - _pipelineThread.SetApartmentState(ApartmentState.STA); - } - - public bool IsExiting { get; set; } - - public Task RunTaskAsync(SynchronousTask synchronousTask) - { - if (synchronousTask.ExecutionOptions.InterruptCurrentForeground) - { - return CancelCurrentAndRunTaskNowAsync(synchronousTask); - } - - switch (synchronousTask.ExecutionOptions.Priority) - { - case ExecutionPriority.Next: - _taskQueue.Prepend(synchronousTask); - break; - - case ExecutionPriority.Normal: - _taskQueue.Append(synchronousTask); - break; - } - - return synchronousTask.Task; - } - - public void Start() - { - _pipelineThread.Start(); - } - - public void Stop() - { - _consumerThreadCancellationSource.Cancel(); - _pipelineThread.Join(); - } - - public void CancelCurrentTask() - { - _commandCancellationContext.CancelCurrentTask(); - } - - public void Dispose() - { - Stop(); - } - - private Task CancelCurrentAndRunTaskNowAsync(SynchronousTask synchronousTask) - { - // We need to ensure that we don't: - // - Add this command to the queue and immediately cancel it - // - Allow a consumer to dequeue and run another command after cancellation and before we add this command - // - // To ensure that, we need the following sequence: - // - Stop queue consumption progressing - // - Cancel any current processing - // - Add our task to the front of the queue - // - Recommence processing - - using (_taskQueue.BlockConsumers()) - { - _commandCancellationContext.CancelCurrentTaskStack(); - _taskQueue.Prepend(synchronousTask); - return synchronousTask.Task; - } - } - - private void Run() - { - _psesHost.PushInitialPowerShell(); - // We need to override the idle handler here, - // since readline will be overridden when the initial Powershell runspace is instantiated above - _readLineProvider.ReadLine.TryOverrideIdleHandler(OnPowerShellIdle); - _psesHost.StartRepl(); - RunTopLevelConsumerLoop(); - } - - public void RunPowerShellLoop(PowerShellFrameType powerShellFrameType) - { - using (CancellationScope cancellationScope = _loopCancellationContext.EnterScope(_runIdleLoop, _psesHost.CurrentCancellationSource.Token, _consumerThreadCancellationSource.Token)) - { - try - { - if (_runIdleLoop) - { - RunIdleLoop(cancellationScope); - return; - } - - _psesHost.PushNewReplTask(); - - if ((powerShellFrameType & PowerShellFrameType.Debug) != 0) - { - RunDebugLoop(cancellationScope); - return; - } - - RunNestedLoop(cancellationScope); - } - finally - { - _runIdleLoop = false; - _psesHost.PopPowerShell(); - } - } - } - - private void RunTopLevelConsumerLoop() - { - using (CancellationScope cancellationScope = _loopCancellationContext.EnterScope(isIdleScope: false, _psesHost.CurrentCancellationSource.Token, _consumerThreadCancellationSource.Token)) - { - try - { - while (true) - { - RunNextForegroundTaskSynchronously(cancellationScope.CancellationToken); - } - } - catch (OperationCanceledException) - { - // Catch cancellations to end nicely - } - } - } - - private void RunNestedLoop(in CancellationScope cancellationScope) - { - try - { - while (true) - { - RunNextForegroundTaskSynchronously(cancellationScope.CancellationToken); - - if (IsExiting) - { - break; - } - } - } - catch (OperationCanceledException) - { - // Catch cancellations to end nicely - } - } - - private void RunDebugLoop(in CancellationScope cancellationScope) - { - _psesHost.DebugContext.EnterDebugLoop(cancellationScope.CancellationToken); - try - { - // Run commands, but cancelling our blocking wait if the debugger resumes - while (true) - { - ISynchronousTask task = _taskQueue.Take(_psesHost.DebugContext.OnResumeCancellationToken); - - // We don't want to cancel the current command when the debugger resumes, - // since that command will be resuming the debugger. - // Instead let it complete and check the cancellation afterward. - RunTaskSynchronously(task, cancellationScope.CancellationToken); - - if (_psesHost.DebugContext.OnResumeCancellationToken.IsCancellationRequested) - { - break; - } - } - } - catch (OperationCanceledException) - { - // Catch cancellations to end nicely - } - finally - { - _psesHost.DebugContext.ExitDebugLoop(); - } - } - - private void RunIdleLoop(in CancellationScope cancellationScope) - { - try - { - while (!cancellationScope.CancellationToken.IsCancellationRequested - && _taskQueue.TryTake(out ISynchronousTask task)) - { - if (task.ExecutionOptions.MustRunInForeground) - { - _taskQueue.Prepend(task); - _loopCancellationContext.CancelIdleParentTask(); - _commandCancellationContext.CancelIdleParentTask(); - break; - } - - RunTaskSynchronously(task, cancellationScope.CancellationToken); - } - - // TODO: Handle engine events here using a nested pipeline - } - catch (OperationCanceledException) - { - } - } - - private void RunNextForegroundTaskSynchronously(CancellationToken loopCancellationToken) - { - ISynchronousTask task = _taskQueue.Take(loopCancellationToken); - RunTaskSynchronously(task, loopCancellationToken); - } - - private void RunTaskSynchronously(ISynchronousTask task, CancellationToken loopCancellationToken) - { - if (task.IsCanceled) - { - return; - } - - using (CancellationScope commandCancellationScope = _commandCancellationContext.EnterScope(_runIdleLoop, loopCancellationToken)) - { - task.ExecuteSynchronously(commandCancellationScope.CancellationToken); - } - } - - public void OnPowerShellIdle() - { - if (_taskQueue.Count == 0) - { - return; - } - - _runIdleLoop = true; - _psesHost.PushNonInteractivePowerShell(); - } - } -} -*/ From f3360928836b6b9445bc68d14921962a459d622f Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:33:52 -0700 Subject: [PATCH 082/176] Add comment about remote debugging --- src/PowerShellEditorServices/Server/PsesDebugServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index e7f2235ee..6d352945d 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -119,7 +119,7 @@ public async Task StartAsync() // The OnInitialize delegate gets run when we first receive the _Initialize_ request: // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize .OnInitialize(async (server, request, cancellationToken) => { - // Ensure the debugger mode is set correctly + // Ensure the debugger mode is set correctly - this is required for remote debugging to work _debugContext.EnableDebugMode(); var breakpointService = server.GetService(); From fab64e909f160ded120a368c373c0a081a81106f Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:34:27 -0700 Subject: [PATCH 083/176] Rename event handler --- .../Services/DebugAdapter/DebugEventHandlerService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs index 3dbcc9156..66d4b5e16 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs @@ -45,7 +45,7 @@ internal void RegisterEventHandlers() _executionService.RunspaceChanged += ExecutionService_RunspaceChanged; _debugService.BreakpointUpdated += DebugService_BreakpointUpdated; _debugService.DebuggerStopped += DebugService_DebuggerStopped; - _debugContext.DebuggerResuming += PowerShellContext_DebuggerResuming; + _debugContext.DebuggerResuming += OnDebuggerResuming; } internal void UnregisterEventHandlers() @@ -53,7 +53,7 @@ internal void UnregisterEventHandlers() _executionService.RunspaceChanged -= ExecutionService_RunspaceChanged; _debugService.BreakpointUpdated -= DebugService_BreakpointUpdated; _debugService.DebuggerStopped -= DebugService_DebuggerStopped; - _debugContext.DebuggerResuming -= PowerShellContext_DebuggerResuming; + _debugContext.DebuggerResuming -= OnDebuggerResuming; } #region Public methods @@ -125,7 +125,7 @@ private void ExecutionService_RunspaceChanged(object sender, RunspaceChangedEven } } - private void PowerShellContext_DebuggerResuming(object sender, DebuggerResumingEventArgs e) + private void OnDebuggerResuming(object sender, DebuggerResumingEventArgs e) { _debugAdapterServer.SendNotification(EventNames.Continued, new ContinuedEvent From d53e02cb28a6fe4f43e70a798c990e2c693e28fa Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:35:06 -0700 Subject: [PATCH 084/176] Add ConfigureAwait(false) --- .../Services/DebugAdapter/DebugService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 80f7ea83f..c7fe57398 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -408,7 +408,7 @@ public async Task SetVariableAsync(int variableContainerReferenceId, str // Evaluate the expression to get back a PowerShell object from the expression string. // This may throw, in which case the exception is propagated to the caller PSCommand evaluateExpressionCommand = new PSCommand().AddScript(value); - object expressionResult = (await _executionService.ExecutePSCommandAsync(evaluateExpressionCommand, CancellationToken.None)).FirstOrDefault(); + object expressionResult = (await _executionService.ExecutePSCommandAsync(evaluateExpressionCommand, CancellationToken.None).ConfigureAwait(false)).FirstOrDefault(); // If PowerShellContext.ExecuteCommand returns an ErrorRecord as output, the expression failed evaluation. // Ideally we would have a separate means from communicating error records apart from normal output. From 9743969490acaa3935886791e2c4c8458c9bf098 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:36:40 -0700 Subject: [PATCH 085/176] Make psEditor variable name a constant --- .../Services/Extension/ExtensionService.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs index 01966203b..a05898d50 100644 --- a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs @@ -20,6 +20,8 @@ namespace Microsoft.PowerShell.EditorServices.Services.Extension /// internal sealed class ExtensionService { + public const string PSEditorVariableName = "psEditor"; + #region Fields private readonly Dictionary editorCommands = @@ -105,12 +107,12 @@ internal Task InitializeAsync() // Register the editor object in the runspace return ExecutionService.ExecuteDelegateAsync( - "Create $psEditorObject", + $"Create ${PSEditorVariableName} object", ExecutionOptions.Default, CancellationToken.None, (pwsh, cancellationToken) => { - pwsh.Runspace.SessionStateProxy.PSVariable.Set("psEditor", EditorObject); + pwsh.Runspace.SessionStateProxy.PSVariable.Set(PSEditorVariableName, EditorObject); }); } From 03a5c3afa10d50ceee311525d08f0e3762e372c5 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:37:22 -0700 Subject: [PATCH 086/176] Remove debug cancellation token --- .../Services/PowerShell/Console/ConsoleReadLine.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index d1afa6020..6209b4e92 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -149,10 +149,6 @@ private static ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) private string InvokePSReadLine(CancellationToken cancellationToken) { - cancellationToken.Register(() => - { - Debug.WriteLine("PSRL CANCELLED"); - }); EngineIntrinsics engineIntrinsics = _psesHost.IsRunspacePushed ? null : _engineIntrinsics; return _psrlProxy.ReadLine(_psesHost.Runspace, engineIntrinsics, cancellationToken, /* lastExecutionStatus */ null); } From 7a313c3bbf0d2b24d33c3e88d30b86f7c0fa6537 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:39:02 -0700 Subject: [PATCH 087/176] Remove duplicated check --- .../Services/PowerShell/Host/InternalHost.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs index e68a191c1..797ddacc4 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs @@ -440,12 +440,9 @@ private void PopPowerShell() private void RunTopLevelExecutionLoop() { // Make sure we execute any startup tasks first - if (_psFrameStack.Count == 1) + while (_taskQueue.TryTake(out ISynchronousTask task)) { - while (_taskQueue.TryTake(out ISynchronousTask task)) - { - task.ExecuteSynchronously(CancellationToken.None); - } + task.ExecuteSynchronously(CancellationToken.None); } RunExecutionLoop(); From 29da9a0d710d9b01d3971fcfc026c44fbf4ccb52 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:40:37 -0700 Subject: [PATCH 088/176] Add comment --- .../Services/PowerShell/Host/InternalHost.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs index 797ddacc4..4631c3072 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs @@ -485,6 +485,9 @@ private void RunExecutionLoop() private void DoOneRepl(CancellationToken cancellationToken) { + // When a task must run in the foreground, we cancel out of the idle loop and return to the top level. + // At that point, we would normally run a REPL, but we need to immediately execute the task. + // So we set _skipNextPrompt to do that. if (_skipNextPrompt) { _skipNextPrompt = false; From d8a5e1a6d23a17c01448c7a5b9617b0f1b216ba6 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:51:30 -0700 Subject: [PATCH 089/176] Add data structure for interlocked latch pattern --- .../Services/Extension/ExtensionService.cs | 4 ++-- .../Services/PowerShell/Host/InternalHost.cs | 8 +++---- .../Utility/IdempotentLatch.cs | 21 +++++++++++++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 src/PowerShellEditorServices/Utility/IdempotentLatch.cs diff --git a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs index a05898d50..55830de81 100644 --- a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs @@ -29,7 +29,7 @@ internal sealed class ExtensionService private readonly ILanguageServerFacade _languageServer; - private int _initialized = 0; + private IdempotentLatch _initializedLatch = new(); #endregion @@ -97,7 +97,7 @@ internal ExtensionService( /// A Task that can be awaited for completion. internal Task InitializeAsync() { - if (Interlocked.Exchange(ref _initialized, 1) != 0) + if (!_initializedLatch.TryEnter()) { return Task.CompletedTask; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs index 4631c3072..7d5175e94 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs @@ -53,9 +53,9 @@ internal class InternalHost : PSHost, IHostSupportsInteractiveSession, IRunspace private readonly Thread _pipelineThread; - private bool _shouldExit = false; + private readonly IdempotentLatch _isRunningLatch = new(); - private int _isRunning = 0; + private bool _shouldExit = false; private string _localComputerName; @@ -118,7 +118,7 @@ public InternalHost( public PowerShellDebugContext DebugContext { get; } - public bool IsRunning => _isRunning != 0; + public bool IsRunning => _isRunningLatch.IsSignaled; public string InitialWorkingDirectory { get; private set; } @@ -167,7 +167,7 @@ public override void SetShouldExit(int exitCode) public async Task StartAsync(HostStartOptions startOptions, CancellationToken cancellationToken) { _logger.LogInformation("Host starting"); - if (Interlocked.Exchange(ref _isRunning, 1) != 0) + if (!_isRunningLatch.TryEnter()) { _logger.LogDebug("Host start requested after already started"); return; diff --git a/src/PowerShellEditorServices/Utility/IdempotentLatch.cs b/src/PowerShellEditorServices/Utility/IdempotentLatch.cs new file mode 100644 index 000000000..1f4b965bf --- /dev/null +++ b/src/PowerShellEditorServices/Utility/IdempotentLatch.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace Microsoft.PowerShell.EditorServices.Utility +{ + internal class IdempotentLatch + { + private int _signaled; + + public IdempotentLatch() + { + _signaled = 0; + } + + public bool IsSignaled => _signaled != 0; + + public bool TryEnter() => Interlocked.Exchange(ref _signaled, 1) == 0; + } +} From e2156ad865149ac9461e97f8d697335b436f0ebc Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 10:16:55 -0700 Subject: [PATCH 090/176] Move cancellation token argument to end of methods --- .../Services/DebugAdapter/DebugService.cs | 4 +- .../Services/Extension/ExtensionService.cs | 4 +- .../Debugging/DscBreakpointCapability.cs | 4 +- .../Execution/SynchronousDelegateTask.cs | 16 +++---- .../Services/PowerShell/Host/InternalHost.cs | 43 +++++++++---------- .../PowerShell/PowerShellExecutionService.cs | 24 +++++------ .../Services/Symbols/Vistors/AstOperations.cs | 4 +- 7 files changed, 48 insertions(+), 51 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index c7fe57398..10c5dc20d 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -497,7 +497,6 @@ public async Task SetVariableAsync(int variableContainerReferenceId, str psVariable.Value = await _executionService.ExecuteDelegateAsync( "PS debugger argument converter", ExecutionOptions.Default, - CancellationToken.None, (pwsh, cancellationToken) => { var engineIntrinsics = (EngineIntrinsics)pwsh.Runspace.SessionStateProxy.GetVariable("ExecutionContext"); @@ -506,7 +505,8 @@ public async Task SetVariableAsync(int variableContainerReferenceId, str // We should investigate changing it. return argTypeConverterAttr.Transform(engineIntrinsics, expressionResult); - }).ConfigureAwait(false); + }, + CancellationToken.None).ConfigureAwait(false); } else diff --git a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs index 55830de81..86e9156c5 100644 --- a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs @@ -109,11 +109,11 @@ internal Task InitializeAsync() return ExecutionService.ExecuteDelegateAsync( $"Create ${PSEditorVariableName} object", ExecutionOptions.Default, - CancellationToken.None, (pwsh, cancellationToken) => { pwsh.Runspace.SessionStateProxy.PSVariable.Set(PSEditorVariableName, EditorObject); - }); + }, + CancellationToken.None); } /// diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index 05b645d6d..87112d165 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -167,8 +167,8 @@ public static async Task GetDscCapabilityAsync( return await psesHost.ExecuteDelegateAsync( nameof(getDscBreakpointCapabilityFunc), ExecutionOptions.Default, - cancellationToken, - getDscBreakpointCapabilityFunc); + getDscBreakpointCapabilityFunc, + cancellationToken); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs index 4aad8035b..db578602b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs @@ -17,8 +17,8 @@ public SynchronousDelegateTask( ILogger logger, string representation, ExecutionOptions executionOptions, - CancellationToken cancellationToken, - Action action) + Action action, + CancellationToken cancellationToken) : base(logger, cancellationToken) { ExecutionOptions = executionOptions; @@ -50,8 +50,8 @@ public SynchronousDelegateTask( ILogger logger, string representation, ExecutionOptions executionOptions, - CancellationToken cancellationToken, - Func func) + Func func, + CancellationToken cancellationToken) : base(logger, cancellationToken) { _func = func; @@ -85,8 +85,8 @@ public SynchronousPSDelegateTask( InternalHost psesHost, string representation, ExecutionOptions executionOptions, - CancellationToken cancellationToken, - Action action) + Action action, + CancellationToken cancellationToken) : base(logger, cancellationToken) { _psesHost = psesHost; @@ -122,8 +122,8 @@ public SynchronousPSDelegateTask( InternalHost psesHost, string representation, ExecutionOptions executionOptions, - CancellationToken cancellationToken, - Func func) + Func func, + CancellationToken cancellationToken) : base(logger, cancellationToken) { _psesHost = psesHost; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs index 7d5175e94..3652f4f11 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/InternalHost.cs @@ -178,13 +178,10 @@ public async Task StartAsync(HostStartOptions startOptions, CancellationToken ca if (startOptions.LoadProfiles) { await ExecuteDelegateAsync( - "LoadProfiles", - new PowerShellExecutionOptions { MustRunInForeground = true }, - cancellationToken, - (pwsh, delegateCancellation) => - { - pwsh.LoadProfiles(_hostInfo.ProfilePaths); - }).ConfigureAwait(false); + "LoadProfiles", + new PowerShellExecutionOptions { MustRunInForeground = true }, + (pwsh, delegateCancellation) => pwsh.LoadProfiles(_hostInfo.ProfilePaths), + cancellationToken).ConfigureAwait(false); _logger.LogInformation("Profiles loaded"); } @@ -247,37 +244,37 @@ public void CancelCurrentTask() public Task ExecuteDelegateAsync( string representation, ExecutionOptions executionOptions, - CancellationToken cancellationToken, - Func func) + Func func, + CancellationToken cancellationToken) { - return InvokeTaskOnPipelineThreadAsync(new SynchronousPSDelegateTask(_logger, this, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, func)); + return InvokeTaskOnPipelineThreadAsync(new SynchronousPSDelegateTask(_logger, this, representation, executionOptions ?? ExecutionOptions.Default, func, cancellationToken)); } public Task ExecuteDelegateAsync( string representation, ExecutionOptions executionOptions, - CancellationToken cancellationToken, - Action action) + Action action, + CancellationToken cancellationToken) { - return InvokeTaskOnPipelineThreadAsync(new SynchronousPSDelegateTask(_logger, this, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, action)); + return InvokeTaskOnPipelineThreadAsync(new SynchronousPSDelegateTask(_logger, this, representation, executionOptions ?? ExecutionOptions.Default, action, cancellationToken)); } public Task ExecuteDelegateAsync( string representation, ExecutionOptions executionOptions, - CancellationToken cancellationToken, - Func func) + Func func, + CancellationToken cancellationToken) { - return InvokeTaskOnPipelineThreadAsync(new SynchronousDelegateTask(_logger, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, func)); + return InvokeTaskOnPipelineThreadAsync(new SynchronousDelegateTask(_logger, representation, executionOptions ?? ExecutionOptions.Default, func, cancellationToken)); } public Task ExecuteDelegateAsync( string representation, ExecutionOptions executionOptions, - CancellationToken cancellationToken, - Action action) + Action action, + CancellationToken cancellationToken) { - return InvokeTaskOnPipelineThreadAsync(new SynchronousDelegateTask(_logger, representation, executionOptions ?? ExecutionOptions.Default, cancellationToken, action)); + return InvokeTaskOnPipelineThreadAsync(new SynchronousDelegateTask(_logger, representation, executionOptions ?? ExecutionOptions.Default, action, cancellationToken)); } public Task> ExecutePSCommandAsync( @@ -300,13 +297,13 @@ public Task ExecutePSCommandAsync( public TResult InvokeDelegate(string representation, ExecutionOptions executionOptions, Func func, CancellationToken cancellationToken) { - var task = new SynchronousDelegateTask(_logger, representation, executionOptions, cancellationToken, func); + var task = new SynchronousDelegateTask(_logger, representation, executionOptions, func, cancellationToken); return task.ExecuteAndGetResult(cancellationToken); } public void InvokeDelegate(string representation, ExecutionOptions executionOptions, Action action, CancellationToken cancellationToken) { - var task = new SynchronousDelegateTask(_logger, representation, executionOptions, cancellationToken, action); + var task = new SynchronousDelegateTask(_logger, representation, executionOptions, action, cancellationToken); task.ExecuteAndGetResult(cancellationToken); } @@ -321,13 +318,13 @@ public void InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions exec public TResult InvokePSDelegate(string representation, ExecutionOptions executionOptions, Func func, CancellationToken cancellationToken) { - var task = new SynchronousPSDelegateTask(_logger, this, representation, executionOptions, cancellationToken, func); + var task = new SynchronousPSDelegateTask(_logger, this, representation, executionOptions, func, cancellationToken); return task.ExecuteAndGetResult(cancellationToken); } public void InvokePSDelegate(string representation, ExecutionOptions executionOptions, Action action, CancellationToken cancellationToken) { - var task = new SynchronousPSDelegateTask(_logger, this, representation, executionOptions, cancellationToken, action); + var task = new SynchronousPSDelegateTask(_logger, this, representation, executionOptions, action, cancellationToken); task.ExecuteAndGetResult(cancellationToken); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 43a37efb2..240cf8dcd 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -35,30 +35,30 @@ public PowerShellExecutionService( public Task ExecuteDelegateAsync( string representation, ExecutionOptions executionOptions, - CancellationToken cancellationToken, - Func func) - => _psesHost.ExecuteDelegateAsync(representation, executionOptions, cancellationToken, func); + Func func, + CancellationToken cancellationToken) + => _psesHost.ExecuteDelegateAsync(representation, executionOptions, func, cancellationToken); public Task ExecuteDelegateAsync( string representation, ExecutionOptions executionOptions, - CancellationToken cancellationToken, - Action action) - => _psesHost.ExecuteDelegateAsync(representation, executionOptions, cancellationToken, action); + Action action, + CancellationToken cancellationToken) + => _psesHost.ExecuteDelegateAsync(representation, executionOptions, action, cancellationToken); public Task ExecuteDelegateAsync( string representation, ExecutionOptions executionOptions, - CancellationToken cancellationToken, - Func func) - => _psesHost.ExecuteDelegateAsync(representation, executionOptions, cancellationToken, func); + Func func, + CancellationToken cancellationToken) + => _psesHost.ExecuteDelegateAsync(representation, executionOptions, func, cancellationToken); public Task ExecuteDelegateAsync( string representation, ExecutionOptions executionOptions, - CancellationToken cancellationToken, - Action action) - => _psesHost.ExecuteDelegateAsync(representation, executionOptions, cancellationToken, action); + Action action, + CancellationToken cancellationToken) + => _psesHost.ExecuteDelegateAsync(representation, executionOptions, action, cancellationToken); public Task> ExecutePSCommandAsync( PSCommand psCommand, diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index f67fcf65a..71d2d588b 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -91,7 +91,6 @@ public static async Task GetCompletionsAsync( await executionService.ExecuteDelegateAsync( representation: "CompleteInput", new ExecutionOptions { Priority = ExecutionPriority.Next }, - cancellationToken, (pwsh, cancellationToken) => { stopwatch.Start(); @@ -101,7 +100,8 @@ await executionService.ExecuteDelegateAsync( cursorPosition, options: null, powershell: pwsh); - }); + }, + cancellationToken); stopwatch.Stop(); logger.LogTrace($"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); From 50635e49e1655d95e22e547c3c6f251c4ccb141e Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 10:27:12 -0700 Subject: [PATCH 091/176] Make BlockingConcurrentDeque disposable --- .../PowerShell/Execution/BlockingConcurrentDeque.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs index b35bfbe4f..afaf59cc9 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs @@ -17,7 +17,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution /// This behavior is unlikely to change and ensuring its correctness at our layer is likely to be costly. /// See https://stackoverflow.com/q/26472251. /// - internal class BlockingConcurrentDeque + internal class BlockingConcurrentDeque : IDisposable { private readonly ManualResetEventSlim _blockConsumersEvent; @@ -68,6 +68,11 @@ public bool TryTake(out T item) public IDisposable BlockConsumers() => PriorityQueueBlockLifetime.StartBlocking(_blockConsumersEvent); + public void Dispose() + { + ((IDisposable)_blockConsumersEvent).Dispose(); + } + private class PriorityQueueBlockLifetime : IDisposable { public static PriorityQueueBlockLifetime StartBlocking(ManualResetEventSlim blockEvent) From be8eb1ee98eabd50f7beb36a2a80fa83d3e8ba38 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 5 Oct 2021 15:52:05 -0700 Subject: [PATCH 092/176] Rename PSES InternalHost to PsesInternalHost --- src/PowerShellEditorServices/Server/PsesDebugServer.cs | 2 +- .../Server/PsesServiceCollectionExtensions.cs | 10 +++++----- .../Services/DebugAdapter/BreakpointService.cs | 4 ++-- .../Services/DebugAdapter/DebugService.cs | 4 ++-- .../Services/Extension/EditorOperationsService.cs | 4 ++-- .../Services/PowerShell/Console/ConsoleReadLine.cs | 4 ++-- .../PowerShell/Debugging/DscBreakpointCapability.cs | 2 +- .../PowerShell/Debugging/PowerShellDebugContext.cs | 4 ++-- .../PowerShell/Execution/SynchronousDelegateTask.cs | 8 ++++---- .../PowerShell/Execution/SynchronousPowerShellTask.cs | 4 ++-- .../PowerShell/Host/EditorServicesConsolePSHost.cs | 4 ++-- .../Host/{InternalHost.cs => PsesInternalHost.cs} | 6 +++--- .../Services/PowerShell/PowerShellExecutionService.cs | 4 ++-- .../Services/PowerShell/Runspace/RunspaceInfo.cs | 2 +- .../Workspace/Handlers/ConfigurationHandler.cs | 4 ++-- 15 files changed, 33 insertions(+), 33 deletions(-) rename src/PowerShellEditorServices/Services/PowerShell/Host/{InternalHost.cs => PsesInternalHost.cs} (99%) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 6d352945d..f089b5c68 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -76,7 +76,7 @@ public async Task StartAsync() { // We need to let the PowerShell Context Service know that we are in a debug session // so that it doesn't send the powerShell/startDebugger message. - _debugContext = ServiceProvider.GetService().DebugContext; + _debugContext = ServiceProvider.GetService().DebugContext; _debugContext.IsDebugServerActive = true; /* diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index 27db3cc5a..86ab0449c 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -28,13 +28,13 @@ public static IServiceCollection AddPsesLanguageServices( .AddSingleton(hostStartupInfo) .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton( - (provider) => provider.GetService()) + (provider) => provider.GetService()) .AddSingleton() .AddSingleton() .AddSingleton( - (provider) => provider.GetService().DebugContext) + (provider) => provider.GetService().DebugContext) .AddSingleton() .AddSingleton() .AddSingleton() @@ -64,10 +64,10 @@ public static IServiceCollection AddPsesDebugServices( PsesDebugServer psesDebugServer, bool useTempSession) { - InternalHost internalHost = languageServiceProvider.GetService(); + PsesInternalHost internalHost = languageServiceProvider.GetService(); return collection - .AddSingleton(internalHost) + .AddSingleton(internalHost) .AddSingleton(internalHost) .AddSingleton(internalHost.DebugContext) .AddSingleton(languageServiceProvider.GetService()) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs index 62208c5f7..54e9c53eb 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs @@ -22,7 +22,7 @@ internal class BreakpointService private readonly ILogger _logger; private readonly PowerShellExecutionService _executionService; - private readonly InternalHost _editorServicesHost; + private readonly PsesInternalHost _editorServicesHost; private readonly DebugStateService _debugStateService; // TODO: This needs to be managed per nested session @@ -35,7 +35,7 @@ internal class BreakpointService public BreakpointService( ILoggerFactory factory, PowerShellExecutionService executionService, - InternalHost editorServicesHost, + PsesInternalHost editorServicesHost, DebugStateService debugStateService) { _logger = factory.CreateLogger(); diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 10c5dc20d..973996051 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -39,7 +39,7 @@ internal class DebugService private readonly BreakpointService _breakpointService; private readonly RemoteFileManagerService remoteFileManager; - private readonly InternalHost _psesHost; + private readonly PsesInternalHost _psesHost; private readonly IPowerShellDebugContext _debugContext; @@ -107,7 +107,7 @@ public DebugService( IPowerShellDebugContext debugContext, RemoteFileManagerService remoteFileManager, BreakpointService breakpointService, - InternalHost psesHost, + PsesInternalHost psesHost, ILoggerFactory factory) { Validate.IsNotNull(nameof(executionService), executionService); diff --git a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs index ef7ed5ae3..d56347a28 100644 --- a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs +++ b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs @@ -16,7 +16,7 @@ internal class EditorOperationsService : IEditorOperations { private const bool DefaultPreviewSetting = true; - private readonly InternalHost _psesHost; + private readonly PsesInternalHost _psesHost; private readonly WorkspaceService _workspaceService; private readonly ILanguageServerFacade _languageServer; @@ -24,7 +24,7 @@ internal class EditorOperationsService : IEditorOperations private readonly PowerShellExecutionService _executionService; public EditorOperationsService( - InternalHost psesHost, + PsesInternalHost psesHost, WorkspaceService workspaceService, PowerShellExecutionService executionService, ILanguageServerFacade languageServer) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index 6209b4e92..f6de4b1fc 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -21,7 +21,7 @@ internal class ConsoleReadLine : IReadLine { private readonly PSReadLineProxy _psrlProxy; - private readonly InternalHost _psesHost; + private readonly PsesInternalHost _psesHost; private readonly EngineIntrinsics _engineIntrinsics; @@ -29,7 +29,7 @@ internal class ConsoleReadLine : IReadLine public ConsoleReadLine( PSReadLineProxy psrlProxy, - InternalHost psesHost, + PsesInternalHost psesHost, EngineIntrinsics engineIntrinsics) { _psrlProxy = psrlProxy; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index 87112d165..98c27690a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -89,7 +89,7 @@ public bool IsDscResourcePath(string scriptPath) public static async Task GetDscCapabilityAsync( ILogger logger, IRunspaceInfo currentRunspace, - InternalHost psesHost, + PsesInternalHost psesHost, CancellationToken cancellationToken) { // DSC support is enabled only for Windows PowerShell. diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index 09472ff82..63f2ab5bf 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -38,12 +38,12 @@ internal class PowerShellDebugContext : IPowerShellDebugContext private readonly ILanguageServerFacade _languageServer; - private readonly InternalHost _psesHost; + private readonly PsesInternalHost _psesHost; public PowerShellDebugContext( ILoggerFactory loggerFactory, ILanguageServerFacade languageServer, - InternalHost psesHost) + PsesInternalHost psesHost) { _logger = loggerFactory.CreateLogger(); _languageServer = languageServer; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs index db578602b..3ff4b676f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs @@ -78,11 +78,11 @@ internal class SynchronousPSDelegateTask : SynchronousTask private readonly string _representation; - private readonly InternalHost _psesHost; + private readonly PsesInternalHost _psesHost; public SynchronousPSDelegateTask( ILogger logger, - InternalHost psesHost, + PsesInternalHost psesHost, string representation, ExecutionOptions executionOptions, Action action, @@ -115,11 +115,11 @@ internal class SynchronousPSDelegateTask : SynchronousTask private readonly string _representation; - private readonly InternalHost _psesHost; + private readonly PsesInternalHost _psesHost; public SynchronousPSDelegateTask( ILogger logger, - InternalHost psesHost, + PsesInternalHost psesHost, string representation, ExecutionOptions executionOptions, Func func, diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index b264cf364..3bbd2c0ca 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -15,7 +15,7 @@ internal class SynchronousPowerShellTask : SynchronousTask : SynchronousTask(); + _logger = loggerFactory.CreateLogger(); _languageServer = languageServer; _hostInfo = hostInfo; diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs index 240cf8dcd..6d4180895 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs @@ -20,11 +20,11 @@ internal class PowerShellExecutionService { private readonly ILogger _logger; - private readonly InternalHost _psesHost; + private readonly PsesInternalHost _psesHost; public PowerShellExecutionService( ILoggerFactory loggerFactory, - InternalHost psesHost) + PsesInternalHost psesHost) { _logger = loggerFactory.CreateLogger(); _psesHost = psesHost; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs index 3a97442ed..dd6fe91f1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs @@ -83,7 +83,7 @@ public RunspaceInfo( public async Task GetDscBreakpointCapabilityAsync( ILogger logger, - InternalHost psesHost, + PsesInternalHost psesHost, CancellationToken cancellationToken) { if (_dscBreakpointCapability == null) diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index 196e196d1..4f25c7386 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -29,7 +29,7 @@ internal class PsesConfigurationHandler : DidChangeConfigurationHandlerBase private readonly WorkspaceService _workspaceService; private readonly ConfigurationService _configurationService; private readonly ExtensionService _extensionService; - private readonly InternalHost _psesHost; + private readonly PsesInternalHost _psesHost; private readonly ILanguageServerFacade _languageServer; private DidChangeConfigurationCapability _capability; private bool _profilesLoaded; @@ -44,7 +44,7 @@ public PsesConfigurationHandler( ConfigurationService configurationService, ILanguageServerFacade languageServer, ExtensionService extensionService, - InternalHost psesHost) + PsesInternalHost psesHost) { _logger = factory.CreateLogger(); _workspaceService = workspaceService; From 330afeb46a764fc121ef2b02cecbe57a2d63499f Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 5 Oct 2021 16:10:54 -0700 Subject: [PATCH 093/176] Change WriteErrorsToHost to ThrowOnError --- .../DebugAdapter/Handlers/ConfigurationDoneHandler.cs | 1 + .../Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs | 2 +- .../Services/Extension/ExtensionService.cs | 2 +- .../Services/PowerShell/Execution/ExecutionOptions.cs | 4 ++-- .../PowerShell/Execution/SynchronousPowerShellTask.cs | 6 +++--- .../Services/PowerShell/Handlers/EvaluateHandler.cs | 2 +- .../Services/PowerShell/Handlers/GetVersionHandler.cs | 2 +- .../Services/PowerShell/Handlers/ShowHelpHandler.cs | 2 +- .../Services/PowerShell/Host/PsesInternalHost.cs | 4 ++-- .../Services/Template/TemplateService.cs | 2 +- 10 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 8b5cf6452..3c0291c12 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -28,6 +28,7 @@ internal class ConfigurationDoneHandler : IConfigurationDoneHandler MustRunInForeground = true, WriteInputToHost = true, WriteOutputToHost = true, + ThrowOnError = false, AddToHistory = true, }; diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs index e5304225e..e7d6076e1 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs @@ -50,7 +50,7 @@ public async Task Handle(EvaluateRequestArguments request, _executionService.ExecutePSCommandAsync( new PSCommand().AddScript(request.Expression), CancellationToken.None, - new PowerShellExecutionOptions { WriteOutputToHost = true }).HandleErrorsAsync(_logger); + new PowerShellExecutionOptions { WriteOutputToHost = true, ThrowOnError = false, AddToHistory = true }).HandleErrorsAsync(_logger); } else { diff --git a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs index 86e9156c5..379d7dfc1 100644 --- a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs @@ -135,7 +135,7 @@ public async Task InvokeCommandAsync(string commandName, EditorContext editorCon await ExecutionService.ExecutePSCommandAsync( executeCommand, CancellationToken.None, - new PowerShellExecutionOptions { WriteOutputToHost = !editorCommand.SuppressOutput }) + new PowerShellExecutionOptions { WriteOutputToHost = !editorCommand.SuppressOutput, ThrowOnError = false, AddToHistory = !editorCommand.SuppressOutput }) .ConfigureAwait(false); } else diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs index e21c3bd46..46099b073 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs @@ -39,7 +39,7 @@ public record PowerShellExecutionOptions : ExecutionOptions InterruptCurrentForeground = false, WriteOutputToHost = false, WriteInputToHost = false, - WriteErrorsToHost = false, + ThrowOnError = true, AddToHistory = false, }; @@ -47,7 +47,7 @@ public record PowerShellExecutionOptions : ExecutionOptions public bool WriteInputToHost { get; init; } - public bool WriteErrorsToHost { get; init; } + public bool ThrowOnError { get; init; } public bool AddToHistory { get; init; } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 3bbd2c0ca..ed158287f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -75,7 +75,7 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok AddToHistory = PowerShellExecutionOptions.AddToHistory, }; - if (!PowerShellExecutionOptions.WriteErrorsToHost) + if (PowerShellExecutionOptions.ThrowOnError) { invocationSettings.ErrorActionPreference = ActionPreference.Stop; } @@ -94,7 +94,7 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok { Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}"); - if (!PowerShellExecutionOptions.WriteErrorsToHost) + if (PowerShellExecutionOptions.ThrowOnError) { throw; } @@ -161,7 +161,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT { Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}"); - if (!PowerShellExecutionOptions.WriteErrorsToHost) + if (!PowerShellExecutionOptions.ThrowOnError) { throw; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs index c2bcbb2f1..a065b42ac 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs @@ -35,7 +35,7 @@ public Task Handle(EvaluateRequestArguments request, Cance _executionService.ExecutePSCommandAsync( new PSCommand().AddScript(request.Expression), CancellationToken.None, - new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true }); + new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true, ThrowOnError = false }); return Task.FromResult(new EvaluateResponseBody { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs index 21113ede8..375bd5c1e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs @@ -127,7 +127,7 @@ private async Task CheckPackageManagement() await _executionService.ExecutePSCommandAsync( command, CancellationToken.None, - new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true }).ConfigureAwait(false); + new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true, ThrowOnError = false }).ConfigureAwait(false); // TODO: Error handling here diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs index 54ef54ea0..7be270f11 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs @@ -74,7 +74,7 @@ public async Task Handle(ShowHelpParams request, CancellationToken cancell // TODO: Rather than print the help in the console, we should send the string back // to VSCode to display in a help pop-up (or similar) - await _executionService.ExecutePSCommandAsync(checkHelpPSCommand, cancellationToken, new PowerShellExecutionOptions { WriteOutputToHost = true }).ConfigureAwait(false); + await _executionService.ExecutePSCommandAsync(checkHelpPSCommand, cancellationToken, new PowerShellExecutionOptions { WriteOutputToHost = true, ThrowOnError = false }).ConfigureAwait(false); return Unit.Value; } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 2b9fb4c6a..230f326f5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -179,7 +179,7 @@ public async Task StartAsync(HostStartOptions startOptions, CancellationToken ca { await ExecuteDelegateAsync( "LoadProfiles", - new PowerShellExecutionOptions { MustRunInForeground = true }, + new PowerShellExecutionOptions { MustRunInForeground = true, ThrowOnError = false }, (pwsh, delegateCancellation) => pwsh.LoadProfiles(_hostInfo.ProfilePaths), cancellationToken).ConfigureAwait(false); @@ -549,7 +549,7 @@ private string InvokeReadLine(CancellationToken cancellationToken) private void InvokeInput(string input, CancellationToken cancellationToken) { var command = new PSCommand().AddScript(input, useLocalScope: false); - InvokePSCommand(command, new PowerShellExecutionOptions { AddToHistory = true, WriteErrorsToHost = true, WriteOutputToHost = true }, cancellationToken); + InvokePSCommand(command, new PowerShellExecutionOptions { AddToHistory = true, ThrowOnError = false, WriteOutputToHost = true }, cancellationToken); } private void AddRunspaceEventHandlers(Runspace runspace) diff --git a/src/PowerShellEditorServices/Services/Template/TemplateService.cs b/src/PowerShellEditorServices/Services/Template/TemplateService.cs index f817e9f2f..d769cac18 100644 --- a/src/PowerShellEditorServices/Services/Template/TemplateService.cs +++ b/src/PowerShellEditorServices/Services/Template/TemplateService.cs @@ -166,7 +166,7 @@ public async Task CreateFromTemplateAsync( await _executionService.ExecutePSCommandAsync( command, CancellationToken.None, - new PowerShellExecutionOptions { WriteOutputToHost = true, InterruptCurrentForeground = true }).ConfigureAwait(false); + new PowerShellExecutionOptions { WriteOutputToHost = true, InterruptCurrentForeground = true, ThrowOnError = false }).ConfigureAwait(false); // If any errors were written out, creation was not successful return true; From 8760388a549385aef0b1949de6a0db381979de8b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 14:36:10 -0700 Subject: [PATCH 094/176] Make default PowerShellExecutionOptions static --- .../Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 3c0291c12..6239a44c6 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -23,7 +23,7 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { internal class ConfigurationDoneHandler : IConfigurationDoneHandler { - private readonly PowerShellExecutionOptions s_debuggerExecutionOptions = new() + private static readonly PowerShellExecutionOptions s_debuggerExecutionOptions = new() { MustRunInForeground = true, WriteInputToHost = true, From 15ffe03bcd34cb3e6ef87a06f14a2a6e4cebf87d Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:16:58 -0700 Subject: [PATCH 095/176] Remove PowerShellContextService --- .../PowerShellEditorServices.csproj | 1 - .../Console/ChoicePromptHandler.cs | 348 -- .../Console/CollectionFieldDetails.cs | 136 - .../Console/ConsoleChoicePromptHandler.cs | 131 - .../Console/ConsoleInputPromptHandler.cs | 100 - .../PowerShellContext/Console/ConsoleProxy.cs | 199 -- .../Console/ConsoleReadLine.cs | 616 ---- .../Console/CredentialFieldDetails.cs | 120 - .../PowerShellContext/Console/FieldDetails.cs | 232 -- .../Console/IConsoleOperations.cs | 138 - .../Console/InputPromptHandler.cs | 326 -- .../Console/PromptHandler.cs | 53 - .../Console/TerminalChoicePromptHandler.cs | 60 - .../Console/TerminalInputPromptHandler.cs | 77 - .../Console/UnixConsoleOperations.cs | 296 -- .../Console/WindowsConsoleOperations.cs | 75 - .../PowerShellContextService.cs | 2854 ----------------- .../Capabilities/DscBreakpointCapability.cs | 169 - .../Session/ExecutionOptions.cs | 102 - .../Session/ExecutionStatus.cs | 37 - .../ExecutionStatusChangedEventArgs.cs | 50 - .../Session/ExecutionTarget.cs | 26 - .../Session/Host/EditorServicesPSHost.cs | 392 --- .../Host/EditorServicesPSHostUserInterface.cs | 1071 ------- .../Session/Host/IHostInput.cs | 26 - .../Session/Host/IHostOutput.cs | 173 - .../Session/Host/PromptHandlers.cs | 178 - .../Host/ProtocolPSHostUserInterface.cs | 101 - .../Host/SimplePSHostRawUserInterface.cs | 223 -- .../Host/TerminalPSHostRawUserInterface.cs | 374 --- .../Host/TerminalPSHostUserInterface.cs | 202 -- .../Session/IPromptContext.cs | 65 - .../Session/IRunspaceCapability.cs | 10 - .../Session/IVersionSpecificOperations.cs | 31 - .../Session/InvocationEventQueue.cs | 261 -- .../Session/LegacyReadLineContext.cs | 53 - .../PowerShellContext/Session/OutputType.cs | 39 - .../Session/OutputWrittenEventArgs.cs | 62 - .../Session/PSReadLinePromptContext.cs | 202 -- .../Session/PSReadLineProxy.cs | 116 - .../Session/PipelineExecutionRequest.cs | 78 - .../Session/PowerShell5Operations.cs | 104 - .../Session/PowerShellContextState.cs | 44 - .../Session/PowerShellExecutionResult.cs | 37 - .../Session/ProgressDetails.cs | 30 - .../PowerShellContext/Session/PromptNest.cs | 562 ---- .../Session/PromptNestFrame.cs | 135 - .../Session/PromptNestFrameType.cs | 19 - .../Session/RunspaceChangedEventArgs.cs | 67 - .../Session/RunspaceDetails.cs | 300 -- .../Session/RunspaceHandle.cs | 58 - .../Session/SessionStateChangedEventArgs.cs | 45 - .../Session/ThreadController.cs | 129 - 53 files changed, 11333 deletions(-) delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoicePromptHandler.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/CollectionFieldDetails.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleChoicePromptHandler.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleInputPromptHandler.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleProxy.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleReadLine.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/CredentialFieldDetails.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/FieldDetails.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/IConsoleOperations.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/InputPromptHandler.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/PromptHandler.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/TerminalChoicePromptHandler.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/TerminalInputPromptHandler.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/UnixConsoleOperations.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Console/WindowsConsoleOperations.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/Capabilities/DscBreakpointCapability.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionOptions.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionStatus.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionStatusChangedEventArgs.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionTarget.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHost.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHostUserInterface.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/IHostInput.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/IHostOutput.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/PromptHandlers.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/ProtocolPSHostUserInterface.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/SimplePSHostRawUserInterface.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostRawUserInterface.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostUserInterface.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/IPromptContext.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/IRunspaceCapability.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/IVersionSpecificOperations.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/InvocationEventQueue.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/LegacyReadLineContext.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/OutputType.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/OutputWrittenEventArgs.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLineProxy.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/PipelineExecutionRequest.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShell5Operations.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellContextState.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellExecutionResult.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/ProgressDetails.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNest.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNestFrame.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNestFrameType.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceChangedEventArgs.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceDetails.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceHandle.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/SessionStateChangedEventArgs.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShellContext/Session/ThreadController.cs diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 4f5225819..6fdc36b3c 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -44,7 +44,6 @@ - diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoicePromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoicePromptHandler.cs deleted file mode 100644 index 499a757a1..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoicePromptHandler.cs +++ /dev/null @@ -1,348 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Management.Automation; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Indicates the style of prompt to be displayed. - /// - internal enum PromptStyle - { - /// - /// Indicates that the full prompt should be displayed - /// with all relevant details. - /// - Full, - - /// - /// Indicates that a minimal prompt should be displayed, - /// generally used after the full prompt has already been - /// displayed and the options must be displayed again. - /// - Minimal - } - - /// - /// Provides a base implementation for IPromptHandler classes - /// that present the user a set of options from which a selection - /// should be made. - /// - internal abstract class ChoicePromptHandler : PromptHandler - { - #region Private Fields - - private CancellationTokenSource promptCancellationTokenSource = - new CancellationTokenSource(); - private TaskCompletionSource> cancelTask = - new TaskCompletionSource>(); - - #endregion - - /// - /// - /// - /// An ILogger implementation used for writing log messages. - public ChoicePromptHandler(ILogger logger) : base(logger) - { - } - - #region Properties - - /// - /// Returns true if the choice prompt allows multiple selections. - /// - protected bool IsMultiChoice { get; private set; } - - /// - /// Gets the caption (title) string to display with the prompt. - /// - protected string Caption { get; private set; } - - /// - /// Gets the descriptive message to display with the prompt. - /// - protected string Message { get; private set; } - - /// - /// Gets the array of choices from which the user must select. - /// - protected ChoiceDetails[] Choices { get; private set; } - - /// - /// Gets the index of the default choice so that the user - /// interface can make it easy to select this option. - /// - protected int[] DefaultChoices { get; private set; } - - #endregion - - #region Public Methods - - /// - /// Prompts the user to make a choice using the provided details. - /// - /// - /// The caption string which will be displayed to the user. - /// - /// - /// The descriptive message which will be displayed to the user. - /// - /// - /// The list of choices from which the user will select. - /// - /// - /// The default choice to highlight for the user. - /// - /// - /// A CancellationToken that can be used to cancel the prompt. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's choice. - /// - public Task PromptForChoiceAsync( - string promptCaption, - string promptMessage, - ChoiceDetails[] choices, - int defaultChoice, - CancellationToken cancellationToken) - { - // TODO: Guard against multiple calls - - this.Caption = promptCaption; - this.Message = promptMessage; - this.Choices = choices; - - this.DefaultChoices = - defaultChoice == -1 - ? Array.Empty() - : new int[] { defaultChoice }; - - // Cancel the TaskCompletionSource if the caller cancels the task - cancellationToken.Register(this.CancelPrompt, true); - - // Convert the int[] result to int - return this.WaitForTaskAsync( - this.StartPromptLoopAsync(this.promptCancellationTokenSource.Token) - .ContinueWith( - task => - { - if (task.IsFaulted) - { - throw task.Exception; - } - else if (task.IsCanceled) - { - throw new TaskCanceledException(task); - } - - return ChoicePromptHandler.GetSingleResult(task.GetAwaiter().GetResult()); - })); - } - - /// - /// Prompts the user to make a choice of one or more options using the - /// provided details. - /// - /// - /// The caption string which will be displayed to the user. - /// - /// - /// The descriptive message which will be displayed to the user. - /// - /// - /// The list of choices from which the user will select. - /// - /// - /// The default choice(s) to highlight for the user. - /// - /// - /// A CancellationToken that can be used to cancel the prompt. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's choices. - /// - public Task PromptForChoiceAsync( - string promptCaption, - string promptMessage, - ChoiceDetails[] choices, - int[] defaultChoices, - CancellationToken cancellationToken) - { - // TODO: Guard against multiple calls - - this.Caption = promptCaption; - this.Message = promptMessage; - this.Choices = choices; - this.DefaultChoices = defaultChoices; - this.IsMultiChoice = true; - - // Cancel the TaskCompletionSource if the caller cancels the task - cancellationToken.Register(this.CancelPrompt, true); - - return this.WaitForTaskAsync( - this.StartPromptLoopAsync( - this.promptCancellationTokenSource.Token)); - } - - private async Task WaitForTaskAsync(Task taskToWait) - { - _ = await Task.WhenAny(cancelTask.Task, taskToWait).ConfigureAwait(false); - - if (this.cancelTask.Task.IsCanceled) - { - throw new PipelineStoppedException(); - } - - return await taskToWait.ConfigureAwait(false); - } - - private async Task StartPromptLoopAsync( - CancellationToken cancellationToken) - { - int[] choiceIndexes = null; - - // Show the prompt to the user - this.ShowPrompt(PromptStyle.Full); - - while (!cancellationToken.IsCancellationRequested) - { - string responseString = await ReadInputStringAsync(cancellationToken).ConfigureAwait(false); - if (responseString == null) - { - // If the response string is null, the prompt has been cancelled - break; - } - - choiceIndexes = this.HandleResponse(responseString); - - // Return the default choice values if no choices were entered - if (choiceIndexes == null && string.IsNullOrEmpty(responseString)) - { - choiceIndexes = this.DefaultChoices; - } - - // If the user provided no choices, we should prompt again - if (choiceIndexes != null) - { - break; - } - - // The user did not respond with a valid choice, - // show the prompt again to give another chance - this.ShowPrompt(PromptStyle.Minimal); - } - - if (cancellationToken.IsCancellationRequested) - { - // Throw a TaskCanceledException to stop the pipeline - throw new TaskCanceledException(); - } - - return choiceIndexes?.ToArray(); - } - - /// - /// Implements behavior to handle the user's response. - /// - /// The string representing the user's response. - /// - /// True if the prompt is complete, false if the prompt is - /// still waiting for a valid response. - /// - protected virtual int[] HandleResponse(string responseString) - { - List choiceIndexes = new List(); - - // Clean up the response string and split it - var choiceStrings = - responseString.Trim().Split( - new char[] { ',' }, - StringSplitOptions.RemoveEmptyEntries); - - foreach (string choiceString in choiceStrings) - { - for (int i = 0; i < this.Choices.Length; i++) - { - if (this.Choices[i].MatchesInput(choiceString)) - { - choiceIndexes.Add(i); - - // If this is a single-choice prompt, break out after - // the first matched choice - if (!this.IsMultiChoice) - { - break; - } - } - } - } - - if (choiceIndexes.Count == 0) - { - // The user did not respond with a valid choice, - // show the prompt again to give another chance - return null; - } - - return choiceIndexes.ToArray(); - } - - /// - /// Called when the active prompt should be cancelled. - /// - protected override void OnPromptCancelled() - { - // Cancel the prompt task - this.promptCancellationTokenSource.Cancel(); - this.cancelTask.TrySetCanceled(); - } - - #endregion - - #region Abstract Methods - - /// - /// Called when the prompt should be displayed to the user. - /// - /// - /// Indicates the prompt style to use when showing the prompt. - /// - protected abstract void ShowPrompt(PromptStyle promptStyle); - - /// - /// Reads an input string asynchronously from the console. - /// - /// - /// A CancellationToken that can be used to cancel the read. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's input. - /// - protected abstract Task ReadInputStringAsync(CancellationToken cancellationToken); - - #endregion - - #region Private Methods - - private static int GetSingleResult(int[] choiceArray) - { - return - choiceArray != null - ? choiceArray.DefaultIfEmpty(-1).First() - : -1; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/CollectionFieldDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/CollectionFieldDetails.cs deleted file mode 100644 index 03a3dff31..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/CollectionFieldDetails.cs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Contains the details of an colleciton input field shown - /// from an InputPromptHandler. This class is meant to be - /// serializable to the user's UI. - /// - internal class CollectionFieldDetails : FieldDetails - { - #region Private Fields - - private bool isArray; - private bool isEntryComplete; - private string fieldName; - private int currentCollectionIndex; - private ArrayList collectionItems = new ArrayList(); - - #endregion - - #region Constructors - - /// - /// Creates an instance of the CollectionFieldDetails class. - /// - /// The field's name. - /// The field's label. - /// The field's value type. - /// If true, marks the field as mandatory. - /// The field's default value. - public CollectionFieldDetails( - string name, - string label, - Type fieldType, - bool isMandatory, - object defaultValue) - : base(name, label, fieldType, isMandatory, defaultValue) - { - this.fieldName = name; - - this.FieldType = typeof(object); - - if (fieldType.IsArray) - { - this.isArray = true; - this.FieldType = fieldType.GetElementType(); - } - - this.Name = - string.Format( - "{0}[{1}]", - this.fieldName, - this.currentCollectionIndex); - } - - #endregion - - #region Public Methods - - /// - /// Gets the next field to display if this is a complex - /// field, otherwise returns null. - /// - /// - /// A FieldDetails object if there's another field to - /// display or if this field is complete. - /// - public override FieldDetails GetNextField() - { - if (!this.isEntryComplete) - { - // Get the next collection field - this.currentCollectionIndex++; - this.Name = - string.Format( - "{0}[{1}]", - this.fieldName, - this.currentCollectionIndex); - - return this; - } - else - { - return null; - } - } - - /// - /// Sets the field's value. - /// - /// The field's value. - /// - /// True if a value has been supplied by the user, false if the user supplied no value. - /// - public override void SetValue(object fieldValue, bool hasValue) - { - if (hasValue) - { - // Add the item to the collection - this.collectionItems.Add(fieldValue); - } - else - { - this.isEntryComplete = true; - } - } - - /// - /// Gets the field's final value after the prompt is - /// complete. - /// - /// The field's final value. - protected override object OnGetValue() - { - object collection = this.collectionItems; - - // Should the result collection be an array? - if (this.isArray) - { - // Convert the ArrayList to an array - collection = - this.collectionItems.ToArray( - this.FieldType); - } - - return collection; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleChoicePromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleChoicePromptHandler.cs deleted file mode 100644 index 905d81748..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleChoicePromptHandler.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Linq; -using Microsoft.Extensions.Logging; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides a standard implementation of ChoicePromptHandler - /// for use in the interactive console (REPL). - /// - internal abstract class ConsoleChoicePromptHandler : ChoicePromptHandler - { - #region Private Fields - - /// - /// The IHostOutput instance to use for this prompt. - /// - protected IHostOutput hostOutput; - - #endregion - - #region Constructors - - /// - /// Creates an instance of the ConsoleChoicePromptHandler class. - /// - /// - /// The IHostOutput implementation to use for writing to the - /// console. - /// - /// An ILogger implementation used for writing log messages. - public ConsoleChoicePromptHandler( - IHostOutput hostOutput, - ILogger logger) - : base(logger) - { - this.hostOutput = hostOutput; - } - - #endregion - - /// - /// Called when the prompt should be displayed to the user. - /// - /// - /// Indicates the prompt style to use when showing the prompt. - /// - protected override void ShowPrompt(PromptStyle promptStyle) - { - if (promptStyle == PromptStyle.Full) - { - if (this.Caption != null) - { - this.hostOutput.WriteOutput(this.Caption); - } - - if (this.Message != null) - { - this.hostOutput.WriteOutput(this.Message); - } - } - - foreach (var choice in this.Choices) - { - string hotKeyString = - choice.HotKeyIndex > -1 ? - choice.Label[choice.HotKeyIndex].ToString().ToUpper() : - string.Empty; - - this.hostOutput.WriteOutput( - string.Format( - "[{0}] {1} ", - hotKeyString, - choice.Label), - false); - } - - this.hostOutput.WriteOutput("[?] Help", false); - - var validDefaultChoices = - this.DefaultChoices.Where( - choice => choice > -1 && choice < this.Choices.Length); - - if (validDefaultChoices.Any()) - { - var choiceString = - string.Join( - ", ", - this.DefaultChoices - .Select(choice => this.Choices[choice].Label)); - - this.hostOutput.WriteOutput( - $" (default is \"{choiceString}\"): ", - false); - } - } - - - /// - /// Implements behavior to handle the user's response. - /// - /// The string representing the user's response. - /// - /// True if the prompt is complete, false if the prompt is - /// still waiting for a valid response. - /// - protected override int[] HandleResponse(string responseString) - { - if (responseString.Trim() == "?") - { - // Print help text - foreach (var choice in this.Choices) - { - this.hostOutput.WriteOutput( - string.Format( - "{0} - {1}", - (choice.HotKeyCharacter.HasValue ? - choice.HotKeyCharacter.Value.ToString() : - choice.Label), - choice.HelpMessage)); - } - - return null; - } - - return base.HandleResponse(responseString); - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleInputPromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleInputPromptHandler.cs deleted file mode 100644 index 0897cc10f..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleInputPromptHandler.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using Microsoft.Extensions.Logging; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides a standard implementation of InputPromptHandler - /// for use in the interactive console (REPL). - /// - internal abstract class ConsoleInputPromptHandler : InputPromptHandler - { - #region Private Fields - - /// - /// The IHostOutput instance to use for this prompt. - /// - protected IHostOutput hostOutput; - - #endregion - - #region Constructors - - /// - /// Creates an instance of the ConsoleInputPromptHandler class. - /// - /// - /// The IHostOutput implementation to use for writing to the - /// console. - /// - /// An ILogger implementation used for writing log messages. - public ConsoleInputPromptHandler( - IHostOutput hostOutput, - ILogger logger) - : base(logger) - { - this.hostOutput = hostOutput; - } - - #endregion - - #region Public Methods - - /// - /// Called when the prompt caption and message should be - /// displayed to the user. - /// - /// The caption string to be displayed. - /// The message string to be displayed. - protected override void ShowPromptMessage(string caption, string message) - { - if (!string.IsNullOrEmpty(caption)) - { - this.hostOutput.WriteOutput(caption, true); - } - - if (!string.IsNullOrEmpty(message)) - { - this.hostOutput.WriteOutput(message, true); - } - } - - /// - /// Called when a prompt should be displayed for a specific - /// input field. - /// - /// The details of the field to be displayed. - protected override void ShowFieldPrompt(FieldDetails fieldDetails) - { - // For a simple prompt there won't be any field name. - // In this case don't write anything - if (!string.IsNullOrEmpty(fieldDetails.Name)) - { - this.hostOutput.WriteOutput( - fieldDetails.Name + ": ", - false); - } - } - - /// - /// Called when an error should be displayed, such as when the - /// user types in a string with an incorrect format for the - /// current field. - /// - /// - /// The Exception containing the error to be displayed. - /// - protected override void ShowErrorMessage(Exception e) - { - this.hostOutput.WriteOutput( - e.Message, - true, - OutputType.Error); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleProxy.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleProxy.cs deleted file mode 100644 index 234d5b4f3..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleProxy.cs +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides asynchronous implementations of the API's as well as - /// synchronous implementations that work around platform specific issues. - /// - internal static class ConsoleProxy - { - private static IConsoleOperations s_consoleProxy; - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline", Justification = "Platform specific initialization")] - static ConsoleProxy() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - s_consoleProxy = new WindowsConsoleOperations(); - return; - } - - s_consoleProxy = new UnixConsoleOperations(); - } - - /// - /// Obtains the next character or function key pressed by the user asynchronously. - /// Does not block when other console API's are called. - /// - /// - /// Determines whether to display the pressed key in the console window. - /// to not display the pressed key; otherwise, . - /// - /// The CancellationToken to observe. - /// - /// An object that describes the constant and Unicode character, if any, - /// that correspond to the pressed console key. The object also - /// describes, in a bitwise combination of values, whether - /// one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously with the console key. - /// - public static ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken) => - s_consoleProxy.ReadKey(intercept, cancellationToken); - - /// - /// Obtains the next character or function key pressed by the user asynchronously. - /// Does not block when other console API's are called. - /// - /// - /// Determines whether to display the pressed key in the console window. - /// to not display the pressed key; otherwise, . - /// - /// The CancellationToken to observe. - /// - /// A task that will complete with a result of the key pressed by the user. - /// - public static Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken) => - s_consoleProxy.ReadKeyAsync(intercept, cancellationToken); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The horizontal position of the console cursor. - public static int GetCursorLeft() => - s_consoleProxy.GetCursorLeft(); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// The horizontal position of the console cursor. - public static int GetCursorLeft(CancellationToken cancellationToken) => - s_consoleProxy.GetCursorLeft(cancellationToken); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// - /// A representing the asynchronous operation. The - /// property will return the horizontal position - /// of the console cursor. - /// - public static Task GetCursorLeftAsync() => - s_consoleProxy.GetCursorLeftAsync(); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// - /// A representing the asynchronous operation. The - /// property will return the horizontal position - /// of the console cursor. - /// - public static Task GetCursorLeftAsync(CancellationToken cancellationToken) => - s_consoleProxy.GetCursorLeftAsync(cancellationToken); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The vertical position of the console cursor. - public static int GetCursorTop() => - s_consoleProxy.GetCursorTop(); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// The vertical position of the console cursor. - public static int GetCursorTop(CancellationToken cancellationToken) => - s_consoleProxy.GetCursorTop(cancellationToken); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// - /// A representing the asynchronous operation. The - /// property will return the vertical position - /// of the console cursor. - /// - public static Task GetCursorTopAsync() => - s_consoleProxy.GetCursorTopAsync(); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// - /// A representing the asynchronous operation. The - /// property will return the vertical position - /// of the console cursor. - /// - public static Task GetCursorTopAsync(CancellationToken cancellationToken) => - s_consoleProxy.GetCursorTopAsync(cancellationToken); - - /// - /// This method is sent to PSReadLine as a workaround for issues with the System.Console - /// implementation. Functionally it is the same as System.Console.ReadKey, - /// with the exception that it will not lock the standard input stream. - /// - /// - /// Determines whether to display the pressed key in the console window. - /// true to not display the pressed key; otherwise, false. - /// - /// - /// The that can be used to cancel the request. - /// - /// - /// An object that describes the ConsoleKey constant and Unicode character, if any, - /// that correspond to the pressed console key. The ConsoleKeyInfo object also describes, - /// in a bitwise combination of ConsoleModifiers values, whether one or more Shift, Alt, - /// or Ctrl modifier keys was pressed simultaneously with the console key. - /// - internal static ConsoleKeyInfo SafeReadKey(bool intercept, CancellationToken cancellationToken) - { - try - { - return s_consoleProxy.ReadKey(intercept, cancellationToken); - } - catch (OperationCanceledException) - { - return new ConsoleKeyInfo( - keyChar: ' ', - ConsoleKey.DownArrow, - shift: false, - alt: false, - control: false); - } - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleReadLine.cs deleted file mode 100644 index 145cf7360..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleReadLine.cs +++ /dev/null @@ -1,616 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - using System; - using System.Management.Automation; - using System.Management.Automation.Language; - using System.Security; - - internal class ConsoleReadLine - { - #region Private Field - private readonly PowerShellContextService powerShellContext; - - #endregion - - #region Constructors - - public ConsoleReadLine(PowerShellContextService powerShellContext) - { - this.powerShellContext = powerShellContext; - } - - #endregion - - #region Public Methods - - public Task ReadCommandLineAsync(CancellationToken cancellationToken) - { - return this.ReadLineAsync(true, cancellationToken); - } - - public Task ReadSimpleLineAsync(CancellationToken cancellationToken) - { - return this.ReadLineAsync(false, cancellationToken); - } - - public async static Task ReadSecureLineAsync(CancellationToken cancellationToken) - { - SecureString secureString = new SecureString(); - - // TODO: Are these values used? - int initialPromptRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false); - int initialPromptCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken).ConfigureAwait(false); - int previousInputLength = 0; - - Console.TreatControlCAsInput = true; - - try - { - while (!cancellationToken.IsCancellationRequested) - { - ConsoleKeyInfo keyInfo = await ReadKeyAsync(cancellationToken).ConfigureAwait(false); - - if ((int)keyInfo.Key == 3 || - keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) - { - throw new PipelineStoppedException(); - } - if (keyInfo.Key == ConsoleKey.Enter) - { - // Break to return the completed string - break; - } - if (keyInfo.Key == ConsoleKey.Tab) - { - continue; - } - if (keyInfo.Key == ConsoleKey.Backspace) - { - if (secureString.Length > 0) - { - secureString.RemoveAt(secureString.Length - 1); - } - } - else if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar)) - { - secureString.AppendChar(keyInfo.KeyChar); - } - - // Re-render the secure string characters - int currentInputLength = secureString.Length; - int consoleWidth = Console.WindowWidth; - - if (currentInputLength > previousInputLength) - { - Console.Write('*'); - } - else if (previousInputLength > 0 && currentInputLength < previousInputLength) - { - int row = await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false); - int col = await ConsoleProxy.GetCursorLeftAsync(cancellationToken).ConfigureAwait(false); - - // Back up the cursor before clearing the character - col--; - if (col < 0) - { - col = consoleWidth - 1; - row--; - } - - Console.SetCursorPosition(col, row); - Console.Write(' '); - Console.SetCursorPosition(col, row); - } - - previousInputLength = currentInputLength; - } - } - finally - { - Console.TreatControlCAsInput = false; - } - - return secureString; - } - - #endregion - - #region Private Methods - - private static Task ReadKeyAsync(CancellationToken cancellationToken) - { - return ConsoleProxy.ReadKeyAsync(intercept: true, cancellationToken); - } - - private Task ReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) - { - return this.powerShellContext.InvokeReadLineAsync(isCommandLine, cancellationToken); - } - - /// - /// Invokes a custom ReadLine method that is similar to but more basic than PSReadLine. - /// This method should be used when PSReadLine is disabled, either by user settings or - /// unsupported PowerShell versions. - /// - /// - /// Indicates whether ReadLine should act like a command line. - /// - /// - /// The cancellation token that will be checked prior to completing the returned task. - /// - /// - /// A task object representing the asynchronus operation. The Result property on - /// the task object returns the user input string. - /// - internal async Task InvokeLegacyReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) - { - // TODO: Is inputBeforeCompletion used? - string inputBeforeCompletion = null; - string inputAfterCompletion = null; - CommandCompletion currentCompletion = null; - - int historyIndex = -1; - Collection currentHistory = null; - - StringBuilder inputLine = new StringBuilder(); - - int initialCursorCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken).ConfigureAwait(false); - int initialCursorRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false); - - // TODO: Are these used? - int initialWindowLeft = Console.WindowLeft; - int initialWindowTop = Console.WindowTop; - - int currentCursorIndex = 0; - - Console.TreatControlCAsInput = true; - - try - { - while (!cancellationToken.IsCancellationRequested) - { - ConsoleKeyInfo keyInfo = await ReadKeyAsync(cancellationToken).ConfigureAwait(false); - - // Do final position calculation after the key has been pressed - // because the window could have been resized before then - int promptStartCol = initialCursorCol; - int promptStartRow = initialCursorRow; - int consoleWidth = Console.WindowWidth; - - if ((int)keyInfo.Key == 3 || - keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) - { - throw new PipelineStoppedException(); - } - else if (keyInfo.Key == ConsoleKey.Tab && isCommandLine) - { - if (currentCompletion == null) - { - inputBeforeCompletion = inputLine.ToString(); - inputAfterCompletion = null; - - // TODO: This logic should be moved to AstOperations or similar! - - if (this.powerShellContext.IsDebuggerStopped) - { - PSCommand command = new PSCommand(); - command.AddCommand("TabExpansion2"); - command.AddParameter("InputScript", inputBeforeCompletion); - command.AddParameter("CursorColumn", currentCursorIndex); - command.AddParameter("Options", null); - - var results = await this.powerShellContext.ExecuteCommandAsync( - command, sendOutputToHost: false, sendErrorToHost: false, cancellationToken).ConfigureAwait(false); - - currentCompletion = results.FirstOrDefault(); - } - else - { - using (RunspaceHandle runspaceHandle = await this.powerShellContext.GetRunspaceHandleAsync(cancellationToken) .ConfigureAwait(false)) - using (PowerShell powerShell = PowerShell.Create()) - { - powerShell.Runspace = runspaceHandle.Runspace; - currentCompletion = - CommandCompletion.CompleteInput( - inputBeforeCompletion, - currentCursorIndex, - null, - powerShell); - - if (currentCompletion.CompletionMatches.Count > 0) - { - int replacementEndIndex = - currentCompletion.ReplacementIndex + - currentCompletion.ReplacementLength; - - inputAfterCompletion = - inputLine.ToString( - replacementEndIndex, - inputLine.Length - replacementEndIndex); - } - else - { - currentCompletion = null; - } - } - } - } - - CompletionResult completion = - currentCompletion?.GetNextResult( - !keyInfo.Modifiers.HasFlag(ConsoleModifiers.Shift)); - - if (completion != null) - { - currentCursorIndex = - ConsoleReadLine.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - $"{completion.CompletionText}{inputAfterCompletion}", - currentCursorIndex, - insertIndex: currentCompletion.ReplacementIndex, - replaceLength: inputLine.Length - currentCompletion.ReplacementIndex, - finalCursorIndex: currentCompletion.ReplacementIndex + completion.CompletionText.Length); - } - } - else if (keyInfo.Key == ConsoleKey.LeftArrow) - { - currentCompletion = null; - - if (currentCursorIndex > 0) - { - currentCursorIndex = - ConsoleReadLine.MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - currentCursorIndex - 1); - } - } - else if (keyInfo.Key == ConsoleKey.Home) - { - currentCompletion = null; - - currentCursorIndex = - ConsoleReadLine.MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - 0); - } - else if (keyInfo.Key == ConsoleKey.RightArrow) - { - currentCompletion = null; - - if (currentCursorIndex < inputLine.Length) - { - currentCursorIndex = - ConsoleReadLine.MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - currentCursorIndex + 1); - } - } - else if (keyInfo.Key == ConsoleKey.End) - { - currentCompletion = null; - - currentCursorIndex = - ConsoleReadLine.MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - inputLine.Length); - } - else if (keyInfo.Key == ConsoleKey.UpArrow && isCommandLine) - { - currentCompletion = null; - - // TODO: Ctrl+Up should allow navigation in multi-line input - - if (currentHistory == null) - { - historyIndex = -1; - - PSCommand command = new PSCommand(); - command.AddCommand("Get-History"); - - currentHistory = await this.powerShellContext.ExecuteCommandAsync( - command, sendOutputToHost: false, sendErrorToHost: false, cancellationToken).ConfigureAwait(false) - as Collection; - - if (currentHistory != null) - { - historyIndex = currentHistory.Count; - } - } - - if (currentHistory != null && currentHistory.Count > 0 && historyIndex > 0) - { - historyIndex--; - - currentCursorIndex = - ConsoleReadLine.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - (string)currentHistory[historyIndex].Properties["CommandLine"].Value, - currentCursorIndex, - insertIndex: 0, - replaceLength: inputLine.Length); - } - } - else if (keyInfo.Key == ConsoleKey.DownArrow && isCommandLine) - { - currentCompletion = null; - - // The down arrow shouldn't cause history to be loaded, - // it's only for navigating an active history array - - if (historyIndex > -1 && historyIndex < currentHistory.Count && - currentHistory != null && currentHistory.Count > 0) - { - historyIndex++; - - if (historyIndex < currentHistory.Count) - { - currentCursorIndex = - ConsoleReadLine.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - (string)currentHistory[historyIndex].Properties["CommandLine"].Value, - currentCursorIndex, - insertIndex: 0, - replaceLength: inputLine.Length); - } - else if (historyIndex == currentHistory.Count) - { - currentCursorIndex = - ConsoleReadLine.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - string.Empty, - currentCursorIndex, - insertIndex: 0, - replaceLength: inputLine.Length); - } - } - } - else if (keyInfo.Key == ConsoleKey.Escape) - { - currentCompletion = null; - historyIndex = currentHistory != null ? currentHistory.Count : -1; - - currentCursorIndex = - ConsoleReadLine.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - string.Empty, - currentCursorIndex, - insertIndex: 0, - replaceLength: inputLine.Length); - } - else if (keyInfo.Key == ConsoleKey.Backspace) - { - currentCompletion = null; - - if (currentCursorIndex > 0) - { - currentCursorIndex = - ConsoleReadLine.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - string.Empty, - currentCursorIndex, - insertIndex: currentCursorIndex - 1, - replaceLength: 1, - finalCursorIndex: currentCursorIndex - 1); - } - } - else if (keyInfo.Key == ConsoleKey.Delete) - { - currentCompletion = null; - - if (currentCursorIndex < inputLine.Length) - { - currentCursorIndex = - ConsoleReadLine.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - string.Empty, - currentCursorIndex, - replaceLength: 1, - finalCursorIndex: currentCursorIndex); - } - } - else if (keyInfo.Key == ConsoleKey.Enter) - { - string completedInput = inputLine.ToString(); - currentCompletion = null; - currentHistory = null; - - //if ((keyInfo.Modifiers & ConsoleModifiers.Shift) == ConsoleModifiers.Shift) - //{ - // // TODO: Start a new line! - // continue; - //} - - Parser.ParseInput( - completedInput, - out Token[] tokens, - out ParseError[] parseErrors); - - //if (parseErrors.Any(e => e.IncompleteInput)) - //{ - // // TODO: Start a new line! - // continue; - //} - - return completedInput; - } - else if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar)) - { - // Normal character input - currentCompletion = null; - - currentCursorIndex = - ConsoleReadLine.InsertInput( - inputLine, - promptStartCol, - promptStartRow, - keyInfo.KeyChar.ToString(), // TODO: Determine whether this should take culture into account - currentCursorIndex, - finalCursorIndex: currentCursorIndex + 1); - } - } - } - finally - { - Console.TreatControlCAsInput = false; - } - - return null; - } - - // TODO: Is this used? - private static int CalculateIndexFromCursor( - int promptStartCol, - int promptStartRow, - int consoleWidth) - { - return - ((ConsoleProxy.GetCursorTop() - promptStartRow) * consoleWidth) + - ConsoleProxy.GetCursorLeft() - promptStartCol; - } - - private static void CalculateCursorFromIndex( - int promptStartCol, - int promptStartRow, - int consoleWidth, - int inputIndex, - out int cursorCol, - out int cursorRow) - { - cursorCol = promptStartCol + inputIndex; - cursorRow = promptStartRow + cursorCol / consoleWidth; - cursorCol = cursorCol % consoleWidth; - } - - private static int InsertInput( - StringBuilder inputLine, - int promptStartCol, - int promptStartRow, - string insertedInput, - int cursorIndex, - int insertIndex = -1, - int replaceLength = 0, - int finalCursorIndex = -1) - { - int consoleWidth = Console.WindowWidth; - int previousInputLength = inputLine.Length; - - if (insertIndex == -1) - { - insertIndex = cursorIndex; - } - - // Move the cursor to the new insertion point - ConsoleReadLine.MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - insertIndex); - - // Edit the input string based on the insertion - if (insertIndex < inputLine.Length) - { - if (replaceLength > 0) - { - inputLine.Remove(insertIndex, replaceLength); - } - - inputLine.Insert(insertIndex, insertedInput); - } - else - { - inputLine.Append(insertedInput); - } - - // Re-render affected section - Console.Write( - inputLine.ToString( - insertIndex, - inputLine.Length - insertIndex)); - - if (inputLine.Length < previousInputLength) - { - Console.Write( - new string( - ' ', - previousInputLength - inputLine.Length)); - } - - // Automatically set the final cursor position to the end - // of the new input string. This is needed if the previous - // input string is longer than the new one and needed to have - // its old contents overwritten. This will position the cursor - // back at the end of the new text - if (finalCursorIndex == -1 && inputLine.Length < previousInputLength) - { - finalCursorIndex = inputLine.Length; - } - - if (finalCursorIndex > -1) - { - // Move the cursor to the final position - return - ConsoleReadLine.MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - finalCursorIndex); - } - else - { - return inputLine.Length; - } - } - - private static int MoveCursorToIndex( - int promptStartCol, - int promptStartRow, - int consoleWidth, - int newCursorIndex) - { - ConsoleReadLine.CalculateCursorFromIndex( - promptStartCol, - promptStartRow, - consoleWidth, - newCursorIndex, - out int newCursorCol, - out int newCursorRow); - - Console.SetCursorPosition(newCursorCol, newCursorRow); - - return newCursorIndex; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/CredentialFieldDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/CredentialFieldDetails.cs deleted file mode 100644 index 6fa605f10..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/CredentialFieldDetails.cs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Management.Automation; -using System.Security; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Contains the details of a PSCredential field shown - /// from an InputPromptHandler. This class is meant to - /// be serializable to the user's UI. - /// - internal class CredentialFieldDetails : FieldDetails - { - private string userName; - private SecureString password; - - /// - /// Creates an instance of the CredentialFieldDetails class. - /// - /// The field's name. - /// The field's label. - /// The initial value of the userName field. - public CredentialFieldDetails( - string name, - string label, - string userName) - : this(name, label, typeof(PSCredential), true, null) - { - if (!string.IsNullOrEmpty(userName)) - { - // Call GetNextField to prepare the password field - this.userName = userName; - this.GetNextField(); - } - } - - /// - /// Creates an instance of the CredentialFieldDetails class. - /// - /// The field's name. - /// The field's label. - /// The field's value type. - /// If true, marks the field as mandatory. - /// The field's default value. - public CredentialFieldDetails( - string name, - string label, - Type fieldType, - bool isMandatory, - object defaultValue) - : base(name, label, fieldType, isMandatory, defaultValue) - { - this.Name = "User"; - this.FieldType = typeof(string); - } - - #region Public Methods - - /// - /// Gets the next field to display if this is a complex - /// field, otherwise returns null. - /// - /// - /// A FieldDetails object if there's another field to - /// display or if this field is complete. - /// - public override FieldDetails GetNextField() - { - if (this.password != null) - { - // No more fields to display - return null; - } - else if (this.userName != null) - { - this.Name = $"Password for user {this.userName}"; - this.FieldType = typeof(SecureString); - } - - return this; - } - - /// - /// Sets the field's value. - /// - /// The field's value. - /// - /// True if a value has been supplied by the user, false if the user supplied no value. - /// - public override void SetValue(object fieldValue, bool hasValue) - { - if (hasValue) - { - if (this.userName == null) - { - this.userName = (string)fieldValue; - } - else - { - this.password = (SecureString)fieldValue; - } - } - } - - /// - /// Gets the field's final value after the prompt is - /// complete. - /// - /// The field's final value. - protected override object OnGetValue() - { - return new PSCredential(this.userName, this.password); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/FieldDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/FieldDetails.cs deleted file mode 100644 index f11a9fbd8..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/FieldDetails.cs +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Reflection; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Contains the details of an input field shown from an - /// InputPromptHandler. This class is meant to be - /// serializable to the user's UI. - /// - internal class FieldDetails - { - #region Private Fields - - private object fieldValue; - - #endregion - - #region Properties - - /// - /// Gets or sets the name of the field. - /// - public string Name { get; set; } - - /// - /// Gets or sets the original name of the field before it was manipulated. - /// - public string OriginalName { get; set; } - - /// - /// Gets or sets the descriptive label for the field. - /// - public string Label { get; set; } - - /// - /// Gets or sets the field's value type. - /// - public Type FieldType { get; set; } - - /// - /// Gets or sets the field's help message. - /// - public string HelpMessage { get; set; } - - /// - /// Gets or sets a boolean that is true if the user - /// must enter a value for the field. - /// - public bool IsMandatory { get; set; } - - /// - /// Gets or sets the default value for the field. - /// - public object DefaultValue { get; set; } - - #endregion - - #region Constructors - - /// - /// Creates an instance of the FieldDetails class. - /// - /// The field's name. - /// The field's label. - /// The field's value type. - /// If true, marks the field as mandatory. - /// The field's default value. - public FieldDetails( - string name, - string label, - Type fieldType, - bool isMandatory, - object defaultValue) - { - this.OriginalName = name; - this.Name = name; - this.Label = label; - this.FieldType = fieldType; - this.IsMandatory = isMandatory; - this.DefaultValue = defaultValue; - - if (fieldType.GetTypeInfo().IsGenericType) - { - throw new PSArgumentException( - "Generic types are not supported for input fields at this time."); - } - } - - #endregion - - #region Public Methods - - /// - /// Sets the field's value. - /// - /// The field's value. - /// - /// True if a value has been supplied by the user, false if the user supplied no value. - /// - public virtual void SetValue(object fieldValue, bool hasValue) - { - if (hasValue) - { - this.fieldValue = fieldValue; - } - } - - /// - /// Gets the field's final value after the prompt is - /// complete. - /// - /// The field's final value. - public object GetValue(ILogger logger) - { - object fieldValue = this.OnGetValue(); - - if (fieldValue == null) - { - if (!this.IsMandatory) - { - fieldValue = this.DefaultValue; - } - else - { - // This "shoudln't" happen, so log in case it does - logger.LogError( - $"Cannot retrieve value for field {this.Label}"); - } - } - - return fieldValue; - } - - /// - /// Gets the field's final value after the prompt is - /// complete. - /// - /// The field's final value. - protected virtual object OnGetValue() - { - return this.fieldValue; - } - - /// - /// Gets the next field if this field can accept multiple - /// values, like a collection or an object with multiple - /// properties. - /// - /// - /// A new FieldDetails instance if there is a next field - /// or null otherwise. - /// - public virtual FieldDetails GetNextField() - { - return null; - } - - #endregion - - #region Internal Methods - - internal static FieldDetails Create( - FieldDescription fieldDescription, - ILogger logger) - { - Type fieldType = - GetFieldTypeFromTypeName( - fieldDescription.ParameterAssemblyFullName, - logger); - - if (typeof(IList).GetTypeInfo().IsAssignableFrom(fieldType.GetTypeInfo())) - { - return new CollectionFieldDetails( - fieldDescription.Name, - fieldDescription.Label, - fieldType, - fieldDescription.IsMandatory, - fieldDescription.DefaultValue); - } - else if (typeof(PSCredential) == fieldType) - { - return new CredentialFieldDetails( - fieldDescription.Name, - fieldDescription.Label, - fieldType, - fieldDescription.IsMandatory, - fieldDescription.DefaultValue); - } - else - { - return new FieldDetails( - fieldDescription.Name, - fieldDescription.Label, - fieldType, - fieldDescription.IsMandatory, - fieldDescription.DefaultValue); - } - } - - private static Type GetFieldTypeFromTypeName( - string assemblyFullName, - ILogger logger) - { - Type fieldType = typeof(string); - - if (!string.IsNullOrEmpty(assemblyFullName)) - { - if (!LanguagePrimitives.TryConvertTo(assemblyFullName, out fieldType)) - { - logger.LogWarning( - string.Format( - "Could not resolve type of field: {0}", - assemblyFullName)); - } - } - - return fieldType; - } - - #endregion - } -} - diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/IConsoleOperations.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/IConsoleOperations.cs deleted file mode 100644 index f2d431692..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/IConsoleOperations.cs +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides platform specific console utilities. - /// - internal interface IConsoleOperations - { - /// - /// Obtains the next character or function key pressed by the user asynchronously. - /// Does not block when other console API's are called. - /// - /// - /// Determines whether to display the pressed key in the console window. - /// to not display the pressed key; otherwise, . - /// - /// The CancellationToken to observe. - /// - /// An object that describes the constant and Unicode character, if any, - /// that correspond to the pressed console key. The object also - /// describes, in a bitwise combination of values, whether - /// one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously with the console key. - /// - ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken); - - /// - /// Obtains the next character or function key pressed by the user asynchronously. - /// Does not block when other console API's are called. - /// - /// - /// Determines whether to display the pressed key in the console window. - /// to not display the pressed key; otherwise, . - /// - /// The CancellationToken to observe. - /// - /// A task that will complete with a result of the key pressed by the user. - /// - Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The horizontal position of the console cursor. - int GetCursorLeft(); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// The horizontal position of the console cursor. - int GetCursorLeft(CancellationToken cancellationToken); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// - /// A representing the asynchronous operation. The - /// property will return the horizontal position - /// of the console cursor. - /// - Task GetCursorLeftAsync(); - - /// - /// Obtains the horizontal position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// - /// A representing the asynchronous operation. The - /// property will return the horizontal position - /// of the console cursor. - /// - Task GetCursorLeftAsync(CancellationToken cancellationToken); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The vertical position of the console cursor. - int GetCursorTop(); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// The vertical position of the console cursor. - int GetCursorTop(CancellationToken cancellationToken); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// - /// A representing the asynchronous operation. The - /// property will return the vertical position - /// of the console cursor. - /// - Task GetCursorTopAsync(); - - /// - /// Obtains the vertical position of the console cursor. Use this method - /// instead of to avoid triggering - /// pending calls to - /// on Unix platforms. - /// - /// The to observe. - /// - /// A representing the asynchronous operation. The - /// property will return the vertical position - /// of the console cursor. - /// - Task GetCursorTopAsync(CancellationToken cancellationToken); - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/InputPromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/InputPromptHandler.cs deleted file mode 100644 index 29e8a0ec2..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/InputPromptHandler.cs +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Management.Automation; -using System.Security; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides a base implementation for IPromptHandler classes - /// that present the user a set of fields for which values - /// should be entered. - /// - internal abstract class InputPromptHandler : PromptHandler - { - #region Private Fields - - private int currentFieldIndex = -1; - private FieldDetails currentField; - private CancellationTokenSource promptCancellationTokenSource = - new CancellationTokenSource(); - private TaskCompletionSource> cancelTask = - new TaskCompletionSource>(); - - #endregion - - /// - /// - /// - /// An ILogger implementation used for writing log messages. - public InputPromptHandler(ILogger logger) : base(logger) - { - } - - #region Properties - - /// - /// Gets the array of fields for which the user must enter values. - /// - protected FieldDetails[] Fields { get; private set; } - - #endregion - - #region Public Methods - - /// - /// Prompts the user for a line of input without writing any message or caption. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's input. - /// - public Task PromptForInputAsync( - CancellationToken cancellationToken) - { - Task> innerTask = - this.PromptForInputAsync( - null, - null, - new FieldDetails[] { new FieldDetails("", "", typeof(string), false, "") }, - cancellationToken); - - return - innerTask.ContinueWith( - task => - { - if (task.IsFaulted) - { - throw task.Exception; - } - else if (task.IsCanceled) - { - throw new TaskCanceledException(task); - } - - // Return the value of the sole field - return (string)task.Result[""]; - }); - } - - /// - /// Prompts the user for a line (or lines) of input. - /// - /// - /// A title shown before the series of input fields. - /// - /// - /// A descritpive message shown before the series of input fields. - /// - /// - /// An array of FieldDetails items to be displayed which prompt the - /// user for input of a specific type. - /// - /// - /// A CancellationToken that can be used to cancel the prompt. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's input. - /// - public async Task> PromptForInputAsync( - string promptCaption, - string promptMessage, - FieldDetails[] fields, - CancellationToken cancellationToken) - { - // Cancel the prompt if the caller cancels the task - cancellationToken.Register(this.CancelPrompt, true); - - this.Fields = fields; - - this.ShowPromptMessage(promptCaption, promptMessage); - - Task> promptTask = - this.StartPromptLoopAsync(this.promptCancellationTokenSource.Token); - - _ = await Task.WhenAny(cancelTask.Task, promptTask).ConfigureAwait(false); - - if (this.cancelTask.Task.IsCanceled) - { - throw new PipelineStoppedException(); - } - - return promptTask.Result; - } - - /// - /// Prompts the user for a SecureString without writing any message or caption. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's input. - /// - public Task PromptForSecureInputAsync( - CancellationToken cancellationToken) - { - Task> innerTask = - this.PromptForInputAsync( - null, - null, - new FieldDetails[] { new FieldDetails("", "", typeof(SecureString), false, "") }, - cancellationToken); - - return - innerTask.ContinueWith( - task => - { - if (task.IsFaulted) - { - throw task.Exception; - } - else if (task.IsCanceled) - { - throw new TaskCanceledException(task); - } - - // Return the value of the sole field - return (SecureString)task.Result?[""]; - }); - } - - /// - /// Called when the active prompt should be cancelled. - /// - protected override void OnPromptCancelled() - { - // Cancel the prompt task - this.promptCancellationTokenSource.Cancel(); - this.cancelTask.TrySetCanceled(); - } - - #endregion - - #region Abstract Methods - - /// - /// Called when the prompt caption and message should be - /// displayed to the user. - /// - /// The caption string to be displayed. - /// The message string to be displayed. - protected abstract void ShowPromptMessage(string caption, string message); - - /// - /// Called when a prompt should be displayed for a specific - /// input field. - /// - /// The details of the field to be displayed. - protected abstract void ShowFieldPrompt(FieldDetails fieldDetails); - - /// - /// Reads an input string asynchronously from the console. - /// - /// - /// A CancellationToken that can be used to cancel the read. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's input. - /// - protected abstract Task ReadInputStringAsync(CancellationToken cancellationToken); - - /// - /// Reads a SecureString asynchronously from the console. - /// - /// - /// A CancellationToken that can be used to cancel the read. - /// - /// - /// A Task instance that can be monitored for completion to get - /// the user's input. - /// - protected abstract Task ReadSecureStringAsync(CancellationToken cancellationToken); - - /// - /// Called when an error should be displayed, such as when the - /// user types in a string with an incorrect format for the - /// current field. - /// - /// - /// The Exception containing the error to be displayed. - /// - protected abstract void ShowErrorMessage(Exception e); - - #endregion - - #region Private Methods - - private async Task> StartPromptLoopAsync( - CancellationToken cancellationToken) - { - this.GetNextField(); - - // Loop until there are no more prompts to process - while (this.currentField != null && !cancellationToken.IsCancellationRequested) - { - // Show current prompt - this.ShowFieldPrompt(this.currentField); - - bool enteredValue = false; - object responseValue = null; - string responseString = null; - - // Read input depending on field type - if (this.currentField.FieldType == typeof(SecureString)) - { - SecureString secureString = await this.ReadSecureStringAsync(cancellationToken).ConfigureAwait(false); - responseValue = secureString; - enteredValue = secureString != null; - } - else - { - responseString = await this.ReadInputStringAsync(cancellationToken).ConfigureAwait(false); - responseValue = responseString; - enteredValue = responseString != null && responseString.Length > 0; - - try - { - responseValue = - LanguagePrimitives.ConvertTo( - responseString, - this.currentField.FieldType, - CultureInfo.CurrentCulture); - } - catch (PSInvalidCastException e) - { - this.ShowErrorMessage(e.InnerException ?? e); - continue; - } - } - - // Set the field's value and get the next field - this.currentField.SetValue(responseValue, enteredValue); - this.GetNextField(); - } - - if (cancellationToken.IsCancellationRequested) - { - // Throw a TaskCanceledException to stop the pipeline - throw new TaskCanceledException(); - } - - // Return the field values - return this.GetFieldValues(); - } - - private FieldDetails GetNextField() - { - FieldDetails nextField = this.currentField?.GetNextField(); - - if (nextField == null) - { - this.currentFieldIndex++; - - // Have we shown all the prompts already? - if (this.currentFieldIndex < this.Fields.Length) - { - nextField = this.Fields[this.currentFieldIndex]; - } - } - - this.currentField = nextField; - return nextField; - } - - private Dictionary GetFieldValues() - { - Dictionary fieldValues = new Dictionary(); - - foreach (FieldDetails field in this.Fields) - { - fieldValues.Add(field.OriginalName, field.GetValue(this.Logger)); - } - - return fieldValues; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/PromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/PromptHandler.cs deleted file mode 100644 index 259a0c028..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/PromptHandler.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using Microsoft.Extensions.Logging; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Defines an abstract base class for prompt handler implementations. - /// - internal abstract class PromptHandler - { - /// - /// Gets the ILogger implementation used for this instance. - /// - protected ILogger Logger { get; private set; } - - /// - /// - /// - /// An ILogger implementation used for writing log messages. - public PromptHandler(ILogger logger) - { - this.Logger = logger; - } - - /// - /// Called when the active prompt should be cancelled. - /// - public void CancelPrompt() - { - // Allow the implementation to clean itself up - this.OnPromptCancelled(); - this.PromptCancelled?.Invoke(this, new EventArgs()); - } - - /// - /// An event that gets raised if the prompt is cancelled, either - /// by the user or due to a timeout. - /// - public event EventHandler PromptCancelled; - - /// - /// Implementation classes may override this method to perform - /// cleanup when the CancelPrompt method gets called. - /// - protected virtual void OnPromptCancelled() - { - } - } -} - diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/TerminalChoicePromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/TerminalChoicePromptHandler.cs deleted file mode 100644 index 19e2f7973..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/TerminalChoicePromptHandler.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides a standard implementation of ChoicePromptHandler - /// for use in the interactive console (REPL). - /// - internal class TerminalChoicePromptHandler : ConsoleChoicePromptHandler - { - #region Private Fields - - private ConsoleReadLine consoleReadLine; - - #endregion - - #region Constructors - - /// - /// Creates an instance of the ConsoleChoicePromptHandler class. - /// - /// - /// The ConsoleReadLine instance to use for interacting with the terminal. - /// - /// - /// The IHostOutput implementation to use for writing to the - /// console. - /// - /// An ILogger implementation used for writing log messages. - public TerminalChoicePromptHandler( - ConsoleReadLine consoleReadLine, - IHostOutput hostOutput, - ILogger logger) - : base(hostOutput, logger) - { - this.hostOutput = hostOutput; - this.consoleReadLine = consoleReadLine; - } - - #endregion - - /// - /// Reads an input string from the user. - /// - /// A CancellationToken that can be used to cancel the prompt. - /// A Task that can be awaited to get the user's response. - protected override async Task ReadInputStringAsync(CancellationToken cancellationToken) - { - string inputString = await this.consoleReadLine.ReadSimpleLineAsync(cancellationToken).ConfigureAwait(false); - this.hostOutput.WriteOutput(string.Empty); - - return inputString; - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/TerminalInputPromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/TerminalInputPromptHandler.cs deleted file mode 100644 index a9d07c03f..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/TerminalInputPromptHandler.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Security; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides a standard implementation of InputPromptHandler - /// for use in the interactive console (REPL). - /// - internal class TerminalInputPromptHandler : ConsoleInputPromptHandler - { - #region Private Fields - - private ConsoleReadLine consoleReadLine; - - #endregion - - #region Constructors - - /// - /// Creates an instance of the ConsoleInputPromptHandler class. - /// - /// - /// The ConsoleReadLine instance to use for interacting with the terminal. - /// - /// - /// The IHostOutput implementation to use for writing to the - /// console. - /// - /// An ILogger implementation used for writing log messages. - public TerminalInputPromptHandler( - ConsoleReadLine consoleReadLine, - IHostOutput hostOutput, - ILogger logger) - : base(hostOutput, logger) - { - this.consoleReadLine = consoleReadLine; - } - - #endregion - - #region Public Methods - - /// - /// Reads an input string from the user. - /// - /// A CancellationToken that can be used to cancel the prompt. - /// A Task that can be awaited to get the user's response. - protected override async Task ReadInputStringAsync(CancellationToken cancellationToken) - { - string inputString = await this.consoleReadLine.ReadSimpleLineAsync(cancellationToken).ConfigureAwait(false); - this.hostOutput.WriteOutput(string.Empty); - - return inputString; - } - - /// - /// Reads a SecureString from the user. - /// - /// A CancellationToken that can be used to cancel the prompt. - /// A Task that can be awaited to get the user's response. - protected override async Task ReadSecureStringAsync(CancellationToken cancellationToken) - { - SecureString secureString = await ConsoleReadLine.ReadSecureLineAsync(cancellationToken).ConfigureAwait(false); - this.hostOutput.WriteOutput(string.Empty); - - return secureString; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/UnixConsoleOperations.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/UnixConsoleOperations.cs deleted file mode 100644 index 18bb3063a..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/UnixConsoleOperations.cs +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; -using UnixConsoleEcho; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - internal class UnixConsoleOperations : IConsoleOperations - { - private const int LongWaitForKeySleepTime = 300; - - private const int ShortWaitForKeyTimeout = 5000; - - private const int ShortWaitForKeySpinUntilSleepTime = 30; - - private static readonly ManualResetEventSlim s_waitHandle = new ManualResetEventSlim(); - - private static readonly SemaphoreSlim s_readKeyHandle = AsyncUtils.CreateSimpleLockingSemaphore(); - - private static readonly SemaphoreSlim s_stdInHandle = AsyncUtils.CreateSimpleLockingSemaphore(); - - private Func WaitForKeyAvailable; - - private Func> WaitForKeyAvailableAsync; - - internal UnixConsoleOperations() - { - // Switch between long and short wait periods depending on if the - // user has recently (last 5 seconds) pressed a key to avoid preventing - // the CPU from entering low power mode. - WaitForKeyAvailable = LongWaitForKey; - WaitForKeyAvailableAsync = LongWaitForKeyAsync; - } - - public ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken) - { - s_readKeyHandle.Wait(cancellationToken); - - // On Unix platforms System.Console.ReadKey has an internal lock on stdin. Because - // of this, if a ReadKey call is pending in one thread and in another thread - // Console.CursorLeft is called, both threads block until a key is pressed. - - // To work around this we wait for a key to be pressed before actually calling Console.ReadKey. - // However, any pressed keys during this time will be echoed to the console. To get around - // this we use the UnixConsoleEcho package to disable echo prior to waiting. - if (VersionUtils.IsPS6) - { - InputEcho.Disable(); - } - - try - { - // The WaitForKeyAvailable delegate switches between a long delay between waits and - // a short timeout depending on how recently a key has been pressed. This allows us - // to let the CPU enter low power mode without compromising responsiveness. - while (!WaitForKeyAvailable(cancellationToken)); - } - finally - { - if (VersionUtils.IsPS6) - { - InputEcho.Disable(); - } - s_readKeyHandle.Release(); - } - - // A key has been pressed, so aquire a lock on our internal stdin handle. This is done - // so any of our calls to cursor position API's do not release ReadKey. - s_stdInHandle.Wait(cancellationToken); - try - { - return System.Console.ReadKey(intercept); - } - finally - { - s_stdInHandle.Release(); - } - } - - public async Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken) - { - await s_readKeyHandle.WaitAsync(cancellationToken).ConfigureAwait(false); - - // I tried to replace this library with a call to `stty -echo`, but unfortunately - // the library also sets up allowing backspace to trigger `Console.KeyAvailable`. - if (VersionUtils.IsPS6) - { - InputEcho.Disable(); - } - - try - { - while (!await WaitForKeyAvailableAsync(cancellationToken).ConfigureAwait(false)) ; - } - finally - { - if (VersionUtils.IsPS6) - { - InputEcho.Enable(); - } - s_readKeyHandle.Release(); - } - - await s_stdInHandle.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - return System.Console.ReadKey(intercept); - } - finally - { - s_stdInHandle.Release(); - } - } - - public int GetCursorLeft() - { - return GetCursorLeft(CancellationToken.None); - } - - public int GetCursorLeft(CancellationToken cancellationToken) - { - s_stdInHandle.Wait(cancellationToken); - try - { - return System.Console.CursorLeft; - } - finally - { - s_stdInHandle.Release(); - } - } - - public Task GetCursorLeftAsync() - { - return GetCursorLeftAsync(CancellationToken.None); - } - - public async Task GetCursorLeftAsync(CancellationToken cancellationToken) - { - await s_stdInHandle.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - return System.Console.CursorLeft; - } - finally - { - s_stdInHandle.Release(); - } - } - - public int GetCursorTop() - { - return GetCursorTop(CancellationToken.None); - } - - public int GetCursorTop(CancellationToken cancellationToken) - { - s_stdInHandle.Wait(cancellationToken); - try - { - return System.Console.CursorTop; - } - finally - { - s_stdInHandle.Release(); - } - } - - public Task GetCursorTopAsync() - { - return GetCursorTopAsync(CancellationToken.None); - } - - public async Task GetCursorTopAsync(CancellationToken cancellationToken) - { - await s_stdInHandle.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - return System.Console.CursorTop; - } - finally - { - s_stdInHandle.Release(); - } - } - - private bool LongWaitForKey(CancellationToken cancellationToken) - { - // Wait for a key to be buffered (in other words, wait for Console.KeyAvailable to become - // true) with a long delay between checks. - while (!IsKeyAvailable(cancellationToken)) - { - s_waitHandle.Wait(LongWaitForKeySleepTime, cancellationToken); - } - - // As soon as a key is buffered, return true and switch the wait logic to be more - // responsive, but also more expensive. - WaitForKeyAvailable = ShortWaitForKey; - return true; - } - - private async Task LongWaitForKeyAsync(CancellationToken cancellationToken) - { - while (!await IsKeyAvailableAsync(cancellationToken).ConfigureAwait(false)) - { - await Task.Delay(LongWaitForKeySleepTime, cancellationToken).ConfigureAwait(false); - } - - WaitForKeyAvailableAsync = ShortWaitForKeyAsync; - return true; - } - - private bool ShortWaitForKey(CancellationToken cancellationToken) - { - // Check frequently for a new key to be buffered. - if (SpinUntilKeyAvailable(ShortWaitForKeyTimeout, cancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - return true; - } - - // If the user has not pressed a key before the end of the SpinUntil timeout then - // the user is idle and we can switch back to long delays between KeyAvailable checks. - cancellationToken.ThrowIfCancellationRequested(); - WaitForKeyAvailable = LongWaitForKey; - return false; - } - - private async Task ShortWaitForKeyAsync(CancellationToken cancellationToken) - { - if (await SpinUntilKeyAvailableAsync(ShortWaitForKeyTimeout, cancellationToken).ConfigureAwait(false)) - { - cancellationToken.ThrowIfCancellationRequested(); - return true; - } - - cancellationToken.ThrowIfCancellationRequested(); - WaitForKeyAvailableAsync = LongWaitForKeyAsync; - return false; - } - - private static bool SpinUntilKeyAvailable(int millisecondsTimeout, CancellationToken cancellationToken) - { - return SpinWait.SpinUntil( - () => - { - s_waitHandle.Wait(ShortWaitForKeySpinUntilSleepTime, cancellationToken); - return IsKeyAvailable(cancellationToken); - }, - millisecondsTimeout); - } - - private static Task SpinUntilKeyAvailableAsync(int millisecondsTimeout, CancellationToken cancellationToken) - { - return Task.Factory.StartNew( - () => SpinWait.SpinUntil( - () => - { - // The wait handle is never set, it's just used to enable cancelling the wait. - s_waitHandle.Wait(ShortWaitForKeySpinUntilSleepTime, cancellationToken); - return IsKeyAvailable(cancellationToken); - }, - millisecondsTimeout), cancellationToken); - } - - private static bool IsKeyAvailable(CancellationToken cancellationToken) - { - s_stdInHandle.Wait(cancellationToken); - try - { - return System.Console.KeyAvailable; - } - finally - { - s_stdInHandle.Release(); - } - } - - private async static Task IsKeyAvailableAsync(CancellationToken cancellationToken) - { - await s_stdInHandle.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - return System.Console.KeyAvailable; - } - finally - { - s_stdInHandle.Release(); - } - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/WindowsConsoleOperations.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/WindowsConsoleOperations.cs deleted file mode 100644 index 09ecd8368..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/WindowsConsoleOperations.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - internal class WindowsConsoleOperations : IConsoleOperations - { - private ConsoleKeyInfo? _bufferedKey; - - private SemaphoreSlim _readKeyHandle = AsyncUtils.CreateSimpleLockingSemaphore(); - - public int GetCursorLeft() => System.Console.CursorLeft; - - public int GetCursorLeft(CancellationToken cancellationToken) => System.Console.CursorLeft; - - public Task GetCursorLeftAsync() => Task.FromResult(System.Console.CursorLeft); - - public Task GetCursorLeftAsync(CancellationToken cancellationToken) => Task.FromResult(System.Console.CursorLeft); - - public int GetCursorTop() => System.Console.CursorTop; - - public int GetCursorTop(CancellationToken cancellationToken) => System.Console.CursorTop; - - public Task GetCursorTopAsync() => Task.FromResult(System.Console.CursorTop); - - public Task GetCursorTopAsync(CancellationToken cancellationToken) => Task.FromResult(System.Console.CursorTop); - - public async Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken) - { - await _readKeyHandle.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - if (_bufferedKey == null) - { - _bufferedKey = await Task.Run(() => Console.ReadKey(intercept)).ConfigureAwait(false); - } - - return _bufferedKey.Value; - } - finally - { - _readKeyHandle.Release(); - - // Throw if we're cancelled so the buffered key isn't cleared. - cancellationToken.ThrowIfCancellationRequested(); - _bufferedKey = null; - } - } - - public ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken) - { - _readKeyHandle.Wait(cancellationToken); - try - { - return - _bufferedKey.HasValue - ? _bufferedKey.Value - : (_bufferedKey = System.Console.ReadKey(intercept)).Value; - } - finally - { - _readKeyHandle.Release(); - - // Throw if we're cancelled so the buffered key isn't cleared. - cancellationToken.ThrowIfCancellationRequested(); - _bufferedKey = null; - } - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs deleted file mode 100644 index ea932161d..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs +++ /dev/null @@ -1,2854 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Management.Automation.Host; -using System.Management.Automation.Remoting; -using System.Management.Automation.Runspaces; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Handlers; -using Microsoft.PowerShell.EditorServices.Hosting; -using Microsoft.PowerShell.EditorServices.Logging; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; -using Microsoft.PowerShell.EditorServices.Utility; -using SMA = System.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices.Services -{ - using System.Management.Automation; - - /// - /// Manages the lifetime and usage of a PowerShell session. - /// Handles nested PowerShell prompts and also manages execution of - /// commands whether inside or outside of the debugger. - /// - internal class PowerShellContextService : IHostSupportsInteractiveSession - { - // This is a default that can be overriden at runtime by the user or tests. - private static string s_bundledModulePath = Path.GetFullPath(Path.Combine( - Path.GetDirectoryName(typeof(PowerShellContextService).Assembly.Location), - "..", - "..", - "..")); - - private static string s_commandsModulePath => Path.GetFullPath(Path.Combine( - s_bundledModulePath, - "PowerShellEditorServices", - "Commands", - "PowerShellEditorServices.Commands.psd1")); - - private static readonly Action s_runspaceApartmentStateSetter; - private static readonly PropertyInfo s_writeStreamProperty; - private static readonly object s_errorStreamValue; - - [SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline", Justification = "cctor needed for version specific initialization")] - static PowerShellContextService() - { - // PowerShell ApartmentState APIs aren't available in PSStandard, so we need to use reflection. - if (!VersionUtils.IsNetCore || VersionUtils.IsPS7OrGreater) - { - MethodInfo setterInfo = typeof(Runspace).GetProperty("ApartmentState").GetSetMethod(); - Delegate setter = Delegate.CreateDelegate(typeof(Action), firstArgument: null, method: setterInfo); - s_runspaceApartmentStateSetter = (Action)setter; - } - - if (VersionUtils.IsPS7OrGreater) - { - // Used to write ErrorRecords to the Error stream. Using Public and NonPublic because the plan is to make this property - // public in 7.0.1 - s_writeStreamProperty = typeof(PSObject).GetProperty("WriteStream", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); - Type writeStreamType = typeof(PSObject).Assembly.GetType("System.Management.Automation.WriteStreamType"); - s_errorStreamValue = Enum.Parse(writeStreamType, "Error"); - } - } - - #region Fields - - private readonly SemaphoreSlim resumeRequestHandle = AsyncUtils.CreateSimpleLockingSemaphore(); - private readonly SessionStateLock sessionStateLock = new SessionStateLock(); - - private readonly OmniSharp.Extensions.LanguageServer.Protocol.Server.ILanguageServerFacade _languageServer; - private readonly bool isPSReadLineEnabled; - private readonly ILogger logger; - - private SMA.PowerShell powerShell; - private bool ownsInitialRunspace; - private RunspaceDetails initialRunspace; - private SessionDetails mostRecentSessionDetails; - - private ProfilePathInfo profilePaths; - - private IVersionSpecificOperations versionSpecificOperations; - - private readonly Stack runspaceStack = new Stack(); - - private int isCommandLoopRestarterSet; - - #endregion - - #region Properties - - private IPromptContext PromptContext { get; set; } - - private PromptNest PromptNest { get; set; } - - private InvocationEventQueue InvocationEventQueue { get; set; } - - private EngineIntrinsics EngineIntrinsics { get; set; } - - internal PSHost ExternalHost { get; set; } - - /// - /// Gets a boolean that indicates whether the debugger is currently stopped, - /// either at a breakpoint or because the user broke execution. - /// - public bool IsDebuggerStopped => - this.versionSpecificOperations.IsDebuggerStopped( - PromptNest, - CurrentRunspace.Runspace); - - /// - /// Gets the current state of the session. - /// - public PowerShellContextState SessionState - { - get; - private set; - } - - /// - /// Gets the PowerShell version details for the initial local runspace. - /// - public PowerShellVersionDetails LocalPowerShellVersion - { - get; - private set; - } - - /// - /// Gets or sets an IHostOutput implementation for use in - /// writing output to the console. - /// - private IHostOutput ConsoleWriter { get; set; } - - internal IHostInput ConsoleReader { get; private set; } - - /// - /// Gets details pertaining to the current runspace. - /// - public RunspaceDetails CurrentRunspace - { - get; - private set; - } - - /// - /// Gets a value indicating whether the current runspace - /// is ready for a command - /// - public bool IsAvailable => this.SessionState == PowerShellContextState.Ready; - - /// - /// Gets the working directory path the PowerShell context was inititially set when the debugger launches. - /// This path is used to determine whether a script in the call stack is an "external" script. - /// - public string InitialWorkingDirectory { get; private set; } - - /// - /// Tracks the state of the LSP debug server (not the PowerShell debugger). - /// - internal bool IsDebugServerActive { get; set; } - - /// - /// Tracks if the PowerShell session started the debug server itself (true), or if it was - /// started by an LSP notification (false). Essentially, this marks if we're responsible for - /// stopping the debug server (and thus need to send a notification to do so). - /// - internal bool OwnsDebugServerState { get; set; } - - internal DebuggerStopEventArgs CurrentDebuggerStopEventArgs { get; private set; } - - #endregion - - #region Constructors - - /// - /// - /// - /// An ILogger implementation used for writing log messages. - /// - /// Indicates whether PSReadLine should be used if possible - /// - public PowerShellContextService( - ILogger logger, - OmniSharp.Extensions.LanguageServer.Protocol.Server.ILanguageServerFacade languageServer, - bool isPSReadLineEnabled) - { - _languageServer = languageServer; - this.logger = logger; - this.isPSReadLineEnabled = isPSReadLineEnabled; - - RunspaceChanged += PowerShellContext_RunspaceChangedAsync; - ExecutionStatusChanged += PowerShellContext_ExecutionStatusChangedAsync; - } - - [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Checked by Validate call")] - public static PowerShellContextService Create( - ILoggerFactory factory, - OmniSharp.Extensions.LanguageServer.Protocol.Server.ILanguageServerFacade languageServer, - HostStartupInfo hostStartupInfo) - { - var logger = factory.CreateLogger(); - - Validate.IsNotNull(nameof(hostStartupInfo), hostStartupInfo); - - // Respect a user provided bundled module path. - if (Directory.Exists(hostStartupInfo.BundledModulePath)) - { - logger.LogDebug($"Using new bundled module path: {hostStartupInfo.BundledModulePath}"); - s_bundledModulePath = hostStartupInfo.BundledModulePath; - } - - bool shouldUsePSReadLine = hostStartupInfo.ConsoleReplEnabled - && !hostStartupInfo.UsesLegacyReadLine; - - var powerShellContext = new PowerShellContextService( - logger, - languageServer, - shouldUsePSReadLine); - - EditorServicesPSHostUserInterface hostUserInterface = - hostStartupInfo.ConsoleReplEnabled - ? (EditorServicesPSHostUserInterface) new TerminalPSHostUserInterface(powerShellContext, hostStartupInfo.PSHost, logger) - : new ProtocolPSHostUserInterface(languageServer, powerShellContext, logger); - - EditorServicesPSHost psHost = - new EditorServicesPSHost( - powerShellContext, - hostStartupInfo, - hostUserInterface, - logger); - - Runspace initialRunspace = PowerShellContextService.CreateRunspace(psHost, hostStartupInfo.InitialSessionState); - powerShellContext.Initialize(hostStartupInfo.ProfilePaths, initialRunspace, true, hostUserInterface); - powerShellContext.ImportCommandsModuleAsync(); - - // TODO: This can be moved to the point after the $psEditor object - // gets initialized when that is done earlier than LanguageServer.Initialize - foreach (string module in hostStartupInfo.AdditionalModules) - { - var command = - new PSCommand() - .AddCommand("Microsoft.PowerShell.Core\\Import-Module") - .AddParameter("Name", module); - -#pragma warning disable CS4014 - // This call queues the loading on the pipeline thread, so no need to await - powerShellContext.ExecuteCommandAsync( - command, - sendOutputToHost: false, - sendErrorToHost: true); -#pragma warning restore CS4014 - } - - return powerShellContext; - } - - /// - /// Only used in testing. Creates a Runspace given HostStartupInfo instead of a PSHost. - /// - /// - /// TODO: We should use `CreateRunspace` in testing instead of this, if possible. - /// - /// - /// - /// The EditorServicesPSHostUserInterface to use for this instance. - /// An ILogger implementation to use for this instance. - /// - public static Runspace CreateTestRunspace( - HostStartupInfo hostDetails, - PowerShellContextService powerShellContext, - EditorServicesPSHostUserInterface hostUserInterface, - ILogger logger) - { - Validate.IsNotNull(nameof(powerShellContext), powerShellContext); - - var psHost = new EditorServicesPSHost(powerShellContext, hostDetails, hostUserInterface, logger); - powerShellContext.ConsoleWriter = hostUserInterface; - powerShellContext.ConsoleReader = hostUserInterface; - return CreateRunspace(psHost, hostDetails.InitialSessionState); - } - - /// - /// - /// - /// The PSHost that will be used for this Runspace. - /// This will be used when creating runspaces so that we honor the same InitialSessionState. - /// - public static Runspace CreateRunspace(PSHost psHost, InitialSessionState initialSessionState) - { - Runspace runspace = RunspaceFactory.CreateRunspace(psHost, initialSessionState); - - // Windows PowerShell must be hosted in STA mode - // This must be set on the runspace *before* it is opened - if (s_runspaceApartmentStateSetter != null) - { - s_runspaceApartmentStateSetter(runspace, ApartmentState.STA); - } - - runspace.ThreadOptions = PSThreadOptions.ReuseThread; - runspace.Open(); - - return runspace; - } - - /// - /// Initializes a new instance of the PowerShellContext class using - /// an existing runspace for the session. - /// - /// An object containing the profile paths for the session. - /// The initial runspace to use for this instance. - /// If true, the PowerShellContext owns this runspace. - /// An IHostOutput implementation. Optional. - public void Initialize( - ProfilePathInfo profilePaths, - Runspace initialRunspace, - bool ownsInitialRunspace, - IHostOutput consoleHost) - { - Validate.IsNotNull("initialRunspace", initialRunspace); - - this.ownsInitialRunspace = ownsInitialRunspace; - this.SessionState = PowerShellContextState.NotStarted; - this.ConsoleWriter = consoleHost; - this.ConsoleReader = consoleHost as IHostInput; - - // Get the PowerShell runtime version - this.LocalPowerShellVersion = - PowerShellVersionDetails.GetVersionDetails( - initialRunspace, - this.logger); - - this.powerShell = SMA.PowerShell.Create(); - this.powerShell.Runspace = initialRunspace; - - this.initialRunspace = - new RunspaceDetails( - initialRunspace, - this.GetSessionDetailsInRunspace(initialRunspace), - this.LocalPowerShellVersion, - RunspaceLocation.Local, - RunspaceContext.Original, - connectionString: null); - this.CurrentRunspace = this.initialRunspace; - - // Write out the PowerShell version for tracking purposes - this.logger.LogInformation($"PowerShell Version: {this.LocalPowerShellVersion.Version}, Edition: {this.LocalPowerShellVersion.Edition}"); - - Version powerShellVersion = this.LocalPowerShellVersion.Version; - if (powerShellVersion >= new Version(5, 0)) - { - this.versionSpecificOperations = new PowerShell5Operations(); - } - else - { - // TODO: Also throw for PowerShell 6 - throw new NotSupportedException( - "This computer has an unsupported version of PowerShell installed: " + - powerShellVersion.ToString()); - } - - // Set up the runspace - this.ConfigureRunspace(this.CurrentRunspace); - - // Add runspace capabilities - this.ConfigureRunspaceCapabilities(this.CurrentRunspace); - - // Set the $profile variable in the runspace - this.profilePaths = profilePaths; - if (profilePaths != null) - { - this.SetProfileVariableInCurrentRunspace(profilePaths); - } - - // Now that initialization is complete we can watch for InvocationStateChanged - this.SessionState = PowerShellContextState.Ready; - - // EngineIntrinsics is used in some instances to interact with the initial - // runspace without having to wait for PSReadLine to check for events. - this.EngineIntrinsics = - initialRunspace - .SessionStateProxy - .PSVariable - .GetValue("ExecutionContext") - as EngineIntrinsics; - - // The external host is used to properly exit from a nested prompt that - // was entered by the user. - this.ExternalHost = - initialRunspace - .SessionStateProxy - .PSVariable - .GetValue("Host") - as PSHost; - - // Now that the runspace is ready, enqueue it for first use - this.PromptNest = new PromptNest( - this, - this.powerShell, - this.ConsoleReader, - this.versionSpecificOperations); - this.InvocationEventQueue = InvocationEventQueue.Create(this, this.PromptNest); - - if (powerShellVersion.Major >= 5 && - this.isPSReadLineEnabled && - PSReadLinePromptContext.TryGetPSReadLineProxy(logger, initialRunspace, s_bundledModulePath, out PSReadLineProxy proxy)) - { - this.PromptContext = new PSReadLinePromptContext( - this, - this.PromptNest, - this.InvocationEventQueue, - proxy); - } - else - { - this.PromptContext = new LegacyReadLineContext(this); - } - - // Finally, restore the runspace's execution policy to the user's policy instead of - // Bypass. - this.RestoreExecutionPolicy(); - } - - /// - /// Imports the PowerShellEditorServices.Commands module into - /// the runspace. This method will be moved somewhere else soon. - /// - /// - public Task ImportCommandsModuleAsync() - { - this.logger.LogTrace($"Importing PowershellEditorServices commands from {s_commandsModulePath}"); - - PSCommand importCommand = new PSCommand() - .AddCommand("Import-Module") - .AddArgument(s_commandsModulePath); - - return this.ExecuteCommandAsync(importCommand, sendOutputToHost: false, sendErrorToHost: false); - } - - private static bool CheckIfRunspaceNeedsEventHandlers(RunspaceDetails runspaceDetails) - { - // The only types of runspaces that need to be configured are: - // - Locally created runspaces - // - Local process entered with Enter-PSHostProcess - // - Remote session entered with Enter-PSSession - return - (runspaceDetails.Location == RunspaceLocation.Local && - (runspaceDetails.Context == RunspaceContext.Original || - runspaceDetails.Context == RunspaceContext.EnteredProcess)) || - (runspaceDetails.Location == RunspaceLocation.Remote && runspaceDetails.Context == RunspaceContext.Original); - } - - private void ConfigureRunspace(RunspaceDetails runspaceDetails) - { - this.logger.LogTrace("Configuring Runspace"); - runspaceDetails.Runspace.StateChanged += this.HandleRunspaceStateChanged; - if (runspaceDetails.Runspace.Debugger != null) - { - runspaceDetails.Runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; - runspaceDetails.Runspace.Debugger.DebuggerStop += OnDebuggerStop; - } - - this.versionSpecificOperations.ConfigureDebugger(runspaceDetails.Runspace); - } - - private void CleanupRunspace(RunspaceDetails runspaceDetails) - { - this.logger.LogTrace("Cleaning Up Runspace"); - runspaceDetails.Runspace.StateChanged -= this.HandleRunspaceStateChanged; - if (runspaceDetails.Runspace.Debugger != null) - { - runspaceDetails.Runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated; - runspaceDetails.Runspace.Debugger.DebuggerStop -= OnDebuggerStop; - } - } - - #endregion - - #region Public Methods - - /// - /// Gets a RunspaceHandle for the session's runspace. This - /// handle is used to gain temporary ownership of the runspace - /// so that commands can be executed against it directly. - /// - /// A RunspaceHandle instance that gives access to the session's runspace. - public Task GetRunspaceHandleAsync() - { - return this.GetRunspaceHandleImplAsync(isReadLine: false, CancellationToken.None); - } - - /// - /// Gets a RunspaceHandle for the session's runspace. This - /// handle is used to gain temporary ownership of the runspace - /// so that commands can be executed against it directly. - /// - /// A CancellationToken that can be used to cancel the request. - /// A RunspaceHandle instance that gives access to the session's runspace. - public Task GetRunspaceHandleAsync(CancellationToken cancellationToken) - { - return this.GetRunspaceHandleImplAsync(isReadLine: false, cancellationToken); - } - - /// - /// Executes a PSCommand against the session's runspace and returns - /// a collection of results of the expected type. - /// - /// The expected result type. - /// The PSCommand to be executed. - /// - /// If true, causes any output written during command execution to be written to the host. - /// - /// - /// If true, causes any errors encountered during command execution to be written to the host. - /// - /// - /// An awaitable Task which will provide results once the command - /// execution completes. - /// - public Task> ExecuteCommandAsync( - PSCommand psCommand, - bool sendOutputToHost = false, - bool sendErrorToHost = true, - CancellationToken cancellationToken = default) - { - return this.ExecuteCommandAsync( - psCommand, errorMessages: null, sendOutputToHost, sendErrorToHost, cancellationToken: cancellationToken); - } - - /// - /// Executes a PSCommand against the session's runspace and returns - /// a collection of results of the expected type. - /// - /// The expected result type. - /// The PSCommand to be executed. - /// Error messages from PowerShell will be written to the StringBuilder. - /// - /// If true, causes any output written during command execution to be written to the host. - /// - /// - /// If true, causes any errors encountered during command execution to be written to the host. - /// - /// - /// If true, adds the command to the user's command history. - /// - /// - /// An awaitable Task which will provide results once the command - /// execution completes. - /// - public Task> ExecuteCommandAsync( - PSCommand psCommand, - StringBuilder errorMessages, - bool sendOutputToHost = false, - bool sendErrorToHost = true, - bool addToHistory = false, - CancellationToken cancellationToken = default) - { - return - this.ExecuteCommandAsync( - psCommand, - errorMessages, - new ExecutionOptions - { - WriteOutputToHost = sendOutputToHost, - WriteErrorsToHost = sendErrorToHost, - AddToHistory = addToHistory - }, - cancellationToken); - } - - /// - /// Executes a PSCommand against the session's runspace and returns - /// a collection of results of the expected type. This function needs help. - /// - /// The expected result type. - /// The PSCommand to be executed. - /// Error messages from PowerShell will be written to the StringBuilder. - /// Specifies options to be used when executing this command. - /// - /// An awaitable Task which will provide results once the command - /// execution completes. - /// - [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Checked by Validate call")] - [SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "PowerShellContext must catch and log all exceptions to be robust")] - public async Task> ExecuteCommandAsync( - PSCommand psCommand, - StringBuilder errorMessages, - ExecutionOptions executionOptions, - CancellationToken cancellationToken = default) - { - Validate.IsNotNull(nameof(psCommand), psCommand); - Validate.IsNotNull(nameof(executionOptions), executionOptions); - - // Add history to PSReadLine before cancelling, otherwise it will be restored as the - // cancelled prompt when it's called again. - if (executionOptions.AddToHistory) - { - this.PromptContext.AddToHistory(executionOptions.InputString ?? psCommand.Commands[0].CommandText); - } - - bool hadErrors = false; - RunspaceHandle runspaceHandle = null; - ExecutionTarget executionTarget = ExecutionTarget.PowerShell; - IEnumerable executionResult = Enumerable.Empty(); - var shouldCancelReadLine = - executionOptions.InterruptCommandPrompt || - executionOptions.WriteOutputToHost; - - // If the debugger is active and the caller isn't on the pipeline - // thread, send the command over to that thread to be executed. - // Determine if execution should take place in a different thread - // using the following criteria: - // 1. The current frame in the prompt nest has a thread controller - // (meaning it is a nested prompt or is in the debugger) - // 2. We aren't already on the thread in question - // 3. The command is not a candidate for background invocation - // via PowerShell eventing - // 4. The command cannot be for a PSReadLine pipeline while we - // are currently in a out of process runspace - var threadController = PromptNest.GetThreadController(); - if (!(threadController == null || - !threadController.IsPipelineThread || - threadController.IsCurrentThread() || - this.ShouldExecuteWithEventing(executionOptions) || - (PromptNest.IsRemote && executionOptions.IsReadLine))) - { - if (shouldCancelReadLine && PromptNest.IsReadLineBusy()) - { - // If a ReadLine pipeline is running in the debugger then we'll stop responding here - // if we don't cancel it. Typically we can rely on OnExecutionStatusChanged but - // the pipeline request won't even start without clearing the current task. - this.ConsoleReader?.StopCommandLoop(); - } - - // Send the pipeline execution request to the pipeline thread - return await threadController.RequestPipelineExecutionAsync( - new PipelineExecutionRequest( - this, - psCommand, - errorMessages, - executionOptions)).ConfigureAwait(false); - } - - Task writeErrorsToConsoleTask = null; - try - { - // Instruct PowerShell to send output and errors to the host - if (executionOptions.WriteOutputToHost) - { - psCommand.Commands[0].MergeMyResults( - PipelineResultTypes.Error, - PipelineResultTypes.Output); - - psCommand.Commands.Add( - this.GetOutputCommand( - endOfStatement: false)); - } - - executionTarget = GetExecutionTarget(executionOptions); - - // If a ReadLine pipeline is running we can still execute commands that - // don't write output (e.g. command completion) - if (executionTarget == ExecutionTarget.InvocationEvent) - { - return await this.InvocationEventQueue.ExecuteCommandOnIdleAsync( - psCommand, - errorMessages, - executionOptions).ConfigureAwait(false); - } - - // Prompt is stopped and started based on the execution status, so naturally - // we don't want PSReadLine pipelines to factor in. - if (!executionOptions.IsReadLine) - { - this.OnExecutionStatusChanged( - ExecutionStatus.Running, - executionOptions, - false); - } - - runspaceHandle = await this.GetRunspaceHandleAsync(executionOptions.IsReadLine).ConfigureAwait(false); - if (executionOptions.WriteInputToHost) - { - this.WriteOutput( - executionOptions.InputString ?? psCommand.Commands[0].CommandText, - includeNewLine: true); - } - - if (executionTarget == ExecutionTarget.Debugger) - { - // Manually change the session state for debugger commands because - // we don't have an invocation state event to attach to. - if (!executionOptions.IsReadLine) - { - this.OnSessionStateChanged( - this, - new SessionStateChangedEventArgs( - PowerShellContextState.Running, - PowerShellExecutionResult.NotFinished, - null)); - } - try - { - this.logger.LogTrace($"Executing in debugger: {GetStringForPSCommand(psCommand)}"); - return this.ExecuteCommandInDebugger( - psCommand, - executionOptions.WriteOutputToHost); - } - catch (Exception e) - { - this.logger.LogException("Exception occurred while executing debugger command", e); - } - finally - { - if (!executionOptions.IsReadLine) - { - this.OnSessionStateChanged( - this, - new SessionStateChangedEventArgs( - PowerShellContextState.Ready, - PowerShellExecutionResult.Stopped, - null)); - } - } - } - - var invocationSettings = new PSInvocationSettings() - { - AddToHistory = executionOptions.AddToHistory - }; - - this.logger.LogTrace("Passing to PowerShell"); - - SMA.PowerShell shell = this.PromptNest.GetPowerShell(executionOptions.IsReadLine); - - // Due to the following PowerShell bug, we can't just assign shell.Commands to psCommand - // because PowerShell strips out CommandInfo: - // https://github.com/PowerShell/PowerShell/issues/12297 - shell.Commands.Clear(); - foreach (Command command in psCommand.Commands) - { - shell.Commands.AddCommand(command); - } - - // Don't change our SessionState for ReadLine. - if (!executionOptions.IsReadLine) - { - await this.sessionStateLock.AcquireForExecuteCommandAsync().ConfigureAwait(false); - shell.InvocationStateChanged += PowerShell_InvocationStateChanged; - } - - shell.Runspace = executionOptions.ShouldExecuteInOriginalRunspace - ? this.initialRunspace.Runspace - : this.CurrentRunspace.Runspace; - try - { - this.logger.LogDebug($"Invoking: {GetStringForPSCommand(psCommand)}"); - - // Nested PowerShell instances can't be invoked asynchronously. This occurs - // in nested prompts and pipeline requests from eventing. - if (shell.IsNested) - { - return shell.Invoke(null, invocationSettings); - } - - // This is the primary reason that ExecuteCommandAsync takes a CancellationToken - return await Task.Run>( - () => shell.Invoke(input: null, invocationSettings), cancellationToken) - .ConfigureAwait(false); - } - finally - { - if (!executionOptions.IsReadLine) - { - shell.InvocationStateChanged -= PowerShell_InvocationStateChanged; - await this.sessionStateLock.ReleaseForExecuteCommand().ConfigureAwait(false); - } - - // This is the edge case where the debug server is running because it was - // started by PowerShell (and not by an LSP event), and we're no longer in the - // debugger within PowerShell, so since we own the state we need to stop the - // debug server too. - // - // Strangely one would think we could check `!PromptNest.IsInDebugger` but that - // doesn't work, we have to check if the shell is nested instead. Therefore this - // is a bit fragile, and I don't know how it'll work in a remoting scenario. - if (IsDebugServerActive && OwnsDebugServerState && !shell.IsNested) - { - logger.LogDebug("Stopping LSP debugger because PowerShell debugger stopped running!"); - OwnsDebugServerState = false; - _languageServer?.SendNotification("powerShell/stopDebugger"); - } - - if (shell.HadErrors) - { - var strBld = new StringBuilder(1024); - strBld.AppendFormat("Execution of the following command(s) completed with errors:\r\n\r\n{0}\r\n", - GetStringForPSCommand(psCommand)); - - int i = 1; - foreach (var error in shell.Streams.Error) - { - if (i > 1) strBld.Append("\r\n\r\n"); - strBld.Append($"Error #{i++}:\r\n"); - strBld.Append(error.ToString() + "\r\n"); - strBld.Append("ScriptStackTrace:\r\n"); - strBld.Append((error.ScriptStackTrace ?? "") + "\r\n"); - strBld.Append($"Exception:\r\n {error.Exception?.ToString() ?? ""}"); - Exception innerEx = error.Exception?.InnerException; - while (innerEx != null) - { - strBld.Append($"InnerException:\r\n {innerEx.ToString()}"); - innerEx = innerEx.InnerException; - } - } - - // We've reported these errors, clear them so they don't keep showing up. - shell.Streams.Error.Clear(); - - var errorMessage = strBld.ToString(); - - errorMessages?.Append(errorMessage); - this.logger.LogError(errorMessage); - - hadErrors = true; - } - } - } - catch (PSRemotingDataStructureException e) - { - this.logger.LogHandledException("PSRemotingDataStructure exception while executing command", e); - errorMessages?.Append(e.Message); - } - catch (PipelineStoppedException e) - { - this.logger.LogHandledException("Pipeline stopped while executing command", e); - errorMessages?.Append(e.Message); - } - catch (RuntimeException e) - { - this.logger.LogHandledException("Runtime exception occurred while executing command", e); - - hadErrors = true; - errorMessages?.Append(e.Message); - - if (executionOptions.WriteErrorsToHost) - { - // Write the error to the host - // We must await this after the runspace handle has been released or we will deadlock - writeErrorsToConsoleTask = this.WriteExceptionToHostAsync(e); - } - } - catch (Exception) - { - this.OnExecutionStatusChanged( - ExecutionStatus.Failed, - executionOptions, - true); - - throw; - } - finally - { - // If the RunspaceAvailability is None, it means that the runspace we're in is dead. - // If this is the case, we should abort the execution which will clean up the runspace - // (and clean up the debugger) and then pop it off the stack. - // An example of when this happens is when the "attach" debug config is used and the - // process you're attached to dies randomly. - if (this.CurrentRunspace.Runspace.RunspaceAvailability == RunspaceAvailability.None) - { - this.AbortExecution(shouldAbortDebugSession: true); - this.PopRunspace(); - } - - // Get the new prompt before releasing the runspace handle - if (executionOptions.WriteOutputToHost) - { - SessionDetails sessionDetails = null; - - // Get the SessionDetails and then write the prompt - if (executionTarget == ExecutionTarget.Debugger) - { - sessionDetails = this.GetSessionDetailsInDebugger(); - } - else if (this.CurrentRunspace.Runspace.RunspaceAvailability == RunspaceAvailability.Available) - { - // This state can happen if the user types a command that causes the - // debugger to exit before we reach this point. No RunspaceHandle - // will exist already so we need to create one and then use it - if (runspaceHandle == null) - { - runspaceHandle = await this.GetRunspaceHandleAsync(cancellationToken).ConfigureAwait(false); - } - - sessionDetails = this.GetSessionDetailsInRunspace(runspaceHandle.Runspace); - } - else - { - sessionDetails = this.GetSessionDetailsInNestedPipeline(); - } - - // Check if the runspace has changed - this.UpdateRunspaceDetailsIfSessionChanged(sessionDetails); - } - - // Dispose of the execution context - if (runspaceHandle != null) - { - runspaceHandle.Dispose(); - if (writeErrorsToConsoleTask != null) - { - await writeErrorsToConsoleTask.ConfigureAwait(false); - } - } - - this.OnExecutionStatusChanged( - ExecutionStatus.Completed, - executionOptions, - hadErrors); - } - - return executionResult; - } - - /// - /// Executes a PSCommand in the session's runspace without - /// expecting to receive any result. - /// - /// The PSCommand to be executed. - /// - /// An awaitable Task that the caller can use to know when - /// execution completes. - /// - public Task ExecuteCommandAsync(PSCommand psCommand) - { - return this.ExecuteCommandAsync(psCommand); - } - - /// - /// Executes a script string in the session's runspace. - /// - /// The script string to execute. - /// A Task that can be awaited for the script completion. - public Task> ExecuteScriptStringAsync( - string scriptString) - { - return this.ExecuteScriptStringAsync(scriptString, false, true); - } - - /// - /// Executes a script string in the session's runspace. - /// - /// The script string to execute. - /// Error messages from PowerShell will be written to the StringBuilder. - /// A Task that can be awaited for the script completion. - public Task> ExecuteScriptStringAsync( - string scriptString, - StringBuilder errorMessages) - { - return this.ExecuteScriptStringAsync(scriptString, errorMessages, false, true, false); - } - - /// - /// Executes a script string in the session's runspace. - /// - /// The script string to execute. - /// If true, causes the script string to be written to the host. - /// If true, causes the script output to be written to the host. - /// A Task that can be awaited for the script completion. - public Task> ExecuteScriptStringAsync( - string scriptString, - bool writeInputToHost, - bool writeOutputToHost) - { - return this.ExecuteScriptStringAsync(scriptString, null, writeInputToHost, writeOutputToHost, false); - } - - /// - /// Executes a script string in the session's runspace. - /// - /// The script string to execute. - /// If true, causes the script string to be written to the host. - /// If true, causes the script output to be written to the host. - /// If true, adds the command to the user's command history. - /// A Task that can be awaited for the script completion. - public Task> ExecuteScriptStringAsync( - string scriptString, - bool writeInputToHost, - bool writeOutputToHost, - bool addToHistory) - { - return this.ExecuteScriptStringAsync(scriptString, null, writeInputToHost, writeOutputToHost, addToHistory); - } - - /// - /// Executes a script string in the session's runspace. - /// - /// The script string to execute. - /// Error messages from PowerShell will be written to the StringBuilder. - /// If true, causes the script string to be written to the host. - /// If true, causes the script output to be written to the host. - /// If true, adds the command to the user's command history. - /// A Task that can be awaited for the script completion. - public Task> ExecuteScriptStringAsync( - string scriptString, - StringBuilder errorMessages, - bool writeInputToHost, - bool writeOutputToHost, - bool addToHistory) - { - Validate.IsNotNull(nameof(scriptString), scriptString); - - PSCommand command = null; - if(CurrentRunspace.Runspace.SessionStateProxy.LanguageMode != PSLanguageMode.FullLanguage) - { - try - { - var scriptBlock = ScriptBlock.Create(scriptString); - SMA.PowerShell ps = scriptBlock.GetPowerShell(isTrustedInput: false, null); - command = ps.Commands; - } - catch (Exception e) - { - logger.LogException("Exception getting trusted/untrusted PSCommand.", e); - } - } - - // fall back to old behavior - if(command == null) - { - command = new PSCommand().AddScript(scriptString.Trim()); - } - - return this.ExecuteCommandAsync( - command, - errorMessages, - new ExecutionOptions() - { - WriteOutputToHost = writeOutputToHost, - AddToHistory = addToHistory, - WriteInputToHost = writeInputToHost - }); - } - - /// - /// Executes a script file at the specified path. - /// - /// The script execute. - /// Arguments to pass to the script. - /// Writes the executed script path and arguments to the host. - /// A Task that can be awaited for completion. - public async Task ExecuteScriptWithArgsAsync(string script, string arguments = null, bool writeInputToHost = false) - { - Validate.IsNotNull(nameof(script), script); - - PSCommand command = new PSCommand(); - - if (arguments != null) - { - // Add CWD from PowerShell if the script is a file (not a command/inline script) and - // it's not an absolute path. - if (File.Exists(script) && !Path.IsPathRooted(script)) - { - try - { - // Assume we can only debug scripts from the FileSystem provider - string workingDir = (await ExecuteCommandAsync( - new PSCommand() - .AddCommand("Microsoft.PowerShell.Management\\Get-Location") - .AddParameter("PSProvider", "FileSystem"), - sendOutputToHost: false, - sendErrorToHost: false).ConfigureAwait(false)) - .FirstOrDefault() - .ProviderPath; - - this.logger.LogDebug($"Prepending working directory {workingDir} to script path {script}"); - script = Path.Combine(workingDir, script); - } - catch (System.Management.Automation.DriveNotFoundException e) - { - this.logger.LogHandledException("Could not determine current filesystem location", e); - } - } - - var strBld = new StringBuilder(); - - // The script parameter can refer to either a "script path" or a "command name". If it is a - // script path, we can determine that by seeing if the path exists. If so, we always single - // quote that path in case it includes special PowerShell characters like ', &, (, ), [, ] and - // . Any embedded single quotes are escaped. - // If the provided path is already quoted, then File.Exists will not find it. - // This keeps us from quoting an already quoted path. - // Related to issue #123. - if (File.Exists(script)) - { - // Dot-source the launched script path and single quote the path in case it includes - strBld.Append(". ").Append(QuoteEscapeString(script)); - } - else - { - strBld.Append(script); - } - - // Add arguments - strBld.Append(' ').Append(arguments); - - var launchedScript = strBld.ToString(); - - command.AddScript(launchedScript, false); - } - else - { - // AddCommand can handle script paths including those with special chars e.g.: - // ".\foo & [bar]\foo.ps1" and it can handle arbitrary commands, like "Invoke-Pester" - command.AddCommand(script, false); - } - - - await this.ExecuteCommandAsync( - command, - errorMessages: null, - new ExecutionOptions - { - WriteInputToHost = true, - WriteOutputToHost = true, - WriteErrorsToHost = true, - AddToHistory = true, - }).ConfigureAwait(false); - } - - /// - /// Forces the to trigger PowerShell event handling, - /// reliquishing control of the pipeline thread during event processing. - /// - /// - /// This method is called automatically by and - /// . Consider using them instead of this method directly when - /// possible. - /// - internal void ForcePSEventHandling() - { - PromptContext.ForcePSEventHandling(); - } - - /// - /// Marshals a to run on the pipeline thread. A new - /// will be created for the invocation. - /// - /// - /// The to invoke on the pipeline thread. The nested - /// instance for the created - /// will be passed as an argument. - /// - /// - /// An awaitable that the caller can use to know when execution completes. - /// - /// - /// This method is called automatically by . Consider using - /// that method instead of calling this directly when possible. - /// - internal Task InvokeOnPipelineThreadAsync(Action invocationAction) - { - if (this.PromptNest.IsReadLineBusy()) - { - return this.InvocationEventQueue.InvokeOnPipelineThreadAsync(invocationAction); - } - - // If this is invoked when ReadLine isn't busy then there shouldn't be any running - // pipelines. Right now this method is only used by command completion which doesn't - // actually require running on the pipeline thread, as long as nothing else is running. - invocationAction.Invoke(this.PromptNest.GetPowerShell()); - return Task.CompletedTask; - } - - internal async Task InvokeReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) - { - return await PromptContext.InvokeReadLineAsync( - isCommandLine, - cancellationToken).ConfigureAwait(false); - } - - internal static TResult ExecuteScriptAndGetItem( - string scriptToExecute, - Runspace runspace, - TResult defaultValue = default, - bool useLocalScope = false) - { - using (SMA.PowerShell pwsh = SMA.PowerShell.Create()) - { - pwsh.Runspace = runspace; - IEnumerable results = pwsh.AddScript(scriptToExecute, useLocalScope).Invoke(); - return results.DefaultIfEmpty(defaultValue).First(); - } - } - - /// - /// Loads PowerShell profiles for the host from the specified - /// profile locations. Only the profile paths which exist are - /// loaded. - /// - /// A Task that can be awaited for completion. - public async Task LoadHostProfilesAsync() - { - if (this.profilePaths == null) - { - return; - } - - // Load any of the profile paths that exist - var command = new PSCommand(); - bool hasLoadablePath = false; - foreach (var profilePath in GetLoadableProfilePaths(this.profilePaths)) - { - hasLoadablePath = true; - command.AddCommand(profilePath, false).AddStatement(); - } - - if (!hasLoadablePath) - { - return; - } - - await ExecuteCommandAsync(command, sendOutputToHost: true).ConfigureAwait(false); - - // Gather the session details (particularly the prompt) after - // loading the user's profiles. - await this.GetSessionDetailsInRunspaceAsync().ConfigureAwait(false); - } - - /// - /// Causes the most recent execution to be aborted no matter what state - /// it is currently in. - /// - public void AbortExecution() - { - this.AbortExecution(shouldAbortDebugSession: false); - } - - /// - /// Causes the most recent execution to be aborted no matter what state - /// it is currently in. - /// - /// - /// A value indicating whether a debug session should be aborted if one - /// is currently active. - /// - public void AbortExecution(bool shouldAbortDebugSession) - { - this.logger.LogTrace("Execution abort requested..."); - - if (this.SessionState == PowerShellContextState.Aborting - || this.SessionState == PowerShellContextState.Disposed) - { - this.logger.LogTrace($"Execution abort requested when already aborted (SessionState = {this.SessionState})"); - return; - } - - if (shouldAbortDebugSession) - { - this.ExitAllNestedPrompts(); - } - - if (this.PromptNest.IsInDebugger) - { - this.versionSpecificOperations.StopCommandInDebugger(this); - if (shouldAbortDebugSession) - { - this.ResumeDebugger(DebuggerResumeAction.Stop, shouldWaitForExit: false); - } - } - else - { - this.PromptNest.GetPowerShell(isReadLine: false).BeginStop(null, null); - } - - // TODO: - // This lock and state reset are a temporary fix at best. - // We need to investigate how the debugger should be interacting - // with PowerShell in this cancellation scenario. - // - // Currently we try to acquire a lock on the execution status changed event. - // If we can't, it's because a command is executing, so we shouldn't change the status. - // If we can, we own the status and should fire the event. - if (this.sessionStateLock.TryAcquireForDebuggerAbort()) - { - try - { - this.SessionState = PowerShellContextState.Aborting; - this.OnExecutionStatusChanged( - ExecutionStatus.Aborted, - null, - false); - } - finally - { - this.SessionState = PowerShellContextState.Ready; - this.sessionStateLock.ReleaseForDebuggerAbort(); - } - } - } - - /// - /// Exit all consecutive nested prompts that the user has entered. - /// - internal void ExitAllNestedPrompts() - { - while (this.PromptNest.IsNestedPrompt) - { - this.PromptNest.WaitForCurrentFrameExit(frame => this.ExitNestedPrompt()); - this.versionSpecificOperations.ExitNestedPrompt(ExternalHost); - } - } - - /// - /// Exit all consecutive nested prompts that the user has entered. - /// - /// - /// A task object that represents all nested prompts being exited - /// - internal async Task ExitAllNestedPromptsAsync() - { - while (this.PromptNest.IsNestedPrompt) - { - await this.PromptNest.WaitForCurrentFrameExitAsync(frame => this.ExitNestedPrompt()).ConfigureAwait(false); - this.versionSpecificOperations.ExitNestedPrompt(ExternalHost); - } - } - - /// - /// Causes the debugger to break execution wherever it currently is. - /// This method is internal because the real Break API is provided - /// by the DebugService. - /// - internal void BreakExecution() - { - this.logger.LogTrace("Debugger break requested..."); - - // Pause the debugger - this.versionSpecificOperations.PauseDebugger( - this.CurrentRunspace.Runspace); - } - - internal void ResumeDebugger(DebuggerResumeAction resumeAction) - { - ResumeDebugger(resumeAction, shouldWaitForExit: true); - } - - private void ResumeDebugger(DebuggerResumeAction resumeAction, bool shouldWaitForExit) - { - resumeRequestHandle.Wait(); - try - { - if (this.PromptNest.IsNestedPrompt) - { - this.ExitAllNestedPrompts(); - } - - if (this.PromptNest.IsInDebugger) - { - // Set the result so that the execution thread resumes. - // The execution thread will clean up the task. - if (shouldWaitForExit) - { - this.PromptNest.WaitForCurrentFrameExit( - frame => - { - frame.ThreadController.StartThreadExit(resumeAction); - this.ConsoleReader?.StopCommandLoop(); - if (this.SessionState != PowerShellContextState.Ready) - { - this.versionSpecificOperations.StopCommandInDebugger(this); - } - }); - } - else - { - this.PromptNest.GetThreadController().StartThreadExit(resumeAction); - this.ConsoleReader?.StopCommandLoop(); - if (this.SessionState != PowerShellContextState.Ready) - { - this.versionSpecificOperations.StopCommandInDebugger(this); - } - } - } - else - { - this.logger.LogError( - $"Tried to resume debugger with action {resumeAction} but there was no debuggerStoppedTask."); - } - } - finally - { - resumeRequestHandle.Release(); - } - } - - /// - /// Closes the runspace and any other resources being used - /// by this PowerShellContext. - /// - public void Close() - { - logger.LogTrace("Closing PowerShellContextService..."); - this.PromptNest.Dispose(); - this.SessionState = PowerShellContextState.Disposed; - - // Clean up the active runspace - this.CleanupRunspace(this.CurrentRunspace); - - // Push the active runspace so it will be included in the loop - this.runspaceStack.Push(this.CurrentRunspace); - - while (this.runspaceStack.Count > 0) - { - RunspaceDetails poppedRunspace = this.runspaceStack.Pop(); - - // Close the popped runspace if it isn't the initial runspace - // or if it is the initial runspace and we own that runspace - if (this.initialRunspace != poppedRunspace || this.ownsInitialRunspace) - { - this.CloseRunspace(poppedRunspace); - } - - this.OnRunspaceChanged( - this, - new RunspaceChangedEventArgs( - RunspaceChangeAction.Shutdown, - poppedRunspace, - null)); - } - - this.initialRunspace = null; - } - - private Task GetRunspaceHandleAsync(bool isReadLine) - { - return this.GetRunspaceHandleImplAsync(isReadLine, CancellationToken.None); - } - - private Task GetRunspaceHandleImplAsync(bool isReadLine, CancellationToken cancellationToken) - { - return this.PromptNest.GetRunspaceHandleAsync(isReadLine, cancellationToken); - } - - private ExecutionTarget GetExecutionTarget(ExecutionOptions options = null) - { - if (options == null) - { - options = new ExecutionOptions(); - } - - var noBackgroundInvocation = - options.InterruptCommandPrompt || - options.WriteOutputToHost || - options.IsReadLine || - PromptNest.IsRemote; - - // Take over the pipeline if PSReadLine is running, we aren't trying to run PSReadLine, and - // we aren't in a remote session. - if (!noBackgroundInvocation && PromptNest.IsReadLineBusy() && PromptNest.IsMainThreadBusy()) - { - return ExecutionTarget.InvocationEvent; - } - - // We can't take the pipeline from PSReadLine if it's in a remote session, so we need to - // invoke locally in that case. - if (IsDebuggerStopped && PromptNest.IsInDebugger && !(options.IsReadLine && PromptNest.IsRemote)) - { - return ExecutionTarget.Debugger; - } - - return ExecutionTarget.PowerShell; - } - - private bool ShouldExecuteWithEventing(ExecutionOptions executionOptions) - { - return - this.PromptNest.IsReadLineBusy() && - this.PromptNest.IsMainThreadBusy() && - !(executionOptions.IsReadLine || - executionOptions.InterruptCommandPrompt || - executionOptions.WriteOutputToHost || - IsCurrentRunspaceOutOfProcess()); - } - - private void CloseRunspace(RunspaceDetails runspaceDetails) - { - string exitCommand = null; - - switch (runspaceDetails.Context) - { - case RunspaceContext.Original: - if (runspaceDetails.Location == RunspaceLocation.Local) - { - runspaceDetails.Runspace.Close(); - runspaceDetails.Runspace.Dispose(); - } - else - { - exitCommand = "Exit-PSSession"; - } - - break; - - case RunspaceContext.EnteredProcess: - exitCommand = "Exit-PSHostProcess"; - break; - - case RunspaceContext.DebuggedRunspace: - // An attached runspace will be detached when the - // running pipeline is aborted - break; - } - - if (exitCommand != null) - { - Exception exitException = null; - - try - { - using (SMA.PowerShell ps = SMA.PowerShell.Create()) - { - ps.Runspace = runspaceDetails.Runspace; - ps.AddCommand(exitCommand); - ps.Invoke(); - } - } - catch (RemoteException e) - { - exitException = e; - } - catch (RuntimeException e) - { - exitException = e; - } - - if (exitException != null) - { - this.logger.LogHandledException( - $"Caught {exitException.GetType().Name} while exiting {runspaceDetails.Location} runspace", exitException); - } - } - } - - internal void ReleaseRunspaceHandle(RunspaceHandle runspaceHandle) - { - Validate.IsNotNull("runspaceHandle", runspaceHandle); - - if (PromptNest.IsMainThreadBusy() || (runspaceHandle.IsReadLine && PromptNest.IsReadLineBusy())) - { - _ = PromptNest - .ReleaseRunspaceHandleAsync(runspaceHandle) - .ConfigureAwait(false); - } - else - { - // Write the situation to the log since this shouldn't happen - this.logger.LogError( - "ReleaseRunspaceHandle was called when the main thread was not busy."); - } - } - - /// - /// Determines if the current runspace is out of process. - /// - /// - /// A value indicating whether the current runspace is out of process. - /// - internal bool IsCurrentRunspaceOutOfProcess() - { - return - CurrentRunspace.Context == RunspaceContext.EnteredProcess || - CurrentRunspace.Context == RunspaceContext.DebuggedRunspace || - CurrentRunspace.Location == RunspaceLocation.Remote; - } - - /// - /// Called by the external PSHost when $Host.EnterNestedPrompt is called. - /// - internal void EnterNestedPrompt() - { - this.logger.LogTrace("Entering nested prompt"); - - if (this.IsCurrentRunspaceOutOfProcess()) - { - throw new NotSupportedException(); - } - - this.PromptNest.PushPromptContext(PromptNestFrameType.NestedPrompt); - var localThreadController = this.PromptNest.GetThreadController(); - this.OnSessionStateChanged( - this, - new SessionStateChangedEventArgs( - PowerShellContextState.Ready, - PowerShellExecutionResult.Stopped, - null)); - - // Reset command loop mainly for PSReadLine - this.ConsoleReader?.StopCommandLoop(); - this.ConsoleReader?.StartCommandLoop(); - - var localPipelineExecutionTask = localThreadController.TakeExecutionRequestAsync(); - var localDebuggerStoppedTask = localThreadController.Exit(); - - // Wait for off-thread pipeline requests and/or ExitNestedPrompt - while (true) - { - int taskIndex = Task.WaitAny( - localPipelineExecutionTask, - localDebuggerStoppedTask); - - if (taskIndex == 0) - { - var localExecutionTask = localPipelineExecutionTask.GetAwaiter().GetResult(); - localPipelineExecutionTask = localThreadController.TakeExecutionRequestAsync(); - localExecutionTask.ExecuteAsync().GetAwaiter().GetResult(); - continue; - } - - this.ConsoleReader?.StopCommandLoop(); - this.PromptNest.PopPromptContext(); - break; - } - } - - /// - /// Called by the external PSHost when $Host.ExitNestedPrompt is called. - /// - internal void ExitNestedPrompt() - { - if (this.PromptNest.NestedPromptLevel == 1 || !this.PromptNest.IsNestedPrompt) - { - this.logger.LogError( - "ExitNestedPrompt was called outside of a nested prompt."); - return; - } - - // Stop the command input loop so PSReadLine isn't invoked between ExitNestedPrompt - // being invoked and EnterNestedPrompt getting the message to exit. - this.ConsoleReader?.StopCommandLoop(); - this.PromptNest.GetThreadController().StartThreadExit(DebuggerResumeAction.Stop); - } - - /// - /// Sets the current working directory of the powershell context. The path should be - /// unescaped before calling this method. - /// - /// - public Task SetWorkingDirectoryAsync(string path) - { - return this.SetWorkingDirectoryAsync(path, isPathAlreadyEscaped: true); - } - - /// - /// Sets the current working directory of the powershell context. - /// - /// - /// Specify false to have the path escaped, otherwise specify true if the path has already been escaped. - public async Task SetWorkingDirectoryAsync(string path, bool isPathAlreadyEscaped) - { - Validate.IsNotNull(nameof(path), path); - this.InitialWorkingDirectory = path; - - if (!isPathAlreadyEscaped) - { - path = WildcardEscapePath(path); - } - - await ExecuteCommandAsync( - new PSCommand().AddCommand("Set-Location").AddParameter("Path", path), - errorMessages: null, - sendOutputToHost: false, - sendErrorToHost: false, - addToHistory: false).ConfigureAwait(false); - } - - /// - /// Fully escape a given path for use in PowerShell script. - /// Note: this will not work with PowerShell.AddParameter() - /// - /// The path to escape. - /// An escaped version of the path that can be embedded in PowerShell script. - internal static string FullyPowerShellEscapePath(string path) - { - string wildcardEscapedPath = WildcardEscapePath(path); - return QuoteEscapeString(wildcardEscapedPath); - } - - /// - /// Wrap a string in quotes to make it safe to use in scripts. - /// - /// The glob-escaped path to wrap in quotes. - /// The given path wrapped in quotes appropriately. - internal static string QuoteEscapeString(string escapedPath) - { - var sb = new StringBuilder(escapedPath.Length + 2); // Length of string plus two quotes - sb.Append('\''); - if (!escapedPath.Contains('\'')) - { - sb.Append(escapedPath); - } - else - { - foreach (char c in escapedPath) - { - if (c == '\'') - { - sb.Append("''"); - continue; - } - - sb.Append(c); - } - } - sb.Append('\''); - return sb.ToString(); - } - - /// - /// Return the given path with all PowerShell globbing characters escaped, - /// plus optionally the whitespace. - /// - /// The path to process. - /// Specify True to escape spaces in the path, otherwise False. - /// The path with [ and ] escaped. - internal static string WildcardEscapePath(string path, bool escapeSpaces = false) - { - var sb = new StringBuilder(); - for (int i = 0; i < path.Length; i++) - { - char curr = path[i]; - switch (curr) - { - // Escape '[', ']', '?' and '*' with '`' - case '[': - case ']': - case '*': - case '?': - case '`': - sb.Append('`').Append(curr); - break; - - default: - // Escape whitespace if required - if (escapeSpaces && char.IsWhiteSpace(curr)) - { - sb.Append('`').Append(curr); - break; - } - sb.Append(curr); - break; - } - } - - return sb.ToString(); - } - - /// - /// Returns the passed in path with the [ and ] characters escaped. Escaping spaces is optional. - /// - /// The path to process. - /// Specify True to escape spaces in the path, otherwise False. - /// The path with [ and ] escaped. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("This API is not meant for public usage and should not be used.")] - public static string EscapePath(string path, bool escapeSpaces) - { - Validate.IsNotNull(nameof(path), path); - - return WildcardEscapePath(path, escapeSpaces); - } - - internal static string UnescapeWildcardEscapedPath(string wildcardEscapedPath) - { - // Prevent relying on my implementation if we can help it - if (!wildcardEscapedPath.Contains('`')) - { - return wildcardEscapedPath; - } - - var sb = new StringBuilder(wildcardEscapedPath.Length); - for (int i = 0; i < wildcardEscapedPath.Length; i++) - { - // If we see a backtick perform a lookahead - char curr = wildcardEscapedPath[i]; - if (curr == '`' && i + 1 < wildcardEscapedPath.Length) - { - // If the next char is an escapable one, don't add this backtick to the new string - char next = wildcardEscapedPath[i + 1]; - switch (next) - { - case '[': - case ']': - case '?': - case '*': - continue; - - default: - if (char.IsWhiteSpace(next)) - { - continue; - } - break; - } - } - - sb.Append(curr); - } - - return sb.ToString(); - } - - /// - /// Unescapes any escaped [, ] or space characters. Typically use this before calling a - /// .NET API that doesn't understand PowerShell escaped chars. - /// - /// The path to unescape. - /// The path with the ` character before [, ] and spaces removed. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("This API is not meant for public usage and should not be used.")] - public static string UnescapePath(string path) - { - Validate.IsNotNull(nameof(path), path); - - return UnescapeWildcardEscapedPath(path); - } - - #endregion - - #region Events - - /// - /// Raised when the state of the session has changed. - /// - public event EventHandler SessionStateChanged; - - private void OnSessionStateChanged(object sender, SessionStateChangedEventArgs e) - { - if (this.SessionState != PowerShellContextState.Disposed) - { - this.logger.LogTrace($"Session state was: {SessionState}, is now: {e.NewSessionState}, result: {e.ExecutionResult}"); - this.SessionState = e.NewSessionState; - this.SessionStateChanged?.Invoke(sender, e); - } - else - { - this.logger.LogWarning( - $"Received session state change to {e.NewSessionState} when already disposed"); - } - } - - /// - /// Raised when the runspace changes by entering a remote session or one in a different process. - /// - public event EventHandler RunspaceChanged; - - private void OnRunspaceChanged(object sender, RunspaceChangedEventArgs e) - { - this.RunspaceChanged?.Invoke(sender, e); - } - - /// - /// Raised when the status of an executed command changes. - /// - public event EventHandler ExecutionStatusChanged; - - private void OnExecutionStatusChanged( - ExecutionStatus executionStatus, - ExecutionOptions executionOptions, - bool hadErrors) - { - this.ExecutionStatusChanged?.Invoke( - this, - new ExecutionStatusChangedEventArgs( - executionStatus, - executionOptions, - hadErrors)); - } - - /// - /// TODO: This should somehow check if the server has actually started because we are - /// currently sending this notification before it has initialized, which is not allowed. - /// This might be the cause of our deadlock! - /// - private void PowerShellContext_RunspaceChangedAsync(object sender, RunspaceChangedEventArgs e) - { - _languageServer?.SendNotification( - "powerShell/runspaceChanged", - new MinifiedRunspaceDetails(e.NewRunspace)); - } - - - // TODO: Refactor this, RunspaceDetails, PowerShellVersion, and PowerShellVersionDetails - // It's odd that this is 4 different types. - // P.S. MinifiedRunspaceDetails use to be called RunspaceDetails... as in, there were 2 DIFFERENT - // RunspaceDetails types in this codebase but I've changed it to be minified since the type is - // slightly simpler than the other RunspaceDetails. - internal class MinifiedRunspaceDetails - { - public PowerShellVersion PowerShellVersion { get; set; } - - public RunspaceLocation RunspaceType { get; set; } - - public string ConnectionString { get; set; } - - public MinifiedRunspaceDetails() - { - } - - public MinifiedRunspaceDetails(RunspaceDetails eventArgs) - { - Validate.IsNotNull(nameof(eventArgs), eventArgs); - - this.PowerShellVersion = new PowerShellVersion(eventArgs.PowerShellVersion); - this.RunspaceType = eventArgs.Location; - this.ConnectionString = eventArgs.ConnectionString; - } - } - - /// - /// Event hook on the PowerShell context to listen for changes in script execution status - /// - /// - /// TODO: This should somehow check if the server has actually started because we are - /// currently sending this notification before it has initialized, which is not allowed. - /// - /// the PowerShell context sending the execution event - /// details of the execution status change - private void PowerShellContext_ExecutionStatusChangedAsync(object sender, ExecutionStatusChangedEventArgs e) - { - // The cancelling of the prompt (PSReadLine) causes an ExecutionStatus.Aborted to be sent after every - // actual execution (ExecutionStatus.Running) on the pipeline. We ignore that event since it's counterintuitive to - // the goal of this method which is to send updates when the pipeline is actually running something. - // In the event that we don't know if it was a ReadLine cancel, we default to sending the notification. - var options = e?.ExecutionOptions; - if (options == null || !options.IsReadLine) - { - _languageServer?.SendNotification( - "powerShell/executionStatusChanged", - e); - } - } - - #endregion - - #region Private Methods - - private IEnumerable ExecuteCommandInDebugger(PSCommand psCommand, bool sendOutputToHost) - { - IEnumerable output = - this.versionSpecificOperations.ExecuteCommandInDebugger( - this, - this.CurrentRunspace.Runspace, - psCommand, - sendOutputToHost, - out DebuggerResumeAction? debuggerResumeAction); - - if (debuggerResumeAction.HasValue) - { - // Resume the debugger with the specificed action - this.ResumeDebugger( - debuggerResumeAction.Value, - shouldWaitForExit: false); - } - - return output; - } - - internal void WriteOutput(string outputString, bool includeNewLine) - { - this.WriteOutput( - outputString, - includeNewLine, - OutputType.Normal); - } - - internal void WriteOutput( - string outputString, - bool includeNewLine, - OutputType outputType) - { - if (this.ConsoleWriter != null) - { - this.ConsoleWriter.WriteOutput( - outputString, - includeNewLine, - outputType); - } - } - - private Task WriteExceptionToHostAsync(RuntimeException e) - { - var psObject = PSObject.AsPSObject(e.ErrorRecord); - - // Used to write ErrorRecords to the Error stream so they are rendered in the console correctly. - if (VersionUtils.IsPS7OrGreater) - { - s_writeStreamProperty.SetValue(psObject, s_errorStreamValue); - } - else - { - var note = new PSNoteProperty("writeErrorStream", true); - psObject.Properties.Add(note); - } - - return ExecuteCommandAsync(new PSCommand().AddCommand("Microsoft.PowerShell.Core\\Out-Default").AddParameter("InputObject", psObject)); - } - - private void WriteError( - string errorMessage, - string filePath, - int lineNumber, - int columnNumber) - { - const string ErrorLocationFormat = "At {0}:{1} char:{2}"; - - this.WriteError( - errorMessage + - Environment.NewLine + - string.Format( - ErrorLocationFormat, - String.IsNullOrEmpty(filePath) ? "line" : filePath, - lineNumber, - columnNumber)); - } - - private void WriteError(string errorMessage) - { - if (this.ConsoleWriter != null) - { - this.ConsoleWriter.WriteOutput( - errorMessage, - true, - OutputType.Error, - ConsoleColor.Red, - ConsoleColor.Black); - } - } - - void PowerShell_InvocationStateChanged(object sender, PSInvocationStateChangedEventArgs e) - { - SessionStateChangedEventArgs eventArgs = TranslateInvocationStateInfo(e.InvocationStateInfo); - this.OnSessionStateChanged(this, eventArgs); - } - - private static SessionStateChangedEventArgs TranslateInvocationStateInfo(PSInvocationStateInfo invocationState) - { - PowerShellExecutionResult executionResult = PowerShellExecutionResult.NotFinished; - - PowerShellContextState newState; - switch (invocationState.State) - { - case PSInvocationState.NotStarted: - newState = PowerShellContextState.NotStarted; - break; - - case PSInvocationState.Failed: - newState = PowerShellContextState.Ready; - executionResult = PowerShellExecutionResult.Failed; - break; - - case PSInvocationState.Disconnected: - // TODO: Any extra work to do in this case? - // TODO: Is this a unique state that can be re-connected? - newState = PowerShellContextState.Disposed; - executionResult = PowerShellExecutionResult.Stopped; - break; - - case PSInvocationState.Running: - newState = PowerShellContextState.Running; - break; - - case PSInvocationState.Completed: - newState = PowerShellContextState.Ready; - executionResult = PowerShellExecutionResult.Completed; - break; - - case PSInvocationState.Stopping: - newState = PowerShellContextState.Aborting; - break; - - case PSInvocationState.Stopped: - newState = PowerShellContextState.Ready; - executionResult = PowerShellExecutionResult.Aborted; - break; - - default: - newState = PowerShellContextState.Unknown; - break; - } - - return - new SessionStateChangedEventArgs( - newState, - executionResult, - invocationState.Reason); - } - - private Command GetOutputCommand(bool endOfStatement) - { - Command outputCommand = - new Command( - command: this.PromptNest.IsInDebugger ? "Out-String" : "Out-Default", - isScript: false, - useLocalScope: true); - - if (this.PromptNest.IsInDebugger) - { - // Out-String needs the -Stream parameter added - outputCommand.Parameters.Add("Stream"); - } - - return outputCommand; - } - - private static string GetStringForPSCommand(PSCommand psCommand) - { - StringBuilder stringBuilder = new StringBuilder(); - - foreach (var command in psCommand.Commands) - { - stringBuilder.Append(" "); - stringBuilder.Append(command.CommandText); - foreach (var param in command.Parameters) - { - if (param.Name != null) - { - stringBuilder.Append($" -{param.Name} {param.Value}"); - } - else - { - stringBuilder.Append($" {param.Value}"); - } - } - - stringBuilder.AppendLine(); - } - - return stringBuilder.ToString(); - } - - /// - /// This function restores the execution policy for the process by examining the user's - /// execution policy hierarchy. We do this because the process policy will always be set to - /// Bypass when initializing our runspaces. - /// - internal void RestoreExecutionPolicy() - { - // Execution policy is a Windows-only feature. - if (!VersionUtils.IsWindows) - { - return; - } - - this.logger.LogTrace("Restoring execution policy..."); - - // We want to get the list hierarchy of execution policies - // Calling the cmdlet is the simplest way to do that - IReadOnlyList policies = this.powerShell - .AddCommand("Microsoft.PowerShell.Security\\Get-ExecutionPolicy") - .AddParameter("-List") - .Invoke(); - - this.powerShell.Commands.Clear(); - - // The policies come out in the following order: - // - MachinePolicy - // - UserPolicy - // - Process - // - CurrentUser - // - LocalMachine - // We want to ignore policy settings, since we'll already have those anyway. - // Then we need to look at the CurrentUser setting, and then the LocalMachine setting. - // - // Get-ExecutionPolicy -List emits PSObjects with Scope and ExecutionPolicy note properties - // set to expected values, so we must sift through those. - - ExecutionPolicy policyToSet = ExecutionPolicy.Bypass; - var currentUserPolicy = (ExecutionPolicy)policies[policies.Count - 2].Members["ExecutionPolicy"].Value; - if (currentUserPolicy != ExecutionPolicy.Undefined) - { - policyToSet = currentUserPolicy; - } - else - { - var localMachinePolicy = (ExecutionPolicy)policies[policies.Count - 1].Members["ExecutionPolicy"].Value; - if (localMachinePolicy != ExecutionPolicy.Undefined) - { - policyToSet = localMachinePolicy; - } - } - - // If there's nothing to do, save ourselves a PowerShell invocation - if (policyToSet == ExecutionPolicy.Bypass) - { - this.logger.LogTrace("Execution policy already set to Bypass. Skipping execution policy set"); - return; - } - - // Finally set the inherited execution policy - this.logger.LogTrace($"Setting execution policy to {policyToSet}"); - try - { - this.powerShell - .AddCommand("Microsoft.PowerShell.Security\\Set-ExecutionPolicy") - .AddParameter("Scope", ExecutionPolicyScope.Process) - .AddParameter("ExecutionPolicy", policyToSet) - .AddParameter("Force") - .Invoke(); - } - catch (CmdletInvocationException e) - { - this.logger.LogHandledException( - $"Error occurred calling 'Set-ExecutionPolicy -Scope Process -ExecutionPolicy {policyToSet} -Force'", e); - } - finally - { - this.powerShell.Commands.Clear(); - } - } - - private SessionDetails GetSessionDetails(Func invokeAction) - { - try - { - this.mostRecentSessionDetails = - new SessionDetails( - invokeAction( - SessionDetails.GetDetailsCommand())); - - return this.mostRecentSessionDetails; - } - catch (RuntimeException e) - { - this.logger.LogHandledException("Runtime exception occurred while gathering runspace info", e); - } - catch (ArgumentNullException) - { - this.logger.LogError("Could not retrieve session details but no exception was thrown."); - } - - // TODO: Return a harmless object if necessary - this.mostRecentSessionDetails = null; - return this.mostRecentSessionDetails; - } - - private async Task GetSessionDetailsInRunspaceAsync() - { - using (RunspaceHandle runspaceHandle = await this.GetRunspaceHandleAsync().ConfigureAwait(false)) - { - return this.GetSessionDetailsInRunspace(runspaceHandle.Runspace); - } - } - - private SessionDetails GetSessionDetailsInRunspace(Runspace runspace) - { - SessionDetails sessionDetails = - this.GetSessionDetails( - command => - { - using (SMA.PowerShell powerShell = SMA.PowerShell.Create()) - { - powerShell.Runspace = runspace; - powerShell.Commands = command; - - return - powerShell - .Invoke() - .FirstOrDefault(); - } - }); - - return sessionDetails; - } - - private SessionDetails GetSessionDetailsInDebugger() - { - return this.GetSessionDetails( - command => - { - // Use LastOrDefault to get the last item returned. This - // is necessary because advanced prompt functions (like those - // in posh-git) may return multiple objects in the result. - return - this.ExecuteCommandInDebugger(command, false) - .LastOrDefault(); - }); - } - - private SessionDetails GetSessionDetailsInNestedPipeline() - { - // We don't need to check what thread we're on here. If it's a local - // nested pipeline then we will already be on the correct thread, and - // non-debugger nested pipelines aren't supported in remote runspaces. - return this.GetSessionDetails( - command => - { - using (var localPwsh = SMA.PowerShell.Create(RunspaceMode.CurrentRunspace)) - { - localPwsh.Commands = command; - return localPwsh.Invoke().FirstOrDefault(); - } - }); - } - - private void SetProfileVariableInCurrentRunspace(ProfilePathInfo profilePaths) - { - // Create the $profile variable - PSObject profile = new PSObject(profilePaths.CurrentUserCurrentHost); - - profile.Members.Add( - new PSNoteProperty( - nameof(profilePaths.AllUsersAllHosts), - profilePaths.AllUsersAllHosts)); - - profile.Members.Add( - new PSNoteProperty( - nameof(profilePaths.AllUsersCurrentHost), - profilePaths.AllUsersCurrentHost)); - - profile.Members.Add( - new PSNoteProperty( - nameof(profilePaths.CurrentUserAllHosts), - profilePaths.CurrentUserAllHosts)); - - profile.Members.Add( - new PSNoteProperty( - nameof(profilePaths.CurrentUserCurrentHost), - profilePaths.CurrentUserCurrentHost)); - - this.logger.LogTrace( - $"Setting $profile variable in runspace. Current user host profile path: {profilePaths.CurrentUserCurrentHost}"); - - // Set the variable in the runspace - this.powerShell.Commands.Clear(); - this.powerShell - .AddCommand("Set-Variable") - .AddParameter("Name", "profile") - .AddParameter("Value", profile) - .AddParameter("Option", "None"); - this.powerShell.Invoke(); - this.powerShell.Commands.Clear(); - } - - private void HandleRunspaceStateChanged(object sender, RunspaceStateEventArgs args) - { - switch (args.RunspaceStateInfo.State) - { - case RunspaceState.Opening: - case RunspaceState.Opened: - // These cases don't matter, just return - return; - - case RunspaceState.Closing: - case RunspaceState.Closed: - case RunspaceState.Broken: - // If the runspace closes or fails, pop the runspace - ((IHostSupportsInteractiveSession)this).PopRunspace(); - break; - } - } - - private static IEnumerable GetLoadableProfilePaths(ProfilePathInfo profilePaths) - { - if (profilePaths == null) - { - yield break; - } - - foreach (string path in new [] { profilePaths.AllUsersAllHosts, profilePaths.AllUsersCurrentHost, profilePaths.CurrentUserAllHosts, profilePaths.CurrentUserCurrentHost }) - { - if (path != null && File.Exists(path)) - { - yield return path; - } - } - } - - #endregion - - #region Events - - // NOTE: This event is 'internal' because the DebugService provides - // the publicly consumable event. - internal event EventHandler DebuggerStop; - - /// - /// Raised when the debugger is resumed after it was previously stopped. - /// - public event EventHandler DebuggerResumed; - - private void StartCommandLoopOnRunspaceAvailable() - { - if (Interlocked.CompareExchange(ref this.isCommandLoopRestarterSet, 1, 1) == 1) - { - return; - } - - void availabilityChangedHandler(object runspace, RunspaceAvailabilityEventArgs eventArgs) - { - if (eventArgs.RunspaceAvailability != RunspaceAvailability.Available || - this.versionSpecificOperations.IsDebuggerStopped(this.PromptNest, (Runspace)runspace)) - { - return; - } - - ((Runspace)runspace).AvailabilityChanged -= availabilityChangedHandler; - Interlocked.Exchange(ref this.isCommandLoopRestarterSet, 0); - this.ConsoleReader?.StartCommandLoop(); - } - - this.CurrentRunspace.Runspace.AvailabilityChanged += availabilityChangedHandler; - Interlocked.Exchange(ref this.isCommandLoopRestarterSet, 1); - } - - private void OnDebuggerStop(object sender, DebuggerStopEventArgs e) - { - // We maintain the current stop event args so that we can use it in the DebugServer to fire the "stopped" event - // when the DebugServer is fully started. - CurrentDebuggerStopEventArgs = e; - - // If this event has fired but the LSP debug server is not active, it means that the - // PowerShell debugger has started some other way (most likely an existing PSBreakPoint - // was executed). So not only do we have to start the server, but later we will be - // responsible for stopping it too. - if (!IsDebugServerActive) - { - logger.LogDebug("Starting LSP debugger because PowerShell debugger is running!"); - OwnsDebugServerState = true; - _languageServer?.SendNotification("powerShell/startDebugger"); - } - - // We've hit a breakpoint so go to a new line so that the prompt can be rendered. - this.WriteOutput("", includeNewLine: true); - - if (CurrentRunspace.Context == RunspaceContext.Original) - { - StartCommandLoopOnRunspaceAvailable(); - } - - this.logger.LogTrace("Debugger stopped execution."); - - PromptNest.PushPromptContext( - IsCurrentRunspaceOutOfProcess() - ? PromptNestFrameType.Debug | PromptNestFrameType.Remote - : PromptNestFrameType.Debug); - - ThreadController localThreadController = PromptNest.GetThreadController(); - - // Update the session state - this.OnSessionStateChanged( - this, - new SessionStateChangedEventArgs( - PowerShellContextState.Ready, - PowerShellExecutionResult.Stopped, - null)); - - // Get the session details and push the current - // runspace if the session has changed - SessionDetails sessionDetails = null; - try - { - sessionDetails = this.GetSessionDetailsInDebugger(); - } - catch (InvalidOperationException) - { - this.logger.LogTrace( - "Attempting to get session details failed, most likely due to a running pipeline that is attempting to stop."); - } - - if (!localThreadController.FrameExitTask.Task.IsCompleted) - { - // Push the current runspace if the session has changed - this.UpdateRunspaceDetailsIfSessionChanged(sessionDetails, isDebuggerStop: true); - - // Raise the event for the debugger service - this.DebuggerStop?.Invoke(sender, e); - } - - this.logger.LogTrace("Starting pipeline thread message loop..."); - - Task localPipelineExecutionTask = - localThreadController.TakeExecutionRequestAsync(); - Task localDebuggerStoppedTask = - localThreadController.Exit(); - while (true) - { - int taskIndex = - Task.WaitAny( - localDebuggerStoppedTask, - localPipelineExecutionTask); - - if (taskIndex == 0) - { - // Write a new output line before continuing - this.WriteOutput("", true); - - e.ResumeAction = localDebuggerStoppedTask.GetAwaiter().GetResult(); - this.logger.LogTrace("Received debugger resume action " + e.ResumeAction.ToString()); - - // Since we are no longer at a breakpoint, we set this to null. - CurrentDebuggerStopEventArgs = null; - - // Notify listeners that the debugger has resumed - this.DebuggerResumed?.Invoke(this, e.ResumeAction); - - // Pop the current RunspaceDetails if we were attached - // to a runspace and the resume action is Stop - if (this.CurrentRunspace.Context == RunspaceContext.DebuggedRunspace && - e.ResumeAction == DebuggerResumeAction.Stop) - { - this.PopRunspace(); - } - else if (e.ResumeAction != DebuggerResumeAction.Stop) - { - // Update the session state - this.OnSessionStateChanged( - this, - new SessionStateChangedEventArgs( - PowerShellContextState.Running, - PowerShellExecutionResult.NotFinished, - null)); - } - - break; - } - else if (taskIndex == 1) - { - this.logger.LogTrace("Received pipeline thread execution request."); - - IPipelineExecutionRequest executionRequest = localPipelineExecutionTask.Result; - localPipelineExecutionTask = localThreadController.TakeExecutionRequestAsync(); - executionRequest.ExecuteAsync().GetAwaiter().GetResult(); - - this.logger.LogTrace("Pipeline thread execution completed."); - - if (!this.versionSpecificOperations.IsDebuggerStopped( - this.PromptNest, - this.CurrentRunspace.Runspace)) - { - // Since we are no longer at a breakpoint, we set this to null. - CurrentDebuggerStopEventArgs = null; - - if (this.CurrentRunspace.Context == RunspaceContext.DebuggedRunspace) - { - // Notify listeners that the debugger has resumed - this.DebuggerResumed?.Invoke(this, DebuggerResumeAction.Stop); - - // We're detached from the runspace now, send a runspace update. - this.PopRunspace(); - } - - // If the executed command caused the debugger to exit, break - // from the pipeline loop - break; - } - } - else - { - // TODO: How to handle this? - this.logger.LogError($"Unhandled TaskIndex: {taskIndex}"); - } - } - - PromptNest.PopPromptContext(); - } - - // NOTE: This event is 'internal' because the DebugService provides - // the publicly consumable event. - internal event EventHandler BreakpointUpdated; - - private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) - { - this.BreakpointUpdated?.Invoke(sender, e); - } - - #endregion - - #region Nested Classes - - private void ConfigureRunspaceCapabilities(RunspaceDetails runspaceDetails) - { - DscBreakpointCapability.CheckForCapability(this.CurrentRunspace, this, this.logger); - } - - private void PushRunspace(RunspaceDetails newRunspaceDetails) - { - this.logger.LogTrace( - $"Pushing {this.CurrentRunspace.Location} ({this.CurrentRunspace.Context}), new runspace is {newRunspaceDetails.Location} ({newRunspaceDetails.Context}), connection: {newRunspaceDetails.ConnectionString}"); - - RunspaceDetails previousRunspace = this.CurrentRunspace; - - if (newRunspaceDetails.Context == RunspaceContext.DebuggedRunspace) - { - this.WriteOutput( - $"Entering debugged runspace on {newRunspaceDetails.Location.ToString().ToLower()} machine {newRunspaceDetails.SessionDetails.ComputerName}", - true); - } - - // Switch out event handlers if necessary - if (CheckIfRunspaceNeedsEventHandlers(newRunspaceDetails)) - { - this.CleanupRunspace(previousRunspace); - this.ConfigureRunspace(newRunspaceDetails); - } - - this.runspaceStack.Push(previousRunspace); - this.CurrentRunspace = newRunspaceDetails; - - // Check for runspace capabilities - this.ConfigureRunspaceCapabilities(newRunspaceDetails); - - this.OnRunspaceChanged( - this, - new RunspaceChangedEventArgs( - RunspaceChangeAction.Enter, - previousRunspace, - this.CurrentRunspace)); - } - - private void UpdateRunspaceDetailsIfSessionChanged(SessionDetails sessionDetails, bool isDebuggerStop = false) - { - RunspaceDetails newRunspaceDetails = null; - - // If we've exited an entered process or debugged runspace, pop what we've - // got before we evaluate where we're at - if ( - (this.CurrentRunspace.Context == RunspaceContext.DebuggedRunspace && - this.CurrentRunspace.SessionDetails.InstanceId != sessionDetails.InstanceId) || - (this.CurrentRunspace.Context == RunspaceContext.EnteredProcess && - this.CurrentRunspace.SessionDetails.ProcessId != sessionDetails.ProcessId)) - { - this.PopRunspace(); - } - - // Are we in a new session that the PushRunspace command won't - // notify us about? - // - // Possible cases: - // - Debugged runspace in a local or remote session - // - Entered process in a remote session - // - // We don't need additional logic to check for the cases that - // PowerShell would have notified us about because the CurrentRunspace - // will already be updated by PowerShell by the time we reach - // these checks. - - if (this.CurrentRunspace.SessionDetails.InstanceId != sessionDetails.InstanceId && isDebuggerStop) - { - // Are we on a local or remote computer? - bool differentComputer = - !string.Equals( - sessionDetails.ComputerName, - this.initialRunspace.SessionDetails.ComputerName, - StringComparison.CurrentCultureIgnoreCase); - - // We started debugging a runspace - newRunspaceDetails = - RunspaceDetails.CreateFromDebugger( - this.CurrentRunspace, - differentComputer ? RunspaceLocation.Remote : RunspaceLocation.Local, - RunspaceContext.DebuggedRunspace, - sessionDetails); - } - else if (this.CurrentRunspace.SessionDetails.ProcessId != sessionDetails.ProcessId) - { - // We entered a different PowerShell host process - newRunspaceDetails = - RunspaceDetails.CreateFromContext( - this.CurrentRunspace, - RunspaceContext.EnteredProcess, - sessionDetails); - } - - if (newRunspaceDetails != null) - { - this.PushRunspace(newRunspaceDetails); - } - } - - private void PopRunspace() - { - if (this.SessionState != PowerShellContextState.Disposed) - { - if (this.runspaceStack.Count > 0) - { - RunspaceDetails previousRunspace = this.CurrentRunspace; - this.CurrentRunspace = this.runspaceStack.Pop(); - - this.logger.LogTrace( - $"Popping {previousRunspace.Location} ({previousRunspace.Context}), new runspace is {this.CurrentRunspace.Location} ({this.CurrentRunspace.Context}), connection: {this.CurrentRunspace.ConnectionString}"); - - if (previousRunspace.Context == RunspaceContext.DebuggedRunspace) - { - this.WriteOutput( - $"Leaving debugged runspace on {previousRunspace.Location.ToString().ToLower()} machine {previousRunspace.SessionDetails.ComputerName}", - true); - } - - // Switch out event handlers if necessary - if (CheckIfRunspaceNeedsEventHandlers(previousRunspace)) - { - this.CleanupRunspace(previousRunspace); - this.ConfigureRunspace(this.CurrentRunspace); - } - - this.OnRunspaceChanged( - this, - new RunspaceChangedEventArgs( - RunspaceChangeAction.Exit, - previousRunspace, - this.CurrentRunspace)); - } - else - { - this.logger.LogError( - "Caller attempted to pop a runspace when no runspaces are on the stack."); - } - } - } - - #endregion - - #region IHostSupportsInteractiveSession Implementation - - bool IHostSupportsInteractiveSession.IsRunspacePushed - { - get - { - return this.runspaceStack.Count > 0; - } - } - - Runspace IHostSupportsInteractiveSession.Runspace - { - get - { - return this.CurrentRunspace.Runspace; - } - } - - void IHostSupportsInteractiveSession.PushRunspace(Runspace runspace) - { - // Get the session details for the new runspace - SessionDetails sessionDetails = this.GetSessionDetailsInRunspace(runspace); - - this.PushRunspace( - RunspaceDetails.CreateFromRunspace( - runspace, - sessionDetails, - this.logger)); - } - - void IHostSupportsInteractiveSession.PopRunspace() - { - this.PopRunspace(); - } - - #endregion - - /// - /// Encapsulates the locking semantics hacked together for debugging to work. - /// This allows ExecuteCommandAsync locking to work "re-entrantly", - /// while making sure that a debug abort won't corrupt state. - /// - private class SessionStateLock - { - /// - /// The actual lock to acquire to modify the session state of the PowerShellContextService. - /// - private readonly SemaphoreSlim _sessionStateLock; - - /// - /// A lock used by this class to ensure that count incrementing and session state locking happens atomically. - /// - private readonly SemaphoreSlim _internalLock; - - /// - /// A count of how re-entrant the current execute command lock call is, - /// so we can effectively use it as a two-way semaphore. - /// - private int _executeCommandLockCount; - - public SessionStateLock() - { - _sessionStateLock = AsyncUtils.CreateSimpleLockingSemaphore(); - _internalLock = AsyncUtils.CreateSimpleLockingSemaphore(); - _executeCommandLockCount = 0; - } - - public async Task AcquireForExecuteCommandAsync() - { - // Algorithm here is: - // - Acquire the internal lock to keep operations atomic - // - Increment the number of lock holders - // - If we're the only one, acquire the lock - // - Release the internal lock - - await _internalLock.WaitAsync().ConfigureAwait(false); - try - { - if (_executeCommandLockCount++ == 0) - { - await _sessionStateLock.WaitAsync().ConfigureAwait(false); - } - } - finally - { - _internalLock.Release(); - } - } - - public bool TryAcquireForDebuggerAbort() - { - return _sessionStateLock.Wait(0); - } - - public async Task ReleaseForExecuteCommand() - { - // Algorithm here is the opposite of the acquisition algorithm: - // - Acquire the internal lock to ensure the operation is atomic - // - Decrement the lock holder count - // - If we were the last ones, release the lock - // - Release the internal lock - - await _internalLock.WaitAsync().ConfigureAwait(false); - try - { - if (--_executeCommandLockCount == 0) - { - _sessionStateLock.Release(); - } - } - finally - { - _internalLock.Release(); - } - } - - public void ReleaseForDebuggerAbort() - { - _sessionStateLock.Release(); - } - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Capabilities/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Capabilities/DscBreakpointCapability.cs deleted file mode 100644 index 354cb8b60..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Capabilities/DscBreakpointCapability.cs +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Logging; -using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Management.Automation; - - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - internal class DscBreakpointCapability - { - private string[] dscResourceRootPaths = Array.Empty(); - - private Dictionary breakpointsPerFile = - new Dictionary(); - - public async Task SetLineBreakpointsAsync( - PowerShellContextService powerShellContext, - string scriptPath, - BreakpointDetails[] breakpoints) - { - List resultBreakpointDetails = - new List(); - - // We always get the latest array of breakpoint line numbers - // so store that for future use - if (breakpoints.Length > 0) - { - // Set the breakpoints for this scriptPath - this.breakpointsPerFile[scriptPath] = - breakpoints.Select(b => b.LineNumber).ToArray(); - } - else - { - // No more breakpoints for this scriptPath, remove it - this.breakpointsPerFile.Remove(scriptPath); - } - - string hashtableString = - string.Join( - ", ", - this.breakpointsPerFile - .Select(file => $"@{{Path=\"{file.Key}\";Line=@({string.Join(",", file.Value)})}}")); - - // Run Enable-DscDebug as a script because running it as a PSCommand - // causes an error which states that the Breakpoint parameter has not - // been passed. - await powerShellContext.ExecuteScriptStringAsync( - hashtableString.Length > 0 - ? $"Enable-DscDebug -Breakpoint {hashtableString}" - : "Disable-DscDebug", - false, - false).ConfigureAwait(false); - - // Verify all the breakpoints and return them - foreach (var breakpoint in breakpoints) - { - breakpoint.Verified = true; - } - - return breakpoints.ToArray(); - } - - public bool IsDscResourcePath(string scriptPath) - { - return dscResourceRootPaths.Any( - dscResourceRootPath => - scriptPath.StartsWith( - dscResourceRootPath, - StringComparison.CurrentCultureIgnoreCase)); - } - - public static DscBreakpointCapability CheckForCapability( - RunspaceDetails runspaceDetails, - PowerShellContextService powerShellContext, - ILogger logger) - { - DscBreakpointCapability capability = null; - - // DSC support is enabled only for Windows PowerShell. - if ((runspaceDetails.PowerShellVersion.Version.Major < 6) && - (runspaceDetails.Context != RunspaceContext.DebuggedRunspace)) - { - using (PowerShell powerShell = PowerShell.Create()) - { - powerShell.Runspace = runspaceDetails.Runspace; - - // Attempt to import the updated DSC module - powerShell.AddCommand("Import-Module"); - powerShell.AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1"); - powerShell.AddParameter("PassThru"); - powerShell.AddParameter("ErrorAction", "Ignore"); - - PSObject moduleInfo = null; - - try - { - moduleInfo = powerShell.Invoke().FirstOrDefault(); - } - catch (RuntimeException e) - { - logger.LogException("Could not load the DSC module!", e); - } - - if (moduleInfo != null) - { - logger.LogTrace("Side-by-side DSC module found, gathering DSC resource paths..."); - - // The module was loaded, add the breakpoint capability - capability = new DscBreakpointCapability(); - runspaceDetails.AddCapability(capability); - - powerShell.Commands.Clear(); - powerShell - .AddCommand("Microsoft.PowerShell.Utility\\Write-Host") - .AddArgument("Gathering DSC resource paths, this may take a while...") - .Invoke(); - - // Get the list of DSC resource paths - powerShell.Commands.Clear(); - powerShell - .AddCommand("Get-DscResource") - .AddCommand("Select-Object") - .AddParameter("ExpandProperty", "ParentPath"); - - Collection resourcePaths = null; - - try - { - resourcePaths = powerShell.Invoke(); - } - catch (CmdletInvocationException e) - { - logger.LogException("Get-DscResource failed!", e); - } - - if (resourcePaths != null) - { - capability.dscResourceRootPaths = - resourcePaths - .Select(o => (string)o.BaseObject) - .ToArray(); - - logger.LogTrace($"DSC resources found: {resourcePaths.Count}"); - } - else - { - logger.LogTrace($"No DSC resources found."); - } - } - else - { - logger.LogTrace($"Side-by-side DSC module was not found."); - } - } - } - - return capability; - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionOptions.cs deleted file mode 100644 index 05535d516..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionOptions.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Defines options for the execution of a command. - /// - internal class ExecutionOptions - { - private bool? _shouldExecuteInOriginalRunspace; - - #region Properties - - /// - /// Gets or sets a boolean that determines whether command output - /// should be written to the host. - /// - public bool WriteOutputToHost { get; set; } - - /// - /// Gets or sets a boolean that determines whether command errors - /// should be written to the host. - /// - public bool WriteErrorsToHost { get; set; } - - /// - /// Gets or sets a boolean that determines whether the executed - /// command should be added to the command history. - /// - public bool AddToHistory { get; set; } - - /// - /// Gets or sets a boolean that determines whether the execution - /// of the command should interrupt the command prompt. Should - /// only be set if WriteOutputToHost is false but the command - /// should still interrupt the command prompt. - /// - public bool InterruptCommandPrompt { get; set; } - - /// - /// Gets or sets a value indicating whether the text of the command - /// should be written to the host as if it was ran interactively. - /// - public bool WriteInputToHost { get; set; } - - /// - /// If this is set, we will use this string for history and writing to the host - /// instead of grabbing the command from the PSCommand. - /// - public string InputString { get; set; } - - /// - /// If this is set, we will use this string for history and writing to the host - /// instead of grabbing the command from the PSCommand. - /// - public bool UseNewScope { get; set; } - - /// - /// Gets or sets a value indicating whether the command to - /// be executed is a console input prompt, such as the - /// PSConsoleHostReadLine function. - /// - internal bool IsReadLine { get; set; } - - /// - /// Gets or sets a value indicating whether the command should - /// be invoked in the original runspace. In the majority of cases - /// this should remain unset. - /// - internal bool ShouldExecuteInOriginalRunspace - { - get - { - return _shouldExecuteInOriginalRunspace ?? IsReadLine; - } - set - { - _shouldExecuteInOriginalRunspace = value; - } - } - - #endregion - - #region Constructors - - /// - /// Creates an instance of the ExecutionOptions class with - /// default settings configured. - /// - public ExecutionOptions() - { - this.WriteOutputToHost = true; - this.WriteErrorsToHost = true; - this.WriteInputToHost = false; - this.AddToHistory = false; - this.InterruptCommandPrompt = false; - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionStatus.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionStatus.cs deleted file mode 100644 index fb88d6013..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionStatus.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Enumerates the possible execution results that can occur after - /// executing a command or script. - /// - internal enum ExecutionStatus - { - /// - /// Indicates that execution has not yet started. - /// - Pending, - - /// - /// Indicates that the command is executing. - /// - Running, - - /// - /// Indicates that execution has failed. - /// - Failed, - - /// - /// Indicates that execution was aborted by the user. - /// - Aborted, - - /// - /// Indicates that execution completed successfully. - /// - Completed - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionStatusChangedEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionStatusChangedEventArgs.cs deleted file mode 100644 index bb0cc6cc7..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionStatusChangedEventArgs.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Contains details about an executed - /// - internal class ExecutionStatusChangedEventArgs - { - #region Properties - - /// - /// Gets the options used when the command was executed. - /// - public ExecutionOptions ExecutionOptions { get; private set; } - - /// - /// Gets the command execution's current status. - /// - public ExecutionStatus ExecutionStatus { get; private set; } - - /// - /// If true, the command execution had errors. - /// - public bool HadErrors { get; private set; } - - #endregion - - #region Constructors - - /// - /// Creates an instance of the ExecutionStatusChangedEventArgs class. - /// - /// The command execution's current status. - /// The options used when the command was executed. - /// If execution has completed, indicates whether there were errors. - public ExecutionStatusChangedEventArgs( - ExecutionStatus executionStatus, - ExecutionOptions executionOptions, - bool hadErrors) - { - this.ExecutionStatus = executionStatus; - this.ExecutionOptions = executionOptions; - this.HadErrors = hadErrors || (executionStatus == ExecutionStatus.Failed); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionTarget.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionTarget.cs deleted file mode 100644 index 097b53426..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionTarget.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Represents the different API's available for executing commands. - /// - internal enum ExecutionTarget - { - /// - /// Indicates that the command should be invoked through the PowerShell debugger. - /// - Debugger, - - /// - /// Indicates that the command should be invoked via an instance of the PowerShell class. - /// - PowerShell, - - /// - /// Indicates that the command should be invoked through the PowerShell engine's event manager. - /// - InvocationEvent - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHost.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHost.cs deleted file mode 100644 index 9cbc1fef7..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHost.cs +++ /dev/null @@ -1,392 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Hosting; -using System; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides an implementation of the PSHost class for the - /// ConsoleService and routes its calls to an IConsoleHost - /// implementation. - /// - internal class EditorServicesPSHost : PSHost, IHostSupportsInteractiveSession - { - #region Private Fields - - private ILogger Logger; - private HostStartupInfo hostDetails; - private Guid instanceId = Guid.NewGuid(); - private EditorServicesPSHostUserInterface hostUserInterface; - private IHostSupportsInteractiveSession hostSupportsInteractiveSession; - private PowerShellContextService powerShellContext; - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the ConsoleServicePSHost class - /// with the given IConsoleHost implementation. - /// - /// - /// An implementation of IHostSupportsInteractiveSession for runspace management. - /// - /// - /// Provides details about the host application. - /// - /// - /// The EditorServicesPSHostUserInterface implementation to use for this host. - /// - /// An ILogger implementation to use for this host. - public EditorServicesPSHost( - PowerShellContextService powerShellContext, - HostStartupInfo hostDetails, - EditorServicesPSHostUserInterface hostUserInterface, - ILogger logger) - { - this.Logger = logger; - this.hostDetails = hostDetails; - this.hostUserInterface = hostUserInterface; - this.hostSupportsInteractiveSession = powerShellContext; - this.powerShellContext = powerShellContext; - } - - #endregion - - #region PSHost Implementation - - /// - /// - /// - public override Guid InstanceId - { - get { return this.instanceId; } - } - - /// - /// - /// - public override string Name - { - get { return this.hostDetails.Name; } - } - - internal class ConsoleColorProxy - { - private EditorServicesPSHostUserInterface _hostUserInterface; - - internal ConsoleColorProxy(EditorServicesPSHostUserInterface hostUserInterface) - { - if (hostUserInterface == null) throw new ArgumentNullException(nameof(hostUserInterface)); - _hostUserInterface = hostUserInterface; - } - - /// - /// The Accent Color for Formatting - /// - public ConsoleColor FormatAccentColor - { - get - { return _hostUserInterface.FormatAccentColor; } - set - { _hostUserInterface.FormatAccentColor = value; } - } - - /// - /// The Accent Color for Error - /// - public ConsoleColor ErrorAccentColor - { - get - { return _hostUserInterface.ErrorAccentColor; } - set - { _hostUserInterface.ErrorAccentColor = value; } - } - - /// - /// The ForegroundColor for Error - /// - public ConsoleColor ErrorForegroundColor - { - get - { return _hostUserInterface.ErrorForegroundColor; } - set - { _hostUserInterface.ErrorForegroundColor = value; } - } - - /// - /// The BackgroundColor for Error - /// - public ConsoleColor ErrorBackgroundColor - { - get - { return _hostUserInterface.ErrorBackgroundColor; } - set - { _hostUserInterface.ErrorBackgroundColor = value; } - } - - /// - /// The ForegroundColor for Warning - /// - public ConsoleColor WarningForegroundColor - { - get - { return _hostUserInterface.WarningForegroundColor; } - set - { _hostUserInterface.WarningForegroundColor = value; } - } - - /// - /// The BackgroundColor for Warning - /// - public ConsoleColor WarningBackgroundColor - { - get - { return _hostUserInterface.WarningBackgroundColor; } - set - { _hostUserInterface.WarningBackgroundColor = value; } - } - - /// - /// The ForegroundColor for Debug - /// - public ConsoleColor DebugForegroundColor - { - get - { return _hostUserInterface.DebugForegroundColor; } - set - { _hostUserInterface.DebugForegroundColor = value; } - } - - /// - /// The BackgroundColor for Debug - /// - public ConsoleColor DebugBackgroundColor - { - get - { return _hostUserInterface.DebugBackgroundColor; } - set - { _hostUserInterface.DebugBackgroundColor = value; } - } - - /// - /// The ForegroundColor for Verbose - /// - public ConsoleColor VerboseForegroundColor - { - get - { return _hostUserInterface.VerboseForegroundColor; } - set - { _hostUserInterface.VerboseForegroundColor = value; } - } - - /// - /// The BackgroundColor for Verbose - /// - public ConsoleColor VerboseBackgroundColor - { - get - { return _hostUserInterface.VerboseBackgroundColor; } - set - { _hostUserInterface.VerboseBackgroundColor = value; } - } - - /// - /// The ForegroundColor for Progress - /// - public ConsoleColor ProgressForegroundColor - { - get - { return _hostUserInterface.ProgressForegroundColor; } - set - { _hostUserInterface.ProgressForegroundColor = value; } - } - - /// - /// The BackgroundColor for Progress - /// - public ConsoleColor ProgressBackgroundColor - { - get - { return _hostUserInterface.ProgressBackgroundColor; } - set - { _hostUserInterface.ProgressBackgroundColor = value; } - } - } - - /// - /// Return the actual console host object so that the user can get at - /// the unproxied methods. - /// - public override PSObject PrivateData - { - get - { - if (hostUserInterface == null) return null; - return _consoleColorProxy ?? (_consoleColorProxy = PSObject.AsPSObject(new ConsoleColorProxy(hostUserInterface))); - } - } - private PSObject _consoleColorProxy; - - /// - /// - /// - public override Version Version - { - get { return this.hostDetails.Version; } - } - - // TODO: Pull these from IConsoleHost - - /// - /// - /// - public override System.Globalization.CultureInfo CurrentCulture - { - get { return System.Globalization.CultureInfo.CurrentCulture; } - } - - /// - /// - /// - public override System.Globalization.CultureInfo CurrentUICulture - { - get { return System.Globalization.CultureInfo.CurrentUICulture; } - } - - /// - /// - /// - public override PSHostUserInterface UI - { - get { return this.hostUserInterface; } - } - - /// - /// - /// - public override void EnterNestedPrompt() - { - this.powerShellContext.EnterNestedPrompt(); - } - - /// - /// - /// - public override void ExitNestedPrompt() - { - this.powerShellContext.ExitNestedPrompt(); - } - - /// - /// - /// - public override void NotifyBeginApplication() - { - Logger.LogTrace("NotifyBeginApplication() called."); - this.hostUserInterface.IsNativeApplicationRunning = true; - } - - /// - /// - /// - public override void NotifyEndApplication() - { - Logger.LogTrace("NotifyEndApplication() called."); - this.hostUserInterface.IsNativeApplicationRunning = false; - } - - /// - /// - /// - /// - public override void SetShouldExit(int exitCode) - { - if (this.IsRunspacePushed) - { - this.PopRunspace(); - } - } - - #endregion - - #region IHostSupportsInteractiveSession Implementation - - /// - /// - /// - /// - public bool IsRunspacePushed - { - get - { - if (this.hostSupportsInteractiveSession != null) - { - return this.hostSupportsInteractiveSession.IsRunspacePushed; - } - else - { - throw new NotImplementedException(); - } - } - } - - /// - /// - /// - /// - public Runspace Runspace - { - get - { - if (this.hostSupportsInteractiveSession != null) - { - return this.hostSupportsInteractiveSession.Runspace; - } - else - { - throw new NotImplementedException(); - } - } - } - - /// - /// - /// - /// - public void PushRunspace(Runspace runspace) - { - if (this.hostSupportsInteractiveSession != null) - { - this.hostSupportsInteractiveSession.PushRunspace(runspace); - } - else - { - throw new NotImplementedException(); - } - } - - /// - /// - /// - public void PopRunspace() - { - if (this.hostSupportsInteractiveSession != null) - { - this.hostSupportsInteractiveSession.PopRunspace(); - } - else - { - throw new NotImplementedException(); - } - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHostUserInterface.cs deleted file mode 100644 index 744cd0c41..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHostUserInterface.cs +++ /dev/null @@ -1,1071 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Linq; -using System.Security; -using System.Threading.Tasks; -using System.Threading; -using System.Globalization; -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Logging; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides an implementation of the PSHostUserInterface class - /// for the ConsoleService and routes its calls to an IConsoleHost - /// implementation. - /// - internal abstract class EditorServicesPSHostUserInterface : - PSHostUserInterface, - IHostInput, - IHostOutput, - IHostUISupportsMultipleChoiceSelection - { - #region Private Fields - - private readonly ConcurrentDictionary currentProgressMessages = - new ConcurrentDictionary(); - - private PromptHandler activePromptHandler; - private PSHostRawUserInterface rawUserInterface; - private CancellationTokenSource commandLoopCancellationToken; - - /// - /// The PowerShellContext to use for executing commands. - /// - protected PowerShellContextService powerShellContext; - - #endregion - - #region Public Constants - - /// - /// Gets a const string for the console's debug message prefix. - /// - public const string DebugMessagePrefix = "DEBUG: "; - - /// - /// Gets a const string for the console's warning message prefix. - /// - public const string WarningMessagePrefix = "WARNING: "; - - /// - /// Gets a const string for the console's verbose message prefix. - /// - public const string VerboseMessagePrefix = "VERBOSE: "; - - #endregion - - #region Properties - - /// - /// Returns true if the host supports VT100 output codes. - /// - public override bool SupportsVirtualTerminal => false; - - /// - /// Returns true if a native application is currently running. - /// - public bool IsNativeApplicationRunning { get; internal set; } - - private bool IsCommandLoopRunning { get; set; } - - /// - /// Gets the ILogger implementation used for this host. - /// - protected ILogger Logger { get; private set; } - - /// - /// Gets a value indicating whether writing progress is supported. - /// - internal protected virtual bool SupportsWriteProgress => false; - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the ConsoleServicePSHostUserInterface - /// class with the given IConsoleHost implementation. - /// - /// The PowerShellContext to use for executing commands. - /// The PSHostRawUserInterface implementation to use for this host. - /// An ILogger implementation to use for this host. - public EditorServicesPSHostUserInterface( - PowerShellContextService powerShellContext, - PSHostRawUserInterface rawUserInterface, - ILogger logger) - { - this.Logger = logger; - this.powerShellContext = powerShellContext; - this.rawUserInterface = rawUserInterface; - - this.powerShellContext.DebuggerStop += PowerShellContext_DebuggerStop; - this.powerShellContext.DebuggerResumed += PowerShellContext_DebuggerResumed; - this.powerShellContext.ExecutionStatusChanged += PowerShellContext_ExecutionStatusChanged; - } - - #endregion - - #region Public Methods - - /// - /// Starts the host's interactive command loop. - /// - public void StartCommandLoop() - { - if (!this.IsCommandLoopRunning) - { - this.IsCommandLoopRunning = true; - this.ShowCommandPrompt(); - } - } - - /// - /// Stops the host's interactive command loop. - /// - public void StopCommandLoop() - { - if (this.IsCommandLoopRunning) - { - this.IsCommandLoopRunning = false; - this.CancelCommandPrompt(); - } - } - - private void ShowCommandPrompt() - { - if (this.commandLoopCancellationToken == null) - { - this.commandLoopCancellationToken = new CancellationTokenSource(); - Task.Run(() => this.StartReplLoopAsync(this.commandLoopCancellationToken.Token)); - } - else - { - Logger.LogTrace("StartReadLoop called while read loop is already running"); - } - } - - private void CancelCommandPrompt() - { - if (this.commandLoopCancellationToken != null) - { - // Set this to false so that Ctrl+C isn't trapped by any - // lingering ReadKey - // TOOD: Move this to Terminal impl! - //Console.TreatControlCAsInput = false; - - this.commandLoopCancellationToken.Cancel(); - this.commandLoopCancellationToken = null; - } - } - - /// - /// Cancels the currently executing command or prompt. - /// - public void SendControlC() - { - if (this.activePromptHandler != null) - { - this.activePromptHandler.CancelPrompt(); - } - else - { - // Cancel the current execution - this.powerShellContext.AbortExecution(); - } - } - - #endregion - - #region Abstract Methods - - /// - /// Requests that the HostUI implementation read a command line - /// from the user to be executed in the integrated console command - /// loop. - /// - /// - /// A CancellationToken used to cancel the command line request. - /// - /// A Task that can be awaited for the resulting input string. - protected abstract Task ReadCommandLineAsync(CancellationToken cancellationToken); - - /// - /// Creates an InputPrompt handle to use for displaying input - /// prompts to the user. - /// - /// A new InputPromptHandler instance. - protected abstract InputPromptHandler OnCreateInputPromptHandler(); - - /// - /// Creates a ChoicePromptHandler to use for displaying a - /// choice prompt to the user. - /// - /// A new ChoicePromptHandler instance. - protected abstract ChoicePromptHandler OnCreateChoicePromptHandler(); - - /// - /// Writes output of the given type to the user interface with - /// the given foreground and background colors. Also includes - /// a newline if requested. - /// - /// - /// The output string to be written. - /// - /// - /// If true, a newline should be appended to the output's contents. - /// - /// - /// Specifies the type of output to be written. - /// - /// - /// Specifies the foreground color of the output to be written. - /// - /// - /// Specifies the background color of the output to be written. - /// - public abstract void WriteOutput( - string outputString, - bool includeNewLine, - OutputType outputType, - ConsoleColor foregroundColor, - ConsoleColor backgroundColor); - - /// - /// Sends a progress update event to the user. - /// - /// The source ID of the progress event. - /// The details of the activity's current progress. - protected abstract void UpdateProgress( - long sourceId, - ProgressDetails progressDetails); - - #endregion - - #region IHostInput Implementation - - #endregion - - #region PSHostUserInterface Implementation - - /// - /// - /// - /// - /// - /// - /// - public override Dictionary Prompt( - string promptCaption, - string promptMessage, - Collection fieldDescriptions) - { - FieldDetails[] fields = - fieldDescriptions - .Select(f => { return FieldDetails.Create(f, this.Logger); }) - .ToArray(); - - CancellationTokenSource cancellationToken = new CancellationTokenSource(); - Task> promptTask = - this.CreateInputPromptHandler() - .PromptForInputAsync( - promptCaption, - promptMessage, - fields, - cancellationToken.Token); - - // Run the prompt task and wait for it to return - this.WaitForPromptCompletion( - promptTask, - "Prompt", - cancellationToken); - - // Convert all values to PSObjects - var psObjectDict = new Dictionary(); - - // The result will be null if the prompt was cancelled - if (promptTask.Result != null) - { - // Convert all values to PSObjects - foreach (var keyValuePair in promptTask.Result) - { - psObjectDict.Add( - keyValuePair.Key, - PSObject.AsPSObject(keyValuePair.Value ?? string.Empty)); - } - } - - // Return the result - return psObjectDict; - } - - /// - /// - /// - /// - /// - /// - /// - /// - public override int PromptForChoice( - string promptCaption, - string promptMessage, - Collection choiceDescriptions, - int defaultChoice) - { - ChoiceDetails[] choices = - choiceDescriptions - .Select(ChoiceDetails.Create) - .ToArray(); - - CancellationTokenSource cancellationToken = new CancellationTokenSource(); - Task promptTask = - this.CreateChoicePromptHandler() - .PromptForChoiceAsync( - promptCaption, - promptMessage, - choices, - defaultChoice, - cancellationToken.Token); - - // Run the prompt task and wait for it to return - this.WaitForPromptCompletion( - promptTask, - "PromptForChoice", - cancellationToken); - - // Return the result - return promptTask.Result; - } - - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public override PSCredential PromptForCredential( - string promptCaption, - string promptMessage, - string userName, - string targetName, - PSCredentialTypes allowedCredentialTypes, - PSCredentialUIOptions options) - { - CancellationTokenSource cancellationToken = new CancellationTokenSource(); - - Task> promptTask = - this.CreateInputPromptHandler() - .PromptForInputAsync( - promptCaption, - promptMessage, - new FieldDetails[] { new CredentialFieldDetails("Credential", "Credential", userName) }, - cancellationToken.Token); - - Task unpackTask = - promptTask.ContinueWith( - task => - { - if (task.IsFaulted) - { - throw task.Exception; - } - else if (task.IsCanceled) - { - throw new TaskCanceledException(task); - } - - // Return the value of the sole field - return (PSCredential)task.Result?["Credential"]; - }); - - // Run the prompt task and wait for it to return - this.WaitForPromptCompletion( - unpackTask, - "PromptForCredential", - cancellationToken); - - return unpackTask.Result; - } - - /// - /// - /// - /// - /// - /// - /// - /// - public override PSCredential PromptForCredential( - string caption, - string message, - string userName, - string targetName) - { - return this.PromptForCredential( - caption, - message, - userName, - targetName, - PSCredentialTypes.Default, - PSCredentialUIOptions.Default); - } - - /// - /// - /// - /// - public override PSHostRawUserInterface RawUI - { - get { return this.rawUserInterface; } - } - - /// - /// - /// - /// - public override string ReadLine() - { - CancellationTokenSource cancellationToken = new CancellationTokenSource(); - - Task promptTask = - this.CreateInputPromptHandler() - .PromptForInputAsync(cancellationToken.Token); - - // Run the prompt task and wait for it to return - this.WaitForPromptCompletion( - promptTask, - "ReadLine", - cancellationToken); - - return promptTask.Result; - } - - /// - /// - /// - /// - public override SecureString ReadLineAsSecureString() - { - CancellationTokenSource cancellationToken = new CancellationTokenSource(); - - Task promptTask = - this.CreateInputPromptHandler() - .PromptForSecureInputAsync(cancellationToken.Token); - - // Run the prompt task and wait for it to return - this.WaitForPromptCompletion( - promptTask, - "ReadLineAsSecureString", - cancellationToken); - - return promptTask.Result; - } - - /// - /// - /// - /// - /// - /// - public override void Write( - ConsoleColor foregroundColor, - ConsoleColor backgroundColor, - string value) - { - this.WriteOutput( - value, - false, - OutputType.Normal, - foregroundColor, - backgroundColor); - } - - /// - /// - /// - /// - public override void Write(string value) - { - this.WriteOutput( - value, - false, - OutputType.Normal, - this.rawUserInterface.ForegroundColor, - this.rawUserInterface.BackgroundColor); - } - - /// - /// - /// - /// - public override void WriteLine(string value) - { - this.WriteOutput( - value, - true, - OutputType.Normal, - this.rawUserInterface.ForegroundColor, - this.rawUserInterface.BackgroundColor); - } - - /// - /// - /// - /// - public override void WriteDebugLine(string message) - { - this.WriteOutput( - DebugMessagePrefix + message, - true, - OutputType.Debug, - foregroundColor: this.DebugForegroundColor, - backgroundColor: this.DebugBackgroundColor); - } - - /// - /// - /// - /// - public override void WriteVerboseLine(string message) - { - this.WriteOutput( - VerboseMessagePrefix + message, - true, - OutputType.Verbose, - foregroundColor: this.VerboseForegroundColor, - backgroundColor: this.VerboseBackgroundColor); - } - - /// - /// - /// - /// - public override void WriteWarningLine(string message) - { - this.WriteOutput( - WarningMessagePrefix + message, - true, - OutputType.Warning, - foregroundColor: this.WarningForegroundColor, - backgroundColor: this.WarningBackgroundColor); - } - - /// - /// - /// - /// - public override void WriteErrorLine(string value) - { - // PowerShell's ConsoleHost also skips over empty lines: - // https://github.com/PowerShell/PowerShell/blob/8e683972284a5a7f773ea6d027d9aac14d7e7524/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs#L1334-L1337 - if (string.IsNullOrEmpty(value)) - { - return; - } - - this.WriteOutput( - value, - true, - OutputType.Error, - foregroundColor: this.ErrorForegroundColor, - backgroundColor: this.ErrorBackgroundColor); - } - - /// - /// Invoked by to display a progress record. - /// - /// - /// Unique identifier of the source of the record. An int64 is used because typically, - /// the 'this' pointer of the command from whence the record is originating is used, and - /// that may be from a remote Runspace on a 64-bit machine. - /// - /// - /// The record being reported to the host. - /// - public sealed override void WriteProgress( - long sourceId, - ProgressRecord record) - { - // Maintain old behavior if this isn't overridden. - if (!this.SupportsWriteProgress) - { - this.UpdateProgress(sourceId, ProgressDetails.Create(record)); - return; - } - - // Keep a list of progress records we write so we can automatically - // clean them up after the pipeline ends. - if (record.RecordType == ProgressRecordType.Completed) - { - this.currentProgressMessages.TryRemove(new ProgressKey(sourceId, record), out _); - } - else - { - // Adding with a value of null here because we don't actually need a dictionary. We're - // only using ConcurrentDictionary<,> becuase there is no ConcurrentHashSet<>. - this.currentProgressMessages.TryAdd(new ProgressKey(sourceId, record), null); - } - - this.WriteProgressImpl(sourceId, record); - } - - /// - /// Invoked by to display a progress record. - /// - /// - /// Unique identifier of the source of the record. An int64 is used because typically, - /// the 'this' pointer of the command from whence the record is originating is used, and - /// that may be from a remote Runspace on a 64-bit machine. - /// - /// - /// The record being reported to the host. - /// - protected virtual void WriteProgressImpl(long sourceId, ProgressRecord record) - { - } - - internal void ClearProgress() - { - const string nonEmptyString = "noop"; - if (!this.SupportsWriteProgress) - { - return; - } - - foreach (ProgressKey key in this.currentProgressMessages.Keys) - { - // This constructor throws if the activity description is empty even - // with completed records. - var record = new ProgressRecord( - key.ActivityId, - activity: nonEmptyString, - statusDescription: nonEmptyString); - - record.RecordType = ProgressRecordType.Completed; - this.WriteProgressImpl(key.SourceId, record); - } - - this.currentProgressMessages.Clear(); - } - - #endregion - - #region IHostUISupportsMultipleChoiceSelection Implementation - - /// - /// - /// - /// - /// - /// - /// - /// - public Collection PromptForChoice( - string promptCaption, - string promptMessage, - Collection choiceDescriptions, - IEnumerable defaultChoices) - { - ChoiceDetails[] choices = - choiceDescriptions - .Select(ChoiceDetails.Create) - .ToArray(); - - CancellationTokenSource cancellationToken = new CancellationTokenSource(); - Task promptTask = - this.CreateChoicePromptHandler() - .PromptForChoiceAsync( - promptCaption, - promptMessage, - choices, - defaultChoices.ToArray(), - cancellationToken.Token); - - // Run the prompt task and wait for it to return - this.WaitForPromptCompletion( - promptTask, - "PromptForChoice", - cancellationToken); - - // Return the result - return new Collection(promptTask.Result.ToList()); - } - - #endregion - - #region Private Methods - - private Coordinates lastPromptLocation; - - private async Task WritePromptStringToHostAsync(CancellationToken cancellationToken) - { - try - { - if (this.lastPromptLocation != null && - this.lastPromptLocation.X == await ConsoleProxy.GetCursorLeftAsync(cancellationToken).ConfigureAwait(false) && - this.lastPromptLocation.Y == await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false)) - { - return; - } - } - // When output is redirected (like when running tests) attempting to get - // the cursor position will throw. - catch (System.IO.IOException) - { - } - - PSCommand promptCommand = new PSCommand().AddCommand("prompt"); - - cancellationToken.ThrowIfCancellationRequested(); - string promptString = - (await this.powerShellContext.ExecuteCommandAsync( - promptCommand, false, false, cancellationToken).ConfigureAwait(false)) - .Select(pso => pso.BaseObject) - .OfType() - .FirstOrDefault() ?? "PS> "; - - // Add the [DBG] prefix if we're stopped in the debugger and the prompt doesn't already have [DBG] in it - if (this.powerShellContext.IsDebuggerStopped && !promptString.Contains("[DBG]")) - { - promptString = - string.Format( - CultureInfo.InvariantCulture, - "[DBG]: {0}", - promptString); - } - - // Update the stored prompt string if the session is remote - if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote) - { - promptString = - string.Format( - CultureInfo.InvariantCulture, - "[{0}]: {1}", - this.powerShellContext.CurrentRunspace.Runspace.ConnectionInfo != null - ? this.powerShellContext.CurrentRunspace.Runspace.ConnectionInfo.ComputerName - : this.powerShellContext.CurrentRunspace.SessionDetails.ComputerName, - promptString); - } - - cancellationToken.ThrowIfCancellationRequested(); - - // Write the prompt string - this.WriteOutput(promptString, false); - this.lastPromptLocation = new Coordinates( - await ConsoleProxy.GetCursorLeftAsync(cancellationToken).ConfigureAwait(false), - await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false)); - } - - private void WriteDebuggerBanner(DebuggerStopEventArgs eventArgs) - { - // TODO: What do we display when we don't know why we stopped? - - if (eventArgs.Breakpoints.Count > 0) - { - // The breakpoint classes have nice ToString output so use that - this.WriteOutput( - Environment.NewLine + $"Hit {eventArgs.Breakpoints[0].ToString()}\n", - true, - OutputType.Normal, - ConsoleColor.Blue); - } - } - - internal static ConsoleColor BackgroundColor { get; set; } - - internal ConsoleColor FormatAccentColor { get; set; } = ConsoleColor.Green; - internal ConsoleColor ErrorAccentColor { get; set; } = ConsoleColor.Cyan; - - internal ConsoleColor ErrorForegroundColor { get; set; } = ConsoleColor.Red; - internal ConsoleColor ErrorBackgroundColor { get; set; } = BackgroundColor; - - internal ConsoleColor WarningForegroundColor { get; set; } = ConsoleColor.Yellow; - internal ConsoleColor WarningBackgroundColor { get; set; } = BackgroundColor; - - internal ConsoleColor DebugForegroundColor { get; set; } = ConsoleColor.Yellow; - internal ConsoleColor DebugBackgroundColor { get; set; } = BackgroundColor; - - internal ConsoleColor VerboseForegroundColor { get; set; } = ConsoleColor.Yellow; - internal ConsoleColor VerboseBackgroundColor { get; set; } = BackgroundColor; - - internal virtual ConsoleColor ProgressForegroundColor { get; set; } = ConsoleColor.Yellow; - internal virtual ConsoleColor ProgressBackgroundColor { get; set; } = ConsoleColor.DarkCyan; - - private async Task StartReplLoopAsync(CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - string commandString = null; - - try - { - await this.WritePromptStringToHostAsync(cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - break; - } - - try - { - commandString = await this.ReadCommandLineAsync(cancellationToken).ConfigureAwait(false); - } - catch (PipelineStoppedException) - { - this.WriteOutput( - "^C", - true, - OutputType.Normal, - foregroundColor: ConsoleColor.Red); - } - // Do nothing here, the while loop condition will exit. - catch (TaskCanceledException) - { } - catch (OperationCanceledException) - { } - catch (Exception e) // Narrow this if possible - { - this.WriteOutput( - $"\n\nAn error occurred while reading input:\n\n{e.ToString()}\n", - true, - OutputType.Error); - - Logger.LogException("Caught exception while reading command line", e); - } - finally - { - // This supplies the newline in the Legacy ReadLine when executing code in the terminal via hitting the ENTER key - // or Ctrl+C. Without this, hitting ENTER with a no input looks like it does nothing (no new prompt is written) - // and also the output would show up on the same line as the code you wanted to execute (the prompt line). - // This is AlSO applied to PSReadLine for the Ctrl+C scenario which appears like it does nothing... - // TODO: This still gives an extra newline when you hit ENTER in the PSReadLine experience. We should figure - // out if there's any way to avoid that... but unfortunately, in both scenarios, we only see that empty - // string is returned. - if (!cancellationToken.IsCancellationRequested) - { - this.WriteLine(); - } - } - - if (!string.IsNullOrWhiteSpace(commandString)) - { - var unusedTask = - this.powerShellContext - .ExecuteScriptStringAsync( - commandString, - writeInputToHost: false, - writeOutputToHost: true, - addToHistory: true) - .ConfigureAwait(continueOnCapturedContext: false); - - break; - } - } - } - - private InputPromptHandler CreateInputPromptHandler() - { - if (this.activePromptHandler != null) - { - Logger.LogError( - "Prompt handler requested while another prompt is already active."); - } - - InputPromptHandler inputPromptHandler = this.OnCreateInputPromptHandler(); - this.activePromptHandler = inputPromptHandler; - this.activePromptHandler.PromptCancelled += activePromptHandler_PromptCancelled; - - return inputPromptHandler; - } - - private ChoicePromptHandler CreateChoicePromptHandler() - { - if (this.activePromptHandler != null) - { - Logger.LogError( - "Prompt handler requested while another prompt is already active."); - } - - ChoicePromptHandler choicePromptHandler = this.OnCreateChoicePromptHandler(); - this.activePromptHandler = choicePromptHandler; - this.activePromptHandler.PromptCancelled += activePromptHandler_PromptCancelled; - - return choicePromptHandler; - } - - private void activePromptHandler_PromptCancelled(object sender, EventArgs e) - { - // Clean up the existing prompt - this.activePromptHandler.PromptCancelled -= activePromptHandler_PromptCancelled; - this.activePromptHandler = null; - } - private void WaitForPromptCompletion( - Task promptTask, - string promptFunctionName, - CancellationTokenSource cancellationToken) - { - try - { - // This will synchronously block on the prompt task - // method which gets run on another thread. - promptTask.Wait(); - - if (promptTask.Status == TaskStatus.WaitingForActivation) - { - // The Wait() call has timed out, cancel the prompt - cancellationToken.Cancel(); - - this.WriteOutput("\r\nPrompt has been cancelled due to a timeout.\r\n"); - throw new PipelineStoppedException(); - } - } - catch (AggregateException e) - { - // Find the right InnerException - Exception innerException = e.InnerException; - while (innerException is AggregateException) - { - innerException = innerException.InnerException; - } - - // Was the task cancelled? - if (innerException is TaskCanceledException) - { - // Stop the pipeline if the prompt was cancelled - throw new PipelineStoppedException(); - } - else if (innerException is PipelineStoppedException) - { - // The prompt is being cancelled, rethrow the exception - throw innerException; - } - else - { - // Rethrow the exception - throw new Exception( - string.Format( - "{0} failed, check inner exception for details", - promptFunctionName), - innerException); - } - } - } - - private void PowerShellContext_DebuggerStop(object sender, System.Management.Automation.DebuggerStopEventArgs e) - { - if (!this.IsCommandLoopRunning) - { - StartCommandLoop(); - return; - } - - // Cancel any existing prompt first - this.CancelCommandPrompt(); - - this.WriteDebuggerBanner(e); - this.ShowCommandPrompt(); - } - - private void PowerShellContext_DebuggerResumed(object sender, System.Management.Automation.DebuggerResumeAction e) - { - this.CancelCommandPrompt(); - } - - private void PowerShellContext_ExecutionStatusChanged(object sender, ExecutionStatusChangedEventArgs eventArgs) - { - // The command loop should only be manipulated if it's already started - if (eventArgs.ExecutionStatus == ExecutionStatus.Aborted) - { - this.ClearProgress(); - - // When aborted, cancel any lingering prompts - if (this.activePromptHandler != null) - { - this.activePromptHandler.CancelPrompt(); - this.WriteOutput(string.Empty); - } - } - else if ( - eventArgs.ExecutionOptions.WriteOutputToHost || - eventArgs.ExecutionOptions.InterruptCommandPrompt) - { - // Any command which writes output to the host will affect - // the display of the prompt - if (eventArgs.ExecutionStatus != ExecutionStatus.Running) - { - this.ClearProgress(); - - // Execution has completed, start the input prompt - this.ShowCommandPrompt(); - StartCommandLoop(); - } - else - { - // A new command was started, cancel the input prompt - StopCommandLoop(); - this.CancelCommandPrompt(); - } - } - else if ( - eventArgs.ExecutionOptions.WriteErrorsToHost && - (eventArgs.ExecutionStatus == ExecutionStatus.Failed || - eventArgs.HadErrors)) - { - this.ClearProgress(); - this.WriteOutput(string.Empty, true); - var unusedTask = this.WritePromptStringToHostAsync(CancellationToken.None); - } - } - - #endregion - - private readonly struct ProgressKey : IEquatable - { - internal readonly long SourceId; - - internal readonly int ActivityId; - - internal readonly int ParentActivityId; - - internal ProgressKey(long sourceId, ProgressRecord record) - { - SourceId = sourceId; - ActivityId = record.ActivityId; - ParentActivityId = record.ParentActivityId; - } - - public bool Equals(ProgressKey other) - { - return SourceId == other.SourceId - && ActivityId == other.ActivityId - && ParentActivityId == other.ParentActivityId; - } - - public override int GetHashCode() - { - // Algorithm from https://stackoverflow.com/questions/1646807/quick-and-simple-hash-code-combinations - unchecked - { - int hash = 17; - hash = hash * 31 + SourceId.GetHashCode(); - hash = hash * 31 + ActivityId.GetHashCode(); - hash = hash * 31 + ParentActivityId.GetHashCode(); - return hash; - } - } - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/IHostInput.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/IHostInput.cs deleted file mode 100644 index 3a99f81c3..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/IHostInput.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides methods for integrating with the host's input system. - /// - internal interface IHostInput - { - /// - /// Starts the host's interactive command loop. - /// - void StartCommandLoop(); - - /// - /// Stops the host's interactive command loop. - /// - void StopCommandLoop(); - - /// - /// Cancels the currently executing command or prompt. - /// - void SendControlC(); - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/IHostOutput.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/IHostOutput.cs deleted file mode 100644 index 732386fd9..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/IHostOutput.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides a simplified interface for writing output to a - /// PowerShell host implementation. - /// - internal interface IHostOutput - { - /// - /// Writes output of the given type to the user interface with - /// the given foreground and background colors. Also includes - /// a newline if requested. - /// - /// - /// The output string to be written. - /// - /// - /// If true, a newline should be appended to the output's contents. - /// - /// - /// Specifies the type of output to be written. - /// - /// - /// Specifies the foreground color of the output to be written. - /// - /// - /// Specifies the background color of the output to be written. - /// - void WriteOutput( - string outputString, - bool includeNewLine, - OutputType outputType, - ConsoleColor foregroundColor, - ConsoleColor backgroundColor); - } - - /// - /// Provides helpful extension methods for the IHostOutput interface. - /// - internal static class IHostOutputExtensions - { - /// - /// Writes normal output with a newline to the user interface. - /// - /// - /// The IHostOutput implementation to use for WriteOutput calls. - /// - /// - /// The output string to be written. - /// - public static void WriteOutput( - this IHostOutput hostOutput, - string outputString) - { - hostOutput.WriteOutput(outputString, true); - } - - /// - /// Writes normal output to the user interface. - /// - /// - /// The IHostOutput implementation to use for WriteOutput calls. - /// - /// - /// The output string to be written. - /// - /// - /// If true, a newline should be appended to the output's contents. - /// - public static void WriteOutput( - this IHostOutput hostOutput, - string outputString, - bool includeNewLine) - { - hostOutput.WriteOutput( - outputString, - includeNewLine, - OutputType.Normal); - } - - /// - /// Writes output of a particular type to the user interface - /// with a newline ending. - /// - /// - /// The IHostOutput implementation to use for WriteOutput calls. - /// - /// - /// The output string to be written. - /// - /// - /// Specifies the type of output to be written. - /// - public static void WriteOutput( - this IHostOutput hostOutput, - string outputString, - OutputType outputType) - { - hostOutput.WriteOutput( - outputString, - true, - OutputType.Normal); - } - - /// - /// Writes output of a particular type to the user interface. - /// - /// - /// The IHostOutput implementation to use for WriteOutput calls. - /// - /// - /// The output string to be written. - /// - /// - /// If true, a newline should be appended to the output's contents. - /// - /// - /// Specifies the type of output to be written. - /// - public static void WriteOutput( - this IHostOutput hostOutput, - string outputString, - bool includeNewLine, - OutputType outputType) - { - hostOutput.WriteOutput( - outputString, - includeNewLine, - outputType, - ConsoleColor.Gray, - (ConsoleColor)(-1)); // -1 indicates the console's raw background color - } - - /// - /// Writes output of a particular type to the user interface using - /// a particular foreground color. - /// - /// - /// The IHostOutput implementation to use for WriteOutput calls. - /// - /// - /// The output string to be written. - /// - /// - /// If true, a newline should be appended to the output's contents. - /// - /// - /// Specifies the type of output to be written. - /// - /// - /// Specifies the foreground color of the output to be written. - /// - public static void WriteOutput( - this IHostOutput hostOutput, - string outputString, - bool includeNewLine, - OutputType outputType, - ConsoleColor foregroundColor) - { - hostOutput.WriteOutput( - outputString, - includeNewLine, - outputType, - foregroundColor, - (ConsoleColor)(-1)); // -1 indicates the console's raw background color - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/PromptHandlers.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/PromptHandlers.cs deleted file mode 100644 index 4bd29ba29..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/PromptHandlers.cs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Threading.Tasks; -using System.Threading; -using System.Security; -using Microsoft.Extensions.Logging; -using OmniSharp.Extensions.LanguageServer.Protocol.Server; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - internal class ProtocolChoicePromptHandler : ConsoleChoicePromptHandler - { - private readonly ILanguageServerFacade _languageServer; - private readonly IHostInput _hostInput; - private TaskCompletionSource _readLineTask; - - public ProtocolChoicePromptHandler( - ILanguageServerFacade languageServer, - IHostInput hostInput, - IHostOutput hostOutput, - ILogger logger) - : base(hostOutput, logger) - { - _languageServer = languageServer; - this._hostInput = hostInput; - this.hostOutput = hostOutput; - } - - protected override void ShowPrompt(PromptStyle promptStyle) - { - base.ShowPrompt(promptStyle); - - _languageServer.SendRequest( - "powerShell/showChoicePrompt", - new ShowChoicePromptRequest - { - IsMultiChoice = this.IsMultiChoice, - Caption = this.Caption, - Message = this.Message, - Choices = this.Choices, - DefaultChoices = this.DefaultChoices - }) - .Returning(CancellationToken.None) - .ContinueWith(HandlePromptResponse) - .ConfigureAwait(false); - } - - protected override Task ReadInputStringAsync(CancellationToken cancellationToken) - { - this._readLineTask = new TaskCompletionSource(); - return this._readLineTask.Task; - } - - private void HandlePromptResponse( - Task responseTask) - { - if (responseTask.IsCompleted) - { - ShowChoicePromptResponse response = responseTask.Result; - - if (!response.PromptCancelled) - { - this.hostOutput.WriteOutput( - response.ResponseText, - OutputType.Normal); - - this._readLineTask.TrySetResult(response.ResponseText); - } - else - { - // Cancel the current prompt - this._hostInput.SendControlC(); - } - } - else - { - if (responseTask.IsFaulted) - { - // Log the error - Logger.LogError( - "ShowChoicePrompt request failed with error:\r\n{0}", - responseTask.Exception.ToString()); - } - - // Cancel the current prompt - this._hostInput.SendControlC(); - } - - this._readLineTask = null; - } - } - - internal class ProtocolInputPromptHandler : ConsoleInputPromptHandler - { - private readonly ILanguageServerFacade _languageServer; - private readonly IHostInput hostInput; - private TaskCompletionSource readLineTask; - - public ProtocolInputPromptHandler( - ILanguageServerFacade languageServer, - IHostInput hostInput, - IHostOutput hostOutput, - ILogger logger) - : base(hostOutput, logger) - { - _languageServer = languageServer; - this.hostInput = hostInput; - this.hostOutput = hostOutput; - } - - protected override void ShowFieldPrompt(FieldDetails fieldDetails) - { - base.ShowFieldPrompt(fieldDetails); - - _languageServer.SendRequest( - "powerShell/showInputPrompt", - new ShowInputPromptRequest - { - Name = fieldDetails.Name, - Label = fieldDetails.Label - }).Returning(CancellationToken.None) - .ContinueWith(HandlePromptResponse) - .ConfigureAwait(false); - } - - protected override Task ReadInputStringAsync(CancellationToken cancellationToken) - { - this.readLineTask = new TaskCompletionSource(); - return this.readLineTask.Task; - } - - private void HandlePromptResponse( - Task responseTask) - { - if (responseTask.IsCompleted) - { - ShowInputPromptResponse response = responseTask.Result; - - if (!response.PromptCancelled) - { - this.hostOutput.WriteOutput( - response.ResponseText, - OutputType.Normal); - - this.readLineTask.TrySetResult(response.ResponseText); - } - else - { - // Cancel the current prompt - this.hostInput.SendControlC(); - } - } - else - { - if (responseTask.IsFaulted) - { - // Log the error - Logger.LogError( - "ShowInputPrompt request failed with error:\r\n{0}", - responseTask.Exception.ToString()); - } - - // Cancel the current prompt - this.hostInput.SendControlC(); - } - - this.readLineTask = null; - } - - protected override Task ReadSecureStringAsync(CancellationToken cancellationToken) - { - // TODO: Write a message to the console - throw new NotImplementedException(); - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/ProtocolPSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/ProtocolPSHostUserInterface.cs deleted file mode 100644 index 89203e5c1..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/ProtocolPSHostUserInterface.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Extensions.Logging; -using OmniSharp.Extensions.LanguageServer.Protocol.Server; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - internal class ProtocolPSHostUserInterface : EditorServicesPSHostUserInterface - { - #region Private Fields - - private readonly ILanguageServerFacade _languageServer; - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the ConsoleServicePSHostUserInterface - /// class with the given IConsoleHost implementation. - /// - /// - public ProtocolPSHostUserInterface( - ILanguageServerFacade languageServer, - PowerShellContextService powerShellContext, - ILogger logger) - : base ( - powerShellContext, - new SimplePSHostRawUserInterface(logger), - logger) - { - _languageServer = languageServer; - } - - #endregion - - /// - /// Writes output of the given type to the user interface with - /// the given foreground and background colors. Also includes - /// a newline if requested. - /// - /// - /// The output string to be written. - /// - /// - /// If true, a newline should be appended to the output's contents. - /// - /// - /// Specifies the type of output to be written. - /// - /// - /// Specifies the foreground color of the output to be written. - /// - /// - /// Specifies the background color of the output to be written. - /// - public override void WriteOutput( - string outputString, - bool includeNewLine, - OutputType outputType, - ConsoleColor foregroundColor, - ConsoleColor backgroundColor) - { - // TODO: Invoke the "output" notification! - } - - /// - /// Sends a progress update event to the user. - /// - /// The source ID of the progress event. - /// The details of the activity's current progress. - protected override void UpdateProgress( - long sourceId, - ProgressDetails progressDetails) - { - // TODO: Send a new message. - } - - protected override Task ReadCommandLineAsync(CancellationToken cancellationToken) - { - // This currently does nothing because the "evaluate" request - // will cancel the current prompt and execute the user's - // script selection. - return new TaskCompletionSource().Task; - } - - protected override InputPromptHandler OnCreateInputPromptHandler() - { - return new ProtocolInputPromptHandler(_languageServer, this, this, this.Logger); - } - - protected override ChoicePromptHandler OnCreateChoicePromptHandler() - { - return new ProtocolChoicePromptHandler(_languageServer, this, this, this.Logger); - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/SimplePSHostRawUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/SimplePSHostRawUserInterface.cs deleted file mode 100644 index 2ac77b94d..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/SimplePSHostRawUserInterface.cs +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Management.Automation.Host; -using Microsoft.Extensions.Logging; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides an simple implementation of the PSHostRawUserInterface class. - /// - internal class SimplePSHostRawUserInterface : PSHostRawUserInterface - { - #region Private Fields - - private const int DefaultConsoleHeight = 100; - private const int DefaultConsoleWidth = 120; - - private ILogger Logger; - - private Size currentBufferSize = new Size(DefaultConsoleWidth, DefaultConsoleHeight); - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the SimplePSHostRawUserInterface - /// class with the given IConsoleHost implementation. - /// - /// The ILogger implementation to use for this instance. - public SimplePSHostRawUserInterface(ILogger logger) - { - this.Logger = logger; - this.ForegroundColor = ConsoleColor.White; - this.BackgroundColor = ConsoleColor.Black; - } - - #endregion - - #region PSHostRawUserInterface Implementation - - /// - /// Gets or sets the background color of the console. - /// - public override ConsoleColor BackgroundColor - { - get; - set; - } - - /// - /// Gets or sets the foreground color of the console. - /// - public override ConsoleColor ForegroundColor - { - get; - set; - } - - /// - /// Gets or sets the size of the console buffer. - /// - public override Size BufferSize - { - get - { - return this.currentBufferSize; - } - set - { - this.currentBufferSize = value; - } - } - - /// - /// Gets or sets the cursor's position in the console buffer. - /// - public override Coordinates CursorPosition - { - get; - set; - } - - /// - /// Gets or sets the size of the cursor in the console buffer. - /// - public override int CursorSize - { - get; - set; - } - - /// - /// Gets or sets the position of the console's window. - /// - public override Coordinates WindowPosition - { - get; - set; - } - - /// - /// Gets or sets the size of the console's window. - /// - public override Size WindowSize - { - get; - set; - } - - /// - /// Gets or sets the console window's title. - /// - public override string WindowTitle - { - get; - set; - } - - /// - /// Gets a boolean that determines whether a keypress is available. - /// - public override bool KeyAvailable - { - get { return false; } - } - - /// - /// Gets the maximum physical size of the console window. - /// - public override Size MaxPhysicalWindowSize - { - get { return new Size(DefaultConsoleWidth, DefaultConsoleHeight); } - } - - /// - /// Gets the maximum size of the console window. - /// - public override Size MaxWindowSize - { - get { return new Size(DefaultConsoleWidth, DefaultConsoleHeight); } - } - - /// - /// Reads the current key pressed in the console. - /// - /// Options for reading the current keypress. - /// A KeyInfo struct with details about the current keypress. - public override KeyInfo ReadKey(ReadKeyOptions options) - { - Logger.LogWarning( - "PSHostRawUserInterface.ReadKey was called"); - - throw new System.NotImplementedException(); - } - - /// - /// Flushes the current input buffer. - /// - public override void FlushInputBuffer() - { - Logger.LogWarning( - "PSHostRawUserInterface.FlushInputBuffer was called"); - } - - /// - /// Gets the contents of the console buffer in a rectangular area. - /// - /// The rectangle inside which buffer contents will be accessed. - /// A BufferCell array with the requested buffer contents. - public override BufferCell[,] GetBufferContents(Rectangle rectangle) - { - return new BufferCell[0,0]; - } - - /// - /// Scrolls the contents of the console buffer. - /// - /// The source rectangle to scroll. - /// The destination coordinates by which to scroll. - /// The rectangle inside which the scrolling will be clipped. - /// The cell with which the buffer will be filled. - public override void ScrollBufferContents( - Rectangle source, - Coordinates destination, - Rectangle clip, - BufferCell fill) - { - Logger.LogWarning( - "PSHostRawUserInterface.ScrollBufferContents was called"); - } - - /// - /// Sets the contents of the buffer inside the specified rectangle. - /// - /// The rectangle inside which buffer contents will be filled. - /// The BufferCell which will be used to fill the requested space. - public override void SetBufferContents( - Rectangle rectangle, - BufferCell fill) - { - Logger.LogWarning( - "PSHostRawUserInterface.SetBufferContents was called"); - } - - /// - /// Sets the contents of the buffer at the given coordinate. - /// - /// The coordinate at which the buffer will be changed. - /// The new contents for the buffer at the given coordinate. - public override void SetBufferContents( - Coordinates origin, - BufferCell[,] contents) - { - Logger.LogWarning( - "PSHostRawUserInterface.SetBufferContents was called"); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostRawUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostRawUserInterface.cs deleted file mode 100644 index 423a00e9c..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostRawUserInterface.cs +++ /dev/null @@ -1,374 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Extensions.Logging; -using System; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Threading; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides an implementation of the PSHostRawUserInterface class - /// for the ConsoleService and routes its calls to an IConsoleHost - /// implementation. - /// - internal class TerminalPSHostRawUserInterface : PSHostRawUserInterface - { - #region Private Fields - - private readonly PSHostRawUserInterface internalRawUI; - private ILogger Logger; - private KeyInfo? lastKeyDown; - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the TerminalPSHostRawUserInterface - /// class with the given IConsoleHost implementation. - /// - /// The ILogger implementation to use for this instance. - /// The InternalHost instance from the origin runspace. - public TerminalPSHostRawUserInterface(ILogger logger, PSHost internalHost) - { - this.Logger = logger; - this.internalRawUI = internalHost.UI.RawUI; - } - - #endregion - - #region PSHostRawUserInterface Implementation - - /// - /// Gets or sets the background color of the console. - /// - public override ConsoleColor BackgroundColor - { - get { return System.Console.BackgroundColor; } - set { System.Console.BackgroundColor = value; } - } - - /// - /// Gets or sets the foreground color of the console. - /// - public override ConsoleColor ForegroundColor - { - get { return System.Console.ForegroundColor; } - set { System.Console.ForegroundColor = value; } - } - - /// - /// Gets or sets the size of the console buffer. - /// - public override Size BufferSize - { - get => this.internalRawUI.BufferSize; - set => this.internalRawUI.BufferSize = value; - } - - /// - /// Gets or sets the cursor's position in the console buffer. - /// - public override Coordinates CursorPosition - { - get - { - return new Coordinates( - ConsoleProxy.GetCursorLeft(), - ConsoleProxy.GetCursorTop()); - } - - set => this.internalRawUI.CursorPosition = value; - } - - /// - /// Gets or sets the size of the cursor in the console buffer. - /// - public override int CursorSize - { - get => this.internalRawUI.CursorSize; - set => this.internalRawUI.CursorSize = value; - } - - /// - /// Gets or sets the position of the console's window. - /// - public override Coordinates WindowPosition - { - get => this.internalRawUI.WindowPosition; - set => this.internalRawUI.WindowPosition = value; - } - - /// - /// Gets or sets the size of the console's window. - /// - public override Size WindowSize - { - get => this.internalRawUI.WindowSize; - set => this.internalRawUI.WindowSize = value; - } - - /// - /// Gets or sets the console window's title. - /// - public override string WindowTitle - { - get => this.internalRawUI.WindowTitle; - set => this.internalRawUI.WindowTitle = value; - } - - /// - /// Gets a boolean that determines whether a keypress is available. - /// - public override bool KeyAvailable => this.internalRawUI.KeyAvailable; - - /// - /// Gets the maximum physical size of the console window. - /// - public override Size MaxPhysicalWindowSize => this.internalRawUI.MaxPhysicalWindowSize; - - /// - /// Gets the maximum size of the console window. - /// - public override Size MaxWindowSize => this.internalRawUI.MaxWindowSize; - - /// - /// Reads the current key pressed in the console. - /// - /// Options for reading the current keypress. - /// A KeyInfo struct with details about the current keypress. - public override KeyInfo ReadKey(ReadKeyOptions options) - { - - bool includeUp = (options & ReadKeyOptions.IncludeKeyUp) != 0; - - // Key Up was requested and we have a cached key down we can return. - if (includeUp && this.lastKeyDown != null) - { - KeyInfo info = this.lastKeyDown.Value; - this.lastKeyDown = null; - return new KeyInfo( - info.VirtualKeyCode, - info.Character, - info.ControlKeyState, - keyDown: false); - } - - bool intercept = (options & ReadKeyOptions.NoEcho) != 0; - bool includeDown = (options & ReadKeyOptions.IncludeKeyDown) != 0; - if (!(includeDown || includeUp)) - { - throw new PSArgumentException( - "Cannot read key options. To read options, set one or both of the following: IncludeKeyDown, IncludeKeyUp.", - nameof(options)); - } - - // Allow ControlC as input so we can emulate pipeline stop requests. We can't actually - // determine if a stop is requested without using non-public API's. - bool oldValue = System.Console.TreatControlCAsInput; - try - { - System.Console.TreatControlCAsInput = true; - ConsoleKeyInfo key = ConsoleProxy.ReadKey(intercept, default(CancellationToken)); - - if (IsCtrlC(key)) - { - // Caller wants CtrlC as input so return it. - if ((options & ReadKeyOptions.AllowCtrlC) != 0) - { - return ProcessKey(key, includeDown); - } - - // Caller doesn't want CtrlC so throw a PipelineStoppedException to emulate - // a real stop. This will not show an exception to a script based caller and it - // will avoid having to return something like default(KeyInfo). - throw new PipelineStoppedException(); - } - - return ProcessKey(key, includeDown); - } - finally - { - System.Console.TreatControlCAsInput = oldValue; - } - } - - /// - /// Flushes the current input buffer. - /// - public override void FlushInputBuffer() - { - Logger.LogWarning( - "PSHostRawUserInterface.FlushInputBuffer was called"); - } - - /// - /// Gets the contents of the console buffer in a rectangular area. - /// - /// The rectangle inside which buffer contents will be accessed. - /// A BufferCell array with the requested buffer contents. - public override BufferCell[,] GetBufferContents(Rectangle rectangle) - { - return this.internalRawUI.GetBufferContents(rectangle); - } - - /// - /// Scrolls the contents of the console buffer. - /// - /// The source rectangle to scroll. - /// The destination coordinates by which to scroll. - /// The rectangle inside which the scrolling will be clipped. - /// The cell with which the buffer will be filled. - public override void ScrollBufferContents( - Rectangle source, - Coordinates destination, - Rectangle clip, - BufferCell fill) - { - this.internalRawUI.ScrollBufferContents(source, destination, clip, fill); - } - - /// - /// Sets the contents of the buffer inside the specified rectangle. - /// - /// The rectangle inside which buffer contents will be filled. - /// The BufferCell which will be used to fill the requested space. - public override void SetBufferContents( - Rectangle rectangle, - BufferCell fill) - { - // If the rectangle is all -1s then it means clear the visible buffer - if (rectangle.Top == -1 && - rectangle.Bottom == -1 && - rectangle.Left == -1 && - rectangle.Right == -1) - { - System.Console.Clear(); - return; - } - - this.internalRawUI.SetBufferContents(rectangle, fill); - } - - /// - /// Sets the contents of the buffer at the given coordinate. - /// - /// The coordinate at which the buffer will be changed. - /// The new contents for the buffer at the given coordinate. - public override void SetBufferContents( - Coordinates origin, - BufferCell[,] contents) - { - this.internalRawUI.SetBufferContents(origin, contents); - } - - /// - /// Determines the number of BufferCells a character occupies. - /// - /// - /// The character whose length we want to know. - /// - /// - /// The length in buffer cells according to the original host - /// implementation for the process. - /// - public override int LengthInBufferCells(char source) - { - return this.internalRawUI.LengthInBufferCells(source); - } - /// - /// Determines the number of BufferCells a string occupies. - /// - /// - /// The string whose length we want to know. - /// - /// - /// The length in buffer cells according to the original host - /// implementation for the process. - /// - public override int LengthInBufferCells(string source) - { - return this.internalRawUI.LengthInBufferCells(source); - } - - /// - /// Determines the number of BufferCells a substring of a string occupies. - /// - /// - /// The string whose substring length we want to know. - /// - /// - /// Offset where the substring begins in - /// - /// - /// The length in buffer cells according to the original host - /// implementation for the process. - /// - public override int LengthInBufferCells(string source, int offset) - { - return this.internalRawUI.LengthInBufferCells(source, offset); - } - - #endregion - - /// - /// Determines if a key press represents the input Ctrl + C. - /// - /// The key to test. - /// - /// if the key represents the input Ctrl + C, - /// otherwise . - /// - private static bool IsCtrlC(ConsoleKeyInfo keyInfo) - { - // In the VSCode terminal Ctrl C is processed as virtual key code "3", which - // is not a named value in the ConsoleKey enum. - if ((int)keyInfo.Key == 3) - { - return true; - } - - return keyInfo.Key == ConsoleKey.C && (keyInfo.Modifiers & ConsoleModifiers.Control) != 0; - } - - /// - /// Converts objects to objects and caches - /// key down events for the next key up request. - /// - /// The key to convert. - /// - /// A value indicating whether the result should be a key down event. - /// - /// The converted value. - private KeyInfo ProcessKey(ConsoleKeyInfo key, bool isDown) - { - // Translate ConsoleModifiers to ControlKeyStates - ControlKeyStates states = default; - if ((key.Modifiers & ConsoleModifiers.Alt) != 0) - { - states |= ControlKeyStates.LeftAltPressed; - } - - if ((key.Modifiers & ConsoleModifiers.Control) != 0) - { - states |= ControlKeyStates.LeftCtrlPressed; - } - - if ((key.Modifiers & ConsoleModifiers.Shift) != 0) - { - states |= ControlKeyStates.ShiftPressed; - } - - var result = new KeyInfo((int)key.Key, key.KeyChar, states, isDown); - if (isDown) - { - this.lastKeyDown = result; - } - - return result; - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostUserInterface.cs deleted file mode 100644 index 86d713a98..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostUserInterface.cs +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Management.Automation.Host; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - using System.Management.Automation; - - /// - /// Provides an EditorServicesPSHostUserInterface implementation - /// that integrates with the user's terminal UI. - /// - internal class TerminalPSHostUserInterface : EditorServicesPSHostUserInterface - { - #region Private Fields - - private readonly PSHostUserInterface internalHostUI; - private readonly PSObject _internalHostPrivateData; - private readonly ConsoleReadLine _consoleReadLine; - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the ConsoleServicePSHostUserInterface - /// class with the given IConsoleHost implementation. - /// - /// The PowerShellContext to use for executing commands. - /// An ILogger implementation to use for this host. - /// The InternalHost instance from the origin runspace. - public TerminalPSHostUserInterface( - PowerShellContextService powerShellContext, - PSHost internalHost, - ILogger logger) - : base ( - powerShellContext, - new TerminalPSHostRawUserInterface(logger, internalHost), - logger) - { - internalHostUI = internalHost.UI; - _internalHostPrivateData = internalHost.PrivateData; - _consoleReadLine = new ConsoleReadLine(powerShellContext); - - // Set the output encoding to UTF-8 so that special - // characters are written to the console correctly - System.Console.OutputEncoding = System.Text.Encoding.UTF8; - - System.Console.CancelKeyPress += - (obj, args) => - { - if (!IsNativeApplicationRunning) - { - // We'll handle Ctrl+C - args.Cancel = true; - SendControlC(); - } - }; - } - - #endregion - - /// - /// Returns true if the host supports VT100 output codes. - /// - public override bool SupportsVirtualTerminal => internalHostUI.SupportsVirtualTerminal; - - /// - /// Gets a value indicating whether writing progress is supported. - /// - internal protected override bool SupportsWriteProgress => true; - - /// - /// Gets and sets the value of progress foreground from the internal host since Progress is handled there. - /// - internal override ConsoleColor ProgressForegroundColor - { - get => (ConsoleColor)_internalHostPrivateData.Properties["ProgressForegroundColor"].Value; - set => _internalHostPrivateData.Properties["ProgressForegroundColor"].Value = value; - } - - /// - /// Gets and sets the value of progress background from the internal host since Progress is handled there. - /// - internal override ConsoleColor ProgressBackgroundColor - { - get => (ConsoleColor)_internalHostPrivateData.Properties["ProgressBackgroundColor"].Value; - set => _internalHostPrivateData.Properties["ProgressBackgroundColor"].Value = value; - } - - /// - /// Requests that the HostUI implementation read a command line - /// from the user to be executed in the integrated console command - /// loop. - /// - /// - /// A CancellationToken used to cancel the command line request. - /// - /// A Task that can be awaited for the resulting input string. - protected override Task ReadCommandLineAsync(CancellationToken cancellationToken) - { - return _consoleReadLine.ReadCommandLineAsync(cancellationToken); - } - - /// - /// Creates an InputPrompt handle to use for displaying input - /// prompts to the user. - /// - /// A new InputPromptHandler instance. - protected override InputPromptHandler OnCreateInputPromptHandler() - { - return new TerminalInputPromptHandler( - _consoleReadLine, - this, - Logger); - } - - /// - /// Creates a ChoicePromptHandler to use for displaying a - /// choice prompt to the user. - /// - /// A new ChoicePromptHandler instance. - protected override ChoicePromptHandler OnCreateChoicePromptHandler() - { - return new TerminalChoicePromptHandler( - _consoleReadLine, - this, - Logger); - } - - /// - /// Writes output of the given type to the user interface with - /// the given foreground and background colors. Also includes - /// a newline if requested. - /// - /// - /// The output string to be written. - /// - /// - /// If true, a newline should be appended to the output's contents. - /// - /// - /// Specifies the type of output to be written. - /// - /// - /// Specifies the foreground color of the output to be written. - /// - /// - /// Specifies the background color of the output to be written. - /// - public override void WriteOutput( - string outputString, - bool includeNewLine, - OutputType outputType, - ConsoleColor foregroundColor, - ConsoleColor backgroundColor) - { - ConsoleColor oldForegroundColor = System.Console.ForegroundColor; - ConsoleColor oldBackgroundColor = System.Console.BackgroundColor; - - System.Console.ForegroundColor = foregroundColor; - System.Console.BackgroundColor = ((int)backgroundColor != -1) ? backgroundColor : oldBackgroundColor; - - System.Console.Write(outputString + (includeNewLine ? Environment.NewLine : "")); - - System.Console.ForegroundColor = oldForegroundColor; - System.Console.BackgroundColor = oldBackgroundColor; - } - - /// - /// Invoked by to display a progress record. - /// - /// - /// Unique identifier of the source of the record. An int64 is used because typically, - /// the 'this' pointer of the command from whence the record is originating is used, and - /// that may be from a remote Runspace on a 64-bit machine. - /// - /// - /// The record being reported to the host. - /// - protected override void WriteProgressImpl(long sourceId, ProgressRecord record) - { - internalHostUI.WriteProgress(sourceId, record); - } - - /// - /// Sends a progress update event to the user. - /// - /// The source ID of the progress event. - /// The details of the activity's current progress. - protected override void UpdateProgress( - long sourceId, - ProgressDetails progressDetails) - { - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/IPromptContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/IPromptContext.cs deleted file mode 100644 index e0618ed04..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/IPromptContext.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides methods for interacting with implementations of ReadLine. - /// - internal interface IPromptContext - { - /// - /// Read a string that has been input by the user. - /// - /// Indicates if ReadLine should act like a command REPL. - /// - /// The cancellation token can be used to cancel reading user input. - /// - /// - /// A task object that represents the completion of reading input. The Result property will - /// return the input string. - /// - Task InvokeReadLineAsync(bool isCommandLine, CancellationToken cancellationToken); - - /// - /// Performs any additional actions required to cancel the current ReadLine invocation. - /// - void AbortReadLine(); - - /// - /// Creates a task that completes when the current ReadLine invocation has been aborted. - /// - /// - /// A task object that represents the abortion of the current ReadLine invocation. - /// - Task AbortReadLineAsync(); - - /// - /// Blocks until the current ReadLine invocation has exited. - /// - void WaitForReadLineExit(); - - /// - /// Creates a task that completes when the current ReadLine invocation has exited. - /// - /// - /// A task object that represents the exit of the current ReadLine invocation. - /// - Task WaitForReadLineExitAsync(); - - /// - /// Adds the specified command to the history managed by the ReadLine implementation. - /// - /// The command to record. - void AddToHistory(string command); - - /// - /// Forces the prompt handler to trigger PowerShell event handling, reliquishing control - /// of the pipeline thread during event processing. - /// - void ForcePSEventHandling(); - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/IRunspaceCapability.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/IRunspaceCapability.cs deleted file mode 100644 index 12e5e6fcd..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/IRunspaceCapability.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - internal interface IRunspaceCapability - { - // NOTE: This interface is intentionally empty for now. - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/IVersionSpecificOperations.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/IVersionSpecificOperations.cs deleted file mode 100644 index cdc5f5507..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/IVersionSpecificOperations.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Generic; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - internal interface IVersionSpecificOperations - { - void ConfigureDebugger(Runspace runspace); - - void PauseDebugger(Runspace runspace); - - IEnumerable ExecuteCommandInDebugger( - PowerShellContextService powerShellContext, - Runspace currentRunspace, - PSCommand psCommand, - bool sendOutputToHost, - out DebuggerResumeAction? debuggerResumeAction); - - void StopCommandInDebugger(PowerShellContextService powerShellContext); - - bool IsDebuggerStopped(PromptNest promptNest, Runspace runspace); - - void ExitNestedPrompt(PSHost host); - } -} - diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/InvocationEventQueue.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/InvocationEventQueue.cs deleted file mode 100644 index 4d7a87fd0..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/InvocationEventQueue.cs +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Management.Automation.Runspaces; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using System.Threading; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - using System.Management.Automation; - - /// - /// Provides the ability to take over the current pipeline in a runspace. - /// - internal class InvocationEventQueue - { - private const string ShouldProcessInExecutionThreadPropertyName = "ShouldProcessInExecutionThread"; - - private static readonly PropertyInfo s_shouldProcessInExecutionThreadProperty = - typeof(PSEventSubscriber) - .GetProperty( - ShouldProcessInExecutionThreadPropertyName, - BindingFlags.Instance | BindingFlags.NonPublic); - - private readonly PromptNest _promptNest; - - private readonly Runspace _runspace; - - private readonly PowerShellContextService _powerShellContext; - - private InvocationRequest _invocationRequest; - - private SemaphoreSlim _lock = AsyncUtils.CreateSimpleLockingSemaphore(); - - private InvocationEventQueue(PowerShellContextService powerShellContext, PromptNest promptNest) - { - _promptNest = promptNest; - _powerShellContext = powerShellContext; - _runspace = powerShellContext.CurrentRunspace.Runspace; - } - - internal static InvocationEventQueue Create(PowerShellContextService powerShellContext, PromptNest promptNest) - { - var eventQueue = new InvocationEventQueue(powerShellContext, promptNest); - eventQueue.CreateInvocationSubscriber(); - return eventQueue; - } - - /// - /// Executes a command on the main pipeline thread through - /// eventing. A event subscriber will - /// be created that creates a nested PowerShell instance for - /// to utilize. - /// - /// - /// Avoid using this method directly if possible. - /// will route commands - /// through this method if required. - /// - /// The expected result type. - /// The to be executed. - /// - /// Error messages from PowerShell will be written to the . - /// - /// Specifies options to be used when executing this command. - /// - /// An awaitable which will provide results once the command - /// execution completes. - /// - internal async Task> ExecuteCommandOnIdleAsync( - PSCommand psCommand, - StringBuilder errorMessages, - ExecutionOptions executionOptions) - { - var request = new PipelineExecutionRequest( - _powerShellContext, - psCommand, - errorMessages, - executionOptions); - - await SetInvocationRequestAsync( - new InvocationRequest( - pwsh => request.ExecuteAsync().GetAwaiter().GetResult())).ConfigureAwait(false); - - try - { - return await request.Results.ConfigureAwait(false); - } - finally - { - await SetInvocationRequestAsync(request: null).ConfigureAwait(false); - } - } - - /// - /// Marshals a to run on the pipeline thread. A new - /// will be created for the invocation. - /// - /// - /// The to invoke on the pipeline thread. The nested - /// instance for the created - /// will be passed as an argument. - /// - /// - /// An awaitable that the caller can use to know when execution completes. - /// - internal async Task InvokeOnPipelineThreadAsync(Action invocationAction) - { - var request = new InvocationRequest(pwsh => - { - using (_promptNest.GetRunspaceHandle(isReadLine: false, CancellationToken.None)) - { - pwsh.Runspace = _runspace; - invocationAction(pwsh); - } - }); - - await SetInvocationRequestAsync(request).ConfigureAwait(false); - try - { - await request.Task.ConfigureAwait(false); - } - finally - { - await SetInvocationRequestAsync(null).ConfigureAwait(false); - } - } - - private async Task WaitForExistingRequestAsync() - { - InvocationRequest existingRequest; - await _lock.WaitAsync().ConfigureAwait(false); - try - { - existingRequest = _invocationRequest; - if (existingRequest == null || existingRequest.Task.IsCompleted) - { - return; - } - } - finally - { - _lock.Release(); - } - - await existingRequest.Task.ConfigureAwait(false); - } - - private async Task SetInvocationRequestAsync(InvocationRequest request) - { - await WaitForExistingRequestAsync().ConfigureAwait(false); - await _lock.WaitAsync().ConfigureAwait(false); - try - { - _invocationRequest = request; - } - finally - { - _lock.Release(); - } - - _powerShellContext.ForcePSEventHandling(); - } - - private void OnPowerShellIdle(object sender, EventArgs e) - { - if (!_lock.Wait(0)) - { - return; - } - - InvocationRequest currentRequest = null; - try - { - if (_invocationRequest == null) - { - return; - } - - currentRequest = _invocationRequest; - } - finally - { - _lock.Release(); - } - - _promptNest.PushPromptContext(); - try - { - currentRequest.Invoke(_promptNest.GetPowerShell()); - } - finally - { - _promptNest.PopPromptContext(); - } - } - - private PSEventSubscriber CreateInvocationSubscriber() - { - PSEventSubscriber subscriber = _runspace.Events.SubscribeEvent( - source: null, - eventName: PSEngineEvent.OnIdle, - sourceIdentifier: PSEngineEvent.OnIdle, - data: null, - handlerDelegate: OnPowerShellIdle, - supportEvent: true, - forwardEvent: false); - - SetSubscriberExecutionThreadWithReflection(subscriber); - - subscriber.Unsubscribed += OnInvokerUnsubscribed; - - return subscriber; - } - - private void OnInvokerUnsubscribed(object sender, PSEventUnsubscribedEventArgs e) - { - CreateInvocationSubscriber(); - } - - private static void SetSubscriberExecutionThreadWithReflection(PSEventSubscriber subscriber) - { - // We need to create the PowerShell object in the same thread so we can get a nested - // PowerShell. This is the only way to consistently take control of the pipeline. The - // alternative is to make the subscriber a script block and have that create and process - // the PowerShell object, but that puts us in a different SessionState and is a lot slower. - s_shouldProcessInExecutionThreadProperty.SetValue(subscriber, true); - } - - private class InvocationRequest : TaskCompletionSource - { - private readonly Action _invocationAction; - - internal InvocationRequest(Action invocationAction) - { - _invocationAction = invocationAction; - } - - internal void Invoke(PowerShell pwsh) - { - try - { - _invocationAction(pwsh); - - // Ensure the result is set in another thread otherwise the caller - // may take over the pipeline thread. - System.Threading.Tasks.Task.Run(() => SetResult(true)); - } - catch (Exception e) - { - System.Threading.Tasks.Task.Run(() => SetException(e)); - } - } - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/LegacyReadLineContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/LegacyReadLineContext.cs deleted file mode 100644 index ce77c4343..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/LegacyReadLineContext.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - internal class LegacyReadLineContext : IPromptContext - { - private readonly ConsoleReadLine _legacyReadLine; - - internal LegacyReadLineContext(PowerShellContextService powerShellContext) - { - _legacyReadLine = new ConsoleReadLine(powerShellContext); - } - - public Task AbortReadLineAsync() - { - return Task.FromResult(true); - } - - public Task InvokeReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) - { - return _legacyReadLine.InvokeLegacyReadLineAsync(isCommandLine, cancellationToken); - } - - public Task WaitForReadLineExitAsync() - { - return Task.FromResult(true); - } - - public void AddToHistory(string command) - { - // Do nothing, history is managed completely by the PowerShell engine in legacy ReadLine. - } - - public void AbortReadLine() - { - // Do nothing, no additional actions are needed to cancel ReadLine. - } - - public void WaitForReadLineExit() - { - // Do nothing, ReadLine cancellation is instant or not appliciable. - } - - public void ForcePSEventHandling() - { - // Do nothing, the pipeline thread is not occupied by legacy ReadLine. - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/OutputType.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/OutputType.cs deleted file mode 100644 index 6e69386ce..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/OutputType.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Enumerates the types of output lines that will be sent - /// to an IConsoleHost implementation. - /// - internal enum OutputType - { - /// - /// A normal output line, usually written with the or Write-Host or - /// Write-Output cmdlets. - /// - Normal, - - /// - /// A debug output line, written with the Write-Debug cmdlet. - /// - Debug, - - /// - /// A verbose output line, written with the Write-Verbose cmdlet. - /// - Verbose, - - /// - /// A warning output line, written with the Write-Warning cmdlet. - /// - Warning, - - /// - /// An error output line, written with the Write-Error cmdlet or - /// as a result of some error during PowerShell pipeline execution. - /// - Error - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/OutputWrittenEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/OutputWrittenEventArgs.cs deleted file mode 100644 index 3724a7269..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/OutputWrittenEventArgs.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides details about output that has been written to the - /// PowerShell host. - /// - internal class OutputWrittenEventArgs - { - /// - /// Gets the text of the output. - /// - public string OutputText { get; private set; } - - /// - /// Gets the type of the output. - /// - public OutputType OutputType { get; private set; } - - /// - /// Gets a boolean which indicates whether a newline - /// should be written after the output. - /// - public bool IncludeNewLine { get; private set; } - - /// - /// Gets the foreground color of the output text. - /// - public ConsoleColor ForegroundColor { get; private set; } - - /// - /// Gets the background color of the output text. - /// - public ConsoleColor BackgroundColor { get; private set; } - - /// - /// Creates an instance of the OutputWrittenEventArgs class. - /// - /// The text of the output. - /// A boolean which indicates whether a newline should be written after the output. - /// The type of the output. - /// The foreground color of the output text. - /// The background color of the output text. - public OutputWrittenEventArgs( - string outputText, - bool includeNewLine, - OutputType outputType, - ConsoleColor foregroundColor, - ConsoleColor backgroundColor) - { - this.OutputText = outputText; - this.IncludeNewLine = includeNewLine; - this.OutputType = outputType; - this.ForegroundColor = foregroundColor; - this.BackgroundColor = backgroundColor; - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs deleted file mode 100644 index 62f5e21e2..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Management.Automation.Runspaces; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - using System.IO; - using System.Management.Automation; - - internal class PSReadLinePromptContext : IPromptContext - { - private static readonly Lazy s_lazyInvokeReadLineForEditorServicesCmdletInfo = new Lazy(() => - { - var type = Type.GetType("Microsoft.PowerShell.EditorServices.Commands.InvokeReadLineForEditorServicesCommand, Microsoft.PowerShell.EditorServices.Hosting"); - return new CmdletInfo("__Invoke-ReadLineForEditorServices", type); - }); - - private static ExecutionOptions s_psrlExecutionOptions = new ExecutionOptions - { - WriteErrorsToHost = false, - WriteOutputToHost = false, - InterruptCommandPrompt = false, - AddToHistory = false, - IsReadLine = true, - }; - - private readonly PowerShellContextService _powerShellContext; - - private readonly PromptNest _promptNest; - - private readonly InvocationEventQueue _invocationEventQueue; - - private readonly ConsoleReadLine _consoleReadLine; - - private readonly PSReadLineProxy _readLineProxy; - - private CancellationTokenSource _readLineCancellationSource; - - internal PSReadLinePromptContext( - PowerShellContextService powerShellContext, - PromptNest promptNest, - InvocationEventQueue invocationEventQueue, - PSReadLineProxy readLineProxy) - { - _promptNest = promptNest; - _powerShellContext = powerShellContext; - _invocationEventQueue = invocationEventQueue; - _consoleReadLine = new ConsoleReadLine(powerShellContext); - _readLineProxy = readLineProxy; - - _readLineProxy.OverrideReadKey( - intercept => ConsoleProxy.SafeReadKey( - intercept, - _readLineCancellationSource.Token)); - } - - internal static bool TryGetPSReadLineProxy( - ILogger logger, - Runspace runspace, - string bundledModulePath, - out PSReadLineProxy readLineProxy) - { - readLineProxy = null; - logger.LogTrace("Attempting to load PSReadLine"); - using (var pwsh = PowerShell.Create()) - { - pwsh.Runspace = runspace; - pwsh.AddCommand("Microsoft.PowerShell.Core\\Import-Module") - .AddParameter("Name", Path.Combine(bundledModulePath, "PSReadLine")) - .Invoke(); - - if (pwsh.HadErrors) - { - logger.LogWarning("PSConsoleReadline type not found: {Reason}", pwsh.Streams.Error[0].ToString()); - return false; - } - - var psReadLineType = Type.GetType("Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2"); - - if (psReadLineType == null) - { - // NOTE: For some reason `Type.GetType(...)` can fail to find the type, - // and in that case, this search through the `AppDomain` for some reason will succeed. - // It's slower, but only happens when needed. - logger.LogTrace("PSConsoleReadline type not found using Type.GetType(), searching all loaded assemblies..."); - psReadLineType = AppDomain.CurrentDomain - .GetAssemblies() - .FirstOrDefault(asm => asm.GetName().Name.Equals("Microsoft.PowerShell.PSReadLine2")) - ?.ExportedTypes - ?.FirstOrDefault(type => type.FullName.Equals("Microsoft.PowerShell.PSConsoleReadLine")); - if (psReadLineType == null) - { - logger.LogWarning("PSConsoleReadLine type not found anywhere!"); - return false; - } - } - - try - { - readLineProxy = new PSReadLineProxy(psReadLineType, logger); - } - catch (InvalidOperationException e) - { - // The Type we got back from PowerShell doesn't have the members we expected. - // Could be an older version, a custom build, or something a newer version with - // breaking changes. - logger.LogWarning("PSReadLineProxy unable to be initialized: {Reason}", e); - return false; - } - } - - return true; - } - - public async Task InvokeReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) - { - _readLineCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var localTokenSource = _readLineCancellationSource; - if (localTokenSource.Token.IsCancellationRequested) - { - throw new TaskCanceledException(); - } - - if (!isCommandLine) - { - return await _consoleReadLine.InvokeLegacyReadLineAsync( - isCommandLine: false, - _readLineCancellationSource.Token).ConfigureAwait(false); - } - - var readLineCommand = new PSCommand() - .AddCommand(s_lazyInvokeReadLineForEditorServicesCmdletInfo.Value) - .AddParameter("CancellationToken", _readLineCancellationSource.Token); - - IEnumerable readLineResults = await _powerShellContext.ExecuteCommandAsync( - readLineCommand, - errorMessages: null, - s_psrlExecutionOptions, - // NOTE: This command should always be allowed to complete, as the command itself - // has a linked cancellation token such that PSReadLine will be correctly cancelled. - CancellationToken.None).ConfigureAwait(false); - - string line = readLineResults.FirstOrDefault(); - - return cancellationToken.IsCancellationRequested - ? string.Empty - : line; - } - - public void AbortReadLine() - { - if (_readLineCancellationSource == null) - { - return; - } - - _readLineCancellationSource.Cancel(); - - WaitForReadLineExit(); - } - - public async Task AbortReadLineAsync() { - if (_readLineCancellationSource == null) - { - return; - } - - _readLineCancellationSource.Cancel(); - - await WaitForReadLineExitAsync().ConfigureAwait(false); - } - - public void WaitForReadLineExit() - { - using (_promptNest.GetRunspaceHandle(isReadLine: true, CancellationToken.None)) - { } - } - - public async Task WaitForReadLineExitAsync() { - using (await _promptNest.GetRunspaceHandleAsync(isReadLine: true, CancellationToken.None).ConfigureAwait(false)) - { } - } - - public void AddToHistory(string command) - { - _readLineProxy.AddToHistory(command); - } - - public void ForcePSEventHandling() - { - _readLineProxy.ForcePSEventHandling(); - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLineProxy.cs deleted file mode 100644 index 69cf90f5f..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLineProxy.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Reflection; -using Microsoft.Extensions.Logging; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - internal class PSReadLineProxy - { - private const string FieldMemberType = "field"; - - private const string MethodMemberType = "method"; - - private const string AddToHistoryMethodName = "AddToHistory"; - - private const string SetKeyHandlerMethodName = "SetKeyHandler"; - - private const string ReadKeyOverrideFieldName = "_readKeyOverride"; - - private const string VirtualTerminalTypeName = "Microsoft.PowerShell.Internal.VirtualTerminal"; - - private const string ForcePSEventHandlingMethodName = "ForcePSEventHandling"; - - private static readonly Type[] s_setKeyHandlerTypes = - { - typeof(string[]), - typeof(Action), - typeof(string), - typeof(string) - }; - - private static readonly Type[] s_addToHistoryTypes = { typeof(string) }; - - private readonly FieldInfo _readKeyOverrideField; - - internal PSReadLineProxy(Type psConsoleReadLine, ILogger logger) - { - ForcePSEventHandling = - (Action)psConsoleReadLine.GetMethod( - ForcePSEventHandlingMethodName, - BindingFlags.Static | BindingFlags.NonPublic) - ?.CreateDelegate(typeof(Action)); - - AddToHistory = (Action)psConsoleReadLine.GetMethod( - AddToHistoryMethodName, - s_addToHistoryTypes) - ?.CreateDelegate(typeof(Action)); - - SetKeyHandler = - (Action, string, string>)psConsoleReadLine.GetMethod( - SetKeyHandlerMethodName, - s_setKeyHandlerTypes) - ?.CreateDelegate(typeof(Action, string, string>)); - - _readKeyOverrideField = psConsoleReadLine.GetTypeInfo().Assembly - .GetType(VirtualTerminalTypeName) - ?.GetField(ReadKeyOverrideFieldName, BindingFlags.Static | BindingFlags.NonPublic); - - if (_readKeyOverrideField == null) - { - throw NewInvalidPSReadLineVersionException( - FieldMemberType, - ReadKeyOverrideFieldName, - logger); - } - - if (SetKeyHandler == null) - { - throw NewInvalidPSReadLineVersionException( - MethodMemberType, - SetKeyHandlerMethodName, - logger); - } - - if (AddToHistory == null) - { - throw NewInvalidPSReadLineVersionException( - MethodMemberType, - AddToHistoryMethodName, - logger); - } - - if (ForcePSEventHandling == null) - { - throw NewInvalidPSReadLineVersionException( - MethodMemberType, - ForcePSEventHandlingMethodName, - logger); - } - } - - internal Action AddToHistory { get; } - - internal Action, object>, string, string> SetKeyHandler { get; } - - internal Action ForcePSEventHandling { get; } - - internal void OverrideReadKey(Func readKeyFunc) - { - _readKeyOverrideField.SetValue(null, readKeyFunc); - } - - private static InvalidOperationException NewInvalidPSReadLineVersionException( - string memberType, - string memberName, - ILogger logger) - { - logger.LogError( - $"The loaded version of PSReadLine is not supported. The {memberType} \"{memberName}\" was not found."); - - return new InvalidOperationException(); - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PipelineExecutionRequest.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PipelineExecutionRequest.cs deleted file mode 100644 index 21fdaaf01..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PipelineExecutionRequest.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Generic; -using System.Management.Automation; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - internal interface IPipelineExecutionRequest - { - Task ExecuteAsync(); - - Task WaitTask { get; } - } - - /// - /// Contains details relating to a request to execute a - /// command on the PowerShell pipeline thread. - /// - /// The expected result type of the execution. - internal class PipelineExecutionRequest : IPipelineExecutionRequest - { - private PowerShellContextService _powerShellContext; - private PSCommand _psCommand; - private StringBuilder _errorMessages; - private ExecutionOptions _executionOptions; - private TaskCompletionSource> _resultsTask; - - public Task> Results - { - get { return this._resultsTask.Task; } - } - - public Task WaitTask { get { return Results; } } - - public PipelineExecutionRequest( - PowerShellContextService powerShellContext, - PSCommand psCommand, - StringBuilder errorMessages, - bool sendOutputToHost) - : this( - powerShellContext, - psCommand, - errorMessages, - new ExecutionOptions() - { - WriteOutputToHost = sendOutputToHost - }) - { } - - - public PipelineExecutionRequest( - PowerShellContextService powerShellContext, - PSCommand psCommand, - StringBuilder errorMessages, - ExecutionOptions executionOptions) - { - _powerShellContext = powerShellContext; - _psCommand = psCommand; - _errorMessages = errorMessages; - _executionOptions = executionOptions; - _resultsTask = new TaskCompletionSource>(); - } - - public async Task ExecuteAsync() - { - var results = - await _powerShellContext.ExecuteCommandAsync( - _psCommand, - _errorMessages, - _executionOptions).ConfigureAwait(false); - - _ = Task.Run(() => _resultsTask.SetResult(results)); - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShell5Operations.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShell5Operations.cs deleted file mode 100644 index ec40599d2..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShell5Operations.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Generic; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - internal class PowerShell5Operations : IVersionSpecificOperations - { - public void ConfigureDebugger(Runspace runspace) - { - if (runspace.Debugger != null) - { - runspace.Debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript); - } - } - - public virtual void PauseDebugger(Runspace runspace) - { - if (runspace.Debugger != null) - { - runspace.Debugger.SetDebuggerStepMode(true); - } - } - - public virtual bool IsDebuggerStopped(PromptNest promptNest, Runspace runspace) - { - return runspace.Debugger.InBreakpoint || (promptNest.IsRemote && promptNest.IsInDebugger); - } - - public IEnumerable ExecuteCommandInDebugger( - PowerShellContextService powerShellContext, - Runspace currentRunspace, - PSCommand psCommand, - bool sendOutputToHost, - out DebuggerResumeAction? debuggerResumeAction) - { - debuggerResumeAction = null; - PSDataCollection outputCollection = new PSDataCollection(); - - if (sendOutputToHost) - { - outputCollection.DataAdded += - (obj, e) => - { - for (int i = e.Index; i < outputCollection.Count; i++) - { - powerShellContext.WriteOutput( - outputCollection[i].ToString(), - true); - } - }; - } - - DebuggerCommandResults commandResults = - currentRunspace.Debugger.ProcessCommand( - psCommand, - outputCollection); - - // Pass along the debugger's resume action if the user's - // command caused one to be returned - debuggerResumeAction = commandResults.ResumeAction; - - IEnumerable results = null; - if (typeof(TResult) != typeof(PSObject)) - { - results = - outputCollection - .Select(pso => pso.BaseObject) - .Cast(); - } - else - { - results = outputCollection.Cast(); - } - - return results; - } - - public void StopCommandInDebugger(PowerShellContextService powerShellContext) - { - // If the RunspaceAvailability is None, the runspace is dead and we should not try to run anything in it. - if (powerShellContext.CurrentRunspace.Runspace.RunspaceAvailability != RunspaceAvailability.None) - { - powerShellContext.CurrentRunspace.Runspace.Debugger.StopProcessCommand(); - } - } - - public void ExitNestedPrompt(PSHost host) - { - try - { - host.ExitNestedPrompt(); - } - catch (FlowControlException) - { - } - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellContextState.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellContextState.cs deleted file mode 100644 index 00849b045..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellContextState.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Enumerates the possible states for a PowerShellContext. - /// - internal enum PowerShellContextState - { - /// - /// Indicates an unknown, potentially uninitialized state. - /// - Unknown = 0, - - /// - /// Indicates the state where the session is starting but - /// not yet fully initialized. - /// - NotStarted, - - /// - /// Indicates that the session is ready to accept commands - /// for execution. - /// - Ready, - - /// - /// Indicates that the session is currently running a command. - /// - Running, - - /// - /// Indicates that the session is aborting the current execution. - /// - Aborting, - - /// - /// Indicates that the session is already disposed and cannot - /// accept further execution requests. - /// - Disposed - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellExecutionResult.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellExecutionResult.cs deleted file mode 100644 index 7ce502c1b..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellExecutionResult.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Enumerates the possible execution results that can occur after - /// executing a command or script. - /// - internal enum PowerShellExecutionResult - { - /// - /// Indicates that execution is not yet finished. - /// - NotFinished, - - /// - /// Indicates that execution has failed. - /// - Failed, - - /// - /// Indicates that execution was aborted by the user. - /// - Aborted, - - /// - /// Indicates that execution was stopped by the debugger. - /// - Stopped, - - /// - /// Indicates that execution completed successfully. - /// - Completed - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/ProgressDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ProgressDetails.cs deleted file mode 100644 index 86a06b83b..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/ProgressDetails.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides details about the progress of a particular activity. - /// - internal class ProgressDetails - { - /// - /// Gets the percentage of the activity that has been completed. - /// - public int PercentComplete { get; private set; } - - internal static ProgressDetails Create(ProgressRecord progressRecord) - { - //progressRecord.RecordType == ProgressRecordType.Completed; - //progressRecord.Activity; - //progressRecord. - - return new ProgressDetails - { - PercentComplete = progressRecord.PercentComplete - }; - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNest.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNest.cs deleted file mode 100644 index 208353632..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNest.cs +++ /dev/null @@ -1,562 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Concurrent; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - using System; - using System.Management.Automation; - - /// - /// Represents the stack of contexts in which PowerShell commands can be invoked. - /// - internal class PromptNest : IDisposable - { - private readonly ConcurrentStack _frameStack; - - private readonly PromptNestFrame _readLineFrame; - - private readonly IVersionSpecificOperations _versionSpecificOperations; - - private readonly object _syncObject = new object(); - - private readonly object _disposeSyncObject = new object(); - - private IHostInput _consoleReader; - - private PowerShellContextService _powerShellContext; - - private bool _isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The to track prompt status for. - /// - /// - /// The instance for the first frame. - /// - /// - /// The input handler. - /// - /// - /// The for the calling - /// instance. - /// - /// - /// This constructor should only be called when - /// is set to the initial runspace. - /// - internal PromptNest( - PowerShellContextService powerShellContext, - PowerShell initialPowerShell, - IHostInput consoleReader, - IVersionSpecificOperations versionSpecificOperations) - { - _versionSpecificOperations = versionSpecificOperations; - _consoleReader = consoleReader; - _powerShellContext = powerShellContext; - _frameStack = new ConcurrentStack(); - _frameStack.Push( - new PromptNestFrame( - initialPowerShell, - NewHandleQueue())); - - var readLineShell = PowerShell.Create(); - readLineShell.Runspace = powerShellContext.CurrentRunspace.Runspace; - _readLineFrame = new PromptNestFrame( - readLineShell, - new AsyncQueue()); - - ReleaseRunspaceHandleImpl(isReadLine: true); - } - - /// - /// Gets a value indicating whether the current frame was created by a debugger stop event. - /// - internal bool IsInDebugger => CurrentFrame.FrameType.HasFlag(PromptNestFrameType.Debug); - - /// - /// Gets a value indicating whether the current frame was created for an out of process runspace. - /// - internal bool IsRemote => CurrentFrame.FrameType.HasFlag(PromptNestFrameType.Remote); - - /// - /// Gets a value indicating whether the current frame was created by PSHost.EnterNestedPrompt(). - /// - internal bool IsNestedPrompt => CurrentFrame.FrameType.HasFlag(PromptNestFrameType.NestedPrompt); - - /// - /// Gets a value indicating the current number of frames managed by this PromptNest. - /// - internal int NestedPromptLevel => _frameStack.Count; - - private PromptNestFrame CurrentFrame - { - get - { - _frameStack.TryPeek(out PromptNestFrame currentFrame); - return _isDisposed ? _readLineFrame : currentFrame; - } - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - lock (_disposeSyncObject) - { - if (_isDisposed || !disposing) - { - return; - } - - while (NestedPromptLevel > 1) - { - _consoleReader?.StopCommandLoop(); - var currentFrame = CurrentFrame; - if (currentFrame.FrameType.HasFlag(PromptNestFrameType.Debug)) - { - _versionSpecificOperations.StopCommandInDebugger(_powerShellContext); - currentFrame.ThreadController.StartThreadExit(DebuggerResumeAction.Stop); - currentFrame.WaitForFrameExit(CancellationToken.None); - continue; - } - - if (currentFrame.FrameType.HasFlag(PromptNestFrameType.NestedPrompt)) - { - _powerShellContext.ExitAllNestedPrompts(); - continue; - } - - currentFrame.PowerShell.BeginStop(null, null); - currentFrame.WaitForFrameExit(CancellationToken.None); - } - - _consoleReader?.StopCommandLoop(); - _readLineFrame.Dispose(); - CurrentFrame.Dispose(); - _frameStack.Clear(); - _powerShellContext = null; - _consoleReader = null; - _isDisposed = true; - } - } - - /// - /// Gets the for the current frame. - /// - /// - /// The for the current frame, or - /// if the current frame does not have one. - /// - internal ThreadController GetThreadController() - { - if (_isDisposed) - { - return null; - } - - return CurrentFrame.IsThreadController ? CurrentFrame.ThreadController : null; - } - - /// - /// Create a new and set it as the current frame. - /// - internal void PushPromptContext() - { - if (_isDisposed) - { - return; - } - - PushPromptContext(PromptNestFrameType.Normal); - } - - /// - /// Create a new and set it as the current frame. - /// - /// The frame type. - internal void PushPromptContext(PromptNestFrameType frameType) - { - if (_isDisposed) - { - return; - } - - _frameStack.Push( - new PromptNestFrame( - frameType.HasFlag(PromptNestFrameType.Remote) - ? PowerShell.Create() - : PowerShell.Create(RunspaceMode.CurrentRunspace), - NewHandleQueue(), - frameType)); - } - - /// - /// Dispose of the current and revert to the previous frame. - /// - internal void PopPromptContext() - { - PromptNestFrame currentFrame; - lock (_syncObject) - { - if (_isDisposed || _frameStack.Count == 1) - { - return; - } - - _frameStack.TryPop(out currentFrame); - } - - currentFrame.Dispose(); - } - - /// - /// Get the instance for the current - /// . - /// - /// Indicates whether this is for a PSReadLine command. - /// The instance for the current frame. - internal PowerShell GetPowerShell(bool isReadLine = false) - { - if (_isDisposed) - { - return null; - } - - // Typically we want to run PSReadLine on the current nest frame. - // The exception is when the current frame is remote, in which - // case we need to run it in it's own frame because we can't take - // over a remote pipeline through event invocation. - if (NestedPromptLevel > 1 && !IsRemote) - { - return CurrentFrame.PowerShell; - } - - return isReadLine ? _readLineFrame.PowerShell : CurrentFrame.PowerShell; - } - - /// - /// Get the for the current . - /// - /// - /// The that can be used to cancel the request. - /// - /// Indicates whether this is for a PSReadLine command. - /// The for the current frame. - internal RunspaceHandle GetRunspaceHandle(bool isReadLine, CancellationToken cancellationToken) - { - if (_isDisposed) - { - return null; - } - - // Also grab the main runspace handle if this is for a ReadLine pipeline and the runspace - // is in process. - if (isReadLine && !_powerShellContext.IsCurrentRunspaceOutOfProcess()) - { - GetRunspaceHandleImpl(isReadLine: false, cancellationToken); - } - - return GetRunspaceHandleImpl(isReadLine, cancellationToken); - } - - - /// - /// Get the for the current . - /// - /// - /// The that will be checked prior to - /// completing the returned task. - /// - /// Indicates whether this is for a PSReadLine command. - /// - /// A object representing the asynchronous operation. - /// The property will return the - /// for the current frame. - /// - internal async Task GetRunspaceHandleAsync(bool isReadLine, CancellationToken cancellationToken) - { - if (_isDisposed) - { - return null; - } - - // Also grab the main runspace handle if this is for a ReadLine pipeline and the runspace - // is in process. - if (isReadLine && !_powerShellContext.IsCurrentRunspaceOutOfProcess()) - { - await GetRunspaceHandleImplAsync(isReadLine: false, cancellationToken).ConfigureAwait(false); - } - - return await GetRunspaceHandleImplAsync(isReadLine, cancellationToken).ConfigureAwait(false); - } - - /// - /// Releases control of the runspace aquired via the . - /// - /// - /// The representing the control to release. - /// - internal void ReleaseRunspaceHandle(RunspaceHandle runspaceHandle) - { - if (_isDisposed) - { - return; - } - - ReleaseRunspaceHandleImpl(runspaceHandle.IsReadLine); - if (runspaceHandle.IsReadLine && !_powerShellContext.IsCurrentRunspaceOutOfProcess()) - { - ReleaseRunspaceHandleImpl(isReadLine: false); - } - } - - /// - /// Releases control of the runspace aquired via the . - /// - /// - /// The representing the control to release. - /// - /// - /// A object representing the release of the - /// . - /// - internal async Task ReleaseRunspaceHandleAsync(RunspaceHandle runspaceHandle) - { - if (_isDisposed) - { - return; - } - - await ReleaseRunspaceHandleImplAsync(runspaceHandle.IsReadLine).ConfigureAwait(false); - if (runspaceHandle.IsReadLine && !_powerShellContext.IsCurrentRunspaceOutOfProcess()) - { - await ReleaseRunspaceHandleImplAsync(isReadLine: false).ConfigureAwait(false); - } - } - - /// - /// Determines if the current frame is unavailable for commands. - /// - /// - /// A value indicating whether the current frame is unavailable for commands. - /// - internal bool IsMainThreadBusy() - { - return !_isDisposed && CurrentFrame.Queue.IsEmpty; - } - - /// - /// Determines if a PSReadLine command is currently running. - /// - /// - /// A value indicating whether a PSReadLine command is currently running. - /// - internal bool IsReadLineBusy() - { - return !_isDisposed && _readLineFrame.Queue.IsEmpty; - } - - /// - /// Blocks until the current frame has been disposed. - /// - /// - /// A delegate that when invoked initates the exit of the current frame. - /// - internal void WaitForCurrentFrameExit(Action initiator) - { - if (_isDisposed) - { - return; - } - - var currentFrame = CurrentFrame; - try - { - initiator.Invoke(currentFrame); - } - finally - { - currentFrame.WaitForFrameExit(CancellationToken.None); - } - } - - /// - /// Blocks until the current frame has been disposed. - /// - internal void WaitForCurrentFrameExit() - { - if (_isDisposed) - { - return; - } - - CurrentFrame.WaitForFrameExit(CancellationToken.None); - } - - /// - /// Blocks until the current frame has been disposed. - /// - /// - /// The used the exit the block prior to - /// the current frame being disposed. - /// - internal void WaitForCurrentFrameExit(CancellationToken cancellationToken) - { - if (_isDisposed) - { - return; - } - - CurrentFrame.WaitForFrameExit(cancellationToken); - } - - /// - /// Creates a task that is completed when the current frame has been disposed. - /// - /// - /// A delegate that when invoked initates the exit of the current frame. - /// - /// - /// A object representing the current frame being disposed. - /// - internal async Task WaitForCurrentFrameExitAsync(Func initiator) - { - if (_isDisposed) - { - return; - } - - var currentFrame = CurrentFrame; - try - { - await initiator.Invoke(currentFrame).ConfigureAwait(false); - } - finally - { - await currentFrame.WaitForFrameExitAsync(CancellationToken.None).ConfigureAwait(false); - } - } - - /// - /// Creates a task that is completed when the current frame has been disposed. - /// - /// - /// A delegate that when invoked initates the exit of the current frame. - /// - /// - /// A object representing the current frame being disposed. - /// - internal async Task WaitForCurrentFrameExitAsync(Action initiator) - { - if (_isDisposed) - { - return; - } - - var currentFrame = CurrentFrame; - try - { - initiator.Invoke(currentFrame); - } - finally - { - await currentFrame.WaitForFrameExitAsync(CancellationToken.None).ConfigureAwait(false); - } - } - - /// - /// Creates a task that is completed when the current frame has been disposed. - /// - /// - /// A object representing the current frame being disposed. - /// - internal async Task WaitForCurrentFrameExitAsync() - { - if (_isDisposed) - { - return; - } - - await WaitForCurrentFrameExitAsync(CancellationToken.None).ConfigureAwait(false); - } - - /// - /// Creates a task that is completed when the current frame has been disposed. - /// - /// - /// The used the exit the block prior to the current frame being disposed. - /// - /// - /// A object representing the current frame being disposed. - /// - internal async Task WaitForCurrentFrameExitAsync(CancellationToken cancellationToken) - { - if (_isDisposed) - { - return; - } - - await CurrentFrame.WaitForFrameExitAsync(cancellationToken).ConfigureAwait(false); - } - - private AsyncQueue NewHandleQueue() - { - var queue = new AsyncQueue(); - queue.Enqueue(new RunspaceHandle(_powerShellContext)); - return queue; - } - - private RunspaceHandle GetRunspaceHandleImpl(bool isReadLine, CancellationToken cancellationToken) - { - if (isReadLine) - { - return _readLineFrame.Queue.Dequeue(cancellationToken); - } - - return CurrentFrame.Queue.Dequeue(cancellationToken); - } - - private async Task GetRunspaceHandleImplAsync(bool isReadLine, CancellationToken cancellationToken) - { - if (isReadLine) - { - return await _readLineFrame.Queue.DequeueAsync(cancellationToken).ConfigureAwait(false); - } - - return await CurrentFrame.Queue.DequeueAsync(cancellationToken).ConfigureAwait(false); - } - - private void ReleaseRunspaceHandleImpl(bool isReadLine) - { - if (isReadLine) - { - _readLineFrame.Queue.Enqueue(new RunspaceHandle(_powerShellContext, true)); - return; - } - - CurrentFrame.Queue.Enqueue(new RunspaceHandle(_powerShellContext, false)); - } - - private async Task ReleaseRunspaceHandleImplAsync(bool isReadLine) - { - if (isReadLine) - { - await _readLineFrame.Queue.EnqueueAsync(new RunspaceHandle(_powerShellContext, true)).ConfigureAwait(false); - return; - } - - await CurrentFrame.Queue.EnqueueAsync(new RunspaceHandle(_powerShellContext, false)).ConfigureAwait(false); - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNestFrame.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNestFrame.cs deleted file mode 100644 index a23d87d8c..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNestFrame.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - using System.Management.Automation; - - /// - /// Represents a single frame in the . - /// - internal class PromptNestFrame : IDisposable - { - private const PSInvocationState IndisposableStates = PSInvocationState.Stopping | PSInvocationState.Running; - - private SemaphoreSlim _frameExited = new SemaphoreSlim(initialCount: 0); - - private bool _isDisposed = false; - - /// - /// Gets the instance. - /// - internal PowerShell PowerShell { get; } - - /// - /// Gets the queue that controls command invocation order. - /// - internal AsyncQueue Queue { get; } - - /// - /// Gets the frame type. - /// - internal PromptNestFrameType FrameType { get; } - - /// - /// Gets the . - /// - internal ThreadController ThreadController { get; } - - /// - /// Gets a value indicating whether the frame requires command invocations - /// to be routed to a specific thread. - /// - internal bool IsThreadController { get; } - - internal PromptNestFrame(PowerShell powerShell, AsyncQueue handleQueue) - : this(powerShell, handleQueue, PromptNestFrameType.Normal) - { } - - internal PromptNestFrame( - PowerShell powerShell, - AsyncQueue handleQueue, - PromptNestFrameType frameType) - { - PowerShell = powerShell; - Queue = handleQueue; - FrameType = frameType; - IsThreadController = (frameType & (PromptNestFrameType.Debug | PromptNestFrameType.NestedPrompt)) != 0; - if (!IsThreadController) - { - return; - } - - ThreadController = new ThreadController(this); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (_isDisposed) - { - return; - } - - if (disposing) - { - if (IndisposableStates.HasFlag(PowerShell.InvocationStateInfo.State)) - { - PowerShell.BeginStop( - asyncResult => - { - PowerShell.Runspace = null; - PowerShell.Dispose(); - }, - state: null); - } - else - { - PowerShell.Runspace = null; - PowerShell.Dispose(); - } - - _frameExited.Release(); - } - - _isDisposed = true; - } - - /// - /// Blocks until the frame has been disposed. - /// - /// - /// The that will exit the block when cancelled. - /// - internal void WaitForFrameExit(CancellationToken cancellationToken) - { - _frameExited.Wait(cancellationToken); - _frameExited.Release(); - } - - /// - /// Creates a task object that is completed when the frame has been disposed. - /// - /// - /// The that will be checked prior to completing - /// the returned task. - /// - /// - /// A object that represents this frame being disposed. - /// - internal async Task WaitForFrameExitAsync(CancellationToken cancellationToken) - { - await _frameExited.WaitAsync(cancellationToken).ConfigureAwait(false); - _frameExited.Release(); - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNestFrameType.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNestFrameType.cs deleted file mode 100644 index 34246b576..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PromptNestFrameType.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - [Flags] - internal enum PromptNestFrameType - { - Normal = 0, - - NestedPrompt = 1, - - Debug = 2, - - Remote = 4 - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceChangedEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceChangedEventArgs.cs deleted file mode 100644 index b835b12a8..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceChangedEventArgs.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Defines the set of actions that will cause the runspace to be changed. - /// - internal enum RunspaceChangeAction - { - /// - /// The runspace change was caused by entering a new session. - /// - Enter, - - /// - /// The runspace change was caused by exiting the current session. - /// - Exit, - - /// - /// The runspace change was caused by shutting down the service. - /// - Shutdown - } - - /// - /// Provides arguments for the PowerShellContext.RunspaceChanged event. - /// - internal class RunspaceChangedEventArgs - { - /// - /// Gets the RunspaceChangeAction which caused this event. - /// - public RunspaceChangeAction ChangeAction { get; private set; } - - /// - /// Gets a RunspaceDetails object describing the previous runspace. - /// - public RunspaceDetails PreviousRunspace { get; private set; } - - /// - /// Gets a RunspaceDetails object describing the new runspace. - /// - public RunspaceDetails NewRunspace { get; private set; } - - /// - /// Creates a new instance of the RunspaceChangedEventArgs class. - /// - /// The action which caused the runspace to change. - /// The previously active runspace. - /// The newly active runspace. - public RunspaceChangedEventArgs( - RunspaceChangeAction changeAction, - RunspaceDetails previousRunspace, - RunspaceDetails newRunspace) - { - Validate.IsNotNull(nameof(previousRunspace), previousRunspace); - - this.ChangeAction = changeAction; - this.PreviousRunspace = previousRunspace; - this.NewRunspace = newRunspace; - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceDetails.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceDetails.cs deleted file mode 100644 index c744567b8..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceDetails.cs +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.CSharp.RuntimeBinder; -using System; -using System.Management.Automation.Runspaces; -using System.Collections.Generic; -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Specifies the possible types of a runspace. - /// - internal enum RunspaceLocation - { - /// - /// A runspace on the local machine. - /// - Local, - - /// - /// A runspace on a different machine. - /// - Remote - } - - /// - /// Provides details about a runspace being used in the current - /// editing session. - /// - internal class RunspaceDetails - { - #region Private Fields - - private Dictionary capabilities = - new Dictionary(); - - #endregion - - #region Properties - - /// - /// Gets the Runspace instance for which this class contains details. - /// - internal Runspace Runspace { get; private set; } - - /// - /// Gets the PowerShell version of the new runspace. - /// - public PowerShellVersionDetails PowerShellVersion { get; private set; } - - /// - /// Gets the runspace location, either Local or Remote. - /// - public RunspaceLocation Location { get; private set; } - - /// - /// Gets the context in which the runspace was encountered. - /// - public RunspaceContext Context { get; private set; } - - /// - /// Gets the "connection string" for the runspace, generally the - /// ComputerName for a remote runspace or the ProcessId of an - /// "Attach" runspace. - /// - public string ConnectionString { get; private set; } - - /// - /// Gets the details of the runspace's session at the time this - /// RunspaceDetails object was created. - /// - public SessionDetails SessionDetails { get; private set; } - - #endregion - - #region Constructors - - /// - /// Creates a new instance of the RunspaceDetails class. - /// - /// - /// The runspace for which this instance contains details. - /// - /// - /// The SessionDetails for the runspace. - /// - /// - /// The PowerShellVersionDetails of the runspace. - /// - /// - /// The RunspaceLocation of the runspace. - /// - /// - /// The RunspaceContext of the runspace. - /// - /// - /// The connection string of the runspace. - /// - public RunspaceDetails( - Runspace runspace, - SessionDetails sessionDetails, - PowerShellVersionDetails powerShellVersion, - RunspaceLocation runspaceLocation, - RunspaceContext runspaceContext, - string connectionString) - { - this.Runspace = runspace; - this.SessionDetails = sessionDetails; - this.PowerShellVersion = powerShellVersion; - this.Location = runspaceLocation; - this.Context = runspaceContext; - this.ConnectionString = connectionString; - } - - #endregion - - #region Public Methods - - internal void AddCapability(TCapability capability) - where TCapability : IRunspaceCapability - { - this.capabilities.Add(typeof(TCapability), capability); - } - - internal TCapability GetCapability() - where TCapability : IRunspaceCapability - { - TCapability capability = default(TCapability); - this.TryGetCapability(out capability); - return capability; - } - - internal bool TryGetCapability(out TCapability capability) - where TCapability : IRunspaceCapability - { - IRunspaceCapability capabilityAsInterface = default(TCapability); - if (this.capabilities.TryGetValue(typeof(TCapability), out capabilityAsInterface)) - { - capability = (TCapability)capabilityAsInterface; - return true; - } - - capability = default(TCapability); - return false; - } - - internal bool HasCapability() - { - return this.capabilities.ContainsKey(typeof(TCapability)); - } - - /// - /// Creates and populates a new RunspaceDetails instance for the given runspace. - /// - /// - /// The runspace for which details will be gathered. - /// - /// - /// The SessionDetails for the runspace. - /// - /// An ILogger implementation used for writing log messages. - /// A new RunspaceDetails instance. - internal static RunspaceDetails CreateFromRunspace( - Runspace runspace, - SessionDetails sessionDetails, - ILogger logger) - { - Validate.IsNotNull(nameof(runspace), runspace); - Validate.IsNotNull(nameof(sessionDetails), sessionDetails); - - var runspaceLocation = RunspaceLocation.Local; - var runspaceContext = RunspaceContext.Original; - var versionDetails = PowerShellVersionDetails.GetVersionDetails(runspace, logger); - - string connectionString = null; - - if (runspace.ConnectionInfo != null) - { - // Use 'dynamic' to avoid missing NamedPipeRunspaceConnectionInfo - // on PS v3 and v4 - try - { - dynamic connectionInfo = runspace.ConnectionInfo; - if (connectionInfo.ProcessId != null) - { - connectionString = connectionInfo.ProcessId.ToString(); - runspaceContext = RunspaceContext.EnteredProcess; - } - } - catch (RuntimeBinderException) - { - // ProcessId property isn't on the object, move on. - } - - // Grab the $host.name which will tell us if we're in a PSRP session or not - string hostName = - PowerShellContextService.ExecuteScriptAndGetItem( - "$Host.Name", - runspace, - defaultValue: string.Empty, - useLocalScope: true); - - // hostname is 'ServerRemoteHost' when the user enters a session. - // ex. Enter-PSSession - // Attaching to process currently needs to be marked as a local session - // so we skip this if block if the runspace is from Enter-PSHostProcess - if (hostName.Equals("ServerRemoteHost", StringComparison.Ordinal) - && runspace.OriginalConnectionInfo?.GetType().ToString() != "System.Management.Automation.Runspaces.NamedPipeConnectionInfo") - { - runspaceLocation = RunspaceLocation.Remote; - connectionString = - runspace.ConnectionInfo.ComputerName + - (connectionString != null ? $"-{connectionString}" : string.Empty); - } - } - - return - new RunspaceDetails( - runspace, - sessionDetails, - versionDetails, - runspaceLocation, - runspaceContext, - connectionString); - } - - /// - /// Creates a clone of the given runspace through which another - /// runspace was attached. Sets the IsAttached property of the - /// resulting RunspaceDetails object to true. - /// - /// - /// The RunspaceDetails object which the new object based. - /// - /// - /// The RunspaceContext of the runspace. - /// - /// - /// The SessionDetails for the runspace. - /// - /// - /// A new RunspaceDetails instance for the attached runspace. - /// - public static RunspaceDetails CreateFromContext( - RunspaceDetails runspaceDetails, - RunspaceContext runspaceContext, - SessionDetails sessionDetails) - { - return - new RunspaceDetails( - runspaceDetails.Runspace, - sessionDetails, - runspaceDetails.PowerShellVersion, - runspaceDetails.Location, - runspaceContext, - runspaceDetails.ConnectionString); - } - - /// - /// Creates a new RunspaceDetails object from a remote - /// debugging session. - /// - /// - /// The RunspaceDetails object which the new object based. - /// - /// - /// The RunspaceLocation of the runspace. - /// - /// - /// The RunspaceContext of the runspace. - /// - /// - /// The SessionDetails for the runspace. - /// - /// - /// A new RunspaceDetails instance for the attached runspace. - /// - public static RunspaceDetails CreateFromDebugger( - RunspaceDetails runspaceDetails, - RunspaceLocation runspaceLocation, - RunspaceContext runspaceContext, - SessionDetails sessionDetails) - { - // TODO: Get the PowerShellVersion correctly! - return - new RunspaceDetails( - runspaceDetails.Runspace, - sessionDetails, - runspaceDetails.PowerShellVersion, - runspaceLocation, - runspaceContext, - runspaceDetails.ConnectionString); - } - - #endregion - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceHandle.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceHandle.cs deleted file mode 100644 index 24c5f1df5..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceHandle.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides a handle to the runspace that is managed by - /// a PowerShellContext. The holder of this handle. - /// - internal class RunspaceHandle : IDisposable - { - private PowerShellContextService powerShellContext; - - /// - /// Gets the runspace that is held by this handle. - /// - public Runspace Runspace - { - get - { - return ((IHostSupportsInteractiveSession)this.powerShellContext).Runspace; - } - } - - internal bool IsReadLine { get; } - - /// - /// Initializes a new instance of the RunspaceHandle class using the - /// given runspace. - /// - /// The PowerShellContext instance which manages the runspace. - public RunspaceHandle(PowerShellContextService powerShellContext) - : this(powerShellContext, false) - { } - - internal RunspaceHandle(PowerShellContextService powerShellContext, bool isReadLine) - { - this.powerShellContext = powerShellContext; - this.IsReadLine = isReadLine; - } - - /// - /// Disposes the RunspaceHandle once the holder is done using it. - /// Causes the handle to be released back to the PowerShellContext. - /// - public void Dispose() - { - // Release the handle and clear the runspace so that - // no further operations can be performed on it. - this.powerShellContext.ReleaseRunspaceHandle(this); - } - } -} - diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/SessionStateChangedEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/SessionStateChangedEventArgs.cs deleted file mode 100644 index 422934f1d..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/SessionStateChangedEventArgs.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides details about a change in state of a PowerShellContext. - /// - internal class SessionStateChangedEventArgs - { - /// - /// Gets the new state for the session. - /// - public PowerShellContextState NewSessionState { get; private set; } - - /// - /// Gets the execution result of the operation that caused - /// the state change. - /// - public PowerShellExecutionResult ExecutionResult { get; private set; } - - /// - /// Gets the exception that caused a failure state or null otherwise. - /// - public Exception ErrorException { get; private set; } - - /// - /// Creates a new instance of the SessionStateChangedEventArgs class. - /// - /// The new session state. - /// The result of the operation that caused the state change. - /// An exception that describes the failure, if any. - public SessionStateChangedEventArgs( - PowerShellContextState newSessionState, - PowerShellExecutionResult executionResult, - Exception errorException) - { - this.NewSessionState = newSessionState; - this.ExecutionResult = executionResult; - this.ErrorException = errorException; - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/ThreadController.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ThreadController.cs deleted file mode 100644 index 2a8b83bbd..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/ThreadController.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Generic; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext -{ - /// - /// Provides the ability to route PowerShell command invocations to a specific thread. - /// - internal class ThreadController - { - private PromptNestFrame _nestFrame; - - internal AsyncQueue PipelineRequestQueue { get; } - - internal TaskCompletionSource FrameExitTask { get; } - - internal int ManagedThreadId { get; } - - internal bool IsPipelineThread { get; } - - /// - /// Initializes an new instance of the ThreadController class. This constructor should only - /// ever been called from the thread it is meant to control. - /// - /// The parent PromptNestFrame object. - internal ThreadController(PromptNestFrame nestFrame) - { - _nestFrame = nestFrame; - PipelineRequestQueue = new AsyncQueue(); - FrameExitTask = new TaskCompletionSource(); - ManagedThreadId = Thread.CurrentThread.ManagedThreadId; - - // If the debugger stop is triggered on a thread with no default runspace we - // shouldn't attempt to route commands to it. - IsPipelineThread = Runspace.DefaultRunspace != null; - } - - /// - /// Determines if the caller is already on the thread that this object maintains. - /// - /// - /// A value indicating if the caller is already on the thread maintained by this object. - /// - internal bool IsCurrentThread() - { - return Thread.CurrentThread.ManagedThreadId == ManagedThreadId; - } - - /// - /// Requests the invocation of a PowerShell command on the thread maintained by this object. - /// - /// The execution request to send. - /// - /// A task object representing the asynchronous operation. The Result property will return - /// the output of the command invocation. - /// - internal async Task> RequestPipelineExecutionAsync( - PipelineExecutionRequest executionRequest) - { - await PipelineRequestQueue.EnqueueAsync(executionRequest).ConfigureAwait(false); - return await executionRequest.Results.ConfigureAwait(false); - } - - /// - /// Retrieves the first currently queued execution request. If there are no pending - /// execution requests then the task will be completed when one is requested. - /// - /// - /// A task object representing the asynchronous operation. The Result property will return - /// the retrieved pipeline execution request. - /// - internal Task TakeExecutionRequestAsync() - { - return PipelineRequestQueue.DequeueAsync(); - } - - /// - /// Marks the thread to be exited. - /// - /// - /// The resume action for the debugger. If the frame is not a debugger frame this parameter - /// is ignored. - /// - internal void StartThreadExit(DebuggerResumeAction action) - { - StartThreadExit(action, waitForExit: false); - } - - /// - /// Marks the thread to be exited. - /// - /// - /// The resume action for the debugger. If the frame is not a debugger frame this parameter - /// is ignored. - /// - /// - /// Indicates whether the method should block until the exit is completed. - /// - internal void StartThreadExit(DebuggerResumeAction action, bool waitForExit) - { - Task.Run(() => FrameExitTask.TrySetResult(action)); - if (!waitForExit) - { - return; - } - - _nestFrame.WaitForFrameExit(CancellationToken.None); - } - - /// - /// Creates a task object that completes when the thread has be marked for exit. - /// - /// - /// A task object representing the frame receiving a request to exit. The Result property - /// will return the DebuggerResumeAction supplied with the request. - /// - internal async Task Exit() - { - return await FrameExitTask.Task.ConfigureAwait(false); - } - } -} From cf0065e827d7c4b582d1e1000f05bc3320faf02e Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:38:26 -0700 Subject: [PATCH 096/176] Ensure ConfigureAwait(false) is used everywhere --- .../Extensions/Api/EditorUIService.cs | 12 +++++++++--- .../Services/DebugAdapter/BreakpointService.cs | 12 ++++++------ .../Services/DebugAdapter/DebugService.cs | 11 +++++------ .../DebugAdapter/Handlers/LaunchAndAttachHandler.cs | 8 ++++---- .../PowerShell/Handlers/GetVersionHandler.cs | 3 ++- .../Services/PowerShell/Runspace/RunspaceInfo.cs | 3 ++- .../Workspace/Handlers/ConfigurationHandler.cs | 2 +- 7 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs b/src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs index 8fd462e31..1fcafabcf 100644 --- a/src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs +++ b/src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs @@ -116,7 +116,9 @@ public async Task PromptInputAsync(string message) new ShowInputPromptRequest { Name = message, - }).Returning(CancellationToken.None).ConfigureAwait(false); + }) + .Returning(CancellationToken.None) + .ConfigureAwait(false); if (response.PromptCancelled) { @@ -142,7 +144,9 @@ public async Task> PromptMultipleSelectionAsync(string mes Message = message, Choices = choiceDetails, DefaultChoices = defaultChoiceIndexes?.ToArray(), - }).Returning(CancellationToken.None).ConfigureAwait(false); + }) + .Returning(CancellationToken.None) + .ConfigureAwait(false); if (response.PromptCancelled) { @@ -168,7 +172,9 @@ public async Task PromptSelectionAsync(string message, IReadOnlyList -1 ? new[] { defaultChoiceIndex } : null, - }).Returning(CancellationToken.None).ConfigureAwait(false); + }) + .Returning(CancellationToken.None) + .ConfigureAwait(false); if (response.PromptCancelled) { diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs index 54e9c53eb..629085abd 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs @@ -54,9 +54,9 @@ public async Task> GetBreakpointsAsync() } // Legacy behavior - PSCommand psCommand = new PSCommand(); - psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint"); - IEnumerable breakpoints = await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None); + PSCommand psCommand = new PSCommand() + .AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint"); + IEnumerable breakpoints = await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None).ConfigureAwait(false); return breakpoints.ToList(); } @@ -142,7 +142,7 @@ public async Task> SetBreakpointsAsync(string esc if (psCommand != null) { IEnumerable setBreakpoints = - await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None); + await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None).ConfigureAwait(false); configuredBreakpoints.AddRange( setBreakpoints.Select((breakpoint) => BreakpointDetails.Create(breakpoint)) ); @@ -220,7 +220,7 @@ public async Task> SetCommandBreakpoints(I if (psCommand != null) { IEnumerable setBreakpoints = - await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None); + await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None).ConfigureAwait(false); configuredBreakpoints.AddRange( setBreakpoints.Select(CommandBreakpointDetails.Create)); } @@ -311,7 +311,7 @@ public async Task RemoveBreakpointsAsync(IEnumerable breakpoints) psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Remove-PSBreakpoint"); psCommand.AddParameter("Id", breakpoints.Select(b => b.Id).ToArray()); - await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None); + await _executionService.ExecutePSCommandAsync(psCommand, CancellationToken.None).ConfigureAwait(false); } } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 973996051..f4ea138de 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -11,7 +11,6 @@ using Microsoft.PowerShell.EditorServices.Utility; using System.Threading; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; using Microsoft.PowerShell.EditorServices.Services.PowerShell; @@ -146,7 +145,7 @@ public async Task SetLineBreakpointsAsync( BreakpointDetails[] breakpoints, bool clearExisting = true) { - DscBreakpointCapability dscBreakpoints = await _debugContext.GetDscBreakpointCapabilityAsync(CancellationToken.None); + DscBreakpointCapability dscBreakpoints = await _debugContext.GetDscBreakpointCapabilityAsync(CancellationToken.None).ConfigureAwait(false); string scriptPath = scriptFile.FilePath; // Make sure we're using the remote script path @@ -195,7 +194,8 @@ public async Task SetLineBreakpointsAsync( return await dscBreakpoints.SetLineBreakpointsAsync( _executionService, escapedScriptPath, - breakpoints).ConfigureAwait(false); + breakpoints) + .ConfigureAwait(false); } /// @@ -213,9 +213,8 @@ public async Task SetCommandBreakpointsAsync( if (clearExisting) { // Flatten dictionary values into one list and remove them all. - await _breakpointService.RemoveBreakpointsAsync( - (await _breakpointService.GetBreakpointsAsync().ConfigureAwait(false)) - .Where( i => i is CommandBreakpoint)).ConfigureAwait(false); + IEnumerable existingBreakpoints = await _breakpointService.GetBreakpointsAsync().ConfigureAwait(false); + await _breakpointService.RemoveBreakpointsAsync(existingBreakpoints.OfType()).ConfigureAwait(false); } if (breakpoints.Length > 0) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index 6e208a223..01a27cdd2 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -165,7 +165,7 @@ public async Task Handle(PsesLaunchRequestArguments request, Can if (!string.IsNullOrEmpty(workingDir)) { var setDirCommand = new PSCommand().AddCommand("Set-Location").AddParameter("LiteralPath", workingDir); - await _executionService.ExecutePSCommandAsync(setDirCommand, cancellationToken); + await _executionService.ExecutePSCommandAsync(setDirCommand, cancellationToken).ConfigureAwait(false); } _logger.LogTrace("Working dir " + (string.IsNullOrEmpty(workingDir) ? "not set." : $"set to '{workingDir}'")); @@ -299,7 +299,7 @@ public async Task Handle(PsesAttachRequestArguments request, Can try { - await _executionService.ExecutePSCommandAsync(enterPSHostProcessCommand, cancellationToken); + await _executionService.ExecutePSCommandAsync(enterPSHostProcessCommand, cancellationToken).ConfigureAwait(false); } catch (Exception e) { @@ -423,12 +423,12 @@ private async Task OnExecutionCompletedAsync(Task executeTask) { try { - await _executionService.ExecutePSCommandAsync(new PSCommand().AddCommand("Exit-PSHostProcess"), CancellationToken.None); + await _executionService.ExecutePSCommandAsync(new PSCommand().AddCommand("Exit-PSHostProcess"), CancellationToken.None).ConfigureAwait(false); if (_debugStateService.IsRemoteAttach && _runspaceContext.CurrentRunspace.RunspaceOrigin != RunspaceOrigin.Local) { - await _executionService.ExecutePSCommandAsync(new PSCommand().AddCommand("Exit-PSSession"), CancellationToken.None); + await _executionService.ExecutePSCommandAsync(new PSCommand().AddCommand("Exit-PSSession"), CancellationToken.None).ConfigureAwait(false); } } catch (Exception e) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs index 375bd5c1e..2b3ffc93a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs @@ -117,7 +117,8 @@ private async Task CheckPackageManagement() Title = "Not now" } } - }).ConfigureAwait(false); + }) + .ConfigureAwait(false); // If the user chose "Not now" ignore it for the rest of the session. if (messageAction?.Title == takeActionText) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs index dd6fe91f1..d29a5d11e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs @@ -92,7 +92,8 @@ public async Task GetDscBreakpointCapabilityAsync( logger, this, psesHost, - cancellationToken); + cancellationToken) + .ConfigureAwait(false); } return _dscBreakpointCapability; diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index 4f25c7386..9e34bf0e3 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -137,7 +137,7 @@ await _psesHost.SetInitialWorkingDirectoryAsync( if (!_extensionServiceInitialized) { - await _extensionService.InitializeAsync(); + await _extensionService.InitializeAsync().ConfigureAwait(false); } // Run any events subscribed to configuration updates From aca79c9f2968c97bc9874fa6e366465bdc9b6f0c Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:42:30 -0700 Subject: [PATCH 097/176] Fix debugger evaluation not being written out --- .../Services/DebugAdapter/DebugService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index f4ea138de..2000b2397 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -542,7 +542,7 @@ public async Task EvaluateExpressionAsync( IReadOnlyList results = await _executionService.ExecutePSCommandAsync( command, CancellationToken.None, - new PowerShellExecutionOptions { WriteOutputToHost = true }).ConfigureAwait(false); + new PowerShellExecutionOptions { WriteOutputToHost = writeResultAsOutput, ThrowOnError = !writeResultAsOutput }).ConfigureAwait(false); // Since this method should only be getting invoked in the debugger, // we can assume that Out-String will be getting used to format results From 545095459fa118a4b7f850ec5522719b36d6882f Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:43:09 -0700 Subject: [PATCH 098/176] Add comment about nameless variables --- .../Services/DebugAdapter/DebugService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 2000b2397..b35c3a021 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -696,6 +696,8 @@ private async Task FetchVariableContainerAsync( { foreach (PSObject psVariableObject in results) { + // Under some circumstances, we seem to get variables back with no "Name" field + // We skip over those here if (psVariableObject.Properties["Name"] is null) { continue; From 27c10412b24115ad8c64314b5498ea1893c95dc5 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:44:17 -0700 Subject: [PATCH 099/176] Use nicer pattern matching for condition --- .../Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 6239a44c6..1a42e0090 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -147,8 +147,7 @@ await _executionService private PSCommand BuildPSCommandFromArguments(string command, IReadOnlyList arguments) { - if (arguments is null - || arguments.Count == 0) + if (arguments is null or { Count: 0 }) { return new PSCommand().AddCommand(command); } From 4c39aaca89b054c4c6df065f952a330007848b73 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:46:16 -0700 Subject: [PATCH 100/176] Add TODO about debugger disconnection handling --- .../Services/DebugAdapter/Handlers/DisconnectHandler.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs index 1e5a7d0e0..12e02bf05 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs @@ -47,6 +47,10 @@ public DisconnectHandler( public async Task Handle(DisconnectArguments request, CancellationToken cancellationToken) { + // TODO: We need to sort out the proper order of operations here. + // Currently we just tear things down in some order without really checking what the debugger is doing. + // We should instead ensure that the debugger is in some valid state, lock it and then tear things down + _debugEventHandlerService.UnregisterEventHandlers(); if (!_debugStateService.ExecutionCompleted) From 92afce8d9a32d09c9b20ef5e6c17fe2ae883fd79 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:47:31 -0700 Subject: [PATCH 101/176] Convert to `is null` for consistency --- .../Services/PowerShell/Console/PSReadLineProxy.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs index f35728c70..3a81a4c82 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs @@ -119,7 +119,7 @@ public PSReadLineProxy( .GetType(VirtualTerminalTypeName) ?.GetField(ReadKeyOverrideFieldName, BindingFlags.Static | BindingFlags.NonPublic); - if (_readKeyOverrideField == null) + if (_readKeyOverrideField is null) { throw NewInvalidPSReadLineVersionException( FieldMemberType, @@ -135,7 +135,7 @@ public PSReadLineProxy( _logger); } - if (ReadLine == null) + if (ReadLine is null) { throw NewInvalidPSReadLineVersionException( MethodMemberType, @@ -143,7 +143,7 @@ public PSReadLineProxy( _logger); } - if (SetKeyHandler == null) + if (SetKeyHandler is null) { throw NewInvalidPSReadLineVersionException( MethodMemberType, @@ -151,7 +151,7 @@ public PSReadLineProxy( _logger); } - if (AddToHistory == null) + if (AddToHistory is null) { throw NewInvalidPSReadLineVersionException( MethodMemberType, From c6d9518db4104edbdb56c52ebd12d134b7193f05 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:52:26 -0700 Subject: [PATCH 102/176] Remove unused dependencies --- .../Services/PowerShell/Utility/PowerShellExtensions.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs index d868fdb8f..bf026238a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs @@ -10,8 +10,6 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility using System.Collections.Generic; using System.IO; using System.Management.Automation; - using System.Runtime.CompilerServices; - using UnixConsoleEcho; internal static class PowerShellExtensions { From fcf5d35a4e493f34b813a3f7581f58ba6bbf72c3 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:57:02 -0700 Subject: [PATCH 103/176] Use AllThreadsContinued consistently --- .../Services/DebugAdapter/DebugEventHandlerService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs index 66d4b5e16..c8372af8f 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs @@ -119,6 +119,7 @@ private void ExecutionService_RunspaceChanged(object sender, RunspaceChangedEven new ContinuedEvent { ThreadId = ThreadsHandler.PipelineThread.Id, + AllThreadsContinued = true, }); } return; From 1bedf3a2ebdbc8e801bf3cc6d6ae927fd1ee65c2 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 16:00:12 -0700 Subject: [PATCH 104/176] Add comment about debug context lifetime --- src/PowerShellEditorServices/Server/PsesDebugServer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index f089b5c68..5e5b01bd5 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -143,12 +143,15 @@ public async Task StartAsync() public void Dispose() { - // TODO: If the debugger has stopped, should we clear the breakpoints? + // Note that the lifetime of the DebugContext is longer than the debug server; + // It represents the debugger on the PowerShell process we're in, + // while a new debug server is spun up for every debugging session _debugContext.IsDebugServerActive = false; _debugAdapterServer.Dispose(); _inputStream.Dispose(); _outputStream.Dispose(); _serverStopped.SetResult(true); + // TODO: If the debugger has stopped, should we clear the breakpoints? } public async Task WaitForShutdown() From 24016433273791e048d808ee01d4f8139404ed8d Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 16:01:44 -0700 Subject: [PATCH 105/176] Add context to comment --- .../Services/PowerShell/Utility/CancellationContext.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs index 6789dd4ae..81e6c1f3d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs @@ -66,6 +66,8 @@ public void CancelIdleParentTask() // Note that this check is done *after* the cancellation because we want to cancel // not just the idle task, but its parent as well + // because we want to cancel the ReadLine call that the idle handler is running in + // so we can run something else in the foreground if (!scope.IsIdleScope) { break; From 9db4639cc22d05100417e9cef39c7a05ff81c4c3 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 12:44:12 -0700 Subject: [PATCH 106/176] Fix breakpoint API test --- .../DebugAdapter/BreakpointService.cs | 12 +++---- .../Debugging/BreakpointApiUtils.cs | 11 +++--- .../Handlers/BreakpointHandlers.cs | 34 ++++++++++++++++--- .../Handlers/ConfigurationDoneHandler.cs | 8 +++-- 4 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs index 629085abd..3699ea9cb 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs @@ -18,8 +18,6 @@ namespace Microsoft.PowerShell.EditorServices.Services { internal class BreakpointService { - private static readonly Version s_minimumBreakpointApiVersion = new Version(7, 0, 0, 0); - private readonly ILogger _logger; private readonly PowerShellExecutionService _executionService; private readonly PsesInternalHost _editorServicesHost; @@ -46,7 +44,7 @@ public BreakpointService( public async Task> GetBreakpointsAsync() { - if (_editorServicesHost.CurrentRunspace.PowerShellVersionDetails.Version >= s_minimumBreakpointApiVersion) + if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace)) { return BreakpointApiUtils.GetBreakpoints( _editorServicesHost.Runspace.Debugger, @@ -62,7 +60,7 @@ public async Task> GetBreakpointsAsync() public async Task> SetBreakpointsAsync(string escapedScriptPath, IEnumerable breakpoints) { - if (_editorServicesHost.CurrentRunspace.PowerShellVersionDetails.Version >= s_minimumBreakpointApiVersion) + if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace)) { foreach (BreakpointDetails breakpointDetails in breakpoints) { @@ -153,7 +151,7 @@ public async Task> SetBreakpointsAsync(string esc public async Task> SetCommandBreakpoints(IEnumerable breakpoints) { - if (_editorServicesHost.CurrentRunspace.PowerShellVersionDetails.Version >= s_minimumBreakpointApiVersion) + if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace)) { foreach (CommandBreakpointDetails commandBreakpointDetails in breakpoints) { @@ -235,7 +233,7 @@ public async Task RemoveAllBreakpointsAsync(string scriptPath = null) { try { - if (_editorServicesHost.CurrentRunspace.PowerShellVersionDetails.Version >= s_minimumBreakpointApiVersion) + if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace)) { foreach (Breakpoint breakpoint in BreakpointApiUtils.GetBreakpoints( _editorServicesHost.Runspace.Debugger, @@ -275,7 +273,7 @@ public async Task RemoveAllBreakpointsAsync(string scriptPath = null) public async Task RemoveBreakpointsAsync(IEnumerable breakpoints) { - if (_editorServicesHost.CurrentRunspace.PowerShellVersionDetails.Version >= s_minimumBreakpointApiVersion) + if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace)) { foreach (Breakpoint breakpoint in breakpoints) { diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointApiUtils.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointApiUtils.cs index 2ef66ddd8..82eacfe73 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointApiUtils.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointApiUtils.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Text; using System.Threading; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.DebugAdapter @@ -27,6 +28,8 @@ internal static class BreakpointApiUtils private static readonly Lazy> s_removeBreakpointLazy; + private static readonly Version s_minimumBreakpointApiPowerShellVersion = new(7, 0, 0, 0); + private static int breakpointHitCounter; #endregion @@ -37,7 +40,7 @@ static BreakpointApiUtils() { // If this version of PowerShell does not support the new Breakpoint APIs introduced in PowerShell 7.0.0, // do nothing as this class will not get used. - if (!SupportsBreakpointApis) + if (!VersionUtils.IsPS7OrGreater) { return; } @@ -89,7 +92,7 @@ static BreakpointApiUtils() #endregion - #region Public Static Properties + #region Private Static Properties private static Func SetLineBreakpointDelegate => s_setLineBreakpointLazy.Value; @@ -103,9 +106,7 @@ static BreakpointApiUtils() #region Public Static Properties - // TODO: Try to compute this more dynamically. If we're launching a script in the PSIC, there are APIs are available in PS 5.1 and up. - // For now, only PS7 or greater gets this feature. - public static bool SupportsBreakpointApis => VersionUtils.IsPS7OrGreater; + public static bool SupportsBreakpointApis(IRunspaceInfo targetRunspace) => targetRunspace.PowerShellVersionDetails.Version >= s_minimumBreakpointApiPowerShellVersion; #endregion diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs index f612dd88c..7055a5a5f 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs @@ -10,6 +10,7 @@ using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.DebugAdapter.Protocol.Models; @@ -19,21 +20,30 @@ namespace Microsoft.PowerShell.EditorServices.Handlers { internal class BreakpointHandlers : ISetFunctionBreakpointsHandler, ISetBreakpointsHandler, ISetExceptionBreakpointsHandler { + private static readonly string[] s_supportedDebugFileExtensions = new[] + { + ".ps1", + ".psm1" + }; + private readonly ILogger _logger; private readonly DebugService _debugService; private readonly DebugStateService _debugStateService; private readonly WorkspaceService _workspaceService; + private readonly IRunspaceContext _runspaceContext; public BreakpointHandlers( ILoggerFactory loggerFactory, DebugService debugService, DebugStateService debugStateService, - WorkspaceService workspaceService) + WorkspaceService workspaceService, + IRunspaceContext runspaceContext) { _logger = loggerFactory.CreateLogger(); _debugService = debugService; _debugStateService = debugStateService; _workspaceService = workspaceService; + _runspaceContext = runspaceContext; } public async Task Handle(SetBreakpointsArguments request, CancellationToken cancellationToken) @@ -53,10 +63,7 @@ public async Task Handle(SetBreakpointsArguments request } // Verify source file is a PowerShell script file. - string fileExtension = Path.GetExtension(scriptFile?.FilePath ?? "")?.ToLower(); - bool isUntitledPath = ScriptFile.IsUntitledPath(request.Source.Path); - if ((!isUntitledPath && fileExtension != ".ps1" && fileExtension != ".psm1") || - (!BreakpointApiUtils.SupportsBreakpointApis && isUntitledPath)) + if (!IsFileSupportedForBreakpoints(request.Source.Path, scriptFile)) { _logger.LogWarning( $"Attempted to set breakpoints on a non-PowerShell file: {request.Source.Path}"); @@ -182,5 +189,22 @@ public Task Handle(SetExceptionBreakpointsArgum return Task.FromResult(new SetExceptionBreakpointsResponse()); } + + private bool IsFileSupportedForBreakpoints(string requestedPath, ScriptFile resolvedScriptFile) + { + // PowerShell 7 and above support breakpoints in untitled files + if (ScriptFile.IsUntitledPath(requestedPath)) + { + return BreakpointApiUtils.SupportsBreakpointApis(_runspaceContext.CurrentRunspace); + } + + if (string.IsNullOrEmpty(resolvedScriptFile?.FilePath)) + { + return false; + } + + string fileExtension = Path.GetExtension(resolvedScriptFile.FilePath); + return s_supportedDebugFileExtensions.Contains(fileExtension, StringComparer.OrdinalIgnoreCase); + } } } diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 1a42e0090..5d455d516 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -13,6 +13,7 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.DebugAdapter.Protocol.Events; @@ -41,6 +42,7 @@ internal class ConfigurationDoneHandler : IConfigurationDoneHandler private readonly WorkspaceService _workspaceService; private readonly IPowerShellDebugContext _debugContext; + private readonly IRunspaceContext _runspaceContext; public ConfigurationDoneHandler( ILoggerFactory loggerFactory, @@ -50,7 +52,8 @@ public ConfigurationDoneHandler( DebugEventHandlerService debugEventHandlerService, PowerShellExecutionService executionService, WorkspaceService workspaceService, - IPowerShellDebugContext debugContext) + IPowerShellDebugContext debugContext, + IRunspaceContext runspaceContext) { _logger = loggerFactory.CreateLogger(); _debugAdapterServer = debugAdapterServer; @@ -60,6 +63,7 @@ public ConfigurationDoneHandler( _executionService = executionService; _workspaceService = workspaceService; _debugContext = debugContext; + _runspaceContext = runspaceContext; } public Task Handle(ConfigurationDoneArguments request, CancellationToken cancellationToken) @@ -108,7 +112,7 @@ private async Task LaunchScriptAsync(string scriptToLaunch) { ScriptFile untitledScript = _workspaceService.GetFile(scriptToLaunch); - if (BreakpointApiUtils.SupportsBreakpointApis) + if (BreakpointApiUtils.SupportsBreakpointApis(_runspaceContext.CurrentRunspace)) { // Parse untitled files with their `Untitled:` URI as the file name which will cache the URI & contents within the PowerShell parser. // By doing this, we light up the ability to debug Untitled files with breakpoints. From e97123e48d6bf3379f46c471260620ebd821a7ab Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 14:36:24 -0700 Subject: [PATCH 107/176] Fix ThrowOnError configuration check --- .../Services/PowerShell/Execution/SynchronousPowerShellTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index ed158287f..085d1bd6b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -161,7 +161,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT { Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}"); - if (!PowerShellExecutionOptions.ThrowOnError) + if (PowerShellExecutionOptions.ThrowOnError) { throw; } From 5abc952729401030aaea63713d1091d2be136025 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 15:54:19 -0700 Subject: [PATCH 108/176] Ensure the DSC module import fails with an error --- .../Services/PowerShell/Debugging/DscBreakpointCapability.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index 98c27690a..d2c3d9b8a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -113,7 +113,6 @@ public static async Task GetDscCapabilityAsync( dscModule = pwsh.AddCommand("Import-Module") .AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1") .AddParameter("PassThru") - .AddParameter("ErrorAction", "Ignore") .InvokeAndClear(invocationSettings) .FirstOrDefault(); } From afc6eabe50397615e3b69f7c52790d8f35928a29 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 15:56:03 -0700 Subject: [PATCH 109/176] Remove unneeded async/await --- .../Services/PowerShell/Debugging/DscBreakpointCapability.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index d2c3d9b8a..dc0ec6bf7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -86,7 +86,7 @@ public bool IsDscResourcePath(string scriptPath) StringComparison.CurrentCultureIgnoreCase)); } - public static async Task GetDscCapabilityAsync( + public static Task GetDscCapabilityAsync( ILogger logger, IRunspaceInfo currentRunspace, PsesInternalHost psesHost, @@ -163,12 +163,11 @@ public static async Task GetDscCapabilityAsync( return capability; }; - return await psesHost.ExecuteDelegateAsync( + return psesHost.ExecuteDelegateAsync( nameof(getDscBreakpointCapabilityFunc), ExecutionOptions.Default, getDscBreakpointCapabilityFunc, cancellationToken); - } } } From 00ea2b7b3d0d3d72084d56409d2273e93005e9f5 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 15:59:32 -0700 Subject: [PATCH 110/176] Add comment explaining remote debug setting --- .../Services/PowerShell/Debugging/PowerShellDebugContext.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index 63f2ab5bf..7b9833c59 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -67,6 +67,8 @@ public Task GetDscBreakpointCapabilityAsync(Cancellatio public void EnableDebugMode() { + // This is required by the PowerShell API so that remote debugging works. + // Without it, a runspace may not have these options set and attempting to set breakpoints remotely can fail. _psesHost.Runspace.Debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript); } From f2433b4fca2a77e4baf13141c91948d17d527c97 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 16:10:11 -0700 Subject: [PATCH 111/176] Remove unused enum --- .../Services/PowerShell/Execution/ExecutionOptions.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs index 46099b073..9aa4b9af5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs @@ -2,12 +2,6 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution { - public enum TaskKind - { - Foreground, - Background, - } - public enum ExecutionPriority { Normal, From dbd98f5718fb5e7816f485c0d770d18ca71a475f Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 16:16:46 -0700 Subject: [PATCH 112/176] Add comment to execution options class --- .../Services/PowerShell/Execution/ExecutionOptions.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs index 9aa4b9af5..5e678e83a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs @@ -8,6 +8,10 @@ public enum ExecutionPriority Next, } + // Some of the fields of this class are not orthogonal, + // so it's possible to construct self-contradictory execution options. + // We should see if it's possible to rework this class to make the options less misconfigurable. + // Generally the executor will do the right thing though; some options just priority over others. public record ExecutionOptions { public static ExecutionOptions Default = new() From 216a946d8fd0f1773830d34055552c2a004cc78e Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 16:19:11 -0700 Subject: [PATCH 113/176] Add comment to synchronous task about synchronous results --- .../Services/PowerShell/Execution/SynchronousTask.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs index 845fd59d6..a2b0ae51d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs @@ -41,6 +41,8 @@ protected SynchronousTask( public Task Task => _taskCompletionSource.Task; + // Sometimes we need the result of task run on the same thread, + // which this property allows us to do. public TResult Result { get From 7c34495702c10cf5c70b80ae4c5be4334ba71579 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 16:21:26 -0700 Subject: [PATCH 114/176] Ensure F8 works --- .../Services/PowerShell/Handlers/EvaluateHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs index a065b42ac..447ba42fa 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs @@ -32,10 +32,11 @@ public Task Handle(EvaluateRequestArguments request, Cance // TODO: Understand why we currently handle this asynchronously and why we return a dummy result value // instead of awaiting the execution and returing a real result of some kind + // This API is mostly used for F8 execution, so needs to interrupt the command prompt _executionService.ExecutePSCommandAsync( new PSCommand().AddScript(request.Expression), CancellationToken.None, - new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true, ThrowOnError = false }); + new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true, ThrowOnError = false, InterruptCurrentForeground = true }); return Task.FromResult(new EvaluateResponseBody { From dc7ae36f75af88a4d04a06e000acbc6dd9233d8c Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 16:37:12 -0700 Subject: [PATCH 115/176] Add comment about not exiting the top level --- .../Services/PowerShell/Host/PsesInternalHost.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 230f326f5..f1fd8032a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -194,6 +194,8 @@ await ExecuteDelegateAsync( public void SetExit() { + // Can't exit from the top level of PSES + // since if you do, you lose all LSP services if (_psFrameStack.Count <= 1) { return; From 49bc7fb228ab72174269f89a73dcf4d468c233bf Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 16:56:32 -0700 Subject: [PATCH 116/176] Generalize exit handling --- .../Services/PowerShell/Host/PsesInternalHost.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index f1fd8032a..128d749e7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -468,12 +468,8 @@ private void RunExecutionLoop() { DoOneRepl(cancellationScope.CancellationToken); - if (_shouldExit) - { - break; - } - - while (!cancellationScope.CancellationToken.IsCancellationRequested + while (!_shouldExit + && !cancellationScope.CancellationToken.IsCancellationRequested && _taskQueue.TryTake(out ISynchronousTask task)) { task.ExecuteSynchronously(cancellationScope.CancellationToken); From 168a2395b3583223d962ada24464c8edc76bbea6 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 17:02:01 -0700 Subject: [PATCH 117/176] Add comment about remote prompt --- .../Services/PowerShell/Host/PsesInternalHost.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 128d749e7..dc447f946 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -533,6 +533,8 @@ private string GetPrompt(CancellationToken cancellationToken) if (CurrentRunspace.RunspaceOrigin != RunspaceOrigin.Local) { + // This is a PowerShell-internal method that we reuse to decorate the prompt string + // with the remote details when execution is occurring over a remoting session prompt = Runspace.GetRemotePrompt(prompt); } From 3ec25c68483c4a6d356b1c2b1632fb3a4d0587c6 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 17:04:01 -0700 Subject: [PATCH 118/176] Simplify nested PowerShell creation --- .../Services/PowerShell/Host/PsesInternalHost.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index dc447f946..395b7d7d2 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -570,9 +570,7 @@ private PowerShell CreateNestedPowerShell(RunspaceInfo currentRunspace) { if (currentRunspace.RunspaceOrigin != RunspaceOrigin.Local) { - var remotePwsh = PowerShell.Create(); - remotePwsh.Runspace = currentRunspace.Runspace; - return remotePwsh; + return CreatePowerShellForRunspace(currentRunspace.Runspace); } // PowerShell.CreateNestedPowerShell() sets IsNested but not IsChild @@ -595,9 +593,7 @@ public PowerShell CreateInitialPowerShell( ReadLineProvider readLineProvider) { Runspace runspace = CreateInitialRunspace(hostStartupInfo.LanguageMode); - - var pwsh = PowerShell.Create(); - pwsh.Runspace = runspace; + PowerShell pwsh = CreatePowerShellForRunspace(runspace); var engineIntrinsics = (EngineIntrinsics)runspace.SessionStateProxy.GetVariable("ExecutionContext"); From 0a3ce49267cf680a439b89c565ffed5f65562748 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 17:07:44 -0700 Subject: [PATCH 119/176] Use a more explicit return --- .../Services/PowerShell/Host/PsesInternalHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 395b7d7d2..07a9afbfe 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -666,7 +666,7 @@ private void OnPowerShellIdle() _taskQueue.Prepend(task); _skipNextPrompt = true; _cancellationContext.CancelIdleParentTask(); - break; + return; } task.ExecuteSynchronously(cancellationScope.CancellationToken); From 482ac919ecb656bd2ebb3ca92701b415cd8ec8c7 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 17:09:07 -0700 Subject: [PATCH 120/176] Fix Ctrl-C detection --- .../Services/PowerShell/Host/PsesInternalHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 07a9afbfe..b52594efb 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -695,7 +695,7 @@ private bool LastKeyWasCtrlC() return _lastKey.HasValue && _lastKey.Value.Key == ConsoleKey.C && (_lastKey.Value.Modifiers & ConsoleModifiers.Control) != 0 - && (_lastKey.Value.Modifiers & ConsoleModifiers.Alt) != 0; + && (_lastKey.Value.Modifiers & ConsoleModifiers.Alt) == 0; } private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) From ac2e63b76cefb50fb83661f2f9c72a3090a04cb4 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 10:14:52 -0700 Subject: [PATCH 121/176] Rename debug event handlers --- .../DebugAdapter/DebugEventHandlerService.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs index c8372af8f..dd0cda02f 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs @@ -42,17 +42,17 @@ public DebugEventHandlerService( internal void RegisterEventHandlers() { - _executionService.RunspaceChanged += ExecutionService_RunspaceChanged; - _debugService.BreakpointUpdated += DebugService_BreakpointUpdated; - _debugService.DebuggerStopped += DebugService_DebuggerStopped; + _executionService.RunspaceChanged += OnRunspaceChanged; + _debugService.BreakpointUpdated += OnBreakpointUpdated; + _debugService.DebuggerStopped += OnDebuggerStopped; _debugContext.DebuggerResuming += OnDebuggerResuming; } internal void UnregisterEventHandlers() { - _executionService.RunspaceChanged -= ExecutionService_RunspaceChanged; - _debugService.BreakpointUpdated -= DebugService_BreakpointUpdated; - _debugService.DebuggerStopped -= DebugService_DebuggerStopped; + _executionService.RunspaceChanged -= OnRunspaceChanged; + _debugService.BreakpointUpdated -= OnBreakpointUpdated; + _debugService.DebuggerStopped -= OnDebuggerStopped; _debugContext.DebuggerResuming -= OnDebuggerResuming; } @@ -60,14 +60,14 @@ internal void UnregisterEventHandlers() internal void TriggerDebuggerStopped(DebuggerStoppedEventArgs e) { - DebugService_DebuggerStopped(null, e); + OnDebuggerStopped(null, e); } #endregion #region Event Handlers - private void DebugService_DebuggerStopped(object sender, DebuggerStoppedEventArgs e) + private void OnDebuggerStopped(object sender, DebuggerStoppedEventArgs e) { // Provide the reason for why the debugger has stopped script execution. // See https://github.com/Microsoft/vscode/issues/3648 @@ -93,7 +93,7 @@ e.OriginalEvent.Breakpoints[0] is CommandBreakpoint }); } - private void ExecutionService_RunspaceChanged(object sender, RunspaceChangedEventArgs e) + private void OnRunspaceChanged(object sender, RunspaceChangedEventArgs e) { switch (e.ChangeAction) { @@ -136,7 +136,7 @@ private void OnDebuggerResuming(object sender, DebuggerResumingEventArgs e) }); } - private void DebugService_BreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) + private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) { // Don't send breakpoint update notifications when setting // breakpoints on behalf of the client. From 2a119c67d6d8472c79e5a4fe5b702bb2d37be19b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 10:22:23 -0700 Subject: [PATCH 122/176] Remove unneeded PSRL static ctor call - PSRL is now loaded on the pipeline thread --- .../Server/PsesDebugServer.cs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 5e5b01bd5..ddcc02132 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -14,6 +14,7 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; +using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.DebugAdapter.Server; using OmniSharp.Extensions.LanguageServer.Server; @@ -27,7 +28,7 @@ internal class PsesDebugServer : IDisposable /// /// This is a bool but must be an int, since Interlocked.Exchange can't handle a bool /// - private static int s_hasRunPsrlStaticCtor = 0; + private static readonly IdempotentLatch s_psrlCtorLatch = new(); private static readonly Lazy s_lazyInvokeReadLineConstructorCmdletInfo = new Lazy(() => { @@ -79,22 +80,6 @@ public async Task StartAsync() _debugContext = ServiceProvider.GetService().DebugContext; _debugContext.IsDebugServerActive = true; - /* - // Needed to make sure PSReadLine's static properties are initialized in the pipeline thread. - // This is only needed for Temp sessions who only have a debug server. - if (_usePSReadLine && _useTempSession && Interlocked.Exchange(ref s_hasRunPsrlStaticCtor, 1) == 0) - { - // This must be run synchronously to ensure debugging works - _executionService - .ExecuteDelegateAsync((cancellationToken) => - { - // Is this needed now that we do things the cool way?? - }, "PSRL static constructor execution", CancellationToken.None) - .GetAwaiter() - .GetResult(); - } - */ - options .WithInput(_inputStream) .WithOutput(_outputStream) From 8f21cea6e22aeec7d598f7178537e1a986b9c236 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 11:23:59 -0700 Subject: [PATCH 123/176] Move execution service to an interface --- .../Server/PsesServiceCollectionExtensions.cs | 7 +- .../DebugAdapter/BreakpointService.cs | 4 +- .../DebugAdapter/DebugEventHandlerService.cs | 4 +- .../Services/DebugAdapter/DebugService.cs | 4 +- .../Handlers/ConfigurationDoneHandler.cs | 4 +- .../Handlers/DebugEvaluateHandler.cs | 4 +- .../Handlers/DisconnectHandler.cs | 4 +- .../Handlers/LaunchAndAttachHandler.cs | 4 +- .../Extension/EditorOperationsService.cs | 4 +- .../Services/Extension/ExtensionService.cs | 4 +- .../Debugging/DscBreakpointCapability.cs | 2 +- .../PowerShell/Handlers/EvaluateHandler.cs | 4 +- .../PowerShell/Handlers/ExpandAliasHandler.cs | 4 +- .../PowerShell/Handlers/GetCommandHandler.cs | 4 +- .../PowerShell/Handlers/GetVersionHandler.cs | 4 +- .../PSHostProcessAndRunspaceHandlers.cs | 4 +- .../PowerShell/Handlers/ShowHelpHandler.cs | 4 +- .../PowerShell/Host/PsesInternalHost.cs | 49 +++++++----- .../PowerShell/IPowerShellExecutionService.cs | 62 +++++++++++++++ .../PowerShell/PowerShellExecutionService.cs | 79 ------------------- .../PowerShell/Utility/CommandHelpers.cs | 4 +- .../Services/Symbols/SymbolDetails.cs | 2 +- .../Services/Symbols/SymbolsService.cs | 4 +- .../Services/Symbols/Vistors/AstOperations.cs | 2 +- .../Services/Template/TemplateService.cs | 4 +- .../Handlers/CompletionHandler.cs | 4 +- .../Handlers/SignatureHelpHandler.cs | 4 +- .../Workspace/RemoteFileManagerService.cs | 4 +- 28 files changed, 142 insertions(+), 145 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index 86ab0449c..1d8620442 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -31,7 +31,8 @@ public static IServiceCollection AddPsesLanguageServices( .AddSingleton() .AddSingleton( (provider) => provider.GetService()) - .AddSingleton() + .AddSingleton( + (provider) => provider.GetService()) .AddSingleton() .AddSingleton( (provider) => provider.GetService().DebugContext) @@ -44,7 +45,7 @@ public static IServiceCollection AddPsesLanguageServices( provider.GetService(), provider, provider.GetService(), - provider.GetService()); + provider.GetService()); // This is where we create the $psEditor variable // so that when the console is ready, it will be available @@ -70,7 +71,7 @@ public static IServiceCollection AddPsesDebugServices( .AddSingleton(internalHost) .AddSingleton(internalHost) .AddSingleton(internalHost.DebugContext) - .AddSingleton(languageServiceProvider.GetService()) + .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(languageServiceProvider.GetService()) .AddSingleton(psesDebugServer) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs index 3699ea9cb..f64bed806 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs @@ -19,7 +19,7 @@ namespace Microsoft.PowerShell.EditorServices.Services internal class BreakpointService { private readonly ILogger _logger; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; private readonly PsesInternalHost _editorServicesHost; private readonly DebugStateService _debugStateService; @@ -32,7 +32,7 @@ internal class BreakpointService public BreakpointService( ILoggerFactory factory, - PowerShellExecutionService executionService, + IInternalPowerShellExecutionService executionService, PsesInternalHost editorServicesHost, DebugStateService debugStateService) { diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs index dd0cda02f..d6328801c 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs @@ -17,7 +17,7 @@ namespace Microsoft.PowerShell.EditorServices.Services internal class DebugEventHandlerService { private readonly ILogger _logger; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; private readonly DebugService _debugService; private readonly DebugStateService _debugStateService; private readonly IDebugAdapterServerFacade _debugAdapterServer; @@ -26,7 +26,7 @@ internal class DebugEventHandlerService public DebugEventHandlerService( ILoggerFactory factory, - PowerShellExecutionService executionService, + IInternalPowerShellExecutionService executionService, DebugService debugService, DebugStateService debugStateService, IDebugAdapterServerFacade debugAdapterServer, diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index b35c3a021..f0a3f5496 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -34,7 +34,7 @@ internal class DebugService private readonly BreakpointDetails[] s_emptyBreakpointDetailsArray = Array.Empty(); private readonly ILogger _logger; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; private readonly BreakpointService _breakpointService; private readonly RemoteFileManagerService remoteFileManager; @@ -102,7 +102,7 @@ internal class DebugService //// /// An ILogger implementation used for writing log messages. public DebugService( - PowerShellExecutionService executionService, + IInternalPowerShellExecutionService executionService, IPowerShellDebugContext debugContext, RemoteFileManagerService remoteFileManager, BreakpointService breakpointService, diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 5d455d516..4280282ca 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -38,7 +38,7 @@ internal class ConfigurationDoneHandler : IConfigurationDoneHandler private readonly DebugService _debugService; private readonly DebugStateService _debugStateService; private readonly DebugEventHandlerService _debugEventHandlerService; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; private readonly WorkspaceService _workspaceService; private readonly IPowerShellDebugContext _debugContext; @@ -50,7 +50,7 @@ public ConfigurationDoneHandler( DebugService debugService, DebugStateService debugStateService, DebugEventHandlerService debugEventHandlerService, - PowerShellExecutionService executionService, + IInternalPowerShellExecutionService executionService, WorkspaceService workspaceService, IPowerShellDebugContext debugContext, IRunspaceContext runspaceContext) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs index e7d6076e1..0e1a9ce5f 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs @@ -19,13 +19,13 @@ internal class DebugEvaluateHandler : IEvaluateHandler { private readonly ILogger _logger; private readonly IPowerShellDebugContext _debugContext; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; private readonly DebugService _debugService; public DebugEvaluateHandler( ILoggerFactory factory, IPowerShellDebugContext debugContext, - PowerShellExecutionService executionService, + IInternalPowerShellExecutionService executionService, DebugService debugService) { _logger = factory.CreateLogger(); diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs index 12e02bf05..9fddc371f 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs @@ -20,7 +20,7 @@ namespace Microsoft.PowerShell.EditorServices.Handlers internal class DisconnectHandler : IDisconnectHandler { private readonly ILogger _logger; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; private readonly DebugService _debugService; private readonly DebugStateService _debugStateService; private readonly DebugEventHandlerService _debugEventHandlerService; @@ -31,7 +31,7 @@ public DisconnectHandler( ILoggerFactory factory, PsesDebugServer psesDebugServer, IRunspaceContext runspaceContext, - PowerShellExecutionService executionService, + IInternalPowerShellExecutionService executionService, DebugService debugService, DebugStateService debugStateService, DebugEventHandlerService debugEventHandlerService) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index 01a27cdd2..d59ad94e9 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -92,7 +92,7 @@ internal class LaunchAndAttachHandler : ILaunchHandler /// Gets the PowerShellContext in which extension code will be executed. /// - internal PowerShellExecutionService ExecutionService { get; private set; } + internal IInternalPowerShellExecutionService ExecutionService { get; private set; } #endregion @@ -68,7 +68,7 @@ internal ExtensionService( ILanguageServerFacade languageServer, IServiceProvider serviceProvider, IEditorOperations editorOperations, - PowerShellExecutionService executionService) + IInternalPowerShellExecutionService executionService) { ExecutionService = executionService; _languageServer = languageServer; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index dc0ec6bf7..922ad1fa4 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -29,7 +29,7 @@ internal class DscBreakpointCapability new Dictionary(); public async Task SetLineBreakpointsAsync( - PowerShellExecutionService executionService, + IInternalPowerShellExecutionService executionService, string scriptPath, BreakpointDetails[] breakpoints) { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs index 447ba42fa..5ae13498c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs @@ -17,11 +17,11 @@ namespace Microsoft.PowerShell.EditorServices.Handlers internal class EvaluateHandler : IEvaluateHandler { private readonly ILogger _logger; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; public EvaluateHandler( ILoggerFactory factory, - PowerShellExecutionService executionService) + IInternalPowerShellExecutionService executionService) { _logger = factory.CreateLogger(); _executionService = executionService; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs index 0db866d85..90d144bf5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs @@ -30,9 +30,9 @@ internal class ExpandAliasResult internal class ExpandAliasHandler : IExpandAliasHandler { private readonly ILogger _logger; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; - public ExpandAliasHandler(ILoggerFactory factory, PowerShellExecutionService executionService) + public ExpandAliasHandler(ILoggerFactory factory, IInternalPowerShellExecutionService executionService) { _logger = factory.CreateLogger(); _executionService = executionService; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs index 42d930769..c15a37a7c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs @@ -35,9 +35,9 @@ internal class PSCommandMessage internal class GetCommandHandler : IGetCommandHandler { private readonly ILogger _logger; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; - public GetCommandHandler(ILoggerFactory factory, PowerShellExecutionService executionService) + public GetCommandHandler(ILoggerFactory factory, IInternalPowerShellExecutionService executionService) { _logger = factory.CreateLogger(); _executionService = executionService; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs index 2b3ffc93a..34d86b7f5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs @@ -26,14 +26,14 @@ internal class GetVersionHandler : IGetVersionHandler private readonly ILogger _logger; private IRunspaceContext _runspaceContext; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; private readonly ILanguageServerFacade _languageServer; private readonly ConfigurationService _configurationService; public GetVersionHandler( ILoggerFactory factory, IRunspaceContext runspaceContext, - PowerShellExecutionService executionService, + IInternalPowerShellExecutionService executionService, ILanguageServerFacade languageServer, ConfigurationService configurationService) { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs index e99839460..a36a24b8c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs @@ -18,9 +18,9 @@ namespace Microsoft.PowerShell.EditorServices.Handlers internal class PSHostProcessAndRunspaceHandlers : IGetPSHostProcessesHandler, IGetRunspaceHandler { private readonly ILogger _logger; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; - public PSHostProcessAndRunspaceHandlers(ILoggerFactory factory, PowerShellExecutionService executionService) + public PSHostProcessAndRunspaceHandlers(ILoggerFactory factory, IInternalPowerShellExecutionService executionService) { _logger = factory.CreateLogger(); _executionService = executionService; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs index 7be270f11..3e0f0a903 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs @@ -24,9 +24,9 @@ internal class ShowHelpParams : IRequest internal class ShowHelpHandler : IShowHelpHandler { private readonly ILogger _logger; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; - public ShowHelpHandler(ILoggerFactory factory, PowerShellExecutionService executionService) + public ShowHelpHandler(ILoggerFactory factory, IInternalPowerShellExecutionService executionService) { _logger = factory.CreateLogger(); _executionService = executionService; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index b52594efb..ee27fb61f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -24,7 +24,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host using System.Threading; using System.Threading.Tasks; - internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRunspaceContext + internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRunspaceContext, IInternalPowerShellExecutionService { private const string DefaultPrompt = "PSIC> "; @@ -45,7 +45,7 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns private readonly Stack _psFrameStack; - private readonly Stack<(Runspace, RunspaceInfo)> _runspaceStack; + private readonly Stack _runspaceStack; private readonly CancellationContext _cancellationContext; @@ -76,7 +76,7 @@ public PsesInternalHost( _readLineProvider = new ReadLineProvider(loggerFactory); _taskQueue = new BlockingConcurrentDeque(); _psFrameStack = new Stack(); - _runspaceStack = new Stack<(Runspace, RunspaceInfo)>(); + _runspaceStack = new Stack(); _cancellationContext = new CancellationContext(); _pipelineThread = new Thread(Run) @@ -108,7 +108,7 @@ public PsesInternalHost( public bool IsRunspacePushed { get; private set; } - public Runspace Runspace => _runspaceStack.Peek().Item1; + public Runspace Runspace => _runspaceStack.Peek().Runspace; public RunspaceInfo CurrentRunspace => CurrentFrame.RunspaceInfo; @@ -126,6 +126,8 @@ public PsesInternalHost( private PowerShellContextFrame CurrentFrame => _psFrameStack.Peek(); + public event Action RunspaceChanged; + public override void EnterNestedPrompt() { PushPowerShellAndRunLoop(CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested); @@ -344,39 +346,43 @@ private void Run() SMA.PowerShell pwsh = CreateInitialPowerShell(_hostInfo, _readLineProvider); RunspaceInfo localRunspaceInfo = RunspaceInfo.CreateFromLocalPowerShell(_logger, pwsh); _localComputerName = localRunspaceInfo.SessionDetails.ComputerName; - _runspaceStack.Push((pwsh.Runspace, localRunspaceInfo)); + _runspaceStack.Push(new RunspaceFrame(pwsh.Runspace, localRunspaceInfo)); PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal, localRunspaceInfo); } - private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType, RunspaceInfo runspaceInfo = null) + private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType, RunspaceInfo newRunspaceInfo = null) { // TODO: Improve runspace origin detection here - if (runspaceInfo is null) + if (newRunspaceInfo is null) { - runspaceInfo = GetRunspaceInfoForPowerShell(pwsh, out bool isNewRunspace); + newRunspaceInfo = GetRunspaceInfoForPowerShell(pwsh, out bool isNewRunspace, out RunspaceFrame oldRunspaceFrame); if (isNewRunspace) { - _runspaceStack.Push((pwsh.Runspace, runspaceInfo)); + Runspace newRunspace = pwsh.Runspace; + _runspaceStack.Push(new RunspaceFrame(newRunspace, newRunspaceInfo)); + RunspaceChanged.Invoke(this, new RunspaceChangedEventArgs(RunspaceChangeAction.Enter, oldRunspaceFrame.RunspaceInfo, newRunspaceInfo)); } } - PushPowerShellAndRunLoop(new PowerShellContextFrame(pwsh, runspaceInfo, frameType)); + PushPowerShellAndRunLoop(new PowerShellContextFrame(pwsh, newRunspaceInfo, frameType)); } - private RunspaceInfo GetRunspaceInfoForPowerShell(SMA.PowerShell pwsh, out bool isNewRunspace) + private RunspaceInfo GetRunspaceInfoForPowerShell(SMA.PowerShell pwsh, out bool isNewRunspace, out RunspaceFrame oldRunspaceFrame) { + oldRunspaceFrame = null; + if (_runspaceStack.Count > 0) { // This is more than just an optimization. // When debugging, we cannot execute PowerShell directly to get this information; // trying to do so will block on the command that called us, deadlocking execution. // Instead, since we are reusing the runspace, we reuse that runspace's info as well. - (Runspace currentRunspace, RunspaceInfo currentRunspaceInfo) = _runspaceStack.Peek(); - if (currentRunspace == pwsh.Runspace) + oldRunspaceFrame = _runspaceStack.Peek(); + if (oldRunspaceFrame.Runspace == pwsh.Runspace) { isNewRunspace = false; - return currentRunspaceInfo; + return oldRunspaceFrame.RunspaceInfo; } } @@ -423,11 +429,14 @@ private void PopPowerShell() try { // If we're changing runspace, make sure we move the handlers over - if (_runspaceStack.Peek().Item1 != CurrentPowerShell.Runspace) + RunspaceFrame previousRunspaceFrame = _runspaceStack.Peek(); + if (previousRunspaceFrame.Runspace != CurrentPowerShell.Runspace) { - (Runspace parentRunspace, _) = _runspaceStack.Pop(); - RemoveRunspaceEventHandlers(frame.PowerShell.Runspace); - AddRunspaceEventHandlers(parentRunspace); + _runspaceStack.Pop(); + RunspaceFrame currentRunspaceFrame = _runspaceStack.Peek(); + RemoveRunspaceEventHandlers(previousRunspaceFrame.Runspace); + AddRunspaceEventHandlers(currentRunspaceFrame.Runspace); + RunspaceChanged?.Invoke(this, new RunspaceChangedEventArgs(RunspaceChangeAction.Exit, previousRunspaceFrame.RunspaceInfo, currentRunspaceFrame.RunspaceInfo)); } } finally @@ -767,5 +776,9 @@ private void PopOrReinitializeRunspace() CancellationToken.None); } */ + + private record RunspaceFrame( + Runspace Runspace, + RunspaceInfo RunspaceInfo); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs new file mode 100644 index 000000000..81011b124 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs @@ -0,0 +1,62 @@ +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Threading; +using System.Threading.Tasks; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell +{ + public interface IPowerShellExecutionService + { + Task ExecuteDelegateAsync( + string representation, + ExecutionOptions executionOptions, + Func func, + CancellationToken cancellationToken); + + Task ExecuteDelegateAsync( + string representation, + ExecutionOptions executionOptions, + Action action, + CancellationToken cancellationToken); + + Task ExecuteDelegateAsync( + string representation, + ExecutionOptions executionOptions, + Func func, + CancellationToken cancellationToken); + + Task ExecuteDelegateAsync( + string representation, + ExecutionOptions executionOptions, + Action action, + CancellationToken cancellationToken); + + Task> ExecutePSCommandAsync( + PSCommand psCommand, + CancellationToken cancellationToken, + PowerShellExecutionOptions executionOptions = null); + + Task ExecutePSCommandAsync( + PSCommand psCommand, + CancellationToken cancellationToken, + PowerShellExecutionOptions executionOptions = null); + + void CancelCurrentTask(); + } + + internal interface IInternalPowerShellExecutionService : IPowerShellExecutionService + { + event Action RunspaceChanged; + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs deleted file mode 100644 index 6d4180895..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Hosting; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; -using OmniSharp.Extensions.LanguageServer.Protocol.Server; -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Threading; -using System.Threading.Tasks; -using SMA = System.Management.Automation; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell -{ - internal class PowerShellExecutionService - { - private readonly ILogger _logger; - - private readonly PsesInternalHost _psesHost; - - public PowerShellExecutionService( - ILoggerFactory loggerFactory, - PsesInternalHost psesHost) - { - _logger = loggerFactory.CreateLogger(); - _psesHost = psesHost; - } - - public Action RunspaceChanged; - - public Task ExecuteDelegateAsync( - string representation, - ExecutionOptions executionOptions, - Func func, - CancellationToken cancellationToken) - => _psesHost.ExecuteDelegateAsync(representation, executionOptions, func, cancellationToken); - - public Task ExecuteDelegateAsync( - string representation, - ExecutionOptions executionOptions, - Action action, - CancellationToken cancellationToken) - => _psesHost.ExecuteDelegateAsync(representation, executionOptions, action, cancellationToken); - - public Task ExecuteDelegateAsync( - string representation, - ExecutionOptions executionOptions, - Func func, - CancellationToken cancellationToken) - => _psesHost.ExecuteDelegateAsync(representation, executionOptions, func, cancellationToken); - - public Task ExecuteDelegateAsync( - string representation, - ExecutionOptions executionOptions, - Action action, - CancellationToken cancellationToken) - => _psesHost.ExecuteDelegateAsync(representation, executionOptions, action, cancellationToken); - - public Task> ExecutePSCommandAsync( - PSCommand psCommand, - CancellationToken cancellationToken, - PowerShellExecutionOptions executionOptions = null) - => _psesHost.ExecutePSCommandAsync(psCommand, cancellationToken, executionOptions); - - public Task ExecutePSCommandAsync( - PSCommand psCommand, - CancellationToken cancellationToken, - PowerShellExecutionOptions executionOptions = null) => ExecutePSCommandAsync(psCommand, cancellationToken, executionOptions); - - public void CancelCurrentTask() - { - _psesHost.CancelCurrentTask(); - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs index 2c2e1207e..a7264edac 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs @@ -65,7 +65,7 @@ internal static class CommandHelpers public static async Task GetCommandInfoAsync( string commandName, IRunspaceInfo currentRunspace, - PowerShellExecutionService executionService) + IInternalPowerShellExecutionService executionService) { // This mechanism only works in-process if (currentRunspace.RunspaceOrigin != RunspaceOrigin.Local) @@ -117,7 +117,7 @@ public static async Task GetCommandInfoAsync( /// public static async Task GetCommandSynopsisAsync( CommandInfo commandInfo, - PowerShellExecutionService executionService) + IInternalPowerShellExecutionService executionService) { Validate.IsNotNull(nameof(commandInfo), commandInfo); Validate.IsNotNull(nameof(executionService), executionService); diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs index c3ad77e22..47b5e40a4 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs @@ -41,7 +41,7 @@ internal class SymbolDetails internal static async Task CreateAsync( SymbolReference symbolReference, IRunspaceInfo currentRunspace, - PowerShellExecutionService executionService) + IInternalPowerShellExecutionService executionService) { SymbolDetails symbolDetails = new SymbolDetails { diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 0d3922294..84220d7c8 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -35,7 +35,7 @@ internal class SymbolsService private readonly ILogger _logger; private readonly IRunspaceContext _runspaceContext; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; private readonly WorkspaceService _workspaceService; private readonly ConcurrentDictionary _codeLensProviders; @@ -53,7 +53,7 @@ internal class SymbolsService public SymbolsService( ILoggerFactory factory, IRunspaceContext runspaceContext, - PowerShellExecutionService executionService, + IInternalPowerShellExecutionService executionService, WorkspaceService workspaceService, ConfigurationService configurationService) { diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index 71d2d588b..5b966d30d 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -72,7 +72,7 @@ public static async Task GetCompletionsAsync( Ast scriptAst, Token[] currentTokens, int fileOffset, - PowerShellExecutionService executionService, + IInternalPowerShellExecutionService executionService, ILogger logger, CancellationToken cancellationToken) { diff --git a/src/PowerShellEditorServices/Services/Template/TemplateService.cs b/src/PowerShellEditorServices/Services/Template/TemplateService.cs index d769cac18..a3c416190 100644 --- a/src/PowerShellEditorServices/Services/Template/TemplateService.cs +++ b/src/PowerShellEditorServices/Services/Template/TemplateService.cs @@ -25,7 +25,7 @@ internal class TemplateService private readonly ILogger _logger; private bool isPlasterLoaded; private bool? isPlasterInstalled; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; #endregion @@ -36,7 +36,7 @@ internal class TemplateService /// /// The PowerShellContext to use for this service. /// An ILoggerFactory implementation used for writing log messages. - public TemplateService(PowerShellExecutionService executionService, ILoggerFactory factory) + public TemplateService(IInternalPowerShellExecutionService executionService, ILoggerFactory factory) { _logger = factory.CreateLogger(); _executionService = executionService; diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs index ca8f56eab..2581e2a6e 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs @@ -27,7 +27,7 @@ internal class PsesCompletionHandler : ICompletionHandler, ICompletionResolveHan const int DefaultWaitTimeoutMilliseconds = 5000; private readonly ILogger _logger; private readonly IRunspaceContext _runspaceContext; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; private readonly WorkspaceService _workspaceService; private CompletionResults _mostRecentCompletions; private int _mostRecentRequestLine; @@ -40,7 +40,7 @@ internal class PsesCompletionHandler : ICompletionHandler, ICompletionResolveHan public PsesCompletionHandler( ILoggerFactory factory, IRunspaceContext runspaceContext, - PowerShellExecutionService executionService, + IInternalPowerShellExecutionService executionService, WorkspaceService workspaceService) { _logger = factory.CreateLogger(); diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs index 2fd71f452..689b762ca 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs @@ -21,13 +21,13 @@ internal class PsesSignatureHelpHandler : SignatureHelpHandlerBase private readonly ILogger _logger; private readonly SymbolsService _symbolsService; private readonly WorkspaceService _workspaceService; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; public PsesSignatureHelpHandler( ILoggerFactory factory, SymbolsService symbolsService, WorkspaceService workspaceService, - PowerShellExecutionService executionService) + IInternalPowerShellExecutionService executionService) { _logger = factory.CreateLogger(); _symbolsService = symbolsService; diff --git a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs index 0ffe84698..e8ddaa304 100644 --- a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs @@ -33,7 +33,7 @@ internal class RemoteFileManagerService private string remoteFilesPath; private string processTempPath; private readonly IRunspaceContext _runspaceContext; - private readonly PowerShellExecutionService _executionService; + private readonly IInternalPowerShellExecutionService _executionService; private IEditorOperations editorOperations; private Dictionary filesPerComputer = @@ -252,7 +252,7 @@ function New-EditorFile { public RemoteFileManagerService( ILoggerFactory factory, IRunspaceContext runspaceContext, - PowerShellExecutionService executionService, + IInternalPowerShellExecutionService executionService, EditorOperationsService editorOperations) { this.logger = factory.CreateLogger(); From bdf13790aba6d5f38a6adfa2431c817875b7a54e Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 11:29:26 -0700 Subject: [PATCH 124/176] Enhance remoting comment --- .../Services/PowerShell/Host/PsesInternalHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index ee27fb61f..fceea1770 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -543,7 +543,8 @@ private string GetPrompt(CancellationToken cancellationToken) if (CurrentRunspace.RunspaceOrigin != RunspaceOrigin.Local) { // This is a PowerShell-internal method that we reuse to decorate the prompt string - // with the remote details when execution is occurring over a remoting session + // with the remote details when remoting, + // so the prompt changes to indicate when you're in a remote session prompt = Runspace.GetRemotePrompt(prompt); } From 5f5a86ef122d38f3034d4f113257c3ab2741a235 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 11:30:38 -0700 Subject: [PATCH 125/176] Turn on psedit registration in remote sessions --- .../Services/Workspace/RemoteFileManagerService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs index e8ddaa304..41a5fba95 100644 --- a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs @@ -274,7 +274,7 @@ public RemoteFileManagerService( // TODO: Do this somewhere other than the constructor and make it async // Register the psedit function in the current runspace - //this.RegisterPSEditFunction(this.powerShellContext.CurrentRunspace); + this.RegisterPSEditFunction(_runspaceContext.CurrentRunspace); } #endregion From 6e03fcc92071d0cd4e5f478d2f551ebd00394025 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 11:33:00 -0700 Subject: [PATCH 126/176] Ensure failed remote file saves are logged --- .../Workspace/RemoteFileManagerService.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs index 41a5fba95..5249b2504 100644 --- a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs @@ -392,16 +392,16 @@ public async Task SaveRemoteFileAsync(string localFilePath) .AddParameter("RemoteFilePath", remoteFilePath) .AddParameter("Content", localFileContents); - await _executionService.ExecutePSCommandAsync( - saveCommand, - CancellationToken.None).ConfigureAwait(false); - - /* - if (errorMessages.Length > 0) + try + { + await _executionService.ExecutePSCommandAsync( + saveCommand, + CancellationToken.None).ConfigureAwait(false); + } + catch (Exception e) { - this.logger.LogError($"Remote file save failed due to an error:\r\n\r\n{errorMessages}"); + this.logger.LogError(e, "Remote file save failed"); } - */ } /// From 27b694e23b031a17534788d36e4a7d6e80f6cab8 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 11:34:32 -0700 Subject: [PATCH 127/176] Add comment about IsExternalInit --- src/PowerShellEditorServices/Utility/IsExternalInit.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/PowerShellEditorServices/Utility/IsExternalInit.cs b/src/PowerShellEditorServices/Utility/IsExternalInit.cs index 66a88a4ce..ff74defa3 100644 --- a/src/PowerShellEditorServices/Utility/IsExternalInit.cs +++ b/src/PowerShellEditorServices/Utility/IsExternalInit.cs @@ -2,6 +2,11 @@ namespace System.Runtime.CompilerServices { + /// + /// This type must be defined to use init property accessors, + /// but is not in .NET Standard 2.0. + /// So instead we define the type in our own code. + /// [EditorBrowsable(EditorBrowsableState.Never)] internal class IsExternalInit{} } From 279a8108ebd2bd30eeca5d27205d1db1e57bb601 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 11:49:54 -0700 Subject: [PATCH 128/176] Reinstate runspace cleanup logic --- .../PowerShell/Host/PsesInternalHost.cs | 59 ++++++++++++------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index fceea1770..22fb1d2b4 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -63,6 +63,8 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns private bool _skipNextPrompt = false; + private bool _resettingRunspace = false; + public PsesInternalHost( ILoggerFactory loggerFactory, ILanguageServerFacade languageServer, @@ -343,13 +345,19 @@ public Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cance private void Run() { - SMA.PowerShell pwsh = CreateInitialPowerShell(_hostInfo, _readLineProvider); - RunspaceInfo localRunspaceInfo = RunspaceInfo.CreateFromLocalPowerShell(_logger, pwsh); + (PowerShell pwsh, RunspaceInfo localRunspaceInfo) = CreateInitialPowerShellSession(); _localComputerName = localRunspaceInfo.SessionDetails.ComputerName; _runspaceStack.Push(new RunspaceFrame(pwsh.Runspace, localRunspaceInfo)); PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal, localRunspaceInfo); } + private (PowerShell, RunspaceInfo) CreateInitialPowerShellSession() + { + PowerShell pwsh = CreateInitialPowerShell(_hostInfo, _readLineProvider); + RunspaceInfo localRunspaceInfo = RunspaceInfo.CreateFromLocalPowerShell(_logger, pwsh); + return (pwsh, localRunspaceInfo); + } + private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType, RunspaceInfo newRunspaceInfo = null) { // TODO: Improve runspace origin detection here @@ -392,14 +400,7 @@ private RunspaceInfo GetRunspaceInfoForPowerShell(SMA.PowerShell pwsh, out bool private void PushPowerShellAndRunLoop(PowerShellContextFrame frame) { - if (_psFrameStack.Count > 0) - { - RemoveRunspaceEventHandlers(CurrentFrame.PowerShell.Runspace); - } - - AddRunspaceEventHandlers(frame.PowerShell.Runspace); - - _psFrameStack.Push(frame); + PushPowerShell(frame); try { @@ -422,7 +423,19 @@ private void PushPowerShellAndRunLoop(PowerShellContextFrame frame) } } - private void PopPowerShell() + private void PushPowerShell(PowerShellContextFrame frame) + { + if (_psFrameStack.Count > 0) + { + RemoveRunspaceEventHandlers(CurrentFrame.PowerShell.Runspace); + } + + AddRunspaceEventHandlers(frame.PowerShell.Runspace); + + _psFrameStack.Push(frame); + } + + private void PopPowerShell(RunspaceChangeAction runspaceChangeAction = RunspaceChangeAction.Exit) { _shouldExit = false; PowerShellContextFrame frame = _psFrameStack.Pop(); @@ -436,7 +449,7 @@ private void PopPowerShell() RunspaceFrame currentRunspaceFrame = _runspaceStack.Peek(); RemoveRunspaceEventHandlers(previousRunspaceFrame.Runspace); AddRunspaceEventHandlers(currentRunspaceFrame.Runspace); - RunspaceChanged?.Invoke(this, new RunspaceChangedEventArgs(RunspaceChangeAction.Exit, previousRunspaceFrame.RunspaceInfo, currentRunspaceFrame.RunspaceInfo)); + RunspaceChanged?.Invoke(this, new RunspaceChangedEventArgs(runspaceChangeAction, previousRunspaceFrame.RunspaceInfo, currentRunspaceFrame.RunspaceInfo)); } } finally @@ -730,37 +743,40 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs break private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) { - if (!_shouldExit && !runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) + if (!_shouldExit && !_resettingRunspace && !runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) { - //PopOrReinitializeRunspaceAsync(); + _resettingRunspace = true; + PopOrReinitializeRunspaceAsync().HandleErrorsAsync(_logger); } } - /* - private void PopOrReinitializeRunspace() + private Task PopOrReinitializeRunspaceAsync() { - SetExit(); + _cancellationContext.CancelCurrentTaskStack(); RunspaceStateInfo oldRunspaceState = CurrentPowerShell.Runspace.RunspaceStateInfo; // Rather than try to lock the PowerShell executor while we alter its state, // we simply run this on its thread, guaranteeing that no other action can occur - _executor.InvokeDelegate( - nameof(PopOrReinitializeRunspace), + return ExecuteDelegateAsync( + nameof(PopOrReinitializeRunspaceAsync), new ExecutionOptions { InterruptCurrentForeground = true }, (cancellationToken) => { while (_psFrameStack.Count > 0 && !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable()) { - PopPowerShell(); + PopPowerShell(RunspaceChangeAction.Shutdown); } + _resettingRunspace = false; + if (_psFrameStack.Count == 0) { // If our main runspace was corrupted, // we must re-initialize our state. // TODO: Use runspace.ResetRunspaceState() here instead - PushInitialPowerShell(); + (PowerShell pwsh, RunspaceInfo runspaceInfo) = CreateInitialPowerShellSession(); + PushPowerShell(new PowerShellContextFrame(pwsh, runspaceInfo, PowerShellFrameType.Normal)); _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." + " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs."); @@ -776,7 +792,6 @@ private void PopOrReinitializeRunspace() }, CancellationToken.None); } - */ private record RunspaceFrame( Runspace Runspace, From 8364c5db7c5c72dbbebc3db7a34098b7e6f81d64 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 14:47:48 -0700 Subject: [PATCH 129/176] Fix remote psedit registration --- .../Workspace/RemoteFileManagerService.cs | 240 ++++++++++-------- 1 file changed, 137 insertions(+), 103 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs index 5249b2504..6db4fc4d9 100644 --- a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs @@ -8,6 +8,8 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Generic; using System.Diagnostics; @@ -18,6 +20,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using SMA = System.Management.Automation; namespace Microsoft.PowerShell.EditorServices.Services { @@ -258,7 +261,7 @@ public RemoteFileManagerService( this.logger = factory.CreateLogger(); _runspaceContext = runspaceContext; _executionService = executionService; - _executionService.RunspaceChanged += HandleRunspaceChangedAsync; + _executionService.RunspaceChanged += HandleRunspaceChanged; this.editorOperations = editorOperations; @@ -274,7 +277,7 @@ public RemoteFileManagerService( // TODO: Do this somewhere other than the constructor and make it async // Register the psedit function in the current runspace - this.RegisterPSEditFunction(_runspaceContext.CurrentRunspace); + this.RegisterPSEditFunctionAsync().HandleErrorsAsync(logger); } #endregion @@ -505,10 +508,9 @@ private static void StoreRemoteFile( private RemotePathMappings GetPathMappings(IRunspaceInfo runspaceInfo) { - RemotePathMappings remotePathMappings = null; string computerName = runspaceInfo.SessionDetails.ComputerName; - if (!this.filesPerComputer.TryGetValue(computerName, out remotePathMappings)) + if (!this.filesPerComputer.TryGetValue(computerName, out RemotePathMappings remotePathMappings)) { remotePathMappings = new RemotePathMappings(runspaceInfo, this); this.filesPerComputer.Add(computerName, remotePathMappings); @@ -517,28 +519,33 @@ private RemotePathMappings GetPathMappings(IRunspaceInfo runspaceInfo) return remotePathMappings; } - private async void HandleRunspaceChangedAsync(object sender, RunspaceChangedEventArgs e) + private void HandleRunspaceChanged(object sender, RunspaceChangedEventArgs e) { if (e.ChangeAction == RunspaceChangeAction.Enter) { - this.RegisterPSEditFunction(e.NewRunspace); + this.RegisterPSEditFunction(e.NewRunspace.Runspace); return; } // Close any remote files that were opened - if (e.PreviousRunspace.IsOnRemoteMachine && - (e.ChangeAction == RunspaceChangeAction.Shutdown || - !string.Equals( - e.NewRunspace.SessionDetails.ComputerName, - e.PreviousRunspace.SessionDetails.ComputerName, - StringComparison.CurrentCultureIgnoreCase))) + if (ShouldTearDownRemoteFiles(e)) { RemotePathMappings remotePathMappings; if (this.filesPerComputer.TryGetValue(e.PreviousRunspace.SessionDetails.ComputerName, out remotePathMappings)) { + var fileCloseTasks = new List(); foreach (string remotePath in remotePathMappings.OpenedPaths) { - await (this.editorOperations?.CloseFileAsync(remotePath)).ConfigureAwait(false); + fileCloseTasks.Add(this.editorOperations?.CloseFileAsync(remotePath)); + } + + try + { + Task.WaitAll(fileCloseTasks.ToArray()); + } + catch (Exception ex) + { + this.logger.LogError(ex, "Unable to close all files in closed runspace"); } } } @@ -549,139 +556,166 @@ private async void HandleRunspaceChangedAsync(object sender, RunspaceChangedEven } } + private static bool ShouldTearDownRemoteFiles(RunspaceChangedEventArgs runspaceChangedEvent) + { + if (!runspaceChangedEvent.PreviousRunspace.IsOnRemoteMachine) + { + return false; + } + + if (runspaceChangedEvent.ChangeAction == RunspaceChangeAction.Shutdown) + { + return true; + } + + // Check to see if the runspace we're changing to is on a different machine to the one we left + return !string.Equals( + runspaceChangedEvent.NewRunspace.SessionDetails.ComputerName, + runspaceChangedEvent.PreviousRunspace.SessionDetails.ComputerName, + StringComparison.CurrentCultureIgnoreCase); + } + private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) { - if (string.Equals(RemoteSessionOpenFile, args.SourceIdentifier, StringComparison.CurrentCultureIgnoreCase)) + if (!string.Equals(RemoteSessionOpenFile, args.SourceIdentifier, StringComparison.CurrentCultureIgnoreCase)) { - try + return; + } + + try + { + if (args.SourceArgs.Length >= 1) { - if (args.SourceArgs.Length >= 1) + string localFilePath = string.Empty; + string remoteFilePath = args.SourceArgs[0] as string; + + // Is this a local process runspace? Treat as a local file + if (!_runspaceContext.CurrentRunspace.IsOnRemoteMachine) { - string localFilePath = string.Empty; - string remoteFilePath = args.SourceArgs[0] as string; + localFilePath = remoteFilePath; + } + else + { + byte[] fileContent = null; - // Is this a local process runspace? Treat as a local file - if (!_runspaceContext.CurrentRunspace.IsOnRemoteMachine) - { - localFilePath = remoteFilePath; - } - else + if (args.SourceArgs.Length >= 2) { - byte[] fileContent = null; - - if (args.SourceArgs.Length >= 2) + // Try to cast as a PSObject to get the BaseObject, if not, then try to case as a byte[] + PSObject sourceObj = args.SourceArgs[1] as PSObject; + if (sourceObj != null) { - // Try to cast as a PSObject to get the BaseObject, if not, then try to case as a byte[] - PSObject sourceObj = args.SourceArgs[1] as PSObject; - if (sourceObj != null) - { - fileContent = sourceObj.BaseObject as byte[]; - } - else - { - fileContent = args.SourceArgs[1] as byte[]; - } - } - - // If fileContent is still null after trying to - // unpack the contents, just return an empty byte - // array. - fileContent = fileContent ?? Array.Empty(); - - if (remoteFilePath != null) - { - localFilePath = - this.StoreRemoteFile( - remoteFilePath, - fileContent, - _runspaceContext.CurrentRunspace); + fileContent = sourceObj.BaseObject as byte[]; } else { - await (this.editorOperations?.NewFileAsync()).ConfigureAwait(false); - EditorContext context = await (editorOperations?.GetEditorContextAsync()).ConfigureAwait(false); - context?.CurrentFile.InsertText(Encoding.UTF8.GetString(fileContent, 0, fileContent.Length)); + fileContent = args.SourceArgs[1] as byte[]; } } - bool preview = true; - if (args.SourceArgs.Length >= 3) + // If fileContent is still null after trying to + // unpack the contents, just return an empty byte + // array. + fileContent = fileContent ?? Array.Empty(); + + if (remoteFilePath != null) { - bool? previewCheck = args.SourceArgs[2] as bool?; - preview = previewCheck ?? true; + localFilePath = + this.StoreRemoteFile( + remoteFilePath, + fileContent, + _runspaceContext.CurrentRunspace); } + else + { + await (this.editorOperations?.NewFileAsync()).ConfigureAwait(false); + EditorContext context = await (editorOperations?.GetEditorContextAsync()).ConfigureAwait(false); + context?.CurrentFile.InsertText(Encoding.UTF8.GetString(fileContent, 0, fileContent.Length)); + } + } - // Open the file in the editor - this.editorOperations?.OpenFileAsync(localFilePath, preview); + bool preview = true; + if (args.SourceArgs.Length >= 3) + { + bool? previewCheck = args.SourceArgs[2] as bool?; + preview = previewCheck ?? true; } - } - catch (NullReferenceException e) - { - this.logger.LogException("Could not store null remote file content", e); + + // Open the file in the editor + await (this.editorOperations?.OpenFileAsync(localFilePath, preview)).ConfigureAwait(false); } } + catch (NullReferenceException e) + { + this.logger.LogException("Could not store null remote file content", e); + } + catch (Exception e) + { + this.logger.LogException("Unable to handle remote file update", e); + } } - private void RegisterPSEditFunction(IRunspaceInfo runspaceInfo) + private Task RegisterPSEditFunctionAsync() + => _executionService.ExecuteDelegateAsync( + "Register psedit function", + ExecutionOptions.Default, + (pwsh, cancellationToken) => RegisterPSEditFunction(pwsh.Runspace), + CancellationToken.None); + + private void RegisterPSEditFunction(Runspace runspace) { - if (!runspaceInfo.IsOnRemoteMachine) + if (!runspace.RunspaceIsRemote) { return; } - try - { - runspaceInfo.Runspace.Events.ReceivedEvents.PSEventReceived += HandlePSEventReceivedAsync; + runspace.Events.ReceivedEvents.PSEventReceived += HandlePSEventReceivedAsync; - PSCommand createCommand = new PSCommand() - .AddScript(CreatePSEditFunctionScript) - .AddParameter("PSEditModule", PSEditModule); + PSCommand createCommand = new PSCommand() + .AddScript(CreatePSEditFunctionScript) + .AddParameter("PSEditModule", PSEditModule); - if (runspaceInfo.RunspaceOrigin == RunspaceOrigin.DebuggedRunspace) - { - _executionService.ExecutePSCommandAsync(createCommand, CancellationToken.None).GetAwaiter().GetResult(); - } - else - { - using (var powerShell = System.Management.Automation.PowerShell.Create()) - { - powerShell.Runspace = runspaceInfo.Runspace; - powerShell.Commands = createCommand; - powerShell.Invoke(); - } - } + var pwsh = SMA.PowerShell.Create(); + pwsh.Runspace = runspace; + try + { + pwsh.InvokeCommand(createCommand, new PSInvocationSettings { AddToHistory = false, ErrorActionPreference = ActionPreference.Stop }); } - catch (RemoteException e) + catch (Exception e) { this.logger.LogException("Could not create psedit function.", e); } + finally + { + pwsh.Dispose(); + } } private void RemovePSEditFunction(IRunspaceInfo runspaceInfo) { - if (runspaceInfo.RunspaceOrigin == RunspaceOrigin.PSSession) + if (runspaceInfo.RunspaceOrigin != RunspaceOrigin.PSSession) { - try + return; + } + try + { + if (runspaceInfo.Runspace.Events != null) { - if (runspaceInfo.Runspace.Events != null) - { - runspaceInfo.Runspace.Events.ReceivedEvents.PSEventReceived -= HandlePSEventReceivedAsync; - } + runspaceInfo.Runspace.Events.ReceivedEvents.PSEventReceived -= HandlePSEventReceivedAsync; + } - if (runspaceInfo.Runspace.RunspaceStateInfo.State == RunspaceState.Opened) + if (runspaceInfo.Runspace.RunspaceStateInfo.State == RunspaceState.Opened) + { + using (var powerShell = SMA.PowerShell.Create()) { - using (var powerShell = System.Management.Automation.PowerShell.Create()) - { - powerShell.Runspace = runspaceInfo.Runspace; - powerShell.Commands.AddScript(RemovePSEditFunctionScript); - powerShell.Invoke(); - } + powerShell.Runspace = runspaceInfo.Runspace; + powerShell.Commands.AddScript(RemovePSEditFunctionScript); + powerShell.Invoke(); } } - catch (Exception e) when (e is RemoteException || e is PSInvalidOperationException) - { - this.logger.LogException("Could not remove psedit function.", e); - } + } + catch (Exception e) when (e is RemoteException || e is PSInvalidOperationException) + { + this.logger.LogException("Could not remove psedit function.", e); } } From d5c69007f4ae002145a1e2b9149342f6a35b0054 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 15:24:45 -0700 Subject: [PATCH 130/176] Fix AnalysisService using --- .../Services/Analysis/AnalysisService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs index 22c61adc6..36f8f5181 100644 --- a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs @@ -14,6 +14,7 @@ using Microsoft.PowerShell.EditorServices.Services.Analysis; using Microsoft.PowerShell.EditorServices.Services.Configuration; using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; From 5331817e9b35925abc2bfe8186f15f8601d5bbd7 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 15:25:39 -0700 Subject: [PATCH 131/176] Fix InitialSessionState LanguageMode usage --- .../Services/PowerShell/Host/PsesInternalHost.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 22fb1d2b4..17f27faa6 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -615,7 +615,7 @@ public PowerShell CreateInitialPowerShell( HostStartupInfo hostStartupInfo, ReadLineProvider readLineProvider) { - Runspace runspace = CreateInitialRunspace(hostStartupInfo.LanguageMode); + Runspace runspace = CreateInitialRunspace(hostStartupInfo.InitialSessionState); PowerShell pwsh = CreatePowerShellForRunspace(runspace); var engineIntrinsics = (EngineIntrinsics)runspace.SessionStateProxy.GetVariable("ExecutionContext"); @@ -650,15 +650,9 @@ public PowerShell CreateInitialPowerShell( return pwsh; } - private Runspace CreateInitialRunspace(PSLanguageMode languageMode) + private Runspace CreateInitialRunspace(InitialSessionState initialSessionState) { - InitialSessionState iss = Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1" - ? InitialSessionState.CreateDefault() - : InitialSessionState.CreateDefault2(); - - iss.LanguageMode = languageMode; - - Runspace runspace = RunspaceFactory.CreateRunspace(PublicHost, iss); + Runspace runspace = RunspaceFactory.CreateRunspace(PublicHost, initialSessionState); runspace.SetApartmentStateToSta(); runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; From 32a7deae0c8d0ca957bba67c77aa3560e8d42698 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 15:36:07 -0700 Subject: [PATCH 132/176] Address static analyser compiler errors --- .../Server/PsesDebugServer.cs | 11 ------ .../Services/DebugAdapter/DebugService.cs | 2 - .../Handlers/ConfigurationDoneHandler.cs | 4 +- .../PowerShell/Console/ConsoleReadLine.cs | 38 +++++++++---------- .../PowerShell/Console/PSReadLineProxy.cs | 2 - .../Console/UnixConsoleOperations.cs | 11 +++--- .../Debugging/DscBreakpointCapability.cs | 3 +- ...ditorServicesConsolePSHostUserInterface.cs | 4 +- .../PowerShell/Host/PsesInternalHost.cs | 4 +- .../Services/Symbols/Vistors/AstOperations.cs | 3 +- .../Handlers/ConfigurationHandler.cs | 4 -- 11 files changed, 35 insertions(+), 51 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index ddcc02132..30aaa0ca6 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -25,17 +25,6 @@ namespace Microsoft.PowerShell.EditorServices.Server /// internal class PsesDebugServer : IDisposable { - /// - /// This is a bool but must be an int, since Interlocked.Exchange can't handle a bool - /// - private static readonly IdempotentLatch s_psrlCtorLatch = new(); - - private static readonly Lazy s_lazyInvokeReadLineConstructorCmdletInfo = new Lazy(() => - { - var type = Type.GetType("Microsoft.PowerShell.EditorServices.Commands.InvokeReadLineConstructorCommand, Microsoft.PowerShell.EditorServices.Hosting"); - return new CmdletInfo("__Invoke-ReadLineConstructor", type); - }); - private readonly Stream _inputStream; private readonly Stream _outputStream; private readonly bool _useTempSession; diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index f0a3f5496..56b6ecb11 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -31,8 +31,6 @@ internal class DebugService private const string PsesGlobalVariableNamePrefix = "__psEditorServices_"; private const string TemporaryScriptFileName = "Script Listing.ps1"; - private readonly BreakpointDetails[] s_emptyBreakpointDetailsArray = Array.Empty(); - private readonly ILogger _logger; private readonly IInternalPowerShellExecutionService _executionService; private readonly BreakpointService _breakpointService; diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 4280282ca..719e2d682 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -149,7 +149,7 @@ await _executionService _debugAdapterServer.SendNotification(EventNames.Terminated); } - private PSCommand BuildPSCommandFromArguments(string command, IReadOnlyList arguments) + private static PSCommand BuildPSCommandFromArguments(string command, IReadOnlyList arguments) { if (arguments is null or { Count: 0 }) { @@ -179,7 +179,7 @@ private PSCommand BuildPSCommandFromArguments(string command, IReadOnlyList 0) { currentCursorIndex = - this.MoveCursorToIndex( + MoveCursorToIndex( promptStartCol, promptStartRow, consoleWidth, @@ -300,7 +300,7 @@ internal string InvokeLegacyReadLine(bool isCommandLine, CancellationToken cance currentCompletion = null; currentCursorIndex = - this.MoveCursorToIndex( + MoveCursorToIndex( promptStartCol, promptStartRow, consoleWidth, @@ -313,7 +313,7 @@ internal string InvokeLegacyReadLine(bool isCommandLine, CancellationToken cance if (currentCursorIndex < inputLine.Length) { currentCursorIndex = - this.MoveCursorToIndex( + MoveCursorToIndex( promptStartCol, promptStartRow, consoleWidth, @@ -325,7 +325,7 @@ internal string InvokeLegacyReadLine(bool isCommandLine, CancellationToken cance currentCompletion = null; currentCursorIndex = - this.MoveCursorToIndex( + MoveCursorToIndex( promptStartCol, promptStartRow, consoleWidth, @@ -359,7 +359,7 @@ internal string InvokeLegacyReadLine(bool isCommandLine, CancellationToken cance historyIndex--; currentCursorIndex = - this.InsertInput( + InsertInput( inputLine, promptStartCol, promptStartRow, @@ -384,7 +384,7 @@ internal string InvokeLegacyReadLine(bool isCommandLine, CancellationToken cance if (historyIndex < currentHistory.Count) { currentCursorIndex = - this.InsertInput( + InsertInput( inputLine, promptStartCol, promptStartRow, @@ -396,7 +396,7 @@ internal string InvokeLegacyReadLine(bool isCommandLine, CancellationToken cance else if (historyIndex == currentHistory.Count) { currentCursorIndex = - this.InsertInput( + InsertInput( inputLine, promptStartCol, promptStartRow, @@ -413,7 +413,7 @@ internal string InvokeLegacyReadLine(bool isCommandLine, CancellationToken cance historyIndex = currentHistory != null ? currentHistory.Count : -1; currentCursorIndex = - this.InsertInput( + InsertInput( inputLine, promptStartCol, promptStartRow, @@ -429,7 +429,7 @@ internal string InvokeLegacyReadLine(bool isCommandLine, CancellationToken cance if (currentCursorIndex > 0) { currentCursorIndex = - this.InsertInput( + InsertInput( inputLine, promptStartCol, promptStartRow, @@ -447,7 +447,7 @@ internal string InvokeLegacyReadLine(bool isCommandLine, CancellationToken cance if (currentCursorIndex < inputLine.Length) { currentCursorIndex = - this.InsertInput( + InsertInput( inputLine, promptStartCol, promptStartRow, @@ -488,7 +488,7 @@ internal string InvokeLegacyReadLine(bool isCommandLine, CancellationToken cance currentCompletion = null; currentCursorIndex = - this.InsertInput( + InsertInput( inputLine, promptStartCol, promptStartRow, @@ -507,7 +507,7 @@ internal string InvokeLegacyReadLine(bool isCommandLine, CancellationToken cance } // TODO: Is this used? - private int CalculateIndexFromCursor( + private static int CalculateIndexFromCursor( int promptStartCol, int promptStartRow, int consoleWidth) @@ -517,7 +517,7 @@ private int CalculateIndexFromCursor( ConsoleProxy.GetCursorLeft() - promptStartCol; } - private void CalculateCursorFromIndex( + private static void CalculateCursorFromIndex( int promptStartCol, int promptStartRow, int consoleWidth, @@ -530,7 +530,7 @@ private void CalculateCursorFromIndex( cursorCol = cursorCol % consoleWidth; } - private int InsertInput( + private static int InsertInput( StringBuilder inputLine, int promptStartCol, int promptStartRow, @@ -549,7 +549,7 @@ private int InsertInput( } // Move the cursor to the new insertion point - this.MoveCursorToIndex( + MoveCursorToIndex( promptStartCol, promptStartRow, consoleWidth, @@ -598,7 +598,7 @@ private int InsertInput( { // Move the cursor to the final position return - this.MoveCursorToIndex( + MoveCursorToIndex( promptStartCol, promptStartRow, consoleWidth, @@ -610,13 +610,13 @@ private int InsertInput( } } - private int MoveCursorToIndex( + private static int MoveCursorToIndex( int promptStartCol, int promptStartRow, int consoleWidth, int newCursorIndex) { - this.CalculateCursorFromIndex( + CalculateCursorFromIndex( promptStartCol, promptStartRow, consoleWidth, diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs index 3a81a4c82..c529d5da7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs @@ -37,8 +37,6 @@ internal class PSReadLineProxy private const string VirtualTerminalTypeName = "Microsoft.PowerShell.Internal.VirtualTerminal"; - private const string ForcePSEventHandlingMethodName = "ForcePSEventHandling"; - private static readonly Type[] s_setKeyHandlerTypes = { typeof(string[]), diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/UnixConsoleOperations.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/UnixConsoleOperations.cs index 6197a8dc4..464dd69ad 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/UnixConsoleOperations.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/UnixConsoleOperations.cs @@ -245,7 +245,7 @@ private async Task ShortWaitForKeyAsync(CancellationToken cancellationToke return false; } - private bool SpinUntilKeyAvailable(int millisecondsTimeout, CancellationToken cancellationToken) + private static bool SpinUntilKeyAvailable(int millisecondsTimeout, CancellationToken cancellationToken) { return SpinWait.SpinUntil( () => @@ -256,7 +256,7 @@ private bool SpinUntilKeyAvailable(int millisecondsTimeout, CancellationToken ca millisecondsTimeout); } - private Task SpinUntilKeyAvailableAsync(int millisecondsTimeout, CancellationToken cancellationToken) + private static Task SpinUntilKeyAvailableAsync(int millisecondsTimeout, CancellationToken cancellationToken) { return Task.Factory.StartNew( () => SpinWait.SpinUntil( @@ -266,10 +266,11 @@ private Task SpinUntilKeyAvailableAsync(int millisecondsTimeout, Cancellat s_waitHandle.Wait(ShortWaitForKeySpinUntilSleepTime, cancellationToken); return IsKeyAvailable(cancellationToken); }, - millisecondsTimeout)); + millisecondsTimeout), + cancellationToken); } - private bool IsKeyAvailable(CancellationToken cancellationToken) + private static bool IsKeyAvailable(CancellationToken cancellationToken) { s_stdInHandle.Wait(cancellationToken); try @@ -282,7 +283,7 @@ private bool IsKeyAvailable(CancellationToken cancellationToken) } } - private async Task IsKeyAvailableAsync(CancellationToken cancellationToken) + private static async Task IsKeyAvailableAsync(CancellationToken cancellationToken) { await s_stdInHandle.WaitAsync(cancellationToken).ConfigureAwait(false); try diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index 922ad1fa4..744905a41 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -66,7 +66,8 @@ public async Task SetLineBreakpointsAsync( await executionService.ExecutePSCommandAsync( dscCommand, - CancellationToken.None); + CancellationToken.None) + .ConfigureAwait(false); // Verify all the breakpoints and return them foreach (var breakpoint in breakpoints) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs index 2f0646c5a..9a386abae 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs @@ -129,7 +129,7 @@ public override void WriteWarningLine(string message) _underlyingHostUI.WriteWarningLine(message); } - private PSHostUserInterface GetConsoleHostUI(PSHostUserInterface ui) + private static PSHostUserInterface GetConsoleHostUI(PSHostUserInterface ui) { FieldInfo externalUIField = ui.GetType().GetField("_externalUI", BindingFlags.NonPublic | BindingFlags.Instance); @@ -141,7 +141,7 @@ private PSHostUserInterface GetConsoleHostUI(PSHostUserInterface ui) return (PSHostUserInterface)externalUIField.GetValue(ui); } - private void SetConsoleHostUIToInteractive(PSHostUserInterface ui) + private static void SetConsoleHostUIToInteractive(PSHostUserInterface ui) { ui.GetType().GetProperty("ThrowOnReadAndPrompt", BindingFlags.NonPublic | BindingFlags.Instance)?.SetValue(ui, false); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 17f27faa6..12fbbfdb7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -589,7 +589,7 @@ private void RemoveRunspaceEventHandlers(Runspace runspace) runspace.StateChanged -= OnRunspaceStateChanged; } - private PowerShell CreateNestedPowerShell(RunspaceInfo currentRunspace) + private static PowerShell CreateNestedPowerShell(RunspaceInfo currentRunspace) { if (currentRunspace.RunspaceOrigin != RunspaceOrigin.Local) { @@ -604,7 +604,7 @@ private PowerShell CreateNestedPowerShell(RunspaceInfo currentRunspace) return pwsh; } - private PowerShell CreatePowerShellForRunspace(Runspace runspace) + private static PowerShell CreatePowerShellForRunspace(Runspace runspace) { var pwsh = PowerShell.Create(); pwsh.Runspace = runspace; diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index 5b966d30d..7108f8f52 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -101,7 +101,8 @@ await executionService.ExecuteDelegateAsync( options: null, powershell: pwsh); }, - cancellationToken); + cancellationToken) + .ConfigureAwait(false); stopwatch.Stop(); logger.LogTrace($"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index 9e34bf0e3..d50c1363e 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -31,9 +31,7 @@ internal class PsesConfigurationHandler : DidChangeConfigurationHandlerBase private readonly ExtensionService _extensionService; private readonly PsesInternalHost _psesHost; private readonly ILanguageServerFacade _languageServer; - private DidChangeConfigurationCapability _capability; private bool _profilesLoaded; - private bool _consoleReplStarted; private bool _extensionServiceInitialized; private bool _cwdSet; @@ -97,8 +95,6 @@ public override async Task Handle(DidChangeConfigurationParams request, Ca await _psesHost.StartAsync(new HostStartOptions(), CancellationToken.None).ConfigureAwait(false); - _consoleReplStarted = true; - if (loadProfiles) { _profilesLoaded = true; From 4fcdd8389235afbe961b0bc6e606eb71a748ec85 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 15:38:45 -0700 Subject: [PATCH 133/176] Apply suppression to ExitDebugLoop() API --- .../Services/PowerShell/Debugging/PowerShellDebugContext.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index 7b9833c59..f72f2a32b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -116,7 +116,9 @@ public void EnterDebugLoop(CancellationToken loopCancellationToken) RaiseDebuggerStoppedEvent(); } + // This must be called BEFORE the debug PowerShell has been popped + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "This method may acquire an implementation later, at which point it will need instance data")] public void ExitDebugLoop() { } From ccc2fc2bb59b3292c9d17abb6d2386bccd0da996 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 15:46:28 -0700 Subject: [PATCH 134/176] Fix NRE in DSC capability discovery --- .../Services/PowerShell/Runspace/RunspaceInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs index d29a5d11e..5013e09ba 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs @@ -86,7 +86,7 @@ public async Task GetDscBreakpointCapabilityAsync( PsesInternalHost psesHost, CancellationToken cancellationToken) { - if (_dscBreakpointCapability == null) + if (_dscBreakpointCapability is not null) { _dscBreakpointCapability = await DscBreakpointCapability.GetDscCapabilityAsync( logger, From b2e452f4b07a79277875bc37ac2e9679dac83378 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 17:57:14 -0700 Subject: [PATCH 135/176] Remove unused using statements --- .../Extensions/Api/EditorExtensionServiceProvider.cs | 1 - .../Extensions/Api/ExtensionCommandService.cs | 1 - .../Extensions/Api/LanguageServerService.cs | 1 - .../Extensions/Api/WorkspaceService.cs | 1 - src/PowerShellEditorServices/Extensions/EditorContext.cs | 1 - .../Extensions/EditorWorkspace.cs | 2 -- .../Hosting/EditorServicesServerFactory.cs | 1 - .../Logging/HostLoggerAdapter.cs | 2 -- src/PowerShellEditorServices/Server/PsesDebugServer.cs | 5 ----- .../Server/PsesLanguageServer.cs | 3 --- .../Server/PsesServiceCollectionExtensions.cs | 3 --- .../Services/DebugAdapter/BreakpointService.cs | 1 - .../DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs | 1 - .../DebugAdapter/Debugging/VariableDetailsBase.cs | 1 - .../Services/DebugAdapter/Handlers/DisconnectHandler.cs | 2 -- .../DebugAdapter/Handlers/LaunchAndAttachHandler.cs | 3 --- .../Services/DebugAdapter/Handlers/SetVariableHandler.cs | 3 --- .../Services/DebugAdapter/Handlers/VariablesHandler.cs | 1 - .../Extension/Handlers/InvokeExtensionCommandHandler.cs | 1 - .../Services/PowerShell/Console/ColorConfiguration.cs | 2 -- .../Services/PowerShell/Console/ConsoleReadLine.cs | 1 - .../Services/PowerShell/Console/IReadLine.cs | 3 --- .../Services/PowerShell/Console/PSReadLineProxy.cs | 1 - .../PowerShell/Context/PowerShellContextFrame.cs | 1 - .../PowerShell/Context/PowerShellVersionDetails.cs | 2 -- .../Services/PowerShell/Execution/ExecutionOptions.cs | 4 +--- .../PowerShell/Execution/SynchronousDelegateTask.cs | 1 - .../Services/PowerShell/Handlers/ExpandAliasHandler.cs | 2 -- .../Services/PowerShell/Handlers/GetCommandHandler.cs | 2 -- .../Services/PowerShell/Handlers/GetVersionHandler.cs | 3 --- .../Handlers/PSHostProcessAndRunspaceHandlers.cs | 3 --- .../Services/PowerShell/Handlers/ShowHelpHandler.cs | 1 - .../PowerShell/Host/EditorServicesConsolePSHost.cs | 3 --- .../Services/PowerShell/IPowerShellExecutionService.cs | 9 +-------- .../Services/PowerShell/Runspace/IRunspaceInfo.cs | 1 - .../PowerShell/Runspace/RunspaceChangedEventArgs.cs | 3 --- .../Services/PowerShell/Runspace/SessionDetails.cs | 2 -- .../Services/PowerShell/Utility/CommandHelpers.cs | 1 - .../Services/PowerShell/Utility/RunspaceExtensions.cs | 3 +-- .../Services/Symbols/ScriptDocumentSymbolProvider.cs | 2 -- .../Services/Symbols/SymbolsService.cs | 1 - .../Services/Symbols/Vistors/AstOperations.cs | 2 -- .../Services/Symbols/Vistors/FindSymbolsVisitor2.cs | 3 --- .../Services/Template/Handlers/TemplateHandlers.cs | 1 - .../Services/TextDocument/Handlers/FormattingHandlers.cs | 2 -- .../Services/Workspace/Handlers/ConfigurationHandler.cs | 1 - src/PowerShellEditorServices/Utility/IdempotentLatch.cs | 5 +---- src/PowerShellEditorServices/Utility/LspDebugUtils.cs | 1 - 48 files changed, 4 insertions(+), 96 deletions(-) diff --git a/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs b/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs index f59ff7a61..73e8e209e 100644 --- a/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs +++ b/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs @@ -5,7 +5,6 @@ using System.Linq.Expressions; using System.Reflection; using Microsoft.Extensions.DependencyInjection; -using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.Extension; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.LanguageServer.Protocol.Server; diff --git a/src/PowerShellEditorServices/Extensions/Api/ExtensionCommandService.cs b/src/PowerShellEditorServices/Extensions/Api/ExtensionCommandService.cs index 55c7c693d..64435154f 100644 --- a/src/PowerShellEditorServices/Extensions/Api/ExtensionCommandService.cs +++ b/src/PowerShellEditorServices/Extensions/Api/ExtensionCommandService.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.Extension; using System; using System.Collections.Generic; diff --git a/src/PowerShellEditorServices/Extensions/Api/LanguageServerService.cs b/src/PowerShellEditorServices/Extensions/Api/LanguageServerService.cs index 466e6b106..1a5254319 100644 --- a/src/PowerShellEditorServices/Extensions/Api/LanguageServerService.cs +++ b/src/PowerShellEditorServices/Extensions/Api/LanguageServerService.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using MediatR; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using System.Threading; using System.Threading.Tasks; diff --git a/src/PowerShellEditorServices/Extensions/Api/WorkspaceService.cs b/src/PowerShellEditorServices/Extensions/Api/WorkspaceService.cs index 98eb9f06e..dc1300e00 100644 --- a/src/PowerShellEditorServices/Extensions/Api/WorkspaceService.cs +++ b/src/PowerShellEditorServices/Extensions/Api/WorkspaceService.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Management.Automation.Language; -using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Extensions.Services { diff --git a/src/PowerShellEditorServices/Extensions/EditorContext.cs b/src/PowerShellEditorServices/Extensions/EditorContext.cs index f8e65f472..a844febe3 100644 --- a/src/PowerShellEditorServices/Extensions/EditorContext.cs +++ b/src/PowerShellEditorServices/Extensions/EditorContext.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using Microsoft.PowerShell.EditorServices.Services.TextDocument; namespace Microsoft.PowerShell.EditorServices.Extensions diff --git a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs index 02d0be68f..6a5c07bc2 100644 --- a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs +++ b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Threading.Tasks; - namespace Microsoft.PowerShell.EditorServices.Extensions { /// diff --git a/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs b/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs index 48c10f1ee..ff827646e 100644 --- a/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs +++ b/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs @@ -8,7 +8,6 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Server; -using Microsoft.PowerShell.EditorServices.Services; using Serilog; using Serilog.Events; using OmniSharp.Extensions.LanguageServer.Protocol.Server; diff --git a/src/PowerShellEditorServices/Logging/HostLoggerAdapter.cs b/src/PowerShellEditorServices/Logging/HostLoggerAdapter.cs index 802d6c762..41026a5f1 100644 --- a/src/PowerShellEditorServices/Logging/HostLoggerAdapter.cs +++ b/src/PowerShellEditorServices/Logging/HostLoggerAdapter.cs @@ -3,8 +3,6 @@ using Microsoft.Extensions.Logging; using System; -using System.Collections.Generic; -using System.Text; namespace Microsoft.PowerShell.EditorServices.Logging { diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 30aaa0ca6..0d45df793 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -3,18 +3,13 @@ using System; using System.IO; -using System.Management.Automation; -using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services; -using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; -using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.DebugAdapter.Server; using OmniSharp.Extensions.LanguageServer.Server; diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index 2014414c4..86eceac43 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.Management.Automation; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -11,8 +10,6 @@ using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.Extension; -using Microsoft.PowerShell.EditorServices.Services.PowerShell; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.Template; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using OmniSharp.Extensions.LanguageServer.Server; diff --git a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs index 1d8620442..55f440a07 100644 --- a/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs +++ b/src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs @@ -2,10 +2,7 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; -using System.Management.Automation.Host; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.Extension; diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs index f64bed806..1718c3168 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs @@ -11,7 +11,6 @@ using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; using Microsoft.PowerShell.EditorServices.Services.PowerShell; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; namespace Microsoft.PowerShell.EditorServices.Services diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs index 87dfd860f..25942c7c9 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/DebuggerStoppedEventArgs.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Utility; using System.Management.Automation; diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetailsBase.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetailsBase.cs index badaff9bc..51e613f2c 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetailsBase.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetailsBase.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.DebugAdapter { diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs index 9fddc371f..704312d48 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DisconnectHandler.cs @@ -10,8 +10,6 @@ using Microsoft.PowerShell.EditorServices.Server; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShell; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index d59ad94e9..c2d0e34f8 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Management.Automation; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -18,9 +17,7 @@ using OmniSharp.Extensions.DebugAdapter.Protocol.Server; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; namespace Microsoft.PowerShell.EditorServices.Handlers { diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/SetVariableHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/SetVariableHandler.cs index 5ce610ead..2eeb2fef6 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/SetVariableHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/SetVariableHandler.cs @@ -2,15 +2,12 @@ // Licensed under the MIT License. using System; -using System.Linq; using System.Management.Automation; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; -using Microsoft.PowerShell.EditorServices.Utility; -using OmniSharp.Extensions.DebugAdapter.Protocol.Models; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; using OmniSharp.Extensions.JsonRpc; diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/VariablesHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/VariablesHandler.cs index 494bfc2c0..a97232c06 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/VariablesHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/VariablesHandler.cs @@ -9,7 +9,6 @@ using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; using Microsoft.PowerShell.EditorServices.Utility; -using OmniSharp.Extensions.DebugAdapter.Protocol.Models; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; namespace Microsoft.PowerShell.EditorServices.Handlers diff --git a/src/PowerShellEditorServices/Services/Extension/Handlers/InvokeExtensionCommandHandler.cs b/src/PowerShellEditorServices/Services/Extension/Handlers/InvokeExtensionCommandHandler.cs index ae65bfae9..1a50779cc 100644 --- a/src/PowerShellEditorServices/Services/Extension/Handlers/InvokeExtensionCommandHandler.cs +++ b/src/PowerShellEditorServices/Services/Extension/Handlers/InvokeExtensionCommandHandler.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using MediatR; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Extensions; namespace Microsoft.PowerShell.EditorServices.Services.Extension diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs index da885c41d..7cad3721e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index fc13a54b6..e8f5d86ac 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -12,7 +12,6 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using System; using System.Collections.Generic; - using System.Diagnostics; using System.Management.Automation; using System.Management.Automation.Language; using System.Security; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs index 3e1afb44c..59724bcec 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; using System.Security; -using System.Text; using System.Threading; -using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs index c529d5da7..09bcda3ae 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs @@ -16,7 +16,6 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { - using OmniSharp.Extensions.DebugAdapter.Protocol.Models; using System.Management.Automation.Runspaces; internal class PSReadLineProxy diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs index 9e028463a..fb7421af5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using System; -using System.Threading; using SMA = System.Management.Automation; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs index 64d848b2c..b18c3ac04 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs @@ -2,11 +2,9 @@ // Licensed under the MIT License. using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using System; using System.Collections; using System.Linq; -using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs index 5e678e83a..dae155171 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs @@ -1,6 +1,4 @@ -using System; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution { public enum ExecutionPriority { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs index 3ff4b676f..7995a2587 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using System; using System.Threading; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs index 90d144bf5..44ba1bfe7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs @@ -6,10 +6,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services; using MediatR; using OmniSharp.Extensions.JsonRpc; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell; namespace Microsoft.PowerShell.EditorServices.Handlers diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs index c15a37a7c..6db8a898a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommandHandler.cs @@ -6,11 +6,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services; using MediatR; using OmniSharp.Extensions.JsonRpc; using Microsoft.PowerShell.EditorServices.Services.PowerShell; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; namespace Microsoft.PowerShell.EditorServices.Handlers { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs index 34d86b7f5..f8a6d5521 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetVersionHandler.cs @@ -3,15 +3,12 @@ using System; using System.Management.Automation; -using System.Runtime.CompilerServices; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.LanguageServer.Protocol.Models; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs index a36a24b8c..e4f5586bb 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PSHostProcessAndRunspaceHandlers.cs @@ -3,16 +3,13 @@ using System.Collections.Generic; using System.Management.Automation.Runspaces; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services; namespace Microsoft.PowerShell.EditorServices.Handlers { using Microsoft.PowerShell.EditorServices.Services.PowerShell; - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using System.Management.Automation; internal class PSHostProcessAndRunspaceHandlers : IGetPSHostProcessesHandler, IGetRunspaceHandler diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs index 3e0f0a903..5e13a5f8b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs @@ -5,7 +5,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Services; using MediatR; using OmniSharp.Extensions.JsonRpc; using Microsoft.PowerShell.EditorServices.Services.PowerShell; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index ed6a3aa5c..d60a66857 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -1,9 +1,6 @@ using System; using System.Globalization; using System.Management.Automation.Host; -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Hosting; -using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host { diff --git a/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs index 81011b124..bea1fa902 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs @@ -1,12 +1,5 @@ -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Hosting; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; -using OmniSharp.Extensions.LanguageServer.Protocol.Server; using System; using System.Collections.Generic; using System.Management.Automation; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs index 8f44e3e5d..d8b1bbc8e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs @@ -1,6 +1,5 @@  using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; -using System.Threading.Tasks; using SMA = System.Management.Automation.Runspaces; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceChangedEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceChangedEventArgs.cs index 04d590c33..044fded99 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceChangedEventArgs.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceChangedEventArgs.cs @@ -1,7 +1,4 @@ using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Collections.Generic; -using System.Text; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs index f21f521bd..02322a111 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs @@ -2,9 +2,7 @@ // Licensed under the MIT License. using System; -using System.Management.Automation; using System.Collections; -using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs index a7264edac..15f6845ae 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs @@ -7,7 +7,6 @@ using System.Management.Automation; using System.Threading; using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Utility; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs index f91ac11a8..0d3685290 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs @@ -1,5 +1,4 @@ -using Microsoft.PowerShell.EditorServices.Utility; -using System; +using System; using System.Linq.Expressions; using System.Management.Automation; using System.Reflection; diff --git a/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs index 89e5f5837..8399b943b 100644 --- a/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs +++ b/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Management.Automation.Language; using Microsoft.PowerShell.EditorServices.Services.TextDocument; diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs index 84220d7c8..2da5ec251 100644 --- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs +++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs @@ -16,7 +16,6 @@ using Microsoft.PowerShell.EditorServices.CodeLenses; using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using Microsoft.PowerShell.EditorServices.Services.Symbols; diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs index 7108f8f52..f312352f1 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; using System.Linq.Expressions; using System.Management.Automation; @@ -15,7 +14,6 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; -using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.Symbols { diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs index 545d56ebb..b7949b096 100644 --- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs +++ b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; -using System.Management.Automation.Language; - namespace Microsoft.PowerShell.EditorServices.Services.Symbols { // TODO: Restore this when we figure out how to support multiple diff --git a/src/PowerShellEditorServices/Services/Template/Handlers/TemplateHandlers.cs b/src/PowerShellEditorServices/Services/Template/Handlers/TemplateHandlers.cs index 58b4e6a78..57f7552ce 100644 --- a/src/PowerShellEditorServices/Services/Template/Handlers/TemplateHandlers.cs +++ b/src/PowerShellEditorServices/Services/Template/Handlers/TemplateHandlers.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Logging; -using Microsoft.PowerShell.EditorServices.Services; namespace Microsoft.PowerShell.EditorServices.Services.Template { diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs index 8f80350d1..c62c9f5db 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs @@ -6,11 +6,9 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Utility; -using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace Microsoft.PowerShell.EditorServices.Handlers { diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index d50c1363e..48f7dacab 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -12,7 +12,6 @@ using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.Configuration; using Newtonsoft.Json.Linq; -using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using OmniSharp.Extensions.LanguageServer.Protocol.Window; diff --git a/src/PowerShellEditorServices/Utility/IdempotentLatch.cs b/src/PowerShellEditorServices/Utility/IdempotentLatch.cs index 1f4b965bf..596f377ee 100644 --- a/src/PowerShellEditorServices/Utility/IdempotentLatch.cs +++ b/src/PowerShellEditorServices/Utility/IdempotentLatch.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; +using System.Threading; namespace Microsoft.PowerShell.EditorServices.Utility { diff --git a/src/PowerShellEditorServices/Utility/LspDebugUtils.cs b/src/PowerShellEditorServices/Utility/LspDebugUtils.cs index fadf8719e..254bc41a6 100644 --- a/src/PowerShellEditorServices/Utility/LspDebugUtils.cs +++ b/src/PowerShellEditorServices/Utility/LspDebugUtils.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; using OmniSharp.Extensions.DebugAdapter.Protocol.Models; From 08e2f696e8d9a3f8fa5240717582443321677d22 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 18:14:47 -0700 Subject: [PATCH 136/176] Add copyright headers --- .../Services/PowerShell/Console/ColorConfiguration.cs | 5 ++++- .../Services/PowerShell/Console/ConsoleProxy.cs | 4 +--- .../Services/PowerShell/Console/ConsoleReadLine.cs | 4 +--- .../Services/PowerShell/Console/IConsoleOperations.cs | 2 -- .../Services/PowerShell/Console/IReadLine.cs | 5 ++++- .../Services/PowerShell/Console/PSReadLineProxy.cs | 4 +--- .../Services/PowerShell/Console/ReadLineProvider.cs | 5 ++++- .../Services/PowerShell/Console/UnixConsoleOperations.cs | 4 +--- .../Services/PowerShell/Console/WindowsConsoleOperations.cs | 4 +--- .../Services/PowerShell/Context/PowerShellContextFrame.cs | 5 ++++- .../Services/PowerShell/Context/PowerShellFrameType.cs | 5 ++++- .../PowerShell/Debugging/DebuggerResumingEventArgs.cs | 5 ++++- .../PowerShell/Debugging/DscBreakpointCapability.cs | 6 ++---- .../PowerShell/Debugging/IPowerShellDebugContext.cs | 5 ++++- .../Services/PowerShell/Debugging/PowerShellDebugContext.cs | 5 ++++- .../PowerShell/Execution/BlockingConcurrentDeque.cs | 5 ++++- .../Services/PowerShell/Execution/ExecutionOptions.cs | 5 ++++- .../PowerShell/Execution/SynchronousDelegateTask.cs | 5 ++++- .../PowerShell/Execution/SynchronousPowerShellTask.cs | 5 ++++- .../Services/PowerShell/Execution/SynchronousTask.cs | 5 ++++- .../Services/PowerShell/Host/ConsoleColorProxy.cs | 4 +--- .../Services/PowerShell/Host/EditorServicesConsolePSHost.cs | 3 +++ .../Host/EditorServicesConsolePSHostRawUserInterface.cs | 5 ++++- .../Host/EditorServicesConsolePSHostUserInterface.cs | 5 ++++- .../Services/PowerShell/Host/HostStartOptions.cs | 4 +++- .../Services/PowerShell/Host/PsesInternalHost.cs | 5 ++++- .../Services/PowerShell/IPowerShellExecutionService.cs | 5 ++++- .../Services/PowerShell/Runspace/IRunspaceContext.cs | 4 +++- .../Services/PowerShell/Runspace/IRunspaceInfo.cs | 4 +++- .../PowerShell/Runspace/RunspaceChangedEventArgs.cs | 5 ++++- .../Services/PowerShell/Runspace/RunspaceInfo.cs | 5 ++++- .../Services/PowerShell/Utility/CancellationContext.cs | 5 ++++- .../Services/PowerShell/Utility/ErrorRecordExtensions.cs | 5 ++++- .../Services/PowerShell/Utility/PSCommandExtensions.cs | 5 ++++- .../Services/PowerShell/Utility/PowerShellExtensions.cs | 5 ++++- .../Services/PowerShell/Utility/RunspaceExtensions.cs | 5 ++++- src/PowerShellEditorServices/Utility/IdempotentLatch.cs | 5 ++++- src/PowerShellEditorServices/Utility/IsExternalInit.cs | 5 ++++- 38 files changed, 124 insertions(+), 53 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs index 7cad3721e..1bd80051c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ColorConfiguration.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleProxy.cs index 22892b445..c73e4f916 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleProxy.cs @@ -1,7 +1,5 @@ -// // Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// +// Licensed under the MIT license. using System; using System.Runtime.InteropServices; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index e8f5d86ac..12a917a6b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -// using System.Text; using System.Threading; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/IConsoleOperations.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/IConsoleOperations.cs index 6efe2d26e..4972f23a0 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/IConsoleOperations.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/IConsoleOperations.cs @@ -1,7 +1,5 @@ -// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -// using System; using System.Threading; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs index 59724bcec..c8c05b73b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Security; using System.Threading; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs index 09bcda3ae..7633435ac 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs @@ -1,7 +1,5 @@ -// // Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// +// Licensed under the MIT license. using System; using System.IO; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ReadLineProvider.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ReadLineProvider.cs index 312da2b05..c85c92097 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ReadLineProvider.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ReadLineProvider.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.Logging; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Extensions.Logging; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/UnixConsoleOperations.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/UnixConsoleOperations.cs index 464dd69ad..e1c39536d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/UnixConsoleOperations.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/UnixConsoleOperations.cs @@ -1,7 +1,5 @@ -// // Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// +// Licensed under the MIT license. using System; using System.Threading; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/WindowsConsoleOperations.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/WindowsConsoleOperations.cs index e4568b65b..20bc886ce 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/WindowsConsoleOperations.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/WindowsConsoleOperations.cs @@ -1,7 +1,5 @@ -// // Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// +// Licensed under the MIT license. using System; using System.Threading; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs index fb7421af5..9a31bf629 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellContextFrame.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.Logging; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using System; using SMA = System.Management.Automation; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellFrameType.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellFrameType.cs index 4b07fb164..cb20ff8ff 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellFrameType.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellFrameType.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DebuggerResumingEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DebuggerResumingEventArgs.cs index 94ecaac73..59c8cc902 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DebuggerResumingEventArgs.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DebuggerResumingEventArgs.cs @@ -1,4 +1,7 @@ -using System.Management.Automation; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs index 744905a41..0ad294bd0 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. using System.Linq; using System.Threading.Tasks; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs index d0f9b1e18..bf78d80b1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Management.Automation; using System.Threading; using System.Threading.Tasks; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index f72f2a32b..bbe12a0f5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Management.Automation; using System.Threading; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs index afaf59cc9..e57bfd570 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Collections.Concurrent; using System.Threading; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs index dae155171..4965e3415 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs @@ -1,4 +1,7 @@ -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution { public enum ExecutionPriority { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs index 7995a2587..343f930eb 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousDelegateTask.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.Logging; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using System; using System.Threading; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 085d1bd6b..a0c3082f0 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.Logging; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using System; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs index a2b0ae51d..93ca9aac6 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousTask.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.Logging; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Extensions.Logging; using System; using System.Runtime.ExceptionServices; using System.Threading; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/ConsoleColorProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/ConsoleColorProxy.cs index 1814b2816..75b1bb4be 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/ConsoleColorProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/ConsoleColorProxy.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -// /* using System; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs index d60a66857..b145d0299 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHost.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System; using System.Globalization; using System.Management.Automation.Host; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs index defaa0f7e..de69ff6c3 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostRawUserInterface.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.Logging; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using System; using System.Management.Automation; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs index 9a386abae..cab60cfff 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.Logging; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using System; using System.Collections.Generic; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs index 7c71d446e..2a1fdfd2f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs @@ -1,4 +1,6 @@ - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host { internal struct HostStartOptions diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 12fbbfdb7..ae4487d89 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.Logging; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; diff --git a/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs b/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs index bea1fa902..31a75728f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/IPowerShellExecutionService.cs @@ -1,4 +1,7 @@ -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using System; using System.Collections.Generic; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs index f8f77f707..c9232d7d5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceContext.cs @@ -1,4 +1,6 @@ - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace { internal interface IRunspaceContext diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs index d8b1bbc8e..401d0deab 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/IRunspaceInfo.cs @@ -1,4 +1,6 @@ - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using SMA = System.Management.Automation.Runspaces; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceChangedEventArgs.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceChangedEventArgs.cs index 044fded99..5d889ba1c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceChangedEventArgs.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceChangedEventArgs.cs @@ -1,4 +1,7 @@ -using Microsoft.PowerShell.EditorServices.Utility; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs index 5013e09ba..723796619 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs @@ -1,4 +1,7 @@ -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs index 81e6c1f3d..b9e33fe49 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Collections.Concurrent; using System.Threading; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs index da8d4cf2d..031a24749 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/ErrorRecordExtensions.cs @@ -1,4 +1,7 @@ -using Microsoft.PowerShell.EditorServices.Utility; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs index 33bb20280..fc32d4523 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs @@ -1,4 +1,7 @@ -using System.Management.Automation; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Text; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs index bf026238a..feda5d9b2 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Collections.ObjectModel; using System.Reflection; using System.Text; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs index 0d3685290..0a5076e57 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Linq.Expressions; using System.Management.Automation; using System.Reflection; diff --git a/src/PowerShellEditorServices/Utility/IdempotentLatch.cs b/src/PowerShellEditorServices/Utility/IdempotentLatch.cs index 596f377ee..31c1a95d0 100644 --- a/src/PowerShellEditorServices/Utility/IdempotentLatch.cs +++ b/src/PowerShellEditorServices/Utility/IdempotentLatch.cs @@ -1,4 +1,7 @@ -using System.Threading; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Threading; namespace Microsoft.PowerShell.EditorServices.Utility { diff --git a/src/PowerShellEditorServices/Utility/IsExternalInit.cs b/src/PowerShellEditorServices/Utility/IsExternalInit.cs index ff74defa3..7d336fdc2 100644 --- a/src/PowerShellEditorServices/Utility/IsExternalInit.cs +++ b/src/PowerShellEditorServices/Utility/IsExternalInit.cs @@ -1,4 +1,7 @@ -using System.ComponentModel; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.ComponentModel; namespace System.Runtime.CompilerServices { From de18309695f57a179a947c85af429acf52b91396 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 18:30:17 -0700 Subject: [PATCH 137/176] Remove unneeded internal usings --- .../Handlers/ConfigurationDoneHandler.cs | 12 +-- .../PowerShell/Console/ConsoleReadLine.cs | 15 ++-- .../Context/PowerShellVersionDetails.cs | 2 +- .../Debugging/PowerShellDebugContext.cs | 9 +-- .../Execution/SynchronousPowerShellTask.cs | 1 + .../PowerShell/Host/PsesInternalHost.cs | 33 ++++---- .../PowerShell/Runspace/RunspaceInfo.cs | 12 +-- .../PowerShell/Runspace/SessionDetails.cs | 4 +- .../PowerShell/Utility/PSCommandExtensions.cs | 76 ------------------- .../Utility/PowerShellExtensions.cs | 9 ++- .../Utility/PSCommandExtensions.cs | 67 +++++++++++++++- 11 files changed, 114 insertions(+), 126 deletions(-) delete mode 100644 src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 719e2d682..71cc5d3c5 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -1,12 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; -using System.Management.Automation; -using System.Management.Automation.Language; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; @@ -19,6 +13,12 @@ using OmniSharp.Extensions.DebugAdapter.Protocol.Events; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; using OmniSharp.Extensions.DebugAdapter.Protocol.Server; +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Language; +using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Handlers { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index 12a917a6b..277faace1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -3,17 +3,16 @@ using System.Text; using System.Threading; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Language; +using System.Security; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; - using System; - using System.Collections.Generic; - using System.Management.Automation; - using System.Management.Automation.Language; - using System.Security; - internal class ConsoleReadLine : IReadLine { private readonly PSReadLineProxy _psrlProxy; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs index b18c3ac04..2b1980cc1 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs @@ -2,13 +2,13 @@ // Licensed under the MIT License. using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using System; using System.Collections; using System.Linq; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Context { - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; using System.Management.Automation; /// diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index bbe12a0f5..780992b97 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -4,14 +4,13 @@ using System; using System.Management.Automation; using System.Threading; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging { - using Microsoft.Extensions.Logging; - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; - using OmniSharp.Extensions.LanguageServer.Protocol.Server; - using System.Threading.Tasks; - /// /// Handles the state of the PowerShell debugger. /// diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index a0c3082f0..4511fb1f5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index ae4487d89..0ea856fc9 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -11,21 +11,20 @@ using System.Collections.Generic; using System.Globalization; using System.Management.Automation.Host; -using SMA = System.Management.Automation; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using Microsoft.PowerShell.EditorServices.Utility; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host { - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; - using Microsoft.PowerShell.EditorServices.Utility; - using System.IO; using System.Management.Automation; using System.Management.Automation.Runspaces; - using System.Reflection; - using System.Text; - using System.Threading; - using System.Threading.Tasks; internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRunspaceContext, IInternalPowerShellExecutionService { @@ -117,7 +116,7 @@ public PsesInternalHost( public RunspaceInfo CurrentRunspace => CurrentFrame.RunspaceInfo; - public SMA.PowerShell CurrentPowerShell => CurrentFrame.PowerShell; + public PowerShell CurrentPowerShell => CurrentFrame.PowerShell; public EditorServicesConsolePSHost PublicHost { get; } @@ -253,7 +252,7 @@ public void CancelCurrentTask() public Task ExecuteDelegateAsync( string representation, ExecutionOptions executionOptions, - Func func, + Func func, CancellationToken cancellationToken) { return InvokeTaskOnPipelineThreadAsync(new SynchronousPSDelegateTask(_logger, this, representation, executionOptions ?? ExecutionOptions.Default, func, cancellationToken)); @@ -262,7 +261,7 @@ public Task ExecuteDelegateAsync( public Task ExecuteDelegateAsync( string representation, ExecutionOptions executionOptions, - Action action, + Action action, CancellationToken cancellationToken) { return InvokeTaskOnPipelineThreadAsync(new SynchronousPSDelegateTask(_logger, this, representation, executionOptions ?? ExecutionOptions.Default, action, cancellationToken)); @@ -325,13 +324,13 @@ public IReadOnlyList InvokePSCommand(PSCommand psCommand, Powe public void InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) => InvokePSCommand(psCommand, executionOptions, cancellationToken); - public TResult InvokePSDelegate(string representation, ExecutionOptions executionOptions, Func func, CancellationToken cancellationToken) + public TResult InvokePSDelegate(string representation, ExecutionOptions executionOptions, Func func, CancellationToken cancellationToken) { var task = new SynchronousPSDelegateTask(_logger, this, representation, executionOptions, func, cancellationToken); return task.ExecuteAndGetResult(cancellationToken); } - public void InvokePSDelegate(string representation, ExecutionOptions executionOptions, Action action, CancellationToken cancellationToken) + public void InvokePSDelegate(string representation, ExecutionOptions executionOptions, Action action, CancellationToken cancellationToken) { var task = new SynchronousPSDelegateTask(_logger, this, representation, executionOptions, action, cancellationToken); task.ExecuteAndGetResult(cancellationToken); @@ -361,7 +360,7 @@ private void Run() return (pwsh, localRunspaceInfo); } - private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType, RunspaceInfo newRunspaceInfo = null) + private void PushPowerShellAndRunLoop(PowerShell pwsh, PowerShellFrameType frameType, RunspaceInfo newRunspaceInfo = null) { // TODO: Improve runspace origin detection here if (newRunspaceInfo is null) @@ -379,7 +378,7 @@ private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType f PushPowerShellAndRunLoop(new PowerShellContextFrame(pwsh, newRunspaceInfo, frameType)); } - private RunspaceInfo GetRunspaceInfoForPowerShell(SMA.PowerShell pwsh, out bool isNewRunspace, out RunspaceFrame oldRunspaceFrame) + private RunspaceInfo GetRunspaceInfoForPowerShell(PowerShell pwsh, out bool isNewRunspace, out RunspaceFrame oldRunspaceFrame) { oldRunspaceFrame = null; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs index 723796619..c9b4d247c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/RunspaceInfo.cs @@ -3,16 +3,16 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; +using Microsoft.Extensions.Logging; +using System.Threading.Tasks; +using System.Threading; +using System; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace { - using System.Management.Automation.Runspaces; using System.Management.Automation; - using Microsoft.Extensions.Logging; - using System.Threading.Tasks; - using System.Threading; - using System; - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; + using System.Management.Automation.Runspaces; internal class RunspaceInfo : IRunspaceInfo { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs b/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs index 02322a111..2683e1992 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs @@ -3,11 +3,11 @@ using System; using System.Collections; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using System.Linq; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace { - using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; - using System.Linq; using System.Management.Automation; /// diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs deleted file mode 100644 index fc32d4523..000000000 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PSCommandExtensions.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Text; - -namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility -{ - internal static class PSCommandExtensions - { - public static PSCommand AddOutputCommand(this PSCommand psCommand) - { - return psCommand.MergePipelineResults() - .AddCommand("Out-Default", useLocalScope: true); - } - - public static PSCommand AddDebugOutputCommand(this PSCommand psCommand) - { - return psCommand.MergePipelineResults() - .AddCommand("Out-String", useLocalScope: true) - .AddParameter("Stream"); - } - - public static PSCommand MergePipelineResults(this PSCommand psCommand) - { - // We need to do merge errors and output before rendering with an Out- cmdlet - Command lastCommand = psCommand.Commands[psCommand.Commands.Count - 1]; - lastCommand.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output); - lastCommand.MergeMyResults(PipelineResultTypes.Information, PipelineResultTypes.Output); - return psCommand; - } - - /// - /// Get a representation of the PSCommand, for logging purposes. - /// - public static string GetInvocationText(this PSCommand command) - { - Command currentCommand = command.Commands[0]; - var sb = new StringBuilder().AddCommandText(command.Commands[0]); - - for (int i = 1; i < command.Commands.Count; i++) - { - sb.Append(currentCommand.IsEndOfStatement ? "; " : " | "); - currentCommand = command.Commands[i]; - sb.AddCommandText(currentCommand); - } - - return sb.ToString(); - } - - private static StringBuilder AddCommandText(this StringBuilder sb, Command command) - { - sb.Append(command.CommandText); - if (command.Parameters != null) - { - foreach (CommandParameter parameter in command.Parameters) - { - if (parameter.Name != null) - { - sb.Append(" -").Append(parameter.Name); - } - - if (parameter.Value != null) - { - // This isn't going to get PowerShell's string form of the value, - // but it's good enough, and not as complex or expensive - sb.Append(' ').Append(parameter.Value); - } - } - } - - return sb; - } - } -} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs index feda5d9b2..5724776d5 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs @@ -5,13 +5,14 @@ using System.Collections.ObjectModel; using System.Reflection; using System.Text; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Utility; +using System.Collections.Generic; +using System.IO; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility { - using Microsoft.Extensions.Logging; - using Microsoft.PowerShell.EditorServices.Hosting; - using System.Collections.Generic; - using System.IO; using System.Management.Automation; internal static class PowerShellExtensions diff --git a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs b/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs index d283d215a..55c8eed20 100644 --- a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs +++ b/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs @@ -6,6 +6,7 @@ using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Reflection; +using System.Text; namespace Microsoft.PowerShell.EditorServices.Utility { @@ -32,10 +33,74 @@ static PSCommandExtensions() // PowerShell's missing an API for us to AddCommand using a CommandInfo. // An issue was filed here: https://github.com/PowerShell/PowerShell/issues/12295 // This works around this by creating a `Command` and passing it into PSCommand.AddCommand(Command command) - internal static PSCommand AddCommand(this PSCommand command, CommandInfo commandInfo) + public static PSCommand AddCommand(this PSCommand command, CommandInfo commandInfo) { var rsCommand = s_commandCtor(commandInfo); return command.AddCommand(rsCommand); } + + public static PSCommand AddOutputCommand(this PSCommand psCommand) + { + return psCommand.MergePipelineResults() + .AddCommand("Out-Default", useLocalScope: true); + } + + public static PSCommand AddDebugOutputCommand(this PSCommand psCommand) + { + return psCommand.MergePipelineResults() + .AddCommand("Out-String", useLocalScope: true) + .AddParameter("Stream"); + } + + public static PSCommand MergePipelineResults(this PSCommand psCommand) + { + // We need to do merge errors and output before rendering with an Out- cmdlet + Command lastCommand = psCommand.Commands[psCommand.Commands.Count - 1]; + lastCommand.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output); + lastCommand.MergeMyResults(PipelineResultTypes.Information, PipelineResultTypes.Output); + return psCommand; + } + + /// + /// Get a representation of the PSCommand, for logging purposes. + /// + public static string GetInvocationText(this PSCommand command) + { + Command currentCommand = command.Commands[0]; + var sb = new StringBuilder().AddCommandText(command.Commands[0]); + + for (int i = 1; i < command.Commands.Count; i++) + { + sb.Append(currentCommand.IsEndOfStatement ? "; " : " | "); + currentCommand = command.Commands[i]; + sb.AddCommandText(currentCommand); + } + + return sb.ToString(); + } + + private static StringBuilder AddCommandText(this StringBuilder sb, Command command) + { + sb.Append(command.CommandText); + if (command.Parameters != null) + { + foreach (CommandParameter parameter in command.Parameters) + { + if (parameter.Name != null) + { + sb.Append(" -").Append(parameter.Name); + } + + if (parameter.Value != null) + { + // This isn't going to get PowerShell's string form of the value, + // but it's good enough, and not as complex or expensive + sb.Append(' ').Append(parameter.Value); + } + } + } + + return sb; + } } } From ec85f59e5a119582d5f484976350bf8e4595d847 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 12 Oct 2021 10:42:01 -0700 Subject: [PATCH 138/176] Implement PowerShell engine OnIdle events --- .../PowerShell/Host/PsesInternalHost.cs | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 12fbbfdb7..3eeed44a6 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -55,6 +55,8 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns private readonly IdempotentLatch _isRunningLatch = new(); + private EngineIntrinsics _mainRunspaceEngineIntrinsics; + private bool _shouldExit = false; private string _localComputerName; @@ -345,17 +347,18 @@ public Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cance private void Run() { - (PowerShell pwsh, RunspaceInfo localRunspaceInfo) = CreateInitialPowerShellSession(); + (PowerShell pwsh, RunspaceInfo localRunspaceInfo, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShellSession(); + _mainRunspaceEngineIntrinsics = engineIntrinsics; _localComputerName = localRunspaceInfo.SessionDetails.ComputerName; _runspaceStack.Push(new RunspaceFrame(pwsh.Runspace, localRunspaceInfo)); PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal, localRunspaceInfo); } - private (PowerShell, RunspaceInfo) CreateInitialPowerShellSession() + private (PowerShell, RunspaceInfo, EngineIntrinsics) CreateInitialPowerShellSession() { - PowerShell pwsh = CreateInitialPowerShell(_hostInfo, _readLineProvider); + (PowerShell pwsh, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShell(_hostInfo, _readLineProvider); RunspaceInfo localRunspaceInfo = RunspaceInfo.CreateFromLocalPowerShell(_logger, pwsh); - return (pwsh, localRunspaceInfo); + return (pwsh, localRunspaceInfo, engineIntrinsics); } private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType, RunspaceInfo newRunspaceInfo = null) @@ -611,7 +614,7 @@ private static PowerShell CreatePowerShellForRunspace(Runspace runspace) return pwsh; } - public PowerShell CreateInitialPowerShell( + public (PowerShell, EngineIntrinsics) CreateInitialPowerShell( HostStartupInfo hostStartupInfo, ReadLineProvider readLineProvider) { @@ -647,7 +650,7 @@ public PowerShell CreateInitialPowerShell( } } - return pwsh; + return (pwsh, engineIntrinsics); } private Runspace CreateInitialRunspace(InitialSessionState initialSessionState) @@ -666,7 +669,27 @@ private Runspace CreateInitialRunspace(InitialSessionState initialSessionState) private void OnPowerShellIdle() { - if (_taskQueue.Count == 0) + IReadOnlyList eventSubscribers = _mainRunspaceEngineIntrinsics.Events.Subscribers; + + // Go through pending event subscribers and: + // - if we have any subscribers, ensure we process any events + // - if we have any idle events, generate an idle event and process that + bool runPipelineForEventProcessing = false; + foreach (PSEventSubscriber subscriber in eventSubscribers) + { + runPipelineForEventProcessing = true; + + if (string.Equals(subscriber.SourceIdentifier, PSEngineEvent.OnIdle, StringComparison.OrdinalIgnoreCase)) + { + // PowerShell thinks we're in a call (the ReadLine call) rather than idle, + // but we know we're sitting in the prompt. + // So we need to generate the idle event ourselves + _mainRunspaceEngineIntrinsics.Events.GenerateEvent(PSEngineEvent.OnIdle, sender: null, args: null, extraData: null); + break; + } + } + + if (!runPipelineForEventProcessing && _taskQueue.Count == 0) { return; } @@ -686,9 +709,21 @@ private void OnPowerShellIdle() return; } + // If we're executing a task, we don't need to run an extra pipeline later for events + // TODO: This may not be a PowerShell task, so ideally we can differentiate that here. + // For now it's mostly true and an easy assumption to make. + runPipelineForEventProcessing = false; task.ExecuteSynchronously(cancellationScope.CancellationToken); } } + + // We didn't end up executinng anything in the background, + // so we need to run a small artificial pipeline instead + // to force event processing + if (runPipelineForEventProcessing) + { + InvokePSCommand(new PSCommand().AddScript("0", useLocalScope: true), PowerShellExecutionOptions.Default, CancellationToken.None); + } } private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) @@ -769,7 +804,8 @@ private Task PopOrReinitializeRunspaceAsync() // If our main runspace was corrupted, // we must re-initialize our state. // TODO: Use runspace.ResetRunspaceState() here instead - (PowerShell pwsh, RunspaceInfo runspaceInfo) = CreateInitialPowerShellSession(); + (PowerShell pwsh, RunspaceInfo runspaceInfo, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShellSession(); + _mainRunspaceEngineIntrinsics = engineIntrinsics; PushPowerShell(new PowerShellContextFrame(pwsh, runspaceInfo, PowerShellFrameType.Normal)); _logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized." From e4730a12fe0c6ec819d04bef0bad3d74574e416d Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 12 Oct 2021 17:19:58 -0700 Subject: [PATCH 139/176] Improve artificial idle event comment --- .../Services/PowerShell/Host/PsesInternalHost.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 3eeed44a6..ada728d1e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -681,9 +681,9 @@ private void OnPowerShellIdle() if (string.Equals(subscriber.SourceIdentifier, PSEngineEvent.OnIdle, StringComparison.OrdinalIgnoreCase)) { - // PowerShell thinks we're in a call (the ReadLine call) rather than idle, - // but we know we're sitting in the prompt. - // So we need to generate the idle event ourselves + // We control the pipeline thread, so it's not possible for PowerShell to generate events while we're here. + // But we know we're sitting waiting for the prompt, so we generate the idle event ourselves + // and that will flush idle event subscribers in PowerShell so we can service them _mainRunspaceEngineIntrinsics.Events.GenerateEvent(PSEngineEvent.OnIdle, sender: null, args: null, extraData: null); break; } From fe6a142bdd6bc882dba34518ec871365d58210af Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 12 Oct 2021 17:20:21 -0700 Subject: [PATCH 140/176] Fix typo --- .../Services/PowerShell/Host/PsesInternalHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index ada728d1e..09805bd84 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -717,7 +717,7 @@ private void OnPowerShellIdle() } } - // We didn't end up executinng anything in the background, + // We didn't end up executing anything in the background, // so we need to run a small artificial pipeline instead // to force event processing if (runPipelineForEventProcessing) From 08df557eca01f9f405a8beea6a28e1ff0fe87e48 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 12 Oct 2021 17:24:19 -0700 Subject: [PATCH 141/176] Change BlockingConcurrentDequeue to provide IsEmpty property --- .../Services/PowerShell/Execution/BlockingConcurrentDeque.cs | 2 +- .../Services/PowerShell/Host/PsesInternalHost.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs index afaf59cc9..79339411b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs @@ -36,7 +36,7 @@ public BlockingConcurrentDeque() }; } - public int Count => _queues[0].Count + _queues[1].Count; + public bool IsEmpty => _queues[0].Count == 0 && _queues[1].Count == 0; public void Prepend(T item) { diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 09805bd84..56ab05dda 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -689,7 +689,7 @@ private void OnPowerShellIdle() } } - if (!runPipelineForEventProcessing && _taskQueue.Count == 0) + if (!runPipelineForEventProcessing && _taskQueue.IsEmpty) { return; } From 7ed4903012f9812b4fb17935ad8e5092902e5c09 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 13 Oct 2021 15:05:38 -0700 Subject: [PATCH 142/176] Fix using --- .../Services/PowerShell/Console/ConsoleReadLine.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index 277faace1..d1452e7ed 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -5,7 +5,6 @@ using System.Threading; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using System; using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Language; @@ -13,6 +12,8 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { + using System; + internal class ConsoleReadLine : IReadLine { private readonly PSReadLineProxy _psrlProxy; From 810822495eb6640b8312bb703929b56224425121 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 13 Oct 2021 12:20:09 -0700 Subject: [PATCH 143/176] Convert PSRL OnIdle handler to take a CancellationToken --- .../Services/PowerShell/Console/ConsoleReadLine.cs | 2 +- .../Services/PowerShell/Console/IReadLine.cs | 2 +- .../Services/PowerShell/Console/PSReadLineProxy.cs | 2 +- .../Services/PowerShell/Host/PsesInternalHost.cs | 6 +++--- .../Services/PowerShell/Utility/CancellationContext.cs | 8 +++++--- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs index d1452e7ed..55174610c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs @@ -49,7 +49,7 @@ public bool TryOverrideReadKey(Func readKeyFunc) return true; } - public bool TryOverrideIdleHandler(Action idleHandler) + public bool TryOverrideIdleHandler(Action idleHandler) { _psrlProxy.OverrideIdleHandler(idleHandler); return true; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs index c8c05b73b..3bb0bede8 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs @@ -15,6 +15,6 @@ internal interface IReadLine bool TryOverrideReadKey(Func readKeyOverride); - bool TryOverrideIdleHandler(Action idleHandler); + bool TryOverrideIdleHandler(Action idleHandler); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs index 7633435ac..a9b95b031 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PSReadLineProxy.cs @@ -168,7 +168,7 @@ internal void OverrideReadKey(Func readKeyFunc) _readKeyOverrideField.SetValue(null, readKeyFunc); } - internal void OverrideIdleHandler(Action idleAction) + internal void OverrideIdleHandler(Action idleAction) { _handleIdleOverrideField.SetValue(null, idleAction); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 50db143b1..5893b690a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -616,7 +616,7 @@ private static PowerShell CreatePowerShellForRunspace(Runspace runspace) return pwsh; } - public (PowerShell, EngineIntrinsics) CreateInitialPowerShell( + private (PowerShell, EngineIntrinsics) CreateInitialPowerShell( HostStartupInfo hostStartupInfo, ReadLineProvider readLineProvider) { @@ -669,7 +669,7 @@ private Runspace CreateInitialRunspace(InitialSessionState initialSessionState) return runspace; } - private void OnPowerShellIdle() + private void OnPowerShellIdle(CancellationToken idleCancellationToken) { IReadOnlyList eventSubscribers = _mainRunspaceEngineIntrinsics.Events.Subscribers; @@ -696,7 +696,7 @@ private void OnPowerShellIdle() return; } - using (CancellationScope cancellationScope = _cancellationContext.EnterScope(isIdleScope: true)) + using (CancellationScope cancellationScope = _cancellationContext.EnterScope(isIdleScope: true, idleCancellationToken)) { while (!cancellationScope.CancellationToken.IsCancellationRequested && _taskQueue.TryTake(out ISynchronousTask task)) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs index b9e33fe49..8503b2065 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CancellationContext.cs @@ -33,15 +33,17 @@ public CancellationContext() _cancellationSourceStack = new ConcurrentStack(); } - public CancellationScope EnterScope(bool isIdleScope) + public CancellationScope EnterScope(bool isIdleScope, CancellationToken cancellationToken) { CancellationTokenSource newScopeCancellationSource = _cancellationSourceStack.TryPeek(out CancellationScope parentScope) - ? CancellationTokenSource.CreateLinkedTokenSource(parentScope.CancellationToken) - : new CancellationTokenSource(); + ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, parentScope.CancellationToken) + : CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); return EnterScope(isIdleScope, newScopeCancellationSource); } + public CancellationScope EnterScope(bool isIdleScope) => EnterScope(isIdleScope, CancellationToken.None); + public void CancelCurrentTask() { if (_cancellationSourceStack.TryPeek(out CancellationScope currentCancellationSource)) From 0f8dd05f2d07d36c7770887745fec7987eaa99eb Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 12 Oct 2021 16:08:15 -0700 Subject: [PATCH 144/176] Add initial ReadLine implementation --- .../PowerShell/Console/LegacyReadLine.cs | 536 ++++++++++++++++++ .../{ConsoleReadLine.cs => PsrlReadLine.cs} | 8 +- .../PowerShell/Host/PsesInternalHost.cs | 2 +- 3 files changed, 541 insertions(+), 5 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs rename src/PowerShellEditorServices/Services/PowerShell/Console/{ConsoleReadLine.cs => PsrlReadLine.cs} (99%) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs new file mode 100644 index 000000000..419c7387d --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs @@ -0,0 +1,536 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Management.Automation; +using System.Security; +using System.Text; +using System.Threading; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using System.Linq; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console +{ + using System; + + internal class LegacyReadLine : IReadLine + { + private readonly PsesInternalHost _psesHost; + + private readonly IPowerShellDebugContext _debugContext; + + private Func _readKeyFunc; + + public LegacyReadLine( + PsesInternalHost psesHost, + IPowerShellDebugContext debugContext) + { + _psesHost = psesHost; + _debugContext = debugContext; + } + + public string ReadLine(CancellationToken cancellationToken) + { + // TODO: Is inputBeforeCompletion used? + string inputBeforeCompletion = null; + string inputAfterCompletion = null; + CommandCompletion currentCompletion = null; + + int historyIndex = -1; + IReadOnlyList currentHistory = null; + + StringBuilder inputLine = new StringBuilder(); + + int initialCursorCol = ConsoleProxy.GetCursorLeft(cancellationToken); + int initialCursorRow = ConsoleProxy.GetCursorTop(cancellationToken); + + // TODO: Are these used? + int initialWindowLeft = Console.WindowLeft; + int initialWindowTop = Console.WindowTop; + + int currentCursorIndex = 0; + + Console.TreatControlCAsInput = true; + + try + { + while (!cancellationToken.IsCancellationRequested) + { + ConsoleKeyInfo keyInfo = ReadKey(displayKeyInConsole: false, cancellationToken); + + // Do final position calculation after the key has been pressed + // because the window could have been resized before then + int promptStartCol = initialCursorCol; + int promptStartRow = initialCursorRow; + int consoleWidth = Console.WindowWidth; + + //case ConsoleKey.C when ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0): + // throw new PipelineStoppedException(); + + + switch (keyInfo.Key) + { + case ConsoleKey.Tab: + if (currentCompletion == null) + { + inputBeforeCompletion = inputLine.ToString(); + inputAfterCompletion = null; + + // TODO: This logic should be moved to AstOperations or similar! + + if (_debugContext.IsStopped) + { + PSCommand command = new PSCommand() + .AddCommand("TabExpansion2") + .AddParameter("InputScript", inputBeforeCompletion) + .AddParameter("CursorColumn", currentCursorIndex) + .AddParameter("Options", null); + + currentCompletion = _psesHost.InvokePSCommand(command, PowerShellExecutionOptions.Default, cancellationToken).FirstOrDefault(); + } + else + { + currentCompletion = _psesHost.InvokePSDelegate( + "Legacy readline inline command completion", + ExecutionOptions.Default, + (pwsh, cancellationToken) => CommandCompletion.CompleteInput(inputAfterCompletion, currentCursorIndex, options: null, pwsh), + cancellationToken); + + if (currentCompletion.CompletionMatches.Count > 0) + { + int replacementEndIndex = + currentCompletion.ReplacementIndex + + currentCompletion.ReplacementLength; + + inputAfterCompletion = + inputLine.ToString( + replacementEndIndex, + inputLine.Length - replacementEndIndex); + } + else + { + currentCompletion = null; + } + } + } + + CompletionResult completion = + currentCompletion?.GetNextResult( + !keyInfo.Modifiers.HasFlag(ConsoleModifiers.Shift)); + + if (completion != null) + { + currentCursorIndex = + InsertInput( + inputLine, + promptStartCol, + promptStartRow, + $"{completion.CompletionText}{inputAfterCompletion}", + currentCursorIndex, + insertIndex: currentCompletion.ReplacementIndex, + replaceLength: inputLine.Length - currentCompletion.ReplacementIndex, + finalCursorIndex: currentCompletion.ReplacementIndex + completion.CompletionText.Length); + } + + continue; + + case ConsoleKey.LeftArrow: + currentCompletion = null; + + if (currentCursorIndex > 0) + { + currentCursorIndex = + MoveCursorToIndex( + promptStartCol, + promptStartRow, + consoleWidth, + currentCursorIndex - 1); + } + + continue; + + case ConsoleKey.Home: + currentCompletion = null; + + currentCursorIndex = + MoveCursorToIndex( + promptStartCol, + promptStartRow, + consoleWidth, + 0); + + continue; + + case ConsoleKey.RightArrow: + currentCompletion = null; + + if (currentCursorIndex < inputLine.Length) + { + currentCursorIndex = + MoveCursorToIndex( + promptStartCol, + promptStartRow, + consoleWidth, + currentCursorIndex + 1); + } + + continue; + + case ConsoleKey.End: + currentCompletion = null; + + currentCursorIndex = + MoveCursorToIndex( + promptStartCol, + promptStartRow, + consoleWidth, + inputLine.Length); + + continue; + + case ConsoleKey.UpArrow: + currentCompletion = null; + + // TODO: Ctrl+Up should allow navigation in multi-line input + + if (currentHistory == null) + { + historyIndex = -1; + + PSCommand command = new PSCommand() + .AddCommand("Get-History"); + + currentHistory = _psesHost.InvokePSCommand(command, PowerShellExecutionOptions.Default, cancellationToken); + + if (currentHistory != null) + { + historyIndex = currentHistory.Count; + } + } + + if (currentHistory != null && currentHistory.Count > 0 && historyIndex > 0) + { + historyIndex--; + + currentCursorIndex = + InsertInput( + inputLine, + promptStartCol, + promptStartRow, + (string)currentHistory[historyIndex].Properties["CommandLine"].Value, + currentCursorIndex, + insertIndex: 0, + replaceLength: inputLine.Length); + } + + continue; + + case ConsoleKey.DownArrow: + currentCompletion = null; + + // The down arrow shouldn't cause history to be loaded, + // it's only for navigating an active history array + + if (historyIndex > -1 && historyIndex < currentHistory.Count && + currentHistory != null && currentHistory.Count > 0) + { + historyIndex++; + + if (historyIndex < currentHistory.Count) + { + currentCursorIndex = + InsertInput( + inputLine, + promptStartCol, + promptStartRow, + (string)currentHistory[historyIndex].Properties["CommandLine"].Value, + currentCursorIndex, + insertIndex: 0, + replaceLength: inputLine.Length); + } + else if (historyIndex == currentHistory.Count) + { + currentCursorIndex = + InsertInput( + inputLine, + promptStartCol, + promptStartRow, + string.Empty, + currentCursorIndex, + insertIndex: 0, + replaceLength: inputLine.Length); + } + } + + continue; + + case ConsoleKey.Escape: + currentCompletion = null; + historyIndex = currentHistory != null ? currentHistory.Count : -1; + + currentCursorIndex = + InsertInput( + inputLine, + promptStartCol, + promptStartRow, + string.Empty, + currentCursorIndex, + insertIndex: 0, + replaceLength: inputLine.Length); + + continue; + + case ConsoleKey.Backspace: + currentCompletion = null; + + if (currentCursorIndex > 0) + { + currentCursorIndex = + InsertInput( + inputLine, + promptStartCol, + promptStartRow, + string.Empty, + currentCursorIndex, + insertIndex: currentCursorIndex - 1, + replaceLength: 1, + finalCursorIndex: currentCursorIndex - 1); + } + + continue; + + case ConsoleKey.Delete: + currentCompletion = null; + + if (currentCursorIndex < inputLine.Length) + { + currentCursorIndex = + InsertInput( + inputLine, + promptStartCol, + promptStartRow, + string.Empty, + currentCursorIndex, + replaceLength: 1, + finalCursorIndex: currentCursorIndex); + } + + continue; + + case ConsoleKey.Enter: + string completedInput = inputLine.ToString(); + currentCompletion = null; + currentHistory = null; + + //if ((keyInfo.Modifiers & ConsoleModifiers.Shift) == ConsoleModifiers.Shift) + //{ + // // TODO: Start a new line! + // continue; + //} + + Parser.ParseInput( + completedInput, + out Token[] tokens, + out ParseError[] parseErrors); + + //if (parseErrors.Any(e => e.IncompleteInput)) + //{ + // // TODO: Start a new line! + // continue; + //} + + return completedInput; + + default: + if (IsCtrlC(keyInfo)) + { + throw new PipelineStoppedException(); + } + + // Normal character input + if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar)) + { + currentCompletion = null; + + currentCursorIndex = + InsertInput( + inputLine, + promptStartCol, + promptStartRow, + keyInfo.KeyChar.ToString(), // TODO: Determine whether this should take culture into account + currentCursorIndex, + finalCursorIndex: currentCursorIndex + 1); + } + + continue; + } + } + } + catch (OperationCanceledException) + { + // We've broken out of the loop + } + finally + { + Console.TreatControlCAsInput = false; + } + + // If we break out of the loop without returning (because of the Enter key) + // then the readline has been aborted in some way and we should return nothing + return null; + } + + private static bool IsCtrlC(ConsoleKeyInfo keyInfo) + { + if ((int)keyInfo.Key == 3) + { + return true; + } + + return keyInfo.Key == ConsoleKey.C + && (keyInfo.Modifiers & ConsoleModifiers.Control) != 0 + && (keyInfo.Modifiers & ConsoleModifiers.Alt) == 0; + } + + public SecureString ReadSecureLine(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public bool TryOverrideIdleHandler(Action idleHandler) + { + return true; + } + + public bool TryOverrideReadKey(Func readKeyOverride) + { + _readKeyFunc = readKeyOverride; + return true; + } + + private ConsoleKeyInfo ReadKey(bool displayKeyInConsole, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + return _readKeyFunc(!displayKeyInConsole); + } + finally + { + cancellationToken.ThrowIfCancellationRequested(); + } + } + private static int InsertInput( + StringBuilder inputLine, + int promptStartCol, + int promptStartRow, + string insertedInput, + int cursorIndex, + int insertIndex = -1, + int replaceLength = 0, + int finalCursorIndex = -1) + { + int consoleWidth = Console.WindowWidth; + int previousInputLength = inputLine.Length; + + if (insertIndex == -1) + { + insertIndex = cursorIndex; + } + + // Move the cursor to the new insertion point + MoveCursorToIndex( + promptStartCol, + promptStartRow, + consoleWidth, + insertIndex); + + // Edit the input string based on the insertion + if (insertIndex < inputLine.Length) + { + if (replaceLength > 0) + { + inputLine.Remove(insertIndex, replaceLength); + } + + inputLine.Insert(insertIndex, insertedInput); + } + else + { + inputLine.Append(insertedInput); + } + + // Re-render affected section + Console.Write( + inputLine.ToString( + insertIndex, + inputLine.Length - insertIndex)); + + if (inputLine.Length < previousInputLength) + { + Console.Write( + new string( + ' ', + previousInputLength - inputLine.Length)); + } + + // Automatically set the final cursor position to the end + // of the new input string. This is needed if the previous + // input string is longer than the new one and needed to have + // its old contents overwritten. This will position the cursor + // back at the end of the new text + if (finalCursorIndex == -1 && inputLine.Length < previousInputLength) + { + finalCursorIndex = inputLine.Length; + } + + if (finalCursorIndex > -1) + { + // Move the cursor to the final position + return MoveCursorToIndex( + promptStartCol, + promptStartRow, + consoleWidth, + finalCursorIndex); + } + else + { + return inputLine.Length; + } + } + + private static int MoveCursorToIndex( + int promptStartCol, + int promptStartRow, + int consoleWidth, + int newCursorIndex) + { + CalculateCursorFromIndex( + promptStartCol, + promptStartRow, + consoleWidth, + newCursorIndex, + out int newCursorCol, + out int newCursorRow); + + Console.SetCursorPosition(newCursorCol, newCursorRow); + + return newCursorIndex; + } + private static void CalculateCursorFromIndex( + int promptStartCol, + int promptStartRow, + int consoleWidth, + int inputIndex, + out int cursorCol, + out int cursorRow) + { + cursorCol = promptStartCol + inputIndex; + cursorRow = promptStartRow + cursorCol / consoleWidth; + cursorCol = cursorCol % consoleWidth; + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs similarity index 99% rename from src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs rename to src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs index 55174610c..ff1ff6540 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs @@ -1,20 +1,20 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Text; -using System.Threading; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Language; using System.Security; +using System.Text; +using System.Threading; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { using System; - internal class ConsoleReadLine : IReadLine + internal class PsrlReadLine : IReadLine { private readonly PSReadLineProxy _psrlProxy; @@ -24,7 +24,7 @@ internal class ConsoleReadLine : IReadLine #region Constructors - public ConsoleReadLine( + public PsrlReadLine( PSReadLineProxy psrlProxy, PsesInternalHost psesHost, EngineIntrinsics engineIntrinsics) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 5893b690a..a0eafa43a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -628,7 +628,7 @@ private static PowerShell CreatePowerShellForRunspace(Runspace runspace) if (hostStartupInfo.ConsoleReplEnabled && !hostStartupInfo.UsesLegacyReadLine) { var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, pwsh); - var readLine = new ConsoleReadLine(psrlProxy, this, engineIntrinsics); + var readLine = new PsrlReadLine(psrlProxy, this, engineIntrinsics); readLine.TryOverrideReadKey(ReadKey); readLine.TryOverrideIdleHandler(OnPowerShellIdle); readLineProvider.OverrideReadLine(readLine); From ee874e473fa8cfb0e31e09ccecbaa7b0d28957ff Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 12 Oct 2021 16:21:23 -0700 Subject: [PATCH 145/176] Add common secure string read functionality --- .../PowerShell/Console/LegacyReadLine.cs | 33 +- .../PowerShell/Console/PsrlReadLine.cs | 568 +----------------- .../PowerShell/Console/TerminalReadLine.cs | 114 ++++ 3 files changed, 129 insertions(+), 586 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs index 419c7387d..42cd060d7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs @@ -16,7 +16,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { using System; - internal class LegacyReadLine : IReadLine + internal class LegacyReadLine : TerminalReadLine { private readonly PsesInternalHost _psesHost; @@ -32,7 +32,7 @@ public LegacyReadLine( _debugContext = debugContext; } - public string ReadLine(CancellationToken cancellationToken) + public override string ReadLine(CancellationToken cancellationToken) { // TODO: Is inputBeforeCompletion used? string inputBeforeCompletion = null; @@ -59,7 +59,7 @@ public string ReadLine(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { - ConsoleKeyInfo keyInfo = ReadKey(displayKeyInConsole: false, cancellationToken); + ConsoleKeyInfo keyInfo = ReadKey(cancellationToken); // Do final position calculation after the key has been pressed // because the window could have been resized before then @@ -383,46 +383,31 @@ public string ReadLine(CancellationToken cancellationToken) return null; } - private static bool IsCtrlC(ConsoleKeyInfo keyInfo) - { - if ((int)keyInfo.Key == 3) - { - return true; - } - - return keyInfo.Key == ConsoleKey.C - && (keyInfo.Modifiers & ConsoleModifiers.Control) != 0 - && (keyInfo.Modifiers & ConsoleModifiers.Alt) == 0; - } - - public SecureString ReadSecureLine(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public bool TryOverrideIdleHandler(Action idleHandler) + public override bool TryOverrideIdleHandler(Action idleHandler) { return true; } - public bool TryOverrideReadKey(Func readKeyOverride) + public override bool TryOverrideReadKey(Func readKeyOverride) { _readKeyFunc = readKeyOverride; return true; } - private ConsoleKeyInfo ReadKey(bool displayKeyInConsole, CancellationToken cancellationToken) + protected override ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); try { - return _readKeyFunc(!displayKeyInConsole); + // intercept = false means we display the key in the console + return _readKeyFunc(/* intercept */ false); } finally { cancellationToken.ThrowIfCancellationRequested(); } } + private static int InsertInput( StringBuilder inputLine, int promptStartCol, diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs index ff1ff6540..9a8e4da39 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs @@ -14,7 +14,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { using System; - internal class PsrlReadLine : IReadLine + internal class PsrlReadLine : TerminalReadLine { private readonly PSReadLineProxy _psrlProxy; @@ -38,594 +38,38 @@ public PsrlReadLine( #region Public Methods - public string ReadLine(CancellationToken cancellationToken) + public override string ReadLine(CancellationToken cancellationToken) { return _psesHost.InvokeDelegate(representation: "ReadLine", new ExecutionOptions { MustRunInForeground = true }, InvokePSReadLine, cancellationToken); } - public bool TryOverrideReadKey(Func readKeyFunc) + public override bool TryOverrideReadKey(Func readKeyFunc) { _psrlProxy.OverrideReadKey(readKeyFunc); return true; } - public bool TryOverrideIdleHandler(Action idleHandler) + public override bool TryOverrideIdleHandler(Action idleHandler) { _psrlProxy.OverrideIdleHandler(idleHandler); return true; } - public SecureString ReadSecureLine(CancellationToken cancellationToken) + protected override ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) { - SecureString secureString = new SecureString(); - - // TODO: Are these values used? - int initialPromptRow = ConsoleProxy.GetCursorTop(cancellationToken); - int initialPromptCol = ConsoleProxy.GetCursorLeft(cancellationToken); - int previousInputLength = 0; - - Console.TreatControlCAsInput = true; - - try - { - while (!cancellationToken.IsCancellationRequested) - { - ConsoleKeyInfo keyInfo = ReadKey(cancellationToken); - - if ((int)keyInfo.Key == 3 || - keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) - { - throw new PipelineStoppedException(); - } - if (keyInfo.Key == ConsoleKey.Enter) - { - // Break to return the completed string - break; - } - if (keyInfo.Key == ConsoleKey.Tab) - { - continue; - } - if (keyInfo.Key == ConsoleKey.Backspace) - { - if (secureString.Length > 0) - { - secureString.RemoveAt(secureString.Length - 1); - } - } - else if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar)) - { - secureString.AppendChar(keyInfo.KeyChar); - } - - // Re-render the secure string characters - int currentInputLength = secureString.Length; - int consoleWidth = Console.WindowWidth; - - if (currentInputLength > previousInputLength) - { - Console.Write('*'); - } - else if (previousInputLength > 0 && currentInputLength < previousInputLength) - { - int row = ConsoleProxy.GetCursorTop(cancellationToken); - int col = ConsoleProxy.GetCursorLeft(cancellationToken); - - // Back up the cursor before clearing the character - col--; - if (col < 0) - { - col = consoleWidth - 1; - row--; - } - - Console.SetCursorPosition(col, row); - Console.Write(' '); - Console.SetCursorPosition(col, row); - } - - previousInputLength = currentInputLength; - } - } - finally - { - Console.TreatControlCAsInput = false; - } - - return secureString; + return ConsoleProxy.ReadKey(intercept: true, cancellationToken); } #endregion #region Private Methods - private static ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) - { - return ConsoleProxy.ReadKey(intercept: true, cancellationToken); - } - private string InvokePSReadLine(CancellationToken cancellationToken) { EngineIntrinsics engineIntrinsics = _psesHost.IsRunspacePushed ? null : _engineIntrinsics; return _psrlProxy.ReadLine(_psesHost.Runspace, engineIntrinsics, cancellationToken, /* lastExecutionStatus */ null); } - /// - /// Invokes a custom ReadLine method that is similar to but more basic than PSReadLine. - /// This method should be used when PSReadLine is disabled, either by user settings or - /// unsupported PowerShell versions. - /// - /// - /// Indicates whether ReadLine should act like a command line. - /// - /// - /// The cancellation token that will be checked prior to completing the returned task. - /// - /// - /// A task object representing the asynchronus operation. The Result property on - /// the task object returns the user input string. - /// - internal string InvokeLegacyReadLine(bool isCommandLine, CancellationToken cancellationToken) - { - string inputAfterCompletion = null; - CommandCompletion currentCompletion = null; - - int historyIndex = -1; - IReadOnlyList currentHistory = null; - - StringBuilder inputLine = new StringBuilder(); - - int initialCursorCol = ConsoleProxy.GetCursorLeft(cancellationToken); - int initialCursorRow = ConsoleProxy.GetCursorTop(cancellationToken); - - // TODO: Are these used? - int initialWindowLeft = Console.WindowLeft; - int initialWindowTop = Console.WindowTop; - - int currentCursorIndex = 0; - - Console.TreatControlCAsInput = true; - - try - { - while (!cancellationToken.IsCancellationRequested) - { - ConsoleKeyInfo keyInfo = ReadKey(cancellationToken); - - // Do final position calculation after the key has been pressed - // because the window could have been resized before then - int promptStartCol = initialCursorCol; - int promptStartRow = initialCursorRow; - int consoleWidth = Console.WindowWidth; - - if ((int)keyInfo.Key == 3 || - keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) - { - throw new PipelineStoppedException(); - } - else if (keyInfo.Key == ConsoleKey.Tab && isCommandLine) - { - /* - if (currentCompletion == null) - { - inputBeforeCompletion = inputLine.ToString(); - inputAfterCompletion = null; - - // TODO: This logic should be moved to AstOperations or similar! - - if (this.powerShellContext.IsDebuggerStopped) - { - PSCommand command = new PSCommand(); - command.AddCommand("TabExpansion2"); - command.AddParameter("InputScript", inputBeforeCompletion); - command.AddParameter("CursorColumn", currentCursorIndex); - command.AddParameter("Options", null); - - var results = await this.powerShellContext - .ExecuteCommandAsync(command, sendOutputToHost: false, sendErrorToHost: false) - .ConfigureAwait(false); - - currentCompletion = results.FirstOrDefault(); - } - else - { - */ - /* - using (PowerShell powerShell = PowerShell.Create()) - { - powerShell.Runspace = _ - currentCompletion = - CommandCompletion.CompleteInput( - inputBeforeCompletion, - currentCursorIndex, - null, - powerShell); - - if (currentCompletion.CompletionMatches.Count > 0) - { - int replacementEndIndex = - currentCompletion.ReplacementIndex + - currentCompletion.ReplacementLength; - - inputAfterCompletion = - inputLine.ToString( - replacementEndIndex, - inputLine.Length - replacementEndIndex); - } - else - { - currentCompletion = null; - } - } - } - */ - - CompletionResult completion = - currentCompletion?.GetNextResult( - !keyInfo.Modifiers.HasFlag(ConsoleModifiers.Shift)); - - if (completion != null) - { - currentCursorIndex = - InsertInput( - inputLine, - promptStartCol, - promptStartRow, - $"{completion.CompletionText}{inputAfterCompletion}", - currentCursorIndex, - insertIndex: currentCompletion.ReplacementIndex, - replaceLength: inputLine.Length - currentCompletion.ReplacementIndex, - finalCursorIndex: currentCompletion.ReplacementIndex + completion.CompletionText.Length); - } - } - else if (keyInfo.Key == ConsoleKey.LeftArrow) - { - currentCompletion = null; - - if (currentCursorIndex > 0) - { - currentCursorIndex = - MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - currentCursorIndex - 1); - } - } - else if (keyInfo.Key == ConsoleKey.Home) - { - currentCompletion = null; - - currentCursorIndex = - MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - 0); - } - else if (keyInfo.Key == ConsoleKey.RightArrow) - { - currentCompletion = null; - - if (currentCursorIndex < inputLine.Length) - { - currentCursorIndex = - MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - currentCursorIndex + 1); - } - } - else if (keyInfo.Key == ConsoleKey.End) - { - currentCompletion = null; - - currentCursorIndex = - MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - inputLine.Length); - } - else if (keyInfo.Key == ConsoleKey.UpArrow && isCommandLine) - { - currentCompletion = null; - - // TODO: Ctrl+Up should allow navigation in multi-line input - - if (currentHistory == null) - { - historyIndex = -1; - - PSCommand command = new PSCommand().AddCommand("Get-History"); - - currentHistory = _psesHost.InvokePSCommand( - command, - PowerShellExecutionOptions.Default, - cancellationToken); - - if (currentHistory != null) - { - historyIndex = currentHistory.Count; - } - } - - if (currentHistory != null && currentHistory.Count > 0 && historyIndex > 0) - { - historyIndex--; - - currentCursorIndex = - InsertInput( - inputLine, - promptStartCol, - promptStartRow, - (string)currentHistory[historyIndex].Properties["CommandLine"].Value, - currentCursorIndex, - insertIndex: 0, - replaceLength: inputLine.Length); - } - } - else if (keyInfo.Key == ConsoleKey.DownArrow && isCommandLine) - { - currentCompletion = null; - - // The down arrow shouldn't cause history to be loaded, - // it's only for navigating an active history array - - if (historyIndex > -1 && historyIndex < currentHistory.Count && - currentHistory != null && currentHistory.Count > 0) - { - historyIndex++; - - if (historyIndex < currentHistory.Count) - { - currentCursorIndex = - InsertInput( - inputLine, - promptStartCol, - promptStartRow, - (string)currentHistory[historyIndex].Properties["CommandLine"].Value, - currentCursorIndex, - insertIndex: 0, - replaceLength: inputLine.Length); - } - else if (historyIndex == currentHistory.Count) - { - currentCursorIndex = - InsertInput( - inputLine, - promptStartCol, - promptStartRow, - string.Empty, - currentCursorIndex, - insertIndex: 0, - replaceLength: inputLine.Length); - } - } - } - else if (keyInfo.Key == ConsoleKey.Escape) - { - currentCompletion = null; - historyIndex = currentHistory != null ? currentHistory.Count : -1; - - currentCursorIndex = - InsertInput( - inputLine, - promptStartCol, - promptStartRow, - string.Empty, - currentCursorIndex, - insertIndex: 0, - replaceLength: inputLine.Length); - } - else if (keyInfo.Key == ConsoleKey.Backspace) - { - currentCompletion = null; - - if (currentCursorIndex > 0) - { - currentCursorIndex = - InsertInput( - inputLine, - promptStartCol, - promptStartRow, - string.Empty, - currentCursorIndex, - insertIndex: currentCursorIndex - 1, - replaceLength: 1, - finalCursorIndex: currentCursorIndex - 1); - } - } - else if (keyInfo.Key == ConsoleKey.Delete) - { - currentCompletion = null; - - if (currentCursorIndex < inputLine.Length) - { - currentCursorIndex = - InsertInput( - inputLine, - promptStartCol, - promptStartRow, - string.Empty, - currentCursorIndex, - replaceLength: 1, - finalCursorIndex: currentCursorIndex); - } - } - else if (keyInfo.Key == ConsoleKey.Enter) - { - string completedInput = inputLine.ToString(); - currentCompletion = null; - currentHistory = null; - - //if ((keyInfo.Modifiers & ConsoleModifiers.Shift) == ConsoleModifiers.Shift) - //{ - // // TODO: Start a new line! - // continue; - //} - - Parser.ParseInput( - completedInput, - out Token[] tokens, - out ParseError[] parseErrors); - - //if (parseErrors.Any(e => e.IncompleteInput)) - //{ - // // TODO: Start a new line! - // continue; - //} - - return completedInput; - } - else if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar)) - { - // Normal character input - currentCompletion = null; - - currentCursorIndex = - InsertInput( - inputLine, - promptStartCol, - promptStartRow, - keyInfo.KeyChar.ToString(), // TODO: Determine whether this should take culture into account - currentCursorIndex, - finalCursorIndex: currentCursorIndex + 1); - } - } - } - finally - { - Console.TreatControlCAsInput = false; - } - - return null; - } - - // TODO: Is this used? - private static int CalculateIndexFromCursor( - int promptStartCol, - int promptStartRow, - int consoleWidth) - { - return - ((ConsoleProxy.GetCursorTop() - promptStartRow) * consoleWidth) + - ConsoleProxy.GetCursorLeft() - promptStartCol; - } - - private static void CalculateCursorFromIndex( - int promptStartCol, - int promptStartRow, - int consoleWidth, - int inputIndex, - out int cursorCol, - out int cursorRow) - { - cursorCol = promptStartCol + inputIndex; - cursorRow = promptStartRow + cursorCol / consoleWidth; - cursorCol = cursorCol % consoleWidth; - } - - private static int InsertInput( - StringBuilder inputLine, - int promptStartCol, - int promptStartRow, - string insertedInput, - int cursorIndex, - int insertIndex = -1, - int replaceLength = 0, - int finalCursorIndex = -1) - { - int consoleWidth = Console.WindowWidth; - int previousInputLength = inputLine.Length; - - if (insertIndex == -1) - { - insertIndex = cursorIndex; - } - - // Move the cursor to the new insertion point - MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - insertIndex); - - // Edit the input string based on the insertion - if (insertIndex < inputLine.Length) - { - if (replaceLength > 0) - { - inputLine.Remove(insertIndex, replaceLength); - } - - inputLine.Insert(insertIndex, insertedInput); - } - else - { - inputLine.Append(insertedInput); - } - - // Re-render affected section - Console.Write( - inputLine.ToString( - insertIndex, - inputLine.Length - insertIndex)); - - if (inputLine.Length < previousInputLength) - { - Console.Write( - new string( - ' ', - previousInputLength - inputLine.Length)); - } - - // Automatically set the final cursor position to the end - // of the new input string. This is needed if the previous - // input string is longer than the new one and needed to have - // its old contents overwritten. This will position the cursor - // back at the end of the new text - if (finalCursorIndex == -1 && inputLine.Length < previousInputLength) - { - finalCursorIndex = inputLine.Length; - } - - if (finalCursorIndex > -1) - { - // Move the cursor to the final position - return - MoveCursorToIndex( - promptStartCol, - promptStartRow, - consoleWidth, - finalCursorIndex); - } - else - { - return inputLine.Length; - } - } - - private static int MoveCursorToIndex( - int promptStartCol, - int promptStartRow, - int consoleWidth, - int newCursorIndex) - { - CalculateCursorFromIndex( - promptStartCol, - promptStartRow, - consoleWidth, - newCursorIndex, - out int newCursorCol, - out int newCursorRow); - - Console.SetCursorPosition(newCursorCol, newCursorRow); - - return newCursorIndex; - } - #endregion } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs new file mode 100644 index 000000000..cd047e8e7 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Security; +using System.Threading; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console +{ + using System; + using System.Management.Automation; + + internal abstract class TerminalReadLine : IReadLine + { + public abstract string ReadLine(CancellationToken cancellationToken); + + public abstract bool TryOverrideIdleHandler(Action idleHandler); + + public abstract bool TryOverrideReadKey(Func readKeyOverride); + + protected abstract ConsoleKeyInfo ReadKey(CancellationToken cancellationToken); + + public SecureString ReadSecureLine(CancellationToken cancellationToken) + { + Console.TreatControlCAsInput = true; + int previousInputLength = 0; + SecureString secureString = new SecureString(); + try + { + bool enterPressed = false; + while (!enterPressed && !cancellationToken.IsCancellationRequested) + { + ConsoleKeyInfo keyInfo = ReadKey(cancellationToken); + + if (IsCtrlC(keyInfo)) + { + throw new PipelineStoppedException(); + } + + switch (keyInfo.Key) + { + case ConsoleKey.Enter: + // Break to return the completed string + enterPressed = true; + continue; + + case ConsoleKey.Tab: + break; + + case ConsoleKey.Backspace: + if (secureString.Length > 0) + { + secureString.RemoveAt(secureString.Length - 1); + } + break; + + default: + if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar)) + { + secureString.AppendChar(keyInfo.KeyChar); + } + break; + } + + // Re-render the secure string characters + int currentInputLength = secureString.Length; + int consoleWidth = Console.WindowWidth; + + if (currentInputLength > previousInputLength) + { + Console.Write('*'); + } + else if (previousInputLength > 0 && currentInputLength < previousInputLength) + { + int row = ConsoleProxy.GetCursorTop(cancellationToken); + int col = ConsoleProxy.GetCursorLeft(cancellationToken); + + // Back up the cursor before clearing the character + col--; + if (col < 0) + { + col = consoleWidth - 1; + row--; + } + + Console.SetCursorPosition(col, row); + Console.Write(' '); + Console.SetCursorPosition(col, row); + } + + previousInputLength = currentInputLength; + } + } + finally + { + Console.TreatControlCAsInput = false; + } + + return secureString; + } + + protected static bool IsCtrlC(ConsoleKeyInfo keyInfo) + { + if ((int)keyInfo.Key == 3) + { + return true; + } + + return keyInfo.Key == ConsoleKey.C + && (keyInfo.Modifiers & ConsoleModifiers.Control) != 0 + && (keyInfo.Modifiers & ConsoleModifiers.Alt) == 0; + } + + } +} From 20b3ed3448966a782f9bd9c95f95c1d192bee778 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 12 Oct 2021 16:34:32 -0700 Subject: [PATCH 146/176] Implement shared Ctrl-C test implementation --- .../PowerShell/Console/LegacyReadLine.cs | 12 +++++----- .../PowerShell/Console/TerminalReadLine.cs | 18 +++------------ .../PowerShell/Host/PsesInternalHost.cs | 4 +--- .../Utility/ConsoleKeyInfoExtensions.cs | 23 +++++++++++++++++++ 4 files changed, 33 insertions(+), 24 deletions(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Utility/ConsoleKeyInfoExtensions.cs diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs index 42cd060d7..58566a638 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs @@ -1,16 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; -using System.Management.Automation; -using System.Security; -using System.Text; -using System.Threading; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using System.Collections.Generic; using System.Linq; +using System.Management.Automation; using System.Management.Automation.Language; +using System.Text; +using System.Threading; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { @@ -345,7 +345,7 @@ public override string ReadLine(CancellationToken cancellationToken) return completedInput; default: - if (IsCtrlC(keyInfo)) + if (keyInfo.IsCtrlC()) { throw new PipelineStoppedException(); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs index cd047e8e7..31cc8a8fb 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs @@ -1,13 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; +using System.Management.Automation; using System.Security; using System.Threading; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { using System; - using System.Management.Automation; internal abstract class TerminalReadLine : IReadLine { @@ -31,7 +32,7 @@ public SecureString ReadSecureLine(CancellationToken cancellationToken) { ConsoleKeyInfo keyInfo = ReadKey(cancellationToken); - if (IsCtrlC(keyInfo)) + if (keyInfo.IsCtrlC()) { throw new PipelineStoppedException(); } @@ -97,18 +98,5 @@ public SecureString ReadSecureLine(CancellationToken cancellationToken) return secureString; } - - protected static bool IsCtrlC(ConsoleKeyInfo keyInfo) - { - if ((int)keyInfo.Key == 3) - { - return true; - } - - return keyInfo.Key == ConsoleKey.C - && (keyInfo.Modifiers & ConsoleModifiers.Control) != 0 - && (keyInfo.Modifiers & ConsoleModifiers.Alt) == 0; - } - } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index a0eafa43a..9e478ee39 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -747,9 +747,7 @@ private ConsoleKeyInfo ReadKey(bool intercept) private bool LastKeyWasCtrlC() { return _lastKey.HasValue - && _lastKey.Value.Key == ConsoleKey.C - && (_lastKey.Value.Modifiers & ConsoleModifiers.Control) != 0 - && (_lastKey.Value.Modifiers & ConsoleModifiers.Alt) == 0; + && _lastKey.Value.IsCtrlC(); } private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/ConsoleKeyInfoExtensions.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/ConsoleKeyInfoExtensions.cs new file mode 100644 index 000000000..24498772c --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/ConsoleKeyInfoExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility +{ + internal static class ConsoleKeyInfoExtensions + { + public static bool IsCtrlC(this ConsoleKeyInfo keyInfo) + { + if ((int)keyInfo.Key == 3) + { + return true; + } + + return keyInfo.Key == ConsoleKey.C + && (keyInfo.Modifiers & ConsoleModifiers.Control) != 0 + && (keyInfo.Modifiers & ConsoleModifiers.Shift) == 0 + && (keyInfo.Modifiers & ConsoleModifiers.Alt) == 0; + } + } +} From 8fbfca7747a8afa9cb79962caee2f1ada89e6f5a Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 12 Oct 2021 16:35:29 -0700 Subject: [PATCH 147/176] Avoid unused parser call --- .../Services/PowerShell/Console/LegacyReadLine.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs index 58566a638..6a86c4b79 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs @@ -330,12 +330,10 @@ public override string ReadLine(CancellationToken cancellationToken) // // TODO: Start a new line! // continue; //} - - Parser.ParseInput( - completedInput, - out Token[] tokens, - out ParseError[] parseErrors); - + //Parser.ParseInput( + // completedInput, + // out Token[] tokens, + // out ParseError[] parseErrors); //if (parseErrors.Any(e => e.IncompleteInput)) //{ // // TODO: Start a new line! From 7337696e0ce2f7da7151b720faa93e36149cd7d1 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 12 Oct 2021 16:48:39 -0700 Subject: [PATCH 148/176] Add idle support for legacy readline --- .../PowerShell/Console/LegacyReadLine.cs | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs index 6a86c4b79..a980683e7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs @@ -15,6 +15,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { using System; + using System.Threading.Tasks; internal class LegacyReadLine : TerminalReadLine { @@ -22,14 +23,19 @@ internal class LegacyReadLine : TerminalReadLine private readonly IPowerShellDebugContext _debugContext; + private readonly Task[] _readKeyTasks; + private Func _readKeyFunc; + private Action _onIdleAction; + public LegacyReadLine( PsesInternalHost psesHost, IPowerShellDebugContext debugContext) { _psesHost = psesHost; _debugContext = debugContext; + _readKeyTasks = new Task[2]; } public override string ReadLine(CancellationToken cancellationToken) @@ -383,6 +389,7 @@ public override string ReadLine(CancellationToken cancellationToken) public override bool TryOverrideIdleHandler(Action idleHandler) { + _onIdleAction = idleHandler; return true; } @@ -397,8 +404,9 @@ protected override ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) cancellationToken.ThrowIfCancellationRequested(); try { - // intercept = false means we display the key in the console - return _readKeyFunc(/* intercept */ false); + return _onIdleAction is null + ? InvokeReadKeyFunc() + : ReadKeyWithIdleSupport(cancellationToken); } finally { @@ -406,6 +414,37 @@ protected override ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) } } + private ConsoleKeyInfo ReadKeyWithIdleSupport(CancellationToken cancellationToken) + { + // We run the readkey function on another thread so we can run an idle handler + Task readKeyTask = Task.Run(InvokeReadKeyFunc); + + _readKeyTasks[0] = readKeyTask; + _readKeyTasks[1] = Task.Delay(millisecondsDelay: 300, cancellationToken); + + while (true) + { + switch (Task.WaitAny(_readKeyTasks, cancellationToken)) + { + // ReadKey returned + case 0: + return readKeyTask.Result; + + // The idle timed out + case 1: + _onIdleAction(); + _readKeyTasks[1] = Task.Delay(millisecondsDelay: 300, cancellationToken); + continue; + } + } + } + + private ConsoleKeyInfo InvokeReadKeyFunc() + { + // intercept = false means we display the key in the console + return _readKeyFunc(/* intercept */ false); + } + private static int InsertInput( StringBuilder inputLine, int promptStartCol, From 21401203edd347e19f206010d7bee2bf442f5050 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 12 Oct 2021 16:52:45 -0700 Subject: [PATCH 149/176] Add comment about unimplemented line continuation support --- .../Services/PowerShell/Console/LegacyReadLine.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs index a980683e7..cd39682a7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs @@ -8,14 +8,13 @@ using System.Collections.Generic; using System.Linq; using System.Management.Automation; -using System.Management.Automation.Language; using System.Text; using System.Threading; +using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console { using System; - using System.Threading.Tasks; internal class LegacyReadLine : TerminalReadLine { @@ -331,6 +330,10 @@ public override string ReadLine(CancellationToken cancellationToken) currentCompletion = null; currentHistory = null; + // TODO: Add line continuation support: + // - When shift+enter is pressed, or + // - When the parse indicates incomplete input + //if ((keyInfo.Modifiers & ConsoleModifiers.Shift) == ConsoleModifiers.Shift) //{ // // TODO: Start a new line! From 9787b58de0d5ef22506d4a80b9fd814cb9363ed6 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 12 Oct 2021 17:03:20 -0700 Subject: [PATCH 150/176] Hook up legacy readline support in the host --- .../PowerShell/Console/LegacyReadLine.cs | 8 ++---- .../PowerShell/Host/PsesInternalHost.cs | 26 ++++++++++++++++--- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs index cd39682a7..b6dfe9150 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs @@ -20,8 +20,6 @@ internal class LegacyReadLine : TerminalReadLine { private readonly PsesInternalHost _psesHost; - private readonly IPowerShellDebugContext _debugContext; - private readonly Task[] _readKeyTasks; private Func _readKeyFunc; @@ -29,11 +27,9 @@ internal class LegacyReadLine : TerminalReadLine private Action _onIdleAction; public LegacyReadLine( - PsesInternalHost psesHost, - IPowerShellDebugContext debugContext) + PsesInternalHost psesHost) { _psesHost = psesHost; - _debugContext = debugContext; _readKeyTasks = new Task[2]; } @@ -86,7 +82,7 @@ public override string ReadLine(CancellationToken cancellationToken) // TODO: This logic should be moved to AstOperations or similar! - if (_debugContext.IsStopped) + if (_psesHost.DebugContext.IsStopped) { PSCommand command = new PSCommand() .AddCommand("TabExpansion2") diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 9e478ee39..da7a8ab89 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -625,10 +625,14 @@ private static PowerShell CreatePowerShellForRunspace(Runspace runspace) var engineIntrinsics = (EngineIntrinsics)runspace.SessionStateProxy.GetVariable("ExecutionContext"); - if (hostStartupInfo.ConsoleReplEnabled && !hostStartupInfo.UsesLegacyReadLine) + if (hostStartupInfo.ConsoleReplEnabled) { - var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, pwsh); - var readLine = new PsrlReadLine(psrlProxy, this, engineIntrinsics); + // If we've been configured to use it, or if we can't load PSReadLine, use the legacy readline + if (hostStartupInfo.UsesLegacyReadLine || !TryLoadPSReadLine(pwsh, engineIntrinsics, out IReadLine readLine)) + { + readLine = new LegacyReadLine(this); + } + readLine.TryOverrideReadKey(ReadKey); readLine.TryOverrideIdleHandler(OnPowerShellIdle); readLineProvider.OverrideReadLine(readLine); @@ -823,6 +827,22 @@ private Task PopOrReinitializeRunspaceAsync() CancellationToken.None); } + private bool TryLoadPSReadLine(PowerShell pwsh, EngineIntrinsics engineIntrinsics, out IReadLine psrlReadLine) + { + psrlReadLine = null; + try + { + var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, pwsh); + psrlReadLine = new PsrlReadLine(psrlProxy, this, engineIntrinsics); + return true; + } + catch (Exception e) + { + _logger.LogError(e, "Unable to load PSReadLine. Will fall back to legacy readline implementation."); + return false; + } + } + private record RunspaceFrame( Runspace Runspace, RunspaceInfo RunspaceInfo); From 30f58b06653226bc87f86579179ac3d5395f17aa Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 12 Oct 2021 17:04:54 -0700 Subject: [PATCH 151/176] Remove stale TODOs --- .../Services/PowerShell/Console/LegacyReadLine.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs index b6dfe9150..883533d3f 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs @@ -35,7 +35,6 @@ public LegacyReadLine( public override string ReadLine(CancellationToken cancellationToken) { - // TODO: Is inputBeforeCompletion used? string inputBeforeCompletion = null; string inputAfterCompletion = null; CommandCompletion currentCompletion = null; @@ -48,10 +47,6 @@ public override string ReadLine(CancellationToken cancellationToken) int initialCursorCol = ConsoleProxy.GetCursorLeft(cancellationToken); int initialCursorRow = ConsoleProxy.GetCursorTop(cancellationToken); - // TODO: Are these used? - int initialWindowLeft = Console.WindowLeft; - int initialWindowTop = Console.WindowTop; - int currentCursorIndex = 0; Console.TreatControlCAsInput = true; From 20bd69bc12f5614fe8168c38d222367ee6cfd212 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 12 Oct 2021 17:05:15 -0700 Subject: [PATCH 152/176] Remove commented out code --- .../Services/PowerShell/Console/LegacyReadLine.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs index 883533d3f..33500b4fa 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs @@ -63,10 +63,6 @@ public override string ReadLine(CancellationToken cancellationToken) int promptStartRow = initialCursorRow; int consoleWidth = Console.WindowWidth; - //case ConsoleKey.C when ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0): - // throw new PipelineStoppedException(); - - switch (keyInfo.Key) { case ConsoleKey.Tab: From b68c7acab541d400d1f0011af7468c6f087ab618 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 13 Oct 2021 12:25:50 -0700 Subject: [PATCH 153/176] Remove unused usings --- .../Services/PowerShell/Console/LegacyReadLine.cs | 1 - .../Services/PowerShell/Console/PsrlReadLine.cs | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs index 33500b4fa..51d494e35 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs index 9a8e4da39..0feeb58b7 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs @@ -3,11 +3,7 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using System.Collections.Generic; using System.Management.Automation; -using System.Management.Automation.Language; -using System.Security; -using System.Text; using System.Threading; namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console From 43bddb1d80c63f2a5c06a9a56d0188e9c69f3e88 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 14 Oct 2021 15:13:37 -0700 Subject: [PATCH 154/176] Remove unneeded readline interface methods --- .../Services/PowerShell/Console/IReadLine.cs | 4 ---- .../PowerShell/Console/LegacyReadLine.cs | 24 +++++++------------ .../PowerShell/Console/PsrlReadLine.cs | 18 ++++---------- .../PowerShell/Console/TerminalReadLine.cs | 4 ---- .../PowerShell/Host/PsesInternalHost.cs | 6 ++--- 5 files changed, 15 insertions(+), 41 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs index 3bb0bede8..1233df7b0 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs @@ -12,9 +12,5 @@ internal interface IReadLine string ReadLine(CancellationToken cancellationToken); SecureString ReadSecureLine(CancellationToken cancellationToken); - - bool TryOverrideReadKey(Func readKeyOverride); - - bool TryOverrideIdleHandler(Action idleHandler); } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs index 51d494e35..a29fe17f2 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs @@ -21,15 +21,19 @@ internal class LegacyReadLine : TerminalReadLine private readonly Task[] _readKeyTasks; - private Func _readKeyFunc; + private readonly Func _readKeyFunc; - private Action _onIdleAction; + private readonly Action _onIdleAction; public LegacyReadLine( - PsesInternalHost psesHost) + PsesInternalHost psesHost, + Func readKeyFunc, + Action onIdleAction) { _psesHost = psesHost; _readKeyTasks = new Task[2]; + _readKeyFunc = readKeyFunc; + _onIdleAction = onIdleAction; } public override string ReadLine(CancellationToken cancellationToken) @@ -376,18 +380,6 @@ public override string ReadLine(CancellationToken cancellationToken) return null; } - public override bool TryOverrideIdleHandler(Action idleHandler) - { - _onIdleAction = idleHandler; - return true; - } - - public override bool TryOverrideReadKey(Func readKeyOverride) - { - _readKeyFunc = readKeyOverride; - return true; - } - protected override ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -421,7 +413,7 @@ private ConsoleKeyInfo ReadKeyWithIdleSupport(CancellationToken cancellationToke // The idle timed out case 1: - _onIdleAction(); + _onIdleAction(cancellationToken); _readKeyTasks[1] = Task.Delay(millisecondsDelay: 300, cancellationToken); continue; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs index 0feeb58b7..1bee46f76 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs @@ -23,11 +23,15 @@ internal class PsrlReadLine : TerminalReadLine public PsrlReadLine( PSReadLineProxy psrlProxy, PsesInternalHost psesHost, - EngineIntrinsics engineIntrinsics) + EngineIntrinsics engineIntrinsics, + Func readKeyFunc, + Action onIdleAction) { _psrlProxy = psrlProxy; _psesHost = psesHost; _engineIntrinsics = engineIntrinsics; + _psrlProxy.OverrideReadKey(readKeyFunc); + _psrlProxy.OverrideIdleHandler(onIdleAction); } #endregion @@ -39,18 +43,6 @@ public override string ReadLine(CancellationToken cancellationToken) return _psesHost.InvokeDelegate(representation: "ReadLine", new ExecutionOptions { MustRunInForeground = true }, InvokePSReadLine, cancellationToken); } - public override bool TryOverrideReadKey(Func readKeyFunc) - { - _psrlProxy.OverrideReadKey(readKeyFunc); - return true; - } - - public override bool TryOverrideIdleHandler(Action idleHandler) - { - _psrlProxy.OverrideIdleHandler(idleHandler); - return true; - } - protected override ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) { return ConsoleProxy.ReadKey(intercept: true, cancellationToken); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs index 31cc8a8fb..1e0435b6c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs @@ -14,10 +14,6 @@ internal abstract class TerminalReadLine : IReadLine { public abstract string ReadLine(CancellationToken cancellationToken); - public abstract bool TryOverrideIdleHandler(Action idleHandler); - - public abstract bool TryOverrideReadKey(Func readKeyOverride); - protected abstract ConsoleKeyInfo ReadKey(CancellationToken cancellationToken); public SecureString ReadSecureLine(CancellationToken cancellationToken) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index da7a8ab89..c3ebd9bb8 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -630,11 +630,9 @@ private static PowerShell CreatePowerShellForRunspace(Runspace runspace) // If we've been configured to use it, or if we can't load PSReadLine, use the legacy readline if (hostStartupInfo.UsesLegacyReadLine || !TryLoadPSReadLine(pwsh, engineIntrinsics, out IReadLine readLine)) { - readLine = new LegacyReadLine(this); + readLine = new LegacyReadLine(this, ReadKey, OnPowerShellIdle); } - readLine.TryOverrideReadKey(ReadKey); - readLine.TryOverrideIdleHandler(OnPowerShellIdle); readLineProvider.OverrideReadLine(readLine); System.Console.CancelKeyPress += OnCancelKeyPress; System.Console.InputEncoding = Encoding.UTF8; @@ -833,7 +831,7 @@ private bool TryLoadPSReadLine(PowerShell pwsh, EngineIntrinsics engineIntrinsic try { var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, pwsh); - psrlReadLine = new PsrlReadLine(psrlProxy, this, engineIntrinsics); + psrlReadLine = new PsrlReadLine(psrlProxy, this, engineIntrinsics, ReadKey, OnPowerShellIdle); return true; } catch (Exception e) From ff77a6899af251e9384bb3b4e92647d63415126d Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 14 Oct 2021 15:26:05 -0700 Subject: [PATCH 155/176] Update comment --- .../Services/PowerShell/Console/TerminalReadLine.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs index 1e0435b6c..474c23f1c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/TerminalReadLine.cs @@ -36,7 +36,8 @@ public SecureString ReadSecureLine(CancellationToken cancellationToken) switch (keyInfo.Key) { case ConsoleKey.Enter: - // Break to return the completed string + // Stop the while loop so we can realign the cursor + // and then return the entered string enterPressed = true; continue; From dfee772a883987d579bb60ec0672b5b0dae44eba Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 14 Oct 2021 09:28:30 -0700 Subject: [PATCH 156/176] Initial test build --- integrated.sln | 153 ------------------ .../Handlers/ConfigurationDoneHandler.cs | 29 +--- .../Utility/StringEscaping.cs | 37 +++++ .../LanguageServerProtocolMessageTests.cs | 2 + .../Console/ChoicePromptHandlerTests.cs | 3 +- .../Console/InputPromptHandlerTests.cs | 1 - .../Debugging/DebugServiceTests.cs | 21 ++- .../Language/LanguageServiceTests.cs | 2 + .../PowerShellContextFactory.cs | 3 +- .../Session/PathEscapingTests.cs | 32 +--- .../Session/PowerShellContextTests.cs | 3 +- .../Session/ScriptFileTests.cs | 2 +- .../Utility/AsyncLockTests.cs | 47 ------ .../Utility/AsyncQueueTests.cs | 92 ----------- 14 files changed, 71 insertions(+), 356 deletions(-) delete mode 100644 integrated.sln create mode 100644 src/PowerShellEditorServices/Utility/StringEscaping.cs delete mode 100644 test/PowerShellEditorServices.Test/Utility/AsyncLockTests.cs delete mode 100644 test/PowerShellEditorServices.Test/Utility/AsyncQueueTests.cs diff --git a/integrated.sln b/integrated.sln deleted file mode 100644 index 0b0680083..000000000 --- a/integrated.sln +++ /dev/null @@ -1,153 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26124.0 -MinimumVisualStudioVersion = 15.0.26124.0 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{31BB87E9-A4A1-4266-B150-CEACE7C424C4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices", "src\PowerShellEditorServices\PowerShellEditorServices.csproj", "{00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices.Hosting", "src\PowerShellEditorServices.Hosting\PowerShellEditorServices.Hosting.csproj", "{C6D0523A-B537-4115-ADB3-218E902684DA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices.VSCode", "src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj", "{6A33B29C-74FE-43C2-9207-CE51DC2ABF37}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Management.Automation", "..\PowerShell\src\System.Management.Automation\System.Management.Automation.csproj", "{1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSReadLine", "..\PSReadLine\PSReadLine\PSReadLine.csproj", "{DB03A789-7930-4BB2-B01E-65C2A754FC42}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine", "..\PSScriptAnalyzer\Engine\Engine.csproj", "{04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rules", "..\PSScriptAnalyzer\Rules\Rules.csproj", "{6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.Commands.Utility", "..\PowerShell\src\Microsoft.PowerShell.Commands.Utility\Microsoft.PowerShell.Commands.Utility.csproj", "{E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.ConsoleHost", "..\PowerShell\src\Microsoft.PowerShell.ConsoleHost\Microsoft.PowerShell.ConsoleHost.csproj", "{39FB7ECC-F839-474C-89CC-467E8A4ADB51}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|Any CPU.Build.0 = Debug|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|x64.ActiveCfg = Debug|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|x64.Build.0 = Debug|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|x86.ActiveCfg = Debug|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Debug|x86.Build.0 = Debug|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|Any CPU.ActiveCfg = Release|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|Any CPU.Build.0 = Release|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|x64.ActiveCfg = Release|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|x64.Build.0 = Release|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|x86.ActiveCfg = Release|Any CPU - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09}.Release|x86.Build.0 = Release|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|x64.ActiveCfg = Debug|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|x64.Build.0 = Debug|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|x86.ActiveCfg = Debug|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Debug|x86.Build.0 = Debug|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|Any CPU.Build.0 = Release|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|x64.ActiveCfg = Release|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|x64.Build.0 = Release|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|x86.ActiveCfg = Release|Any CPU - {C6D0523A-B537-4115-ADB3-218E902684DA}.Release|x86.Build.0 = Release|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|x64.ActiveCfg = Debug|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|x64.Build.0 = Debug|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|x86.ActiveCfg = Debug|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Debug|x86.Build.0 = Debug|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|Any CPU.Build.0 = Release|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|x64.ActiveCfg = Release|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|x64.Build.0 = Release|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|x86.ActiveCfg = Release|Any CPU - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37}.Release|x86.Build.0 = Release|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|x64.ActiveCfg = Debug|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|x64.Build.0 = Debug|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|x86.ActiveCfg = Debug|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Debug|x86.Build.0 = Debug|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|Any CPU.Build.0 = Release|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|x64.ActiveCfg = Release|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|x64.Build.0 = Release|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|x86.ActiveCfg = Release|Any CPU - {1C80C3F5-16DD-4385-8C2D-EE38DA2C5EA4}.Release|x86.Build.0 = Release|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|x64.ActiveCfg = Debug|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|x64.Build.0 = Debug|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|x86.ActiveCfg = Debug|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Debug|x86.Build.0 = Debug|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|Any CPU.Build.0 = Release|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|x64.ActiveCfg = Release|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|x64.Build.0 = Release|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|x86.ActiveCfg = Release|Any CPU - {DB03A789-7930-4BB2-B01E-65C2A754FC42}.Release|x86.Build.0 = Release|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|Any CPU.Build.0 = Debug|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|x64.ActiveCfg = Debug|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|x64.Build.0 = Debug|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|x86.ActiveCfg = Debug|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Debug|x86.Build.0 = Debug|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|Any CPU.ActiveCfg = Release|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|Any CPU.Build.0 = Release|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|x64.ActiveCfg = Release|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|x64.Build.0 = Release|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|x86.ActiveCfg = Release|Any CPU - {04D0D7FD-DC38-4BE0-BE9A-54DBC978F201}.Release|x86.Build.0 = Release|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|x64.ActiveCfg = Debug|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|x64.Build.0 = Debug|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|x86.ActiveCfg = Debug|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Debug|x86.Build.0 = Debug|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|Any CPU.Build.0 = Release|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x64.ActiveCfg = Release|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x64.Build.0 = Release|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x86.ActiveCfg = Release|Any CPU - {6F224847-AC0E-4901-B5BA-7DE81D8B3FBC}.Release|x86.Build.0 = Release|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|x64.ActiveCfg = Debug|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|x64.Build.0 = Debug|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|x86.ActiveCfg = Debug|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Debug|x86.Build.0 = Debug|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|Any CPU.Build.0 = Release|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x64.ActiveCfg = Release|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x64.Build.0 = Release|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x86.ActiveCfg = Release|Any CPU - {E64BB0B6-9D93-4B6D-9EFF-F669FD5BA842}.Release|x86.Build.0 = Release|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|Any CPU.Build.0 = Debug|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|x64.ActiveCfg = Debug|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|x64.Build.0 = Debug|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|x86.ActiveCfg = Debug|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Debug|x86.Build.0 = Debug|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|Any CPU.ActiveCfg = Release|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|Any CPU.Build.0 = Release|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|x64.ActiveCfg = Release|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|x64.Build.0 = Release|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|x86.ActiveCfg = Release|Any CPU - {39FB7ECC-F839-474C-89CC-467E8A4ADB51}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {00158E75-B6E0-43E4-98E9-BDAC8EEF4C09} = {31BB87E9-A4A1-4266-B150-CEACE7C424C4} - {C6D0523A-B537-4115-ADB3-218E902684DA} = {31BB87E9-A4A1-4266-B150-CEACE7C424C4} - {6A33B29C-74FE-43C2-9207-CE51DC2ABF37} = {31BB87E9-A4A1-4266-B150-CEACE7C424C4} - EndGlobalSection -EndGlobal diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 71cc5d3c5..7750585c0 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -158,17 +158,16 @@ private static PSCommand BuildPSCommandFromArguments(string command, IReadOnlyLi // We are forced to use a hack here so that we can reuse PowerShell's parameter binding var sb = new StringBuilder() - .Append("& '") - .Append(command.Replace("'", "''")) - .Append("'"); + .Append("& ") + .Append(StringEscaping.SingleQuoteAndEscape(command)); foreach (string arg in arguments) { sb.Append(' '); - if (ArgumentNeedsEscaping(arg)) + if (StringEscaping.PowerShellArgumentNeedsEscaping(arg)) { - sb.Append('\'').Append(arg.Replace("'", "''")).Append('\''); + sb.Append(StringEscaping.SingleQuoteAndEscape(arg)); } else { @@ -178,25 +177,5 @@ private static PSCommand BuildPSCommandFromArguments(string command, IReadOnlyLi return new PSCommand().AddScript(sb.ToString()); } - - private static bool ArgumentNeedsEscaping(string argument) - { - foreach (char c in argument) - { - switch (c) - { - case '\'': - case '"': - case '|': - case '&': - case ';': - case ':': - case char w when char.IsWhiteSpace(w): - return true; - } - } - - return false; - } } } diff --git a/src/PowerShellEditorServices/Utility/StringEscaping.cs b/src/PowerShellEditorServices/Utility/StringEscaping.cs new file mode 100644 index 000000000..5736a9aad --- /dev/null +++ b/src/PowerShellEditorServices/Utility/StringEscaping.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.EditorServices.Utility +{ + internal static class StringEscaping + { + public static StringBuilder SingleQuoteAndEscape(string s) + { + return new StringBuilder(s.Length) + .Append("'") + .Append(s.Replace("'", "''")) + .Append("'"); + } + + public static bool PowerShellArgumentNeedsEscaping(string argument) + { + foreach (char c in argument) + { + switch (c) + { + case '\'': + case '"': + case '|': + case '&': + case ';': + case ':': + case char w when char.IsWhiteSpace(w): + return true; + } + } + + return false; + } + } +} diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 276df5666..1661cebeb 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -24,6 +24,8 @@ using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services.Configuration; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.Template; namespace PowerShellEditorServices.Test.E2E { diff --git a/test/PowerShellEditorServices.Test/Console/ChoicePromptHandlerTests.cs b/test/PowerShellEditorServices.Test/Console/ChoicePromptHandlerTests.cs index a07303472..819be34f0 100644 --- a/test/PowerShellEditorServices.Test/Console/ChoicePromptHandlerTests.cs +++ b/test/PowerShellEditorServices.Test/Console/ChoicePromptHandlerTests.cs @@ -4,11 +4,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Xunit; namespace Microsoft.PowerShell.EditorServices.Test.Console { + /* public class ChoicePromptHandlerTests { private readonly ChoiceDetails[] Choices = @@ -121,5 +121,6 @@ protected override void ShowPrompt(PromptStyle promptStyle) this.TimesPrompted++; } } + */ } diff --git a/test/PowerShellEditorServices.Test/Console/InputPromptHandlerTests.cs b/test/PowerShellEditorServices.Test/Console/InputPromptHandlerTests.cs index abb0acabc..2aadb3658 100644 --- a/test/PowerShellEditorServices.Test/Console/InputPromptHandlerTests.cs +++ b/test/PowerShellEditorServices.Test/Console/InputPromptHandlerTests.cs @@ -8,7 +8,6 @@ using System; using System.Threading; using System.Security; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.PowerShell.EditorServices.Test.Console diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 42386192c..a01ddc20d 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -3,34 +3,36 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Management.Automation; using System.Threading; using System.Threading.Tasks; +using MediatR; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Test.Shared; -using Microsoft.PowerShell.EditorServices.Utility; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Progress; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; using Xunit; namespace Microsoft.PowerShell.EditorServices.Test.Debugging { + /* public class DebugServiceTests : IDisposable { private WorkspaceService workspace; private DebugService debugService; private ScriptFile debugScriptFile; private ScriptFile variableScriptFile; - private PowerShellContextService powerShellContext; - - private AsyncQueue debuggerStoppedQueue = - new AsyncQueue(); - private AsyncQueue sessionStateQueue = - new AsyncQueue(); private ScriptFile GetDebugScript(string fileName) { @@ -44,6 +46,8 @@ private ScriptFile GetDebugScript(string fileName) public DebugServiceTests() { + var loggerFactory = new NullLoggerFactory(); + var logger = NullLogger.Instance; this.powerShellContext = PowerShellContextFactory.Create(logger); @@ -1067,4 +1071,5 @@ await this.powerShellContext.ExecuteCommandAsync( .AddParameter("Script", scriptFile.FilePath)).ConfigureAwait(false); } } + */ } diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index c7b5ac37c..fdb709c1b 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -26,6 +26,7 @@ namespace Microsoft.PowerShell.EditorServices.Test.Language { + /* public class LanguageServiceTests : IDisposable { private readonly WorkspaceService workspace; @@ -526,4 +527,5 @@ private List FindSymbolsInFile(ScriptRegion scriptRegion) GetScriptFile(scriptRegion)); } } + */ } diff --git a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs b/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs index bd5f1a27a..64daeab62 100644 --- a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs +++ b/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs @@ -11,12 +11,12 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Test.Shared; using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Test { + /* internal static class PowerShellContextFactory { // NOTE: These paths are arbitrarily chosen just to verify that the profile paths @@ -122,4 +122,5 @@ protected override void UpdateProgress(long sourceId, ProgressDetails progressDe { } } + */ } diff --git a/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs b/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs index 510bff2c6..495166e09 100644 --- a/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PathEscapingTests.cs @@ -4,6 +4,7 @@ using Xunit; using System.IO; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Test.Session { @@ -28,7 +29,7 @@ public class PathEscapingTests [InlineData("C:\\&nimals\\утка\\qu*ck?.ps1", "C:\\&nimals\\утка\\qu`*ck`?.ps1")] public void CorrectlyWildcardEscapesPaths_NoSpaces(string unescapedPath, string escapedPath) { - string extensionEscapedPath = PowerShellContextService.WildcardEscapePath(unescapedPath); + string extensionEscapedPath = PathUtils.WildcardEscapePath(unescapedPath); Assert.Equal(escapedPath, extensionEscapedPath); } @@ -49,7 +50,7 @@ public void CorrectlyWildcardEscapesPaths_NoSpaces(string unescapedPath, string [InlineData("C:\\&nimals\\утка\\qu*ck?.ps1", "C:\\&nimals\\утка\\qu`*ck`?.ps1")] public void CorrectlyWildcardEscapesPaths_Spaces(string unescapedPath, string escapedPath) { - string extensionEscapedPath = PowerShellContextService.WildcardEscapePath(unescapedPath, escapeSpaces: true); + string extensionEscapedPath = PathUtils.WildcardEscapePath(unescapedPath, escapeSpaces: true); Assert.Equal(escapedPath, extensionEscapedPath); } @@ -71,7 +72,7 @@ public void CorrectlyWildcardEscapesPaths_Spaces(string unescapedPath, string es [InlineData("C:\\&nimals\\утка\\qu*ck?.ps1", "'C:\\&nimals\\утка\\qu*ck?.ps1'")] public void CorrectlyQuoteEscapesPaths(string unquotedPath, string expectedQuotedPath) { - string extensionQuotedPath = PowerShellContextService.QuoteEscapeString(unquotedPath); + string extensionQuotedPath = StringEscaping.SingleQuoteAndEscape(unquotedPath).ToString(); Assert.Equal(expectedQuotedPath, extensionQuotedPath); } @@ -93,31 +94,10 @@ public void CorrectlyQuoteEscapesPaths(string unquotedPath, string expectedQuote [InlineData("C:\\&nimals\\утка\\qu*ck?.ps1", "'C:\\&nimals\\утка\\qu`*ck`?.ps1'")] public void CorrectlyFullyEscapesPaths(string unescapedPath, string escapedPath) { - string extensionEscapedPath = PowerShellContextService.FullyPowerShellEscapePath(unescapedPath); + string extensionEscapedPath = StringEscaping.SingleQuoteAndEscape(PathUtils.WildcardEscapePath(unescapedPath)).ToString(); Assert.Equal(escapedPath, extensionEscapedPath); } - [Trait("Category", "PathEscaping")] - [Theory] - [InlineData("DebugTest.ps1", "DebugTest.ps1")] - [InlineData("../../DebugTest.ps1", "../../DebugTest.ps1")] - [InlineData("C:\\Users\\me\\Documents\\DebugTest.ps1", "C:\\Users\\me\\Documents\\DebugTest.ps1")] - [InlineData("/home/me/Documents/weird&folder/script.ps1", "/home/me/Documents/weird&folder/script.ps1")] - [InlineData("./path/with` some/spaces", "./path/with some/spaces")] - [InlineData("C:\\path\\with`[some`]brackets\\file.ps1", "C:\\path\\with[some]brackets\\file.ps1")] - [InlineData("C:\\look\\an`*\\here.ps1", "C:\\look\\an*\\here.ps1")] - [InlineData("/Users/me/Documents/`?here.ps1", "/Users/me/Documents/?here.ps1")] - [InlineData("/Brackets` `[and` s`]paces/path.ps1", "/Brackets [and s]paces/path.ps1")] - [InlineData("/CJK` chars/脚本/hello.ps1", "/CJK chars/脚本/hello.ps1")] - [InlineData("/CJK` chars/脚本/`[hello`].ps1", "/CJK chars/脚本/[hello].ps1")] - [InlineData("C:\\Animal` s\\утка\\quack.ps1", "C:\\Animal s\\утка\\quack.ps1")] - [InlineData("C:\\&nimals\\утка\\qu`*ck`?.ps1", "C:\\&nimals\\утка\\qu*ck?.ps1")] - public void CorrectlyUnescapesPaths(string escapedPath, string expectedUnescapedPath) - { - string extensionUnescapedPath = PowerShellContextService.UnescapeWildcardEscapedPath(escapedPath); - Assert.Equal(expectedUnescapedPath, extensionUnescapedPath); - } - [Trait("Category", "PathEscaping")] [Theory] [InlineData("NormalScript.ps1")] @@ -126,7 +106,7 @@ public void CorrectlyUnescapesPaths(string escapedPath, string expectedUnescaped public void CanDotSourcePath(string rawFileName) { string fullPath = Path.Combine(ScriptAssetPath, rawFileName); - string quotedPath = PowerShellContextService.QuoteEscapeString(fullPath); + string quotedPath = StringEscaping.SingleQuoteAndEscape(fullPath).ToString(); var psCommand = new System.Management.Automation.PSCommand().AddScript($". {quotedPath}"); diff --git a/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs b/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs index fc15c7854..d65a186ac 100644 --- a/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs @@ -9,13 +9,13 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Services; -using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Test.Shared; using Microsoft.PowerShell.EditorServices.Utility; using Xunit; namespace Microsoft.PowerShell.EditorServices.Test.Console { + /* public class PowerShellContextTests : IDisposable { // Borrowed from `VersionUtils` which can't be used here due to an initialization problem. @@ -174,4 +174,5 @@ private void OnSessionStateChanged(object sender, SessionStateChangedEventArgs e #endregion } + */ } diff --git a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs index 4e716a1d2..91b6a1e14 100644 --- a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs +++ b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs @@ -16,7 +16,7 @@ public class ScriptFileChangeTests { #if CoreCLR - private static readonly Version PowerShellVersion = new Version(6, 2); + private static readonly Version PowerShellVersion = new Version(7, 2); #else private static readonly Version PowerShellVersion = new Version(5, 1); #endif diff --git a/test/PowerShellEditorServices.Test/Utility/AsyncLockTests.cs b/test/PowerShellEditorServices.Test/Utility/AsyncLockTests.cs deleted file mode 100644 index 99b8bb05c..000000000 --- a/test/PowerShellEditorServices.Test/Utility/AsyncLockTests.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.PowerShell.EditorServices.Utility; -using System; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.PowerShell.EditorServices.Test.Utility -{ - public class AsyncLockTests - { - [Fact] - public async Task AsyncLockSynchronizesAccess() - { - AsyncLock asyncLock = new AsyncLock(); - - Task lockOne = asyncLock.LockAsync(); - Task lockTwo = asyncLock.LockAsync(); - - Assert.Equal(TaskStatus.RanToCompletion, lockOne.Status); - Assert.Equal(TaskStatus.WaitingForActivation, lockTwo.Status); - lockOne.Result.Dispose(); - - await lockTwo.ConfigureAwait(false); - Assert.Equal(TaskStatus.RanToCompletion, lockTwo.Status); - } - - [Fact] - public void AsyncLockCancelsWhenRequested() - { - CancellationTokenSource cts = new CancellationTokenSource(); - AsyncLock asyncLock = new AsyncLock(); - - Task lockOne = asyncLock.LockAsync(); - Task lockTwo = asyncLock.LockAsync(cts.Token); - - // Cancel the second lock before the first is released - cts.Cancel(); - lockOne.Result.Dispose(); - - Assert.Equal(TaskStatus.RanToCompletion, lockOne.Status); - Assert.Equal(TaskStatus.Canceled, lockTwo.Status); - } - } -} diff --git a/test/PowerShellEditorServices.Test/Utility/AsyncQueueTests.cs b/test/PowerShellEditorServices.Test/Utility/AsyncQueueTests.cs deleted file mode 100644 index 70c31f444..000000000 --- a/test/PowerShellEditorServices.Test/Utility/AsyncQueueTests.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.PowerShell.EditorServices.Utility; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.PowerShell.EditorServices.Test.Utility -{ - public class AsyncQueueTests - { - [Fact] - public async Task AsyncQueueSynchronizesAccess() - { - ConcurrentBag outputItems = new ConcurrentBag(); - AsyncQueue inputQueue = new AsyncQueue(Enumerable.Range(0, 100)); - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - - try - { - // Start 5 consumers - await Task.WhenAll( - Task.Run(() => ConsumeItemsAsync(inputQueue, outputItems, cancellationTokenSource.Token)), - Task.Run(() => ConsumeItemsAsync(inputQueue, outputItems, cancellationTokenSource.Token)), - Task.Run(() => ConsumeItemsAsync(inputQueue, outputItems, cancellationTokenSource.Token)), - Task.Run(() => ConsumeItemsAsync(inputQueue, outputItems, cancellationTokenSource.Token)), - Task.Run(() => ConsumeItemsAsync(inputQueue, outputItems, cancellationTokenSource.Token)), - Task.Run( - async () => - { - // Wait for a bit and then add more items to the queue - await Task.Delay(250).ConfigureAwait(false); - - foreach (var i in Enumerable.Range(100, 200)) - { - await inputQueue.EnqueueAsync(i).ConfigureAwait(false); - } - - // Cancel the waiters - cancellationTokenSource.Cancel(); - })).ConfigureAwait(false); - } - catch (TaskCanceledException) - { - // Do nothing, this is expected. - } - - // At this point, numbers 0 through 299 should be in the outputItems - IEnumerable expectedItems = Enumerable.Range(0, 300); - Assert.Empty(expectedItems.Except(outputItems)); - } - - [Fact] - public async Task AsyncQueueSkipsCancelledTasks() - { - AsyncQueue inputQueue = new AsyncQueue(); - - // Queue up a couple of tasks to wait for input - CancellationTokenSource cancellationSource = new CancellationTokenSource(); - Task taskOne = inputQueue.DequeueAsync(cancellationSource.Token); - Task taskTwo = inputQueue.DequeueAsync(); - - // Cancel the first task and then enqueue a number - cancellationSource.Cancel(); - await inputQueue.EnqueueAsync(1).ConfigureAwait(false); - - // Wait for things to propegate. - await Task.Delay(1000).ConfigureAwait(false); - - // Did the second task get the number? - Assert.Equal(TaskStatus.Canceled, taskOne.Status); - Assert.Equal(TaskStatus.RanToCompletion, taskTwo.Status); - Assert.Equal(1, taskTwo.Result); - } - - private static async Task ConsumeItemsAsync( - AsyncQueue inputQueue, - ConcurrentBag outputItems, - CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - int consumedItem = await inputQueue.DequeueAsync(cancellationToken).ConfigureAwait(false); - outputItems.Add(consumedItem); - } - } - } -} From 61ae8f23b6e3dae81305d152685d8f494a440b0b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 14 Oct 2021 15:03:06 -0700 Subject: [PATCH 157/176] Add Host factory --- .../Language/LanguageServiceTests.cs | 23 +++--- ...llContextFactory.cs => PsesHostFactory.cs} | 75 ++++--------------- 2 files changed, 26 insertions(+), 72 deletions(-) rename test/PowerShellEditorServices.Test/{PowerShellContextFactory.cs => PsesHostFactory.cs} (59%) diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index fdb709c1b..ca6487012 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.Symbols; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Test.Shared; @@ -26,13 +27,12 @@ namespace Microsoft.PowerShell.EditorServices.Test.Language { - /* public class LanguageServiceTests : IDisposable { private readonly WorkspaceService workspace; private readonly SymbolsService symbolsService; private readonly PsesCompletionHandler completionHandler; - private readonly PowerShellContextService powerShellContext; + private readonly PsesInternalHost _psesHost; private static readonly string s_baseSharedScriptPath = Path.Combine( Path.GetDirectoryName(VersionUtils.IsWindows @@ -45,16 +45,16 @@ public class LanguageServiceTests : IDisposable public LanguageServiceTests() { - var logger = NullLogger.Instance; - powerShellContext = PowerShellContextFactory.Create(logger); + _psesHost = PsesHostFactory.Create(NullLoggerFactory.Instance); + workspace = new WorkspaceService(NullLoggerFactory.Instance); - symbolsService = new SymbolsService(NullLoggerFactory.Instance, powerShellContext, workspace, new ConfigurationService()); - completionHandler = new PsesCompletionHandler(NullLoggerFactory.Instance, powerShellContext, workspace); + symbolsService = new SymbolsService(NullLoggerFactory.Instance, _psesHost, _psesHost, workspace, new ConfigurationService()); + completionHandler = new PsesCompletionHandler(NullLoggerFactory.Instance, _psesHost, _psesHost, workspace); } public void Dispose() { - this.powerShellContext.Close(); + // TODO: Dispose of the host } [Trait("Category", "Completions")] @@ -464,14 +464,12 @@ await this.completionHandler.GetCompletionsInFileAsync( scriptRegion.StartColumnNumber).ConfigureAwait(false); } - private async Task GetParamSetSignatures(ScriptRegion scriptRegion) + private Task GetParamSetSignatures(ScriptRegion scriptRegion) { - return - await this.symbolsService.FindParameterSetsInFileAsync( + return this.symbolsService.FindParameterSetsInFileAsync( GetScriptFile(scriptRegion), scriptRegion.StartLineNumber, - scriptRegion.StartColumnNumber, - powerShellContext).ConfigureAwait(false); + scriptRegion.StartColumnNumber); } private async Task GetDefinition(ScriptRegion scriptRegion) @@ -527,5 +525,4 @@ private List FindSymbolsInFile(ScriptRegion scriptRegion) GetScriptFile(scriptRegion)); } } - */ } diff --git a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs b/test/PowerShellEditorServices.Test/PsesHostFactory.cs similarity index 59% rename from test/PowerShellEditorServices.Test/PowerShellContextFactory.cs rename to test/PowerShellEditorServices.Test/PsesHostFactory.cs index 64daeab62..5d11ea02f 100644 --- a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs +++ b/test/PowerShellEditorServices.Test/PsesHostFactory.cs @@ -3,21 +3,24 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; +using System.Management.Automation; +using System.Management.Automation.Host; using System.Management.Automation.Runspaces; +using System.Security; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Hosting; -using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Test.Shared; using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Test { - /* - internal static class PowerShellContextFactory + internal static class PsesHostFactory { // NOTE: These paths are arbitrarily chosen just to verify that the profile paths // can be set to whatever they need to be for the given host. @@ -38,10 +41,8 @@ internal static class PowerShellContextFactory public static System.Management.Automation.Runspaces.Runspace InitialRunspace; - public static PowerShellContextService Create(ILogger logger) + public static PsesInternalHost Create(ILoggerFactory loggerFactory) { - PowerShellContextService powerShellContext = new PowerShellContextService(logger, null, isPSReadLineEnabled: false); - // We intentionally use `CreateDefault2()` as it loads `Microsoft.PowerShell.Core` only, // which is a more minimal and therefore safer state. var initialSessionState = InitialSessionState.CreateDefault2(); @@ -60,67 +61,23 @@ public static PowerShellContextService Create(ILogger logger) "PowerShell Editor Services Test Host", "Test.PowerShellEditorServices", new Version("1.0.0"), - null, + psHost: null, TestProfilePaths, - new List(), - new List(), + featureFlags: Array.Empty(), + additionalModules: Array.Empty(), initialSessionState, - null, - 0, + logPath: null, + (int)LogLevel.None, consoleReplEnabled: false, usesLegacyReadLine: false, bundledModulePath: BundledModulePath); - InitialRunspace = PowerShellContextService.CreateTestRunspace( - testHostDetails, - powerShellContext, - new TestPSHostUserInterface(powerShellContext, logger), - logger); + var psesHost = new PsesInternalHost(loggerFactory, null, testHostDetails); - powerShellContext.Initialize( - TestProfilePaths, - InitialRunspace, - ownsInitialRunspace: true, - consoleHost: null); + psesHost.StartAsync(new HostStartOptions { LoadProfiles = true }, CancellationToken.None).GetAwaiter().GetResult(); - return powerShellContext; + return psesHost; } } - - internal class TestPSHostUserInterface : EditorServicesPSHostUserInterface - { - public TestPSHostUserInterface( - PowerShellContextService powerShellContext, - ILogger logger) - : base( - powerShellContext, - new SimplePSHostRawUserInterface(logger), - NullLogger.Instance) - { - } - - public override void WriteOutput(string outputString, bool includeNewLine, OutputType outputType, ConsoleColor foregroundColor, ConsoleColor backgroundColor) - { - } - - protected override ChoicePromptHandler OnCreateChoicePromptHandler() - { - throw new NotImplementedException(); - } - - protected override InputPromptHandler OnCreateInputPromptHandler() - { - throw new NotImplementedException(); - } - - protected override Task ReadCommandLineAsync(CancellationToken cancellationToken) - { - return Task.FromResult("USER COMMAND"); - } - - protected override void UpdateProgress(long sourceId, ProgressDetails progressDetails) - { - } - } - */ } + From def819ef630a46e12fd640c5ca33011016268ba0 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 19 Oct 2021 15:42:07 -0700 Subject: [PATCH 158/176] Enable host with no repl --- .../PowerShell/Host/PsesInternalHost.cs | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index c3ebd9bb8..db7f3b765 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -471,7 +471,39 @@ private void RunTopLevelExecutionLoop() task.ExecuteSynchronously(CancellationToken.None); } - RunExecutionLoop(); + if (_hostInfo.ConsoleReplEnabled) + { + RunExecutionLoop(); + } + else + { + RunNoPromptExecutionLoop(); + } + } + + private void RunNoPromptExecutionLoop() + { + while (!_shouldExit) + { + using (CancellationScope cancellationScope = _cancellationContext.EnterScope(isIdleScope: false)) + { + string taskRepresentation = null; + try + { + ISynchronousTask task = _taskQueue.Take(cancellationScope.CancellationToken); + taskRepresentation = task.ToString(); + task.ExecuteSynchronously(cancellationScope.CancellationToken); + } + catch (OperationCanceledException) + { + // Just continue + } + catch (Exception e) + { + _logger.LogError(e, $"Fatal exception occurred with task '{taskRepresentation ?? ""}'"); + } + } + } } private void RunDebugExecutionLoop() From e6f21dcdc19ce561c5bf9b21b0807dc4f78f2789 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 19 Oct 2021 15:56:38 -0700 Subject: [PATCH 159/176] Comment out LSP unit tests --- .../Language/LanguageServiceTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index ca6487012..ec5d3169d 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -27,6 +27,7 @@ namespace Microsoft.PowerShell.EditorServices.Test.Language { + /* public class LanguageServiceTests : IDisposable { private readonly WorkspaceService workspace; @@ -525,4 +526,5 @@ private List FindSymbolsInFile(ScriptRegion scriptRegion) GetScriptFile(scriptRegion)); } } + */ } From 2daa962984064071b8ecfb111031e5e7eecd1b8c Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 19 Oct 2021 16:00:12 -0700 Subject: [PATCH 160/176] Bump CI From 702636c23f8fe2ae5fb64db36275b060a62c9800 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 20 Oct 2021 10:29:11 -0700 Subject: [PATCH 161/176] Add timeout to test --- .../LanguageServerProtocolMessageTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 1661cebeb..6edf649b0 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -1153,6 +1153,8 @@ await PsesLanguageClient [Fact] public async Task CanSendEvaluateRequestAsync() { + using var cancellationSource = new CancellationTokenSource(millisecondsDelay: 5000); + EvaluateResponseBody evaluateResponseBody = await PsesLanguageClient .SendRequest( @@ -1161,7 +1163,7 @@ await PsesLanguageClient { Expression = "Get-ChildItem" }) - .Returning(CancellationToken.None).ConfigureAwait(false); + .Returning(cancellationSource.Token).ConfigureAwait(false); // These always gets returned so this test really just makes sure we get _any_ response. Assert.Equal("", evaluateResponseBody.Result); From d5aebd6d2b56eb3f6b3084c6415b8bf2f476ae23 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 20 Oct 2021 10:52:08 -0700 Subject: [PATCH 162/176] Ensure CI is triggered for PRs not for master --- .vsts-ci/azure-pipelines-ci.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.vsts-ci/azure-pipelines-ci.yml b/.vsts-ci/azure-pipelines-ci.yml index 25706f2c0..dc2f2d461 100644 --- a/.vsts-ci/azure-pipelines-ci.yml +++ b/.vsts-ci/azure-pipelines-ci.yml @@ -8,14 +8,6 @@ variables: - name: DOTNET_CLI_TELEMETRY_OPTOUT value: 'true' -trigger: - branches: - include: - - master - -pr: -- master - jobs: - job: PS51_Win2016 displayName: PowerShell 5.1 - Windows Server 2016 From d1acc64e9d492a91e30c3d1c176a7c6b94e7c837 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 20 Oct 2021 16:59:17 -0700 Subject: [PATCH 163/176] Route startup exceptions through and ensure startup has completed before proceeding --- .../Server/PsesDebugServer.cs | 23 ++++- .../Server/PsesLanguageServer.cs | 17 +++- .../Debugging/PowerShellDebugContext.cs | 7 +- .../PowerShell/Host/PsesInternalHost.cs | 93 ++++++++++++++----- .../Handlers/ConfigurationHandler.cs | 2 +- .../DebugAdapterProtocolMessageTests.cs | 13 +++ .../LSPTestsFixures.cs | 2 + .../LanguageServerProtocolMessageTests.cs | 2 + .../Processes/StdioServerProcess.cs | 47 +++++++++- .../xunit.runner.json | 4 +- 10 files changed, 178 insertions(+), 32 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 0d45df793..0b1bf209b 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -28,7 +29,9 @@ internal class PsesDebugServer : IDisposable private DebugAdapterServer _debugAdapterServer; - private PowerShellDebugContext _debugContext; + private PsesInternalHost _psesHost; + + private bool _startedPses; protected readonly ILoggerFactory _loggerFactory; @@ -61,8 +64,8 @@ public async Task StartAsync() { // We need to let the PowerShell Context Service know that we are in a debug session // so that it doesn't send the powerShell/startDebugger message. - _debugContext = ServiceProvider.GetService().DebugContext; - _debugContext.IsDebugServerActive = true; + _psesHost = ServiceProvider.GetService(); + _psesHost.DebugContext.IsDebugServerActive = true; options .WithInput(_inputStream) @@ -88,8 +91,11 @@ public async Task StartAsync() // The OnInitialize delegate gets run when we first receive the _Initialize_ request: // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize .OnInitialize(async (server, request, cancellationToken) => { + // We need to make sure the host has been started + _startedPses = !(await _psesHost.TryStartAsync(new HostStartOptions(), CancellationToken.None).ConfigureAwait(false)); + // Ensure the debugger mode is set correctly - this is required for remote debugging to work - _debugContext.EnableDebugMode(); + _psesHost.DebugContext.EnableDebugMode(); var breakpointService = server.GetService(); // Clear any existing breakpoints before proceeding @@ -115,7 +121,7 @@ public void Dispose() // Note that the lifetime of the DebugContext is longer than the debug server; // It represents the debugger on the PowerShell process we're in, // while a new debug server is spun up for every debugging session - _debugContext.IsDebugServerActive = false; + _psesHost.DebugContext.IsDebugServerActive = false; _debugAdapterServer.Dispose(); _inputStream.Dispose(); _outputStream.Dispose(); @@ -126,6 +132,13 @@ public void Dispose() public async Task WaitForShutdown() { await _serverStopped.Task.ConfigureAwait(false); + + // If we started the host, we need to ensure any errors are marshalled back to us like this + if (_startedPses) + { + _psesHost.TriggerShutdown(); + await _psesHost.Shutdown.ConfigureAwait(false); + } } #region Events diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index 86eceac43..5b0522818 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -10,6 +10,7 @@ using Microsoft.PowerShell.EditorServices.Hosting; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.Extension; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.Template; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using OmniSharp.Extensions.LanguageServer.Server; @@ -32,6 +33,8 @@ internal class PsesLanguageServer private readonly HostStartupInfo _hostDetails; private readonly TaskCompletionSource _serverStart; + private PsesInternalHost _psesHost; + /// /// Create a new language server instance. /// @@ -75,8 +78,12 @@ public async Task StartAsync() options .WithInput(_inputStream) .WithOutput(_outputStream) - .WithServices(serviceCollection => serviceCollection - .AddPsesLanguageServices(_hostDetails)) // NOTE: This adds a lot of services! + .WithServices(serviceCollection => + { + + // NOTE: This adds a lot of services! + serviceCollection.AddPsesLanguageServices(_hostDetails); + }) .ConfigureLogging(builder => builder .AddSerilog(Log.Logger) // TODO: Set dispose to true? .AddLanguageProtocolLogging() @@ -116,6 +123,8 @@ public async Task StartAsync() IServiceProvider serviceProvider = languageServer.Services; + _psesHost = serviceProvider.GetService(); + var workspaceService = serviceProvider.GetService(); // Grab the workspace path from the parameters @@ -150,6 +159,10 @@ public async Task WaitForShutdown() Log.Logger.Debug("Shutting down OmniSharp Language Server"); await _serverStart.Task.ConfigureAwait(false); await LanguageServer.WaitForExit.ConfigureAwait(false); + + // Doing this means we're able to route through any exceptions experienced on the pipeline thread + _psesHost.TriggerShutdown(); + await _psesHost.Shutdown.ConfigureAwait(false); } } } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs index 780992b97..fb6d11e6d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs @@ -107,7 +107,12 @@ public void StepOver() public void SetDebugResuming(DebuggerResumeAction debuggerResumeAction) { _psesHost.SetExit(); - LastStopEventArgs.ResumeAction = debuggerResumeAction; + + if (LastStopEventArgs is not null) + { + LastStopEventArgs.ResumeAction = debuggerResumeAction; + } + // We need to tell whatever is happening right now in the debug prompt to wrap up so we can continue _psesHost.CancelCurrentTask(); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index db7f3b765..9d56816c3 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -57,10 +57,16 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns private readonly IdempotentLatch _isRunningLatch = new(); + private readonly TaskCompletionSource _started = new(); + + private readonly TaskCompletionSource _stopped = new(); + private EngineIntrinsics _mainRunspaceEngineIntrinsics; private bool _shouldExit = false; + private int _shuttingDown = 0; + private string _localComputerName; private ConsoleKeyInfo? _lastKey; @@ -128,12 +134,16 @@ public PsesInternalHost( public string InitialWorkingDirectory { get; private set; } + public Task Shutdown => _stopped.Task; + IRunspaceInfo IRunspaceContext.CurrentRunspace => CurrentRunspace; private PowerShellContextFrame CurrentFrame => _psFrameStack.Peek(); public event Action RunspaceChanged; + private bool ShouldExitExecutionLoop => _shouldExit || _shuttingDown != 0; + public override void EnterNestedPrompt() { PushPowerShellAndRunLoop(CreateNestedPowerShell(CurrentRunspace), PowerShellFrameType.Nested); @@ -172,13 +182,22 @@ public override void SetShouldExit(int exitCode) SetExit(); } - public async Task StartAsync(HostStartOptions startOptions, CancellationToken cancellationToken) + /// + /// Try to start the PowerShell loop in the host. + /// If the host is already started, this is idempotent. + /// Returns when the host is in a valid initialized state. + /// + /// Options to configure host startup. + /// A token to cancel startup. + /// A task that resolves when the host has finished startup, with the value true if the caller started the host, and false otherwise. + public async Task TryStartAsync(HostStartOptions startOptions, CancellationToken cancellationToken) { _logger.LogInformation("Host starting"); if (!_isRunningLatch.TryEnter()) { - _logger.LogDebug("Host start requested after already started"); - return; + _logger.LogDebug("Host start requested after already started."); + await _started.Task.ConfigureAwait(false); + return false; } _pipelineThread.Start(); @@ -198,6 +217,15 @@ await ExecuteDelegateAsync( { await SetInitialWorkingDirectoryAsync(startOptions.InitialWorkingDirectory, CancellationToken.None).ConfigureAwait(false); } + + await _started.Task.ConfigureAwait(false); + return true; + } + + public void TriggerShutdown() + { + Interlocked.Exchange(ref _shuttingDown, 1); + _cancellationContext.CancelCurrentTaskStack(); } public void SetExit() @@ -349,11 +377,19 @@ public Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cance private void Run() { - (PowerShell pwsh, RunspaceInfo localRunspaceInfo, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShellSession(); - _mainRunspaceEngineIntrinsics = engineIntrinsics; - _localComputerName = localRunspaceInfo.SessionDetails.ComputerName; - _runspaceStack.Push(new RunspaceFrame(pwsh.Runspace, localRunspaceInfo)); - PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal, localRunspaceInfo); + try + { + (PowerShell pwsh, RunspaceInfo localRunspaceInfo, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShellSession(); + _mainRunspaceEngineIntrinsics = engineIntrinsics; + _localComputerName = localRunspaceInfo.SessionDetails.ComputerName; + _runspaceStack.Push(new RunspaceFrame(pwsh.Runspace, localRunspaceInfo)); + PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal, localRunspaceInfo); + } + catch (Exception e) + { + _started.TrySetException(e); + _stopped.TrySetException(e); + } } private (PowerShell, RunspaceInfo, EngineIntrinsics) CreateInitialPowerShellSession() @@ -465,25 +501,40 @@ private void PopPowerShell(RunspaceChangeAction runspaceChangeAction = RunspaceC private void RunTopLevelExecutionLoop() { - // Make sure we execute any startup tasks first - while (_taskQueue.TryTake(out ISynchronousTask task)) + try { - task.ExecuteSynchronously(CancellationToken.None); - } + // Make sure we execute any startup tasks first + while (_taskQueue.TryTake(out ISynchronousTask task)) + { + task.ExecuteSynchronously(CancellationToken.None); + } - if (_hostInfo.ConsoleReplEnabled) - { - RunExecutionLoop(); + // Signal that we are ready for outside services to use + _started.TrySetResult(true); + + if (_hostInfo.ConsoleReplEnabled) + { + RunExecutionLoop(); + } + else + { + RunNoPromptExecutionLoop(); + } } - else + catch (Exception e) { - RunNoPromptExecutionLoop(); + _logger.LogError(e, "PSES pipeline thread loop experienced an unexpected top-level exception"); + _stopped.TrySetException(e); + return; } + + _logger.LogInformation("PSES pipeline thread loop shutting down"); + _stopped.SetResult(true); } private void RunNoPromptExecutionLoop() { - while (!_shouldExit) + while (!ShouldExitExecutionLoop) { using (CancellationScope cancellationScope = _cancellationContext.EnterScope(isIdleScope: false)) { @@ -521,13 +572,13 @@ private void RunDebugExecutionLoop() private void RunExecutionLoop() { - while (!_shouldExit) + while (!ShouldExitExecutionLoop) { using (CancellationScope cancellationScope = _cancellationContext.EnterScope(isIdleScope: false)) { DoOneRepl(cancellationScope.CancellationToken); - while (!_shouldExit + while (!ShouldExitExecutionLoop && !cancellationScope.CancellationToken.IsCancellationRequested && _taskQueue.TryTake(out ISynchronousTask task)) { @@ -806,7 +857,7 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs break private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs) { - if (!_shouldExit && !_resettingRunspace && !runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) + if (!ShouldExitExecutionLoop && !_resettingRunspace && !runspaceStateEventArgs.RunspaceStateInfo.IsUsable()) { _resettingRunspace = true; PopOrReinitializeRunspaceAsync().HandleErrorsAsync(_logger); diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index 48f7dacab..00095a8a4 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -92,7 +92,7 @@ public override async Task Handle(DidChangeConfigurationParams request, Ca _logger.LogTrace("Loading profiles..."); } - await _psesHost.StartAsync(new HostStartOptions(), CancellationToken.None).ConfigureAwait(false); + await _psesHost.TryStartAsync(new HostStartOptions { LoadProfiles = loadProfiles }, CancellationToken.None).ConfigureAwait(false); if (loadProfiles) { diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index bf8b193cc..68722d272 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -44,6 +45,13 @@ public async Task InitializeAsync() await _psesProcess.Start().ConfigureAwait(false); var initialized = new TaskCompletionSource(); + + _psesProcess.ProcessExited += (sender, args) => + { + initialized.TrySetException(new ProcessExitedException("Initialization failed due to process failure", args.ExitCode, args.ErrorMessage)); + Started.TrySetException(new ProcessExitedException("Startup failed due to process failure", args.ExitCode, args.ErrorMessage)); + }; + PsesDebugAdapterClient = DebugAdapterClient.Create(options => { options @@ -61,6 +69,11 @@ public async Task InitializeAsync() initialized.SetResult(true); return Task.CompletedTask; }); + + options.OnUnhandledException = (exception) => { + initialized.SetException(exception); + Started.SetException(exception); + }; }); // PSES follows the following flow: diff --git a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs index 3bdd47096..173554dd9 100644 --- a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs +++ b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -44,6 +45,7 @@ public async Task InitializeAsync() var factory = new LoggerFactory(); _psesProcess = new PsesStdioProcess(factory, IsDebugAdapterTests); await _psesProcess.Start().ConfigureAwait(false); + //Debugger.Launch(); Diagnostics = new List(); TelemetryEvents = new List(); diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs index 6edf649b0..6852bff3a 100644 --- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs @@ -41,6 +41,7 @@ public class LanguageServerProtocolMessageTests : IClassFixture private readonly List Diagnostics; private readonly List TelemetryEvents; private readonly string PwshExe; + private readonly LSPTestsFixture _fixture; public LanguageServerProtocolMessageTests(ITestOutputHelper output, LSPTestsFixture data) { @@ -50,6 +51,7 @@ public LanguageServerProtocolMessageTests(ITestOutputHelper output, LSPTestsFixt Diagnostics.Clear(); TelemetryEvents = data.TelemetryEvents; TelemetryEvents.Clear(); + _fixture = data; PwshExe = PsesStdioProcess.PwshExe; } diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs b/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs index 585e1bf03..2d0a9958c 100644 --- a/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs +++ b/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs @@ -45,6 +45,8 @@ public StdioServerProcess(ILoggerFactory loggerFactory, ProcessStartInfo serverS _serverStartInfo = serverStartInfo; } + public int ProcessId => _serverProcess.Id; + /// /// Dispose of resources being used by the launcher. /// @@ -94,6 +96,7 @@ public override Task Start() _serverStartInfo.UseShellExecute = false; _serverStartInfo.RedirectStandardInput = true; _serverStartInfo.RedirectStandardOutput = true; + _serverStartInfo.RedirectStandardError = true; Process serverProcess = _serverProcess = new Process { @@ -126,6 +129,8 @@ public override async Task Stop() await ServerExitCompletion.Task.ConfigureAwait(false); } + public event EventHandler ProcessExited; + /// /// Called when the server process has exited. /// @@ -139,9 +144,49 @@ void ServerProcess_Exit(object sender, EventArgs args) { Log.LogDebug("Server process has exited."); + var serverProcess = (Process)sender; + + int exitCode = serverProcess.ExitCode; + string errorMsg = serverProcess.StandardError.ReadToEnd(); + OnExited(); - ServerExitCompletion.TrySetResult(null); + ProcessExited?.Invoke(this, new ProcessExitedArgs(exitCode, errorMsg)); + if (exitCode != 0) + { + ServerExitCompletion.TrySetException(new ProcessExitedException("Stdio server process exited unexpectedly", exitCode, errorMsg)); + } + else + { + ServerExitCompletion.TrySetResult(null); + } ServerStartCompletion = new TaskCompletionSource(); } } + + public class ProcessExitedException : Exception + { + public ProcessExitedException(string message, int exitCode, string errorMessage) + : base(message) + { + ExitCode = exitCode; + ErrorMessage = errorMessage; + } + + public int ExitCode { get; init; } + + public string ErrorMessage { get; init; } + } + + public class ProcessExitedArgs : EventArgs + { + public ProcessExitedArgs(int exitCode, string errorMessage) + { + ExitCode = exitCode; + ErrorMessage = errorMessage; + } + + public int ExitCode { get; init; } + + public string ErrorMessage { get; init; } + } } diff --git a/test/PowerShellEditorServices.Test.E2E/xunit.runner.json b/test/PowerShellEditorServices.Test.E2E/xunit.runner.json index 3f3645a0a..2719fd14a 100644 --- a/test/PowerShellEditorServices.Test.E2E/xunit.runner.json +++ b/test/PowerShellEditorServices.Test.E2E/xunit.runner.json @@ -2,5 +2,7 @@ "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", "appDomain": "denied", "parallelizeTestCollections": false, - "methodDisplay": "method" + "methodDisplay": "method", + "diagnosticMessages": true, + "longRunningTestSeconds": 60 } From 5d94ad6ccecceaa6ac0c126f5017e8b184bd1520 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 20 Oct 2021 17:07:06 -0700 Subject: [PATCH 164/176] Fix test compile error --- test/PowerShellEditorServices.Test/PsesHostFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test/PsesHostFactory.cs b/test/PowerShellEditorServices.Test/PsesHostFactory.cs index 5d11ea02f..6f843f12d 100644 --- a/test/PowerShellEditorServices.Test/PsesHostFactory.cs +++ b/test/PowerShellEditorServices.Test/PsesHostFactory.cs @@ -74,7 +74,7 @@ public static PsesInternalHost Create(ILoggerFactory loggerFactory) var psesHost = new PsesInternalHost(loggerFactory, null, testHostDetails); - psesHost.StartAsync(new HostStartOptions { LoadProfiles = true }, CancellationToken.None).GetAwaiter().GetResult(); + psesHost.TryStartAsync(new HostStartOptions { LoadProfiles = true }, CancellationToken.None).GetAwaiter().GetResult(); return psesHost; } From b0edca921d028ed0d6eef7ce809fb47bccc9f08e Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 25 Oct 2021 12:20:00 -0700 Subject: [PATCH 165/176] Fix process exit throwing --- .../Services/PowerShell/Handlers/EvaluateHandler.cs | 4 +++- .../Processes/StdioServerProcess.cs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs index 5ae13498c..fb0f5764a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/EvaluateHandler.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.PowerShell; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Handlers { @@ -36,7 +37,8 @@ public Task Handle(EvaluateRequestArguments request, Cance _executionService.ExecutePSCommandAsync( new PSCommand().AddScript(request.Expression), CancellationToken.None, - new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true, ThrowOnError = false, InterruptCurrentForeground = true }); + new PowerShellExecutionOptions { WriteInputToHost = true, WriteOutputToHost = true, AddToHistory = true, ThrowOnError = false, InterruptCurrentForeground = true }) + .HandleErrorsAsync(_logger); return Task.FromResult(new EvaluateResponseBody { diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs b/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs index 2d0a9958c..1885a655e 100644 --- a/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs +++ b/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs @@ -118,15 +118,15 @@ public override Task Start() /// /// Stop or disconnect from the server. /// - public override async Task Stop() + public override Task Stop() { Process serverProcess = Interlocked.Exchange(ref _serverProcess, null); + ServerExitCompletion.TrySetResult(null); if (serverProcess != null && !serverProcess.HasExited) { serverProcess.Kill(); } - - await ServerExitCompletion.Task.ConfigureAwait(false); + return ServerExitCompletion.Task; } public event EventHandler ProcessExited; From 2846bef932ff8c77c771bdbc0aa87d95a6b15628 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 25 Oct 2021 13:28:40 -0700 Subject: [PATCH 166/176] Fix bad output when console repl is disabled --- .../PowerShell/Host/NullPSHostRawUI.cs | 58 ++++++++++++ .../Services/PowerShell/Host/NullPSHostUI.cs | 90 +++++++++++++++++++ .../PowerShell/Host/PsesInternalHost.cs | 9 +- 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostRawUI.cs create mode 100644 src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostUI.cs diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostRawUI.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostRawUI.cs new file mode 100644 index 000000000..cb5c997c1 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostRawUI.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Management.Automation.Host; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host +{ + internal class NullPSHostRawUI : PSHostRawUserInterface + { + private readonly BufferCell[,] _buffer; + + public NullPSHostRawUI() + { + _buffer = new BufferCell[0, 0]; + } + + public override ConsoleColor BackgroundColor { get; set; } + public override Size BufferSize { get; set; } + public override Coordinates CursorPosition { get; set; } + public override int CursorSize { get; set; } + public override ConsoleColor ForegroundColor { get; set; } + + public override bool KeyAvailable => false; + + public override Size MaxPhysicalWindowSize => MaxWindowSize; + + public override Size MaxWindowSize => new Size { Width = _buffer.GetLength(0), Height = _buffer.GetLength(1) }; + + public override Coordinates WindowPosition { get; set; } + public override Size WindowSize { get; set; } + public override string WindowTitle { get; set; } + + public override void FlushInputBuffer() + { + // Do nothing + } + + public override BufferCell[,] GetBufferContents(Rectangle rectangle) => _buffer; + + public override KeyInfo ReadKey(ReadKeyOptions options) => default; + + public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill) + { + // Do nothing + } + + public override void SetBufferContents(Coordinates origin, BufferCell[,] contents) + { + // Do nothing + } + + public override void SetBufferContents(Rectangle rectangle, BufferCell fill) + { + // Do nothing + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostUI.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostUI.cs new file mode 100644 index 000000000..655fd2fe9 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostUI.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation; +using System.Management.Automation.Host; +using System.Security; + +namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host +{ + internal class NullPSHostUI : PSHostUserInterface + { + public NullPSHostUI() + { + RawUI = new NullPSHostRawUI(); + } + + public override PSHostRawUserInterface RawUI { get; } + + public override Dictionary Prompt(string caption, string message, Collection descriptions) + { + return new Dictionary(); + } + + public override int PromptForChoice(string caption, string message, Collection choices, int defaultChoice) + { + return 0; + } + + public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) + { + return new PSCredential(userName: string.Empty, password: new SecureString()); + } + + public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName) + => PromptForCredential(caption, message, userName, targetName, PSCredentialTypes.Default, PSCredentialUIOptions.Default); + + public override string ReadLine() + { + return string.Empty; + } + + public override SecureString ReadLineAsSecureString() + { + return new SecureString(); + } + + public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) + { + // Do nothing + } + + public override void Write(string value) + { + // Do nothing + } + + public override void WriteDebugLine(string message) + { + // Do nothing + } + + public override void WriteErrorLine(string value) + { + // Do nothing + } + + public override void WriteLine(string value) + { + // Do nothing + } + + public override void WriteProgress(long sourceId, ProgressRecord record) + { + // Do nothing + } + + public override void WriteVerboseLine(string message) + { + // Do nothing + } + + public override void WriteWarningLine(string message) + { + // Do nothing + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 9d56816c3..6a787657e 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -103,7 +103,9 @@ public PsesInternalHost( Version = hostInfo.Version; DebugContext = new PowerShellDebugContext(loggerFactory, languageServer, this); - UI = new EditorServicesConsolePSHostUserInterface(loggerFactory, _readLineProvider, hostInfo.PSHost.UI); + UI = hostInfo.ConsoleReplEnabled + ? new EditorServicesConsolePSHostUserInterface(loggerFactory, _readLineProvider, hostInfo.PSHost.UI) + : new NullPSHostUI(); } public override CultureInfo CurrentCulture => _hostInfo.PSHost.CurrentCulture; @@ -590,6 +592,11 @@ private void RunExecutionLoop() private void DoOneRepl(CancellationToken cancellationToken) { + if (!_hostInfo.ConsoleReplEnabled) + { + return; + } + // When a task must run in the foreground, we cancel out of the idle loop and return to the top level. // At that point, we would normally run a REPL, but we need to immediately execute the task. // So we set _skipNextPrompt to do that. From 0681d3703117644de64447f87ffc8ef05b1160fe Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 25 Oct 2021 13:41:29 -0700 Subject: [PATCH 167/176] Add debug logging for integration test streams --- .../Processes/LoggingStream.cs | 60 +++++++++++++++++++ .../Processes/ServerProcess.cs | 16 ++++- 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 test/PowerShellEditorServices.Test.E2E/Processes/LoggingStream.cs diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/LoggingStream.cs b/test/PowerShellEditorServices.Test.E2E/Processes/LoggingStream.cs new file mode 100644 index 000000000..dc5b2e2a5 --- /dev/null +++ b/test/PowerShellEditorServices.Test.E2E/Processes/LoggingStream.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace PowerShellEditorServices.Test.E2E +{ + internal class LoggingStream : Stream + { + private static readonly string s_banner = new('=', 20); + + private readonly Stream _underlyingStream; + + public LoggingStream(Stream underlyingStream) + { + _underlyingStream = underlyingStream; + } + + public override bool CanRead => _underlyingStream.CanRead; + + public override bool CanSeek => _underlyingStream.CanSeek; + + public override bool CanWrite => _underlyingStream.CanWrite; + + public override long Length => _underlyingStream.Length; + + public override long Position { get => _underlyingStream.Position; set => _underlyingStream.Position = value; } + + public override void Flush() => _underlyingStream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) + { + int actualCount = _underlyingStream.Read(buffer, offset, count); + LogData("READ", buffer, offset, actualCount); + return actualCount; + } + + public override long Seek(long offset, SeekOrigin origin) => _underlyingStream.Seek(offset, origin); + + public override void SetLength(long value) => _underlyingStream.SetLength(value); + + public override void Write(byte[] buffer, int offset, int count) + { + LogData("WRITE", buffer, offset, count); + _underlyingStream.Write(buffer, offset, count); + } + + private static void LogData(string header, byte[] buffer, int offset, int count) + { + Debug.WriteLine($"{header} |{s_banner.Substring(0, Math.Max(s_banner.Length - header.Length - 2, 0))}"); + string data = Encoding.UTF8.GetString(buffer, offset, count); + Debug.WriteLine(data); + Debug.WriteLine(s_banner); + Debug.WriteLine("\n"); + } + } +} diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs b/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs index 68e323338..ca7360cb8 100644 --- a/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs +++ b/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs @@ -15,6 +15,11 @@ namespace PowerShellEditorServices.Test.E2E public abstract class ServerProcess : IDisposable { private readonly ISubject _exitedSubject; + + private readonly Lazy _inStreamLazy; + + private readonly Lazy _outStreamLazy; + /// /// Create a new . /// @@ -37,6 +42,9 @@ protected ServerProcess(ILoggerFactory loggerFactory) ServerExitCompletion.SetResult(null); // Start out as if the server has already exited. Exited = _exitedSubject = new AsyncSubject(); + + _inStreamLazy = new Lazy(() => new LoggingStream(GetInputStream())); + _outStreamLazy = new Lazy(() => new LoggingStream(GetOutputStream())); } /// @@ -105,13 +113,17 @@ protected virtual void Dispose(bool disposing) /// public Task HasExited => ServerExitCompletion.Task; + protected abstract Stream GetInputStream(); + + protected abstract Stream GetOutputStream(); + /// /// The server's input stream. /// /// /// The connection will write to the server's input stream, and read from its output stream. /// - public abstract Stream InputStream { get; } + public Stream InputStream => _inStreamLazy.Value; /// /// The server's output stream. @@ -119,7 +131,7 @@ protected virtual void Dispose(bool disposing) /// /// The connection will read from the server's output stream, and write to its input stream. /// - public abstract Stream OutputStream { get; } + public Stream OutputStream => _outStreamLazy.Value; /// /// Start or connect to the server. From 196ec3b841d0f59244cc7d87c345af49970e03aa Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 25 Oct 2021 13:43:56 -0700 Subject: [PATCH 168/176] Fix overrides --- .../Processes/StdioServerProcess.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs b/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs index 1885a655e..4a0c96d16 100644 --- a/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs +++ b/test/PowerShellEditorServices.Test.E2E/Processes/StdioServerProcess.cs @@ -78,12 +78,12 @@ protected override void Dispose(bool disposing) /// /// The server's input stream. /// - public override Stream InputStream => _serverProcess?.StandardInput?.BaseStream; + protected override Stream GetInputStream() => _serverProcess?.StandardInput?.BaseStream; /// /// The server's output stream. /// - public override Stream OutputStream => _serverProcess?.StandardOutput?.BaseStream; + protected override Stream GetOutputStream() => _serverProcess?.StandardOutput?.BaseStream; /// /// Start or connect to the server. From d22b57cfa1cad176b891c2a95d6c6b6b6d8328be Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 25 Oct 2021 15:18:06 -0700 Subject: [PATCH 169/176] Fix variable test with scope change --- .../DebugAdapterProtocolMessageTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 68722d272..95efa48a9 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -263,7 +263,7 @@ public async Task CanStepPastSystemWindowsForms() string filePath = NewTestFile(string.Join(Environment.NewLine, new [] { "Add-Type -AssemblyName System.Windows.Forms", - "$form = New-Object System.Windows.Forms.Form", + "$global:form = New-Object System.Windows.Forms.Form", "Write-Host $form" })); From 5699e481ec81d27956c20d88b01efa42926d5f02 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 25 Oct 2021 15:18:17 -0700 Subject: [PATCH 170/176] Fix assembly log exception --- src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs index 10b7281af..7b48b9c3d 100644 --- a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs +++ b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs @@ -120,6 +120,11 @@ public static EditorServicesLoader Create( { AppDomain.CurrentDomain.AssemblyLoad += (object sender, AssemblyLoadEventArgs args) => { + if (args.LoadedAssembly.IsDynamic) + { + return; + } + logger.Log( PsesLogLevel.Diagnostic, $"Loaded '{args.LoadedAssembly.GetName()}' from '{args.LoadedAssembly.Location}'"); From ca7c81532ac22214ebeddb555bd27e615bb5c6dd Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 26 Oct 2021 14:55:02 -0700 Subject: [PATCH 171/176] Only set pipeline thread apartment state on Windows --- .../Services/PowerShell/Host/PsesInternalHost.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 6a787657e..05d9d0a92 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -96,7 +96,10 @@ public PsesInternalHost( Name = "PSES Pipeline Execution Thread", }; - _pipelineThread.SetApartmentState(ApartmentState.STA); + if (VersionUtils.IsWindows) + { + _pipelineThread.SetApartmentState(ApartmentState.STA); + } PublicHost = new EditorServicesConsolePSHost(this); Name = hostInfo.Name; From 0c85a01d3a03479fc05049a2b593c2c852c15b85 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 26 Oct 2021 18:26:16 -0400 Subject: [PATCH 172/176] Remove defunct internal cmdlets (#1593) --- .../InvokeReadLineConstructorCommand.cs | 21 -------- .../InvokeReadLineForEditorServicesCommand.cs | 53 ------------------- 2 files changed, 74 deletions(-) delete mode 100644 src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineConstructorCommand.cs delete mode 100644 src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineForEditorServicesCommand.cs diff --git a/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineConstructorCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineConstructorCommand.cs deleted file mode 100644 index 58038d13e..000000000 --- a/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineConstructorCommand.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Management.Automation; -using System.Runtime.CompilerServices; - -namespace Microsoft.PowerShell.EditorServices.Commands -{ - /// - /// The Start-EditorServices command, the conventional entrypoint for PowerShell Editor Services. - /// - public sealed class InvokeReadLineConstructorCommand : PSCmdlet - { - protected override void EndProcessing() - { - Type type = Type.GetType("Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2"); - RuntimeHelpers.RunClassConstructor(type.TypeHandle); - } - } -} diff --git a/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineForEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineForEditorServicesCommand.cs deleted file mode 100644 index b0adf6fc8..000000000 --- a/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineForEditorServicesCommand.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Reflection; -using System.Threading; - -namespace Microsoft.PowerShell.EditorServices.Commands -{ - /// - /// The Start-EditorServices command, the conventional entrypoint for PowerShell Editor Services. - /// - public sealed class InvokeReadLineForEditorServicesCommand : PSCmdlet - { - private delegate string ReadLineInvoker( - Runspace runspace, - EngineIntrinsics engineIntrinsics, - CancellationToken cancellationToken); - - private static Lazy s_readLine = new Lazy(() => - { - Type type = Type.GetType("Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2"); - MethodInfo method = type?.GetMethod( - "ReadLine", - new[] { typeof(Runspace), typeof(EngineIntrinsics), typeof(CancellationToken) }); - - // TODO: Handle method being null here. This shouldn't ever happen. - - return (ReadLineInvoker)method.CreateDelegate(typeof(ReadLineInvoker)); - }); - - /// - /// The ID to give to the host's profile. - /// - [Parameter(Mandatory = true)] - [ValidateNotNullOrEmpty] - public CancellationToken CancellationToken { get; set; } - - protected override void EndProcessing() - { - // This returns a string. - object result = s_readLine.Value( - Runspace.DefaultRunspace, - SessionState.PSVariable.Get("ExecutionContext").Value as EngineIntrinsics, - CancellationToken - ); - - WriteObject(result); - } - } -} From fcc4cd8f37ecb9915bfa076a1fb190daa411ef0b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 26 Oct 2021 16:00:39 -0700 Subject: [PATCH 173/176] Remove debug code --- test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs index 173554dd9..208ea3f6f 100644 --- a/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs +++ b/test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs @@ -45,7 +45,6 @@ public async Task InitializeAsync() var factory = new LoggerFactory(); _psesProcess = new PsesStdioProcess(factory, IsDebugAdapterTests); await _psesProcess.Start().ConfigureAwait(false); - //Debugger.Launch(); Diagnostics = new List(); TelemetryEvents = new List(); From 6447d822341084d19f49289e20ebee7b2464d432 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 27 Oct 2021 16:30:48 -0400 Subject: [PATCH 174/176] Reinstate language service unit tests (#1598) --- .../PowerShell/Host/PsesInternalHost.cs | 12 +++++- .../Language/LanguageServiceTests.cs | 4 +- .../PsesHostFactory.cs | 43 ++++++++++++++++++- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 05d9d0a92..fffed9891 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -227,10 +227,18 @@ await ExecuteDelegateAsync( return true; } + public Task StopAsync() + { + TriggerShutdown(); + return Shutdown; + } + public void TriggerShutdown() { - Interlocked.Exchange(ref _shuttingDown, 1); - _cancellationContext.CancelCurrentTaskStack(); + if (Interlocked.Exchange(ref _shuttingDown, 1) == 0) + { + _cancellationContext.CancelCurrentTaskStack(); + } } public void SetExit() diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index ec5d3169d..68a6db95a 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -27,7 +27,6 @@ namespace Microsoft.PowerShell.EditorServices.Test.Language { - /* public class LanguageServiceTests : IDisposable { private readonly WorkspaceService workspace; @@ -55,7 +54,7 @@ public LanguageServiceTests() public void Dispose() { - // TODO: Dispose of the host + _psesHost.StopAsync().GetAwaiter().GetResult(); } [Trait("Category", "Completions")] @@ -526,5 +525,4 @@ private List FindSymbolsInFile(ScriptRegion scriptRegion) GetScriptFile(scriptRegion)); } } - */ } diff --git a/test/PowerShellEditorServices.Test/PsesHostFactory.cs b/test/PowerShellEditorServices.Test/PsesHostFactory.cs index 6f843f12d..dfe74b836 100644 --- a/test/PowerShellEditorServices.Test/PsesHostFactory.cs +++ b/test/PowerShellEditorServices.Test/PsesHostFactory.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Globalization; using System.IO; using System.Management.Automation; using System.Management.Automation.Host; @@ -61,7 +62,7 @@ public static PsesInternalHost Create(ILoggerFactory loggerFactory) "PowerShell Editor Services Test Host", "Test.PowerShellEditorServices", new Version("1.0.0"), - psHost: null, + psHost: new NullPSHost(), TestProfilePaths, featureFlags: Array.Empty(), additionalModules: Array.Empty(), @@ -79,5 +80,45 @@ public static PsesInternalHost Create(ILoggerFactory loggerFactory) return psesHost; } } + + internal class NullPSHost : PSHost + { + public override CultureInfo CurrentCulture => CultureInfo.CurrentCulture; + + public override CultureInfo CurrentUICulture => CultureInfo.CurrentUICulture; + + public override Guid InstanceId { get; } = Guid.NewGuid(); + + public override string Name => nameof(NullPSHost); + + public override PSHostUserInterface UI { get; } = new NullPSHostUI(); + + public override Version Version { get; } = new Version(1, 0, 0); + + public override void EnterNestedPrompt() + { + // Do nothing + } + + public override void ExitNestedPrompt() + { + // Do nothing + } + + public override void NotifyBeginApplication() + { + // Do nothing + } + + public override void NotifyEndApplication() + { + // Do nothing + } + + public override void SetShouldExit(int exitCode) + { + // Do nothing + } + } } From 328cc8bac504c43ac78cfe39ac35c3e1e39cbe7e Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 27 Oct 2021 13:31:52 -0700 Subject: [PATCH 175/176] Add initial debug service test changes --- .../Handlers/ConfigurationDoneHandler.cs | 31 +-- ...mmandExtensions.cs => PSCommandHelpers.cs} | 34 ++- .../Debugging/DebugServiceTests.cs | 238 +++++++----------- 3 files changed, 124 insertions(+), 179 deletions(-) rename src/PowerShellEditorServices/Utility/{PSCommandExtensions.cs => PSCommandHelpers.cs} (78%) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs index 7750585c0..47b00deef 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs @@ -140,7 +140,7 @@ await _executionService { await _executionService .ExecutePSCommandAsync( - BuildPSCommandFromArguments(scriptToLaunch, _debugStateService.Arguments), + PSCommandHelpers.BuildPSCommandFromArguments(scriptToLaunch, _debugStateService.Arguments), CancellationToken.None, s_debuggerExecutionOptions) .ConfigureAwait(false); @@ -148,34 +148,5 @@ await _executionService _debugAdapterServer.SendNotification(EventNames.Terminated); } - - private static PSCommand BuildPSCommandFromArguments(string command, IReadOnlyList arguments) - { - if (arguments is null or { Count: 0 }) - { - return new PSCommand().AddCommand(command); - } - - // We are forced to use a hack here so that we can reuse PowerShell's parameter binding - var sb = new StringBuilder() - .Append("& ") - .Append(StringEscaping.SingleQuoteAndEscape(command)); - - foreach (string arg in arguments) - { - sb.Append(' '); - - if (StringEscaping.PowerShellArgumentNeedsEscaping(arg)) - { - sb.Append(StringEscaping.SingleQuoteAndEscape(arg)); - } - else - { - sb.Append(arg); - } - } - - return new PSCommand().AddScript(sb.ToString()); - } } } diff --git a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs b/src/PowerShellEditorServices/Utility/PSCommandHelpers.cs similarity index 78% rename from src/PowerShellEditorServices/Utility/PSCommandExtensions.cs rename to src/PowerShellEditorServices/Utility/PSCommandHelpers.cs index 55c8eed20..f023ce086 100644 --- a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs +++ b/src/PowerShellEditorServices/Utility/PSCommandHelpers.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Linq.Expressions; using System.Management.Automation; using System.Management.Automation.Runspaces; @@ -10,11 +11,11 @@ namespace Microsoft.PowerShell.EditorServices.Utility { - internal static class PSCommandExtensions + internal static class PSCommandHelpers { private static readonly Func s_commandCtor; - static PSCommandExtensions() + static PSCommandHelpers() { var ctor = typeof(Command).GetConstructor( BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, @@ -30,6 +31,35 @@ static PSCommandExtensions() .Compile(); } + public static PSCommand BuildPSCommandFromArguments(string command, IReadOnlyList arguments) + { + if (arguments is null or { Count: 0 }) + { + return new PSCommand().AddCommand(command); + } + + // We are forced to use a hack here so that we can reuse PowerShell's parameter binding + var sb = new StringBuilder() + .Append("& ") + .Append(StringEscaping.SingleQuoteAndEscape(command)); + + foreach (string arg in arguments) + { + sb.Append(' '); + + if (StringEscaping.PowerShellArgumentNeedsEscaping(arg)) + { + sb.Append(StringEscaping.SingleQuoteAndEscape(arg)); + } + else + { + sb.Append(arg); + } + } + + return new PSCommand().AddScript(sb.ToString()); + } + // PowerShell's missing an API for us to AddCommand using a CommandInfo. // An issue was filed here: https://github.com/PowerShell/PowerShell/issues/12295 // This works around this by creating a `Command` and passing it into PSCommand.AddCommand(Command command) diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index a01ddc20d..6f072a4a0 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -17,6 +18,7 @@ using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Test.Shared; +using Microsoft.PowerShell.EditorServices.Utility; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Protocol.Models; @@ -26,7 +28,6 @@ namespace Microsoft.PowerShell.EditorServices.Test.Debugging { - /* public class DebugServiceTests : IDisposable { private WorkspaceService workspace; @@ -34,6 +35,9 @@ public class DebugServiceTests : IDisposable private ScriptFile debugScriptFile; private ScriptFile variableScriptFile; + private readonly PsesInternalHost _psesHost; + private readonly BlockingCollection _debuggerStoppedQueue; + private ScriptFile GetDebugScript(string fileName) { return this.workspace.GetFile( @@ -48,10 +52,10 @@ public DebugServiceTests() { var loggerFactory = new NullLoggerFactory(); - var logger = NullLogger.Instance; + System.Diagnostics.Debugger.Launch(); - this.powerShellContext = PowerShellContextFactory.Create(logger); - this.powerShellContext.SessionStateChanged += powerShellContext_SessionStateChanged; + _psesHost = PsesHostFactory.Create(loggerFactory); + _debuggerStoppedQueue = new BlockingCollection(new ConcurrentQueue()); this.workspace = new WorkspaceService(NullLoggerFactory.Instance); @@ -60,30 +64,14 @@ public DebugServiceTests() this.variableScriptFile = GetDebugScript("VariableTest.ps1"); this.debugService = new DebugService( - this.powerShellContext, - null, - new BreakpointService( - NullLoggerFactory.Instance, - powerShellContext, - new DebugStateService()), - NullLoggerFactory.Instance); + _psesHost, + _psesHost.DebugContext, + remoteFileManager: null, + new BreakpointService(loggerFactory, _psesHost, _psesHost, new DebugStateService()), + _psesHost, + loggerFactory); this.debugService.DebuggerStopped += debugService_DebuggerStopped; - this.debugService.BreakpointUpdated += debugService_BreakpointUpdated; - } - - async void powerShellContext_SessionStateChanged(object sender, SessionStateChangedEventArgs e) - { - // Skip all transitions except those back to 'Ready' - if (e.NewSessionState == PowerShellContextState.Ready) - { - await this.sessionStateQueue.EnqueueAsync(e).ConfigureAwait(false); - } - } - - void debugService_BreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) - { - // TODO: Needed? } void debugService_DebuggerStopped(object sender, DebuggerStoppedEventArgs e) @@ -91,12 +79,12 @@ void debugService_DebuggerStopped(object sender, DebuggerStoppedEventArgs e) // We need to ensure this is run on a different thread than the one it's // called on because it can cause PowerShellContext.OnDebuggerStopped to // never hit the while loop. - Task.Run(() => this.debuggerStoppedQueue.Enqueue(e)); + Task.Run(() => _debuggerStoppedQueue.Add(e)); } public void Dispose() { - this.powerShellContext.Close(); + _psesHost.StopAsync().GetAwaiter().GetResult(); } [Trait("Category", "DebugService")] @@ -109,11 +97,11 @@ public async Task DebuggerAcceptsInlineScript() await this.debugService.SetCommandBreakpointsAsync( new[] { CommandBreakpointDetails.Create("Get-Random") }).ConfigureAwait(false); - Task executeTask = - this.powerShellContext.ExecuteScriptWithArgsAsync( - "Get-Random", string.Join(" ", "-Maximum", "100")); + var psCommand = new PSCommand().AddScript("Get-Random -Maximum 100"); - await this.AssertDebuggerStopped("", 1).ConfigureAwait(false); + Task executeTask = _psesHost.ExecutePSCommandAsync(psCommand, CancellationToken.None); + + AssertDebuggerStopped("", 1); this.debugService.Continue(); await executeTask.ConfigureAwait(false); @@ -153,18 +141,23 @@ public async Task DebuggerAcceptsScriptArgs(string[] args) // it should not escape already escaped chars. ScriptFile debugWithParamsFile = GetDebugScript("Debug W&ith Params [Test].ps1"); - await this.debugService.SetLineBreakpointsAsync( + int bpLine = 2; + BreakpointDetails[] breakpoints = await this.debugService.SetLineBreakpointsAsync( debugWithParamsFile, - new[] { BreakpointDetails.Create(debugWithParamsFile.FilePath, 3) }).ConfigureAwait(false); + new[] { BreakpointDetails.Create(debugWithParamsFile.FilePath, bpLine) }).ConfigureAwait(false); - string arguments = string.Join(" ", args); + Assert.Single(breakpoints); + Assert.Collection(breakpoints, (bp) => + { + Assert.Equal(debugWithParamsFile.FilePath, bp.Source); + Assert.Equal(bpLine, bp.LineNumber); + Assert.True(bp.Verified); + }); // Execute the script and wait for the breakpoint to be hit - Task executeTask = - this.powerShellContext.ExecuteScriptWithArgsAsync( - debugWithParamsFile.FilePath, arguments); + Task executeTask = ExecutePowerShellCommand(this.debugScriptFile.FilePath, args); - await this.AssertDebuggerStopped(debugWithParamsFile.FilePath).ConfigureAwait(false); + AssertDebuggerStopped(debugWithParamsFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -245,12 +238,10 @@ await this.debugService.SetCommandBreakpointsAsync( CommandBreakpointDetails.Create("Write-Host") }).ConfigureAwait(false); - Task executeTask = - this.powerShellContext.ExecuteScriptWithArgsAsync( - this.debugScriptFile.FilePath); + Task executeTask = ExecuteDebugFile(); // Wait for function breakpoint to hit - await this.AssertDebuggerStopped(this.debugScriptFile.FilePath, 6).ConfigureAwait(false); + AssertDebuggerStopped(this.debugScriptFile.FilePath, 6); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); VariableDetailsBase[] variables = @@ -264,7 +255,7 @@ await this.debugService.SetCommandBreakpointsAsync( // The function breakpoint should fire the next time through the loop. this.debugService.Continue(); - await this.AssertDebuggerStopped(this.debugScriptFile.FilePath, 6).ConfigureAwait(false); + AssertDebuggerStopped(this.debugScriptFile.FilePath, 6); stackFrames = debugService.GetStackFrames(); variables = debugService.GetVariables(stackFrames[0].LocalVariables.Id); @@ -331,15 +322,13 @@ await this.debugService.SetLineBreakpointsAsync( BreakpointDetails.Create(this.debugScriptFile.FilePath, 7) }).ConfigureAwait(false); - Task executeTask = - this.powerShellContext.ExecuteScriptWithArgsAsync( - this.debugScriptFile.FilePath); + Task executeTask = ExecuteDebugFile(); // Wait for a couple breakpoints - await this.AssertDebuggerStopped(this.debugScriptFile.FilePath, 5).ConfigureAwait(false); + AssertDebuggerStopped(this.debugScriptFile.FilePath, 5); this.debugService.Continue(); - await this.AssertDebuggerStopped(this.debugScriptFile.FilePath, 7).ConfigureAwait(false); + AssertDebuggerStopped(this.debugScriptFile.FilePath, 7); // Abort script execution early and wait for completion this.debugService.Abort(); @@ -359,12 +348,10 @@ await this.debugService.SetLineBreakpointsAsync( BreakpointDetails.Create(this.debugScriptFile.FilePath, 7, null, $"$i -eq {breakpointValue1} -or $i -eq {breakpointValue2}"), }).ConfigureAwait(false); - Task executeTask = - this.powerShellContext.ExecuteScriptWithArgsAsync( - this.debugScriptFile.FilePath); + Task executeTask = ExecuteDebugFile(); // Wait for conditional breakpoint to hit - await this.AssertDebuggerStopped(this.debugScriptFile.FilePath, 7).ConfigureAwait(false); + AssertDebuggerStopped(this.debugScriptFile.FilePath, 7); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); VariableDetailsBase[] variables = @@ -379,7 +366,7 @@ await this.debugService.SetLineBreakpointsAsync( // The conditional breakpoint should not fire again, until the value of // i reaches breakpointValue2. this.debugService.Continue(); - await this.AssertDebuggerStopped(this.debugScriptFile.FilePath, 7).ConfigureAwait(false); + AssertDebuggerStopped(this.debugScriptFile.FilePath, 7); stackFrames = debugService.GetStackFrames(); variables = debugService.GetVariables(stackFrames[0].LocalVariables.Id); @@ -407,12 +394,10 @@ await this.debugService.SetLineBreakpointsAsync( BreakpointDetails.Create(this.debugScriptFile.FilePath, 6, null, null, $"{hitCount}"), }).ConfigureAwait(false); - Task executeTask = - this.powerShellContext.ExecuteScriptWithArgsAsync( - this.debugScriptFile.FilePath); + Task executeTask = ExecuteDebugFile(); // Wait for conditional breakpoint to hit - await this.AssertDebuggerStopped(this.debugScriptFile.FilePath, 6).ConfigureAwait(false); + AssertDebuggerStopped(this.debugScriptFile.FilePath, 6); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); VariableDetailsBase[] variables = @@ -441,12 +426,10 @@ await this.debugService.SetLineBreakpointsAsync( BreakpointDetails.Create(this.debugScriptFile.FilePath, 6, null, $"$i % 2 -eq 0", $"{hitCount}"), }).ConfigureAwait(false); - Task executeTask = - this.powerShellContext.ExecuteScriptWithArgsAsync( - this.debugScriptFile.FilePath); + Task executeTask = ExecuteDebugFile(); // Wait for conditional breakpoint to hit - await this.AssertDebuggerStopped(this.debugScriptFile.FilePath, 6).ConfigureAwait(false); + AssertDebuggerStopped(this.debugScriptFile.FilePath, 6); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); VariableDetailsBase[] variables = @@ -528,33 +511,20 @@ public async Task DebuggerBreaksWhenRequested() { var confirmedBreakpoints = await this.GetConfirmedBreakpoints(this.debugScriptFile).ConfigureAwait(false); - await this.AssertStateChange( - PowerShellContextState.Ready, - PowerShellExecutionResult.Completed).ConfigureAwait(false); - Assert.False( confirmedBreakpoints.Any(), "Unexpected breakpoint found in script file"); - Task executeTask = - this.powerShellContext.ExecuteScriptStringAsync( - this.debugScriptFile.FilePath); + Task executeTask = ExecuteDebugFile(); // Break execution and wait for the debugger to stop this.debugService.Break(); await this.AssertDebuggerPaused().ConfigureAwait(false); - await this.AssertStateChange( - PowerShellContextState.Ready, - PowerShellExecutionResult.Stopped).ConfigureAwait(false); // Abort execution and wait for the debugger to exit this.debugService.Abort(); - await this.AssertStateChange( - PowerShellContextState.Ready, - PowerShellExecutionResult.Stopped).ConfigureAwait(false); - // Abort script execution early and wait for completion this.debugService.Abort(); await executeTask.ConfigureAwait(false); @@ -564,26 +534,20 @@ await this.AssertStateChange( [Fact] public async Task DebuggerRunsCommandsWhileStopped() { - Task executeTask = - this.powerShellContext.ExecuteScriptStringAsync( - this.debugScriptFile.FilePath); + Task executeTask = ExecuteDebugFile(); // Break execution and wait for the debugger to stop this.debugService.Break(); - await this.AssertStateChange( - PowerShellContextState.Ready, - PowerShellExecutionResult.Stopped).ConfigureAwait(false); // Try running a command from outside the pipeline thread - await this.powerShellContext.ExecuteScriptStringAsync("Get-Command Get-Process").ConfigureAwait(false); + var psCommand = new PSCommand() + .AddCommand("Get-Command") + .AddArgument("Get-Process"); + await _psesHost.ExecutePSCommandAsync(psCommand, CancellationToken.None).ConfigureAwait(false); // Abort execution and wait for the debugger to exit this.debugService.Abort(); - await this.AssertStateChange( - PowerShellContextState.Ready, - PowerShellExecutionResult.Stopped).ConfigureAwait(false); - await executeTask.ConfigureAwait(false); } @@ -596,11 +560,9 @@ await this.debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(this.variableScriptFile.FilePath, 8) }).ConfigureAwait(false); // Execute the script and wait for the breakpoint to be hit - Task executeTask = - this.powerShellContext.ExecuteScriptStringAsync( - this.variableScriptFile.FilePath); + Task executeTask = ExecuteVariableScriptFile(); - await this.AssertDebuggerStopped(this.variableScriptFile.FilePath).ConfigureAwait(false); + AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -626,11 +588,9 @@ await this.debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(this.variableScriptFile.FilePath, 14) }).ConfigureAwait(false); // Execute the script and wait for the breakpoint to be hit - Task executeTask = - this.powerShellContext.ExecuteScriptStringAsync( - this.variableScriptFile.FilePath); + Task executeTask = ExecuteVariableScriptFile(); - await this.AssertDebuggerStopped(this.variableScriptFile.FilePath).ConfigureAwait(false); + AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -678,11 +638,9 @@ await this.debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(this.variableScriptFile.FilePath, 14) }).ConfigureAwait(false); // Execute the script and wait for the breakpoint to be hit - Task executeTask = - this.powerShellContext.ExecuteScriptStringAsync( - this.variableScriptFile.FilePath); + Task executeTask = ExecuteVariableScriptFile(); - await this.AssertDebuggerStopped(this.variableScriptFile.FilePath).ConfigureAwait(false); + AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -712,7 +670,7 @@ await this.debugService.SetLineBreakpointsAsync( // The above just tests that the debug service returns the correct new value string. // Let's step the debugger and make sure the values got set to the new values. this.debugService.StepOver(); - await this.AssertDebuggerStopped(this.variableScriptFile.FilePath).ConfigureAwait(false); + AssertDebuggerStopped(this.variableScriptFile.FilePath); stackFrames = debugService.GetStackFrames(); @@ -749,11 +707,9 @@ await this.debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(this.variableScriptFile.FilePath, 14) }).ConfigureAwait(false); // Execute the script and wait for the breakpoint to be hit - Task executeTask = - this.powerShellContext.ExecuteScriptStringAsync( - this.variableScriptFile.FilePath); + Task executeTask = ExecuteVariableScriptFile(); - await this.AssertDebuggerStopped(this.variableScriptFile.FilePath).ConfigureAwait(false); + AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -785,7 +741,7 @@ await this.debugService.SetLineBreakpointsAsync( // The above just tests that the debug service returns the correct new value string. // Let's step the debugger and make sure the values got set to the new values. this.debugService.StepOver(); - await this.AssertDebuggerStopped(this.variableScriptFile.FilePath).ConfigureAwait(false); + AssertDebuggerStopped(this.variableScriptFile.FilePath); stackFrames = debugService.GetStackFrames(); @@ -822,11 +778,9 @@ await this.debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(this.variableScriptFile.FilePath, 15) }).ConfigureAwait(false); // Execute the script and wait for the breakpoint to be hit - Task executeTask = - this.powerShellContext.ExecuteScriptStringAsync( - this.variableScriptFile.FilePath); + Task executeTask = ExecuteVariableScriptFile(); - await this.AssertDebuggerStopped(this.variableScriptFile.FilePath).ConfigureAwait(false); + AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -852,11 +806,9 @@ await this.debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(this.variableScriptFile.FilePath, 11) }).ConfigureAwait(false); // Execute the script and wait for the breakpoint to be hit - Task executeTask = - this.powerShellContext.ExecuteScriptStringAsync( - this.variableScriptFile.FilePath); + Task executeTask = ExecuteVariableScriptFile(); - await this.AssertDebuggerStopped(this.variableScriptFile.FilePath).ConfigureAwait(false); + AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -898,11 +850,9 @@ await this.debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(this.variableScriptFile.FilePath, 16) }).ConfigureAwait(false); // Execute the script and wait for the breakpoint to be hit - Task executeTask = - this.powerShellContext.ExecuteScriptStringAsync( - this.variableScriptFile.FilePath); + Task executeTask = ExecuteVariableScriptFile(); - await this.AssertDebuggerStopped(this.variableScriptFile.FilePath).ConfigureAwait(false); + AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -928,11 +878,9 @@ await this.debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(this.variableScriptFile.FilePath, 17) }).ConfigureAwait(false); // Execute the script and wait for the breakpoint to be hit - Task executeTask = - this.powerShellContext.ExecuteScriptStringAsync( - this.variableScriptFile.FilePath); + Task executeTask = ExecuteVariableScriptFile(); - await this.AssertDebuggerStopped(this.variableScriptFile.FilePath).ConfigureAwait(false); + AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -965,11 +913,9 @@ await this.debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(this.variableScriptFile.FilePath, 18) }).ConfigureAwait(false); // Execute the script and wait for the breakpoint to be hit - Task executeTask = - this.powerShellContext.ExecuteScriptStringAsync( - this.variableScriptFile.FilePath); + Task executeTask = ExecuteVariableScriptFile(); - await this.AssertDebuggerStopped(this.variableScriptFile.FilePath).ConfigureAwait(false); + AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -1004,11 +950,9 @@ await this.debugService.SetLineBreakpointsAsync( new[] { BreakpointDetails.Create(this.variableScriptFile.FilePath, 19) }).ConfigureAwait(false); // Execute the script and wait for the breakpoint to be hit - Task executeTask = - this.powerShellContext.ExecuteScriptStringAsync( - this.variableScriptFile.FilePath); + Task executeTask = ExecuteVariableScriptFile(); - await this.AssertDebuggerStopped(this.variableScriptFile.FilePath).ConfigureAwait(false); + AssertDebuggerStopped(this.variableScriptFile.FilePath); StackFrameDetails[] stackFrames = debugService.GetStackFrames(); @@ -1030,18 +974,16 @@ await this.debugService.SetLineBreakpointsAsync( private async Task AssertDebuggerPaused() { - DebuggerStoppedEventArgs eventArgs = - await this.debuggerStoppedQueue.DequeueAsync(new CancellationTokenSource(10000).Token).ConfigureAwait(false); + DebuggerStoppedEventArgs eventArgs = _debuggerStoppedQueue.Take(new CancellationTokenSource(10000).Token); Assert.Empty(eventArgs.OriginalEvent.Breakpoints); } - private async Task AssertDebuggerStopped( + private void AssertDebuggerStopped( string scriptPath, int lineNumber = -1) { - DebuggerStoppedEventArgs eventArgs = - await this.debuggerStoppedQueue.DequeueAsync(new CancellationTokenSource(10000).Token).ConfigureAwait(false); + DebuggerStoppedEventArgs eventArgs = _debuggerStoppedQueue.Take(new CancellationTokenSource(10000).Token); // TODO: Why does the casing of the path change? Specifically the Drive letter on Windows. Assert.Equal(scriptPath.ToLower(), eventArgs.ScriptPath.ToLower()); @@ -1051,25 +993,27 @@ private async Task AssertDebuggerStopped( } } - private async Task AssertStateChange( - PowerShellContextState expectedState, - PowerShellExecutionResult expectedResult = PowerShellExecutionResult.Completed) + private void AssertDebuggerStopped() { - SessionStateChangedEventArgs newState = - await this.sessionStateQueue.DequeueAsync(new CancellationTokenSource(10000).Token).ConfigureAwait(false); - - Assert.Equal(expectedState, newState.NewSessionState); - Assert.Equal(expectedResult, newState.ExecutionResult); + Assert.True(_psesHost.DebugContext.IsStopped); } - private async Task> GetConfirmedBreakpoints(ScriptFile scriptFile) + private Task> GetConfirmedBreakpoints(ScriptFile scriptFile) { - return - await this.powerShellContext.ExecuteCommandAsync( + return _psesHost.ExecutePSCommandAsync( new PSCommand() .AddCommand("Get-PSBreakpoint") - .AddParameter("Script", scriptFile.FilePath)).ConfigureAwait(false); + .AddParameter("Script", scriptFile.FilePath), + CancellationToken.None); } + + private Task ExecutePowerShellCommand(string command, params string[] args) + { + return _psesHost.ExecutePSCommandAsync(PSCommandHelpers.BuildPSCommandFromArguments(command, args), CancellationToken.None); + } + + private Task ExecuteDebugFile() => ExecutePowerShellCommand(this.debugScriptFile.FilePath); + + private Task ExecuteVariableScriptFile() => ExecutePowerShellCommand(this.variableScriptFile.FilePath); } - */ } From 3aa3fdc5ee9c852edbd33cb5ebe0edb04e343b33 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 28 Oct 2021 10:49:53 -0700 Subject: [PATCH 176/176] Simplify debug queue --- .../Debugging/DebugServiceTests.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 6f072a4a0..d2daf4f13 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -76,10 +76,7 @@ public DebugServiceTests() void debugService_DebuggerStopped(object sender, DebuggerStoppedEventArgs e) { - // We need to ensure this is run on a different thread than the one it's - // called on because it can cause PowerShellContext.OnDebuggerStopped to - // never hit the while loop. - Task.Run(() => _debuggerStoppedQueue.Add(e)); + _debuggerStoppedQueue.Add(e); } public void Dispose()