mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Update the ICommandPredictor to provide more feedback and also make feedback easier to be corelated (#14649)
This commit is contained in:
+56
-14
@@ -27,15 +27,22 @@ namespace System.Management.Automation.Subsystem
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mini-session id that represents a specific invocation to the <see cref="ICommandPredictor.GetSuggestion"/> API of the predictor.
|
||||
/// When it's not specified, it's considered by a client that the predictor doesn't expect feedback.
|
||||
/// </summary>
|
||||
public uint? Session { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the suggestions.
|
||||
/// </summary>
|
||||
public IReadOnlyList<PredictiveSuggestion> Suggestions { get; }
|
||||
|
||||
internal PredictionResult(Guid id, string name, List<PredictiveSuggestion> suggestions)
|
||||
internal PredictionResult(Guid id, string name, uint? session, List<PredictiveSuggestion> suggestions)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Session = session;
|
||||
Suggestions = suggestions;
|
||||
}
|
||||
}
|
||||
@@ -48,22 +55,24 @@ namespace System.Management.Automation.Subsystem
|
||||
/// <summary>
|
||||
/// Collect the predictive suggestions from registered predictors using the default timeout.
|
||||
/// </summary>
|
||||
/// <param name="client">Represents the client that initiates the call.</param>
|
||||
/// <param name="ast">The <see cref="Ast"/> object from parsing the current command line input.</param>
|
||||
/// <param name="astTokens">The <see cref="Token"/> objects from parsing the current command line input.</param>
|
||||
/// <returns>A list of <see cref="PredictionResult"/> objects.</returns>
|
||||
public static Task<List<PredictionResult>?> PredictInput(Ast ast, Token[] astTokens)
|
||||
public static Task<List<PredictionResult>?> PredictInput(string client, Ast ast, Token[] astTokens)
|
||||
{
|
||||
return PredictInput(ast, astTokens, millisecondsTimeout: 20);
|
||||
return PredictInput(client, ast, astTokens, millisecondsTimeout: 20);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collect the predictive suggestions from registered predictors using the specified timeout.
|
||||
/// </summary>
|
||||
/// <param name="client">Represents the client that initiates the call.</param>
|
||||
/// <param name="ast">The <see cref="Ast"/> object from parsing the current command line input.</param>
|
||||
/// <param name="astTokens">The <see cref="Token"/> objects from parsing the current command line input.</param>
|
||||
/// <param name="millisecondsTimeout">The milliseconds to timeout.</param>
|
||||
/// <returns>A list of <see cref="PredictionResult"/> objects.</returns>
|
||||
public static async Task<List<PredictionResult>?> PredictInput(Ast ast, Token[] astTokens, int millisecondsTimeout)
|
||||
public static async Task<List<PredictionResult>?> PredictInput(string client, Ast ast, Token[] astTokens, int millisecondsTimeout)
|
||||
{
|
||||
Requires.Condition(millisecondsTimeout > 0, nameof(millisecondsTimeout));
|
||||
|
||||
@@ -85,8 +94,8 @@ namespace System.Management.Automation.Subsystem
|
||||
state =>
|
||||
{
|
||||
var predictor = (ICommandPredictor)state!;
|
||||
List<PredictiveSuggestion>? texts = predictor.GetSuggestion(context, cancellationSource.Token);
|
||||
return texts?.Count > 0 ? new PredictionResult(predictor.Id, predictor.Name, texts) : null;
|
||||
SuggestionPackage pkg = predictor.GetSuggestion(client, context, cancellationSource.Token);
|
||||
return pkg.SuggestionEntries?.Count > 0 ? new PredictionResult(predictor.Id, predictor.Name, pkg.Session, pkg.SuggestionEntries) : null;
|
||||
},
|
||||
predictor,
|
||||
cancellationSource.Token,
|
||||
@@ -99,7 +108,7 @@ namespace System.Management.Automation.Subsystem
|
||||
Task.Delay(millisecondsTimeout, cancellationSource.Token)).ConfigureAwait(false);
|
||||
cancellationSource.Cancel();
|
||||
|
||||
var results = new List<PredictionResult>(predictors.Count);
|
||||
var resultList = new List<PredictionResult>(predictors.Count);
|
||||
foreach (Task<PredictionResult?> task in tasks)
|
||||
{
|
||||
if (task.IsCompletedSuccessfully)
|
||||
@@ -107,19 +116,20 @@ namespace System.Management.Automation.Subsystem
|
||||
PredictionResult? result = task.Result;
|
||||
if (result != null)
|
||||
{
|
||||
results.Add(result);
|
||||
resultList.Add(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
return resultList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow registered predictors to do early processing when a command line is accepted.
|
||||
/// </summary>
|
||||
/// <param name="client">Represents the client that initiates the call.</param>
|
||||
/// <param name="history">History command lines provided as references for prediction.</param>
|
||||
public static void OnCommandLineAccepted(IReadOnlyList<string> history)
|
||||
public static void OnCommandLineAccepted(string client, IReadOnlyList<string> history)
|
||||
{
|
||||
Requires.NotNull(history, nameof(history));
|
||||
|
||||
@@ -134,7 +144,7 @@ namespace System.Management.Automation.Subsystem
|
||||
if (predictor.SupportEarlyProcessing)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem<ICommandPredictor>(
|
||||
state => state.StartEarlyProcessing(history),
|
||||
state => state.StartEarlyProcessing(client, history),
|
||||
predictor,
|
||||
preferLocal: false);
|
||||
}
|
||||
@@ -142,11 +152,43 @@ namespace System.Management.Automation.Subsystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send feedback to predictors about their last suggestions.
|
||||
/// Send feedback to a predictor when one or more suggestions from it were displayed to the user.
|
||||
/// </summary>
|
||||
/// <param name="client">Represents the client that initiates the call.</param>
|
||||
/// <param name="predictorId">The identifier of the predictor whose prediction result was accepted.</param>
|
||||
/// <param name="session">The mini-session where the displayed suggestions came from.</param>
|
||||
/// <param name="countOrIndex">
|
||||
/// When the value is greater than 0, it's the number of displayed suggestions from the list returned in <paramref name="session"/>, starting from the index 0.
|
||||
/// When the value is less than or equal to 0, it means a single suggestion from the list got displayed, and the index is the absolute value.
|
||||
/// </param>
|
||||
public static void OnSuggestionDisplayed(string client, Guid predictorId, uint session, int countOrIndex)
|
||||
{
|
||||
var predictors = SubsystemManager.GetSubsystems<ICommandPredictor>();
|
||||
if (predictors.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ICommandPredictor predictor in predictors)
|
||||
{
|
||||
if (predictor.AcceptFeedback && predictor.Id == predictorId)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem<ICommandPredictor>(
|
||||
state => state.OnSuggestionDisplayed(client, session, countOrIndex),
|
||||
predictor,
|
||||
preferLocal: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send feedback to a predictor when a suggestion from it was accepted.
|
||||
/// </summary>
|
||||
/// <param name="client">Represents the client that initiates the call.</param>
|
||||
/// <param name="predictorId">The identifier of the predictor whose prediction result was accepted.</param>
|
||||
/// <param name="session">The mini-session where the accepted suggestion came from.</param>
|
||||
/// <param name="suggestionText">The accepted suggestion text.</param>
|
||||
public static void OnSuggestionAccepted(Guid predictorId, string suggestionText)
|
||||
public static void OnSuggestionAccepted(string client, Guid predictorId, uint session, string suggestionText)
|
||||
{
|
||||
Requires.NotNullOrEmpty(suggestionText, nameof(suggestionText));
|
||||
|
||||
@@ -161,7 +203,7 @@ namespace System.Management.Automation.Subsystem
|
||||
if (predictor.AcceptFeedback && predictor.Id == predictorId)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem<ICommandPredictor>(
|
||||
state => state.OnSuggestionAccepted(suggestionText),
|
||||
state => state.OnSuggestionAccepted(client, session, suggestionText),
|
||||
predictor,
|
||||
preferLocal: false);
|
||||
}
|
||||
|
||||
+68
-10
@@ -40,22 +40,37 @@ namespace System.Management.Automation.Subsystem
|
||||
/// A command line was accepted to execute.
|
||||
/// The predictor can start processing early as needed with the latest history.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Represents the client that initiates the call.</param>
|
||||
/// <param name="history">History command lines provided as references for prediction.</param>
|
||||
void StartEarlyProcessing(IReadOnlyList<string> history);
|
||||
void StartEarlyProcessing(string clientId, IReadOnlyList<string> history);
|
||||
|
||||
/// <summary>
|
||||
/// The suggestion given by the predictor was accepted.
|
||||
/// </summary>
|
||||
/// <param name="acceptedSuggestion">The accepted suggestion text.</param>
|
||||
void OnSuggestionAccepted(string acceptedSuggestion);
|
||||
|
||||
/// <summary>
|
||||
/// Get the predictive suggestions.
|
||||
/// Get the predictive suggestions. It indicates the start of a suggestion rendering session.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Represents the client that initiates the call.</param>
|
||||
/// <param name="context">The <see cref="PredictionContext"/> object to be used for prediction.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel the prediction.</param>
|
||||
/// <returns>A list of predictive suggestions.</returns>
|
||||
List<PredictiveSuggestion>? GetSuggestion(PredictionContext context, CancellationToken cancellationToken);
|
||||
/// <returns>An instance of <see cref="SuggestionPackage"/>.</returns>
|
||||
SuggestionPackage GetSuggestion(string clientId, PredictionContext context, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// One or more suggestions provided by the predictor were displayed to the user.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Represents the client that initiates the call.</param>
|
||||
/// <param name="session">The mini-session where the displayed suggestions came from.</param>
|
||||
/// <param name="countOrIndex">
|
||||
/// When the value is greater than 0, it's the number of displayed suggestions from the list returned in <paramref name="session"/>, starting from the index 0.
|
||||
/// When the value is less than or equal to 0, it means a single suggestion from the list got displayed, and the index is the absolute value.
|
||||
/// </param>
|
||||
void OnSuggestionDisplayed(string clientId, uint session, int countOrIndex);
|
||||
|
||||
/// <summary>
|
||||
/// The suggestion provided by the predictor was accepted.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Represents the client that initiates the call.</param>
|
||||
/// <param name="session">Represents the mini-session where the accepted suggestion came from.</param>
|
||||
/// <param name="acceptedSuggestion">The accepted suggestion text.</param>
|
||||
void OnSuggestionAccepted(string clientId, uint session, string acceptedSuggestion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -160,4 +175,47 @@ namespace System.Management.Automation.Subsystem
|
||||
ToolTip = toolTip;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A package returned from <see cref="ICommandPredictor.GetSuggestion"/>.
|
||||
/// </summary>
|
||||
public struct SuggestionPackage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the mini-session that represents a specific invocation to <see cref="ICommandPredictor.GetSuggestion"/>.
|
||||
/// When it's not specified, it's considered by a client that the predictor doesn't expect feedback.
|
||||
/// </summary>
|
||||
public uint? Session { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the suggestion entries returned from that mini-session.
|
||||
/// </summary>
|
||||
public List<PredictiveSuggestion>? SuggestionEntries { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SuggestionPackage"/> struct without providing a session id.
|
||||
/// Note that, when a session id is not specified, it's considered by a client that the predictor doesn't expect feedback.
|
||||
/// </summary>
|
||||
/// <param name="suggestionEntries">The suggestions to return.</param>
|
||||
public SuggestionPackage(List<PredictiveSuggestion> suggestionEntries)
|
||||
{
|
||||
Requires.NotNullOrEmpty(suggestionEntries, nameof(suggestionEntries));
|
||||
|
||||
Session = null;
|
||||
SuggestionEntries = suggestionEntries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SuggestionPackage"/> struct with the mini-session id and the suggestions.
|
||||
/// </summary>
|
||||
/// <param name="session">The mini-session where suggestions came from.</param>
|
||||
/// <param name="suggestionEntries">The suggestions to return.</param>
|
||||
public SuggestionPackage(uint session, List<PredictiveSuggestion> suggestionEntries)
|
||||
{
|
||||
Requires.NotNullOrEmpty(suggestionEntries, nameof(suggestionEntries));
|
||||
|
||||
Session = session;
|
||||
SuggestionEntries = suggestionEntries;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2312,6 +2312,14 @@ namespace System.Management.Automation.Internal
|
||||
}
|
||||
}
|
||||
|
||||
internal static void NotNullOrEmpty(ICollection value, string paramName)
|
||||
{
|
||||
if (value == null || value.Count == 0)
|
||||
{
|
||||
throw new ArgumentNullException(paramName);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Condition([DoesNotReturnIf(false)] bool precondition, string paramName)
|
||||
{
|
||||
if (!precondition)
|
||||
|
||||
@@ -20,9 +20,9 @@ namespace PSTests.Sequential
|
||||
|
||||
public List<string> AcceptedSuggestions { get; }
|
||||
|
||||
public static readonly MyPredictor SlowPredictor;
|
||||
public List<string> DisplayedSuggestions { get; }
|
||||
|
||||
public static readonly MyPredictor FastPredictor;
|
||||
public static readonly MyPredictor SlowPredictor, FastPredictor;
|
||||
|
||||
static MyPredictor()
|
||||
{
|
||||
@@ -48,6 +48,7 @@ namespace PSTests.Sequential
|
||||
|
||||
History = new List<string>();
|
||||
AcceptedSuggestions = new List<string>();
|
||||
DisplayedSuggestions = new List<string>();
|
||||
}
|
||||
|
||||
public Guid Id => _id;
|
||||
@@ -60,17 +61,25 @@ namespace PSTests.Sequential
|
||||
|
||||
bool ICommandPredictor.AcceptFeedback => true;
|
||||
|
||||
public void StartEarlyProcessing(IReadOnlyList<string> history)
|
||||
public void StartEarlyProcessing(string clientId, IReadOnlyList<string> history)
|
||||
{
|
||||
History.AddRange(history);
|
||||
foreach (string item in history)
|
||||
{
|
||||
History.Add($"{clientId}-{item}");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSuggestionAccepted(string acceptedSuggestion)
|
||||
public void OnSuggestionDisplayed(string clientId, uint session, int countOrIndex)
|
||||
{
|
||||
AcceptedSuggestions.Add(acceptedSuggestion);
|
||||
DisplayedSuggestions.Add($"{clientId}-{session}-{countOrIndex}");
|
||||
}
|
||||
|
||||
public List<PredictiveSuggestion> GetSuggestion(PredictionContext context, CancellationToken cancellationToken)
|
||||
public void OnSuggestionAccepted(string clientId, uint session, string acceptedSuggestion)
|
||||
{
|
||||
AcceptedSuggestions.Add($"{clientId}-{session}-{acceptedSuggestion}");
|
||||
}
|
||||
|
||||
public SuggestionPackage GetSuggestion(string clientId, PredictionContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_delay)
|
||||
{
|
||||
@@ -81,24 +90,31 @@ namespace PSTests.Sequential
|
||||
|
||||
// You can get the user input from the AST.
|
||||
var userInput = context.InputAst.Extent.Text;
|
||||
return new List<PredictiveSuggestion> {
|
||||
new PredictiveSuggestion($"{userInput} TEST-1 from {Name}"),
|
||||
new PredictiveSuggestion($"{userInput} TeSt-2 from {Name}"),
|
||||
var entries = new List<PredictiveSuggestion>
|
||||
{
|
||||
new PredictiveSuggestion($"'{userInput}' from '{clientId}' - TEST-1 from {Name}"),
|
||||
new PredictiveSuggestion($"'{userInput}' from '{clientId}' - TeSt-2 from {Name}"),
|
||||
};
|
||||
|
||||
return new SuggestionPackage(56, entries);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandPredictionTests
|
||||
{
|
||||
private const string Client = "PredictionTest";
|
||||
private const uint Session = 56;
|
||||
|
||||
[Fact]
|
||||
public static void PredictInput()
|
||||
{
|
||||
const string Input = "Hello world";
|
||||
MyPredictor slow = MyPredictor.SlowPredictor;
|
||||
MyPredictor fast = MyPredictor.FastPredictor;
|
||||
Ast ast = Parser.ParseInput("Hello world", out Token[] tokens, out _);
|
||||
Ast ast = Parser.ParseInput(Input, out Token[] tokens, out _);
|
||||
|
||||
// Returns null when no predictor implementation registered
|
||||
List<PredictionResult> results = CommandPrediction.PredictInput(ast, tokens).Result;
|
||||
List<PredictionResult> results = CommandPrediction.PredictInput(Client, ast, tokens).Result;
|
||||
Assert.Null(results);
|
||||
|
||||
try
|
||||
@@ -111,32 +127,35 @@ namespace PSTests.Sequential
|
||||
// cannot finish before the specified timeout.
|
||||
// The specified timeout is exaggerated to make the test reliable.
|
||||
// xUnit must spin up a lot tasks, which makes the test unreliable when the time difference between 'delay' and 'timeout' is small.
|
||||
results = CommandPrediction.PredictInput(ast, tokens, millisecondsTimeout: 1000).Result;
|
||||
results = CommandPrediction.PredictInput(Client, ast, tokens, millisecondsTimeout: 1000).Result;
|
||||
Assert.Single(results);
|
||||
|
||||
PredictionResult res = results[0];
|
||||
Assert.Equal(fast.Id, res.Id);
|
||||
Assert.Equal(Session, res.Session);
|
||||
Assert.Equal(2, res.Suggestions.Count);
|
||||
Assert.Equal($"Hello world TEST-1 from {fast.Name}", res.Suggestions[0].SuggestionText);
|
||||
Assert.Equal($"Hello world TeSt-2 from {fast.Name}", res.Suggestions[1].SuggestionText);
|
||||
Assert.Equal($"'{Input}' from '{Client}' - TEST-1 from {fast.Name}", res.Suggestions[0].SuggestionText);
|
||||
Assert.Equal($"'{Input}' from '{Client}' - TeSt-2 from {fast.Name}", res.Suggestions[1].SuggestionText);
|
||||
|
||||
// Expect the results from both 'slow' and 'fast' predictors
|
||||
// Same here -- the specified timeout is exaggerated to make the test reliable.
|
||||
// xUnit must spin up a lot tasks, which makes the test unreliable when the time difference between 'delay' and 'timeout' is small.
|
||||
results = CommandPrediction.PredictInput(ast, tokens, millisecondsTimeout: 4000).Result;
|
||||
results = CommandPrediction.PredictInput(Client, ast, tokens, millisecondsTimeout: 4000).Result;
|
||||
Assert.Equal(2, results.Count);
|
||||
|
||||
PredictionResult res1 = results[0];
|
||||
Assert.Equal(slow.Id, res1.Id);
|
||||
Assert.Equal(Session, res1.Session);
|
||||
Assert.Equal(2, res1.Suggestions.Count);
|
||||
Assert.Equal($"Hello world TEST-1 from {slow.Name}", res1.Suggestions[0].SuggestionText);
|
||||
Assert.Equal($"Hello world TeSt-2 from {slow.Name}", res1.Suggestions[1].SuggestionText);
|
||||
Assert.Equal($"'{Input}' from '{Client}' - TEST-1 from {slow.Name}", res1.Suggestions[0].SuggestionText);
|
||||
Assert.Equal($"'{Input}' from '{Client}' - TeSt-2 from {slow.Name}", res1.Suggestions[1].SuggestionText);
|
||||
|
||||
PredictionResult res2 = results[1];
|
||||
Assert.Equal(fast.Id, res2.Id);
|
||||
Assert.Equal(Session, res2.Session);
|
||||
Assert.Equal(2, res2.Suggestions.Count);
|
||||
Assert.Equal($"Hello world TEST-1 from {fast.Name}", res2.Suggestions[0].SuggestionText);
|
||||
Assert.Equal($"Hello world TeSt-2 from {fast.Name}", res2.Suggestions[1].SuggestionText);
|
||||
Assert.Equal($"'{Input}' from '{Client}' - TEST-1 from {fast.Name}", res2.Suggestions[0].SuggestionText);
|
||||
Assert.Equal($"'{Input}' from '{Client}' - TeSt-2 from {fast.Name}", res2.Suggestions[1].SuggestionText);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -160,8 +179,10 @@ namespace PSTests.Sequential
|
||||
var history = new[] { "hello", "world" };
|
||||
var ids = new HashSet<Guid> { slow.Id, fast.Id };
|
||||
|
||||
CommandPrediction.OnCommandLineAccepted(history);
|
||||
CommandPrediction.OnSuggestionAccepted(slow.Id, "Yeah");
|
||||
CommandPrediction.OnCommandLineAccepted(Client, history);
|
||||
CommandPrediction.OnSuggestionDisplayed(Client, slow.Id, Session, 2);
|
||||
CommandPrediction.OnSuggestionDisplayed(Client, fast.Id, Session, -1);
|
||||
CommandPrediction.OnSuggestionAccepted(Client, slow.Id, Session, "Yeah");
|
||||
|
||||
// The calls to 'StartEarlyProcessing' and 'OnSuggestionAccepted' are queued in thread pool,
|
||||
// so we wait a bit to make sure the calls are done.
|
||||
@@ -171,15 +192,21 @@ namespace PSTests.Sequential
|
||||
}
|
||||
|
||||
Assert.Equal(2, slow.History.Count);
|
||||
Assert.Equal(history[0], slow.History[0]);
|
||||
Assert.Equal(history[1], slow.History[1]);
|
||||
Assert.Equal($"{Client}-{history[0]}", slow.History[0]);
|
||||
Assert.Equal($"{Client}-{history[1]}", slow.History[1]);
|
||||
|
||||
Assert.Equal(2, fast.History.Count);
|
||||
Assert.Equal(history[0], fast.History[0]);
|
||||
Assert.Equal(history[1], fast.History[1]);
|
||||
Assert.Equal($"{Client}-{history[0]}", fast.History[0]);
|
||||
Assert.Equal($"{Client}-{history[1]}", fast.History[1]);
|
||||
|
||||
Assert.Single(slow.DisplayedSuggestions);
|
||||
Assert.Equal($"{Client}-{Session}-2", slow.DisplayedSuggestions[0]);
|
||||
|
||||
Assert.Single(fast.DisplayedSuggestions);
|
||||
Assert.Equal($"{Client}-{Session}--1", fast.DisplayedSuggestions[0]);
|
||||
|
||||
Assert.Single(slow.AcceptedSuggestions);
|
||||
Assert.Equal("Yeah", slow.AcceptedSuggestions[0]);
|
||||
Assert.Equal($"{Client}-{Session}-Yeah", slow.AcceptedSuggestions[0]);
|
||||
|
||||
Assert.Empty(fast.AcceptedSuggestions);
|
||||
}
|
||||
|
||||
@@ -95,10 +95,12 @@ namespace PSTests.Sequential
|
||||
Assert.Null(impl.FunctionsToDefine);
|
||||
Assert.Equal(SubsystemKind.CommandPredictor, impl.Kind);
|
||||
|
||||
var predCxt = PredictionContext.Create("Hello world");
|
||||
var results = impl.GetSuggestion(predCxt, CancellationToken.None);
|
||||
Assert.Equal($"Hello world TEST-1 from {impl.Name}", results[0].SuggestionText);
|
||||
Assert.Equal($"Hello world TeSt-2 from {impl.Name}", results[1].SuggestionText);
|
||||
const string Client = "SubsystemTest";
|
||||
const string Input = "Hello world";
|
||||
var predCxt = PredictionContext.Create(Input);
|
||||
var results = impl.GetSuggestion(Client, predCxt, CancellationToken.None);
|
||||
Assert.Equal($"'{Input}' from '{Client}' - TEST-1 from {impl.Name}", results.SuggestionEntries[0].SuggestionText);
|
||||
Assert.Equal($"'{Input}' from '{Client}' - TeSt-2 from {impl.Name}", results.SuggestionEntries[1].SuggestionText);
|
||||
|
||||
// Now validate the all-subsystem-implementation collection.
|
||||
ReadOnlyCollection<ICommandPredictor> impls = SubsystemManager.GetSubsystems<ICommandPredictor>();
|
||||
|
||||
@@ -2030,24 +2030,27 @@ function CopyReferenceAssemblies
|
||||
switch ($assemblyName) {
|
||||
{ $_ -in $supportedRefList } {
|
||||
$refDll = Join-Path -Path $refBinPath -ChildPath "$assemblyName.dll"
|
||||
Copy-Item $refDll -Destination $refNugetPath -Force
|
||||
Write-Log "Copied file $refDll to $refNugetPath"
|
||||
$refDoc = Join-Path -Path $refBinPath -ChildPath "$assemblyName.xml"
|
||||
Copy-Item $refDll, $refDoc -Destination $refNugetPath -Force
|
||||
Write-Log "Copied file '$refDll' and '$refDoc' to '$refNugetPath'"
|
||||
}
|
||||
|
||||
"Microsoft.PowerShell.SDK" {
|
||||
foreach ($asmFileName in $assemblyFileList) {
|
||||
$refFile = Join-Path -Path $refBinPath -ChildPath $asmFileName
|
||||
if (Test-Path -Path $refFile) {
|
||||
Copy-Item $refFile -Destination $refNugetPath -Force
|
||||
Write-Log "Copied file $refFile to $refNugetPath"
|
||||
$refDoc = Join-Path -Path $refBinPath -ChildPath ([System.IO.Path]::ChangeExtension($asmFileName, "xml"))
|
||||
Copy-Item $refFile, $refDoc -Destination $refNugetPath -Force
|
||||
Write-Log "Copied file '$refFile' and '$refDoc' to '$refNugetPath'"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default {
|
||||
$ref_SMA = Join-Path -Path $refBinPath -ChildPath System.Management.Automation.dll
|
||||
Copy-Item $ref_SMA -Destination $refNugetPath -Force
|
||||
Write-Log "Copied file $ref_SMA to $refNugetPath"
|
||||
$ref_doc = Join-Path -Path $refBinPath -ChildPath System.Management.Automation.xml
|
||||
Copy-Item $ref_SMA, $ref_doc -Destination $refNugetPath -Force
|
||||
Write-Log "Copied file '$ref_SMA' and '$ref_doc' to '$refNugetPath'"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2229,8 +2232,13 @@ function New-ReferenceAssembly
|
||||
throw "$assemblyName.dll was not found at: $Linux64BinPath"
|
||||
}
|
||||
|
||||
$dllXmlDoc = Join-Path $Linux64BinPath "$assemblyName.xml"
|
||||
if (-not (Test-Path $dllXmlDoc)) {
|
||||
throw "$assemblyName.xml was not found at: $Linux64BinPath"
|
||||
}
|
||||
|
||||
$genAPIArgs = "$linuxDllPath","-libPath:$Linux64BinPath,$Linux64BinPath\ref"
|
||||
Write-Log "GenAPI cmd: $genAPIExe $genAPIArgsString"
|
||||
Write-Log "GenAPI cmd: $genAPIExe $genAPIArgs"
|
||||
|
||||
Start-NativeExecution { & $genAPIExe $genAPIArgs } | Out-File $generatedSource -Force
|
||||
Write-Log "Reference assembly file generated at: $generatedSource"
|
||||
@@ -2262,6 +2270,9 @@ function New-ReferenceAssembly
|
||||
Copy-Item $refBinPath $RefAssemblyDestinationPath -Force
|
||||
Write-Log "Reference assembly '$assemblyName.dll' built and copied to $RefAssemblyDestinationPath"
|
||||
|
||||
Copy-Item $dllXmlDoc $RefAssemblyDestinationPath -Force
|
||||
Write-Log "Xml document '$assemblyName.xml' copied to $RefAssemblyDestinationPath"
|
||||
|
||||
if ($assemblyName -eq "System.Management.Automation") {
|
||||
$SMAReferenceAssembly = $refBinPath
|
||||
}
|
||||
@@ -2361,6 +2372,11 @@ function CleanupGeneratedSourceCode
|
||||
Pattern = "[System.Runtime.CompilerServices.CompilerGeneratedAttribute, System.Runtime.CompilerServices.NullableContextAttribute((byte)2)]"
|
||||
Replacement = "/* [System.Runtime.CompilerServices.CompilerGeneratedAttribute, System.Runtime.CompilerServices.NullableContextAttribute((byte)2)] */ "
|
||||
},
|
||||
@{
|
||||
ApplyTo = @("System.Management.Automation")
|
||||
Pattern = "[System.Runtime.CompilerServices.CompilerGeneratedAttribute, System.Runtime.CompilerServices.IsReadOnlyAttribute]"
|
||||
Replacement = "/* [System.Runtime.CompilerServices.CompilerGeneratedAttribute, System.Runtime.CompilerServices.IsReadOnlyAttribute] */ "
|
||||
},
|
||||
@{
|
||||
ApplyTo = @("System.Management.Automation", "Microsoft.PowerShell.ConsoleHost")
|
||||
Pattern = "[System.Runtime.CompilerServices.NullableAttribute((byte)2)]"
|
||||
|
||||
Reference in New Issue
Block a user