diff --git a/MockPSConsole/MockPSConsole.csproj b/MockPSConsole/MockPSConsole.csproj
index 164e083ae..6f57f1342 100644
--- a/MockPSConsole/MockPSConsole.csproj
+++ b/MockPSConsole/MockPSConsole.csproj
@@ -18,7 +18,7 @@
-
+
diff --git a/MockPSConsole/Program.cs b/MockPSConsole/Program.cs
index b26684b5b..f7c68f0a5 100644
--- a/MockPSConsole/Program.cs
+++ b/MockPSConsole/Program.cs
@@ -99,7 +99,7 @@ static void Main()
ps.Commands.Clear();
Console.Write(string.Join("", ps.AddCommand("prompt").Invoke()));
- var line = PSConsoleReadLine.ReadLine(rs, executionContext);
+ var line = PSConsoleReadLine.ReadLine(rs, executionContext, lastRunStatus: null);
Console.WriteLine(line);
line = line.Trim();
if (line.Equals("exit"))
diff --git a/PSReadLine/PSReadLine.csproj b/PSReadLine/PSReadLine.csproj
index 151d4161c..6e90804a8 100644
--- a/PSReadLine/PSReadLine.csproj
+++ b/PSReadLine/PSReadLine.csproj
@@ -22,7 +22,7 @@
-
+
diff --git a/PSReadLine/PSReadLine.psm1 b/PSReadLine/PSReadLine.psm1
index 6b33bd5a1..74c271419 100644
--- a/PSReadLine/PSReadLine.psm1
+++ b/PSReadLine/PSReadLine.psm1
@@ -1,5 +1,8 @@
function PSConsoleHostReadLine
{
+ ## Get the execution status of the last accepted user input.
+ ## This needs to be done as the first thing because any script run will flush $?.
+ $lastRunStatus = $?
Microsoft.PowerShell.Core\Set-StrictMode -Off
- [Microsoft.PowerShell.PSConsoleReadLine]::ReadLine($host.Runspace, $ExecutionContext)
+ [Microsoft.PowerShell.PSConsoleReadLine]::ReadLine($host.Runspace, $ExecutionContext, $lastRunStatus)
}
diff --git a/PSReadLine/Prediction.Views.cs b/PSReadLine/Prediction.Views.cs
index ad207e95b..ed9a4bf93 100644
--- a/PSReadLine/Prediction.Views.cs
+++ b/PSReadLine/Prediction.Views.cs
@@ -6,7 +6,7 @@
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
-using System.Management.Automation.Subsystem;
+using System.Management.Automation.Subsystem.Prediction;
using Microsoft.PowerShell.Internal;
namespace Microsoft.PowerShell
@@ -33,12 +33,12 @@ protected PredictionViewBase(PSConsoleReadLine singleton)
///
/// Gets whether to use plugin as a source.
///
- protected bool UsePlugin => (_singleton._options.PredictionSource & PredictionSource.Plugin) != 0;
+ internal bool UsePlugin => (_singleton._options.PredictionSource & PredictionSource.Plugin) != 0;
///
/// Gets whether to use history as a source.
///
- protected bool UseHistory => (_singleton._options.PredictionSource & PredictionSource.History) != 0;
+ internal bool UseHistory => (_singleton._options.PredictionSource & PredictionSource.History) != 0;
///
/// Gets whether an update to the view is pending.
@@ -80,17 +80,6 @@ internal virtual void Reset()
_predictionTask = null;
}
- ///
- /// Get called when a command line is accepted.
- ///
- internal void OnCommandLineAccepted(string commandLine)
- {
- if (UsePlugin && !string.IsNullOrWhiteSpace(commandLine))
- {
- _singleton._mockableMethods.OnCommandLineAccepted(_singleton._recentHistory.ToArray());
- }
- }
-
///
/// Currently we only select single-line history that is prefixed with the user input,
/// but it can be improved to not strictly use the user input as a prefix, but a hint
@@ -193,7 +182,7 @@ protected List GetHistorySuggestions(string input, int count)
///
protected void PredictInput()
{
- _predictionTask = _singleton._mockableMethods.PredictInput(_singleton._ast, _singleton._tokens);
+ _predictionTask = _singleton._mockableMethods.PredictInputAsync(_singleton._ast, _singleton._tokens);
}
///
diff --git a/PSReadLine/Prediction.cs b/PSReadLine/Prediction.cs
index 597cb02c5..42edb5f84 100644
--- a/PSReadLine/Prediction.cs
+++ b/PSReadLine/Prediction.cs
@@ -7,7 +7,7 @@
using System.Threading.Tasks;
using System.Management.Automation;
using System.Management.Automation.Language;
-using System.Management.Automation.Subsystem;
+using System.Management.Automation.Subsystem.Prediction;
using System.Diagnostics.CodeAnalysis;
using Microsoft.PowerShell.Internal;
using Microsoft.PowerShell.PSReadLine;
@@ -17,34 +17,50 @@ namespace Microsoft.PowerShell
public partial class PSConsoleReadLine
{
private const string PSReadLine = "PSReadLine";
+ private static PredictionClient s_predictionClient = new(PSReadLine, PredictionClientKind.Terminal);
// Stub helper methods so prediction can be mocked
[ExcludeFromCodeCoverage]
- Task> IPSConsoleReadLineMockableMethods.PredictInput(Ast ast, Token[] tokens)
+ Task> IPSConsoleReadLineMockableMethods.PredictInputAsync(Ast ast, Token[] tokens)
{
- return CommandPrediction.PredictInput(PSReadLine, ast, tokens);
+ return CommandPrediction.PredictInputAsync(s_predictionClient, ast, tokens);
}
[ExcludeFromCodeCoverage]
void IPSConsoleReadLineMockableMethods.OnSuggestionDisplayed(Guid predictorId, uint session, int countOrIndex)
{
- CommandPrediction.OnSuggestionDisplayed(PSReadLine, predictorId, session, countOrIndex);
+ CommandPrediction.OnSuggestionDisplayed(s_predictionClient, predictorId, session, countOrIndex);
}
[ExcludeFromCodeCoverage]
void IPSConsoleReadLineMockableMethods.OnSuggestionAccepted(Guid predictorId, uint session, string suggestionText)
{
- CommandPrediction.OnSuggestionAccepted(PSReadLine, predictorId, session, suggestionText);
+ CommandPrediction.OnSuggestionAccepted(s_predictionClient, predictorId, session, suggestionText);
}
[ExcludeFromCodeCoverage]
void IPSConsoleReadLineMockableMethods.OnCommandLineAccepted(IReadOnlyList history)
{
- CommandPrediction.OnCommandLineAccepted(PSReadLine, history);
+ CommandPrediction.OnCommandLineAccepted(s_predictionClient, history);
+ }
+
+ [ExcludeFromCodeCoverage]
+ void IPSConsoleReadLineMockableMethods.OnCommandLineExecuted(string commandLine, bool success)
+ {
+ CommandPrediction.OnCommandLineExecuted(s_predictionClient, commandLine, success);
}
private readonly Prediction _prediction;
+ ///
+ /// Report the execution result (success or failure) of the last accepted command line.
+ ///
+ /// Whether the execution was successful.
+ private void ReportExecutionStatus(bool success)
+ {
+ _prediction.OnCommandLineExecuted(_acceptedCommandLine, success);
+ }
+
///
/// Accept the suggestion text if there is one.
///
@@ -381,6 +397,28 @@ internal bool RevertSuggestion()
return retValue;
}
+
+ ///
+ /// Get called when a command line is accepted.
+ ///
+ internal void OnCommandLineAccepted(string commandLine)
+ {
+ if (ActiveView.UsePlugin && !string.IsNullOrWhiteSpace(commandLine))
+ {
+ _singleton._mockableMethods.OnCommandLineAccepted(_singleton._recentHistory.ToArray());
+ }
+ }
+
+ ///
+ /// Get called when the last accepted command line finished execution.
+ ///
+ internal void OnCommandLineExecuted(string commandLine, bool success)
+ {
+ if (ActiveView.UsePlugin && !string.IsNullOrWhiteSpace(commandLine))
+ {
+ _singleton._mockableMethods.OnCommandLineExecuted(commandLine, success);
+ }
+ }
}
}
}
diff --git a/PSReadLine/PublicAPI.cs b/PSReadLine/PublicAPI.cs
index e661b29a9..3e9696686 100644
--- a/PSReadLine/PublicAPI.cs
+++ b/PSReadLine/PublicAPI.cs
@@ -9,7 +9,7 @@
using System.Management.Automation;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
-using System.Management.Automation.Subsystem;
+using System.Management.Automation.Subsystem.Prediction;
using System.Text;
using System.Threading.Tasks;
using Microsoft.PowerShell.PSReadLine;
@@ -26,8 +26,9 @@ public interface IPSConsoleReadLineMockableMethods
void Ding();
CommandCompletion CompleteInput(string input, int cursorIndex, Hashtable options, System.Management.Automation.PowerShell powershell);
bool RunspaceIsRemote(Runspace runspace);
- Task> PredictInput(Ast ast, Token[] tokens);
+ Task> PredictInputAsync(Ast ast, Token[] tokens);
void OnCommandLineAccepted(IReadOnlyList history);
+ void OnCommandLineExecuted(string commandLine, bool success);
void OnSuggestionDisplayed(Guid predictorId, uint session, int countOrIndex);
void OnSuggestionAccepted(Guid predictorId, uint session, string suggestionText);
void RenderFullHelp(string content, string regexPatternToScrollTo);
diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs
index c3912371a..94e44204a 100644
--- a/PSReadLine/ReadLine.cs
+++ b/PSReadLine/ReadLine.cs
@@ -65,6 +65,7 @@ public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods
private readonly StringBuilder _statusBuffer;
private bool _statusIsErrorMessage;
private string _statusLinePrompt;
+ private string _acceptedCommandLine;
private List _edits;
private int _editGroupStart;
private int _undoEditIndex;
@@ -302,11 +303,11 @@ private void PrependQueuedKeys(PSKeyInfo key)
/// after the prompt has been displayed.
///
/// The complete command line.
- public static string ReadLine(Runspace runspace, EngineIntrinsics engineIntrinsics)
+ public static string ReadLine(Runspace runspace, EngineIntrinsics engineIntrinsics, bool? lastRunStatus)
{
// Use a default cancellation token instead of CancellationToken.None because the
// WaitHandle is shared and could be triggered accidently.
- return ReadLine(runspace, engineIntrinsics, _defaultCancellationToken);
+ return ReadLine(runspace, engineIntrinsics, _defaultCancellationToken, lastRunStatus);
}
///
@@ -314,7 +315,11 @@ public static string ReadLine(Runspace runspace, EngineIntrinsics engineIntrinsi
/// ability to cancel ReadLine.
///
/// The complete command line.
- public static string ReadLine(Runspace runspace, EngineIntrinsics engineIntrinsics, CancellationToken cancellationToken)
+ public static string ReadLine(
+ Runspace runspace,
+ EngineIntrinsics engineIntrinsics,
+ CancellationToken cancellationToken,
+ bool? lastRunStatus)
{
var console = _singleton._console;
@@ -348,6 +353,11 @@ public static string ReadLine(Runspace runspace, EngineIntrinsics engineIntrinsi
catch {}
}
+ if (lastRunStatus.HasValue)
+ {
+ _singleton.ReportExecutionStatus(lastRunStatus.Value);
+ }
+
bool firstTime = true;
while (true)
{
@@ -486,11 +496,11 @@ private string InputLoop()
ProcessOneKey(key, _dispatchTable, ignoreIfNoAction: false, arg: null);
if (_inputAccepted)
{
- var commandLine = _buffer.ToString();
- MaybeAddToHistory(commandLine, _edits, _undoEditIndex);
+ _acceptedCommandLine = _buffer.ToString();
+ MaybeAddToHistory(_acceptedCommandLine, _edits, _undoEditIndex);
- _prediction.ActiveView.OnCommandLineAccepted(commandLine);
- return commandLine;
+ _prediction.OnCommandLineAccepted(_acceptedCommandLine);
+ return _acceptedCommandLine;
}
if (killCommandCount == _killCommandCount)
diff --git a/Polyfill/CommandPrediction.cs b/Polyfill/CommandPrediction.cs
index 3b0c18aa6..e7b331a8b 100644
--- a/Polyfill/CommandPrediction.cs
+++ b/Polyfill/CommandPrediction.cs
@@ -4,8 +4,54 @@
using System.Threading.Tasks;
using System.Management.Automation.Language;
-namespace System.Management.Automation.Subsystem
+namespace System.Management.Automation.Subsystem.Prediction
{
+ ///
+ /// Kinds of prediction clients.
+ ///
+ public enum PredictionClientKind
+ {
+ ///
+ /// A terminal client, representing the command-line experience.
+ ///
+ Terminal,
+
+ ///
+ /// An editor client, representing the editor experience.
+ ///
+ Editor,
+ }
+
+ ///
+ /// The class represents a client that interacts with predictors.
+ ///
+ public sealed class PredictionClient
+ {
+ ///
+ /// Gets the client name.
+ ///
+ [HiddenAttribute]
+ public string Name { get; }
+
+ ///
+ /// Gets the client kind.
+ ///
+ [HiddenAttribute]
+ public PredictionClientKind Kind { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Name of the interactive client.
+ /// Kind of the interactive client.
+ [HiddenAttribute]
+ public PredictionClient(string name, PredictionClientKind kind)
+ {
+ Name = name;
+ Kind = kind;
+ }
+ }
+
///
/// The class represents the prediction result from a predictor.
///
@@ -103,7 +149,7 @@ public static class CommandPrediction
/// The objects from parsing the current command line input.
/// A list of objects.
[HiddenAttribute]
- public static Task> PredictInput(string client, Ast ast, Token[] astTokens)
+ public static Task> PredictInputAsync(PredictionClient client, Ast ast, Token[] astTokens)
{
return null;
}
@@ -117,7 +163,7 @@ public static Task> PredictInput(string client, Ast ast,
/// The milliseconds to timeout.
/// A list of objects.
[HiddenAttribute]
- public static Task> PredictInput(string client, Ast ast, Token[] astTokens, int millisecondsTimeout)
+ public static Task> PredictInputAsync(PredictionClient client, Ast ast, Token[] astTokens, int millisecondsTimeout)
{
return null;
}
@@ -128,7 +174,18 @@ public static Task> PredictInput(string client, Ast ast,
/// Represents the client that initiates the call.
/// History command lines provided as references for prediction.
[HiddenAttribute]
- public static void OnCommandLineAccepted(string client, IReadOnlyList history)
+ public static void OnCommandLineAccepted(PredictionClient client, IReadOnlyList history)
+ {
+ }
+
+ ///
+ /// Allow registered predictors to know the execution result (success/failure) of the last accepted command line.
+ ///
+ /// Represents the client that initiates the call.
+ /// The last accepted command line.
+ /// Whether the execution of the last command line was successful.
+ [HiddenAttribute]
+ public static void OnCommandLineExecuted(PredictionClient client, string commandLine, bool success)
{
}
@@ -143,7 +200,7 @@ public static void OnCommandLineAccepted(string client, IReadOnlyList hi
/// When the value is <= 0
, it means a single suggestion from the list got displayed, and the index is the absolute value.
///
[HiddenAttribute]
- public static void OnSuggestionDisplayed(string client, Guid predictorId, uint session, int countOrIndex)
+ public static void OnSuggestionDisplayed(PredictionClient client, Guid predictorId, uint session, int countOrIndex)
{
}
@@ -155,7 +212,7 @@ public static void OnSuggestionDisplayed(string client, Guid predictorId, uint s
/// The mini-session where the accepted suggestion came from.
/// The accepted suggestion text.
[HiddenAttribute]
- public static void OnSuggestionAccepted(string client, Guid predictorId, uint session, string suggestionText)
+ public static void OnSuggestionAccepted(PredictionClient client, Guid predictorId, uint session, string suggestionText)
{
}
}
@@ -163,9 +220,11 @@ public static void OnSuggestionAccepted(string client, Guid predictorId, uint se
#else
-using System.Management.Automation.Subsystem;
+using System.Management.Automation.Subsystem.Prediction;
using System.Runtime.CompilerServices;
+[assembly: TypeForwardedTo(typeof(PredictionClientKind))]
+[assembly: TypeForwardedTo(typeof(PredictionClient))]
[assembly: TypeForwardedTo(typeof(PredictiveSuggestion))]
[assembly: TypeForwardedTo(typeof(PredictionResult))]
[assembly: TypeForwardedTo(typeof(CommandPrediction))]
diff --git a/Polyfill/Polyfill.csproj b/Polyfill/Polyfill.csproj
index 1e0ff4d44..c8334056e 100644
--- a/Polyfill/Polyfill.csproj
+++ b/Polyfill/Polyfill.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/test/InlinePredictionTest.cs b/test/InlinePredictionTest.cs
index 43fd485e9..b2986e302 100644
--- a/test/InlinePredictionTest.cs
+++ b/test/InlinePredictionTest.cs
@@ -2,7 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation.Language;
-using System.Management.Automation.Subsystem;
+using System.Management.Automation.Subsystem.Prediction;
using System.Reflection;
using Microsoft.PowerShell;
using Xunit;
@@ -566,5 +566,136 @@ public void Inline_HistoryAndPluginSource_Acceptance()
Assert.Equal(1, _mockedMethods.commandHistory.Count);
Assert.Equal("netsh show me", _mockedMethods.commandHistory[0]);
}
+
+ [SkippableFact]
+ public void Inline_NoneSource_ExecutionStatus()
+ {
+ TestSetup(KeyMode.Cmd);
+ using var disp = SetPrediction(PredictionSource.None, PredictionViewStyle.InlineView);
+
+ // The last accepted command line would be "yay" after this.
+ Test("yay", Keys("yay"));
+ _mockedMethods.ClearPredictionFields();
+
+ // We always pass in 'true' as the execution status of the last command line,
+ // and that feedback will be reported when
+ // 1. the plugin source is in use;
+ // 2. the last accepted command is not a whitespace string.
+ Test(" ", Keys(
+ // Since we set the prediction source to be 'None', this feedback won't be reported.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ " "));
+
+ Test("abc", Keys(
+ // The prediction source is 'None', and the last accepted command is a whitespace string,
+ // so this feedback won't be reported.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ "abc"));
+
+ Assert.Null(_mockedMethods.lastCommandRunStatus);
+ }
+
+ [SkippableFact]
+ public void Inline_HistorySource_ExecutionStatus()
+ {
+ TestSetup(KeyMode.Cmd);
+ using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView);
+
+ // The last accepted command line would be "yay" after this.
+ Test("yay", Keys("yay"));
+ _mockedMethods.ClearPredictionFields();
+
+ // We always pass in 'true' as the execution status of the last command line,
+ // and that feedback will be reported when
+ // 1. the plugin source is in use;
+ // 2. the last accepted command is not a whitespace string.
+ Test(" ", Keys(
+ // Since we set the prediction source to be 'History', this feedback won't be reported.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ " "));
+
+ Test("abc", Keys(
+ // The plugin source is in use, but the last accepted command is a whitespace string.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ "abc"));
+
+ Assert.Null(_mockedMethods.lastCommandRunStatus);
+ }
+
+ [SkippableFact]
+ public void Inline_PluginSource_ExecutionStatus()
+ {
+ TestSetup(KeyMode.Cmd);
+ using var disp = SetPrediction(PredictionSource.Plugin, PredictionViewStyle.InlineView);
+
+ // The last accepted command line would be an empty string after this.
+ Test("", Keys(_.Enter));
+ _mockedMethods.ClearPredictionFields();
+
+ // We always pass in 'true' as the execution status of the last command line,
+ // and that feedback will be reported when
+ // 1. the plugin source is in use;
+ // 2. the last accepted command is not a whitespace string.
+ Test("yay", Keys(
+ // The plugin source is in use, but the last accepted command is an empty string.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ "yay"));
+
+ Assert.Null(_mockedMethods.lastCommandRunStatus);
+
+ // The last accepted command line would be a whitespace string with 3 space characters after this.
+ Test(" ", Keys(
+ // The plugin source is in use, and the last accepted command is "yay".
+ CheckThat(() => Assert.True(_mockedMethods.lastCommandRunStatus)),
+ " "));
+
+ Assert.True(_mockedMethods.lastCommandRunStatus);
+ _mockedMethods.ClearPredictionFields();
+
+ Test("abc", Keys(
+ // The plugin source is in use, but the last accepted command is a whitespace string.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ "abc"));
+
+ Assert.Null(_mockedMethods.lastCommandRunStatus);
+ }
+
+ [SkippableFact]
+ public void Inline_HistoryAndPluginSource_ExecutionStatus()
+ {
+ TestSetup(KeyMode.Cmd);
+ using var disp = SetPrediction(PredictionSource.HistoryAndPlugin, PredictionViewStyle.InlineView);
+
+ // The last accepted command line would be an empty string after this.
+ Test("", Keys(_.Enter));
+ _mockedMethods.ClearPredictionFields();
+
+ // We always pass in 'true' as the execution status of the last command line,
+ // and that feedback will be reported when
+ // 1. the plugin source is in use;
+ // 2. the last accepted command is not a whitespace string.
+ Test("yay", Keys(
+ // The plugin source is in use, but the last accepted command is an empty string.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ "yay"));
+
+ Assert.Null(_mockedMethods.lastCommandRunStatus);
+
+ // The last accepted command line would be a whitespace string with 3 space characters after this.
+ Test(" ", Keys(
+ // The plugin source is in use, and the last accepted command is "yay".
+ CheckThat(() => Assert.True(_mockedMethods.lastCommandRunStatus)),
+ " "));
+
+ Assert.True(_mockedMethods.lastCommandRunStatus);
+ _mockedMethods.ClearPredictionFields();
+
+ Test("abc", Keys(
+ // The plugin source is in use, but the last accepted command is a whitespace string.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ "abc"));
+
+ Assert.Null(_mockedMethods.lastCommandRunStatus);
+ }
}
}
diff --git a/test/ListPredictionTest.cs b/test/ListPredictionTest.cs
index 82c6978dd..0bb5afd55 100644
--- a/test/ListPredictionTest.cs
+++ b/test/ListPredictionTest.cs
@@ -1509,5 +1509,137 @@ public void List_HistoryAndPluginSource_Acceptance()
Assert.Equal("eca -zoo", _mockedMethods.commandHistory[2]);
Assert.Equal("SOME NEW TEX SOME TEXT AFTER", _mockedMethods.commandHistory[3]);
}
+
+ [SkippableFact]
+ public void List_NoneSource_ExecutionStatus()
+ {
+ TestSetup(KeyMode.Cmd);
+ using var disp = SetPrediction(PredictionSource.None, PredictionViewStyle.ListView);
+
+ // The last accepted command line would be "yay" after this.
+ Test("yay", Keys("yay"));
+ _mockedMethods.ClearPredictionFields();
+
+ // We always pass in 'true' as the execution status of the last command line,
+ // and that feedback will be reported when
+ // 1. the plugin source is in use;
+ // 2. the last accepted command is not a whitespace string.
+ Test(" ", Keys(
+ // Since we set the prediction source to be 'None', this feedback won't be reported.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ " "));
+
+ Test("abc", Keys(
+ // The prediction source is 'None', and the last accepted command is a whitespace string,
+ // so this feedback won't be reported.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ "abc"));
+
+ Assert.Null(_mockedMethods.lastCommandRunStatus);
+ }
+
+ [SkippableFact]
+ public void List_HistorySource_ExecutionStatus()
+ {
+ TestSetup(KeyMode.Cmd);
+ using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView);
+
+ // The last accepted command line would be "yay" after this.
+ Test("yay", Keys("yay"));
+ _mockedMethods.ClearPredictionFields();
+
+ // We always pass in 'true' as the execution status of the last command line,
+ // and that feedback will be reported when
+ // 1. the plugin source is in use;
+ // 2. the last accepted command is not a whitespace string.
+ Test(" ", Keys(
+ // Since we set the prediction source to be 'History', this feedback won't be reported.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ " "));
+
+ Test("abc", Keys(
+ // The prediction source is 'History', and the last accepted command is a whitespace string,
+ // so this feedback won't be reported.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ "abc"));
+
+ Assert.Null(_mockedMethods.lastCommandRunStatus);
+ }
+
+ [SkippableFact]
+ public void List_PluginSource_ExecutionStatus()
+ {
+ TestSetup(KeyMode.Cmd);
+ using var disp = SetPrediction(PredictionSource.Plugin, PredictionViewStyle.InlineView);
+
+ // The last accepted command line would be an empty string after this.
+ Test("", Keys(_.Enter));
+ _mockedMethods.ClearPredictionFields();
+
+ // We always pass in 'true' as the execution status of the last command line,
+ // and that feedback will be reported when
+ // 1. the plugin source is in use;
+ // 2. the last accepted command is not a whitespace string.
+ Test("yay", Keys(
+ // The plugin source is in use, but the last accepted command is an empty string.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ "yay"));
+
+ Assert.Null(_mockedMethods.lastCommandRunStatus);
+
+ // The last accepted command line would be a whitespace string with 3 space characters after this.
+ Test(" ", Keys(
+ // The plugin source is in use, and the last accepted command is "yay".
+ CheckThat(() => Assert.True(_mockedMethods.lastCommandRunStatus)),
+ " "));
+
+ Assert.True(_mockedMethods.lastCommandRunStatus);
+ _mockedMethods.ClearPredictionFields();
+
+ Test("abc", Keys(
+ // The plugin source is in use, but the last accepted command is a whitespace string.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ "abc"));
+
+ Assert.Null(_mockedMethods.lastCommandRunStatus);
+ }
+
+ [SkippableFact]
+ public void List_HistoryAndPluginSource_ExecutionStatus()
+ {
+ TestSetup(KeyMode.Cmd);
+ using var disp = SetPrediction(PredictionSource.HistoryAndPlugin, PredictionViewStyle.InlineView);
+
+ // The last accepted command line would be an empty string after this.
+ Test("", Keys(_.Enter));
+ _mockedMethods.ClearPredictionFields();
+
+ // We always pass in 'true' as the execution status of the last command line,
+ // and that feedback will be reported when
+ // 1. the plugin source is in use;
+ // 2. the last accepted command is not a whitespace string.
+ Test("yay", Keys(
+ // The plugin source is in use, but the last accepted command is an empty string.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ "yay"));
+
+ Assert.Null(_mockedMethods.lastCommandRunStatus);
+
+ // The last accepted command line would be a whitespace string with 3 space characters after this.
+ Test(" ", Keys(
+ // The plugin source is in use, and the last accepted command is "yay".
+ CheckThat(() => Assert.True(_mockedMethods.lastCommandRunStatus)),
+ " "));
+
+ Assert.True(_mockedMethods.lastCommandRunStatus);
+ _mockedMethods.ClearPredictionFields();
+
+ Test("abc", Keys(
+ // The plugin source is in use, but the last accepted command is a whitespace string.
+ CheckThat(() => Assert.Null(_mockedMethods.lastCommandRunStatus)),
+ "abc"));
+
+ Assert.Null(_mockedMethods.lastCommandRunStatus);
+ }
}
}
diff --git a/test/PSReadLine.Tests.csproj b/test/PSReadLine.Tests.csproj
index f89604daa..f6de2e71f 100644
--- a/test/PSReadLine.Tests.csproj
+++ b/test/PSReadLine.Tests.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/test/UnitTestReadLine.cs b/test/UnitTestReadLine.cs
index e3c5dd06b..f4587285b 100644
--- a/test/UnitTestReadLine.cs
+++ b/test/UnitTestReadLine.cs
@@ -6,7 +6,7 @@
using System.Management.Automation;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
-using System.Management.Automation.Subsystem;
+using System.Management.Automation.Subsystem.Prediction;
using System.Threading.Tasks;
using System.Reflection;
using System.Runtime.InteropServices;
@@ -23,6 +23,7 @@ internal class MockedMethods : IPSConsoleReadLineMockableMethods
{
internal bool didDing;
internal IReadOnlyList commandHistory;
+ internal bool? lastCommandRunStatus;
internal Guid acceptedPredictorId;
internal string acceptedSuggestion;
internal string helpContentRendered;
@@ -31,6 +32,7 @@ internal class MockedMethods : IPSConsoleReadLineMockableMethods
internal void ClearPredictionFields()
{
commandHistory = null;
+ lastCommandRunStatus = null;
acceptedPredictorId = Guid.Empty;
acceptedSuggestion = null;
displayedSuggestions.Clear();
@@ -51,7 +53,7 @@ public bool RunspaceIsRemote(Runspace runspace)
return false;
}
- public Task> PredictInput(Ast ast, Token[] tokens)
+ public Task> PredictInputAsync(Ast ast, Token[] tokens)
{
var result = ReadLine.MockedPredictInput(ast, tokens);
var source = new TaskCompletionSource>();
@@ -65,6 +67,11 @@ public void OnCommandLineAccepted(IReadOnlyList history)
commandHistory = history;
}
+ public void OnCommandLineExecuted(string commandLine, bool success)
+ {
+ lastCommandRunStatus = success;
+ }
+
public void OnSuggestionDisplayed(Guid predictorId, uint session, int countOrIndex)
{
displayedSuggestions[predictorId] = Tuple.Create(session, countOrIndex);
@@ -474,7 +481,10 @@ private void Test(string expectedResult, object[] items, bool resetCursor, strin
_console.Init(items);
- var result = PSConsoleReadLine.ReadLine(null, null);
+ var result = PSConsoleReadLine.ReadLine(
+ runspace: null,
+ engineIntrinsics: null,
+ lastRunStatus: true);
if (_console.validationFailure != null)
{