Support tooltip rendering in the prediction list view (#3667)

This commit is contained in:
Dongbo Wang
2023-05-02 16:52:19 -07:00
committed by GitHub
parent 18b5614545
commit ae08ca6caf
14 changed files with 789 additions and 25 deletions
+10
View File
@@ -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);
+1 -5
View File
@@ -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;
+3
View File
@@ -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):
+2
View File
@@ -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
+4
View File
@@ -204,6 +204,10 @@ $d = [Microsoft.PowerShell.KeyHandler]::GetGroupingDescription($_.Group)
<Label>ListPredictionSelectedColor</Label>
<ScriptBlock>[Microsoft.PowerShell.VTColorUtils]::FormatColor($_.ListPredictionSelectedColor)</ScriptBlock>
</ListItem>
<ListItem>
<Label>ListPredictionTooltipColor</Label>
<ScriptBlock>[Microsoft.PowerShell.VTColorUtils]::FormatColor($_.ListPredictionTooltipColor)</ScriptBlock>
</ListItem>
<ListItem>
<Label>MemberColor</Label>
<ScriptBlock>[Microsoft.PowerShell.VTColorUtils]::FormatColor($_.MemberColor)</ScriptBlock>
+11
View File
@@ -2192,6 +2192,17 @@ namespace Microsoft.PowerShell.PSReadLine {
}
}
/// <summary>
/// Looks up a localized string similar to Show the full tooltip of the selected list-view item in the terminal's alternate screen buffer.
/// </summary>
internal static string ShowFullPredictionTooltipDescription
{
get
{
return ResourceManager.GetString("ShowFullPredictionTooltipDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Make visual selection of the command arguments.
/// </summary>
+3
View File
@@ -837,6 +837,9 @@ Or not saving history with:
<data name="SwitchPredictionViewDescription" xml:space="preserve">
<value>Switch between the inline and list prediction views.</value>
</data>
<data name="ShowFullPredictionTooltipDescription" xml:space="preserve">
<value>Show the full tooltip of the selected list-view item in the terminal's alternate screen buffer.</value>
</data>
<data name="WindowSizeTooSmallForListView" xml:space="preserve">
<value>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}'.</value>
</data>
+4 -2
View File
@@ -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;
+207 -13
View File
@@ -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
}
}
/// <summary>
/// The tooltip of the currently selected item.
/// </summary>
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
/// <summary>
/// Calculate the max width and height of the list view based on the current terminal size.
/// </summary>
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);
}
/// <summary>
@@ -326,8 +349,9 @@ namespace Microsoft.PowerShell
/// </summary>
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 */;
}
/// <summary>
@@ -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;
}
/// <summary>
/// Calculate the number of tooltip lines rendered in the list view.
/// </summary>
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;
}
/// <summary>
/// Generate the rendering text for the metadata line.
/// </summary>
@@ -865,6 +917,146 @@ namespace Microsoft.PowerShell
buffer.Insert(charPosition, " ", padding);
}
/// <summary>
/// Generate the rendering text for the tooltip.
/// </summary>
private int RenderTooltip(string tooltip, List<StringBuilder> consoleBufferLines, ref int currentLogicalLine)
{
const int LengthOfLeadingPart = 6;
const string IndicatorSymbol = ">>";
const string MsgForViewAll = "(<F4> 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 "(<F4> 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;
}
/// <summary>
/// Trigger the feedback about a suggestion was accepted.
/// </summary>
@@ -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;
}
+15
View File
@@ -180,6 +180,21 @@ namespace Microsoft.PowerShell
UpdateListSelection(numericArg);
}
/// <summary>
/// Show the tooltip of the currently selected list item in the full view.
/// </summary>
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);
}
}
}
/// <summary>
/// Implementation for updating the selected item in list view.
/// </summary>
+25
View File
@@ -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);
}
}
}
+16 -1
View File
@@ -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<PredictiveSuggestion>) }, 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<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>
{
+484
View File
@@ -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 '<History(1) Tooltip(2)>'.
dimmedColors, "<History(1) Tooltip(2)>",
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 '<History(1/1) Tooltip(2)>'.
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 '<History(1) Tooltip(1/2)>'.
dimmedColors, "<History(1) ",
TokenClassification.ListPrediction, "Tooltip(1/2)",
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, "(<F4> 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 '<History(1) Tooltip(2/2)>'.
dimmedColors, "<History(1) ",
TokenClassification.ListPrediction, "Tooltip(2/2)",
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 '<History(1) Tooltip(2)>'.
dimmedColors, "<History(1) Tooltip(2)>",
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 '<History(1) Tooltip(1/2)>'.
dimmedColors, "<History(1) ",
TokenClassification.ListPrediction, "Tooltip(1/2)",
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, "(<F4> 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 '<History(1) Tooltip(2/2)>'.
dimmedColors, "<History(1) ",
TokenClassification.ListPrediction, "Tooltip(2/2)",
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 '<History(1) Tooltip(1/2)>'.
dimmedColors, "<History(1) ",
TokenClassification.ListPrediction, "Tooltip(1/2)",
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, "(<F4> 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 '<History(1) Tooltip(2/2)>'.
dimmedColors, "<History(1) ",
TokenClassification.ListPrediction, "Tooltip(2/2)",
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, "(<F4> 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))
));
}
}
}
+4 -4
View File
@@ -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;
}