diff --git a/PSReadLine/Cmdlets.cs b/PSReadLine/Cmdlets.cs index d3686b1..ca86561 100644 --- a/PSReadLine/Cmdlets.cs +++ b/PSReadLine/Cmdlets.cs @@ -190,13 +190,13 @@ function global:PSConsoleHostReadline public class SetKeyHandlerCommand : PSCmdlet { [Parameter(Position = 0, Mandatory = true)] - [ValidateNotNull] - [ConsoleKeyInfoConverter] - public ConsoleKeyInfo Key { get; set; } + [Alias("Key")] + [ValidateNotNullOrEmpty] + public string[] Chord { get; set; } [Parameter(Position = 1, Mandatory = true)] [ValidateNotNull] - public Action Handler { get; set; } + public Action Handler { get; set; } [Parameter(Mandatory = true)] public string BriefDescription { get; set; } @@ -204,13 +204,10 @@ function global:PSConsoleHostReadline [Parameter] public string LongDescription { get; set; } - [Parameter] - public SwitchParameter CtrlXPrefix { get; set; } - [ExcludeFromCodeCoverage] protected override void EndProcessing() { - PSConsoleReadLine.SetKeyHandler(Key, CtrlXPrefix.IsPresent, Handler, BriefDescription, LongDescription); + PSConsoleReadLine.SetKeyHandler(Chord, Handler, BriefDescription, LongDescription); } } diff --git a/PSReadLine/ConsoleKeyInfoConverterAttribute.cs b/PSReadLine/ConsoleKeyChordConverter.cs similarity index 87% rename from PSReadLine/ConsoleKeyInfoConverterAttribute.cs rename to PSReadLine/ConsoleKeyChordConverter.cs index f696b1a..1d2fc00 100644 --- a/PSReadLine/ConsoleKeyInfoConverterAttribute.cs +++ b/PSReadLine/ConsoleKeyChordConverter.cs @@ -2,25 +2,47 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Management.Automation; using System.Runtime.InteropServices; using System.Text; namespace PSConsoleUtilities { - // $c = new-object PSConsoleUtilities.ConsoleKeyInfoConverterAttribute - [AttributeUsage(AttributeTargets.Property)] - public class ConsoleKeyInfoConverterAttribute : ArgumentTransformationAttribute + public class ConsoleKeyChordConverter { - public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) + /// + /// Converts a string to a sequence of ConsoleKeyInfo. + /// Sequences are separated by ','. Modifiers + /// appear before the modified key and are separated by '+'. + /// Examples: + /// Ctrl+X + /// Ctrl+D,M + /// Escape,X + /// + public static ConsoleKeyInfo[] Convert(string chord) { - if (!(inputData is string)) + if (string.IsNullOrEmpty(chord)) { - // pass through - return inputData; + throw new ArgumentNullException("chord"); } - - var sequence = (string)inputData; + + var tokens = chord.Split(new[] {','}); + + if (tokens.Length > 2) + { + throw new ArgumentException("Chord can have at most two keys"); + } + + var result = new ConsoleKeyInfo[tokens.Length]; + for (int i = 0; i < tokens.Length; i++) + { + result[i] = ConvertOneSequence(tokens[i]); + } + + return result; + } + + private static ConsoleKeyInfo ConvertOneSequence(string sequence) + { Stack tokens = null; ConsoleModifiers modifiers = 0; ConsoleKey key = 0; diff --git a/PSReadLine/PSReadLine.csproj b/PSReadLine/PSReadLine.csproj index 1d371df..1e56bd6 100644 --- a/PSReadLine/PSReadLine.csproj +++ b/PSReadLine/PSReadLine.csproj @@ -49,7 +49,7 @@ - + @@ -78,4 +78,4 @@ --> - \ No newline at end of file + diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs index 9e6579e..8a2a94c 100644 --- a/PSReadLine/ReadLine.cs +++ b/PSReadLine/ReadLine.cs @@ -65,11 +65,24 @@ namespace PSConsoleUtilities class KeyHandler { - public Action Action; + // Each key handler will be passed 2 arguments. Most will ignore these arguments, + // but having a consistent signature greatly simplifies dispatch. Defaults + // should be included on all handlers that ignore their parameters so they + // can be called from PowerShell without passing anything. + // + // The first arugment is the key that caused the action to be called + // (the second key when it's a 2 key chord). The default is null (it's nullable) + // because PowerShell can't handle default(ConsoleKeyInfo) as a default. + // Most actions will ignore this argument. + // + // The second argument is an arbitrary object. It will usually be either a number + // (e.g. as a repeat count) or a string. Most actions will ignore this argument. + public Action Action; public string BriefDescription; public string LongDescription; } - static KeyHandler MakeKeyHandler(Action action, string briefDescription, string longDescription = null) + + static KeyHandler MakeKeyHandler(Action action, string briefDescription, string longDescription = null) { return new KeyHandler { @@ -85,6 +98,7 @@ namespace PSConsoleUtilities private static readonly Dictionary _cmdKeyMap; private Dictionary _dispatchTable; + private Dictionary> _chordDispatchTable; private Dictionary _dispatchCtrlXTable; private Dictionary _dispatchMetaTable; @@ -310,7 +324,7 @@ namespace PSConsoleUtilities KeyHandler handler; if (dispatchTable.TryGetValue(key, out handler)) { - handler.Action(); + handler.Action(key, 0); } else if (!ignoreIfNoAction && key.KeyChar != 0) { @@ -380,7 +394,7 @@ namespace PSConsoleUtilities { Keys.DownArrow, MakeKeyHandler(NextHistory, "NextHistory") }, { Keys.Home, MakeKeyHandler(BeginningOfLine, "BeginningOfLine") }, { Keys.End, MakeKeyHandler(EndOfLine, "EndOfLine") }, - { Keys.Escape, MakeKeyHandler(EmacsMeta, "EmacsMeta") }, + { Keys.Escape, MakeKeyHandler(Chord, "ChordFirstKey") }, { Keys.Delete, MakeKeyHandler(DeleteChar, "DeleteChar") }, { Keys.Tab, MakeKeyHandler(Complete, "Complete") }, { Keys.CtrlA, MakeKeyHandler(BeginningOfLine, "BeginningOfLine") }, @@ -393,7 +407,7 @@ namespace PSConsoleUtilities { Keys.CtrlK, MakeKeyHandler(KillLine, "KillLine") }, { Keys.CtrlM, MakeKeyHandler(AcceptLine, "AcceptLine") }, { Keys.CtrlU, MakeKeyHandler(BackwardKillLine, "BackwardKillLine") }, - { Keys.CtrlX, MakeKeyHandler(EmacsCtrlX, "EmacsCtrlX") }, + { Keys.CtrlX, MakeKeyHandler(Chord, "ChordFirstKey") }, { Keys.CtrlY, MakeKeyHandler(Yank, "Yank") }, { Keys.CtrlAt, MakeKeyHandler(SetMark, "SetMark") }, { Keys.AltB, MakeKeyHandler(EmacsBackwardWord, "EmacsBackwardWord") }, @@ -431,6 +445,7 @@ namespace PSConsoleUtilities private PSConsoleReadLine() { _dispatchTable = new Dictionary(_cmdKeyMap); + _chordDispatchTable = new Dictionary>(); _buffer = new StringBuilder(); @@ -501,19 +516,21 @@ namespace PSConsoleUtilities _consoleBuffer = ReadBufferLines(_initialY, 1 + _extraPromptLineCount); } - private static void EmacsMeta() + private static void Chord(ConsoleKeyInfo? key = null, object arg = null) { - var key = ReadKey(); - _singleton.ProcessOneKey(key, _singleton._dispatchMetaTable, ignoreIfNoAction: true); + if (!key.HasValue) + { + throw new ArgumentNullException("key"); + } + Dictionary secondKeyDispatchTable; + if (_singleton._chordDispatchTable.TryGetValue(key.Value, out secondKeyDispatchTable)) + { + var secondKey = ReadKey(); + _singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true); + } } - private static void EmacsCtrlX() - { - var key = ReadKey(); - _singleton.ProcessOneKey(key, _singleton._dispatchCtrlXTable, ignoreIfNoAction: true); - } - - private static void Ignore() + private static void Ignore(ConsoleKeyInfo? key = null, object arg = null) { } @@ -527,7 +544,7 @@ namespace PSConsoleUtilities /// /// Reverts all of the input to the current input. /// - public static void RevertLine() + public static void RevertLine(ConsoleKeyInfo? key = null, object arg = null) { _singleton.RevertLine(supportUndo: true); } @@ -536,7 +553,7 @@ namespace PSConsoleUtilities /// Cancel the current input, leaving the input on the screen, /// but returns back to the host so the prompt is evaluated again. /// - public static void CancelLine() + public static void CancelLine(ConsoleKeyInfo? key = null, object arg = null) { // Clear out the input so it doesn't get executed, but // don't call render so the screen is left alone. @@ -547,7 +564,7 @@ namespace PSConsoleUtilities /// /// Delete the character before the cursor. /// - public static void BackwardDeleteChar() + public static void BackwardDeleteChar(ConsoleKeyInfo? key = null, object arg = null) { if (_singleton._buffer.Length > 0 && _singleton._current > 0) { @@ -560,7 +577,7 @@ namespace PSConsoleUtilities /// /// Delete the character under the cursor. /// - public static void DeleteChar() + public static void DeleteChar(ConsoleKeyInfo? key = null, object arg = null) { if (_singleton._buffer.Length > 0 && _singleton._current < _singleton._buffer.Length) { @@ -575,7 +592,7 @@ namespace PSConsoleUtilities /// continuation prompt is displayed on the next line and PSReadline waits for /// keys to edit the current input. /// - public static void AcceptLine() + public static void AcceptLine(ConsoleKeyInfo? key = null, object arg = null) { _singleton.ParseInput(); if (_singleton._parseErrors.Any(e => e.IncompleteInput)) @@ -595,7 +612,7 @@ namespace PSConsoleUtilities /// keys to edit the current input. This is useful to enter multi-line input as /// a single command even when a single line is complete input by itself. /// - public static void AddLine() + public static void AddLine(ConsoleKeyInfo? key = null, object arg = null) { _singleton.Insert('\n'); } @@ -603,7 +620,7 @@ namespace PSConsoleUtilities /// /// Paste text from the system clipboard. /// - public static void Paste() + public static void Paste(ConsoleKeyInfo? key = null, object arg = null) { if (System.Windows.Clipboard.ContainsText()) { @@ -620,7 +637,7 @@ namespace PSConsoleUtilities /// /// Move the cursor to the end of the input. /// - public static void EndOfLine() + public static void EndOfLine(ConsoleKeyInfo? key = null, object arg = null) { _singleton.MoveCursor(_singleton._buffer.Length); } @@ -628,7 +645,7 @@ namespace PSConsoleUtilities /// /// Move the cursor to the end of the input. /// - public static void BeginningOfLine() + public static void BeginningOfLine(ConsoleKeyInfo? key = null, object arg = null) { _singleton.MoveCursor(0); } @@ -637,7 +654,7 @@ namespace PSConsoleUtilities /// Move the cursor one character to the right. This may move the cursor to the next /// line of multi-line input. /// - public static void ForwardChar() + public static void ForwardChar(ConsoleKeyInfo? key = null, object arg = null) { if (_singleton._current < _singleton._buffer.Length) { @@ -650,7 +667,7 @@ namespace PSConsoleUtilities /// Move the cursor one character to the left. This may move the cursor to the previous /// line of multi-line input. /// - public static void BackwardChar() + public static void BackwardChar(ConsoleKeyInfo? key = null, object arg = null) { if (_singleton._current > 0 && (_singleton._current - 1 < _singleton._buffer.Length)) { @@ -688,7 +705,7 @@ namespace PSConsoleUtilities /// Move the cursor forward to the end of the current word, or if between words, /// to the end of the next word. /// - public static void EmacsForwardWord() + public static void EmacsForwardWord(ConsoleKeyInfo? key = null, object arg = null) { _singleton.ForwardWord(EditMode.Emacs); } @@ -696,7 +713,7 @@ namespace PSConsoleUtilities /// /// Move the cursor forward to the start of the next word. /// - public static void ForwardWord() + public static void ForwardWord(ConsoleKeyInfo? key = null, object arg = null) { _singleton.ForwardWord(EditMode.Windows); } @@ -713,7 +730,7 @@ namespace PSConsoleUtilities /// Move the cursor back to the start of the current word, or if between words, /// the start of the previous word. /// - public static void BackwardWord() + public static void BackwardWord(ConsoleKeyInfo? key = null, object arg = null) { _singleton.BackwardWord(EditMode.Windows); } @@ -722,7 +739,7 @@ namespace PSConsoleUtilities /// Move the cursor back to the start of the current word, or if between words, /// the start of the previous word. /// - public static void EmacsBackwardWord() + public static void EmacsBackwardWord(ConsoleKeyInfo? key = null, object arg = null) { _singleton.BackwardWord(EditMode.Emacs); } @@ -754,7 +771,7 @@ namespace PSConsoleUtilities /// /// Replace the current input with the 'previous' item from PSReadline history. /// - public static void PreviousHistory() + public static void PreviousHistory(ConsoleKeyInfo? key = null, object arg = null) { if (_singleton._currentHistoryIndex > 0) { @@ -766,7 +783,7 @@ namespace PSConsoleUtilities /// /// Replace the current input with the 'next' item from PSReadline history. /// - public static void NextHistory() + public static void NextHistory(ConsoleKeyInfo? key = null, object arg = null) { if (_singleton._currentHistoryIndex < (_singleton._history.Count - 1)) { @@ -799,7 +816,7 @@ namespace PSConsoleUtilities /// Replace the current input with the 'previous' item from PSReadline history /// that matches the characters between the start and the input and the cursor. /// - public static void HistorySearchBackward() + public static void HistorySearchBackward(ConsoleKeyInfo? key = null, object arg = null) { _singleton.HistorySearch(backward: true); } @@ -808,7 +825,7 @@ namespace PSConsoleUtilities /// Replace the current input with the 'next' item from PSReadline history /// that matches the characters between the start and the input and the cursor. /// - public static void HistorySearchForward() + public static void HistorySearchForward(ConsoleKeyInfo? key = null, object arg = null) { _singleton.HistorySearch(backward: false); } @@ -817,7 +834,7 @@ namespace PSConsoleUtilities /// Attempt to complete the text surrounding the cursor with the next /// available completion. /// - public static void TabCompleteNext() + public static void TabCompleteNext(ConsoleKeyInfo? key = null, object arg = null) { _singleton.Complete(forward: true); } @@ -826,7 +843,7 @@ namespace PSConsoleUtilities /// Attempt to complete the text surrounding the cursor with the previous /// available completion. /// - public static void TabCompletePrevious() + public static void TabCompletePrevious(ConsoleKeyInfo? key = null, object arg = null) { _singleton.Complete(forward: false); } @@ -870,7 +887,7 @@ namespace PSConsoleUtilities /// prefix is used for completion. If trying to complete the longest /// unambiguous completion, a list of possible completions is displayed. /// - public static void Complete() + public static void Complete(ConsoleKeyInfo? key = null, object arg = null) { var completions = _singleton.GetCompletions(); if (completions == null || completions.CompletionMatches.Count == 0) @@ -993,7 +1010,7 @@ namespace PSConsoleUtilities /// /// Display the list of possible completions. /// - public static void PossibleCompletions() + public static void PossibleCompletions(ConsoleKeyInfo? key = null, object arg = null) { var completions = _singleton.GetCompletions(); if (completions == null || completions.CompletionMatches.Count == 0) @@ -1063,7 +1080,7 @@ namespace PSConsoleUtilities /// /// Mark the current loction of the cursor for use in a subsequent editing command. /// - public static void SetMark() + public static void SetMark(ConsoleKeyInfo? key = null, object arg = null) { _singleton._mark = _singleton._current; } @@ -1072,7 +1089,7 @@ namespace PSConsoleUtilities /// The cursor is placed at the location of the mark and the mark is moved /// to the location of the cursor. /// - public static void ExchangePointAndMark() + public static void ExchangePointAndMark(ConsoleKeyInfo? key = null, object arg = null) { var tmp = _singleton._mark; _singleton._mark = _singleton._current; @@ -1126,7 +1143,7 @@ namespace PSConsoleUtilities /// Clear the input from the cursor to the end of the input. The cleared text is placed /// in the kill ring. /// - public static void KillLine() + public static void KillLine(ConsoleKeyInfo? key = null, object arg = null) { _singleton.Kill(_singleton._current, _singleton._buffer.Length - _singleton._current); } @@ -1135,7 +1152,7 @@ namespace PSConsoleUtilities /// Clear the input from the start of the input to the cursor. The cleared text is placed /// in the kill ring. /// - public static void BackwardKillLine() + public static void BackwardKillLine(ConsoleKeyInfo? key = null, object arg = null) { _singleton.Kill(0, _singleton._current); } @@ -1145,7 +1162,7 @@ namespace PSConsoleUtilities /// is between words, the input is cleared from the cursor to the end of the next word. /// The cleared text is placed in the kill ring. /// - public static void KillWord() + public static void KillWord(ConsoleKeyInfo? key = null, object arg = null) { var token = _singleton.FindToken(_singleton._current, FindTokenMode.CurrentOrNext); var end = (token.Kind == TokenKind.EndOfInput) @@ -1159,7 +1176,7 @@ namespace PSConsoleUtilities /// is between words, the input is cleared from the start of the previous word to the /// cursor. The cleared text is placed in the kill ring. /// - public static void KillBackwardWord() + public static void KillBackwardWord(ConsoleKeyInfo? key = null, object arg = null) { var token = _singleton.FindToken(_singleton._current, FindTokenMode.Previous); var start = token == null @@ -1184,7 +1201,7 @@ namespace PSConsoleUtilities /// /// Add the most recently killed text to the input. /// - public static void Yank() + public static void Yank(ConsoleKeyInfo? key = null, object arg = null) { _singleton.YankImpl(); } @@ -1208,7 +1225,7 @@ namespace PSConsoleUtilities /// If the previous operation was Yank or YankPop, replace the previously yanked /// text with the next killed text from the kill ring. /// - public static void YankPop() + public static void YankPop(ConsoleKeyInfo? key = null, object arg = null) { _singleton.YankPopImpl(); } @@ -1772,12 +1789,19 @@ namespace PSConsoleUtilities } if (options._editMode.HasValue) { + // Switching modes - clear out chord dispatch table + _chordDispatchTable.Clear(); + switch (options._editMode) { case EditMode.Emacs: _dispatchTable = new Dictionary(_emacsKeyMap); _dispatchCtrlXTable = new Dictionary(_emacsCtrlXMap); _dispatchMetaTable = new Dictionary(_emacsMetaMap); + + _chordDispatchTable = new Dictionary>(); + _chordDispatchTable[Keys.CtrlX] = _dispatchCtrlXTable; + _chordDispatchTable[Keys.Escape] = _dispatchMetaTable; break; #if FALSE case EditMode.Vi: @@ -1786,6 +1810,7 @@ namespace PSConsoleUtilities #endif case EditMode.Windows: _dispatchTable = new Dictionary(_cmdKeyMap); + _chordDispatchTable = new Dictionary>(); break; } } @@ -1826,10 +1851,27 @@ namespace PSConsoleUtilities } } - private void SetKeyHandlerInternal(ConsoleKeyInfo key, bool ctrlX, Action handler, string briefDescription, string longDescription) + private void SetKeyHandlerInternal(string[] keys, Action handler, string briefDescription, string longDescription) { - var table = ctrlX ? _dispatchCtrlXTable : _dispatchTable; - table[key] = MakeKeyHandler(handler, briefDescription, longDescription); + foreach (var key in keys) + { + var chord = ConsoleKeyChordConverter.Convert(key); + if (chord.Length == 1) + { + _dispatchTable[chord[0]] = MakeKeyHandler(handler, briefDescription, longDescription); + } + else + { + _dispatchTable[chord[0]] = MakeKeyHandler(Chord, "ChordFirstKey"); + Dictionary secondDispatchTable; + if (!_chordDispatchTable.TryGetValue(chord[0], out secondDispatchTable)) + { + secondDispatchTable = new Dictionary(); + _chordDispatchTable[chord[0]] = secondDispatchTable; + } + secondDispatchTable[chord[1]] = MakeKeyHandler(handler, briefDescription, longDescription); + } + } } /// @@ -1843,9 +1885,9 @@ namespace PSConsoleUtilities /// /// Helper function for the Set-PSReadlineKeyHandler cmdlet. /// - public static void SetKeyHandler(ConsoleKeyInfo key, bool ctrlX, Action handler, string briefDescription, string longDescription) + public static void SetKeyHandler(string[] key, Action handler, string briefDescription, string longDescription) { - _singleton.SetKeyHandlerInternal(key, ctrlX, handler, briefDescription, longDescription); + _singleton.SetKeyHandlerInternal(key, handler, briefDescription, longDescription); } /// @@ -1856,29 +1898,28 @@ namespace PSConsoleUtilities { foreach (var entry in _singleton._dispatchTable) { - if (entry.Value.BriefDescription == "Ignore" || entry.Value.BriefDescription == "EmacsMeta") + if (entry.Value.BriefDescription == "Ignore" + || entry.Value.BriefDescription == "ChordFirstKey") { continue; } - if (entry.Value.BriefDescription == "EmacsCtrlX") + yield return new PSConsoleUtilities.KeyHandler { - foreach (var xEntry in _singleton._dispatchCtrlXTable) - { - yield return new PSConsoleUtilities.KeyHandler - { - Key = "Ctrl+X," + xEntry.Key.ToGestureString(), - BriefDescription = xEntry.Value.BriefDescription, - LongDescription = xEntry.Value.LongDescription, - }; - } - } - else + Key = entry.Key.ToGestureString(), + BriefDescription = entry.Value.BriefDescription, + LongDescription = entry.Value.LongDescription, + }; + } + + foreach (var entry in _singleton._chordDispatchTable) + { + foreach (var secondEntry in entry.Value) { yield return new PSConsoleUtilities.KeyHandler { - Key = entry.Key.ToGestureString(), - BriefDescription = entry.Value.BriefDescription, - LongDescription = entry.Value.LongDescription, + Key = entry.Key.ToGestureString() + "," + secondEntry.Key.ToGestureString(), + BriefDescription = secondEntry.Value.BriefDescription, + LongDescription = secondEntry.Value.LongDescription, }; } } diff --git a/TestPSReadLine/Program.cs b/TestPSReadLine/Program.cs index 7b6f38e..c2af77b 100644 --- a/TestPSReadLine/Program.cs +++ b/TestPSReadLine/Program.cs @@ -103,8 +103,8 @@ namespace TestPSReadLine HistorySearchCursorMovesToEnd = true, // ExtraPromptLineCount = 1 }); - PSConsoleReadLine.SetKeyHandler(Keys.UpArrow, false, PSConsoleReadLine.HistorySearchBackward, "", ""); - PSConsoleReadLine.SetKeyHandler(Keys.DownArrow, false, PSConsoleReadLine.HistorySearchForward, "", ""); + PSConsoleReadLine.SetKeyHandler(new[] {"UpArrow"}, PSConsoleReadLine.HistorySearchBackward, "", ""); + PSConsoleReadLine.SetKeyHandler(new[] {"DownArrow"}, PSConsoleReadLine.HistorySearchForward, "", ""); while (true) { //Console.Write("C:\\Windows\nPS> "); diff --git a/UnitTestPSReadLine/UnitTestReadLine.cs b/UnitTestPSReadLine/UnitTestReadLine.cs index ec89362..3ee9356 100644 --- a/UnitTestPSReadLine/UnitTestReadLine.cs +++ b/UnitTestPSReadLine/UnitTestReadLine.cs @@ -104,14 +104,14 @@ namespace UnitTestPSReadLine class KeyHandler { - public KeyHandler(ConsoleKeyInfo key, Action handler) + public KeyHandler(string chord, Action handler) { - this.Key = key; + this.Chord = chord; this.Handler = handler; } - public ConsoleKeyInfo Key { get; private set; } - public Action Handler { get; private set; } + public string Chord { get; private set; } + public Action Handler { get; private set; } } class KeyWithValidation @@ -478,7 +478,7 @@ namespace UnitTestPSReadLine foreach (var keyHandler in keyHandlers) { - PSConsoleReadLine.SetKeyHandler(keyHandler.Key, false, keyHandler.Handler, "", ""); + PSConsoleReadLine.SetKeyHandler(new [] {keyHandler.Chord}, keyHandler.Handler, "", ""); } var colorOptions = new SetPSReadlineOption(); @@ -758,8 +758,8 @@ namespace UnitTestPSReadLine public void TestSearchHistory() { TestSetup(KeyMode.Cmd, - new KeyHandler(_.UpArrow, PSConsoleReadLine.HistorySearchBackward), - new KeyHandler(_.DownArrow, PSConsoleReadLine.HistorySearchForward)); + new KeyHandler("UpArrow", PSConsoleReadLine.HistorySearchBackward), + new KeyHandler("DownArrow", PSConsoleReadLine.HistorySearchForward)); // No history var keys = new [] {_.UpArrow, _.DownArrow, _.Enter}; @@ -1021,11 +1021,15 @@ namespace UnitTestPSReadLine [TestMethod] public void TestRevertLine() { - TestSetup(KeyMode.Cmd); + // Add one test for chords + TestSetup(KeyMode.Cmd, new KeyHandler("Ctrl+X,Escape", PSConsoleReadLine.RevertLine)); var keys = new [] {_.D, _.I, _.Escape, _.L, _.S, _.Enter}; var result = Test(keys); Assert.AreEqual("ls", result); + keys = new [] {_.D, _.I, _.CtrlX, _.Escape, _.L, _.S, _.Enter}; + result = Test(keys); Assert.AreEqual("ls", result); + TestSetup(KeyMode.Emacs); keys = new [] {_.D, _.I, _.Escape, _.R, _.L, _.S, _.Enter}; result = Test(keys); Assert.AreEqual("ls", result); @@ -1248,7 +1252,7 @@ namespace UnitTestPSReadLine public void TestExchangePointAndMark() { TestSetup(KeyMode.Emacs, - new KeyHandler(_.CtrlZ, PSConsoleReadLine.ExchangePointAndMark)); + new KeyHandler("Ctrl+Z", PSConsoleReadLine.ExchangePointAndMark)); var keys = Keys(_.A, _.B, _.C, _.D, _.E, new KeyWithValidation(_.CtrlZ, () => AssertCursorLeftIs(0)), @@ -1417,13 +1421,11 @@ namespace UnitTestPSReadLine [TestMethod] public void TestKeyInfoConverterSimpleCharLiteral() { - var converter = new ConsoleKeyInfoConverterAttribute(); - - object result = converter.Transform(null, "x"); + var result = ConsoleKeyChordConverter.Convert("x"); Assert.IsNotNull(result); - Assert.IsInstanceOfType(result, typeof(ConsoleKeyInfo)); + Assert.AreEqual(result.Length, 1); - var key = (ConsoleKeyInfo) result; + var key = result[0]; Assert.AreEqual(key.KeyChar, 'x'); Assert.AreEqual(key.Key, ConsoleKey.X); @@ -1433,13 +1435,11 @@ namespace UnitTestPSReadLine [TestMethod] public void TestKeyInfoConverterSimpleCharLiteralWithModifiers() { - var converter = new ConsoleKeyInfoConverterAttribute(); - - object result = converter.Transform(null, "alt+shift+x"); + var result = ConsoleKeyChordConverter.Convert("alt+shift+x"); Assert.IsNotNull(result); - Assert.IsInstanceOfType(result, typeof(ConsoleKeyInfo)); + Assert.AreEqual(result.Length, 1); - var key = (ConsoleKeyInfo) result; + var key = result[0]; Assert.AreEqual(key.KeyChar, 'X'); Assert.AreEqual(key.Key, ConsoleKey.X); @@ -1449,13 +1449,11 @@ namespace UnitTestPSReadLine [TestMethod] public void TestKeyInfoConverterSymbolLiteral() { - var converter = new ConsoleKeyInfoConverterAttribute(); - - object result = converter.Transform(null, "}"); + var result = ConsoleKeyChordConverter.Convert("}"); Assert.IsNotNull(result); - Assert.IsInstanceOfType(result, typeof(ConsoleKeyInfo)); + Assert.AreEqual(result.Length, 1); - var key = (ConsoleKeyInfo)result; + var key = result[0]; Assert.AreEqual(key.KeyChar, '}'); Assert.AreEqual(key.Key, ConsoleKey.Oem6); @@ -1466,13 +1464,11 @@ namespace UnitTestPSReadLine public void TestKeyInfoConverterShiftedSymbolLiteral() { // } => shift+] / shift+oem6 - var converter = new ConsoleKeyInfoConverterAttribute(); - - object result = converter.Transform(null, "shift+]"); + var result = ConsoleKeyChordConverter.Convert("shift+]"); Assert.IsNotNull(result); - Assert.IsInstanceOfType(result, typeof(ConsoleKeyInfo)); + Assert.AreEqual(result.Length, 1); - var key = (ConsoleKeyInfo)result; + var key = result[0]; Assert.AreEqual(key.KeyChar, '}'); Assert.AreEqual(key.Key, ConsoleKey.Oem6); @@ -1483,13 +1479,11 @@ namespace UnitTestPSReadLine public void TestKeyInfoConverterWellKnownConsoleKey() { // oem6 - var converter = new ConsoleKeyInfoConverterAttribute(); - - object result = converter.Transform(null, "shift+oem6"); + var result = ConsoleKeyChordConverter.Convert("shift+oem6"); Assert.IsNotNull(result); - Assert.IsInstanceOfType(result, typeof(ConsoleKeyInfo)); + Assert.AreEqual(result.Length, 1); - var key = (ConsoleKeyInfo)result; + var key = result[0]; Assert.AreEqual(key.KeyChar, '}'); Assert.AreEqual(key.Key, ConsoleKey.Oem6); @@ -1497,39 +1491,59 @@ namespace UnitTestPSReadLine } [TestMethod] - public void TestKeyInfoConverterPassThrough() + public void TestKeyInfoConverterSequence() { - // pass through consolekeyinfo - var converter = new ConsoleKeyInfoConverterAttribute(); + // oem6 + var result = ConsoleKeyChordConverter.Convert("Escape,X"); + Assert.IsNotNull(result); + Assert.AreEqual(result.Length, 2); - var key = new ConsoleKeyInfo('x', ConsoleKey.X, true, false, false); - object result = converter.Transform(null, key); - - Assert.AreEqual(key, result); + var key = result[0]; + + Assert.AreEqual(key.KeyChar, (char)27); + Assert.AreEqual(key.Key, ConsoleKey.Escape); + Assert.AreEqual(key.Modifiers, (ConsoleModifiers)0); + + key = result[1]; + + Assert.AreEqual(key.KeyChar, 'x'); + Assert.AreEqual(key.Key, ConsoleKey.X); + Assert.AreEqual(key.Modifiers, (ConsoleModifiers)0); } [TestMethod] [ExpectedException(typeof(ArgumentException))] public void TestKeyInfoConverterInvalidKey() { - var converter = new ConsoleKeyInfoConverterAttribute(); - object result = converter.Transform(null, "escrape"); + var result = ConsoleKeyChordConverter.Convert("escrape"); } [TestMethod] [ExpectedException(typeof(ArgumentException))] public void TestKeyInfoConverterInvalidModifierTypo() { - var converter = new ConsoleKeyInfoConverterAttribute(); - object result = converter.Transform(null, "alt+shuft+x"); + var result = ConsoleKeyChordConverter.Convert("alt+shuft+x"); } [TestMethod] [ExpectedException(typeof(ArgumentException))] public void TestKeyInfoConverterInvalidModifierInapplicable() { - var converter = new ConsoleKeyInfoConverterAttribute(); - object result = converter.Transform(null, "shift+}"); + var result = ConsoleKeyChordConverter.Convert("shift+}"); + } + + [TestMethod] + [ExpectedException(typeof (ArgumentException))] + public void TestKeyInfoConverterInvalidSubsequence1() + { + var result = ConsoleKeyChordConverter.Convert("x,"); + } + + [TestMethod] + [ExpectedException(typeof (ArgumentException))] + public void TestKeyInfoConverterInvalidSubsequence2() + { + var result = ConsoleKeyChordConverter.Convert(",x"); } } }