Thanks to visit codestin.com
Credit goes to github.com

Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

#nullable enable

using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation.Internal;
using System.Diagnostics.CodeAnalysis;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -62,61 +62,54 @@ public static class FeedbackHub
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(millisecondsTimeout);

var localRunspace = runspace as LocalRunspace;
if (localRunspace is null)
if (runspace is not LocalRunspace localRunspace)
{
return null;
}

// Get the last value of $?
bool questionMarkValue = localRunspace.ExecutionContext.QuestionMarkVariableValue;
if (questionMarkValue)
{
return null;
}

// Get the last history item
HistoryInfo[] histories = localRunspace.History.GetEntries(id: 0, count: 1, newest: true);
if (histories.Length == 0)
var providers = SubsystemManager.GetSubsystems<IFeedbackProvider>();
if (providers.Count is 0)
{
return null;
}

HistoryInfo lastHistory = histories[0];
ExecutionContext executionContext = localRunspace.ExecutionContext;
bool questionMarkValue = executionContext.QuestionMarkVariableValue;

// Get the last error
ArrayList errorList = (ArrayList)localRunspace.ExecutionContext.DollarErrorVariable;
if (errorList.Count == 0)
// The command line would have run successfully in most cases during an interactive use of the shell.
// So, we do a quick check to see whether we can skip proceeding, so as to avoid unneeded allocations
// from the 'TryGetFeedbackContext' call below.
if (questionMarkValue && CanSkip(providers))
{
return null;
}

var lastError = errorList[0] as ErrorRecord;
if (lastError is null && errorList[0] is RuntimeException rtEx)
{
lastError = rtEx.ErrorRecord;
}

if (lastError?.InvocationInfo is null || lastError.InvocationInfo.HistoryId != lastHistory.Id)
// Get the last history item
HistoryInfo[] histories = localRunspace.History.GetEntries(id: 0, count: 1, newest: true);
if (histories.Length is 0)
{
return null;
}

var providers = SubsystemManager.GetSubsystems<IFeedbackProvider>();
int count = providers.Count;
if (count == 0)
// Try creating the feedback context object.
if (!TryGetFeedbackContext(executionContext, questionMarkValue, histories[0], out FeedbackContext? feedbackContext))
{
return null;
}

int count = providers.Count;
IFeedbackProvider? generalFeedback = null;
List<Task<FeedbackResult?>>? tasks = null;
CancellationTokenSource? cancellationSource = null;
Func<object?, FeedbackResult?>? callBack = null;

