mirror of
https://github.com/PowerShell/PSReadLine
synced 2026-06-08 12:12:13 +00:00
Add BasicScrollingConsole to support test cases for scrolling up scenarios (#3081)
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user