Files
PowerShell-PSReadLine/test/InlinePredictionTest.cs
T
Andy Jordan 06e3b4bcb6 Add fixture to enable existing tests for screen reader mode
Tests that depend on supported features such as menu completions, list
view, inline predictions, continuation prompt, and colors are skipped.

The "helper" module for running tests from the command-line and in
AppVeyor is minimally updated to not skip the new fixture.

A `BlankRestOfBuffer()` function implements the semantics of the `0J`
control sequence in the mock console.
2025-08-27 17:46:47 -07:00

844 lines
44 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation.Language;
using System.Management.Automation.Subsystem.Prediction;
using System.Reflection;
using Microsoft.PowerShell;
using Xunit;
namespace Test
{
public partial class ReadLine
{
[SkippableFact]
public void Inline_RenderSuggestion()
{
Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode.");
TestSetup(KeyMode.Cmd,
new KeyHandler("Ctrl+f", PSConsoleReadLine.ForwardWord));
using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView);
_mockedMethods.ClearPredictionFields();
// No matching history entry
SetHistory("echo -bar", "eca -zoo");
Test("a", Keys(
'a', CheckThat(() => AssertScreenIs(1, TokenClassification.Command, 'a')),
_.Ctrl_f, CheckThat(() => AssertScreenIs(1, TokenClassification.Command, 'a')),
_.RightArrow, CheckThat(() => AssertScreenIs(1, TokenClassification.Command, 'a')),
CheckThat(() => AssertCursorLeftIs(1))
));
// The 'OnXXXAccepted' callback won't be hit when 'History' is the only source.
Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId);
Assert.Null(_mockedMethods.acceptedSuggestion);
Assert.Null(_mockedMethods.commandHistory);
// Different matches as more input coming
SetHistory("echo -bar", "eca -zoo");
Test("ech", Keys(
'e', CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, 'e',
TokenClassification.InlinePrediction, "ca -zoo")),
'c', CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ec",
TokenClassification.InlinePrediction, "a -zoo")),
'h', CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ech",
TokenClassification.InlinePrediction, "o -bar")),
// Once accepted, the suggestion text should be blanked out.
_.Enter, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ech"))
));
// The 'OnXXXAccepted' callback won't be hit when 'History' is the only source.
Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId);
Assert.Null(_mockedMethods.acceptedSuggestion);
Assert.Null(_mockedMethods.commandHistory);
// Accept all or partial suggestion text with 'ForwardChar' and 'ForwardWord'
SetHistory("echo -bar", "eca -zoo");
Test("eca ", Keys(
'e', CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, 'e',
TokenClassification.InlinePrediction, "ca -zoo")),
_.RightArrow, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "eca",
TokenClassification.None, ' ',
TokenClassification.Parameter, "-zoo")),
_.Ctrl_z, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, 'e',
TokenClassification.InlinePrediction, "ca -zoo")),
_.Ctrl_f, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "eca",
TokenClassification.None, ' ',
TokenClassification.InlinePrediction, "-zoo")),
CheckThat(() => AssertCursorLeftIs(4)),
_.Ctrl_f, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "eca",
TokenClassification.None, ' ',
TokenClassification.Parameter, "-zoo")),
_.Ctrl_z, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "eca",
TokenClassification.None, ' ',
TokenClassification.InlinePrediction, "-zoo"))
));
// The 'OnXXXAccepted' callback won't be hit when 'History' is the only source.
Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId);
Assert.Null(_mockedMethods.acceptedSuggestion);
Assert.Null(_mockedMethods.commandHistory);
}
[SkippableFact]
public void Inline_CustomKeyBindingsToAcceptSuggestion()
{
Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode.");
TestSetup(KeyMode.Cmd,
new KeyHandler("Alt+g", PSConsoleReadLine.AcceptSuggestion),
new KeyHandler("Alt+f", PSConsoleReadLine.AcceptNextSuggestionWord));
using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView);
SetHistory("echo -bar", "eca -zoo");
Test("eca ", Keys(
"ec", CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ec",
TokenClassification.InlinePrediction, "a -zoo")),
CheckThat(() => AssertCursorLeftIs(2)),
_.Alt_g, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "eca",
TokenClassification.None, ' ',
TokenClassification.Parameter, "-zoo")),
CheckThat(() => AssertCursorLeftIs(8)),
_.Ctrl_z, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ec",
TokenClassification.InlinePrediction, "a -zoo")),
CheckThat(() => AssertCursorLeftIs(2)),
_.Alt_f, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "eca",
TokenClassification.None, ' ',
TokenClassification.InlinePrediction, "-zoo")),
CheckThat(() => AssertCursorLeftIs(4)),
_.Alt_f, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "eca",
TokenClassification.None, ' ',
TokenClassification.Parameter, "-zoo")),
CheckThat(() => AssertCursorLeftIs(8)),
_.Ctrl_z, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "eca",
TokenClassification.None, ' ',
TokenClassification.InlinePrediction, "-zoo")),
CheckThat(() => AssertCursorLeftIs(4)),
// Revert back to 'ec'.
_.Ctrl_z, CheckThat(() => AssertCursorLeftIs(2)),
// Move cursor to column 0.
_.LeftArrow, _.LeftArrow,
CheckThat(() => AssertCursorLeftIs(0)),
_.Alt_g, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "eca",
TokenClassification.None, ' ',
TokenClassification.Parameter, "-zoo")),
CheckThat(() => AssertCursorLeftIs(8)),
_.Ctrl_z, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ec",
TokenClassification.InlinePrediction, "a -zoo")),
CheckThat(() => AssertCursorLeftIs(2)),
// Move cursor to column 0 again.
_.LeftArrow, _.LeftArrow,
CheckThat(() => AssertCursorLeftIs(0)),
_.Alt_f, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "eca",
TokenClassification.None, ' ',
TokenClassification.InlinePrediction, "-zoo")),
CheckThat(() => AssertCursorLeftIs(4))
));
}
[SkippableFact]
public void Inline_AcceptNextSuggestionWordCanAcceptMoreThanOneWords()
{
Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode.");
TestSetup(KeyMode.Cmd,
new KeyHandler("Ctrl+f", PSConsoleReadLine.ForwardWord),
new KeyHandler("Alt+f", PSConsoleReadLine.AcceptNextSuggestionWord));
using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView);
SetHistory("abc def ghi jkl");
Test("abc def ghi jkl", Keys(
'a', CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, 'a',
TokenClassification.InlinePrediction, "bc def ghi jkl")),
_.Alt_3, _.Ctrl_f,
CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "abc",
TokenClassification.None, " def ghi ",
TokenClassification.InlinePrediction, "jkl")),
_.Ctrl_z, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, 'a',
TokenClassification.InlinePrediction, "bc def ghi jkl")),
_.Alt_3, _.Alt_f,
CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "abc",
TokenClassification.None, " def ghi ",
TokenClassification.InlinePrediction, "jkl")),
_.Ctrl_z, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, 'a',
TokenClassification.InlinePrediction, "bc def ghi jkl")),
_.Alt_8, _.Alt_f,
CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "abc",
TokenClassification.None, " def ghi jkl"))
));
}
[SkippableFact]
public void Inline_AcceptSuggestionWithSelection()
{
Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode.");
TestSetup(KeyMode.Cmd,
new KeyHandler("Ctrl+f", PSConsoleReadLine.ForwardWord));
using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView);
SetHistory("git diff --cached", "git diff");
Test("git diff", Keys(
"git", CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.InlinePrediction, " diff")),
_.RightArrow, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.None, " diff")),
_.Ctrl_z, CheckThat(() => AssertCursorLeftIs(3)),
_.Ctrl_f, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.None, " diff",
TokenClassification.InlinePrediction, " --cached")),
// Perform visual selection and then accept suggestion.
_.Ctrl_z, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.InlinePrediction, " diff")),
_.LeftArrow, _.Shift_RightArrow,
CheckThat(() =>
{
PSConsoleReadLine.GetSelectionState(out var start, out var length);
Assert.Equal(2, start);
Assert.Equal(1, length);
}),
_.RightArrow, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.None, " diff")),
// Perform visual selection and then accept next suggestion word.
_.Ctrl_z, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.InlinePrediction, " diff")),
_.LeftArrow, _.Shift_RightArrow,
CheckThat(() =>
{
PSConsoleReadLine.GetSelectionState(out var start, out var length);
Assert.Equal(2, start);
Assert.Equal(1, length);
}),
_.Ctrl_f, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.None, " diff",
TokenClassification.InlinePrediction, " --cached"))
));
}
[SkippableFact]
public void Inline_DisablePrediction()
{
TestSetup(KeyMode.Cmd);
SetHistory("echo -bar", "eca -zoo");
using var disp = SetPrediction(PredictionSource.None, PredictionViewStyle.InlineView);
Test("ech", Keys(
'e', CheckThat(() => AssertScreenIs(1, TokenClassification.Command, 'e')),
'c', CheckThat(() => AssertScreenIs(1, TokenClassification.Command, "ec")),
'h', CheckThat(() => AssertScreenIs(1, TokenClassification.Command, "ech")),
_.Enter, CheckThat(() => AssertScreenIs(1, TokenClassification.Command, "ech"))
));
}
[SkippableFact]
public void Inline_SetPredictionColor()
{
Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode.");
TestSetup(KeyMode.Cmd);
var predictionColor = MakeCombinedColor(ConsoleColor.DarkYellow, ConsoleColor.Yellow);
var predictionColorToCheck = Tuple.Create(ConsoleColor.DarkYellow, ConsoleColor.Yellow);
using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView);
PSConsoleReadLine.SetOptions(new SetPSReadLineOption { Colors = new Hashtable() { { "InlinePrediction", predictionColor } } });
SetHistory("echo -bar", "eca -zoo");
Test("ech", Keys(
'e', CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, 'e',
predictionColorToCheck, "ca -zoo")),
'c', CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ec",
predictionColorToCheck, "a -zoo")),
'h', CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ech",
predictionColorToCheck, "o -bar")),
// Once accepted, the suggestion text should be blanked out.
_.Enter, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ech"))
));
}
[SkippableFact]
public void Inline_HistoryEditsCanUndoProperly()
{
Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode.");
TestSetup(KeyMode.Cmd,
new KeyHandler("Ctrl+f", PSConsoleReadLine.ForwardWord));
SetHistory("git checkout -b branch origin/bbbb");
using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView);
// Accept partial suggestion.
Test("git checkout ", Keys(
"git ch", CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.None, " ch",
TokenClassification.InlinePrediction, "eckout -b branch origin/bbbb")),
_.Ctrl_f, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.None, " checkout ",
TokenClassification.InlinePrediction, "-b branch origin/bbbb")),
_.Enter, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.None, " checkout "))
));
// Get the last command line from history, and revert the line.
// 'RevertLine' will undo all edits of the history command.
Test("", Keys(
_.UpArrow, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.None, " checkout ")),
_.Escape));
}
[SkippableFact]
public void Inline_AcceptSuggestionInVIMode()
{
Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode.");
TestSetup(KeyMode.Vi);
using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView);
SetHistory("echo -bar", "eca -zoo");
Test("echo", Keys(
'e', CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, 'e',
TokenClassification.InlinePrediction, "ca -zoo")),
'c', CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ec",
TokenClassification.InlinePrediction, "a -zoo")),
'h', CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ech",
TokenClassification.InlinePrediction, "o -bar")),
CheckThat(() => AssertCursorLeftIs(3)),
_.RightArrow, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "echo",
TokenClassification.None, ' ',
TokenClassification.Parameter, "-bar")),
CheckThat(() => AssertCursorLeftIs(9)),
_.Ctrl_z, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ech",
TokenClassification.InlinePrediction, "o -bar")),
CheckThat(() => AssertCursorLeftIs(3)),
// Suggestion should be cleared when switching to the command mode.
_.Escape, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ech")),
CheckThat(() => AssertCursorLeftIs(2)),
'i', _.RightArrow, 'o',
CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "echo",
TokenClassification.InlinePrediction, " -bar")),
CheckThat(() => AssertCursorLeftIs(4)),
_.RightArrow, _.Escape,
CheckThat(() => AssertCursorLeftIs(8)),
_.Ctrl_z, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "echo")),
CheckThat(() => AssertCursorLeftIs(3))
));
}
[SkippableFact]
public void ViDefect2408()
{
Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode.");
TestSetup(KeyMode.Vi);
using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView);
SetHistory("echo -bar");
Test("ech", Keys(
"abcd",
CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "abcd")),
_.Escape, 'S',
CheckThat(() => AssertLineIs(string.Empty)),
CheckThat(() => AssertCursorLeftIs(0)),
"ech",
CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ech",
TokenClassification.InlinePrediction, "o -bar")),
_.RightArrow,
CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "echo",
TokenClassification.None, ' ',
TokenClassification.Parameter, "-bar")),
CheckThat(() => AssertCursorLeftIs(9)),
_.Ctrl_z,
CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "ech",
TokenClassification.InlinePrediction, "o -bar"))));
}
private const uint MiniSessionId = 56;
private static readonly Guid predictorId_0 = Guid.Parse("c69fa9fc-6157-4877-8cf4-a9c83a0128af");
private static readonly Guid predictorId_1 = Guid.Parse("b45b5fbe-90fa-486c-9c87-e7940fdd6273");
private static readonly Guid predictorId_2 = Guid.Parse("74a86463-033b-44a3-b386-41ee191c94be");
private static readonly Guid predictorId_3 = Guid.Parse("19e98622-99e0-41f7-9ee0-a8bed92cde51");
/// <summary>
/// Mocked implementation of 'PredictInput'.
/// </summary>
internal static List<PredictionResult> MockedPredictInput(Ast ast, Token[] tokens)
{
var ctor = typeof(PredictionResult).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance, null,
new[] { typeof(Guid), typeof(string), typeof(uint), typeof(List<PredictiveSuggestion>) }, null);
var input = ast.Extent.Text;
if (input is "netsh")
{
return null;
}
else if (input is "tooltip")
{
var suggestions_0 = new List<PredictiveSuggestion>
{
new PredictiveSuggestion("tooltip NO1", "Hello\nBinary\nWorld\nPowerShell is a task automation and configuration management program from Microsoft"),
new PredictiveSuggestion("tooltip NO2", "Hello\nWorld"),
};
return new List<PredictionResult>
{
(PredictionResult)ctor.Invoke(
new object[] { predictorId_0, "Tooltip", MiniSessionId, suggestions_0 }),
};
}
var suggestions_1 = new List<PredictiveSuggestion>
{
new PredictiveSuggestion($"SOME TEXT BEFORE {input}"),
new PredictiveSuggestion($"{input} SOME TEXT AFTER"),
};
var suggestions_2 = new List<PredictiveSuggestion>
{
new PredictiveSuggestion($"SOME NEW TEXT"),
};
var result = new List<PredictionResult>
{
(PredictionResult)ctor.Invoke(
new object[] { predictorId_1, "TestPredictor", MiniSessionId, suggestions_1 }),
(PredictionResult)ctor.Invoke(
new object[] { predictorId_2, "LongNamePredictor", MiniSessionId, suggestions_2 }),
};
// Return an extra source if it's for testing the metadata line.
if (input == "metadata-line")
{
result.Add((PredictionResult)ctor.Invoke(
new object[] { predictorId_3, "Metadata", MiniSessionId, suggestions_2 }));
}
return result;
}
[SkippableFact]
public void Inline_PluginSource_Acceptance()
{
Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode.");
// Using the 'Plugin' source will make PSReadLine get prediction from the plugin only.
TestSetup(KeyMode.Cmd,
new KeyHandler("Ctrl+f", PSConsoleReadLine.ForwardWord));
using var disp = SetPrediction(PredictionSource.Plugin, PredictionViewStyle.InlineView);
_mockedMethods.ClearPredictionFields();
// When plugin returns any results, it will be used for inline view.
SetHistory("git diff");
Test("git SOME TEXT AFTER", Keys(
"git", CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.InlinePrediction, " SOME TEXT AFTER")),
// `OnSuggestionDisplayed` should be fired for only one predictor because we are in 'inline' view.
CheckThat(() => AssertDisplayedSuggestions(count: 1, predictorId_1, MiniSessionId, -1)),
CheckThat(() => _mockedMethods.ClearPredictionFields()),
// 'ctrl+f' will trigger 'OnSuggestionAccepted'.
_.Ctrl_f, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.None, " SOME ",
TokenClassification.InlinePrediction, "TEXT AFTER")),
CheckThat(() => Assert.Empty(_mockedMethods.displayedSuggestions)),
CheckThat(() => Assert.Equal(predictorId_1, _mockedMethods.acceptedPredictorId)),
CheckThat(() => Assert.Equal("git SOME TEXT AFTER", _mockedMethods.acceptedSuggestion)),
CheckThat(() => Assert.Null(_mockedMethods.commandHistory)),
CheckThat(() => _mockedMethods.ClearPredictionFields()),
// Subsequent 'ctrl+f' or 'rightarrow' on the same suggestion won't trigger 'OnSuggestionAccepted' again.
_.Ctrl_f, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.None, " SOME TEXT ",
TokenClassification.InlinePrediction, "AFTER")),
CheckThat(() => Assert.Empty(_mockedMethods.displayedSuggestions)),
CheckThat(() => Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId)),
CheckThat(() => Assert.Null(_mockedMethods.acceptedSuggestion)),
CheckThat(() => Assert.Null(_mockedMethods.commandHistory)),
_.RightArrow, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.None, " SOME TEXT AFTER")),
CheckThat(() => Assert.Empty(_mockedMethods.displayedSuggestions)),
CheckThat(() => Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId)),
CheckThat(() => Assert.Null(_mockedMethods.acceptedSuggestion)),
CheckThat(() => Assert.Null(_mockedMethods.commandHistory))
));
// 'Enter' will trigger 'OnCommandLineAccepted'.
Assert.Empty(_mockedMethods.displayedSuggestions);
Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId);
Assert.Null(_mockedMethods.acceptedSuggestion);
Assert.NotNull(_mockedMethods.commandHistory);
Assert.Equal(2, _mockedMethods.commandHistory.Count);
Assert.Equal("git diff", _mockedMethods.commandHistory[0]);
Assert.Equal("git SOME TEXT AFTER", _mockedMethods.commandHistory[1]);
// When plugin doesn't return any results, no suggestion will be shown for inline view because history is not used.
// The mocked implementation of 'PredictInput' treats 'netsh' as the hint to return nothing.
_mockedMethods.ClearPredictionFields();
SetHistory("netsh show me");
Test("netsh", Keys(
"netsh", CheckThat(() => AssertScreenIs(1, TokenClassification.Command, "netsh"))
));
// 'Enter' will trigger 'OnCommandLineAccepted', because plugin is in use.
// Also, we still have `OnSuggestionDisplayed` fired, from the typing of each character of `nets`.
AssertDisplayedSuggestions(count: 1, predictorId_1, MiniSessionId, -1);
Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId);
Assert.Null(_mockedMethods.acceptedSuggestion);
Assert.NotNull(_mockedMethods.commandHistory);
Assert.Equal(2, _mockedMethods.commandHistory.Count);
Assert.Equal("netsh show me", _mockedMethods.commandHistory[0]);
Assert.Equal("netsh", _mockedMethods.commandHistory[1]);
}
[SkippableFact]
public void Inline_HistoryAndPluginSource_Acceptance()
{
Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode.");
// Using the 'HistoryAndPlugin' source will make PSReadLine get prediction from the plugin and history,
// and plugin takes precedence.
TestSetup(KeyMode.Cmd,
new KeyHandler("Ctrl+f", PSConsoleReadLine.ForwardWord));
using var disp = SetPrediction(PredictionSource.HistoryAndPlugin, PredictionViewStyle.InlineView);
_mockedMethods.ClearPredictionFields();
// When plugin returns any results, it will be used for inline view.
SetHistory("git diff");
Test("git SOME TEXT AFTER", Keys(
"git", CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.InlinePrediction, " SOME TEXT AFTER")),
// `OnSuggestionDisplayed` should be fired for only one predictor because we are in 'inline' view.
CheckThat(() => AssertDisplayedSuggestions(count: 1, predictorId_1, MiniSessionId, -1)),
CheckThat(() => _mockedMethods.ClearPredictionFields()),
_.Ctrl_f, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.None, " SOME ",
TokenClassification.InlinePrediction, "TEXT AFTER")),
CheckThat(() => Assert.Empty(_mockedMethods.displayedSuggestions)),
CheckThat(() => Assert.Equal(predictorId_1, _mockedMethods.acceptedPredictorId)),
CheckThat(() => Assert.Equal("git SOME TEXT AFTER", _mockedMethods.acceptedSuggestion)),
CheckThat(() => Assert.Null(_mockedMethods.commandHistory)),
CheckThat(() => _mockedMethods.ClearPredictionFields()),
_.Ctrl_f, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.None, " SOME TEXT ",
TokenClassification.InlinePrediction, "AFTER")),
CheckThat(() => Assert.Empty(_mockedMethods.displayedSuggestions)),
CheckThat(() => Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId)),
CheckThat(() => Assert.Null(_mockedMethods.acceptedSuggestion)),
CheckThat(() => Assert.Null(_mockedMethods.commandHistory)),
_.RightArrow, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "git",
TokenClassification.None, " SOME TEXT AFTER")),
CheckThat(() => Assert.Empty(_mockedMethods.displayedSuggestions)),
CheckThat(() => Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId)),
CheckThat(() => Assert.Null(_mockedMethods.acceptedSuggestion)),
CheckThat(() => Assert.Null(_mockedMethods.commandHistory))
));
Assert.Empty(_mockedMethods.displayedSuggestions);
Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId);
Assert.Null(_mockedMethods.acceptedSuggestion);
Assert.NotNull(_mockedMethods.commandHistory);
Assert.Equal(2, _mockedMethods.commandHistory.Count);
Assert.Equal("git diff", _mockedMethods.commandHistory[0]);
Assert.Equal("git SOME TEXT AFTER", _mockedMethods.commandHistory[1]);
// When plugin doesn't return any results, history will be used for inline view.
// The mocked implementation of 'PredictInput' treats 'netsh' as the hint to return nothing.
_mockedMethods.ClearPredictionFields();
SetHistory("netsh show me");
Test("netsh show me", Keys(
"netsh", CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "netsh",
TokenClassification.InlinePrediction, " show me")),
// Yeah, we still have `OnSuggestionDisplayed` fired, from the typing of each character of `nets`.
CheckThat(() => AssertDisplayedSuggestions(count: 1, predictorId_1, MiniSessionId, -1)),
CheckThat(() => _mockedMethods.ClearPredictionFields()),
// 'ctrl+f' won't trigger 'OnSuggestionAccepted' as the suggestion is from history.
_.Ctrl_f, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "netsh",
TokenClassification.None, " show ",
TokenClassification.InlinePrediction, "me")),
CheckThat(() => Assert.Empty(_mockedMethods.displayedSuggestions)),
CheckThat(() => Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId)),
CheckThat(() => Assert.Null(_mockedMethods.acceptedSuggestion)),
CheckThat(() => Assert.Null(_mockedMethods.commandHistory)),
// 'rightarrow' won't trigger 'OnSuggestionAccepted' as the suggestion is from history.
_.RightArrow, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "netsh",
TokenClassification.None, " show me")),
CheckThat(() => Assert.Empty(_mockedMethods.displayedSuggestions)),
CheckThat(() => Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId)),
CheckThat(() => Assert.Null(_mockedMethods.acceptedSuggestion)),
CheckThat(() => Assert.Null(_mockedMethods.commandHistory))
));
Assert.Empty(_mockedMethods.displayedSuggestions);
Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId);
Assert.Null(_mockedMethods.acceptedSuggestion);
Assert.NotNull(_mockedMethods.commandHistory);
Assert.Equal(1, _mockedMethods.commandHistory.Count);
Assert.Equal("netsh show me", _mockedMethods.commandHistory[0]);
_mockedMethods.ClearPredictionFields();
SetHistory("netsh show me");
Test("netsh SOME TEXT AFTER", Keys(
"netsh", CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "netsh",
TokenClassification.InlinePrediction, " show me")),
// Yeah, we still have `OnSuggestionDisplayed` fired, from the typing of each character of `nets`.
CheckThat(() => AssertDisplayedSuggestions(count: 1, predictorId_1, MiniSessionId, countOrIndex: -1)),
CheckThat(() => _mockedMethods.ClearPredictionFields()),
// Now mimic pressing a space key. This will trigger the refreshing of suggestions even though the
// current history suggestion still applies to the new input, because plugin is in use and we favor
// plugin over history results.
' ', CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "netsh",
TokenClassification.None, " ",
TokenClassification.InlinePrediction, " SOME TEXT AFTER")),
CheckThat(() => AssertDisplayedSuggestions(count: 1, predictorId_1, MiniSessionId, countOrIndex: -1)),
CheckThat(() => Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId)),
CheckThat(() => Assert.Null(_mockedMethods.acceptedSuggestion)),
CheckThat(() => Assert.Null(_mockedMethods.commandHistory)),
CheckThat(() => _mockedMethods.ClearPredictionFields()),
// 'RightArrow' will trigger 'OnSuggestionAccepted' as the suggestion is now from plugin.
_.RightArrow, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "netsh",
TokenClassification.None, " SOME TEXT AFTER")),
CheckThat(() => Assert.Empty(_mockedMethods.displayedSuggestions)),
CheckThat(() => Assert.Equal(predictorId_1, _mockedMethods.acceptedPredictorId)),
CheckThat(() => Assert.Equal("netsh SOME TEXT AFTER", _mockedMethods.acceptedSuggestion)),
CheckThat(() => Assert.Null(_mockedMethods.commandHistory))
));
Assert.Empty(_mockedMethods.displayedSuggestions);
Assert.Equal(predictorId_1, _mockedMethods.acceptedPredictorId);
Assert.Equal("netsh SOME TEXT AFTER", _mockedMethods.acceptedSuggestion);
Assert.NotNull(_mockedMethods.commandHistory);
Assert.Equal(2, _mockedMethods.commandHistory.Count);
Assert.Equal("netsh show me", _mockedMethods.commandHistory[0]);
Assert.Equal("netsh SOME TEXT AFTER", _mockedMethods.commandHistory[1]);
_mockedMethods.ClearPredictionFields();
}
[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);
}
[SkippableFact]
public void Inline_TruncateVeryLongSuggestion()
{
Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode.");
TestSetup(new TestConsole(keyboardLayout: _, width: 10, height: 2), KeyMode.Cmd);
using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView);
// Truncate long suggestion to make sure the user input is not scrolled up-off the console buffer.
SetHistory(new string('v', 25));
Test("vv", Keys(
'v', CheckThat(() => AssertScreenIs(2,
TokenClassification.Command, 'v',
TokenClassification.InlinePrediction, new string('v', 9),
TokenClassification.InlinePrediction, new string('v', 6) + "...")),
'v', CheckThat(() => AssertScreenIs(2,
TokenClassification.Command, "vv",
TokenClassification.InlinePrediction, new string('v', 8),
TokenClassification.InlinePrediction, new string('v', 6) + "...")),
// Once accepted, the suggestion text should be blanked out.
_.Enter, CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "vv"))
));
}
}
}