using Microsoft.PowerShell; using System; using System.Linq; using System.Runtime.InteropServices; using Xunit; namespace Test { public partial class ReadLine { [SkippableFact] public void Input() { TestSetup(KeyMode.Cmd); Test("exit", Keys( "exit", _.Enter, CheckThat(() => AssertCursorLeftIs(0)))); } [SkippableFact] public void RevertLine() { // Add one test for chords TestSetup(KeyMode.Cmd, new KeyHandler("Ctrl+x,Escape", PSConsoleReadLine.RevertLine)); Test("ls", Keys("di", _.Escape, "ls")); Test("ls", Keys("di", _.Ctrl_x, _.Escape, "ls")); TestSetup(KeyMode.Emacs); Test("ls", Keys("di", _.Escape, _.r, "ls")); Test("ls", Keys("di", _.Alt_r, "ls")); } [SkippableFact] public void CancelLine() { TestSetup(KeyMode.Cmd, new KeyHandler("Ctrl+C", PSConsoleReadLine.CancelLine)); Test("", Keys("oops", _.Ctrl_C, CheckThat(() => AssertScreenIs(1, TokenClassification.Command, "oops", Tuple.Create(ConsoleColor.Red, _console.BackgroundColor), "^C")), InputAcceptedNow)); Test("", Keys("exit", _.Ctrl_C, CheckThat(() => AssertScreenIs(1, TokenClassification.Keyword, "exit", Tuple.Create(ConsoleColor.Red, _console.BackgroundColor), "^C")), InputAcceptedNow)); // Test near/at/over buffer width input var width = _console.BufferWidth; var line1 = new string('a', width - 2); Test("", Keys(line1, _.Ctrl_C, CheckThat(() => AssertScreenIs(1, TokenClassification.Command, line1, Tuple.Create(ConsoleColor.Red, _console.BackgroundColor), "^C")), InputAcceptedNow)); var line2 = new string('a', width - 1); Test("", Keys(line2, _.Ctrl_C, CheckThat(() => AssertScreenIs(2, TokenClassification.Command, line2, Tuple.Create(ConsoleColor.Red, _console.BackgroundColor), "^C")), InputAcceptedNow)); var line3 = new string('a', width); Test("", Keys(line3, _.Ctrl_C, CheckThat(() => AssertScreenIs(2, TokenClassification.Command, line3, Tuple.Create(ConsoleColor.Red, _console.BackgroundColor), "^C")), InputAcceptedNow)); } [SkippableFact] public void ForwardDeleteInput() { ConsoleKeyInfo deleteToEnd; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { TestSetup(KeyMode.Cmd); deleteToEnd = _.Ctrl_End; } else { TestSetup(KeyMode.Emacs); deleteToEnd = _.Ctrl_k; } // Empty input (does nothing but don't crash) Test("", Keys("", deleteToEnd)); // at end of input - doesn't change anything Test("abc", Keys("abc", deleteToEnd)); // More normal usage - actually delete stuff Test("a", Keys("abc", _.LeftArrow, _.LeftArrow, deleteToEnd)); } [SkippableFact] public void ForwardDeleteLine() { TestSetup(KeyMode.Emacs); PSConsoleReadLine.SetKeyHandler(new[] { "Shift+Tab" }, PSConsoleReadLine.ForwardDeleteLine, "", ""); Test("\"H\nWorld\"", Keys( _.DQuote, "Hello", _.Enter, "World", _.DQuote, _.Home, _.Home, _.RightArrow, _.RightArrow, _.Shift_Tab // delete to the end of the current logical line )); } [SkippableFact] public void BackwardDeleteInput() { TestSetup(KeyMode.Cmd); // Empty input (does nothing but don't crash) Test("", Keys(_.Ctrl_Home)); // at beginning of input - doesn't change anything Test("abc", Keys("abc", _.Home, _.Ctrl_Home)); // More typical usage Test("c", Keys("abc", _.LeftArrow, _.Ctrl_Home)); Test("", Keys("abc", _.Ctrl_Home)); } [SkippableFact] public void BackwardDeleteChar() { TestSetup(KeyMode.Cmd); // Empty input (does nothing but don't crash) Test("", Keys("", _.Backspace)); // At end, delete all input Test("", Keys("a", _.Backspace)); // At end, delete all input with extra backspaces Test("", Keys("a", _.Backspace, _.Backspace)); // Delete first character Test("b", Keys("ab", _.LeftArrow, _.Backspace)); // Delete first character with extra backspaces Test("b", Keys("ab", _.LeftArrow, _.Backspace, _.Backspace)); // Delete middle character Test("ac", Keys("abc", _.LeftArrow, _.Backspace)); } [SkippableFact] public void DeleteChar() { TestSetup(KeyMode.Cmd); // Empty input (does nothing, but don't crash) Test("", Keys(_.Delete)); // At end but input not empty (does nothing, but don't crash) Test("a", Keys('a', _.Delete)); // Delete last character Test("a", Keys("ab", _.LeftArrow, _.Delete)); // Delete first character Test("b", Keys("ab", _.Home, _.Delete)); // Delete middle character Test("ac", Keys("abc", _.Home, _.RightArrow, _.Delete)); } [SkippableFact] public void DeleteCharAfterDigitArgument() { TestSetup(KeyMode.Cmd); Test("abc", Keys( "ab", _.LeftArrow, _.Alt_2, _.Delete, CheckThat(() => AssertLineIs("a")), CheckThat(() => AssertCursorLeftIs(1)), _.Delete, // 'Delete' again does nothing, but doesn't crash CheckThat(() => AssertLineIs("a")), CheckThat(() => AssertCursorLeftIs(1)), "bc")); } [SkippableFact] public void DeleteCharOrExit() { TestSetup(KeyMode.Emacs); Test("exit", Keys(_.Ctrl_d, InputAcceptedNow)); Test("foo", Keys("foo", _.Ctrl_d)); Test("oo", Keys("foo", _.Home, _.Ctrl_d)); Test("exit", Keys("foo", _.Home, Enumerable.Repeat(_.Ctrl_d, 4), InputAcceptedNow)); } [SkippableFact] public void UpcaseWord() { TestSetup(KeyMode.Emacs); Test("FOO", Keys("foo", _.Alt_b, _.Alt_u)); Test("FOO bar", Keys("foo bar", _.Home, _.Alt_u)); Test("foo BAR", Keys("foo bar", _.Alt_b, _.Alt_u)); Test("FOO BAR", Keys("foo bar", _.Home, Enumerable.Repeat(_.Alt_u, 2))); } [SkippableFact] public void DowncaseWord() { TestSetup(KeyMode.Emacs); Test("foo", Keys("fOO", _.Alt_b, _.Alt_l)); Test("FOO bar", Keys("FOO BAR", _.Alt_b, _.Alt_l)); Test("foo BAR", Keys("FOO BAR", _.Home, _.Alt_l)); Test("foo bar", Keys("FOO BAR", _.Home, Enumerable.Repeat(_.Alt_l, 2))); } [SkippableFact] public void CapitalizeWord() { TestSetup(KeyMode.Emacs); Test("Foo", Keys("fOO", _.Alt_b, _.Alt_c)); Test("fOO Bar", Keys("fOO bAR", _.Alt_b, _.Alt_c)); Test("Foo BAR", Keys("fOO BAR", _.Home, _.Alt_c)); Test("Foo Bar", Keys("fOO BAR", _.Home, Enumerable.Repeat(_.Alt_c, 2))); Test("Foo ^&*() Bar", Keys("Foo ^&*() bar", _.Home, Enumerable.Repeat(_.Alt_c, 2))); } [SkippableFact] public void SelectAndDelete() { TestSetup(KeyMode.Cmd); Test("abcde", Keys( "abcde", CheckThat(() => AssertCursorLeftIs(5)), _.Shift_LeftArrow, _.Shift_LeftArrow, _.Shift_LeftArrow, _.Backspace, CheckThat(() => AssertLineIs("ab")), CheckThat(() => AssertCursorLeftIs(2)), _.Ctrl_z, CheckThat(() => AssertLineIs("abcde")), CheckThat(() => AssertCursorLeftIs(5)))); Test("abcde", Keys( "abcde", _.Home, CheckThat(() => AssertCursorLeftIs(0)), _.RightArrow, _.RightArrow, CheckThat(() => AssertCursorLeftIs(2)), _.Shift_RightArrow, _.Shift_RightArrow, _.Delete, CheckThat(() => AssertLineIs("abe")), CheckThat(() => AssertCursorLeftIs(2)), _.Ctrl_z, CheckThat(() => AssertLineIs("abcde")), CheckThat(() => AssertCursorLeftIs(4)))); } [SkippableFact] public void SwapCharacters() { TestSetup(KeyMode.Emacs); TestMustDing("", Keys(_.Ctrl_t)); TestMustDing("a", Keys("a", _.Ctrl_t)); TestMustDing("abc", Keys("abc", _.Home, _.Ctrl_t)); Test("abc", Keys( "abc", CheckThat(() => AssertLineIs("abc")), _.Ctrl_t, CheckThat(() => AssertLineIs("acb")), _.Ctrl_Underbar, CheckThat(() => AssertCursorLeftIs(3)) )); Test("abcd", Keys( "abcd", CheckThat(() => AssertLineIs("abcd")), _.Ctrl_a, _.Ctrl_t, CheckThat(() => AssertLineIs("abcd")), _.Ctrl_f, Enumerable.Repeat(_.Ctrl_t, 3), CheckThat(() => AssertLineIs("bcda")), _.Ctrl_t, CheckThat(() => AssertLineIs("bcad")), Enumerable.Repeat(_.Ctrl_Underbar, 4) )); } [SkippableFact] public void AcceptAndGetNext() { TestSetup(KeyMode.Emacs); // No history Test("", Keys(_.Ctrl_o, InputAcceptedNow)); // One item in history SetHistory("echo 1"); Test("", Keys(_.Ctrl_o, InputAcceptedNow)); // Two items in history, make sure after Ctrl+O, second history item // is recalled. SetHistory("echo 1", "echo 2"); Test("echo 1", Keys(_.UpArrow, _.UpArrow, _.Ctrl_o, InputAcceptedNow)); Test("echo 2", Keys(_.Enter)); // Test that the current saved line is saved after AcceptAndGetNext SetHistory("echo 1", "echo 2"); Test("echo 1", Keys("e", _.UpArrow, _.UpArrow, _.Ctrl_o, InputAcceptedNow)); Test("e", Keys(_.DownArrow, _.DownArrow, _.Enter)); // Test that we can edit after recalling the current line SetHistory("echo 1", "echo 2"); Test("echo 1", Keys("e", _.UpArrow, _.UpArrow, _.Ctrl_o, InputAcceptedNow)); Test("eee", Keys(_.DownArrow, _.DownArrow, "ee", _.Enter)); } [SkippableFact] public void AcceptAndGetNextWithHistorySearch() { TestSetup(KeyMode.Emacs, new KeyHandler("UpArrow", PSConsoleReadLine.HistorySearchBackward), new KeyHandler("DownArrow", PSConsoleReadLine.HistorySearchForward)); // Test that after AcceptAndGetNext, the previous search is not applied SetHistory("echo 1", "echo 2", "zzz"); Test("echo 1", Keys("e", _.UpArrow, _.UpArrow, _.Ctrl_o, InputAcceptedNow)); Test("zzz", Keys(_.DownArrow, _.Enter)); } [SkippableFact] public void AddLine() { TestSetup(KeyMode.Cmd); Test("1\n2", Keys('1', _.Shift_Enter, '2')); } [SkippableFact] public void InsertLineAbove() { TestSetup(KeyMode.Cmd); // Test case - start with single line, cursor at end Test("56\n1234", Keys("1234", _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(0, 0)), "56")); // Test case - start with single line, cursor in home position Test("56\n1234", Keys("1234", _.Home, _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(0, 0)), "56")); // Test case - start with single line, cursor in middle Test("56\n1234", Keys("1234", _.LeftArrow, _.LeftArrow, _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(0, 0)), "56")); // Test case - start with multi-line, cursor at end of second line (end of input) Test("1234\n9ABC\n5678", Keys("1234", _.Shift_Enter, "5678", _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "9ABC")); // Test case - start with multi-line, cursor at beginning of second line Test("1234\n9ABC\n5678", Keys("1234", _.Shift_Enter, "5678", _.LeftArrow, _.Home, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "9ABC")); // Test case - start with multi-line, cursor at end of first line Test("9ABC\n1234\n5678", Keys("1234", _.Shift_Enter, "5678", _.UpArrow, _.LeftArrow, _.End, CheckThat(() => AssertCursorLeftTopIs(4, 0)), _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(0, 0)), "9ABC")); // Test case - start with multi-line, cursor at beginning of first line - temporarily having to press Home twice to // work around bug in home handler. Test("9ABC\n1234\n5678", Keys("1234", _.Shift_Enter, "5678", _.UpArrow, _.LeftArrow, _.Home, _.Home, CheckThat(() => AssertCursorLeftTopIs(0, 0)), _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(0, 0)), "9ABC")); // Test case - insert multiple blank lines Test("1234\n9ABC\n\n5678", Keys("1234", _.Shift_Enter, "5678", _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "9ABC")); // Test case - create leading blank line, cursor to stay on same line Test("\n\n1234", Keys("1234", _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(0,0)), _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)))); } [SkippableFact] public void InsertLineBelow() { TestSetup(KeyMode.Cmd); // Test case - start with single line, cursor at end Test("1234\n56", Keys("1234", _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "56")); // Test case - start with single line, cursor in home position Test("1234\n56", Keys("1234", _.Home, _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "56")); // Test case - start with single line, cursor in middle Test("1234\n56", Keys("1234", _.LeftArrow, _.LeftArrow, _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "56")); // Test case - start with multi-line, cursor at end of second line (end of input) Test("1234\n5678\n9ABC", Keys("1234", _.Shift_Enter, "5678", _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 2)), "9ABC")); // Test case - start with multi-line, cursor at beginning of second line Test("1234\n5678\n9ABC", Keys("1234", _.Shift_Enter, "5678", _.LeftArrow, _.Home, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 2)), "9ABC")); // Test case - start with multi-line, cursor at end of first line Test("1234\n9ABC\n5678", Keys("1234", _.Shift_Enter, "5678", _.UpArrow, _.LeftArrow, _.End, CheckThat(() => AssertCursorLeftTopIs(4, 0)), _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "9ABC")); // Test case - start with multi-line, cursor at beginning of first line - temporarily having to press Home twice to // work around bug in home handler. Test("1234\n9ABC\n5678", Keys("1234", _.Shift_Enter, "5678", _.UpArrow, _.LeftArrow, _.Home, _.Home, CheckThat(() => AssertCursorLeftTopIs(0, 0)), _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "9ABC")); // Test case - insert multiple blank lines Test("1234\n5678\n\n9ABC", Keys("1234", _.Shift_Enter, "5678", _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 2)), _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 3)), "9ABC")); } [SkippableFact] public void MultilineHomeBugFixed() { TestSetup(KeyMode.Cmd); // Going from second line to first line, press left arrow and then home. // That puts cursor in column 1 instead of 0. Bug? This could have something // to do with BeginningOfLine testing against i > 1 in multiline edit instead // of i > 0. Test("1234\n9ABC", Keys("1234", _.Shift_Enter, "9ABC", _.UpArrow, _.LeftArrow, _.Home, CheckThat(() => AssertCursorLeftTopIs(0, 0)))); } [SkippableFact] public void Ignore() { TestSetup(KeyMode.Emacs); Test("ab", Keys("a", _.VolumeDown, _.VolumeMute, _.VolumeUp, "b")); } } }