Skip to content

Commit f5a7ebd

Browse files
authored
Update the use of the prediction interface to adapt to the breaking changes introduced in PowerShell 7.2.0-preview.6 (#2524)
1 parent dc38b45 commit f5a7ebd

14 files changed

+420
-47
lines changed

MockPSConsole/MockPSConsole.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
</ItemGroup>
1919

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

2424
<ItemGroup>

MockPSConsole/Program.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ static void Main()
9999
ps.Commands.Clear();
100100
Console.Write(string.Join("", ps.AddCommand("prompt").Invoke<string>()));
101101

102-
var line = PSConsoleReadLine.ReadLine(rs, executionContext);
102+
var line = PSConsoleReadLine.ReadLine(rs, executionContext, lastRunStatus: null);
103103
Console.WriteLine(line);
104104
line = line.Trim();
105105
if (line.Equals("exit"))

PSReadLine/PSReadLine.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
</ItemGroup>
2323

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

2828
<ItemGroup>

PSReadLine/PSReadLine.psm1

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
function PSConsoleHostReadLine
22
{
3+
## Get the execution status of the last accepted user input.
4+
## This needs to be done as the first thing because any script run will flush $?.
5+
$lastRunStatus = $?
36
Microsoft.PowerShell.Core\Set-StrictMode -Off
4-
[Microsoft.PowerShell.PSConsoleReadLine]::ReadLine($host.Runspace, $ExecutionContext)
7+
[Microsoft.PowerShell.PSConsoleReadLine]::ReadLine($host.Runspace, $ExecutionContext, $lastRunStatus)
58
}

PSReadLine/Prediction.Views.cs

+4-15
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
using System.Collections.Generic;
77
using System.Text;
88
using System.Threading.Tasks;
9-
using System.Management.Automation.Subsystem;
9+
using System.Management.Automation.Subsystem.Prediction;
1010
using Microsoft.PowerShell.Internal;
1111

1212
namespace Microsoft.PowerShell
@@ -33,12 +33,12 @@ protected PredictionViewBase(PSConsoleReadLine singleton)
3333
/// <summary>
3434
/// Gets whether to use plugin as a source.
3535
/// </summary>
36-
protected bool UsePlugin => (_singleton._options.PredictionSource & PredictionSource.Plugin) != 0;
36+
internal bool UsePlugin => (_singleton._options.PredictionSource & PredictionSource.Plugin) != 0;
3737

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

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

83-
/// <summary>
84-
/// Get called when a command line is accepted.
85-
/// </summary>
86-
internal void OnCommandLineAccepted(string commandLine)
87-
{
88-
if (UsePlugin && !string.IsNullOrWhiteSpace(commandLine))
89-
{
90-
_singleton._mockableMethods.OnCommandLineAccepted(_singleton._recentHistory.ToArray());
91-
}
92-
}
93-
9483
/// <summary>
9584
/// Currently we only select single-line history that is prefixed with the user input,
9685
/// but it can be improved to not strictly use the user input as a prefix, but a hint
@@ -193,7 +182,7 @@ protected List<SuggestionEntry> GetHistorySuggestions(string input, int count)
193182
/// </summary>
194183
protected void PredictInput()
195184
{
196-
_predictionTask = _singleton._mockableMethods.PredictInput(_singleton._ast, _singleton._tokens);
185+
_predictionTask = _singleton._mockableMethods.PredictInputAsync(_singleton._ast, _singleton._tokens);
197186
}
198187

199188
/// <summary>

PSReadLine/Prediction.cs

+44-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
using System.Threading.Tasks;
88
using System.Management.Automation;
99
using System.Management.Automation.Language;
10-
using System.Management.Automation.Subsystem;
10+
using System.Management.Automation.Subsystem.Prediction;
1111
using System.Diagnostics.CodeAnalysis;
1212
using Microsoft.PowerShell.Internal;
1313
using Microsoft.PowerShell.PSReadLine;
@@ -17,34 +17,50 @@ namespace Microsoft.PowerShell
1717
public partial class PSConsoleReadLine
1818
{
1919
private const string PSReadLine = "PSReadLine";
20+
private static PredictionClient s_predictionClient = new(PSReadLine, PredictionClientKind.Terminal);
2021

2122
// Stub helper methods so prediction can be mocked
2223
[ExcludeFromCodeCoverage]
23-
Task<List<PredictionResult>> IPSConsoleReadLineMockableMethods.PredictInput(Ast ast, Token[] tokens)
24+
Task<List<PredictionResult>> IPSConsoleReadLineMockableMethods.PredictInputAsync(Ast ast, Token[] tokens)
2425
{
25-
return CommandPrediction.PredictInput(PSReadLine, ast, tokens);
26+
return CommandPrediction.PredictInputAsync(s_predictionClient, ast, tokens);
2627
}
2728

2829
[ExcludeFromCodeCoverage]
2930
void IPSConsoleReadLineMockableMethods.OnSuggestionDisplayed(Guid predictorId, uint session, int countOrIndex)
3031
{
31-
CommandPrediction.OnSuggestionDisplayed(PSReadLine, predictorId, session, countOrIndex);
32+
CommandPrediction.OnSuggestionDisplayed(s_predictionClient, predictorId, session, countOrIndex);
3233
}
3334

3435
[ExcludeFromCodeCoverage]
3536
void IPSConsoleReadLineMockableMethods.OnSuggestionAccepted(Guid predictorId, uint session, string suggestionText)
3637
{
37-
CommandPrediction.OnSuggestionAccepted(PSReadLine, predictorId, session, suggestionText);
38+
CommandPrediction.OnSuggestionAccepted(s_predictionClient, predictorId, session, suggestionText);
3839
}
3940

4041
[ExcludeFromCodeCoverage]
4142
void IPSConsoleReadLineMockableMethods.OnCommandLineAccepted(IReadOnlyList<string> history)
4243
{
43-
CommandPrediction.OnCommandLineAccepted(PSReadLine, history);
44+
CommandPrediction.OnCommandLineAccepted(s_predictionClient, history);
45+
}
46+
47+
[ExcludeFromCodeCoverage]
48+
void IPSConsoleReadLineMockableMethods.OnCommandLineExecuted(string commandLine, bool success)
49+
{
50+
CommandPrediction.OnCommandLineExecuted(s_predictionClient, commandLine, success);
4451
}
4552

4653
private readonly Prediction _prediction;
4754

55+
/// <summary>
56+
/// Report the execution result (success or failure) of the last accepted command line.
57+
/// </summary>
58+
/// <param name="success">Whether the execution was successful.</param>
59+
private void ReportExecutionStatus(bool success)
60+
{
61+
_prediction.OnCommandLineExecuted(_acceptedCommandLine, success);
62+
}
63+
4864
/// <summary>
4965
/// Accept the suggestion text if there is one.
5066
/// </summary>
@@ -381,6 +397,28 @@ internal bool RevertSuggestion()
381397

382398
return retValue;
383399
}
400+
401+
/// <summary>
402+
/// Get called when a command line is accepted.
403+
/// </summary>
404+
internal void OnCommandLineAccepted(string commandLine)
405+
{
406+
if (ActiveView.UsePlugin && !string.IsNullOrWhiteSpace(commandLine))
407+
{
408+
_singleton._mockableMethods.OnCommandLineAccepted(_singleton._recentHistory.ToArray());
409+
}
410+
}
411+
412+
/// <summary>
413+
/// Get called when the last accepted command line finished execution.
414+
/// </summary>
415+
internal void OnCommandLineExecuted(string commandLine, bool success)
416+
{
417+
if (ActiveView.UsePlugin && !string.IsNullOrWhiteSpace(commandLine))
418+
{
419+
_singleton._mockableMethods.OnCommandLineExecuted(commandLine, success);
420+
}
421+
}
384422
}
385423
}
386424
}