for (int i = 0; i < providers.Count; i++)
foreach (IFeedbackProvider provider in providers)
{
IFeedbackProvider provider = providers[i];
if (!provider.Trigger.HasFlag(feedbackContext.Trigger))
{
continue;
}

if (provider is GeneralCommandErrorFeedback)
{
// This built-in feedback provider needs to run on the target Runspace.
Expand All @@ -128,7 +121,7 @@ public static class FeedbackHub
{
tasks = new List<Task<FeedbackResult?>>(capacity: count);
cancellationSource = new CancellationTokenSource();
callBack = GetCallBack(lastHistory.CommandLine, lastError, cancellationSource);
callBack = GetCallBack(feedbackContext, cancellationSource);
}

// Other feedback providers will run on background threads in parallel.
Expand All @@ -151,33 +144,11 @@ public static class FeedbackHub
List<FeedbackResult>? resultList = null;
if (generalFeedback is not null)
{
bool changedDefault = false;
Runspace? oldDefault = Runspace.DefaultRunspace;

try
{
if (oldDefault != localRunspace)
{
changedDefault = true;
Runspace.DefaultRunspace = localRunspace;
}

FeedbackItem? item = generalFeedback.GetFeedback(lastHistory.CommandLine, lastError, CancellationToken.None);
if (item is not null)
{
resultList ??= new List<FeedbackResult>(count);
resultList.Add(new FeedbackResult(generalFeedback.Id, generalFeedback.Name, item));
}
}
finally
FeedbackResult? builtInResult = GetBuiltInFeedback(generalFeedback, localRunspace, feedbackContext, questionMarkValue);
if (builtInResult is not null)
{
if (changedDefault)
{
Runspace.DefaultRunspace = oldDefault;
}

// Restore $? for the target Runspace.
localRunspace.ExecutionContext.QuestionMarkVariableValue = questionMarkValue;
resultList ??= new List<FeedbackResult>(count);
resultList.Add(builtInResult);
}
}

Expand Down Expand Up @@ -210,17 +181,134 @@ public static class FeedbackHub
return resultList;
}

private static bool CanSkip(IEnumerable<IFeedbackProvider> providers)
{
const FeedbackTrigger possibleTriggerOnSuccess = FeedbackTrigger.Success | FeedbackTrigger.Comment;

bool canSkip = true;
foreach (IFeedbackProvider provider in providers)
{
if ((provider.Trigger & possibleTriggerOnSuccess) != 0)
{
canSkip = false;
break;
}
}

return canSkip;
}

private static FeedbackResult? GetBuiltInFeedback(
IFeedbackProvider builtInFeedback,
LocalRunspace localRunspace,
FeedbackContext feedbackContext,
bool questionMarkValue)
{
bool changedDefault = false;
Runspace? oldDefault = Runspace.DefaultRunspace;

try
{
if (oldDefault != localRunspace)
{
changedDefault = true;
Runspace.DefaultRunspace = localRunspace;
}

FeedbackItem? item = builtInFeedback.GetFeedback(feedbackContext, CancellationToken.None);
if (item is not null)
{
return new FeedbackResult(builtInFeedback.Id, builtInFeedback.Name, item);
}
}
finally
{
if (changedDefault)
{
Runspace.DefaultRunspace = oldDefault;
}

// Restore $? for the target Runspace.
localRunspace.ExecutionContext.QuestionMarkVariableValue = questionMarkValue;
}

return null;
}

private static bool TryGetFeedbackContext(
ExecutionContext executionContext,
bool questionMarkValue,
HistoryInfo lastHistory,
[NotNullWhen(true)] out FeedbackContext? feedbackContext)
{
feedbackContext = null;
Ast ast = Parser.ParseInput(lastHistory.CommandLine, out Token[] tokens, out _);

FeedbackTrigger trigger;
ErrorRecord? lastError = null;

if (IsPureComment(tokens))
{
trigger = FeedbackTrigger.Comment;
}
else if (questionMarkValue)
{
trigger = FeedbackTrigger.Success;
}
else if (TryGetLastError(executionContext, lastHistory, out lastError))
{
trigger = lastError.FullyQualifiedErrorId is "CommandNotFoundException"
? FeedbackTrigger.CommandNotFound
: FeedbackTrigger.Error;
}
else
{
return false;
}

PathInfo cwd = executionContext.SessionState.Path.CurrentLocation;
feedbackContext = new(trigger, ast, tokens, cwd, lastError);
return true;
}

private static bool IsPureComment(Token[] tokens)
{
return tokens.Length is 2 && tokens[0].Kind is TokenKind.Comment && tokens[1].Kind is TokenKind.EndOfInput;
}

private static bool TryGetLastError(ExecutionContext context, HistoryInfo lastHistory, [NotNullWhen(true)] out ErrorRecord? lastError)
{
lastError = null;
ArrayList errorList = (ArrayList)context.DollarErrorVariable;
if (errorList.Count == 0)
{
return false;
}

lastError = errorList[0] as ErrorRecord;
if (lastError is null && errorList[0] is RuntimeException rtEx)
{
lastError = rtEx.ErrorRecord;
}

if (lastError?.InvocationInfo is null || lastError.InvocationInfo.HistoryId != lastHistory.Id)
{
return false;
}

return true;
}

// A local helper function to avoid creating an instance of the generated delegate helper class
// when no feedback provider is registered.
private static Func<object?, FeedbackResult?> GetCallBack(
string commandLine,
ErrorRecord lastError,
FeedbackContext feedbackContext,
CancellationTokenSource cancellationSource)
{
return state =>
{
var provider = (IFeedbackProvider)state!;
var item = provider.GetFeedback(commandLine, lastError, cancellationSource.Token);
var item = provider.GetFeedback(feedbackContext, cancellationSource.Token);
return item is null ? null : new FeedbackResult(provider.Id, provider.Name, item);
};
}
Expand Down
Loading