From 9e753a79a839138385da2f1f3ac93f70ef67734c Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 10 May 2021 12:21:00 -0700 Subject: [PATCH 01/73] 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 0f5f5e0f17c6030d3d94cc349e8869c1c3673717 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 20 Apr 2021 11:40:48 -0700 Subject: [PATCH 02/73] Debugging dependency injection --- .../Internal/EditorServicesRunner.cs | 2 +- .../Extensions/EditorObject.cs | 2 +- .../Server/PsesDebugServer.cs | 25 ++++++++++++------- .../Server/PsesServiceCollectionExtensions.cs | 3 ++- .../Services/Extension/ExtensionService.cs | 17 +++++++++---- .../Debugging/PowerShellDebugContext.cs | 11 +++++--- 6 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs index 8cba26b62..b4dbf7c63 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/Extensions/EditorObject.cs b/src/PowerShellEditorServices/Extensions/EditorObject.cs index 6535dcfd8..68747ac27 100644 --- a/src/PowerShellEditorServices/Extensions/EditorObject.cs +++ b/src/PowerShellEditorServices/Extensions/EditorObject.cs @@ -137,7 +137,7 @@ public EditorContext GetEditorContext() internal void SetAsStaticInstance() { EditorObject.Instance = this; - s_editorObjectReady.SetResult(true); + s_editorObjectReady.TrySetResult(true); } } } diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 2ee85da19..dd736e1fa 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() @@ -140,6 +146,7 @@ public async Task StartAsync() public void Dispose() { + _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 744fd88e4d666e015cdac37f3a95643a6e66ed34 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 20 Apr 2021 22:38:07 -0700 Subject: [PATCH 03/73] 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 dd736e1fa..0d1800e10 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 0c21f1919aeb962320e90d7673259442cefe5756 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 10 May 2021 14:28:11 -0700 Subject: [PATCH 04/73] 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 f3b3d663e..5f4418e30 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() @@ -88,28 +94,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 876488b96..1d0c1c3bb 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 a0edd158a..29311c1b1 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 4cc3f7795e5e00e359e51b2cda2d8c41973d881d Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 25 May 2021 15:19:19 -0700 Subject: [PATCH 05/73] 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 cdd4e3ef5dde11d8875d463da73aeada7d4f6885 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 10 May 2021 15:13:11 -0700 Subject: [PATCH 06/73] 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 248008a967c041c4571b103dbdaff588b25add5e Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 21 Jun 2021 15:24:42 -0700 Subject: [PATCH 07/73] 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 291ed3601..8b4568371 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(BreakpointDetails.Create)); } @@ -217,7 +217,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)); } @@ -262,7 +262,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) { @@ -308,7 +308,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 d626c6f7b..38e6af74a 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -407,7 +407,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. @@ -467,7 +467,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}'."); @@ -493,15 +493,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 { @@ -536,8 +541,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 @@ -684,7 +689,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) @@ -790,7 +795,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(); @@ -875,7 +880,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 57104d708..1a13098e8 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 1d0c1c3bb..358364ae0 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 29311c1b1..cdbc44162 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 a001af44590d41faf884b4d7dcd77b4195293a64 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 28 Jun 2021 15:24:21 -0700 Subject: [PATCH 08/73] 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 b5b3325dc4dd75ec582f398f00273874dfd7cd3a Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 20 Jul 2021 13:44:35 -0700 Subject: [PATCH 09/73] 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 5428be921772485829255de6b5b9903cda78a670 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 4 Aug 2021 16:22:30 -0700 Subject: [PATCH 10/73] 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 d3f8e0126846de3548867c95bdfee3daae4c05a1 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 13 Aug 2021 14:23:24 -0700 Subject: [PATCH 11/73] 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 0d1800e10..aca155965 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. @@ -143,7 +140,7 @@ public async Task StartAsync() public void Dispose() { - _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 38e6af74a..3b50634e4 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -38,7 +38,7 @@ internal class DebugService private readonly BreakpointService _breakpointService; private readonly RemoteFileManagerService remoteFileManager; - private readonly EditorServicesConsolePSHost _psesHost; + private readonly InternalHost _psesHost; private readonly IPowerShellDebugContext _debugContext; @@ -108,7 +108,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 4aacea4b9..a90c0df82 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 502e4bb61cad6fba67c7f3fc1418da06a02975dc Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 24 Aug 2021 11:37:52 -0700 Subject: [PATCH 12/73] 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 8b4568371..fc10bde8f 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 9ddfd9c55845d3cdb7b30585ec1ba4233137e981 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 30 Aug 2021 11:37:22 -0700 Subject: [PATCH 13/73] 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 942e7a66d61c411134aff75abdf78cfeeb7e6d7f Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 30 Aug 2021 12:15:58 -0700 Subject: [PATCH 14/73] 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 ec9517f1a2e3360700d423fc95d04740497ad99f Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 30 Aug 2021 13:29:05 -0700 Subject: [PATCH 15/73] 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 d50ea57d356424f3d72f08b3be9ad9eae55c4696 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 30 Aug 2021 13:29:14 -0700 Subject: [PATCH 16/73] 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 22d6088ca46313aa389f853d83205510a6231561 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 30 Aug 2021 16:22:35 -0700 Subject: [PATCH 17/73] 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 6883f1ee16b3e9ef2602c6b44f95c38c4badcf0b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 1 Sep 2021 15:16:31 -0700 Subject: [PATCH 18/73] 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 5fa2c4ae128492273bfe1ea41aa053d246d3f78e Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 2 Sep 2021 11:19:52 -0700 Subject: [PATCH 19/73] Enable proper debug UI interaction --- .../DebugAdapter/DebugEventHandlerService.cs | 8 ++++---- .../Debugging/DebuggerResumingEventArgs.cs | 12 ++---------- .../PowerShell/Debugging/PowerShellDebugContext.cs | 6 +++--- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs index 5f4418e30..3ea4d467d 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 @@ -124,13 +124,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 { AllThreadsContinued = true, - ThreadId = 1 + 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 aaf27f74596e443fd4ef9358e2772038e6e6b809 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 3 Sep 2021 10:10:20 -0700 Subject: [PATCH 20/73] 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 d4ec378472f4c46b64cee853240af74408d01641 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 8 Sep 2021 16:47:32 -0700 Subject: [PATCH 21/73] 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 fc10bde8f..9aa5dc931 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) { @@ -150,7 +152,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) { @@ -232,7 +234,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, @@ -272,7 +274,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 b0af038ab221bf628651a1d26951a1338a041835 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 9 Sep 2021 16:44:29 -0700 Subject: [PATCH 22/73] 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 1a13098e8..19a6613b3 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 a1c94be5e986f7aea9c5a1b803dba5b3ba509760 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 14 Sep 2021 16:45:42 -0700 Subject: [PATCH 23/73] 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 aca155965..5e951d28d 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 3b50634e4..81474461a 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -696,6 +696,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 b5aab6e1bd9368887f2d9e918928d17f0d22369e Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 14 Sep 2021 17:02:01 -0700 Subject: [PATCH 24/73] 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 b66cb0ae45734373c810ea67b6a18a0d023ebf4a Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 14 Sep 2021 17:03:36 -0700 Subject: [PATCH 25/73] 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 e8bc3413c2e4e81b8fb27cec9c641fa67d2793c6 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:33:52 -0700 Subject: [PATCH 26/73] 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 5e951d28d..b2ff684b6 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 822f0cc00d04b869ad0873f966bb8a7b3e039a2d Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:34:27 -0700 Subject: [PATCH 27/73] 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 3ea4d467d..0e42d1490 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 @@ -124,7 +124,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 a08855f1b5ce3c1679d94402b084f521362d6e57 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:35:06 -0700 Subject: [PATCH 28/73] 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 81474461a..5250f10a5 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -407,7 +407,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 797d38bceb404f471a73d89b673a01709d57feae Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:36:40 -0700 Subject: [PATCH 29/73] 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 c8a691fcb5f97f29d76388c97fd1e0748418094a Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:37:22 -0700 Subject: [PATCH 30/73] 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 a6789aac1fd171d905efbb50af246742a06b74ed Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:39:02 -0700 Subject: [PATCH 31/73] 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 2d167356c003a5ddd070568500020ebab56bfdda Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:40:37 -0700 Subject: [PATCH 32/73] 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 b39e1fa5a236620cf1e7bfc982432703f806e421 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 09:51:30 -0700 Subject: [PATCH 33/73] 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 298cbf172fb1ea38f33079eb39a209d9ccc8e554 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 10:16:55 -0700 Subject: [PATCH 34/73] 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 5250f10a5..ae6077b2b 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -496,7 +496,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"); @@ -505,7 +504,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 e9521ffcdd4a1660ae2a955885fd26e1c6d7a8fc Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 15 Sep 2021 10:27:12 -0700 Subject: [PATCH 35/73] 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 c9b782fbbe7cdc8b7d33ca1bc9a1bda95e116e51 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 5 Oct 2021 15:52:05 -0700 Subject: [PATCH 36/73] 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 b2ff684b6..8b2459f6c 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 9aa5dc931..72f9deb74 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 ae6077b2b..f26ab98ce 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -38,7 +38,7 @@ internal class DebugService private readonly BreakpointService _breakpointService; private readonly RemoteFileManagerService remoteFileManager; - private readonly InternalHost _psesHost; + private readonly PsesInternalHost _psesHost; private readonly IPowerShellDebugContext _debugContext; @@ -108,7 +108,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 a90c0df82..25b6c2d6a 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 c39e298dbeb41d454acdd5e37fad8f679887e2ac Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 5 Oct 2021 16:10:54 -0700 Subject: [PATCH 37/73] 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 358364ae0..15521baa3 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 fc4f0f41af2c7ea641297102bf8c9c9617c13c2f Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 14:36:10 -0700 Subject: [PATCH 38/73] 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 fdb3ea3533584da418df529206df03fafae1f9d4 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:16:58 -0700 Subject: [PATCH 39/73] 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 | 617 ---- .../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 | 2835 ----------------- .../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 | 1070 ------- .../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 | 327 -- .../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 | 204 -- .../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, 11269 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 75461c1a4..89be0883e 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -45,7 +45,6 @@ - diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoicePromptHandler.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Console/ChoicePromptHandler.cs deleted file mode 100644 index a284fa49c..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 this.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 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 3223eb1d0..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleReadLine.cs +++ /dev/null @@ -1,617 +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 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/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 151ea9a88..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 this.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 8ed801ea5..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 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/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 a3000c549..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs +++ /dev/null @@ -1,2835 +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 - { - private static readonly string s_commandsModulePath = Path.GetFullPath( - Path.Combine( - Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), - "../../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; } - - internal bool IsDebugServerActive { 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) - { - logger.LogTrace("Instantiating PowerShellContextService and adding event handlers"); - _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) - { - Validate.IsNotNull(nameof(hostStartupInfo), hostStartupInfo); - - var logger = factory.CreateLogger(); - - 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); - - logger.LogTrace("Creating initial PowerShell runspace"); - Runspace initialRunspace = PowerShellContextService.CreateRunspace(psHost, hostStartupInfo.LanguageMode); - 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; - } - - /// - /// - /// - /// - /// - /// The EditorServicesPSHostUserInterface to use for this instance. - /// An ILogger implementation to use for this instance. - /// - public static Runspace CreateRunspace( - 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.LanguageMode); - } - - /// - /// - /// - /// The PSHost that will be used for this Runspace. - /// The language mode inherited from the orginal PowerShell process. This will be used when creating runspaces so that we honor the same language mode. - /// - public static Runspace CreateRunspace(PSHost psHost, PSLanguageMode languageMode) - { - InitialSessionState initialSessionState; - if (Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1") { - initialSessionState = InitialSessionState.CreateDefault(); - } else { - initialSessionState = InitialSessionState.CreateDefault2(); - } - - // Create and initialize a new Runspace while honoring the LanguageMode of the original runspace - // that started PowerShell Editor Services. This is because the PowerShell Integrated Console - // should have the same LanguageMode of whatever is set by the system. - initialSessionState.LanguageMode = languageMode; - - 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. - public void Initialize( - ProfilePathInfo profilePaths, - Runspace initialRunspace, - bool ownsInitialRunspace) - { - this.Initialize(profilePaths, initialRunspace, ownsInitialRunspace, consoleHost: null); - } - - /// - /// 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.logger.LogTrace($"Initializing PowerShell context with runspace {initialRunspace.Name}"); - - 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 - { - 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, out PSReadLineProxy proxy)) - { - this.PromptContext = new PSReadLinePromptContext( - this, - this.PromptNest, - this.InvocationEventQueue, - proxy); - } - else - { - this.PromptContext = new LegacyReadLineContext(this); - } - - if (VersionUtils.IsWindows) - { - this.SetExecutionPolicy(); - } - } - - /// - /// Imports the PowerShellEditorServices.Commands module into - /// the runspace. This method will be moved somewhere else soon. - /// - /// - public Task ImportCommandsModuleAsync() => ImportCommandsModuleAsync(s_commandsModulePath); - - public Task ImportCommandsModuleAsync(string path) - { - this.logger.LogTrace($"Importing PowershellEditorServices commands from {path}"); - - PSCommand importCommand = new PSCommand() - .AddCommand("Import-Module") - .AddArgument(path); - - 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(CancellationToken.None, isReadLine: false); - } - - /// - /// 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(cancellationToken, isReadLine: false); - } - - /// - /// 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) - { - return ExecuteCommandAsync(psCommand, errorMessages: null, sendOutputToHost, sendErrorToHost); - } - - /// - /// 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) - { - return - this.ExecuteCommandAsync( - psCommand, - errorMessages, - new ExecutionOptions - { - WriteOutputToHost = sendOutputToHost, - WriteErrorsToHost = sendErrorToHost, - AddToHistory = addToHistory - }); - } - - - /// - /// 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. - /// 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) - { - Validate.IsNotNull(nameof(psCommand), psCommand); - Validate.IsNotNull(nameof(executionOptions), executionOptions); - - this.logger.LogTrace($"Attempting to execute command(s): {GetStringForPSCommand(psCommand)}"); - - // 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))) - { - this.logger.LogTrace("Passing command execution to pipeline thread"); - - 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 - { - 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 - { - // 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); - } - - // May need a cancellation token here - return await Task.Run>(() => shell.Invoke(input: null, invocationSettings), CancellationToken.None).ConfigureAwait(false); - } - finally - { - if (!executionOptions.IsReadLine) - { - shell.InvocationStateChanged -= PowerShell_InvocationStateChanged; - await this.sessionStateLock.ReleaseForExecuteCommand().ConfigureAwait(false); - } - - 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; - } - else - { - this.logger.LogTrace("Execution completed successfully"); - } - } - } - catch (PSRemotingDataStructureException e) - { - this.logger.LogHandledException("Pipeline stopped 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().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) - { - // Need to determine If the script string is a path to a script file. - string scriptAbsPath = string.Empty; - 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; - - workingDir = workingDir.TrimEnd(Path.DirectorySeparatorChar); - scriptAbsPath = workingDir + Path.DirectorySeparatorChar + 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) || File.Exists(scriptAbsPath)) - { - // 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(); - this.logger.LogTrace($"Launch script is: {launchedScript}"); - - 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) - { - if (this.SessionState == PowerShellContextState.Aborting - || this.SessionState == PowerShellContextState.Disposed) - { - this.logger.LogTrace($"Execution abort requested when already aborted (SessionState = {this.SessionState})"); - return; - } - - this.logger.LogTrace("Execution abort requested..."); - - if (shouldAbortDebugSession) - { - this.ExitAllNestedPrompts(); - } - - if (this.PromptNest.IsInDebugger) - { - this.versionSpecificOperations.StopCommandInDebugger(this); - if (shouldAbortDebugSession) - { - this.ResumeDebugger(DebuggerResumeAction.Stop); - } - } - 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.LogDebug("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(CancellationToken.None, isReadLine); - } - - private Task GetRunspaceHandleImplAsync(CancellationToken cancellationToken, bool isReadLine) - { - return this.PromptNest.GetRunspaceHandleAsync(cancellationToken, isReadLine); - } - - 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( - string.Format( - "Session state changed --\r\n\r\n Old state: {0}\r\n New state: {1}\r\n Result: {2}", - this.SessionState.ToString(), - e.NewSessionState.ToString(), - 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) - { - this.logger.LogTrace("Sending runspaceChanged notification"); - - _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) - { - this.logger.LogTrace("Sending executionStatusChanged notification"); - - // 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) - { - this.logger.LogTrace($"Attempting to execute command(s)a in the debugger: {GetStringForPSCommand(psCommand)}"); - - 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(); - } - - private void SetExecutionPolicy() - { - this.logger.LogTrace("Setting 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 (!IsDebugServerActive) - { - _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 37822cb43..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("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 0df1224dc..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHostUserInterface.cs +++ /dev/null @@ -1,1070 +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).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 eea9276b8..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/TerminalPSHostRawUserInterface.cs +++ /dev/null @@ -1,327 +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); - } - - #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 ec14ab7bb..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(CancellationToken.None, isReadLine: false)) - { - 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 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 272830b9e..000000000 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs +++ /dev/null @@ -1,204 +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 string _psReadLineModulePath = Path.Combine( - Path.GetDirectoryName(typeof(PSReadLinePromptContext).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] - }}"; - - 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, - out PSReadLineProxy readLineProxy) - { - readLineProxy = null; - logger.LogTrace("Attempting to load PSReadLine"); - using (var pwsh = PowerShell.Create()) - { - pwsh.Runspace = runspace; - var psReadLineType = pwsh - .AddScript(ReadLineInitScript, useLocalScope: true) - .Invoke() - .FirstOrDefault(); - - if (psReadLineType == null) - { - logger.LogWarning("PSReadLine unable to be loaded: {Reason}", pwsh.HadErrors ? pwsh.Streams.Error[0].ToString() : ""); - 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("PSReadLine unable to be loaded: {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).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(CancellationToken.None, isReadLine: true)) - { } - } - - public async Task WaitForReadLineExitAsync() { - using (await _promptNest.GetRunspaceHandleAsync(CancellationToken.None, isReadLine: true).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 482ec6a31..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(CancellationToken cancellationToken, bool isReadLine) - { - 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(cancellationToken, isReadLine: false); - } - - return GetRunspaceHandleImpl(cancellationToken, isReadLine); - } - - - /// - /// 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(CancellationToken cancellationToken, bool isReadLine) - { - 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(cancellationToken, isReadLine: false).ConfigureAwait(false); - } - - return await GetRunspaceHandleImplAsync(cancellationToken, isReadLine).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(CancellationToken cancellationToken, bool isReadLine) - { - if (isReadLine) - { - return _readLineFrame.Queue.Dequeue(cancellationToken); - } - - return CurrentFrame.Queue.Dequeue(cancellationToken); - } - - private async Task GetRunspaceHandleImplAsync(CancellationToken cancellationToken, bool isReadLine) - { - 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 bbf838cdf09e484bcf4b7725b9a1b2b5587a72d0 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:38:26 -0700 Subject: [PATCH 40/73] Ensure ConfigureAwait(false) is used everywhere --- .../Extensions/Api/EditorUIService.cs | 12 +++++++++--- .../Services/DebugAdapter/BreakpointService.cs | 12 ++++++------ .../Services/DebugAdapter/DebugService.cs | 8 +++++--- .../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(+), 19 deletions(-) diff --git a/src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs b/src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs index 1294ddebe..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); + }) + .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); + }) + .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); + }) + .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 72f9deb74..47434366c 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(BreakpointDetails.Create)); } @@ -219,7 +219,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)); } @@ -310,7 +310,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 f26ab98ce..1158ff659 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -147,7 +147,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 @@ -196,7 +196,8 @@ public async Task SetLineBreakpointsAsync( return await dscBreakpoints.SetLineBreakpointsAsync( _executionService, escapedScriptPath, - breakpoints); + breakpoints) + .ConfigureAwait(false); } /// @@ -214,7 +215,8 @@ public async Task SetCommandBreakpointsAsync( if (clearExisting) { // Flatten dictionary values into one list and remove them all. - await _breakpointService.RemoveBreakpointsAsync((await _breakpointService.GetBreakpointsAsync()).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 15521baa3..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); // 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 25b6c2d6a..2ef125210 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 fbdd81812d7d1498704d788e086a5192b189b3d0 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:42:30 -0700 Subject: [PATCH 41/73] 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 1158ff659..48a083b63 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -544,7 +544,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 bd07885c9bcdfd26134b9f2c2c40d00e70c6a699 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:43:09 -0700 Subject: [PATCH 42/73] 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 48a083b63..713959812 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -698,6 +698,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 32074810eb383e2979739e71927ae03119227f22 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:44:17 -0700 Subject: [PATCH 43/73] 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 98272ffa1e2a60743d847b2581b91e722df25c77 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:46:16 -0700 Subject: [PATCH 44/73] 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 19a6613b3..e5f9141f1 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 2230ef955b6fba618bc253fe207ebb9715b0b4ca Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:47:31 -0700 Subject: [PATCH 45/73] 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 bd19fae7d6add3baac789933ddda64ce80225c10 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:52:26 -0700 Subject: [PATCH 46/73] 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 1fd44f6b2f72442bae45556a823a06ae9e567617 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 15:57:02 -0700 Subject: [PATCH 47/73] 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 0e42d1490..ea27248bd 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs @@ -118,6 +118,7 @@ private void ExecutionService_RunspaceChanged(object sender, RunspaceChangedEven new ContinuedEvent { ThreadId = ThreadsHandler.PipelineThread.Id, + AllThreadsContinued = true, }); } return; From 22868f6d2ec800aed1e58bc2e150ffaa3be66f2a Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 16:00:12 -0700 Subject: [PATCH 48/73] Add comment about debug context lifetime --- src/PowerShellEditorServices/Server/PsesDebugServer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 8b2459f6c..f83d328c6 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -143,6 +143,9 @@ public async Task StartAsync() 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; _debugAdapterServer.Dispose(); _inputStream.Dispose(); From 09b962dffb6858ae8fd41d01c5c0350629d9e399 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 7 Oct 2021 16:01:44 -0700 Subject: [PATCH 49/73] 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 e19878aa781d7d879bb16be94894901b6b02b075 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 12:44:12 -0700 Subject: [PATCH 50/73] 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 47434366c..f1fae98d3 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) { @@ -152,7 +150,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) { @@ -234,7 +232,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, @@ -274,7 +272,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 c2797d1beb13f472585630017390c4d6b3bc8986 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 14:36:24 -0700 Subject: [PATCH 51/73] 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 0f62b1d12fe915964b1fc8107e6488867daa3ee8 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 15:54:19 -0700 Subject: [PATCH 52/73] 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 1a621252db03770bd607b0653bdaaeaf69aeac45 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 15:56:03 -0700 Subject: [PATCH 53/73] 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 0480ab6f12eaa5b7fd1564afcbad82aa82cb46db Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 15:59:32 -0700 Subject: [PATCH 54/73] 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 1d1cab8cafdd5400defabb2ecf2b17a4e9f846de Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 16:10:11 -0700 Subject: [PATCH 55/73] 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 029539a8c4931bbd5f6c6efd2a8304f1c043fbaf Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 16:16:46 -0700 Subject: [PATCH 56/73] 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 03f18aa52c39092cb4d98e741e0768a12d351009 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 16:19:11 -0700 Subject: [PATCH 57/73] 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 a07e7dae97c91f120d604caec10319afef714d2f Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 16:21:26 -0700 Subject: [PATCH 58/73] 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 25d38a16308f3a131903482f1ed4b2903a81c035 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 16:37:12 -0700 Subject: [PATCH 59/73] 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 268eb5fa9497f975d8b1040785a04584b1140745 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 16:56:32 -0700 Subject: [PATCH 60/73] 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 15b16f031028d79cd19e4c5cfab00719a97d6e9f Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 17:02:01 -0700 Subject: [PATCH 61/73] 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 1e98e9c60f901854ed0430b8224b9d56de55424b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 17:04:01 -0700 Subject: [PATCH 62/73] 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 d21efeb6d694ab80e89d39c9c2dc9656d4244d75 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 17:07:44 -0700 Subject: [PATCH 63/73] 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 543dabe0646fc56d589c15c6d385c03b07da170c Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 8 Oct 2021 17:09:07 -0700 Subject: [PATCH 64/73] 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 963fba1ea4727095a2dca68890cec67132755fcb Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 10:14:52 -0700 Subject: [PATCH 65/73] 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 ea27248bd..3e6dd4ea1 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 @@ -92,7 +92,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) { @@ -135,7 +135,7 @@ private void OnDebuggerResuming(object sender, DebuggerResumingEventArgs e) }); } - private void DebugService_BreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) + private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) { string reason = "changed"; From f5b644ecc728e9eed253d62d50df23f839e7f5e0 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 10:22:23 -0700 Subject: [PATCH 66/73] 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 f83d328c6..cc745a097 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 905686c9d157d08ebf4acb297b647fea082a09ea Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 11:23:59 -0700 Subject: [PATCH 67/73] 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 f1fae98d3..d6fc1365d 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 3e6dd4ea1..979db42e1 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 713959812..ab967a638 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; @@ -104,7 +104,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 e5f9141f1..0a6b26f44 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 411e41d85..7b1563fc6 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 6ffcdac5b..272ca1060 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 cdbc44162..c70ba24c1 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 0a6993754dae6e7250d51b7b58bc732cdaa64309 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 11:29:26 -0700 Subject: [PATCH 68/73] 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 da786c854901aaa6440d22a085bf6b57df060a47 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 11:30:38 -0700 Subject: [PATCH 69/73] 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 c70ba24c1..0ead188aa 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 fb08b6792f7b5d083ccc1536592642de77283ed9 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 11:33:00 -0700 Subject: [PATCH 70/73] 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 0ead188aa..cc103bcfe 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 1f52ddee780ee33b8115b671b8f366aa31b1d648 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 11:34:32 -0700 Subject: [PATCH 71/73] 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 27a17358b2b7a2fd1bcf7cee8c086bf7bf29852b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 11:49:54 -0700 Subject: [PATCH 72/73] 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 87022dc12939816b5ada7602356f8f74f2c4eebd Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 11 Oct 2021 14:47:48 -0700 Subject: [PATCH 73/73] 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 cc103bcfe..c1c7d8389 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 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); } }