Skip to content

Update the use of the prediction interface to adapt to the breaking changes introduced in PowerShell 7.2.0-preview.6 #2524

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MockPSConsole/MockPSConsole.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.PowerShell.SDK" version="7.2.0-preview.3" />
<PackageReference Include="Microsoft.PowerShell.SDK" version="7.2.0-preview.6" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion MockPSConsole/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ static void Main()
ps.Commands.Clear();
Console.Write(string.Join("", ps.AddCommand("prompt").Invoke<string>()));

var line = PSConsoleReadLine.ReadLine(rs, executionContext);
var line = PSConsoleReadLine.ReadLine(rs, executionContext, lastRunStatus: null);
Console.WriteLine(line);
line = line.Trim();
if (line.Equals("exit"))
Expand Down
2 changes: 1 addition & 1 deletion PSReadLine/PSReadLine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="System.Management.Automation" Version="7.2.0-preview.3" />
<PackageReference Include="System.Management.Automation" Version="7.2.0-preview.6" />
</ItemGroup>

<ItemGroup>
Expand Down
5 changes: 4 additions & 1 deletion PSReadLine/PSReadLine.psm1
Original file line number Diff line number Diff line change
@@ -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)
}
19 changes: 4 additions & 15 deletions PSReadLine/Prediction.Views.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -33,12 +33,12 @@ protected PredictionViewBase(PSConsoleReadLine singleton)
/// <summary>
/// Gets whether to use plugin as a source.
/// </summary>
protected bool UsePlugin => (_singleton._options.PredictionSource & PredictionSource.Plugin) != 0;
internal bool UsePlugin => (_singleton._options.PredictionSource & PredictionSource.Plugin) != 0;

/// <summary>
/// Gets whether to use history as a source.
/// </summary>
protected bool UseHistory => (_singleton._options.PredictionSource & PredictionSource.History) != 0;
internal bool UseHistory => (_singleton._options.PredictionSource & PredictionSource.History) != 0;

/// <summary>
/// Gets whether an update to the view is pending.
Expand Down Expand Up @@ -80,17 +80,6 @@ internal virtual void Reset()
_predictionTask = null;
}

/// <summary>
/// Get called when a command line is accepted.
/// </summary>
internal void OnCommandLineAccepted(string commandLine)
{
if (UsePlugin && !string.IsNullOrWhiteSpace(commandLine))
{
_singleton._mockableMethods.OnCommandLineAccepted(_singleton._recentHistory.ToArray());
}
}

/// <summary>
/// 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
Expand Down Expand Up @@ -193,7 +182,7 @@ protected List<SuggestionEntry> GetHistorySuggestions(string input, int count)
/// </summary>
protected void PredictInput()
{
_predictionTask = _singleton._mockableMethods.PredictInput(_singleton._ast, _singleton._tokens);
_predictionTask = _singleton._mockableMethods.PredictInputAsync(_singleton._ast, _singleton._tokens);
}

/// <summary>
Expand Down
50 changes: 44 additions & 6 deletions PSReadLine/Prediction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<List<PredictionResult>> IPSConsoleReadLineMockableMethods.PredictInput(Ast ast, Token[] tokens)
Task<List<PredictionResult>> 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<string> 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;

/// <summary>
/// Report the execution result (success or failure) of the last accepted command line.
/// </summary>
/// <param name="success">Whether the execution was successful.</param>
private void ReportExecutionStatus(bool success)
{
_prediction.OnCommandLineExecuted(_acceptedCommandLine, success);
}

/// <summary>
/// Accept the suggestion text if there is one.
/// </summary>
Expand Down Expand Up @@ -381,6 +397,28 @@ internal bool RevertSuggestion()

return retValue;
}

/// <summary>
/// Get called when a command line is accepted.
/// </summary>
internal void OnCommandLineAccepted(string commandLine)
{
if (ActiveView.UsePlugin && !string.IsNullOrWhiteSpace(commandLine))
{
_singleton._mockableMethods.OnCommandLineAccepted(_singleton._recentHistory.ToArray());
}
}

/// <summary>
/// Get called when the last accepted command line finished execution.
/// </summary>
internal void OnCommandLineExecuted(string commandLine, bool success)
{
if (ActiveView.UsePlugin && !string.IsNullOrWhiteSpace(commandLine))
{
_singleton._mockableMethods.OnCommandLineExecuted(commandLine, success);
}
}
}
}
}
5 changes: 3 additions & 2 deletions PSReadLine/PublicAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<List<PredictionResult>> PredictInput(Ast ast, Token[] tokens);
Task<List<PredictionResult>> PredictInputAsync(Ast ast, Token[] tokens);
void OnCommandLineAccepted(IReadOnlyList<string> 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);
Expand Down
24 changes: 17 additions & 7 deletions PSReadLine/ReadLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods
private readonly StringBuilder _statusBuffer;
private bool _statusIsErrorMessage;
private string _statusLinePrompt;
private string _acceptedCommandLine;
private List<EditItem> _edits;
private int _editGroupStart;
private int _undoEditIndex;
Expand Down Expand Up @@ -302,19 +303,23 @@ private void PrependQueuedKeys(PSKeyInfo key)
/// after the prompt has been displayed.
/// </summary>
/// <returns>The complete command line.</returns>
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);
}

