Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command to PSReadLine history before cancellation #1841

Merged
merged 1 commit into from
Jun 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ private static bool IsPromptCommand(PSCommand command)
private IReadOnlyList<TResult> ExecuteNormally(CancellationToken cancellationToken)
{
_frame = _psesHost.CurrentFrame;
MaybeAddToHistory(_psCommand);
if (PowerShellExecutionOptions.WriteOutputToHost)
{
_psCommand.AddOutputCommand();
Expand Down Expand Up @@ -188,7 +187,6 @@ private IReadOnlyList<TResult> ExecuteInDebugger(CancellationToken cancellationT
cancellationToken.Register(CancelDebugExecution);

PSDataCollection<PSObject> outputCollection = new();
MaybeAddToHistory(_psCommand);

// Out-Default doesn't work as needed in the debugger
// Instead we add Out-String to the command and collect results in a PSDataCollection
Expand Down Expand Up @@ -355,7 +353,7 @@ private void CancelNormalExecution()
}
}

private void MaybeAddToHistory(PSCommand command)
internal void MaybeAddToHistory()
{
// Do not add PSES internal commands to history. Also exclude input that came from the
// REPL (e.g. PSReadLine) as it handles history itself in that scenario.
Expand All @@ -365,19 +363,19 @@ private void MaybeAddToHistory(PSCommand command)
}

// Only add pure script commands with no arguments to interactive history.
if (command.Commands is { Count: not 1 }
|| command.Commands[0] is { Parameters.Count: not 0 } or { IsScript: false })
if (_psCommand.Commands is { Count: not 1 }
|| _psCommand.Commands[0] is { Parameters.Count: not 0 } or { IsScript: false })
{
return;
}

try
{
_psesHost.AddToHistory(command.Commands[0].CommandText);
_psesHost.AddToHistory(_psCommand.Commands[0].CommandText);
}
catch
{
// Ignore exceptions as the user can register a scriptblock predicate that
// Ignore exceptions as the user can register a script-block predicate that
// determines if the command should be added to history.
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,31 +314,52 @@ public void SetExit()

internal void ForceSetExit() => _shouldExit = true;

public Task<T> InvokeTaskOnPipelineThreadAsync<T>(
SynchronousTask<T> task)
private bool CancelForegroundAndPrepend(ISynchronousTask task, bool isIdle = false)
{
// 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,
// 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
// - skip the next prompt so the task runs instead
// - unblock the consumer thread
using (_taskQueue.BlockConsumers())
//
// 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
// - skip the next prompt so the task runs instead
// - unblock the consumer thread
if (!task.ExecutionOptions.RequiresForeground)
{
return false;
}

_skipNextPrompt = true;

if (task is SynchronousPowerShellTask<PSObject> psTask)
{
psTask.MaybeAddToHistory();
}

using (_taskQueue.BlockConsumers())
{
_taskQueue.Prepend(task);
if (isIdle)
{
CancelIdleParentTask();
}
else
{
CancelCurrentTask();
_taskQueue.Prepend(task);
_skipNextPrompt = true;
}
}

return true;
}

public Task<T> InvokeTaskOnPipelineThreadAsync<T>(SynchronousTask<T> task)
{
if (CancelForegroundAndPrepend(task))
{
return task.Task;
}

// TODO: Apply stashed `QueueTask` function.
switch (task.ExecutionOptions.Priority)
{
case ExecutionPriority.Next:
Expand Down Expand Up @@ -819,7 +840,7 @@ private void DoOneRepl(CancellationToken cancellationToken)
{
UI.WriteLine();
}
// Propogate cancellation if that's what happened, since ReadLine won't.
// Propagate cancellation if that's what happened, since ReadLine won't.
// TODO: We may not need to do this at all.
cancellationToken.ThrowIfCancellationRequested();
return; // Task wasn't canceled but there was no input.
Expand Down Expand Up @@ -1047,15 +1068,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 (CancelForegroundAndPrepend(task, isIdle: true))
{
// 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();
return;
}

Expand All @@ -1082,7 +1096,7 @@ private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args)
_cancellationContext.CancelCurrentTask();

// If the current task was running under the debugger, we need to synchronize the
// cancelation with our debug context (and likely the debug server). Note that if we're
// cancellation with our debug context (and likely the debug server). Note that if we're
// currently stopped in a breakpoint, that means the task is _not_ under the debugger.
if (!CurrentRunspace.Runspace.Debugger.InBreakpoint)
{
Expand Down Expand Up @@ -1166,7 +1180,7 @@ private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStop
// selection and terminating the debugger. Without this, if the "Stop" button is pressed
// then we hit this repeatedly.
//
// This info is publically accessible via `PSDebugContext` but we'd need to access it
// This info is publicly accessible via `PSDebugContext` but we'd need to access it
// via a script. At this point in the call I'd prefer this to be as light as possible so
// we can escape ASAP but we may want to consider switching to that at some point.
if (!Runspace.RunspaceIsRemote && s_scriptDebuggerTriggerObjectProperty is not null)
Expand Down