Skip to content

Commit 9e6c6b9

Browse files
authored
Add basic support for inline predictive suggestion (#1496)
1 parent 5204b85 commit 9e6c6b9

19 files changed

+699
-58
lines changed

PSReadLine/BasicEditing.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ public static void CancelLine(ConsoleKeyInfo? key = null, object arg = null)
8585
{
8686
_singleton.ClearStatusMessage(false);
8787
_singleton._current = _singleton._buffer.Length;
88+
89+
using var _ = _singleton.PredictionOff();
8890
_singleton.ForceRender();
8991

9092
_singleton._console.Write("\x1b[91m^C\x1b[0m");
@@ -214,6 +216,8 @@ public static void DeleteCharOrExit(ConsoleKeyInfo? key = null, object arg = nul
214216

215217
private bool AcceptLineImpl(bool validate)
216218
{
219+
using var _ = PredictionOff();
220+
217221
ParseInput();
218222
if (_parseErrors.Any(e => e.IncompleteInput))
219223
{
@@ -274,6 +278,12 @@ private bool AcceptLineImpl(bool validate)
274278

275279
// Let public API set cursor to end of line incase end of line is end of buffer
276280
SetCursorPosition(_current);
281+
if (_suggestionText != null)
282+
{
283+
ResetSuggestion();
284+
_console.BlankRestOfLine();
285+
}
286+
277287
_console.Write("\n");
278288
_inputAccepted = true;
279289
return true;

PSReadLine/Changes.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
### Version 2.1.0-beta1
2+
3+
Pre-release notes:
4+
5+
* Experimental support for fish-like suggestions in PSReadLine.
6+
17
### Version 2.0.1
28

39
Bug fixes:

PSReadLine/Cmdlets.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ public enum AddToHistoryOption
6363
MemoryAndFile
6464
}
6565

66+
public enum PredictionSource
67+
{
68+
None,
69+
History,
70+
}
71+
6672
public class PSConsoleReadLineOptions
6773
{
6874
public const ConsoleColor DefaultCommentColor = ConsoleColor.DarkGreen;
@@ -78,6 +84,10 @@ public class PSConsoleReadLineOptions
7884
public const ConsoleColor DefaultEmphasisColor = ConsoleColor.Cyan;
7985
public const ConsoleColor DefaultErrorColor = ConsoleColor.Red;
8086

87+
// Use dark black by default for the suggestion text.
88+
// Find the most suitable color using https://stackoverflow.com/a/33206814
89+
public const string DefaultPredictionColor = "\x1b[38;5;238m";
90+
8191
public static EditMode DefaultEditMode = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
8292
? EditMode.Windows
8393
: EditMode.Emacs;
@@ -128,6 +138,11 @@ public class PSConsoleReadLineOptions
128138

129139
public const HistorySaveStyle DefaultHistorySaveStyle = HistorySaveStyle.SaveIncrementally;
130140

141+
/// <summary>
142+
/// The predictive suggestion feature is disabled by default.
143+
/// </summary>
144+
public const PredictionSource DefaultPredictionSource = PredictionSource.None;
145+
131146
/// <summary>
132147
/// How long in milliseconds should we wait before concluding
133148
/// the input is not an escape sequence?
@@ -155,6 +170,7 @@ public PSConsoleReadLineOptions(string hostName)
155170
HistorySearchCaseSensitive = DefaultHistorySearchCaseSensitive;
156171
HistorySaveStyle = DefaultHistorySaveStyle;
157172
AnsiEscapeTimeout = DefaultAnsiEscapeTimeout;
173+
PredictionSource = DefaultPredictionSource;
158174

159175
var historyFileName = hostName + "_history.txt";
160176
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -309,6 +325,11 @@ public object ContinuationPromptColor
309325
public string HistorySavePath { get; set; }
310326
public HistorySaveStyle HistorySaveStyle { get; set; }
311327

328+
/// <summary>
329+
/// Sets the source to get predictive suggestions.
330+
/// </summary>
331+
public PredictionSource PredictionSource { get; set; }
332+
312333
/// <summary>
313334
/// How long in milliseconds should we wait before concluding
314335
/// the input is not an escape sequence?
@@ -407,6 +428,12 @@ public object SelectionColor
407428
set => _selectionColor = VTColorUtils.AsEscapeSequence(value);
408429
}
409430

431+
public object PredictionColor
432+
{
433+
get => _predictionColor;
434+
set => _predictionColor = VTColorUtils.AsEscapeSequence(value);
435+
}
436+
410437
internal string _defaultTokenColor;
411438
internal string _commentColor;
412439
internal string _keywordColor;
@@ -421,6 +448,7 @@ public object SelectionColor
421448
internal string _emphasisColor;
422449
internal string _errorColor;
423450
internal string _selectionColor;
451+
internal string _predictionColor;
424452

425453
internal void ResetColors()
426454
{
@@ -438,6 +466,7 @@ internal void ResetColors()
438466
MemberColor = DefaultNumberColor;
439467
EmphasisColor = DefaultEmphasisColor;
440468
ErrorColor = DefaultErrorColor;
469+
PredictionColor = DefaultPredictionColor;
441470

442471
var bg = Console.BackgroundColor;
443472
if (fg == VTColorUtils.UnknownColor || bg == VTColorUtils.UnknownColor)
@@ -473,7 +502,8 @@ internal void SetColor(string property, object value)
473502
{"Type", (o, v) => o.TypeColor = v},
474503
{"Number", (o, v) => o.NumberColor = v},
475504
{"Member", (o, v) => o.MemberColor = v},
476-
{"Selection", (o, v) => o.SelectionColor = v },
505+
{"Selection", (o, v) => o.SelectionColor = v},
506+
{"Prediction", (o, v) => o.PredictionColor = v},
477507
};
478508

479509
Interlocked.CompareExchange(ref ColorSetters, setters, null);
@@ -680,6 +710,14 @@ public ViModeStyle ViModeIndicator
680710
[Parameter]
681711
public ScriptBlock ViModeChangeHandler { get; set; }
682712

713+
[Parameter]
714+
public PredictionSource PredictionSource
715+
{
716+
get => _predictionSource.GetValueOrDefault();
717+
set => _predictionSource = value;
718+
}
719+
internal PredictionSource? _predictionSource;
720+
683721
[Parameter]
684722
public Hashtable Colors { get; set; }
685723

PSReadLine/Completion.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ private void CompleteImpl(bool menuSelect)
217217
ViInsertWithAppend();
218218
}
219219

220+
// Do not show suggestion text during tab completion.
221+
using var _ = PredictionOff();
222+
220223
var completions = GetCompletions();
221224
if (completions == null || completions.CompletionMatches.Count == 0)
222225
return;

PSReadLine/History.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,8 @@ private void UpdateFromHistory(HistoryMoveCursor moveCursor)
502502
}
503503
break;
504504
}
505+
506+
using var _ = PredictionOff();
505507
Render();
506508
}
507509

