From d41dedf6d3c85394401c7886601c70288fa084ec Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 17 May 2021 23:00:31 -0700 Subject: [PATCH 1/5] Update PSReadLine according to the prediction interface changes --- MockPSConsole/Program.cs | 2 +- PSReadLine/PSReadLine.csproj | 2 +- PSReadLine/PSReadLine.psm1 | 5 +- PSReadLine/Prediction.Views.cs | 15 +--- PSReadLine/Prediction.cs | 46 +++++++++++- PSReadLine/PublicAPI.cs | 1 + PSReadLine/ReadLine.cs | 24 ++++-- Polyfill/CommandPrediction.cs | 64 ++++++++++++++-- nuget.config | 1 + test/InlinePredictionTest.cs | 131 ++++++++++++++++++++++++++++++++ test/ListPredictionTest.cs | 132 +++++++++++++++++++++++++++++++++ test/UnitTestReadLine.cs | 12 ++- 12 files changed, 402 insertions(+), 33 deletions(-) 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..6127f6236 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..6476a6a71 100644 --- a/PSReadLine/Prediction.Views.cs +++ b/PSReadLine/Prediction.Views.cs @@ -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 diff --git a/PSReadLine/Prediction.cs b/PSReadLine/Prediction.cs index 597cb02c5..64baa4667 100644 --- a/PSReadLine/Prediction.cs +++ b/PSReadLine/Prediction.cs @@ -17,34 +17,50 @@ namespace Microsoft.PowerShell public partial class PSConsoleReadLine { private const string PSReadLine = "PSReadLine"; + private static PredictionClient s_predictionClient = new(PSReadLine, PredictionClient.ClientKind.Terminal); // Stub helper methods so prediction can be mocked [ExcludeFromCodeCoverage] Task> IPSConsoleReadLineMockableMethods.PredictInput(Ast ast, Token[] tokens) { - return CommandPrediction.PredictInput(PSReadLine, ast, tokens); + return CommandPrediction.PredictInput(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 status) + { + CommandPrediction.OnCommandLineExecuted(s_predictionClient, commandLine, status); } private readonly Prediction _prediction; + /// + /// Report the execution result (success or failure) of the last accepted command line. + /// + /// + private void ReportExecutionStatus(bool status) + { + _prediction.OnCommandLineExecuted(_acceptedCommandLine, status); + } + /// /// 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 status) + { + if (ActiveView.UsePlugin && !string.IsNullOrWhiteSpace(commandLine)) + { + _singleton._mockableMethods.OnCommandLineExecuted(commandLine, status); + } + } } } } diff --git a/PSReadLine/PublicAPI.cs b/PSReadLine/PublicAPI.cs index e661b29a9..4997b729b 100644 --- a/PSReadLine/PublicAPI.cs +++ b/PSReadLine/PublicAPI.cs @@ -28,6 +28,7 @@ public interface IPSConsoleReadLineMockableMethods bool RunspaceIsRemote(Runspace runspace); Task> PredictInput(Ast ast, Token[] tokens); void OnCommandLineAccepted(IReadOnlyList history); + void OnCommandLineExecuted(string commandLine, bool status); 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..e36d01155 100644 --- a/Polyfill/CommandPrediction.cs +++ b/Polyfill/CommandPrediction.cs @@ -6,6 +6,49 @@ namespace System.Management.Automation.Subsystem { + /// + /// The class represents a client that interacts with predictors. + /// + public class PredictionClient + { + /// + /// Kinds of the client. + /// + public enum ClientKind + { + /// + /// A terminal client, representing the command-line experience. + /// + Terminal, + + /// + /// An editor client, representing the editor experience. + /// + Editor, + } + + /// + /// Gets the client name. + /// + public string Name { get; } + + /// + /// Gets the client kind. + /// + public ClientKind Kind { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Name of the interactive client. + /// Kind of the interactive client. + public PredictionClient(string name, ClientKind kind) + { + Name = name; + Kind = kind; + } + } + /// /// The class represents the prediction result from a predictor. /// @@ -103,7 +146,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> PredictInput(PredictionClient client, Ast ast, Token[] astTokens) { return null; } @@ -117,7 +160,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> PredictInput(PredictionClient client, Ast ast, Token[] astTokens, int millisecondsTimeout) { return null; } @@ -128,7 +171,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. + /// The execution status of the last command line. True for success, False for failure + [HiddenAttribute] + public static void OnCommandLineExecuted(PredictionClient client, string commandLine, bool status) { } @@ -143,7 +197,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 +209,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) { } } diff --git a/nuget.config b/nuget.config index 654858614..1a7e7a0c6 100644 --- a/nuget.config +++ b/nuget.config @@ -2,6 +2,7 @@ + diff --git a/test/InlinePredictionTest.cs b/test/InlinePredictionTest.cs index 43fd485e9..379d69ec6 100644 --- a/test/InlinePredictionTest.cs +++ b/test/InlinePredictionTest.cs @@ -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/UnitTestReadLine.cs b/test/UnitTestReadLine.cs index e3c5dd06b..8fee0db9b 100644 --- a/test/UnitTestReadLine.cs +++ b/test/UnitTestReadLine.cs @@ -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(); @@ -65,6 +67,11 @@ public void OnCommandLineAccepted(IReadOnlyList history) commandHistory = history; } + public void OnCommandLineExecuted(string commandLine, bool status) + { + lastCommandRunStatus = status; + } + 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) { From 7d57e5783b8b61253a91c370c0822d1c81cdc97d Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 18 May 2021 22:08:17 -0700 Subject: [PATCH 2/5] Update based on changes in prediction interface --- PSReadLine/Prediction.Views.cs | 4 +-- PSReadLine/Prediction.cs | 18 ++++++------- PSReadLine/PublicAPI.cs | 4 +-- Polyfill/CommandPrediction.cs | 48 ++++++++++++++++++---------------- Polyfill/Polyfill.csproj | 2 +- test/InlinePredictionTest.cs | 2 +- test/UnitTestReadLine.cs | 4 +-- 7 files changed, 42 insertions(+), 40 deletions(-) diff --git a/PSReadLine/Prediction.Views.cs b/PSReadLine/Prediction.Views.cs index 6476a6a71..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 @@ -182,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 64baa4667..52b210e71 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,13 +17,13 @@ namespace Microsoft.PowerShell public partial class PSConsoleReadLine { private const string PSReadLine = "PSReadLine"; - private static PredictionClient s_predictionClient = new(PSReadLine, PredictionClient.ClientKind.Terminal); + 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(s_predictionClient, ast, tokens); + return CommandPrediction.PredictInputAsync(s_predictionClient, ast, tokens); } [ExcludeFromCodeCoverage] @@ -55,10 +55,10 @@ void IPSConsoleReadLineMockableMethods.OnCommandLineExecuted(string commandLine, /// /// Report the execution result (success or failure) of the last accepted command line. /// - /// - private void ReportExecutionStatus(bool status) + /// Whether the execution was successful. + private void ReportExecutionStatus(bool success) { - _prediction.OnCommandLineExecuted(_acceptedCommandLine, status); + _prediction.OnCommandLineExecuted(_acceptedCommandLine, success); } /// @@ -412,11 +412,11 @@ internal void OnCommandLineAccepted(string commandLine) /// /// Get called when the last accepted command line finished execution. /// - internal void OnCommandLineExecuted(string commandLine, bool status) + internal void OnCommandLineExecuted(string commandLine, bool success) { if (ActiveView.UsePlugin && !string.IsNullOrWhiteSpace(commandLine)) { - _singleton._mockableMethods.OnCommandLineExecuted(commandLine, status); + _singleton._mockableMethods.OnCommandLineExecuted(commandLine, success); } } } diff --git a/PSReadLine/PublicAPI.cs b/PSReadLine/PublicAPI.cs index 4997b729b..2818a3212 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,7 +26,7 @@ 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 status); void OnSuggestionDisplayed(Guid predictorId, uint session, int countOrIndex); diff --git a/Polyfill/CommandPrediction.cs b/Polyfill/CommandPrediction.cs index e36d01155..f1868cfa9 100644 --- a/Polyfill/CommandPrediction.cs +++ b/Polyfill/CommandPrediction.cs @@ -4,29 +4,29 @@ using System.Threading.Tasks; using System.Management.Automation.Language; -namespace System.Management.Automation.Subsystem +namespace System.Management.Automation.Subsystem.Prediction { /// - /// The class represents a client that interacts with predictors. + /// Kinds of prediction clients. /// - public class PredictionClient + public enum PredictionClientKind { /// - /// Kinds of the client. + /// A terminal client, representing the command-line experience. /// - public enum ClientKind - { - /// - /// A terminal client, representing the command-line experience. - /// - Terminal, - - /// - /// An editor client, representing the editor experience. - /// - Editor, - } + Terminal, + /// + /// An editor client, representing the editor experience. + /// + Editor, + } + + /// + /// The class represents a client that interacts with predictors. + /// + public class PredictionClient + { /// /// Gets the client name. /// @@ -35,14 +35,14 @@ public enum ClientKind /// /// Gets the client kind. /// - public ClientKind Kind { get; } + public PredictionClientKind Kind { get; } /// /// Initializes a new instance of the class. /// /// Name of the interactive client. /// Kind of the interactive client. - public PredictionClient(string name, ClientKind kind) + public PredictionClient(string name, PredictionClientKind kind) { Name = name; Kind = kind; @@ -146,7 +146,7 @@ public static class CommandPrediction /// The objects from parsing the current command line input. /// A list of objects. [HiddenAttribute] - public static Task> PredictInput(PredictionClient client, Ast ast, Token[] astTokens) + public static Task> PredictInputAsync(PredictionClient client, Ast ast, Token[] astTokens) { return null; } @@ -160,7 +160,7 @@ public static Task> PredictInput(PredictionClient client, /// The milliseconds to timeout. /// A list of objects. [HiddenAttribute] - public static Task> PredictInput(PredictionClient client, Ast ast, Token[] astTokens, int millisecondsTimeout) + public static Task> PredictInputAsync(PredictionClient client, Ast ast, Token[] astTokens, int millisecondsTimeout) { return null; } @@ -180,9 +180,9 @@ public static void OnCommandLineAccepted(PredictionClient client, IReadOnlyList< /// /// Represents the client that initiates the call. /// The last accepted command line. - /// The execution status of the last command line. True for success, False for failure + /// Whether the execution of the last command line was successful. [HiddenAttribute] - public static void OnCommandLineExecuted(PredictionClient client, string commandLine, bool status) + public static void OnCommandLineExecuted(PredictionClient client, string commandLine, bool success) { } @@ -217,9 +217,11 @@ public static void OnSuggestionAccepted(PredictionClient client, Guid predictorI #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..0ac05b211 100644 --- a/Polyfill/Polyfill.csproj +++ b/Polyfill/Polyfill.csproj @@ -12,7 +12,7 @@ - + diff --git a/test/InlinePredictionTest.cs b/test/InlinePredictionTest.cs index 379d69ec6..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; diff --git a/test/UnitTestReadLine.cs b/test/UnitTestReadLine.cs index 8fee0db9b..95671d2be 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; @@ -53,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>(); From 54c9086968ae553d5ce0ec3ec5df0f03a564a778 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 31 May 2021 11:06:51 -0700 Subject: [PATCH 3/5] Use the 7.2.0-preview.6 SDK --- PSReadLine/PSReadLine.csproj | 2 +- Polyfill/CommandPrediction.cs | 5 ++++- Polyfill/Polyfill.csproj | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/PSReadLine/PSReadLine.csproj b/PSReadLine/PSReadLine.csproj index 6127f6236..6e90804a8 100644 --- a/PSReadLine/PSReadLine.csproj +++ b/PSReadLine/PSReadLine.csproj @@ -22,7 +22,7 @@ - + diff --git a/Polyfill/CommandPrediction.cs b/Polyfill/CommandPrediction.cs index f1868cfa9..e7b331a8b 100644 --- a/Polyfill/CommandPrediction.cs +++ b/Polyfill/CommandPrediction.cs @@ -25,16 +25,18 @@ public enum PredictionClientKind /// /// The class represents a client that interacts with predictors. /// - public class PredictionClient + public sealed class PredictionClient { /// /// Gets the client name. /// + [HiddenAttribute] public string Name { get; } /// /// Gets the client kind. /// + [HiddenAttribute] public PredictionClientKind Kind { get; } /// @@ -42,6 +44,7 @@ public class PredictionClient /// /// Name of the interactive client. /// Kind of the interactive client. + [HiddenAttribute] public PredictionClient(string name, PredictionClientKind kind) { Name = name; diff --git a/Polyfill/Polyfill.csproj b/Polyfill/Polyfill.csproj index 0ac05b211..c8334056e 100644 --- a/Polyfill/Polyfill.csproj +++ b/Polyfill/Polyfill.csproj @@ -12,7 +12,7 @@ - + From dda4e04716a4636ff51f843639d58f5e6c7b8e5e Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 31 May 2021 14:09:22 -0700 Subject: [PATCH 4/5] Update SDK version in 2 more places --- MockPSConsole/MockPSConsole.csproj | 2 +- test/PSReadLine.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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 @@ - + From 31ddb279ca57ca1c5d8c1817fec32be3d991aaba Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 31 May 2021 14:20:58 -0700 Subject: [PATCH 5/5] Some cleanup --- PSReadLine/Prediction.cs | 4 ++-- PSReadLine/PublicAPI.cs | 2 +- nuget.config | 1 - test/UnitTestReadLine.cs | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/PSReadLine/Prediction.cs b/PSReadLine/Prediction.cs index 52b210e71..42edb5f84 100644 --- a/PSReadLine/Prediction.cs +++ b/PSReadLine/Prediction.cs @@ -45,9 +45,9 @@ void IPSConsoleReadLineMockableMethods.OnCommandLineAccepted(IReadOnlyList> PredictInputAsync(Ast ast, Token[] tokens); void OnCommandLineAccepted(IReadOnlyList history); - void OnCommandLineExecuted(string commandLine, bool status); + 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/nuget.config b/nuget.config index 1a7e7a0c6..654858614 100644 --- a/nuget.config +++ b/nuget.config @@ -2,7 +2,6 @@ - diff --git a/test/UnitTestReadLine.cs b/test/UnitTestReadLine.cs index 95671d2be..f4587285b 100644 --- a/test/UnitTestReadLine.cs +++ b/test/UnitTestReadLine.cs @@ -67,9 +67,9 @@ public void OnCommandLineAccepted(IReadOnlyList history) commandHistory = history; } - public void OnCommandLineExecuted(string commandLine, bool status) + public void OnCommandLineExecuted(string commandLine, bool success) { - lastCommandRunStatus = status; + lastCommandRunStatus = success; } public void OnSuggestionDisplayed(Guid predictorId, uint session, int countOrIndex)