/// <summary>
/// Entry point - called by custom PSHost implementations that require the
/// ability to cancel ReadLine.
/// </summary>
/// <returns>The complete command line.</returns>
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;

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand Down
73 changes: 66 additions & 7 deletions Polyfill/CommandPrediction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,54 @@
using System.Threading.Tasks;
using System.Management.Automation.Language;

namespace System.Management.Automation.Subsystem
namespace System.Management.Automation.Subsystem.Prediction
{
/// <summary>
/// Kinds of prediction clients.
/// </summary>
public enum PredictionClientKind
{
/// <summary>
/// A terminal client, representing the command-line experience.
/// </summary>
Terminal,

/// <summary>
/// An editor client, representing the editor experience.
/// </summary>
Editor,
}

/// <summary>
/// The class represents a client that interacts with predictors.
/// </summary>
public sealed class PredictionClient
{
/// <summary>
/// Gets the client name.
/// </summary>
[HiddenAttribute]
public string Name { get; }

/// <summary>
/// Gets the client kind.
/// </summary>
[HiddenAttribute]
public PredictionClientKind Kind { get; }

/// <summary>
/// Initializes a new instance of the <see cref="PredictionClient"/> class.
/// </summary>
/// <param name="name">Name of the interactive client.</param>
/// <param name="kind">Kind of the interactive client.</param>
[HiddenAttribute]
public PredictionClient(string name, PredictionClientKind kind)
{
Name = name;
Kind = kind;
}
}

/// <summary>
/// The class represents the prediction result from a predictor.
/// </summary>
Expand Down Expand Up @@ -103,7 +149,7 @@ public static class CommandPrediction
/// <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>
[HiddenAttribute]
public static Task<List<PredictionResult>> PredictInput(string client, Ast ast, Token[] astTokens)
public static Task<List<PredictionResult>> PredictInputAsync(PredictionClient client, Ast ast, Token[] astTokens)
{
return null;
}
Expand All @@ -117,7 +163,7 @@ public static Task<List<PredictionResult>> PredictInput(string client, Ast ast,
/// <param name="millisecondsTimeout">The milliseconds to timeout.</param>
/// <returns>A list of <see cref="PredictionResult"/> objects.</returns>
[HiddenAttribute]
public static Task<List<PredictionResult>> PredictInput(string client, Ast ast, Token[] astTokens, int millisecondsTimeout)
public static Task<List<PredictionResult>> PredictInputAsync(PredictionClient client, Ast ast, Token[] astTokens, int millisecondsTimeout)
{
return null;
}
Expand All @@ -128,7 +174,18 @@ public static Task<List<PredictionResult>> PredictInput(string client, Ast ast,
/// <param name="client">Represents the client that initiates the call.</param>
/// <param name="history">History command lines provided as references for prediction.</param>
[HiddenAttribute]
public static void OnCommandLineAccepted(string client, IReadOnlyList<string> history)
public static void OnCommandLineAccepted(PredictionClient client, IReadOnlyList<string> history)
{
}

/// <summary>
/// Allow registered predictors to know the execution result (success/failure) of the last accepted command line.
/// </summary>
/// <param name="client">Represents the client that initiates the call.</param>
/// <param name="commandLine">The last accepted command line.</param>
/// <param name="success">Whether the execution of the last command line was successful.</param>
[HiddenAttribute]
public static void OnCommandLineExecuted(PredictionClient client, string commandLine, bool success)
{
}

Expand All @@ -143,7 +200,7 @@ public static void OnCommandLineAccepted(string client, IReadOnlyList<string> hi
/// When the value is <code><= 0</code>, it means a single suggestion from the list got displayed, and the index is the absolute value.
/// </param>
[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)
{
}

Expand All @@ -155,17 +212,19 @@ public static void OnSuggestionDisplayed(string client, Guid predictorId, uint s
/// <param name="session">The mini-session where the accepted suggestion came from.</param>
/// <param name="suggestionText">The accepted suggestion text.</param>
[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)
{
}
}
}

#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))]
Expand Down
2 changes: 1 addition & 1 deletion Polyfill/Polyfill.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="System.Management.Automation" Version="7.2.0-preview.3" />
<PackageReference Include="System.Management.Automation" Version="7.2.0-preview.6" />
</ItemGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'net461'">
Expand Down
Loading