Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit bc11289

Browse files
committedOct 31, 2023
Refactored key chords handling
1 parent fbdf7fa commit bc11289

13 files changed

+788
-738
lines changed
 

‎PSReadLine/Completion.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1091,7 +1091,8 @@ private void MenuCompleteImpl(Menu menu, CommandCompletion completions)
10911091
prependNextKey = true;
10921092

10931093
// without this branch experience doesn't look naturally
1094-
if (_dispatchTable.TryGetValue(nextKey, out var handler) &&
1094+
if (_dispatchTable.TryGetValue(nextKey, out var handlerOrChordDispatchTable) &&
1095+
handlerOrChordDispatchTable.TryGetKeyHandler(out var handler) &&
10951096
(
10961097
handler.Action == CopyOrCancelLine ||
10971098
handler.Action == Cut ||

‎PSReadLine/ConsoleKeyChordConverter.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,22 @@ public static ConsoleKeyInfo[] Convert(string chord)
3636
throw new ArgumentNullException(nameof(chord));
3737
}
3838

39-
int start = 0;
40-
var first = GetOneKey(chord, ref start);
39+
var keyChord = new List<ConsoleKeyInfo>(2);
4140

42-
if (start >= chord.Length)
43-
return new [] { first };
41+
int index = 0;
4442

45-
if (chord[start++] != ',')
46-
throw CantConvert(PSReadLineResources.InvalidSequence, chord);
43+
while (true)
44+
{
45+
keyChord.Add(GetOneKey(chord, ref index));
46+
47+
if (index >= chord.Length)
48+
break;
4749

48-
var second = GetOneKey(chord, ref start);
49-
if (start < chord.Length)
50-
throw CantConvert(PSReadLineResources.InvalidSequence, chord);
50+
if (chord[index++] != ',')
51+
throw CantConvert(PSReadLineResources.InvalidSequence, chord);
52+
}
5153

52-
return new [] { first, second };
54+
return keyChord.ToArray();
5355
}
5456

5557
private static ConsoleKeyInfo GetOneKey(string chord, ref int start)

‎PSReadLine/History.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,9 +1171,13 @@ private void InteractiveHistorySearchLoop(int direction)
11711171
var toMatch = new StringBuilder(64);
11721172
while (true)
11731173
{
1174+
Action<ConsoleKeyInfo?, object> function = null;
1175+
11741176
var key = ReadKey();
1175-
_dispatchTable.TryGetValue(key, out var handler);
1176-
var function = handler?.Action;
1177+
_dispatchTable.TryGetValue(key, out var handlerOrChordDispatchTable);
1178+
if (handlerOrChordDispatchTable.TryGetKeyHandler(out var handler))
1179+
function = handler.Action;
1180+
11771181
if (function == ReverseSearchHistory)
11781182
{
11791183
UpdateHistoryDuringInteractiveSearch(toMatch.ToString(), -1, ref searchFromPoint);

‎PSReadLine/KeyBindings.cs

Lines changed: 229 additions & 68 deletions
Large diffs are not rendered by default.

‎PSReadLine/KeyBindings.vi.cs

Lines changed: 234 additions & 257 deletions
Large diffs are not rendered by default.

‎PSReadLine/Movement.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ private static bool CheckIsBound(Action<ConsoleKeyInfo?, object> action)
370370
{
371371
foreach (var entry in _singleton._dispatchTable)
372372
{
373-
if (entry.Value.Action == action)
373+
if (entry.Value.TryGetKeyHandler(out var handler) && handler.Action == action)
374374
return true;
375375
}
376376
return false;

‎PSReadLine/Options.cs

Lines changed: 142 additions & 181 deletions
Large diffs are not rendered by default.

‎PSReadLine/ReadLine.cs

Lines changed: 104 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,8 @@ public static string ReadLine(
422422
sb.Append(' ');
423423
sb.Append(_lastNKeys[i].KeyStr);
424424

425-
if (_singleton._dispatchTable.TryGetValue(_lastNKeys[i], out var handler) &&
425+
if (_singleton._dispatchTable.TryGetValue(_lastNKeys[i], out var handlerOrChordDispatchTable) &&
426+
handlerOrChordDispatchTable.TryGetKeyHandler(out var handler) &&
426427
"AcceptLine".Equals(handler.BriefDescription, StringComparison.OrdinalIgnoreCase))
427428
{
428429
// Make it a little easier to see the keys
@@ -459,10 +460,12 @@ bool IsValid(ConsoleColor color)
459460
return color >= ConsoleColor.Black && color <= ConsoleColor.White;
460461
}
461462

462-
if (IsValid(_singleton._initialForeground)) {
463+
if (IsValid(_singleton._initialForeground))
464+
{
463465
console.ForegroundColor = _singleton._initialForeground;
464466
}
465-
if (IsValid(_singleton._initialBackground)) {
467+
if (IsValid(_singleton._initialBackground))
468+
{
466469
console.BackgroundColor = _singleton._initialBackground;
467470
}
468471
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -603,47 +606,114 @@ void CallPossibleExternalApplication(Action action)
603606
CallPossibleExternalApplication<object>(() => { action(); return null; });
604607
}
605608

606-
void ProcessOneKey(PSKeyInfo key, Dictionary<PSKeyInfo, KeyHandler> dispatchTable, bool ignoreIfNoAction, object arg)
609+
void ProcessOneKey(PSKeyInfo key, ChordDispatchTable dispatchTable, bool ignoreIfNoAction, object arg)
610+
=> ProcessKeyChord(key, dispatchTable, ignoreIfNoAction, arg);
611+
void ProcessKeyChord(PSKeyInfo key, ChordDispatchTable dispatchTable, bool ignoreIfNoAction, object arg)
607612
{
608-
var consoleKey = key.AsConsoleKeyInfo();
613+
KeyHandlerOrChordDispatchTable handlerOrChordDispatchTable = null;
609614

610-
// Our dispatch tables are built as much as possible in a portable way, so for example,
611-
// we avoid depending on scan codes like ConsoleKey.Oem6 and instead look at the
612-
// PSKeyInfo.Key. We also want to ignore the shift state as that may differ on
613-
// different keyboard layouts.
614-
//
615-
// That said, we first look up exactly what we get from Console.ReadKey - that will fail
616-
// most of the time, and when it does, we normalize the key.
617-
if (!dispatchTable.TryGetValue(key, out var handler))
615+
do
618616
{
619-
// If we see a control character where Ctrl wasn't used but shift was, treat that like
620-
// shift hadn't be pressed. This cleanly allows Shift+Backspace without adding a key binding.
621-
if (key.Shift && !key.Control && !key.Alt)
617+
var consoleKey = key.AsConsoleKeyInfo();
618+
619+
// Our dispatch tables are built as much as possible in a portable way, so for example,
620+
// we avoid depending on scan codes like ConsoleKey.Oem6 and instead look at the
621+
// PSKeyInfo.Key. We also want to ignore the shift state as that may differ on
622+
// different keyboard layouts.
623+
//
624+
// That said, we first look up exactly what we get from Console.ReadKey - that will fail
625+
// most of the time, and when it does, we normalize the key.
626+
if (!dispatchTable.TryGetValue(key, out handlerOrChordDispatchTable))
622627
{
623-
var c = consoleKey.KeyChar;
624-
if (c != '\0' && char.IsControl(c))
628+
// If we see a control character where Ctrl wasn't used but shift was, treat that like
629+
// shift hadn't been pressed. This cleanly allows Shift+Backspace without adding a key binding.
630+
if (key.Shift && !key.Control && !key.Alt)
625631
{
626-
key = PSKeyInfo.From(consoleKey.Key);
627-
dispatchTable.TryGetValue(key, out handler);
632+
var c = consoleKey.KeyChar;
633+
if (c != '\0' && char.IsControl(c))
634+
{
635+
key = PSKeyInfo.From(consoleKey.Key);
636+
dispatchTable.TryGetValue(key, out handlerOrChordDispatchTable);
637+
}
628638
}
629639
}
630-
}
631640

632-
if (handler != null)
633-
{
634-
if (handler.ScriptBlock != null)
641+
if (handlerOrChordDispatchTable != null && handlerOrChordDispatchTable.TryGetKeyHandler(out var handler))
642+
{
643+
// invoke key handler
644+
645+
if (handler.ScriptBlock != null)
646+
{
647+
CallPossibleExternalApplication(() => handler.Action(consoleKey, arg));
648+
}
649+
else
650+
{
651+
handler.Action(consoleKey, arg);
652+
}
653+
654+
// key chords are processed mostly by looping over each key
655+
// in a loop and identifying their dispatch tables until
656+
// the dispatch table is null.
657+
//
658+
// nullify dispatch table to prevent infinite looping
659+
660+
handlerOrChordDispatchTable = null;
661+
}
662+
663+
// the current key is part of a key chord
664+
// read the next key and select the dispatch
665+
// table for the next iteration of the loop
666+
667+
else if (handlerOrChordDispatchTable != null)
668+
{
669+
key = ReadKey();
670+
671+
dispatchTable = (ChordDispatchTable)handlerOrChordDispatchTable;
672+
ignoreIfNoAction = true;
673+
}
674+
675+
else if (handlerOrChordDispatchTable == null && dispatchTable.InViMode && IsNumeric(key) && arg == null)
676+
{
677+
var argBuffer = _singleton._statusBuffer;
678+
argBuffer.Clear();
679+
_singleton._statusLinePrompt = "digit-argument: ";
680+
681+
while (IsNumeric(key))
682+
{
683+
argBuffer.Append(key.KeyChar);
684+
_singleton.Render();
685+
key = ReadKey();
686+
}
687+
var numericArg = int.Parse(argBuffer.ToString());
688+
if (dispatchTable.TryGetValue(key, out var keyHhandler))
689+
{
690+
ProcessOneKey(key, dispatchTable, ignoreIfNoAction: true, arg: numericArg);
691+
}
692+
else
693+
{
694+
Ding();
695+
}
696+
argBuffer.Clear();
697+
_singleton.ClearStatusMessage(render: true);
698+
699+
// MUST exit from recursive call
700+
701+
return;
702+
}
703+
704+
// insert unmapped keys
705+
706+
else if (!ignoreIfNoAction)
635707
{
636-
CallPossibleExternalApplication(() => handler.Action(consoleKey, arg));
708+
SelfInsert(consoleKey, arg);
637709
}
710+
638711
else
639712
{
640-
handler.Action(consoleKey, arg);
713+
Ding(key.AsConsoleKeyInfo(), arg);
641714
}
642-
}
643-
else if (!ignoreIfNoAction)
644-
{
645-
SelfInsert(consoleKey, arg);
646-
}
715+
716+
} while (handlerOrChordDispatchTable != null);
647717
}
648718

649719
static PSConsoleReadLine()
@@ -877,20 +947,6 @@ private void DelayedOneTimeInitialize()
877947
_readKeyThread.Start();
878948
}
879949

880-
private static void Chord(ConsoleKeyInfo? key = null, object arg = null)
881-
{
882-
if (!key.HasValue)
883-
{
884-
throw new ArgumentNullException(nameof(key));
885-
}
886-
887-
if (_singleton._chordDispatchTable.TryGetValue(PSKeyInfo.FromConsoleKeyInfo(key.Value), out var secondKeyDispatchTable))
888-
{
889-
var secondKey = ReadKey();
890-
_singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: arg);
891-
}
892-
}
893-
894950
/// <summary>
895951
/// Abort current action, e.g. incremental history search.
896952
/// </summary>
@@ -932,7 +988,9 @@ public static void DigitArgument(ConsoleKeyInfo? key = null, object arg = null)
932988
while (true)
933989
{
934990
var nextKey = ReadKey();
935-
if (_singleton._dispatchTable.TryGetValue(nextKey, out var handler))
991+
if (_singleton._dispatchTable.TryGetValue(nextKey, out var handlerOrChordDispatchTable) &&
992+
handlerOrChordDispatchTable.TryGetKeyHandler(out var handler)
993+
)
936994
{
937995
if (handler.Action == DigitArgument)
938996
{

‎PSReadLine/ReadLine.vi.cs

Lines changed: 5 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,6 @@ public static void ViCommandMode(ConsoleKeyInfo? key = null, object arg = null)
443443
_singleton._groupUndoHelper.EndGroup();
444444
}
445445
_singleton._dispatchTable = _viCmdKeyMap;
446-
_singleton._chordDispatchTable = _viCmdChordTable;
447446
ViBackwardChar();
448447
_singleton.ViIndicateCommandMode();
449448
}
@@ -454,7 +453,6 @@ public static void ViCommandMode(ConsoleKeyInfo? key = null, object arg = null)
454453
public static void ViInsertMode(ConsoleKeyInfo? key = null, object arg = null)
455454
{
456455
_singleton._dispatchTable = _viInsKeyMap;
457-
_singleton._chordDispatchTable = _viInsChordTable;
458456
_singleton.ViIndicateInsertMode();
459457
}
460458

@@ -479,15 +477,12 @@ public static void ViInsertMode(ConsoleKeyInfo? key = null, object arg = null)
479477
internal static IDisposable UseViCommandModeTables()
480478
{
481479
var oldDispatchTable = _singleton._dispatchTable;
482-
var oldChordDispatchTable = _singleton._chordDispatchTable;
483480

484481
_singleton._dispatchTable = _viCmdKeyMap;
485-
_singleton._chordDispatchTable = _viCmdChordTable;
486482

487483
return new Disposable(() =>
488484
{
489485
_singleton._dispatchTable = oldDispatchTable;
490-
_singleton._chordDispatchTable = oldChordDispatchTable;
491486
});
492487
}
493488

@@ -497,15 +492,12 @@ internal static IDisposable UseViCommandModeTables()
497492
internal static IDisposable UseViInsertModeTables()
498493
{
499494
var oldDispatchTable = _singleton._dispatchTable;
500-
var oldChordDispatchTable = _singleton._chordDispatchTable;
501495

502496
_singleton._dispatchTable = _viInsKeyMap;
503-
_singleton._chordDispatchTable = _viInsChordTable;
504497

505498
return new Disposable(() =>
506499
{
507500
_singleton._dispatchTable = oldDispatchTable;
508-
_singleton._chordDispatchTable = oldChordDispatchTable;
509501
});
510502
}
511503

@@ -1089,73 +1081,6 @@ public static void RepeatLastCommand(ConsoleKeyInfo? key = null, object arg = nu
10891081
Ding();
10901082
}
10911083

1092-
/// <summary>
1093-
/// Chords in vi needs special handling because a numeric argument can be input between the 1st and 2nd key.
1094-
/// </summary>
1095-
private static void ViChord(ConsoleKeyInfo? key = null, object arg = null)
1096-
{
1097-
if (!key.HasValue)
1098-
{
1099-
throw new ArgumentNullException(nameof(key));
1100-
}
1101-
if (arg != null)
1102-
{
1103-
Chord(key, arg);
1104-
return;
1105-
}
1106-
1107-
if (_singleton._chordDispatchTable.TryGetValue(PSKeyInfo.FromConsoleKeyInfo(key.Value), out var secondKeyDispatchTable))
1108-
{
1109-
ViChordHandler(secondKeyDispatchTable, arg);
1110-
}
1111-
}
1112-
1113-
private static void ViChordHandler(Dictionary<PSKeyInfo, KeyHandler> secondKeyDispatchTable, object arg = null)
1114-
{
1115-
var secondKey = ReadKey();
1116-
if (secondKeyDispatchTable.TryGetValue(secondKey, out var handler))
1117-
{
1118-
_singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: arg);
1119-
}
1120-
else if (!IsNumeric(secondKey))
1121-
{
1122-
_singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: arg);
1123-
}
1124-
else
1125-
{
1126-
var argBuffer = _singleton._statusBuffer;
1127-
argBuffer.Clear();
1128-
_singleton._statusLinePrompt = "digit-argument: ";
1129-
while (IsNumeric(secondKey))
1130-
{
1131-
argBuffer.Append(secondKey.KeyChar);
1132-
_singleton.Render();
1133-
secondKey = ReadKey();
1134-
}
1135-
int numericArg = int.Parse(argBuffer.ToString());
1136-
if (secondKeyDispatchTable.TryGetValue(secondKey, out handler))
1137-
{
1138-
_singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: numericArg);
1139-
}
1140-
else
1141-
{
1142-
Ding();
1143-
}
1144-
argBuffer.Clear();
1145-
_singleton.ClearStatusMessage(render: true);
1146-
}
1147-
}
1148-
1149-
private static void ViDGChord(ConsoleKeyInfo? key = null, object arg = null)
1150-
{
1151-
if (!key.HasValue)
1152-
{
1153-
throw new ArgumentNullException(nameof(key));
1154-
}
1155-
1156-
ViChordHandler(_viChordDGTable, arg);
1157-
}
1158-
11591084
private static bool IsNumeric(PSKeyInfo key)
11601085
{
11611086
return key.KeyChar >= '0' && key.KeyChar <= '9' && !key.Control && !key.Alt;
@@ -1195,7 +1120,11 @@ public static void ViDigitArgumentInChord(ConsoleKeyInfo? key = null, object arg
11951120
while (true)
11961121
{
11971122
var nextKey = ReadKey();
1198-
if (_singleton._dispatchTable.TryGetValue(nextKey, out var handler) && handler.Action == DigitArgument)
1123+
if (
1124+
_singleton._dispatchTable.TryGetValue(nextKey, out var handlerOrChordDispatchTable) &&
1125+
handlerOrChordDispatchTable.TryGetKeyHandler(out var handler) &&
1126+
handler.Action == DigitArgument
1127+
)
11991128
{
12001129
if (nextKey == Keys.Minus)
12011130
{

‎PSReadLine/TextObjects.Vi.cs

Lines changed: 1 addition & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,10 @@
11
using System;
2-
using System.Collections.Generic;
32

43
namespace Microsoft.PowerShell
54
{
65
public partial class PSConsoleReadLine
76
{
8-
internal enum TextObjectOperation
9-
{
10-
None,
11-
Change,
12-
Delete,
13-
}
14-
15-
internal enum TextObjectSpan
16-
{
17-
None,
18-
Around,
19-
Inner,
20-
}
21-
22-
private TextObjectOperation _textObjectOperation = TextObjectOperation.None;
23-
private TextObjectSpan _textObjectSpan = TextObjectSpan.None;
24-
25-
private readonly Dictionary<TextObjectOperation, Dictionary<TextObjectSpan, KeyHandler>> _textObjectHandlers = new()
26-
{
27-
[TextObjectOperation.Delete] = new() { [TextObjectSpan.Inner] = MakeKeyHandler(ViDeleteInnerWord, "ViDeleteInnerWord") },
28-
};
29-
30-
private void ViChordDeleteTextObject(ConsoleKeyInfo? key = null, object arg = null)
31-
{
32-
_textObjectOperation = TextObjectOperation.Delete;
33-
ViChordTextObject(key, arg);
34-
}
35-
36-
private void ViChordTextObject(ConsoleKeyInfo? key = null, object arg = null)
37-
{
38-
if (!key.HasValue)
39-
{
40-
ResetTextObjectState();
41-
throw new ArgumentNullException(nameof(key));
42-
}
43-
44-
_textObjectSpan = GetRequestedTextObjectSpan(key.Value);
45-
46-
// Handle text object
47-
var textObjectKey = ReadKey();
48-
if (_viChordTextObjectsTable.TryGetValue(textObjectKey, out _))
49-
{
50-
_singleton.ProcessOneKey(textObjectKey, _viChordTextObjectsTable, ignoreIfNoAction: true, arg: arg);
51-
}
52-
else
53-
{
54-
ResetTextObjectState();
55-
Ding();
56-
}
57-
}
58-
59-
private TextObjectSpan GetRequestedTextObjectSpan(ConsoleKeyInfo key)
60-
{
61-
if (key.KeyChar == 'i')
62-
{
63-
return TextObjectSpan.Inner;
64-
}
65-
else if (key.KeyChar == 'a')
66-
{
67-
return TextObjectSpan.Around;
68-
}
69-
else
70-
{
71-
System.Diagnostics.Debug.Assert(false);
72-
throw new NotSupportedException();
73-
}
74-
}
75-
76-
private static void ViHandleTextObject(ConsoleKeyInfo? key = null, object arg = null)
77-
{
78-
if (!_singleton._textObjectHandlers.TryGetValue(_singleton._textObjectOperation, out var textObjectHandler) ||
79-
!textObjectHandler.TryGetValue(_singleton._textObjectSpan, out var handler))
80-
{
81-
ResetTextObjectState();
82-
Ding();
83-
return;
84-
}
85-
86-
handler.Action(key, arg);
87-
}
88-
89-
private static void ResetTextObjectState()
90-
{
91-
_singleton._textObjectOperation = TextObjectOperation.None;
92-
_singleton._textObjectSpan = TextObjectSpan.None;
93-
}
94-
95-
private static void ViDeleteInnerWord(ConsoleKeyInfo? key = null, object arg = null)
7+
public static void ViDeleteInnerWord(ConsoleKeyInfo? key = null, object arg = null)
968
{
979
var delimiters = _singleton.Options.WordDelimiters;
9810

‎test/KeyInfoTest.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Linq;
23
using Microsoft.PowerShell;
34
using Xunit;
45

@@ -7,6 +8,7 @@ namespace Test
78
public class KeyInfo
89
{
910
private const ConsoleModifiers NoModifiers = 0;
11+
private const ConsoleModifiers CtrlShift = ConsoleModifiers.Control | ConsoleModifiers.Shift;
1012

1113
[Fact]
1214
public void KeyInfoConverterSimpleCharLiteral()
@@ -109,6 +111,22 @@ void VerifyOne(string input, ConsoleModifiers m)
109111
}
110112
}
111113

114+
[Fact]
115+
public void KeyInfoConverter_ThreeKeyChords()
116+
{
117+
var chord = ConsoleKeyChordConverter.Convert("Ctrl+K,Ctrl+P,x");
118+
Assert.Equal(3, chord.Length);
119+
120+
Assert.Equal(CtrlShift, chord[0].Modifiers);
121+
Assert.Equal(Enum.Parse(typeof(ConsoleKey), "K"), chord[0].Key);
122+
123+
Assert.Equal(CtrlShift, chord[1].Modifiers);
124+
Assert.Equal(Enum.Parse(typeof(ConsoleKey), "P"), chord[1].Key);
125+
126+
Assert.Equal(NoModifiers, chord[2].Modifiers);
127+
Assert.Equal(Enum.Parse(typeof(ConsoleKey), "X"), chord[2].Key);
128+
}
129+
112130
[Fact]
113131
public void KeyInfoConverterErrors()
114132
{

‎test/OptionsTest.VI.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,31 @@ public void ViGetKeyHandlers()
3737
{
3838
Assert.Equal("<d,0>", handler.Key);
3939
}
40+
41+
handlers = PSConsoleReadLine.GetKeyHandlers(Chord: new string[] { "d,i,w" });
42+
Assert.NotEmpty(handlers);
43+
foreach (var handler in handlers)
44+
{
45+
Assert.Equal("<d,i,w>", handler.Key);
46+
}
47+
}
48+
49+
[SkippableFact]
50+
public void ViRemoveKeyHandler()
51+
{
52+
TestSetup(KeyMode.Vi);
53+
54+
using var disposable = PSConsoleReadLine.UseViCommandModeTables();
55+
56+
PSConsoleReadLine.RemoveKeyHandler(new string[] { "d,0" });
57+
58+
var handlers = PSConsoleReadLine.GetKeyHandlers(Chord: new string[] { "d,0" });
59+
Assert.Empty(handlers);
60+
61+
PSConsoleReadLine.RemoveKeyHandler(new string[] { "d,i,w" });
62+
63+
handlers = PSConsoleReadLine.GetKeyHandlers(Chord: new string[] { "d,i,w" });
64+
Assert.Empty(handlers);
4065
}
4166
}
4267
}

‎test/OptionsTest.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ public void ContinuationPrompt()
5656
}
5757

5858
[SkippableFact]
59-
public void GetKeyHandlers()
59+
public void GetKeyHandlers_Unbound()
6060
{
6161
System.Collections.Generic.IEnumerable<Microsoft.PowerShell.KeyHandler> handlers;
6262

63-
foreach (var keymode in new[] {KeyMode.Cmd, KeyMode.Emacs})
63+
foreach (var keymode in new[] { KeyMode.Cmd, KeyMode.Emacs })
6464
{
6565
TestSetup(keymode);
6666

@@ -85,15 +85,17 @@ public void GetKeyHandlers()
8585
Assert.Equal("Home", handler.Key);
8686
}
8787
}
88+
}
89+
90+
[SkippableFact]
91+
public void GetKeyHandlers_Emacs()
92+
{
93+
System.Collections.Generic.IEnumerable<Microsoft.PowerShell.KeyHandler> handlers;
8894

8995
TestSetup(KeyMode.Emacs);
9096

9197
handlers = PSConsoleReadLine.GetKeyHandlers(Chord: new string[] { "ctrl+x" });
92-
Assert.NotEmpty(handlers);
93-
foreach (var handler in handlers)
94-
{
95-
Assert.Equal("Ctrl+x", handler.Key);
96-
}
98+
Assert.Empty(handlers);
9799

98100
handlers = PSConsoleReadLine.GetKeyHandlers(Chord: new string[] { "ctrl+x,ctrl+e" });
99101
Assert.NotEmpty(handlers);

0 commit comments

Comments
 (0)
Please sign in to comment.