@@ -677,6 +679,30 @@ private void HistorySearch(int direction)
677679
}
678680
}
679681

682+
/// <summary>
683+
/// Currently we only select single-line history that is prefixed with the user input,
684+
/// but it can be improved to not strictly use the user input as a prefix, but a hint
685+
/// to extract a partial pipeline or statement from a single-line or multiple-line
686+
/// history entry.
687+
/// </summary>
688+
private string GetHistorySuggestion(string text)
689+
{
690+
for (int index = _history.Count - 1; index >= 0; index --)
691+
{
692+
var line = _history[index].CommandLine.TrimEnd();
693+
if (line.Length > text.Length)
694+
{
695+
bool isMultiLine = line.Contains('\n');
696+
if (!isMultiLine && line.StartsWith(text, Options.HistoryStringComparison))
697+
{
698+
return line;
699+
}
700+
}
701+
}
702+
703+
return null;
704+
}
705+
680706
/// <summary>
681707
/// Move to the first item in the history.
682708
/// </summary>
@@ -889,6 +915,7 @@ private void InteractiveHistorySearchLoop(int direction)
889915

890916
private void InteractiveHistorySearch(int direction)
891917
{
918+
using var _ = PredictionOff();
892919
SaveCurrentLine();
893920

894921
// Add a status line that will contain the search prompt and string

PSReadLine/KeyBindings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,8 @@ public static KeyHandlerGroup GetDisplayGrouping(string function)
549549
case nameof(ViExit):
550550
case nameof(ViInsertMode):
551551
case nameof(WhatIsKey):
552+
case nameof(AcceptSuggestion):
553+
case nameof(AcceptNextSuggestionWord):
552554
return KeyHandlerGroup.Miscellaneous;
553555

554556
case nameof(CharacterSearch):

PSReadLine/Movement.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,14 @@ public static void ForwardChar(ConsoleKeyInfo? key = null, object arg = null)
6161
{
6262
if (TryGetArgAsInt(arg, out var numericArg, 1))
6363
{
64-
SetCursorPosition(_singleton._current + numericArg);
64+
if (_singleton._current == _singleton._buffer.Length && numericArg > 0)
65+
{
66+
AcceptSuggestion(key, arg);
67+
}
68+
else
69+
{
70+
SetCursorPosition(_singleton._current + numericArg);
71+
}
6572
}
6673
}
6774

@@ -311,6 +318,12 @@ public static void ForwardWord(ConsoleKeyInfo? key = null, object arg = null)
311318
return;
312319
}
313320

