Add BasicScrollingConsole to support test cases for scrolling up scenarios (#3081)

This commit is contained in:
Dongbo Wang
2021-12-14 15:14:03 -08:00
committed by GitHub
parent 8e6893c0ba
commit b76883665b
7 changed files with 849 additions and 57 deletions
+9
View File
@@ -867,6 +867,7 @@ namespace Microsoft.PowerShell
// getting shorter or longer.
var endOfCommandLine = ConvertOffsetToPoint(_buffer.Length);
var topAdjustment = (endOfCommandLine.Y + 1) - menu.Top;
int oldInitialY = _initialY;
if (topAdjustment != 0)
{
@@ -877,6 +878,14 @@ namespace Microsoft.PowerShell
// Render did not clear the rest of the command line which flowed
// into the menu, so we must do that here.
menu.SaveCursor();
if (oldInitialY > _initialY)
{
// Scrolling happened when drawing the menu, so we need to adjust
// this point as it was calculated before drawing the menu.
endOfCommandLine.Y -= oldInitialY - _initialY;
}
_console.SetCursorPosition(endOfCommandLine.X, endOfCommandLine.Y);
_console.Write(Spaces(_console.BufferWidth - endOfCommandLine.X));
menu.RestoreCursor();
+3 -1
View File
@@ -1108,7 +1108,9 @@ namespace Microsoft.PowerShell
internal static bool IsRunningCI(IConsole console)
{
return console.GetType().FullName == "Test.TestConsole";
Type consoleType = console.GetType();
return consoleType.FullName == "Test.TestConsole"
|| consoleType.BaseType.FullName == "Test.TestConsole";
}
}
}
+479
View File
@@ -733,6 +733,174 @@ namespace Test
));
}
[SkippableFact]
public void MenuCompletions_Navigation7()
{
// Trigger the menu completion from the last line in the screen buffer, which will cause the screen
// to scroll up. Then test 'DownArrow' and 'UpArrow' with the following menu to verify if scrolling
// was handled correctly:
// Get-Many0 Get-Many3 Get-Many6 Get-Many9 Get-Many12
// Get-Many1 Get-Many4 Get-Many7 Get-Many10 Get-Many13
// Get-Many2 Get-Many5 Get-Many8 Get-Many11 Get-Many14
var basicScrollingConsole = new BasicScrollingConsole(keyboardLayout: _, width: 60, height: 10);
TestSetup(basicScrollingConsole, KeyMode.Cmd, new KeyHandler("Ctrl+Spacebar", PSConsoleReadLine.MenuComplete));
// Write 12 new-lines, so that the next input will be at the last line of the screen buffer.
basicScrollingConsole.Write(new string('\n', 12));
AssertCursorLeftTopIs(0, 9);
Test("Get-Many0", Keys(
"Get-Many",
CheckThat(() => AssertCursorLeftTopIs(8, 9)),
_.Ctrl_Spacebar,
// Menu completion will trigger scrolling.
CheckThat(() => AssertCursorLeftTopIs(8, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Command, "Get-Many",
TokenClassification.Selection, "0", NextLine,
TokenClassification.Selection, "Get-Many0 ",
TokenClassification.None,
"Get-Many3 Get-Many6 Get-Many9 Get-Many12", NextLine,
"Get-Many1 Get-Many4 Get-Many7 Get-Many10 Get-Many13", NextLine,
"Get-Many2 Get-Many5 Get-Many8 Get-Many11 Get-Many14")),
_.DownArrow,
CheckThat(() => AssertCursorLeftTopIs(8, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Command, "Get-Many",
TokenClassification.Selection, "1", NextLine,
TokenClassification.None,
"Get-Many0 Get-Many3 Get-Many6 Get-Many9 Get-Many12", NextLine,
TokenClassification.Selection, "Get-Many1 ",
TokenClassification.None,
"Get-Many4 Get-Many7 Get-Many10 Get-Many13", NextLine,
"Get-Many2 Get-Many5 Get-Many8 Get-Many11 Get-Many14")),
_.DownArrow,
CheckThat(() => AssertCursorLeftTopIs(8, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Command, "Get-Many",
TokenClassification.Selection, "2", NextLine,
TokenClassification.None,
"Get-Many0 Get-Many3 Get-Many6 Get-Many9 Get-Many12", NextLine,
"Get-Many1 Get-Many4 Get-Many7 Get-Many10 Get-Many13", NextLine,
TokenClassification.Selection, "Get-Many2 ",
TokenClassification.None,
"Get-Many5 Get-Many8 Get-Many11 Get-Many14")),
_.DownArrow,
CheckThat(() => AssertCursorLeftTopIs(8, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Command, "Get-Many",
TokenClassification.Selection, "3", NextLine,
TokenClassification.None, "Get-Many0 ",
TokenClassification.Selection, "Get-Many3 ",
TokenClassification.None,
"Get-Many6 Get-Many9 Get-Many12", NextLine,
"Get-Many1 Get-Many4 Get-Many7 Get-Many10 Get-Many13", NextLine,
"Get-Many2 Get-Many5 Get-Many8 Get-Many11 Get-Many14")),
_.DownArrow,
CheckThat(() => AssertCursorLeftTopIs(8, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Command, "Get-Many",
TokenClassification.Selection, "4", NextLine,
TokenClassification.None,
"Get-Many0 Get-Many3 Get-Many6 Get-Many9 Get-Many12", NextLine,
"Get-Many1 ",
TokenClassification.Selection, "Get-Many4 ",
TokenClassification.None,
"Get-Many7 Get-Many10 Get-Many13", NextLine,
"Get-Many2 Get-Many5 Get-Many8 Get-Many11 Get-Many14")),
_.DownArrow,
CheckThat(() => AssertCursorLeftTopIs(8, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Command, "Get-Many",
TokenClassification.Selection, "5", NextLine,
TokenClassification.None,
"Get-Many0 Get-Many3 Get-Many6 Get-Many9 Get-Many12", NextLine,
"Get-Many1 Get-Many4 Get-Many7 Get-Many10 Get-Many13", NextLine,
"Get-Many2 ",
TokenClassification.Selection, "Get-Many5 ",
TokenClassification.None,
"Get-Many8 Get-Many11 Get-Many14")),
_.UpArrow,
CheckThat(() => AssertCursorLeftTopIs(8, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Command, "Get-Many",
TokenClassification.Selection, "4", NextLine,
TokenClassification.None,
"Get-Many0 Get-Many3 Get-Many6 Get-Many9 Get-Many12", NextLine,
"Get-Many1 ",
TokenClassification.Selection, "Get-Many4 ",
TokenClassification.None,
"Get-Many7 Get-Many10 Get-Many13", NextLine,
"Get-Many2 Get-Many5 Get-Many8 Get-Many11 Get-Many14")),
_.UpArrow,
CheckThat(() => AssertCursorLeftTopIs(8, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Command, "Get-Many",
TokenClassification.Selection, "3", NextLine,
TokenClassification.None, "Get-Many0 ",
TokenClassification.Selection, "Get-Many3 ",
TokenClassification.None,
"Get-Many6 Get-Many9 Get-Many12", NextLine,
"Get-Many1 Get-Many4 Get-Many7 Get-Many10 Get-Many13", NextLine,
"Get-Many2 Get-Many5 Get-Many8 Get-Many11 Get-Many14")),
_.UpArrow,
CheckThat(() => AssertCursorLeftTopIs(8, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Command, "Get-Many",
TokenClassification.Selection, "2", NextLine,
TokenClassification.None,
"Get-Many0 Get-Many3 Get-Many6 Get-Many9 Get-Many12", NextLine,
"Get-Many1 Get-Many4 Get-Many7 Get-Many10 Get-Many13", NextLine,
TokenClassification.Selection, "Get-Many2 ",
TokenClassification.None,
"Get-Many5 Get-Many8 Get-Many11 Get-Many14")),
_.UpArrow, _.UpArrow,
CheckThat(() => AssertCursorLeftTopIs(8, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Command, "Get-Many",
TokenClassification.Selection, "0", NextLine,
TokenClassification.Selection, "Get-Many0 ",
TokenClassification.None,
"Get-Many3 Get-Many6 Get-Many9 Get-Many12", NextLine,
"Get-Many1 Get-Many4 Get-Many7 Get-Many10 Get-Many13", NextLine,
"Get-Many2 Get-Many5 Get-Many8 Get-Many11 Get-Many14")),
_.LeftArrow,
CheckThat(() => AssertCursorLeftTopIs(8, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Command, "Get-Many",
TokenClassification.Selection, "14", NextLine,
TokenClassification.None,
"Get-Many0 Get-Many3 Get-Many6 Get-Many9 Get-Many12", NextLine,
"Get-Many1 Get-Many4 Get-Many7 Get-Many10 Get-Many13", NextLine,
"Get-Many2 Get-Many5 Get-Many8 Get-Many11 ",
TokenClassification.Selection, "Get-Many14 ", NextLine)),
_.RightArrow,
CheckThat(() => AssertCursorLeftTopIs(8, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Command, "Get-Many",
TokenClassification.Selection, "0", NextLine,
TokenClassification.Selection, "Get-Many0 ",
TokenClassification.None,
"Get-Many3 Get-Many6 Get-Many9 Get-Many12", NextLine,
"Get-Many1 Get-Many4 Get-Many7 Get-Many10 Get-Many13", NextLine,
"Get-Many2 Get-Many5 Get-Many8 Get-Many11 Get-Many14")),
_.Enter,
_.Enter),
resetCursor: false);
}
[SkippableFact]
public void MenuCompletions_ClearProperly()
{
@@ -856,6 +1024,269 @@ namespace Test
));
}
[SkippableFact]
public void MenuCompletions_HandleScrolling1()
{
// This test case covers the fix to https://github.com/PowerShell/PSReadLine/issues/2928.
var basicScrollingConsole = new BasicScrollingConsole(keyboardLayout: _, width: 133, height: 10);
TestSetup(basicScrollingConsole, KeyMode.Cmd, new KeyHandler("Ctrl+Spacebar", PSConsoleReadLine.MenuComplete));
// Write 12 new-lines, so that the next input will be at the last line of the screen buffer.
basicScrollingConsole.Write(new string('\n', 12));
AssertCursorLeftTopIs(0, 9);
// Input length: 131; BufferWidth: 133. MenuComplete on '[reg' will first get '[regex', which makes the line
// fit exactly the whole buffer width, and thus will cause screen scrolling when the current line is at the
// last line of the screen buffer.
string input = @"$instMods = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' -replace [reg] $env:HOMEPATH ,""`$env:HOMEPATH""";
Test(input, Keys(
input,
CheckThat(() => AssertCursorLeftTopIs(131, 9)),
_.Ctrl_LeftArrow, _.Ctrl_LeftArrow, _.Ctrl_LeftArrow, _.Ctrl_LeftArrow,
_.LeftArrow, _.LeftArrow,
CheckThat(() => AssertCursorLeftTopIs(98, 9)),
_.Ctrl_Spacebar,
CheckThat(() => AssertCursorLeftTopIs(98, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Variable, "$instMods",
TokenClassification.None, ' ',
TokenClassification.Operator, '=',
TokenClassification.None, ' ',
TokenClassification.String, "'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'",
TokenClassification.None, ' ',
TokenClassification.Operator, "-replace",
TokenClassification.None, " [",
TokenClassification.Type, "reg",
TokenClassification.Selection, "ex",
TokenClassification.None, "] ",
TokenClassification.Variable, "$env:HOMEPATH",
TokenClassification.None, ' ',
TokenClassification.Operator, ',',
TokenClassification.String, @"""`$env:HOMEPATH""",
TokenClassification.None, _emptyLine,
TokenClassification.Selection, "Regex ",
TokenClassification.None, "RegionInfo RegisterPSSessionConfigurationCommand", NextLine,
TokenClassification.None, "RegexCompilationInfo RegisterArgumentCompleterCommand RegistryProviderSetItemDynamicParameter")),
_.RightArrow,
CheckThat(() => AssertCursorLeftTopIs(119, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Variable, "$instMods",
TokenClassification.None, ' ',
TokenClassification.Operator, '=',
TokenClassification.None, ' ',
TokenClassification.String, "'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'",
TokenClassification.None, ' ',
TokenClassification.Operator, "-replace",
TokenClassification.None, " [",
TokenClassification.Type, "System.Globalization.Reg",
TokenClassification.Selection, "ionInfo",
TokenClassification.None, "] ",
TokenClassification.Variable, "$env:HOMEPATH",
TokenClassification.None, ' ',
TokenClassification.Operator, ',',
TokenClassification.String, @"""`$env:HOMEPATH""",
NextLine,
TokenClassification.None, "Regex ",
TokenClassification.Selection, "RegionInfo ",
TokenClassification.None, "RegisterPSSessionConfigurationCommand", NextLine,
TokenClassification.None, "RegexCompilationInfo RegisterArgumentCompleterCommand RegistryProviderSetItemDynamicParameter")),
_.Escape,
CheckThat(() => AssertCursorLeftTopIs(98, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.Variable, "$instMods",
TokenClassification.None, ' ',
TokenClassification.Operator, '=',
TokenClassification.None, ' ',
TokenClassification.String, "'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'",
TokenClassification.None, ' ',
TokenClassification.Operator, "-replace",
TokenClassification.None, " [",
TokenClassification.Type, "reg",
TokenClassification.None, "] ",
TokenClassification.Variable, "$env:HOMEPATH",
TokenClassification.None, ' ',
TokenClassification.Operator, ',',
TokenClassification.String, @"""`$env:HOMEPATH""",
NextLine,
NextLine,
NextLine,
NextLine)),
_.Enter),
resetCursor: false);
}
[SkippableFact]
public void MenuCompletions_HandleScrolling2()
{
// This test case covers the fix to https://github.com/PowerShell/PSReadLine/issues/2948.
var basicScrollingConsole = new BasicScrollingConsole(keyboardLayout: _, width: 133, height: 10);
TestSetup(basicScrollingConsole, KeyMode.Cmd, new KeyHandler("Ctrl+Spacebar", PSConsoleReadLine.MenuComplete));
// Write 12 new-lines, so that the next input will be at the last line of the screen buffer.
basicScrollingConsole.Write(new string('\n', 12));
AssertCursorLeftTopIs(0, 9);
// Input length: 69; BufferWidth: 133.
// MenuComplete on '[reg' contains one entry that will make the line fit exactly the whole buffer width,
// and thus will cause screen scrolling. But that entry is not the first one in the menu.
string input = @"'AAAAAAAAAAAAAAAAAAAAA' -replace [reg] $env:HOMEPATH ,'$env:HOMEPATH'";
Test(input, Keys(
input,
CheckThat(() => AssertCursorLeftTopIs(69, 9)),
_.Ctrl_LeftArrow, _.Ctrl_LeftArrow, _.Ctrl_LeftArrow, _.Ctrl_LeftArrow,
_.LeftArrow, _.LeftArrow,
CheckThat(() => AssertCursorLeftTopIs(37, 9)),
_.Ctrl_Spacebar,
CheckThat(() => AssertCursorLeftTopIs(37, 7)),
CheckThat(() => AssertScreenIs(top: 12, lines: 3,
TokenClassification.String, "'AAAAAAAAAAAAAAAAAAAAA'",
TokenClassification.None, ' ',
TokenClassification.Operator, "-replace",
TokenClassification.None, " [",
TokenClassification.Type, "reg",
TokenClassification.Selection, "ex",
TokenClassification.None, "] ",
TokenClassification.Variable, "$env:HOMEPATH",
TokenClassification.None, ' ',
TokenClassification.Operator, ',',
TokenClassification.String, "'$env:HOMEPATH'",
NextLine,
TokenClassification.Selection, "Regex ",
TokenClassification.None, "RegionInfo RegisterPSSessionConfigurationCommand", NextLine,
TokenClassification.None, "RegexCompilationInfo RegisterArgumentCompleterCommand RegistryProviderSetItemDynamicParameter")),
_.RightArrow,
CheckThat(() => AssertCursorLeftTopIs(58, 7)),
CheckThat(() => AssertScreenIs(top: 12, lines: 3,
TokenClassification.String, "'AAAAAAAAAAAAAAAAAAAAA'",
TokenClassification.None, ' ',
TokenClassification.Operator, "-replace",
TokenClassification.None, " [",
TokenClassification.Type, "System.Globalization.Reg",
TokenClassification.Selection, "ionInfo",
TokenClassification.None, "] ",
TokenClassification.Variable, "$env:HOMEPATH",
TokenClassification.None, ' ',
TokenClassification.Operator, ',',
TokenClassification.String, "'$env:HOMEPATH'",
NextLine,
TokenClassification.None, "Regex ",
TokenClassification.Selection, "RegionInfo ",
TokenClassification.None, "RegisterPSSessionConfigurationCommand", NextLine,
TokenClassification.None, "RegexCompilationInfo RegisterArgumentCompleterCommand RegistryProviderSetItemDynamicParameter")),
_.RightArrow,
CheckThat(() => AssertCursorLeftTopIs(67, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.String, "'AAAAAAAAAAAAAAAAAAAAA'",
TokenClassification.None, ' ',
TokenClassification.Operator, "-replace",
TokenClassification.None, " [",
TokenClassification.Type, "Microsoft.PowerShell.Commands.Reg",
TokenClassification.Selection, "isterPSSessionConfigurationCommand",
TokenClassification.None, "] ",
TokenClassification.Variable, "$env:HOMEPATH",
TokenClassification.None, ' ',
TokenClassification.Operator, ',',
TokenClassification.String, "'$env:HOMEPATH'",
TokenClassification.None, _emptyLine,
TokenClassification.None, "Regex RegionInfo ",
TokenClassification.Selection, "RegisterPSSessionConfigurationCommand ", NextLine,
TokenClassification.None, "RegexCompilationInfo RegisterArgumentCompleterCommand RegistryProviderSetItemDynamicParameter")),
_.RightArrow,
CheckThat(() => AssertCursorLeftTopIs(49, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.String, "'AAAAAAAAAAAAAAAAAAAAA'",
TokenClassification.None, ' ',
TokenClassification.Operator, "-replace",
TokenClassification.None, " [",
TokenClassification.Type, "System.Text.Reg",
TokenClassification.Selection, "ularExpressions.RegexCompilationInfo",
TokenClassification.None, "] ",
TokenClassification.Variable, "$env:HOMEPATH",
TokenClassification.None, ' ',
TokenClassification.Operator, ',',
TokenClassification.String, "'$env:HOMEPATH'",
NextLine,
TokenClassification.None, "Regex RegionInfo RegisterPSSessionConfigurationCommand",
NextLine,
TokenClassification.Selection, "RegexCompilationInfo ",
TokenClassification.None, "RegisterArgumentCompleterCommand RegistryProviderSetItemDynamicParameter",
NextLine,
NextLine)),
_.RightArrow,
CheckThat(() => AssertCursorLeftTopIs(66, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.String, "'AAAAAAAAAAAAAAAAAAAAA'",
TokenClassification.None, ' ',
TokenClassification.Operator, "-replace",
TokenClassification.None, " [",
TokenClassification.Type, "System.Management.Automation.Reg",
TokenClassification.Selection, "isterArgumentCompleterCommand",
TokenClassification.None, "] ",
TokenClassification.Variable, "$env:HOMEPATH",
TokenClassification.None, ' ',
TokenClassification.Operator, ',',
TokenClassification.String, "'$env:HOMEPATH'",
NextLine,
TokenClassification.None, "Regex RegionInfo RegisterPSSessionConfigurationCommand",
NextLine,
TokenClassification.None, "RegexCompilationInfo ",
TokenClassification.Selection, "RegisterArgumentCompleterCommand ",
TokenClassification.None, "RegistryProviderSetItemDynamicParameter",
NextLine,
NextLine)),
_.RightArrow,
CheckThat(() => AssertCursorLeftTopIs(67, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.String, "'AAAAAAAAAAAAAAAAAAAAA'",
TokenClassification.None, ' ',
TokenClassification.Operator, "-replace",
TokenClassification.None, " [",
TokenClassification.Type, "Microsoft.PowerShell.Commands.Reg",
TokenClassification.Selection, "istryProviderSetItemDynamicParameter",
TokenClassification.None, "] ",
TokenClassification.Variable, "$env:HOMEPATH",
TokenClassification.None, ' ',
TokenClassification.Operator, ',',
TokenClassification.String, "'$env:HOMEPATH'",
NextLine,
TokenClassification.None, "Regex RegionInfo RegisterPSSessionConfigurationCommand",
NextLine,
TokenClassification.None, "RegexCompilationInfo RegisterArgumentCompleterCommand ",
TokenClassification.Selection, "RegistryProviderSetItemDynamicParameter ",
NextLine)),
_.Escape,
CheckThat(() => AssertCursorLeftTopIs(37, 6)),
CheckThat(() => AssertScreenIs(top: 12, lines: 4,
TokenClassification.String, "'AAAAAAAAAAAAAAAAAAAAA'",
TokenClassification.None, ' ',
TokenClassification.Operator, "-replace",
TokenClassification.None, " [",
TokenClassification.Type, "reg",
TokenClassification.None, "] ",
TokenClassification.Variable, "$env:HOMEPATH",
TokenClassification.None, ' ',
TokenClassification.Operator, ',',
TokenClassification.String, "'$env:HOMEPATH'",
NextLine,
NextLine,
NextLine,
NextLine)),
_.Enter),
resetCursor: false);
}
[SkippableFact]
public void ShowTooltips()
{
@@ -996,10 +1427,58 @@ namespace Test
replacementLength = 6;
completions.Add(new CompletionResult("Get-MockDynamicParameters"));
completions.Add(new CompletionResult("Get-Module"));
break;
}
int index = input.IndexOf("[reg]");
if (index > 0 && index + 4 == cursor)
{
// cursor is pointing at ']'.
replacementIndex = index + 1;
replacementLength = 3;
completions.Add(
new CompletionResult(
"regex",
"Regex",
CompletionResultType.Type,
"regex"));
completions.Add(
new CompletionResult(
"System.Text.RegularExpressions.RegexCompilationInfo",
"RegexCompilationInfo",
CompletionResultType.Type,
"System.Text.RegularExpressions.RegexCompilationInfo"));
completions.Add(
new CompletionResult(
"System.Globalization.RegionInfo",
"RegionInfo",
CompletionResultType.Type,
"System.Globalization.RegionInfo"));
completions.Add(
new CompletionResult(
"System.Management.Automation.RegisterArgumentCompleterCommand",
"RegisterArgumentCompleterCommand",
CompletionResultType.Type,
"System.Management.Automation.RegisterArgumentCompleterCommand"));
completions.Add(
new CompletionResult(
"Microsoft.PowerShell.Commands.RegisterPSSessionConfigurationCommand",
"RegisterPSSessionConfigurationCommand",
CompletionResultType.Type,
"Microsoft.PowerShell.Commands.RegisterPSSessionConfigurationCommand"));
completions.Add(
new CompletionResult(
"Microsoft.PowerShell.Commands.RegistryProviderSetItemDynamicParameter",
"RegistryProviderSetItemDynamicParameter",
CompletionResultType.Type,
"Microsoft.PowerShell.Commands.RegistryProviderSetItemDynamicParameter"));
break;
}
break;
}
//new CommandCompletion(completions, currentMatchIndex, replacementIndex, replacementLength);
return (CommandCompletion)ctor.Invoke(
new object[] {completions, currentMatchIndex, replacementIndex, replacementLength});
}
+153 -3
View File
@@ -159,7 +159,7 @@ PARAMETERS
Test("Get-MultiLineHelp -OneAndHalf", Keys(
"Get-MultiLineHelp -OneAndHalf", _.Alt_h,
CheckThat(() => AssertScreenIs(9,
CheckThat(() => AssertScreenIs(8,
TokenClassification.Command, "Get-MultiLineHelp",
TokenClassification.None, " ",
TokenClassification.Parameter, "-OneAndHalf",
@@ -172,8 +172,7 @@ PARAMETERS
TokenClassification.None, "60 characters but shorter than 120.",
NextLine,
TokenClassification.None, "Required: false, Position: 0, Default Value: None, Pipeline ",
NextLine,
"Input: True (ByPropertyName, ByValue), WildCard: false")),
TokenClassification.None, "Input: True (ByPropertyName, ByValue), WildCard: false")),
_.LeftArrow,
CheckThat(() => AssertScreenIs(1,
TokenClassification.Command, "Get-MultiLineHelp",
@@ -183,6 +182,157 @@ PARAMETERS
));
}
[SkippableFact]
public void DynHelp_GetParameterHelpMultiLine_And_Clear_WithScrolling1()
{
// This test case covers the fix to https://github.com/PowerShell/PSReadLine/issues/2950.
var basicScrollingConsole = new BasicScrollingConsole(keyboardLayout: _, width: 60, height: 10);
TestSetup(basicScrollingConsole, KeyMode.Cmd);
// Write 12 new-lines, so that the next input will be at the last line of the screen buffer.
basicScrollingConsole.Write(new string('\n', 12));
AssertCursorLeftTopIs(0, 9);
Test("Get-MultiLineHelp -OneAndHalf", Keys(
"Get-MultiLineHelp -OneAndHalf",
CheckThat(() => AssertCursorLeftTopIs(29, 9)),
_.Alt_h,
CheckThat(() => AssertCursorLeftTopIs(29, 2)),
CheckThat(() => AssertScreenIs(top: 12, lines: 8,
TokenClassification.Command, "Get-MultiLineHelp",
TokenClassification.None, " ",
TokenClassification.Parameter, "-OneAndHalf",
NextLine,
NextLine,
TokenClassification.None, $"-Date <name>",
NextLine,
NextLine,
TokenClassification.None, "DESC: Some very long description that is over the buffer width of ",
TokenClassification.None, "60 characters but shorter than 120.",
NextLine,
TokenClassification.None, "Required: false, Position: 0, Default Value: None, Pipeline ",
TokenClassification.None, "Input: True (ByPropertyName, ByValue), WildCard: false")),
_.Alt_h,
CheckThat(() => AssertCursorLeftTopIs(29, 2)),
CheckThat(() => AssertScreenIs(top: 12, lines: 8,
TokenClassification.Command, "Get-MultiLineHelp",
TokenClassification.None, " ",
TokenClassification.Parameter, "-OneAndHalf", NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine)),
_.Alt_h,
CheckThat(() => AssertCursorLeftTopIs(29, 2)),
CheckThat(() => AssertScreenIs(top: 12, lines: 8,
TokenClassification.Command, "Get-MultiLineHelp",
TokenClassification.None, " ",
TokenClassification.Parameter, "-OneAndHalf",
NextLine,
NextLine,
TokenClassification.None, $"-Date <name>",
NextLine,
NextLine,
TokenClassification.None, "DESC: Some very long description that is over the buffer width of ",
TokenClassification.None, "60 characters but shorter than 120.",
NextLine,
TokenClassification.None, "Required: false, Position: 0, Default Value: None, Pipeline ",
TokenClassification.None, "Input: True (ByPropertyName, ByValue), WildCard: false")),
_.LeftArrow,
CheckThat(() => AssertCursorLeftTopIs(29, 2)),
CheckThat(() => AssertScreenIs(top: 12, lines: 8,
TokenClassification.Command, "Get-MultiLineHelp",
TokenClassification.None, " ",
TokenClassification.Parameter, "-OneAndHalf", NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine)),
_.Enter),
resetCursor: false);
}
[SkippableFact]
public void DynHelp_GetParameterHelpMultiLine_And_Clear_WithScrolling2()
{
// This test case covers the new changes in 'RecomputeInitialCoords', to verify that the
// previous cursor position gets updated when scrolling happens.
var basicScrollingConsole = new BasicScrollingConsole(keyboardLayout: _, width: 60, height: 10);
TestSetup(basicScrollingConsole, KeyMode.Cmd);
// Write 12 new-lines, so that the next input will be at the last line of the screen buffer.
basicScrollingConsole.Write(new string('\n', 12));
AssertCursorLeftTopIs(0, 9);
Test("Get-MultiLineHelp -OneAndHalf", Keys(
"Get-MultiLineHelp -OneAndHalf",
CheckThat(() => AssertCursorLeftTopIs(29, 9)),
_.Alt_h,
CheckThat(() => AssertCursorLeftTopIs(29, 2)),
CheckThat(() => AssertScreenIs(top: 12, lines: 8,
TokenClassification.Command, "Get-MultiLineHelp",
TokenClassification.None, " ",
TokenClassification.Parameter, "-OneAndHalf",
NextLine,
NextLine,
TokenClassification.None, $"-Date <name>",
NextLine,
NextLine,
TokenClassification.None, "DESC: Some very long description that is over the buffer width of ",
TokenClassification.None, "60 characters but shorter than 120.",
NextLine,
TokenClassification.None, "Required: false, Position: 0, Default Value: None, Pipeline ",
TokenClassification.None, "Input: True (ByPropertyName, ByValue), WildCard: false")),
_.Escape,
CheckThat(() => AssertCursorLeftTopIs(29, 2)),
CheckThat(() => AssertScreenIs(top: 12, lines: 8,
TokenClassification.Command, "Get-MultiLineHelp",
TokenClassification.None, " ",
TokenClassification.Parameter, "-OneAndHalf", NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine)),
// Write more characters after clearing the inline help content, verify that the initial coordinates are up-to-date.
"abc",
CheckThat(() => AssertCursorLeftTopIs(32, 2)),
CheckThat(() => AssertScreenIs(top: 12, lines: 8,
TokenClassification.Command, "Get-MultiLineHelp",
TokenClassification.None, " ",
TokenClassification.Parameter, "-OneAndHalfabc", NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine)),
_.Backspace, _.Backspace, _.Backspace,
CheckThat(() => AssertCursorLeftTopIs(29, 2)),
CheckThat(() => AssertScreenIs(top: 12, lines: 8,
TokenClassification.Command, "Get-MultiLineHelp",
TokenClassification.None, " ",
TokenClassification.Parameter, "-OneAndHalf", NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine,
NextLine)),
_.Enter),
resetCursor: false);
}
[SkippableFact]
public void DynHelp_GetParameterHelpTwoLines_And_Clear()
{
+1 -1
View File
@@ -731,7 +731,7 @@ namespace Test
[SkippableFact]
public void Inline_TruncateVeryLongSuggestion()
{
TestSetup(new TestConsole(width: 10, height: 2, keyboardLayout: _), KeyMode.Cmd);
TestSetup(new TestConsole(keyboardLayout: _, width: 10, height: 2), KeyMode.Cmd);
using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView);
// Truncate long suggestion to make sure the user input is not scrolled up-off the console buffer.
+198 -51
View File
@@ -57,43 +57,54 @@ namespace Test
}
}
internal class TestConsole : IConsole
{
internal int index;
internal object[] inputOrValidateItems;
internal Exception validationFailure;
private readonly CHAR_INFO[] buffer;
private readonly int _bufferWidth;
private readonly int _bufferHeight;
private readonly int _windowWidth;
private readonly int _windowHeight;
private readonly dynamic _keyboardLayout;
private bool _ignoreNextNewline;
protected readonly CHAR_INFO[] buffer;
protected readonly int _bufferWidth;
protected readonly int _windowWidth;
protected readonly int _bufferHeight;
protected readonly int _windowHeight;
protected dynamic _keyboardLayout;
private bool _ignoreNextNewline;
private bool _negative;
private ConsoleColor _foregroundColor;
private ConsoleColor _backgroundColor;
/// <summary>
/// Use big enough window/buffer to avoid the need to implement scrolling.
/// </summary>
internal TestConsole(dynamic keyboardLayout)
: this(width: 60, height: 1000, mimicScrolling: false)
{
_keyboardLayout = keyboardLayout;
BackgroundColor = ReadLine.BackgroundColors[0];
ForegroundColor = ReadLine.Colors[0];
CursorLeft = 0;
CursorTop = 0;
_bufferWidth = _windowWidth = 60;
_bufferHeight = _windowHeight = 1000; // big enough to avoid the need to implement scrolling
buffer = new CHAR_INFO[BufferWidth * BufferHeight];
ClearBuffer();
}
internal TestConsole(int width, int height, dynamic keyboardLayout)
/// <summary>
/// Use specific window width and height without scrolling capability.
/// </summary>
internal TestConsole(dynamic keyboardLayout, int width, int height)
: this(width, height, mimicScrolling: false)
{
_keyboardLayout = keyboardLayout;
}
protected TestConsole(int width, int height, bool mimicScrolling)
{
BackgroundColor = ReadLine.BackgroundColors[0];
ForegroundColor = ReadLine.Colors[0];
CursorLeft = 0;
CursorTop = 0;
_bufferWidth = _windowWidth = width;
_bufferHeight = _windowHeight = height; // big enough to avoid the need to implement scrolling
buffer = new CHAR_INFO[BufferWidth * BufferHeight];
_bufferHeight = _windowHeight = height;
// Use a big enough buffer when we are mimicing scrolling.
int bufferSize = mimicScrolling ? BufferWidth * 1000 : BufferWidth * BufferHeight;
buffer = new CHAR_INFO[bufferSize];
ClearBuffer();
}
@@ -104,7 +115,7 @@ namespace Test
this.validationFailure = null;
}
public ConsoleKeyInfo ReadKey()
public virtual ConsoleKeyInfo ReadKey()
{
while (index < inputOrValidateItems.Length)
{
@@ -133,67 +144,63 @@ namespace Test
return _keyboardLayout.Ctrl_c;
}
public bool KeyAvailable => index < inputOrValidateItems.Length && inputOrValidateItems[index] is ConsoleKeyInfo;
public virtual bool KeyAvailable => index < inputOrValidateItems.Length && inputOrValidateItems[index] is ConsoleKeyInfo;
public int CursorLeft { get; set; }
public int CursorTop { get; set; }
public virtual int CursorLeft { get; set; }
public virtual int CursorTop { get; set; }
public int CursorSize { get; set; }
public bool CursorVisible { get; set; }
public virtual int CursorSize { get; set; }
public virtual bool CursorVisible { get; set; }
public int BufferWidth
public virtual int BufferWidth
{
get => _bufferWidth;
set => throw new NotImplementedException();
}
public int BufferHeight
public virtual int BufferHeight
{
get => _bufferHeight;
set => throw new NotImplementedException();
}
public int WindowWidth
public virtual int WindowWidth
{
get => _windowWidth;
set => throw new NotImplementedException();
}
public int WindowHeight
public virtual int WindowHeight
{
get => _windowHeight;
set => throw new NotImplementedException();
}
public int WindowTop { get; set; }
public virtual int WindowTop { get; set; }
public ConsoleColor BackgroundColor
public virtual ConsoleColor BackgroundColor
{
get => _backgroundColor;
set => _backgroundColor = Negative ? (ConsoleColor)((int)value ^ 7) : value;
set => _backgroundColor = _negative ? (ConsoleColor)((int)value ^ 7) : value;
}
private ConsoleColor _backgroundColor;
public ConsoleColor ForegroundColor
public virtual ConsoleColor ForegroundColor
{
get => _foregroundColor;
set => _foregroundColor = Negative ? (ConsoleColor)((int)value ^ 7) : value;
set => _foregroundColor = _negative ? (ConsoleColor)((int)value ^ 7) : value;
}
private ConsoleColor _foregroundColor;
public Encoding OutputEncoding
public virtual Encoding OutputEncoding
{
get => Encoding.Default;
set { }
}
private bool Negative;
public void SetWindowPosition(int left, int top)
public virtual void SetWindowPosition(int left, int top)
{
}
public void SetCursorPosition(int left, int top)
public virtual void SetCursorPosition(int left, int top)
{
if (left != CursorLeft || top != CursorTop)
{
@@ -204,7 +211,7 @@ namespace Test
CursorTop = top;
}
public void WriteLine(string s)
public virtual void WriteLine(string s)
{
// Crappy code here - no checks for a string that's too long, no scrolling.
Write(s);
@@ -212,8 +219,7 @@ namespace Test
CursorTop += 1;
}
static readonly char[] endEscapeChars = { 'm', 'J' };
public void Write(string s)
public virtual void Write(string s)
{
// Crappy code here - no checks for a string that's too long, no scrolling.
var writePos = CursorTop * BufferWidth + CursorLeft;
@@ -277,7 +283,7 @@ namespace Test
}
}
public void BlankRestOfLine()
public virtual void BlankRestOfLine()
{
var writePos = CursorTop * BufferWidth + CursorLeft;
for (int i = 0; i < BufferWidth - CursorLeft; i++)
@@ -288,13 +294,13 @@ namespace Test
}
}
public void Clear()
public virtual void Clear()
{
SetCursorPosition(0, 0);
ClearBuffer();
}
void ClearBuffer()
protected void ClearBuffer()
{
for (int i = 0; i < buffer.Length; i++)
{
@@ -310,17 +316,19 @@ namespace Test
return result;
}
protected static readonly char[] endEscapeChars = { 'm', 'J' };
private static readonly ConsoleColor DefaultForeground = ReadLine.Colors[0];
private static readonly ConsoleColor DefaultBackground = ReadLine.BackgroundColors[0];
private static void ToggleNegative(TestConsole c, bool b)
{
c.Negative = false;
c._negative = false;
c.ForegroundColor = (ConsoleColor)((int)c.ForegroundColor ^ 7);
c.BackgroundColor = (ConsoleColor)((int)c.BackgroundColor ^ 7);
c.Negative = b;
c._negative = b;
}
private static readonly Dictionary<string, Action<TestConsole>> EscapeSequenceActions = new Dictionary<string, Action<TestConsole>> {
protected static readonly Dictionary<string, Action<TestConsole>> EscapeSequenceActions = new()
{
{"7", c => ToggleNegative(c, true) },
{"27", c => ToggleNegative(c, false) },
{"40", c => c.BackgroundColor = ConsoleColor.Black},
@@ -364,5 +372,144 @@ namespace Test
{"2J", c => c.SetCursorPosition(0, 0) }
};
}
}
internal class BasicScrollingConsole : TestConsole
{
private int _offset;
internal BasicScrollingConsole(dynamic keyboardLayout, int width, int height)
: base(width, height, mimicScrolling: true)
{
_keyboardLayout = keyboardLayout;
_offset = 0;
}
private void AdjustCursorWhenNeeded()
{
// If the last character written out happened to be in the last cell
// of a physical line, we need to adjust the cursor.
if (CursorLeft == BufferWidth)
{
CursorLeft = 0;
CursorTop++;
}
// After adjusting the cursor, we may need to handle scrolling in case
// that the cursor top went beyond the buffer height.
if (CursorTop == BufferHeight)
{
_offset++;
CursorTop--;
}
}
public override void SetCursorPosition(int left, int top)
{
if (left < 0 || left >= _bufferWidth)
{
throw new ArgumentOutOfRangeException(nameof(left), $"Value should be >= 0 and < BufferWidth({_bufferWidth}), but it's {left}.");
}
if (top < 0 || top >= _bufferHeight)
{
throw new ArgumentOutOfRangeException(nameof(top), $"Value should be >= 0 and < BufferHeight({_bufferHeight}), but it's {top}.");
}
CursorLeft = left;
CursorTop = top;
}
public override void WriteLine(string s)
{
Write(s);
CursorLeft = 0;
CursorTop += 1;
AdjustCursorWhenNeeded();
}
public override void Write(string s)
{
// Crappy code here - no checks for a string that's too long, basic scrolling handling.
var writePos = (_offset + CursorTop) * BufferWidth + CursorLeft;
for (int i = 0; i < s.Length; i++)
{
if (s[i] == (char)0x1b)
{
// Escape sequence - limited support here, and assumed to be well formed.
if (s[i + 1] != '[') throw new ArgumentException("Unexpected escape sequence", nameof(s));
var endSequence = s.IndexOfAny(endEscapeChars, i);
var len = endSequence - i - (s[endSequence] != 'm' ? 1 : 2);
var escapeSequence = s.Substring(i + 2, len);
foreach (var subsequence in escapeSequence.Split(';'))
{
EscapeSequenceActions[subsequence](this);
}
i = endSequence;
continue;
}
if (s[i] == '\b')
{
CursorLeft -= 1;
if (CursorLeft < 0)
{
CursorTop -= 1;
CursorLeft = BufferWidth - 1;
}
}
else if (s[i] == '\n')
{
CursorTop += 1;
CursorLeft = 0;
// Explicitly writing a new-line may trigger scrolling.
AdjustCursorWhenNeeded();
writePos = (_offset + CursorTop) * BufferWidth;
}
else
{
AdjustCursorWhenNeeded();
CursorLeft += 1;
// When 'CursorLeft == BufferWidth', it means the current character will take up the last cell in the current physical line.
// Assuming the current physical line is 'Y'.
// In such a case, Windows Terminal will set the cursor position to be the following after writing out the character in the
// last cell of the current physical line:
// CursorLeft: BufferWidth - 1
// CursorTop: Y
// It doesn't directly set the cursor to be (0, Y+1) in this case, but when there are more visible characters (non-control chars)
// to be written, it will automatically adjust the cursor position to be (2, Y+1) after writing out the next visible character.
//
// This behavior makes handling the new-line character '\n' much easier -- you can simply set the cursor to be (0, top+1), and
// then take care of scrolling if needed.
// So, we mimic that behavior here: we allow 'CursorLeft' to be 'BufferWidth' after 'CursorLeft += 1' above, and we adjust the
// cursor when we are about to write a new visible character.
buffer[writePos].UnicodeChar = s[i];
buffer[writePos].BackgroundColor = BackgroundColor;
buffer[writePos].ForegroundColor = ForegroundColor;
writePos += 1;
}
}
}
public override void BlankRestOfLine()
{
var writePos = (_offset + CursorTop) * BufferWidth + CursorLeft;
for (int i = 0; i < BufferWidth - CursorLeft; i++)
{
buffer[writePos + i].UnicodeChar = ' ';
buffer[writePos + i].BackgroundColor = BackgroundColor;
buffer[writePos + i].ForegroundColor = ForegroundColor;
}
}
public override void Clear()
{
_offset = 0;
base.Clear();
}
}
}
+6 -1
View File
@@ -429,7 +429,12 @@ namespace Test
private void AssertScreenIs(int lines, params object[] items)
{
var consoleBuffer = _console.ReadBufferLines(0, lines);
AssertScreenIs(top: 0, lines, items);
}
private void AssertScreenIs(int top, int lines, params object[] items)
{
var consoleBuffer = _console.ReadBufferLines(top, lines);
var expectedBuffer = CreateCharInfoBuffer(lines, items);
Assert.Equal(expectedBuffer.Length, consoleBuffer.Length);