Skip to content

Commit 2fb3c33

Browse files
authored
Force refreshing suggestion in the inline view when plugin is in use (#3644)
1 parent 3d20df7 commit 2fb3c33

File tree

3 files changed

+70
-9
lines changed

3 files changed

+70
-9
lines changed

PSReadLine/Prediction.Views.cs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,9 +1082,29 @@ internal override void GetSuggestion(string userInput)
10821082
{
10831083
_inputText = userInput;
10841084

1085-
if (_suggestionText == null || _suggestionText.Length <= userInput.Length ||
1086-
_lastInputText.Length > userInput.Length ||
1087-
!_suggestionText.StartsWith(userInput, _singleton._options.HistoryStringComparison))
1085+
string currentSugText = null;
1086+
bool needToRefresh = _suggestionText == null
1087+
|| _suggestionText.Length <= userInput.Length
1088+
|| _lastInputText.Length > userInput.Length
1089+
|| !_suggestionText.StartsWith(userInput, _singleton._options.HistoryStringComparison);
1090+
1091+
1092+
// The current suggestion was from history and it still applies to the new input. However, the plugin is in use,
1093+
// so we may need to force refreshing in case the plugin gives more relevant suggestion for the new input. This
1094+
// is because we favor plugin over history in the inline view.
1095+
if (!needToRefresh && _predictorId == Guid.Empty && UsePlugin)
1096+
{
1097+
// We generally want to force refreshing in this case, with only one exception -- the user accepted the next
1098+
// word from the current history suggestion. That means the user is interested in the current suggestion and
1099+
// thus we should keep on using.
1100+
needToRefresh = !_alreadyAccepted;
1101+
_alreadyAccepted = false;
1102+
1103+
// We can reuse the current suggestion text for history to avoid an unnecessary search.
1104+
currentSugText = _suggestionText;
1105+
}
1106+
1107+
if (needToRefresh)
10881108
{
10891109
_alreadyAccepted = false;
10901110
_suggestionText = null;
@@ -1097,7 +1117,7 @@ internal override void GetSuggestion(string userInput)
10971117

10981118
if (UseHistory)
10991119
{
1100-
_suggestionText = GetOneHistorySuggestion(userInput);
1120+
_suggestionText = currentSugText ?? GetOneHistorySuggestion(userInput);
11011121
_predictorId = Guid.Empty;
11021122
_predictorSession = null;
11031123
}
@@ -1215,15 +1235,14 @@ internal override void RenderSuggestion(List<StringBuilder> consoleBufferLines,
12151235

12161236
internal override void OnSuggestionAccepted()
12171237
{
1218-
if (!UsePlugin)
1238+
if (_alreadyAccepted)
12191239
{
12201240
return;
12211241
}
12221242

1223-
if (!_alreadyAccepted && _suggestionText != null && _predictorSession.HasValue)
1243+
_alreadyAccepted = true;
1244+
if (_suggestionText != null && _predictorSession.HasValue)
12241245
{
1225-
_alreadyAccepted = true;
1226-
12271246
// Send feedback only if the mini-session id is specified.
12281247
// When it's not specified, we consider the predictor doesn't accept feedback.
12291248
_singleton._mockableMethods.OnSuggestionAccepted(_predictorId, _predictorSession.Value, _suggestionText);

test/InlinePredictionTest.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,48 @@ public void Inline_HistoryAndPluginSource_Acceptance()
605605
Assert.NotNull(_mockedMethods.commandHistory);
606606
Assert.Equal(1, _mockedMethods.commandHistory.Count);
607607
Assert.Equal("netsh show me", _mockedMethods.commandHistory[0]);
608+
609+
_mockedMethods.ClearPredictionFields();
610+
SetHistory("netsh show me");
611+
Test("netsh SOME TEXT AFTER", Keys(
612+
"netsh", CheckThat(() => AssertScreenIs(1,
613+
TokenClassification.Command, "netsh",
614+
TokenClassification.InlinePrediction, " show me")),
615+
// Yeah, we still have `OnSuggestionDisplayed` fired, from the typing of each character of `nets`.
616+
CheckThat(() => AssertDisplayedSuggestions(count: 1, predictorId_1, MiniSessionId, countOrIndex: -1)),
617+
CheckThat(() => _mockedMethods.ClearPredictionFields()),
618+
619+
// Now mimic pressing a space key. This will trigger the refreshing of suggestions even though the
620+
// current history suggestion still applies to the new input, because plugin is in use and we favor
621+
// plugin over history results.
622+
' ', CheckThat(() => AssertScreenIs(1,
623+
TokenClassification.Command, "netsh",
624+
TokenClassification.None, " ",
625+
TokenClassification.InlinePrediction, " SOME TEXT AFTER")),
626+
CheckThat(() => AssertDisplayedSuggestions(count: 1, predictorId_1, MiniSessionId, countOrIndex: -1)),
627+
CheckThat(() => Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId)),
628+
CheckThat(() => Assert.Null(_mockedMethods.acceptedSuggestion)),
629+
CheckThat(() => Assert.Null(_mockedMethods.commandHistory)),
630+
631+
CheckThat(() => _mockedMethods.ClearPredictionFields()),
632+
// 'RightArrow' will trigger 'OnSuggestionAccepted' as the suggestion is now from plugin.
633+
_.RightArrow, CheckThat(() => AssertScreenIs(1,
634+
TokenClassification.Command, "netsh",
635+
TokenClassification.None, " SOME TEXT AFTER")),
636+
CheckThat(() => Assert.Empty(_mockedMethods.displayedSuggestions)),
637+
CheckThat(() => Assert.Equal(predictorId_1, _mockedMethods.acceptedPredictorId)),
638+
CheckThat(() => Assert.Equal("netsh SOME TEXT AFTER", _mockedMethods.acceptedSuggestion)),
639+
CheckThat(() => Assert.Null(_mockedMethods.commandHistory))
640+
));
641+
642+
Assert.Empty(_mockedMethods.displayedSuggestions);
643+
Assert.Equal(predictorId_1, _mockedMethods.acceptedPredictorId);
644+
Assert.Equal("netsh SOME TEXT AFTER", _mockedMethods.acceptedSuggestion);
645+
Assert.NotNull(_mockedMethods.commandHistory);
646+
Assert.Equal(2, _mockedMethods.commandHistory.Count);
647+
Assert.Equal("netsh show me", _mockedMethods.commandHistory[0]);
648+
Assert.Equal("netsh SOME TEXT AFTER", _mockedMethods.commandHistory[1]);
649+
_mockedMethods.ClearPredictionFields();
608650
}
609651

610652
[SkippableFact]

test/UnitTestReadLine.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ internal class MockedMethods : IPSConsoleReadLineMockableMethods
2727
internal Guid acceptedPredictorId;
2828
internal string acceptedSuggestion;
2929
internal string helpContentRendered;
30-
internal Dictionary<Guid, Tuple<uint, int>> displayedSuggestions = new Dictionary<Guid, Tuple<uint, int>>();
30+
internal Dictionary<Guid, Tuple<uint, int>> displayedSuggestions = new();
3131

3232
internal void ClearPredictionFields()
3333
{

0 commit comments

Comments
 (0)