From 3e23733fd5fbe06cdbbcfc301890506fbdf7908e Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Wed, 11 Sep 2013 17:55:30 -0700 Subject: [PATCH] Support 2 key chords Support for 2 key chords has been added. There is a small breaking change in Set-PSReadlineKeyHandler because the -CtrlX switch is no longer needed. It could have been left in, but it's better to just remove it - not very many people use this module yet. The signature of key handlers was also changed to better support features that have not yet been implemented. The signature change shouldn't affect any existing key handlers because default arguments should cover those cases. --- PSReadLine/Cmdlets.cs | 13 +- ...tribute.cs => ConsoleKeyChordConverter.cs} | 42 ++++- PSReadLine/PSReadLine.csproj | 4 +- PSReadLine/ReadLine.cs | 175 +++++++++++------- TestPSReadLine/Program.cs | 4 +- UnitTestPSReadLine/UnitTestReadLine.cs | 108 ++++++----- 6 files changed, 210 insertions(+), 136 deletions(-) rename PSReadLine/{ConsoleKeyInfoConverterAttribute.cs => ConsoleKeyChordConverter.cs} (87%) 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"); } } }