diff --git a/PSReadLine/Cmdlets.cs b/PSReadLine/Cmdlets.cs
index ee323db..bc89cc1 100644
--- a/PSReadLine/Cmdlets.cs
+++ b/PSReadLine/Cmdlets.cs
@@ -101,6 +101,7 @@ namespace Microsoft.PowerShell
public const string DefaultInlinePredictionColor = "\x1b[97;2;3m";
public const string DefaultListPredictionColor = "\x1b[33m";
public const string DefaultListPredictionSelectedColor = "\x1b[48;5;238m";
+ public const string DefaultListPredictionTooltipColor = "\x1b[97;2;3m";
public static EditMode DefaultEditMode = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? EditMode.Windows
@@ -495,6 +496,12 @@ namespace Microsoft.PowerShell
set => _listPredictionSelectedColor = VTColorUtils.AsEscapeSequence(value);
}
+ public object ListPredictionTooltipColor
+ {
+ get => _listPredictionTooltipColor;
+ set => _listPredictionTooltipColor = VTColorUtils.AsEscapeSequence(value);
+ }
+
internal string _defaultTokenColor;
internal string _commentColor;
internal string _keywordColor;
@@ -512,6 +519,7 @@ namespace Microsoft.PowerShell
internal string _inlinePredictionColor;
internal string _listPredictionColor;
internal string _listPredictionSelectedColor;
+ internal string _listPredictionTooltipColor;
internal void ResetColors()
{
@@ -532,6 +540,7 @@ namespace Microsoft.PowerShell
InlinePredictionColor = DefaultInlinePredictionColor;
ListPredictionColor = DefaultListPredictionColor;
ListPredictionSelectedColor = DefaultListPredictionSelectedColor;
+ ListPredictionTooltipColor = DefaultListPredictionTooltipColor;
var bg = Console.BackgroundColor;
if (fg == VTColorUtils.UnknownColor || bg == VTColorUtils.UnknownColor)
@@ -571,6 +580,7 @@ namespace Microsoft.PowerShell
{"InlinePrediction", (o, v) => o.InlinePredictionColor = v},
{"ListPrediction", (o, v) => o.ListPredictionColor = v},
{"ListPredictionSelected", (o, v) => o.ListPredictionSelectedColor = v},
+ {"ListPredictionTooltip", (o, v) => o.ListPredictionTooltipColor = v},
};
Interlocked.CompareExchange(ref ColorSetters, setters, null);
diff --git a/PSReadLine/DynamicHelp.cs b/PSReadLine/DynamicHelp.cs
index 6334c59..bbd6bf7 100644
--- a/PSReadLine/DynamicHelp.cs
+++ b/PSReadLine/DynamicHelp.cs
@@ -19,6 +19,7 @@ namespace Microsoft.PowerShell
[ExcludeFromCodeCoverage]
void IPSConsoleReadLineMockableMethods.RenderFullHelp(string content, string regexPatternToScrollTo)
{
+ _pager ??= new Pager();
_pager.Write(content, regexPatternToScrollTo);
}
@@ -142,11 +143,6 @@ namespace Microsoft.PowerShell
private void DynamicHelpImpl(bool isFullHelp)
{
- if (isFullHelp)
- {
- _pager ??= new Pager();
- }
-
int cursor = _singleton._current;
string commandName = null;
string parameterName = null;
diff --git a/PSReadLine/KeyBindings.cs b/PSReadLine/KeyBindings.cs
index 69daaa0..8956422 100644
--- a/PSReadLine/KeyBindings.cs
+++ b/PSReadLine/KeyBindings.cs
@@ -235,6 +235,7 @@ namespace Microsoft.PowerShell
{ Keys.F2, MakeKeyHandler(SwitchPredictionView, "SwitchPredictionView") },
{ Keys.F3, MakeKeyHandler(CharacterSearch, "CharacterSearch") },
{ Keys.ShiftF3, MakeKeyHandler(CharacterSearchBackward, "CharacterSearchBackward") },
+ { Keys.F4, MakeKeyHandler(ShowFullPredictionTooltip, "ShowFullPredictionTooltip") },
{ Keys.F8, MakeKeyHandler(HistorySearchBackward, "HistorySearchBackward") },
{ Keys.ShiftF8, MakeKeyHandler(HistorySearchForward, "HistorySearchForward") },
// Added for xtermjs-based terminals that send different key combinations.
@@ -339,6 +340,7 @@ namespace Microsoft.PowerShell
{ Keys.AltH, MakeKeyHandler(ShowParameterHelp, "ShowParameterHelp") },
{ Keys.F1, MakeKeyHandler(ShowCommandHelp, "ShowCommandHelp") },
{ Keys.F2, MakeKeyHandler(SwitchPredictionView, "SwitchPredictionView") },
+ { Keys.F4, MakeKeyHandler(ShowFullPredictionTooltip, "ShowFullPredictionTooltip") },
{ Keys.AltU, MakeKeyHandler(UpcaseWord, "UpcaseWord") },
{ Keys.AltL, MakeKeyHandler(DowncaseWord, "DowncaseWord") },
{ Keys.AltC, MakeKeyHandler(CapitalizeWord, "CapitalizeWord") },
@@ -566,6 +568,7 @@ namespace Microsoft.PowerShell
case nameof(NextSuggestion):
case nameof(PreviousSuggestion):
case nameof(SwitchPredictionView):
+ case nameof(ShowFullPredictionTooltip):
return KeyHandlerGroup.Prediction;
case nameof(CaptureScreen):
diff --git a/PSReadLine/KeyBindings.vi.cs b/PSReadLine/KeyBindings.vi.cs
index e7498c1..9d051ec 100644
--- a/PSReadLine/KeyBindings.vi.cs
+++ b/PSReadLine/KeyBindings.vi.cs
@@ -91,6 +91,8 @@ namespace Microsoft.PowerShell
{ Keys.CtrlG, MakeKeyHandler(Abort, "Abort") },
{ Keys.AltH, MakeKeyHandler(ShowParameterHelp, "ShowParameterHelp") },
{ Keys.F1, MakeKeyHandler(ShowCommandHelp, "ShowCommandHelp") },
+ { Keys.F2, MakeKeyHandler(SwitchPredictionView, "SwitchPredictionView") },
+ { Keys.F4, MakeKeyHandler(ShowFullPredictionTooltip, "ShowFullPredictionTooltip") },
};
// Some bindings are not available on certain platforms
diff --git a/PSReadLine/PSReadLine.format.ps1xml b/PSReadLine/PSReadLine.format.ps1xml
index 3446753..5b20c58 100644
--- a/PSReadLine/PSReadLine.format.ps1xml
+++ b/PSReadLine/PSReadLine.format.ps1xml
@@ -204,6 +204,10 @@ $d = [Microsoft.PowerShell.KeyHandler]::GetGroupingDescription($_.Group)
[Microsoft.PowerShell.VTColorUtils]::FormatColor($_.ListPredictionSelectedColor)
+
+
+ [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.ListPredictionTooltipColor)
+
[Microsoft.PowerShell.VTColorUtils]::FormatColor($_.MemberColor)
diff --git a/PSReadLine/PSReadLineResources.Designer.cs b/PSReadLine/PSReadLineResources.Designer.cs
index 35dd5a3..7d34359 100644
--- a/PSReadLine/PSReadLineResources.Designer.cs
+++ b/PSReadLine/PSReadLineResources.Designer.cs
@@ -2192,6 +2192,17 @@ namespace Microsoft.PowerShell.PSReadLine {
}
}
+ ///
+ /// Looks up a localized string similar to Show the full tooltip of the selected list-view item in the terminal's alternate screen buffer.
+ ///
+ internal static string ShowFullPredictionTooltipDescription
+ {
+ get
+ {
+ return ResourceManager.GetString("ShowFullPredictionTooltipDescription", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Make visual selection of the command arguments.
///
diff --git a/PSReadLine/PSReadLineResources.resx b/PSReadLine/PSReadLineResources.resx
index 28d6e5d..618ef22 100644
--- a/PSReadLine/PSReadLineResources.resx
+++ b/PSReadLine/PSReadLineResources.resx
@@ -837,6 +837,9 @@ Or not saving history with:
Switch between the inline and list prediction views.
+
+ Show the full tooltip of the selected list-view item in the terminal's alternate screen buffer.
+
The prediction 'ListView' is temporarily disabled because the current window size of the console is too small. To use the 'ListView', please make sure the 'WindowWidth' is not less than '{0}' and the 'WindowHeight' is not less than '{1}'.
diff --git a/PSReadLine/Prediction.Entry.cs b/PSReadLine/Prediction.Entry.cs
index f576f62..643afa5 100644
--- a/PSReadLine/Prediction.Entry.cs
+++ b/PSReadLine/Prediction.Entry.cs
@@ -47,6 +47,7 @@ namespace Microsoft.PowerShell
internal readonly Guid PredictorId;
internal readonly uint? PredictorSession;
internal readonly string Source;
+ internal readonly string ToolTip;
internal readonly string SuggestionText;
internal readonly int InputMatchIndex;
@@ -54,16 +55,17 @@ namespace Microsoft.PowerShell
private string _listItemTextSelected;
internal SuggestionEntry(string suggestion, int matchIndex)
- : this(source: HistorySource, predictorId: Guid.Empty, predictorSession: null, suggestion, matchIndex)
+ : this(source: HistorySource, predictorId: Guid.Empty, predictorSession: null, suggestion, tooltip: null, matchIndex)
{
}
- internal SuggestionEntry(string source, Guid predictorId, uint? predictorSession, string suggestion, int matchIndex)
+ internal SuggestionEntry(string source, Guid predictorId, uint? predictorSession, string suggestion, string tooltip, int matchIndex)
{
Source = source;
PredictorId = predictorId;
PredictorSession = predictorSession;
SuggestionText = suggestion;
+ ToolTip = tooltip;
InputMatchIndex = matchIndex;
_listItemTextRegular = _listItemTextSelected = null;
diff --git a/PSReadLine/Prediction.Views.cs b/PSReadLine/Prediction.Views.cs
index 7ab89e6..e4ad730 100644
--- a/PSReadLine/Prediction.Views.cs
+++ b/PSReadLine/Prediction.Views.cs
@@ -215,6 +215,7 @@ namespace Microsoft.PowerShell
// List view constants.
internal const int ListViewMaxHeight = 10;
internal const int ListViewMaxWidth = 100;
+ internal const int TooltipMaxHeight = 4;
internal const int SourceMaxWidth = 15;
// Minimal window size.
@@ -236,6 +237,11 @@ namespace Microsoft.PowerShell
private int _maxViewHeight;
// The actual height of the list view that is currently rendered.
private int _listViewHeight;
+ // The max number of physical lines for rendering tooltip.
+ private int _maxTooltipHeight;
+ // The actual number of lines used for rendering the tooltip.
+ private int _tooltipHeight;
+
// The actual width of the list view that is currently rendered.
private int _listViewWidth;
// An index pointing to the item that is shown in the first slot of the list view.
@@ -294,6 +300,23 @@ namespace Microsoft.PowerShell
}
}
+ ///
+ /// The tooltip of the currently selected item.
+ ///
+ internal string SelectedItemTooltip
+ {
+ get
+ {
+ if (_listItems == null || _selectedIndex == -1)
+ return null;
+
+ if (_selectedIndex >= 0)
+ return _listItems[_selectedIndex].ToolTip;
+
+ throw new InvalidOperationException("Unexpected '_selectedIndex' value: " + _selectedIndex);
+ }
+ }
+
internal PredictionListView(PSConsoleReadLine singleton)
: base(singleton)
{
@@ -306,19 +329,19 @@ namespace Microsoft.PowerShell
///
/// Calculate the max width and height of the list view based on the current terminal size.
///
- private (int maxWidth, int maxHeight, bool checkOnHeight) RefreshMaxViewSize()
+ private (int, int, int, bool) RefreshMaxViewSize()
{
var console = _singleton._console;
- int maxWidth = Math.Min(console.BufferWidth, ListViewMaxWidth);
+ int maxListWidth = Math.Min(console.BufferWidth, ListViewMaxWidth);
- (int maxHeight, bool moreCheck) = console.BufferHeight switch
+ (int maxListHeight, int maxTooltipHeigth, bool moreCheck) = console.BufferHeight switch
{
- > ListViewMaxHeight * 2 => (ListViewMaxHeight, false),
- > ListViewMaxHeight => (ListViewMaxHeight / 2, false),
- _ => (ListViewMaxHeight / 3, true)
+ > ListViewMaxHeight * 2 => (ListViewMaxHeight, TooltipMaxHeight, false),
+ > ListViewMaxHeight => (ListViewMaxHeight / 2, TooltipMaxHeight / 2, false),
+ _ => (ListViewMaxHeight / 3, TooltipMaxHeight / 3, true)
};
- return (maxWidth, maxHeight, moreCheck);
+ return (maxListWidth, maxListHeight, maxTooltipHeigth, moreCheck);
}
///
@@ -326,8 +349,9 @@ namespace Microsoft.PowerShell
///
private bool HeightIsTooSmall()
{
+ int tooltipLineCount = GetToolTipLineCountForHeightCheck();
int physicalLineCountForBuffer = _singleton.EndOfBufferPosition().Y - _singleton._initialY + 1;
- return _singleton._console.BufferHeight < physicalLineCountForBuffer + _maxViewHeight + 1 /* one metadata line */;
+ return _singleton._console.BufferHeight < physicalLineCountForBuffer + _maxViewHeight + tooltipLineCount + 1 /* one metadata line */;
}
///
@@ -364,7 +388,7 @@ namespace Microsoft.PowerShell
// Reset the list item selection.
_selectedIndex = -1;
// Refresh the list view width and height in case the terminal was resized.
- (_listViewWidth, _maxViewHeight, _checkOnHeight) = RefreshMaxViewSize();
+ (_listViewWidth, _maxViewHeight, _maxTooltipHeight, _checkOnHeight) = RefreshMaxViewSize();
if (inputUnchanged)
{
@@ -533,7 +557,7 @@ namespace Microsoft.PowerShell
}
int matchIndex = sugText.IndexOf(_inputText, comparison);
- _listItems.Add(new SuggestionEntry(item.Name, item.Id, item.Session, sugText, matchIndex));
+ _listItems.Add(new SuggestionEntry(item.Name, item.Id, item.Session, sugText, suggestion.ToolTip, matchIndex));
if (--num == 0)
{
@@ -586,6 +610,7 @@ namespace Microsoft.PowerShell
_listViewTop = 0;
_listViewEnd = Math.Min(_listItems.Count, _maxViewHeight);
_listViewHeight = _listViewEnd - _listViewTop;
+ _tooltipHeight = 0;
}
else
{
@@ -658,18 +683,25 @@ namespace Microsoft.PowerShell
}
_listViewHeight = _listViewEnd - _listViewTop;
+ _tooltipHeight = 0;
}
for (int i = _listViewTop; i < _listViewEnd; i++)
{
bool itemSelected = i == _selectedIndex;
string selectionColor = itemSelected ? _singleton._options._listPredictionSelectedColor : null;
+ SuggestionEntry entry = _listItems[i];
NextBufferLine(consoleBufferLines, ref currentLogicalLine)
- .Append(_listItems[i].GetListItemText(
+ .Append(entry.GetListItemText(
_listViewWidth,
_inputText,
selectionColor));
+
+ if (_singleton._options.ShowToolTips && itemSelected && !string.IsNullOrWhiteSpace(entry.ToolTip))
+ {
+ _tooltipHeight = RenderTooltip(entry.ToolTip, consoleBufferLines, ref currentLogicalLine);
+ }
}
}
@@ -703,6 +735,26 @@ namespace Microsoft.PowerShell
return pesudoListHeight;
}
+ ///
+ /// Calculate the number of tooltip lines rendered in the list view.
+ ///
+ private int GetToolTipLineCountForHeightCheck()
+ {
+ int tooltipLineCount = 0;
+ if (_singleton._options.ShowToolTips && _selectedIndex >= 0)
+ {
+ // When '_selectedIndex >= 0', this is an update to the list view triggered by navigation
+ // within the list, and thus '_listItems' is guaranteed to be not null.
+ string tooltip = _listItems[_selectedIndex].ToolTip;
+ if (!string.IsNullOrWhiteSpace(tooltip))
+ {
+ tooltipLineCount = _maxTooltipHeight;
+ }
+ }
+
+ return tooltipLineCount;
+ }
+
///
/// Generate the rendering text for the metadata line.
///
@@ -865,6 +917,146 @@ namespace Microsoft.PowerShell
buffer.Insert(charPosition, " ", padding);
}
+ ///
+ /// Generate the rendering text for the tooltip.
+ ///
+ private int RenderTooltip(string tooltip, List consoleBufferLines, ref int currentLogicalLine)
+ {
+ const int LengthOfLeadingPart = 6;
+ const string IndicatorSymbol = ">>";
+ const string MsgForViewAll = "( to view all)";
+ const string MoreTextIndicator1 = " \u2026 ";
+ const string MoreTextIndicator2 = "\u2026 ";
+ const string BoldDimItalicStyle = "\x1b[1;2;3m";
+
+ bool first = true;
+ int newlineIndex = -1;
+ int cellCount = 0;
+ int start, end;
+
+ StringBuilder buff = null;
+ bool moreToCome = false;
+ int windowWidth = _singleton._console.BufferWidth;
+ int linesLeft = _maxTooltipHeight;
+
+ string tooltipStyle = _singleton._options._listPredictionTooltipColor;
+ if (tooltipStyle != PSConsoleReadLineOptions.DefaultInlinePredictionColor)
+ {
+ tooltipStyle += BoldDimItalicStyle;
+ }
+
+ do
+ {
+ int startIndex = newlineIndex + 1;
+
+ // This may happen when the tooltip ends with a newline character.
+ if (startIndex == tooltip.Length)
+ {
+ break;
+ }
+
+ // Get the range of the current non-whitespace substring line.
+ newlineIndex = tooltip.IndexOf('\n', startIndex);
+ (start, end) = TrimSubstringInPlace(tooltip, startIndex, newlineIndex is -1 ? tooltip.Length - 1 : newlineIndex);
+
+ // This may happen when the current substring contains whitespace characters only.
+ if (start is -1)
+ {
+ if (newlineIndex is -1)
+ {
+ // If we have reached the end, then we are done.
+ break;
+ }
+
+ // Otherwise, we need to continue processing the next substring line.
+ continue;
+ }
+
+ if (linesLeft is 0)
+ {
+ // More non-empty substring lines, but no more space for rendering.
+ moreToCome = true;
+ break;
+ }
+
+ if (buff is not null)
+ {
+ // Append reset for the previous substring line.
+ buff.Append(VTColorUtils.AnsiReset);
+ }
+
+ // Create a new buffer line for the current substring line.
+ linesLeft--;
+ buff = NextBufferLine(consoleBufferLines, ref currentLogicalLine);
+
+ if (first)
+ {
+ first = false;
+ buff.Append(tooltipStyle)
+ .Append(' ', 3)
+ .Append(IndicatorSymbol)
+ .Append(' ');
+ }
+ else
+ {
+ buff.Append(tooltipStyle)
+ .Append(' ', LengthOfLeadingPart);
+ }
+
+ cellCount = LengthOfLeadingPart;
+ for (; start <= end; start++)
+ {
+ char ch = tooltip[start];
+ int charInCells = LengthInBufferCells(ch);
+
+ cellCount += charInCells;
+ if (cellCount > windowWidth)
+ {
+ linesLeft--;
+ if (linesLeft is -1)
+ {
+ // More text from the current substring line, but no more space for rendering.
+ moreToCome = true;
+ cellCount -= charInCells;
+ break;
+ }
+
+ cellCount = charInCells;
+ }
+
+ buff.Append(ch);
+ }
+ }
+ while (linesLeft >= 0 && newlineIndex >= 0);
+
+ if (moreToCome)
+ {
+ // Append the "( to view all)" at the end of the last line
+ string highlightStyle = _singleton._options._listPredictionColor + BoldDimItalicStyle;
+ int remainingCells = windowWidth - cellCount;
+
+ if (remainingCells >= MsgForViewAll.Length + MoreTextIndicator1.Length)
+ {
+ buff.Append(MoreTextIndicator1);
+ }
+ else
+ {
+ int length = MsgForViewAll.Length + MoreTextIndicator2.Length - remainingCells;
+ int buffIndex = buff.Length - length;
+
+ buff.Remove(buffIndex, length);
+ buff.Append(MoreTextIndicator2);
+ }
+
+ buff.Append(VTColorUtils.AnsiReset)
+ .Append(highlightStyle)
+ .Append(MsgForViewAll);
+ }
+
+ buff.Append(VTColorUtils.AnsiReset);
+ return _maxTooltipHeight - linesLeft > 0 ? linesLeft : 0;
+ }
+
///
/// Trigger the feedback about a suggestion was accepted.
///
@@ -899,7 +1091,7 @@ namespace Microsoft.PowerShell
int listHeight = _warningPrinted
? GetPesudoListHeightForWarningRendering()
- : _listViewHeight;
+ : _listViewHeight + _tooltipHeight;
int top = cursorAtEol
? _singleton._console.CursorTop
@@ -919,7 +1111,9 @@ namespace Microsoft.PowerShell
_sources = null;
_listItems = null;
- _maxViewHeight = _listViewTop = _listViewEnd = _listViewWidth = _listViewHeight = _selectedIndex = -1;
+ _maxViewHeight = _maxTooltipHeight = -1;
+ _listViewWidth = _listViewHeight = _tooltipHeight = -1;
+ _listViewTop = _listViewEnd = _selectedIndex = -1;
_warnAboutSize = _checkOnHeight = _updatePending = _renderFromSelected = false;
}
diff --git a/PSReadLine/Prediction.cs b/PSReadLine/Prediction.cs
index fc790f9..7ece2ed 100644
--- a/PSReadLine/Prediction.cs
+++ b/PSReadLine/Prediction.cs
@@ -180,6 +180,21 @@ namespace Microsoft.PowerShell
UpdateListSelection(numericArg);
}
+ ///
+ /// Show the tooltip of the currently selected list item in the full view.
+ ///
+ public static void ShowFullPredictionTooltip(ConsoleKeyInfo? key = null, object arg = null)
+ {
+ if (_singleton._prediction.ActiveView is PredictionListView listView && listView.HasActiveSuggestion)
+ {
+ string tooltip = listView.SelectedItemTooltip;
+ if (!string.IsNullOrWhiteSpace(tooltip))
+ {
+ _singleton._mockableMethods.RenderFullHelp(tooltip, regexPatternToScrollTo: null);
+ }
+ }
+ }
+
///
/// Implementation for updating the selected item in list view.
///
diff --git a/PSReadLine/Render.Helper.cs b/PSReadLine/Render.Helper.cs
index 8fe8de0..8fc56be 100644
--- a/PSReadLine/Render.Helper.cs
+++ b/PSReadLine/Render.Helper.cs
@@ -190,5 +190,30 @@ namespace Microsoft.PowerShell
return charLength;
}
+
+ private static (int newStart, int newEnd) TrimSubstringInPlace(string text, int start, int end)
+ {
+ int newStart = start;
+ int newEnd = end;
+
+ for (; newStart <= end; newStart++)
+ {
+ if (!char.IsWhiteSpace(text[newStart]))
+ {
+ break;
+ }
+ }
+
+ for (; newEnd > newStart; newEnd--)
+ {
+ if (!char.IsWhiteSpace(text[newEnd]))
+ {
+ break;
+ }
+ }
+
+ // Return the new start/end after triming, or (-1, -1) if the substring only consists of whitespaces.
+ return newStart > newEnd ? (-1, -1) : (newStart, newEnd);
+ }
}
}
diff --git a/test/InlinePredictionTest.cs b/test/InlinePredictionTest.cs
index f671255..d9729c6 100644
--- a/test/InlinePredictionTest.cs
+++ b/test/InlinePredictionTest.cs
@@ -395,6 +395,7 @@ namespace Test
}
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");
@@ -409,10 +410,24 @@ namespace Test
new[] { typeof(Guid), typeof(string), typeof(uint), typeof(List) }, null);
var input = ast.Extent.Text;
- if (input == "netsh")
+ if (input is "netsh")
{
return null;
}
+ else if (input is "tooltip")
+ {
+ var suggestions_0 = new List
+ {
+ 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)ctor.Invoke(
+ new object[] { predictorId_0, "Tooltip", MiniSessionId, suggestions_0 }),
+ };
+ }
var suggestions_1 = new List
{
diff --git a/test/ListViewTooltipTest.cs b/test/ListViewTooltipTest.cs
new file mode 100644
index 0000000..624ec0c
--- /dev/null
+++ b/test/ListViewTooltipTest.cs
@@ -0,0 +1,484 @@
+using System;
+using Microsoft.PowerShell;
+using Xunit;
+
+namespace Test
+{
+ public partial class ReadLine
+ {
+ [SkippableFact]
+ public void List_Item_Tooltip_4_Lines()
+ {
+ // Set the terminal height to 22 and width to 60, so the metadata line will be fully rendered
+ // and maximum 4 lines can be used for tooltip for a selected list item.
+ int listWidth = 60;
+ TestSetup(new TestConsole(keyboardLayout: _, width: listWidth, height: 22), KeyMode.Cmd);
+
+ // The font effect sequences of 'dim' and 'italic' used in list view metadata line
+ // are ignored in the mock console, so only the white color will be left.
+ var dimmedColors = Tuple.Create(ConsoleColor.White, _console.BackgroundColor);
+ var emphasisColors = Tuple.Create(PSConsoleReadLineOptions.DefaultEmphasisColor, _console.BackgroundColor);
+ using var disp = SetPrediction(PredictionSource.HistoryAndPlugin, PredictionViewStyle.ListView);
+ _mockedMethods.ClearPredictionFields();
+
+ SetHistory("tooltip -history");
+ Test("tooltip NO2", Keys(
+ "tooltip", CheckThat(() => AssertScreenIs(6,
+ TokenClassification.Command, "tooltip",
+ NextLine,
+ TokenClassification.ListPrediction, "<-/3>",
+ TokenClassification.None, new string(' ', listWidth - 28), // 28 is the length of '<-/3>' plus ''.
+ dimmedColors, "",
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " -history",
+ TokenClassification.None, new string(' ', listWidth - 27), // 27 is the length of '> tooltip -history' plus '[History]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "History",
+ TokenClassification.None, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " NO1",
+ TokenClassification.None, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO1' plus '[Tooltip]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.None, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " NO2",
+ TokenClassification.None, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO2' plus '[Tooltip]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.None, ']',
+ // List view is done, no more list item following.
+ NextLine,
+ NextLine
+ )),
+ _.DownArrow,
+ CheckThat(() => AssertScreenIs(6,
+ TokenClassification.Command, "tooltip",
+ TokenClassification.None, ' ',
+ TokenClassification.Parameter, "-history",
+ NextLine,
+ TokenClassification.ListPrediction, "<1/3>",
+ TokenClassification.None, new string(' ', listWidth - 30), // 30 is the length of '<1/3>' plus ''.
+ dimmedColors, '<',
+ TokenClassification.ListPrediction, "History(1/1) ",
+ dimmedColors, "Tooltip(2)>",
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.ListPredictionSelected, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.ListPredictionSelected, " -history",
+ TokenClassification.ListPredictionSelected, new string(' ', listWidth - 27), // 27 is the length of '> tooltip -history' plus '[History]'.
+ TokenClassification.ListPredictionSelected, '[',
+ TokenClassification.ListPrediction, "History",
+ TokenClassification.ListPredictionSelected, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " NO1",
+ TokenClassification.None, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO1' plus '[Tooltip]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.None, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " NO2",
+ TokenClassification.None, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO2' plus '[Tooltip]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.None, ']',
+ // List view is done, no more list item following.
+ NextLine,
+ NextLine
+ )),
+ _.DownArrow,
+ CheckThat(() => AssertScreenIs(10,
+ TokenClassification.Command, "tooltip",
+ TokenClassification.None, " NO1",
+ NextLine,
+ TokenClassification.ListPrediction, "<2/3>",
+ TokenClassification.None, new string(' ', listWidth - 30), // 30 is the length of '<2/3>' plus ''.
+ dimmedColors, "',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " -history",
+ TokenClassification.None, new string(' ', listWidth - 27), // 27 is the length of '> tooltip -history' plus '[History]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "History",
+ TokenClassification.None, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.ListPredictionSelected, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.ListPredictionSelected, " NO1",
+ TokenClassification.ListPredictionSelected, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO1' plus '[Tooltip]'.
+ TokenClassification.ListPredictionSelected, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.ListPredictionSelected, ']',
+ NextLine,
+ dimmedColors, " >> Hello", NextLine,
+ dimmedColors, " Binary", NextLine,
+ dimmedColors, " World", NextLine,
+ dimmedColors, " PowerShell is a task automation an
",
+ TokenClassification.ListPrediction, "( to view all)",
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " NO2",
+ TokenClassification.None, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO2' plus '[Tooltip]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.None, ']',
+ // List view is done, no more list item following.
+ NextLine,
+ NextLine
+ )),
+ _.DownArrow,
+ CheckThat(() => AssertScreenIs(8,
+ TokenClassification.Command, "tooltip",
+ TokenClassification.None, " NO2",
+ NextLine,
+ TokenClassification.ListPrediction, "<3/3>",
+ TokenClassification.None, new string(' ', listWidth - 30), // 30 is the length of '<3/3>' plus ''.
+ dimmedColors, "',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " -history",
+ TokenClassification.None, new string(' ', listWidth - 27), // 27 is the length of '> tooltip -history' plus '[History]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "History",
+ TokenClassification.None, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " NO1",
+ TokenClassification.None, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO1' plus '[Tooltip]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.None, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.ListPredictionSelected, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.ListPredictionSelected, " NO2",
+ TokenClassification.ListPredictionSelected, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO2' plus '[Tooltip]'.
+ TokenClassification.ListPredictionSelected, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.ListPredictionSelected, ']',
+ NextLine,
+ dimmedColors, " >> Hello", NextLine,
+ dimmedColors, " World",
+ // List view is done, no more list item following.
+ NextLine,
+ NextLine
+ )),
+
+ // Once accepted, the list should be cleared.
+ _.Enter, CheckThat(() => AssertScreenIs(2,
+ TokenClassification.Command, "tooltip",
+ TokenClassification.None, " NO2",
+ NextLine,
+ NextLine))
+ ));
+ }
+
+ [SkippableFact]
+ public void List_Item_Tooltip_2_Lines()
+ {
+ // Set the terminal height to 15 and width to 60, so the metadata line will be fully rendered
+ // and maximum 2 lines can be used for tooltip for a selected list item.
+ int listWidth = 60;
+ TestSetup(new TestConsole(keyboardLayout: _, width: listWidth, height: 15), KeyMode.Cmd);
+
+ // The font effect sequences of 'dim' and 'italic' used in list view metadata line
+ // are ignored in the mock console, so only the white color will be left.
+ var dimmedColors = Tuple.Create(ConsoleColor.White, _console.BackgroundColor);
+ var emphasisColors = Tuple.Create(PSConsoleReadLineOptions.DefaultEmphasisColor, _console.BackgroundColor);
+ using var disp = SetPrediction(PredictionSource.HistoryAndPlugin, PredictionViewStyle.ListView);
+ _mockedMethods.ClearPredictionFields();
+
+ SetHistory("tooltip -history");
+ Test("tooltip NO2", Keys(
+ "tooltip", CheckThat(() => AssertScreenIs(6,
+ TokenClassification.Command, "tooltip",
+ NextLine,
+ TokenClassification.ListPrediction, "<-/3>",
+ TokenClassification.None, new string(' ', listWidth - 28), // 28 is the length of '<-/3>' plus ''.
+ dimmedColors, "",
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " -history",
+ TokenClassification.None, new string(' ', listWidth - 27), // 27 is the length of '> tooltip -history' plus '[History]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "History",
+ TokenClassification.None, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " NO1",
+ TokenClassification.None, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO1' plus '[Tooltip]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.None, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " NO2",
+ TokenClassification.None, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO2' plus '[Tooltip]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.None, ']',
+ // List view is done, no more list item following.
+ NextLine,
+ NextLine
+ )),
+ _.DownArrow, _.DownArrow,
+ CheckThat(() => AssertScreenIs(8,
+ TokenClassification.Command, "tooltip",
+ TokenClassification.None, " NO1",
+ NextLine,
+ TokenClassification.ListPrediction, "<2/3>",
+ TokenClassification.None, new string(' ', listWidth - 30), // 30 is the length of '<2/3>' plus ''.
+ dimmedColors, "',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " -history",
+ TokenClassification.None, new string(' ', listWidth - 27), // 27 is the length of '> tooltip -history' plus '[History]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "History",
+ TokenClassification.None, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.ListPredictionSelected, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.ListPredictionSelected, " NO1",
+ TokenClassification.ListPredictionSelected, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO1' plus '[Tooltip]'.
+ TokenClassification.ListPredictionSelected, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.ListPredictionSelected, ']',
+ NextLine,
+ dimmedColors, " >> Hello", NextLine,
+ dimmedColors, " Binary
",
+ TokenClassification.ListPrediction, "( to view all)",
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " NO2",
+ TokenClassification.None, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO2' plus '[Tooltip]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.None, ']',
+ // List view is done, no more list item following.
+ NextLine,
+ NextLine
+ )),
+ _.F4,
+ CheckThat(() => Assert.Equal(
+ "Hello\nBinary\nWorld\nPowerShell is a task automation and configuration management program from Microsoft",
+ _mockedMethods.helpContentRendered)),
+ _.DownArrow,
+ CheckThat(() => AssertScreenIs(8,
+ TokenClassification.Command, "tooltip",
+ TokenClassification.None, " NO2",
+ NextLine,
+ TokenClassification.ListPrediction, "<3/3>",
+ TokenClassification.None, new string(' ', listWidth - 30), // 30 is the length of '<3/3>' plus ''.
+ dimmedColors, "',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " -history",
+ TokenClassification.None, new string(' ', listWidth - 27), // 27 is the length of '> tooltip -history' plus '[History]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "History",
+ TokenClassification.None, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " NO1",
+ TokenClassification.None, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO1' plus '[Tooltip]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.None, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.ListPredictionSelected, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.ListPredictionSelected, " NO2",
+ TokenClassification.ListPredictionSelected, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO2' plus '[Tooltip]'.
+ TokenClassification.ListPredictionSelected, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.ListPredictionSelected, ']',
+ NextLine,
+ dimmedColors, " >> Hello", NextLine,
+ dimmedColors, " World",
+ // List view is done, no more list item following.
+ NextLine,
+ NextLine
+ )),
+
+ // Once accepted, the list should be cleared.
+ _.Enter, CheckThat(() => AssertScreenIs(2,
+ TokenClassification.Command, "tooltip",
+ TokenClassification.None, " NO2",
+ NextLine,
+ NextLine))
+ ));
+ }
+
+ [SkippableFact]
+ public void List_Item_Tooltip_1_Line()
+ {
+ // Set the terminal height to 6 and width to 60, so the metadata line will be fully rendered
+ // and maximum 2 lines can be used for tooltip for a selected list item.
+ int listWidth = 60;
+ TestSetup(new TestConsole(keyboardLayout: _, width: listWidth, height: 6), KeyMode.Cmd);
+
+ // The font effect sequences of 'dim' and 'italic' used in list view metadata line
+ // are ignored in the mock console, so only the white color will be left.
+ var dimmedColors = Tuple.Create(ConsoleColor.White, _console.BackgroundColor);
+ var emphasisColors = Tuple.Create(PSConsoleReadLineOptions.DefaultEmphasisColor, _console.BackgroundColor);
+ using var disp = SetPrediction(PredictionSource.HistoryAndPlugin, PredictionViewStyle.ListView);
+ _mockedMethods.ClearPredictionFields();
+
+ SetHistory("tooltip -history");
+ Test("tooltip NO2", Keys(
+ "tooltip", _.DownArrow, _.DownArrow,
+ CheckThat(() => AssertScreenIs(6,
+ TokenClassification.Command, "tooltip",
+ TokenClassification.None, " NO1",
+ NextLine,
+ TokenClassification.ListPrediction, "<2/3>",
+ TokenClassification.None, new string(' ', listWidth - 30), // 30 is the length of '<2/3>' plus ''.
+ dimmedColors, "',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " -history",
+ TokenClassification.None, new string(' ', listWidth - 27), // 27 is the length of '> tooltip -history' plus '[History]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "History",
+ TokenClassification.None, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.ListPredictionSelected, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.ListPredictionSelected, " NO1",
+ TokenClassification.ListPredictionSelected, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO1' plus '[Tooltip]'.
+ TokenClassification.ListPredictionSelected, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.ListPredictionSelected, ']',
+ NextLine,
+ dimmedColors, " >> Hello
",
+ TokenClassification.ListPrediction, "( to view all)",
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " NO2",
+ TokenClassification.None, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO2' plus '[Tooltip]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.None, ']',
+ // List view is done, no more list item following.
+ NextLine
+ )),
+ _.F4,
+ CheckThat(() => Assert.Equal(
+ "Hello\nBinary\nWorld\nPowerShell is a task automation and configuration management program from Microsoft",
+ _mockedMethods.helpContentRendered)),
+ _.DownArrow,
+ CheckThat(() => AssertScreenIs(6,
+ TokenClassification.Command, "tooltip",
+ TokenClassification.None, " NO2",
+ NextLine,
+ TokenClassification.ListPrediction, "<3/3>",
+ TokenClassification.None, new string(' ', listWidth - 30), // 30 is the length of '<3/3>' plus ''.
+ dimmedColors, "',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " -history",
+ TokenClassification.None, new string(' ', listWidth - 27), // 27 is the length of '> tooltip -history' plus '[History]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "History",
+ TokenClassification.None, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.None, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.None, " NO1",
+ TokenClassification.None, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO1' plus '[Tooltip]'.
+ TokenClassification.None, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.None, ']',
+ NextLine,
+ TokenClassification.ListPrediction, '>',
+ TokenClassification.ListPredictionSelected, ' ',
+ emphasisColors, "tooltip",
+ TokenClassification.ListPredictionSelected, " NO2",
+ TokenClassification.ListPredictionSelected, new string(' ', listWidth - 22), // 22 is the length of '> tooltip NO2' plus '[Tooltip]'.
+ TokenClassification.ListPredictionSelected, '[',
+ TokenClassification.ListPrediction, "Tooltip",
+ TokenClassification.ListPredictionSelected, ']',
+ NextLine,
+ dimmedColors, " >> Hello
",
+ TokenClassification.ListPrediction, "( to view all)",
+ // List view is done, no more list item following.
+ NextLine
+ )),
+ _.F4,
+ CheckThat(() => Assert.Equal(
+ "Hello\nWorld",
+ _mockedMethods.helpContentRendered)),
+
+ // Once accepted, the list should be cleared.
+ _.Enter, CheckThat(() => AssertScreenIs(2,
+ TokenClassification.Command, "tooltip",
+ TokenClassification.None, " NO2",
+ NextLine,
+ NextLine))
+ ));
+ }
+ }
+}
diff --git a/test/MockConsole.cs b/test/MockConsole.cs
index f827d46..ee0a7c3 100644
--- a/test/MockConsole.cs
+++ b/test/MockConsole.cs
@@ -235,9 +235,9 @@ namespace Test
var escapeSequence = s.Substring(i + 2, len);
foreach (var subsequence in escapeSequence.Split(';'))
{
- if (subsequence is "2" or "3")
+ if (subsequence is "1" or "2" or "3")
{
- // Ignore the font effect sequence: 2 - dimmed color; 3 - italics
+ // Ignore the font effect sequence: 1 - bold; 2 - dimmed color; 3 - italics
// They are used in the metadata line of the list view.
continue;
}
@@ -451,9 +451,9 @@ namespace Test
var escapeSequence = s.Substring(i + 2, len);
foreach (var subsequence in escapeSequence.Split(';'))
{
- if (subsequence is "2" or "3")
+ if (subsequence is "1" or "2" or "3")
{
- // Ignore the font effect sequence: 2 - dimmed color; 3 - italics
+ // Ignore the font effect sequence: 1 - bold; 2 - dimmed color; 3 - italics
// They are used in the metadata line of the list view.
continue;
}