321+
if (_singleton._current == _singleton._buffer.Length && numericArg > 0)
322+
{
323+
AcceptNextSuggestionWord(numericArg);
324+
return;
325+
}
326+
314327
if (numericArg < 0)
315328
{
316329
BackwardWord(key, -numericArg);

PSReadLine/Options.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ private void SetOptionsInternal(SetPSReadLineOption options)
135135
{
136136
Options.PromptText = options.PromptText;
137137
}
138+
if (options._predictionSource.HasValue)
139+
{
140+
if (_console is PlatformWindows.LegacyWin32Console && options.PredictionSource != PredictionSource.None)
141+
{
142+
throw new ArgumentException(PSReadLineResources.PredictiveSuggestionNotSupported);
143+
}
144+
Options.PredictionSource = options.PredictionSource;
145+
}
138146
if (options.Colors != null)
139147
{
140148
IDictionaryEnumerator e = options.Colors.GetEnumerator();

PSReadLine/PSReadLine.csproj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
<OutputType>Library</OutputType>
55
<RootNamespace>Microsoft.PowerShell.PSReadLine</RootNamespace>
66
<AssemblyName>Microsoft.PowerShell.PSReadLine2</AssemblyName>
7-
<AssemblyVersion>2.0.1.0</AssemblyVersion>
8-
<FileVersion>2.0.1</FileVersion>
9-
<InformationalVersion>2.0.1</InformationalVersion>
7+
<AssemblyVersion>2.1.0.0</AssemblyVersion>
8+
<FileVersion>2.1.0</FileVersion>
9+
<InformationalVersion>2.1.0-beta1</InformationalVersion>
1010
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
1111
<TargetFrameworks>net461;netcoreapp2.1</TargetFrameworks>
12+
<LangVersion>8.0</LangVersion>
1213
</PropertyGroup>
1314

1415
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">

PSReadLine/PSReadLine.format.ps1xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ $d = [Microsoft.PowerShell.KeyHandler]::GetGroupingDescription($_.Group)
158158
<ListItem>
159159
<PropertyName>AnsiEscapeTimeout</PropertyName>
160160
</ListItem>
161+
<ListItem>
162+
<PropertyName>PredictionSource</PropertyName>
163+
</ListItem>
161164
<ListItem>
162165
<Label>CommandColor</Label>
163166
<ScriptBlock>[Microsoft.PowerShell.VTColorUtils]::FormatColor($_.CommandColor)</ScriptBlock>
@@ -202,6 +205,10 @@ $d = [Microsoft.PowerShell.KeyHandler]::GetGroupingDescription($_.Group)
202205
<Label>ParameterColor</Label>
203206
<ScriptBlock>[Microsoft.PowerShell.VTColorUtils]::FormatColor($_.ParameterColor)</ScriptBlock>
204207
</ListItem>
208+
<ListItem>
209+
<Label>PredictionColor</Label>
210+
<ScriptBlock>[Microsoft.PowerShell.VTColorUtils]::FormatColor($_.PredictionColor)</ScriptBlock>
211+
</ListItem>
205212
<ListItem>
206213
<Label>SelectionColor</Label>
207214
<ScriptBlock>[Microsoft.PowerShell.VTColorUtils]::FormatColor($_.SelectionColor)</ScriptBlock>

0 commit comments

Comments
 (0)