From 2ec7d5997ee19d19fe4506c8acc07e2e26aba758 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Fri, 30 Sep 2022 09:22:25 -0700 Subject: [PATCH] WIP: Generalized busy indicator --- .../Services/Extension/ExtensionService.cs | 53 +---------------- .../Execution/SynchronousPowerShellTask.cs | 7 ++- .../PowerShell/Host/PsesInternalHost.cs | 58 ++++++++++++------- 3 files changed, 45 insertions(+), 73 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs index 4405a4ce7c..028fe0f198 100644 --- a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs @@ -14,37 +14,6 @@ namespace Microsoft.PowerShell.EditorServices.Services.Extension { - /// 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 - } - /// /// Provides a high-level service which enables PowerShell scripts /// and modules to extend the behavior of the host editor. @@ -154,7 +123,6 @@ internal Task InitializeAsync() /// The command being invoked was not registered. public Task InvokeCommandAsync(string commandName, EditorContext editorContext, CancellationToken cancellationToken) { - _languageServer?.SendNotification("powerShell/executionStatusChanged", ExecutionStatus.Pending); if (editorCommands.TryGetValue(commandName, out EditorCommand editorCommand)) { PSCommand executeCommand = new PSCommand() @@ -163,7 +131,6 @@ public Task InvokeCommandAsync(string commandName, EditorContext editorContext, .AddParameter("ArgumentList", new object[] { editorContext }); // This API is used for editor command execution so it requires the foreground. - _languageServer?.SendNotification("powerShell/executionStatusChanged", ExecutionStatus.Running); return ExecutionService.ExecutePSCommandAsync( executeCommand, cancellationToken, @@ -173,27 +140,9 @@ public Task InvokeCommandAsync(string commandName, EditorContext editorContext, WriteOutputToHost = !editorCommand.SuppressOutput, AddToHistory = !editorCommand.SuppressOutput, ThrowOnError = false, - }).ContinueWith((Task executeTask) => - { - ExecutionStatus status = ExecutionStatus.Failed; - if (executeTask.IsCompleted) - { - status = ExecutionStatus.Completed; - } - else if (executeTask.IsCanceled) - { - status = ExecutionStatus.Aborted; - } - else if (executeTask.IsFaulted) - { - status = ExecutionStatus.Failed; - } - - _languageServer?.SendNotification("powerShell/executionStatusChanged", status); - }, TaskScheduler.Default); + }); } - _languageServer?.SendNotification("powerShell/executionStatusChanged", ExecutionStatus.Failed); throw new KeyNotFoundException($"Editor command not found: '{commandName}'"); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 2fdefbfdcb..257cb82549 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -16,7 +16,12 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution { - internal class SynchronousPowerShellTask : SynchronousTask> + internal interface ISynchronousPowerShellTask + { + PowerShellExecutionOptions PowerShellExecutionOptions { get; } + } + + internal class SynchronousPowerShellTask : SynchronousTask>, ISynchronousPowerShellTask { private static readonly PowerShellExecutionOptions s_defaultPowerShellExecutionOptions = new(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 499757faae..431006318a 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -334,6 +334,23 @@ private bool CancelForegroundAndPrepend(ISynchronousTask task, bool isIdle = fal return true; } + private void ExecuteTaskSynchronously(ISynchronousTask task, CancellationToken cancellationToken) + { + if (task is ISynchronousPowerShellTask t + && (t.PowerShellExecutionOptions.AddToHistory + || t.PowerShellExecutionOptions.FromRepl)) + { + _languageServer?.SendNotification("powerShell/executionBusyStatus", true); + } + try + { + task.ExecuteSynchronously(cancellationToken); + } + finally + { + _languageServer?.SendNotification("powerShell/executionBusyStatus", false); + } + } public Task InvokeTaskOnPipelineThreadAsync(SynchronousTask task) { if (CancelForegroundAndPrepend(task)) @@ -767,14 +784,7 @@ private void RunExecutionLoop(bool isForDebug = false) && !cancellationScope.CancellationToken.IsCancellationRequested && _taskQueue.TryTake(out ISynchronousTask task)) { - try - { - task.ExecuteSynchronously(cancellationScope.CancellationToken); - } - catch (OperationCanceledException e) - { - _logger.LogDebug(e, "Task {Task} was canceled!", task); - } + ExecuteTaskSynchronously(task, cancellationScope.CancellationToken); } if (_shouldExit @@ -935,19 +945,27 @@ private string InvokeReadLine(CancellationToken cancellationToken) } } + // TODO: Should we actually be directly invoking input versus queueing it as a task like everything else? private void InvokeInput(string input, CancellationToken cancellationToken) { - PSCommand command = new PSCommand().AddScript(input, useLocalScope: false); - InvokePSCommand( - command, - new PowerShellExecutionOptions - { - AddToHistory = true, - ThrowOnError = false, - WriteOutputToHost = true, - FromRepl = true, - }, - cancellationToken); + _languageServer?.SendNotification("powerShell/executionBusyStatus", true); + try + { + InvokePSCommand( + new PSCommand().AddScript(input, useLocalScope: false), + new PowerShellExecutionOptions + { + AddToHistory = true, + ThrowOnError = false, + WriteOutputToHost = true, + FromRepl = true, + }, + cancellationToken); + } + finally + { + _languageServer?.SendNotification("powerShell/executionBusyStatus", false); + } } private void AddRunspaceEventHandlers(Runspace runspace) @@ -1085,7 +1103,7 @@ private void OnPowerShellIdle(CancellationToken idleCancellationToken) // TODO: This may not be a PowerShell task, so ideally we can differentiate that here. // For now it's mostly true and an easy assumption to make. runPipelineForEventProcessing = false; - task.ExecuteSynchronously(cancellationScope.CancellationToken); + ExecuteTaskSynchronously(task, cancellationScope.CancellationToken); } }