PSReadLine/PublicAPI.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
using System.Management.Automation;
1010
using System.Management.Automation.Language;
1111
using System.Management.Automation.Runspaces;
12-
using System.Management.Automation.Subsystem;
12+
using System.Management.Automation.Subsystem.Prediction;
1313
using System.Text;
1414
using System.Threading.Tasks;
1515
using Microsoft.PowerShell.PSReadLine;
@@ -26,8 +26,9 @@ public interface IPSConsoleReadLineMockableMethods
2626
void Ding();
2727
CommandCompletion CompleteInput(string input, int cursorIndex, Hashtable options, System.Management.Automation.PowerShell powershell);
2828
bool RunspaceIsRemote(Runspace runspace);
29-
Task<List<PredictionResult>> PredictInput(Ast ast, Token[] tokens);
29+
Task<List<PredictionResult>> PredictInputAsync(Ast ast, Token[] tokens);
3030
void OnCommandLineAccepted(IReadOnlyList<string> history);
31+
void OnCommandLineExecuted(string commandLine, bool success);
3132
void OnSuggestionDisplayed(Guid predictorId, uint session, int countOrIndex);
3233
void OnSuggestionAccepted(Guid predictorId, uint session, string suggestionText);
3334
void RenderFullHelp(string content, string regexPatternToScrollTo);

