diff --git a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs index f4284f934..028fe0f19 100644 --- a/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs +++ b/src/PowerShellEditorServices/Services/Extension/ExtensionService.cs @@ -104,10 +104,11 @@ internal Task InitializeAsync() // This is constant so Remove-Variable cannot remove it. PSVariable psEditor = new(PSEditorVariableName, EditorObject, ScopedItemOptions.Constant); - // Register the editor object in the runspace + // NOTE: This is a special task run on startup! Register the editor object in the + // runspace. It has priority next so it goes before LoadProfiles. return ExecutionService.ExecuteDelegateAsync( $"Create ${PSEditorVariableName} object", - executionOptions: null, + new ExecutionOptions { Priority = ExecutionPriority.Next }, (pwsh, _) => pwsh.Runspace.SessionStateProxy.PSVariable.Set(psEditor), CancellationToken.None); } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs index 83de5301c..dbcab3a9c 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/ExecutionOptions.cs @@ -15,12 +15,15 @@ public enum ExecutionPriority // Generally the executor will do the right thing though; some options just priority over others. public record ExecutionOptions { + // This determines which underlying queue the task is added to. public ExecutionPriority Priority { get; init; } = ExecutionPriority.Normal; + // This implies `ExecutionPriority.Next` because foreground tasks are prepended. public bool RequiresForeground { get; init; } } public record PowerShellExecutionOptions : ExecutionOptions { + // TODO: Because of the above, this is actually unnecessary. internal static PowerShellExecutionOptions ImmediateInteractive = new() { Priority = ExecutionPriority.Next, diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 8b2fd79ad..bf3382b85 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; @@ -277,6 +277,8 @@ public void SetExit() public Task InvokeTaskOnPipelineThreadAsync( SynchronousTask task) { + // NOTE: This causes foreground tasks to act like they have `ExecutionPriority.Next`. + // TODO: Deduplicate this. if (task.ExecutionOptions.RequiresForeground) { // When a task must displace the current foreground command, @@ -296,6 +298,7 @@ public Task InvokeTaskOnPipelineThreadAsync( return task.Task; } + // TODO: Apply stashed `QueueTask` function. switch (task.ExecutionOptions.Priority) { case ExecutionPriority.Next: @@ -404,14 +407,10 @@ public void InvokePSDelegate(string representation, ExecutionOptions executionOp internal Task LoadHostProfilesAsync(CancellationToken cancellationToken) { - // TODO: Why exactly does loading profiles require the foreground? + // NOTE: This is a special task run on startup! return ExecuteDelegateAsync( "LoadProfiles", - new PowerShellExecutionOptions - { - RequiresForeground = true, - ThrowOnError = false - }, + new PowerShellExecutionOptions { ThrowOnError = false }, (pwsh, _) => pwsh.LoadProfiles(_hostInfo.ProfilePaths), cancellationToken); } @@ -625,7 +624,10 @@ private void RunTopLevelExecutionLoop() { try { - // Make sure we execute any startup tasks first + // Make sure we execute any startup tasks first. These should be, in order: + // 1. Delegate to register psEditor variable + // 2. LoadProfiles delegate + // 3. Delegate to import PSEditModule while (_taskQueue.TryTake(out ISynchronousTask task)) { task.ExecuteSynchronously(CancellationToken.None); @@ -990,6 +992,8 @@ private void OnPowerShellIdle(CancellationToken idleCancellationToken) while (!cancellationScope.CancellationToken.IsCancellationRequested && _taskQueue.TryTake(out ISynchronousTask task)) { + // NOTE: This causes foreground tasks to act like they have `ExecutionPriority.Next`. + // TODO: Deduplicate this. if (task.ExecutionOptions.RequiresForeground) { // If we have a task that is queued, but cannot be run under readline diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index 43a341191..f0020f122 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -97,7 +97,8 @@ public override async Task Handle(DidChangeConfigurationParams request, Ca } } - // TODO: Load profiles when the host is already running + // TODO: Load profiles when the host is already running? Note that this might mess up + // the ordering and require the foreground. if (!_cwdSet) { if (!string.IsNullOrEmpty(_configurationService.CurrentSettings.Cwd) @@ -124,6 +125,11 @@ await _psesHost.SetInitialWorkingDirectoryAsync( _cwdSet = true; } + // This is another place we call this to setup $psEditor, which really needs to be done + // _before_ profiles. In current testing, this has already been done by the call to + // InitializeAsync when the ExtensionService class is injected. + // + // TODO: Remove this. await _extensionService.InitializeAsync().ConfigureAwait(false); // Run any events subscribed to configuration updates diff --git a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs index 6c0b9bd1c..1229951de 100644 --- a/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs +++ b/src/PowerShellEditorServices/Services/Workspace/RemoteFileManagerService.cs @@ -645,9 +645,10 @@ private async void HandlePSEventReceivedAsync(object sender, PSEventArgs args) } } + // NOTE: This is a special task run on startup! private Task RegisterPSEditFunctionAsync() => _executionService.ExecuteDelegateAsync( - "Register psedit function", + "Register PSEdit function", executionOptions: null, (pwsh, _) => RegisterPSEditFunction(pwsh.Runspace), CancellationToken.None); @@ -673,7 +674,7 @@ private void RegisterPSEditFunction(Runspace runspace) } catch (Exception e) { - logger.LogException("Could not create psedit function.", e); + logger.LogException("Could not create PSEdit function.", e); } finally {