PSReadLine/ReadLine.cs

+17-7
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods
6565
private readonly StringBuilder _statusBuffer;
6666
private bool _statusIsErrorMessage;
6767
private string _statusLinePrompt;
68+
private string _acceptedCommandLine;
6869
private List<EditItem> _edits;
6970
private int _editGroupStart;
7071
private int _undoEditIndex;
@@ -302,19 +303,23 @@ private void PrependQueuedKeys(PSKeyInfo key)
302303
/// after the prompt has been displayed.
303304
/// </summary>
304305
/// <returns>The complete command line.</returns>
305-
public static string ReadLine(Runspace runspace, EngineIntrinsics engineIntrinsics)
306+
public static string ReadLine(Runspace runspace, EngineIntrinsics engineIntrinsics, bool? lastRunStatus)
306307
{
307308
// Use a default cancellation token instead of CancellationToken.None because the
308309
// WaitHandle is shared and could be triggered accidently.
309-
return ReadLine(runspace, engineIntrinsics, _defaultCancellationToken);
310+
return ReadLine(runspace, engineIntrinsics, _defaultCancellationToken, lastRunStatus);
310311
}
311312

312313
/// <summary>
313314
/// Entry point - called by custom PSHost implementations that require the
314315
/// ability to cancel ReadLine.
315316
/// </summary>
316317
/// <returns>The complete command line.</returns>
317-
public static string ReadLine(Runspace runspace, EngineIntrinsics engineIntrinsics, CancellationToken cancellationToken)
318+
public static string ReadLine(
319+
Runspace runspace,
320+
EngineIntrinsics engineIntrinsics,
321+
CancellationToken cancellationToken,
322+
bool? lastRunStatus)
318323
{
319324
var console = _singleton._console;
320325

@@ -348,6 +353,11 @@ public static string ReadLine(Runspace runspace, EngineIntrinsics engineIntrinsi
348353
catch {}
349354
}
350355

356+
if (lastRunStatus.HasValue)
357+
{
358+
_singleton.ReportExecutionStatus(lastRunStatus.Value);
359+
}
360+
351361
bool firstTime = true;
352362
while (true)
353363
{
@@ -486,11 +496,11 @@ private string InputLoop()
486496
ProcessOneKey(key, _dispatchTable, ignoreIfNoAction: false, arg: null);
487497
if (_inputAccepted)
488498
{
489-
var commandLine = _buffer.ToString();
490-
MaybeAddToHistory(commandLine, _edits, _undoEditIndex);
499+
_acceptedCommandLine = _buffer.ToString();
500+
MaybeAddToHistory(_acceptedCommandLine, _edits, _undoEditIndex);
491501

492-
_prediction.ActiveView.OnCommandLineAccepted(commandLine);
493-
return commandLine;
502+
_prediction.OnCommandLineAccepted(_acceptedCommandLine);
503+
return _acceptedCommandLine;
494504
}
495505

496506
if (killCommandCount == _killCommandCount)

Polyfill/CommandPrediction.cs

+66-7
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,54 @@
44
using System.Threading.Tasks;
55
using System.Management.Automation.Language;
66

7-
namespace System.Management.Automation.Subsystem
7+
namespace System.Management.Automation.Subsystem.Prediction
88
{
9+
/// <summary>
10+
/// Kinds of prediction clients.
11+
/// </summary>
12+
public enum PredictionClientKind
13+
{
14+
/// <summary>
15+
/// A terminal client, representing the command-line experience.
16+
/// </summary>
17+
Terminal,
18+
19+
/// <summary>
20+
/// An editor client, representing the editor experience.
21+
/// </summary>
22+
Editor,
23+
}
24+
25+
/// <summary>
26+
/// The class represents a client that interacts with predictors.
27+
/// </summary>
28+
public sealed class PredictionClient
29+
{
30+
/// <summary>
31+
/// Gets the client name.
32+
/// </summary>
33+
[HiddenAttribute]
34+
public string Name { get; }
35+
36+
/// <summary>
37+
/// Gets the client kind.
38+
/// </summary>
39+
[HiddenAttribute]
40+
public PredictionClientKind Kind { get; }
41+
42+
/// <summary>
43+
/// Initializes a new instance of the <see cref="PredictionClient"/> class.
44+
/// </summary>
45+
/// <param name="name">Name of the interactive client.</param>
46+
/// <param name="kind">Kind of the interactive client.</param>
47+
[HiddenAttribute]
48+
public PredictionClient(string name, PredictionClientKind kind)
49+
{
50+
Name = name;
51+
Kind = kind;
52+
}
53+
}
54+
955
/// <summary>
1056
/// The class represents the prediction result from a predictor.
1157
/// </summary>
@@ -103,7 +149,7 @@ public static class CommandPrediction
103149
/// <param name="astTokens">The <see cref="Token"/> objects from parsing the current command line input.</param>
104150
/// <returns>A list of <see cref="PredictionResult"/> objects.</returns>
105151
[HiddenAttribute]
106-
public static Task<List<PredictionResult>> PredictInput(string client, Ast ast, Token[] astTokens)
152+
public static Task<List<PredictionResult>> PredictInputAsync(PredictionClient client, Ast ast, Token[] astTokens)
107153
{
108154
return null;
109155
}
@@ -117,7 +163,7 @@ public static Task<List<PredictionResult>> PredictInput(string client, Ast ast,
117163
/// <param name="millisecondsTimeout">The milliseconds to timeout.</param>
118164
/// <returns>A list of <see cref="PredictionResult"/> objects.</returns>
119165
[HiddenAttribute]
120-
public static Task<List<PredictionResult>> PredictInput(string client, Ast ast, Token[] astTokens, int millisecondsTimeout)
166+
public static Task<List<PredictionResult>> PredictInputAsync(PredictionClient client, Ast ast, Token[] astTokens, int millisecondsTimeout)
121167
{
122168
return null;
123169
}
@@ -128,7 +174,18 @@ public static Task<List<PredictionResult>> PredictInput(string client, Ast ast,
128174
/// <param name="client">Represents the client that initiates the call.</param>
129175
/// <param name="history">History command lines provided as references for prediction.</param>
130176
[HiddenAttribute]
131-
public static void OnCommandLineAccepted(string client, IReadOnlyList<string> history)
177+
public static void OnCommandLineAccepted(PredictionClient client, IReadOnlyList<string> history)
178+
{
179+
}
180+
181+
/// <summary>
182+
/// Allow registered predictors to know the execution result (success/failure) of the last accepted command line.
183+
/// </summary>
184+
/// <param name="client">Represents the client that initiates the call.</param>
185+
/// <param name="commandLine">The last accepted command line.</param>
186+
/// <param name="success">Whether the execution of the last command line was successful.</param>
187+
[HiddenAttribute]
188+
public static void OnCommandLineExecuted(PredictionClient client, string commandLine, bool success)
132189
{
133190
}
134191

@@ -143,7 +200,7 @@ public static void OnCommandLineAccepted(string client, IReadOnlyList<string> hi
143200
/// When the value is <code><= 0</code>, it means a single suggestion from the list got displayed, and the index is the absolute value.
144201
/// </param>
145202
[HiddenAttribute]
146-
public static void OnSuggestionDisplayed(string client, Guid predictorId, uint session, int countOrIndex)
203+
public static void OnSuggestionDisplayed(PredictionClient client, Guid predictorId, uint session, int countOrIndex)
147204
{
148205
}
149206

@@ -155,17 +212,19 @@ public static void OnSuggestionDisplayed(string client, Guid predictorId, uint s
155212
/// <param name="session">The mini-session where the accepted suggestion came from.</param>
156213
/// <param name="suggestionText">The accepted suggestion text.</param>
157214
[HiddenAttribute]
158-
public static void OnSuggestionAccepted(string client, Guid predictorId, uint session, string suggestionText)
215+
public static void OnSuggestionAccepted(PredictionClient client, Guid predictorId, uint session, string suggestionText)
159216
{
160217
}
161218
}
162219
}
163220

164221
#else
165222

166-
using System.Management.Automation.Subsystem;
223+
using System.Management.Automation.Subsystem.Prediction;
167224
using System.Runtime.CompilerServices;
168225

226+
[assembly: TypeForwardedTo(typeof(PredictionClientKind))]
227+
[assembly: TypeForwardedTo(typeof(PredictionClient))]
169228
[assembly: TypeForwardedTo(typeof(PredictiveSuggestion))]
170229
[assembly: TypeForwardedTo(typeof(PredictionResult))]
171230
[assembly: TypeForwardedTo(typeof(CommandPrediction))]

Polyfill/Polyfill.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</ItemGroup>
1313

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

1818
<PropertyGroup Condition="'$(TargetFramework)' == 'net461'">

0 commit comments

Comments
 (0)