diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs
index c6654f92..fcb77334 100644
--- a/PSReadLine/Completion.cs
+++ b/PSReadLine/Completion.cs
@@ -1094,7 +1094,8 @@ private void MenuCompleteImpl(Menu menu, CommandCompletion completions)
                         prependNextKey = true;
 
                         // without this branch experience doesn't look naturally
-                        if (_dispatchTable.TryGetValue(nextKey, out var handler) &&
+                        if (_dispatchTable.TryGetValue(nextKey, out var handlerOrChordDispatchTable) &&
+                            handlerOrChordDispatchTable.TryGetKeyHandler(out var handler) &&
                             (
                                 handler.Action == CopyOrCancelLine ||
                                 handler.Action == Cut ||
diff --git a/PSReadLine/ConsoleKeyChordConverter.cs b/PSReadLine/ConsoleKeyChordConverter.cs
index 87795354..c2d57055 100644
--- a/PSReadLine/ConsoleKeyChordConverter.cs
+++ b/PSReadLine/ConsoleKeyChordConverter.cs
@@ -36,20 +36,22 @@ public static ConsoleKeyInfo[] Convert(string chord)
                 throw new ArgumentNullException(nameof(chord));
             }
 
-            int start = 0;
-            var first = GetOneKey(chord, ref start);
+            var keyChord = new List<ConsoleKeyInfo>(2);
 
-            if (start >= chord.Length)
-                return new [] { first };
+            int index = 0;
 
-            if (chord[start++] != ',')
-                throw CantConvert(PSReadLineResources.InvalidSequence, chord);
+            while (true)
+            {
+                keyChord.Add(GetOneKey(chord, ref index));
+
+                if (index >= chord.Length)
+                    break;
 
-            var second = GetOneKey(chord, ref start);
-            if (start < chord.Length)
-                throw CantConvert(PSReadLineResources.InvalidSequence, chord);
+                if (chord[index++] != ',')
+                    throw CantConvert(PSReadLineResources.InvalidSequence, chord);
+            }
 
-            return new [] { first, second };
+            return keyChord.ToArray();
         }
 
         private static ConsoleKeyInfo GetOneKey(string chord, ref int start)
diff --git a/PSReadLine/History.cs b/PSReadLine/History.cs
index c1490a23..22eeea38 100644
--- a/PSReadLine/History.cs
+++ b/PSReadLine/History.cs
@@ -1171,9 +1171,13 @@ private void InteractiveHistorySearchLoop(int direction)
             var toMatch = new StringBuilder(64);
             while (true)
             {
+                Action<ConsoleKeyInfo?, object> function = null;
+
                 var key = ReadKey();
-                _dispatchTable.TryGetValue(key, out var handler);
-                var function = handler?.Action;
+                _dispatchTable.TryGetValue(key, out var handlerOrChordDispatchTable);
+                if (handlerOrChordDispatchTable?.TryGetKeyHandler(out var handler) == true)
+                    function = handler.Action;
+
                 if (function == ReverseSearchHistory)
                 {
                     UpdateHistoryDuringInteractiveSearch(toMatch.ToString(), -1, ref searchFromPoint);
diff --git a/PSReadLine/KeyBindings.cs b/PSReadLine/KeyBindings.cs
index 89564228..8ae0542c 100644
--- a/PSReadLine/KeyBindings.cs
+++ b/PSReadLine/KeyBindings.cs
@@ -3,7 +3,9 @@
 --********************************************************************/
 
 using System;
+using System.Collections;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Linq;
 using System.Management.Automation;
@@ -34,6 +36,8 @@ public enum KeyHandlerGroup
         Selection,
         /// <summary>Search functions</summary>
         Search,
+        /// <summary>Text objects functions</summary>
+        TextObjects,
         /// <summary>User defined functions</summary>
         Custom
     }
@@ -99,6 +103,8 @@ public static string GetGroupingDescription(KeyHandlerGroup grouping)
                 return PSReadLineResources.SelectionGrouping;
             case KeyHandlerGroup.Search:
                 return PSReadLineResources.SearchGrouping;
+            case KeyHandlerGroup.TextObjects:
+                return PSReadLineResources.TextObjectsGrouping;
             case KeyHandlerGroup.Custom:
                 return PSReadLineResources.CustomGrouping;
             default: return "";
@@ -152,8 +158,150 @@ static KeyHandler MakeKeyHandler(Action<ConsoleKeyInfo?, object> action, string
             };
         }
 
-        private Dictionary<PSKeyInfo, KeyHandler> _dispatchTable;
-        private Dictionary<PSKeyInfo, Dictionary<PSKeyInfo, KeyHandler>> _chordDispatchTable;
+        static ChordDispatchTable MakeChordDispatchTable(IEnumerable<KeyValuePair<PSKeyInfo, KeyHandlerOrChordDispatchTable>> chordDispatchTable)
+            => new(chordDispatchTable);
+        static ChordDispatchTable MakeViChordDispatchTable(IEnumerable<KeyValuePair<PSKeyInfo, KeyHandlerOrChordDispatchTable>> chordDispatchTable)
+            => new ViChordDispatchTable(chordDispatchTable);
+
+        private ChordDispatchTable _dispatchTable;
+
+        private class ChordDispatchTable : IDictionary<PSKeyInfo, KeyHandlerOrChordDispatchTable>
+        {
+            private readonly IDictionary<PSKeyInfo, KeyHandlerOrChordDispatchTable> _dispatchTable;
+            private readonly bool _viMode;
+
+            public ChordDispatchTable(IEnumerable<KeyValuePair<PSKeyInfo, KeyHandlerOrChordDispatchTable>> dispatchTable)
+                : this(dispatchTable, false)
+            { }
+
+            protected ChordDispatchTable(IEnumerable<KeyValuePair<PSKeyInfo, KeyHandlerOrChordDispatchTable>> dispatchTable, bool viMode)
+            {
+                _dispatchTable = dispatchTable.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+                _viMode = viMode;
+            }
+
+            public bool InViMode
+                => _viMode;
+
+            public KeyHandlerOrChordDispatchTable this[PSKeyInfo key]
+            {
+                get => _dispatchTable[key];
+                set => _dispatchTable[key] = value;
+            }
+
+            public ICollection<PSKeyInfo> Keys
+                => _dispatchTable.Keys;
+
+            public ICollection<KeyHandlerOrChordDispatchTable> Values
+                => _dispatchTable.Values;
+
+            public int Count
+                => _dispatchTable.Count;
+
+            public bool IsReadOnly
+                => _dispatchTable.IsReadOnly;
+
+            public void Add(PSKeyInfo key, KeyHandlerOrChordDispatchTable value)
+                => _dispatchTable.Add(key, value);
+
+            public void Add(KeyValuePair<PSKeyInfo, KeyHandlerOrChordDispatchTable> item)
+                => _dispatchTable.Add(item);
+
+            public void Clear()
+                => _dispatchTable.Clear();
+
+            public bool Contains(KeyValuePair<PSKeyInfo, KeyHandlerOrChordDispatchTable> item)
+                => _dispatchTable.Contains(item);
+
+            public bool ContainsKey(PSKeyInfo key)
+                => _dispatchTable.ContainsKey(key);
+
+            public void CopyTo(KeyValuePair<PSKeyInfo, KeyHandlerOrChordDispatchTable>[] array, int arrayIndex)
+                => _dispatchTable.CopyTo(array, arrayIndex);
+
+            public IEnumerator<KeyValuePair<PSKeyInfo, KeyHandlerOrChordDispatchTable>> GetEnumerator()
+                => _dispatchTable.GetEnumerator();
+
+            public bool Remove(PSKeyInfo key)
+                => _dispatchTable.Remove(key);
+
+            public bool Remove(KeyValuePair<PSKeyInfo, KeyHandlerOrChordDispatchTable> item)
+                => _dispatchTable.Remove(item);
+
+            public bool TryGetValue(
+                PSKeyInfo key,
+#if NET6_0_OR_GREATER
+                [MaybeNullWhen(false)]
+#endif
+                out KeyHandlerOrChordDispatchTable value
+                )
+                => _dispatchTable.TryGetValue(key, out value);
+
+            IEnumerator IEnumerable.GetEnumerator()
+                => ((IEnumerable)_dispatchTable).GetEnumerator();
+        }
+
+        /// <summary>
+        /// Represents a <cee cref="ChordDispatchTable" /> that
+        /// handles vi-specific key bindings like handling digit
+        /// arguments after the first key.
+        /// </summary>
+        private sealed class ViChordDispatchTable : ChordDispatchTable
+        {
+            public ViChordDispatchTable(IEnumerable<KeyValuePair<PSKeyInfo, KeyHandlerOrChordDispatchTable>> dispatchTable)
+                : base(dispatchTable, viMode: true)
+            {
+            }
+        }
+
+        private sealed class KeyHandlerOrChordDispatchTable
+        {
+            private readonly KeyHandler _keyHandler = null;
+            private readonly ChordDispatchTable _chordDispatchTable = null;
+
+            /// <summary>
+            /// Initialize a new instance of the <see cref="KeyHandlerOrChordDispatchTable"/> class
+            /// that acts as a key handler.
+            /// </summary>
+            /// <param name="keyHandler"></param>
+            public KeyHandlerOrChordDispatchTable(KeyHandler keyHandler)
+            {
+                _keyHandler = keyHandler;
+            }
+
+            /// <summary>
+            /// Initialize a new instance of the <see cref="KeyHandlerOrChordDispatchTable"/> class
+            /// that acts as a chord dispatch table.
+            /// </summary>
+            /// <param name="chordDispatchTable"></param>
+            public KeyHandlerOrChordDispatchTable(IEnumerable<KeyValuePair<PSKeyInfo, KeyHandlerOrChordDispatchTable>> chordDispatchTable)
+            {
+                _chordDispatchTable = new ChordDispatchTable(chordDispatchTable);
+            }
+            public KeyHandlerOrChordDispatchTable(ChordDispatchTable chordDispatchTable)
+            {
+                _chordDispatchTable = chordDispatchTable;
+            }
+
+            public static implicit operator KeyHandlerOrChordDispatchTable(KeyHandler handler)
+                => new KeyHandlerOrChordDispatchTable(handler);
+
+            public static implicit operator KeyHandlerOrChordDispatchTable(ChordDispatchTable chordDispatchTable)
+                => new KeyHandlerOrChordDispatchTable(chordDispatchTable);
+
+            public bool TryGetKeyHandler(out KeyHandler keyHandler)
+            {
+                keyHandler = _keyHandler;
+                return (_keyHandler != null);
+            }
+
+            public static explicit operator ChordDispatchTable(KeyHandlerOrChordDispatchTable handlerOrChordDispatchTable)
+            {
+                if (handlerOrChordDispatchTable._chordDispatchTable == null)
+                    throw new InvalidCastException();
+                return handlerOrChordDispatchTable._chordDispatchTable;
+            }
+        }
 
         /// <summary>
         /// Helper to set bindings based on EditMode
@@ -176,7 +324,7 @@ void SetDefaultBindings(EditMode editMode)
 
         void SetDefaultWindowsBindings()
         {
-            _dispatchTable = new Dictionary<PSKeyInfo, KeyHandler>
+            _dispatchTable = MakeChordDispatchTable(new Dictionary<PSKeyInfo, KeyHandlerOrChordDispatchTable>
             {
                 { Keys.Enter,                  MakeKeyHandler(AcceptLine,                "AcceptLine") },
                 { Keys.ShiftEnter,             MakeKeyHandler(AddLine,                   "AddLine") },
@@ -242,7 +390,7 @@ void SetDefaultWindowsBindings()
                 { Keys.AltD,                   MakeKeyHandler(KillWord,                  "KillWord") },
                 { Keys.CtrlAt,                 MakeKeyHandler(MenuComplete,              "MenuComplete") },
                 { Keys.CtrlW,                  MakeKeyHandler(BackwardKillWord,          "BackwardKillWord") },
-            };
+            });
 
             // Some bindings are not available on certain platforms
             if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -260,13 +408,11 @@ void SetDefaultWindowsBindings()
                 _dispatchTable.Add(Keys.CtrlPageUp,   MakeKeyHandler(ScrollDisplayUpLine,   "ScrollDisplayUpLine"));
                 _dispatchTable.Add(Keys.CtrlPageDown, MakeKeyHandler(ScrollDisplayDownLine, "ScrollDisplayDownLine"));
             }
-
-            _chordDispatchTable = new Dictionary<PSKeyInfo, Dictionary<PSKeyInfo, KeyHandler>>();
         }
 
         void SetDefaultEmacsBindings()
         {
-            _dispatchTable = new Dictionary<PSKeyInfo, KeyHandler>
+            _dispatchTable = MakeChordDispatchTable(new Dictionary<PSKeyInfo, KeyHandlerOrChordDispatchTable>
             {
                 { Keys.Backspace,              MakeKeyHandler(BackwardDeleteChar,        "BackwardDeleteChar") },
                 { Keys.Enter,                  MakeKeyHandler(AcceptLine,                "AcceptLine") },
@@ -283,7 +429,23 @@ void SetDefaultEmacsBindings()
                 { Keys.End,                    MakeKeyHandler(EndOfLine,                 "EndOfLine") },
                 { Keys.ShiftHome,              MakeKeyHandler(SelectBackwardsLine,       "SelectBackwardsLine") },
                 { Keys.ShiftEnd,               MakeKeyHandler(SelectLine,                "SelectLine") },
-                { Keys.Escape,                 MakeKeyHandler(Chord,                     "ChordFirstKey") },
+                { Keys.Escape,                 MakeChordDispatchTable(new Dictionary<PSKeyInfo, PSConsoleReadLine.KeyHandlerOrChordDispatchTable>{
+
+                    // Escape,<key> table (meta key)
+                    { Keys.B,         MakeKeyHandler(BackwardWord,     "BackwardWord") },
+                    { Keys.D,         MakeKeyHandler(KillWord,         "KillWord")},
+                    { Keys.F,         MakeKeyHandler(ForwardWord,      "ForwardWord")},
+                    { Keys.R,         MakeKeyHandler(RevertLine,       "RevertLine")},
+                    { Keys.Y,         MakeKeyHandler(YankPop,          "YankPop")},
+                    { Keys.U,         MakeKeyHandler(UpcaseWord,       "UpcaseWord") },
+                    { Keys.L,         MakeKeyHandler(DowncaseWord,     "DowncaseWord") },
+                    { Keys.C,         MakeKeyHandler(CapitalizeWord,   "CapitalizeWord") },
+                    { Keys.CtrlY,     MakeKeyHandler(YankNthArg,       "YankNthArg")},
+                    { Keys.Backspace, MakeKeyHandler(BackwardKillWord, "BackwardKillWord")},
+                    { Keys.Period,    MakeKeyHandler(YankLastArg,      "YankLastArg")},
+                    { Keys.Underbar,  MakeKeyHandler(YankLastArg,      "YankLastArg")},
+                }) },
+
                 { Keys.Delete,                 MakeKeyHandler(DeleteChar,                "DeleteChar") },
                 { Keys.Tab,                    MakeKeyHandler(Complete,                  "Complete") },
                 { Keys.CtrlA,                  MakeKeyHandler(BeginningOfLine,           "BeginningOfLine") },
@@ -303,7 +465,15 @@ void SetDefaultEmacsBindings()
                 { Keys.CtrlS,                  MakeKeyHandler(ForwardSearchHistory,      "ForwardSearchHistory") },
                 { Keys.CtrlT,                  MakeKeyHandler(SwapCharacters,            "SwapCharacters") },
                 { Keys.CtrlU,                  MakeKeyHandler(BackwardKillInput,         "BackwardKillInput") },
-                { Keys.CtrlX,                  MakeKeyHandler(Chord,                     "ChordFirstKey") },
+                { Keys.CtrlX,                  MakeChordDispatchTable(new Dictionary<PSKeyInfo, KeyHandlerOrChordDispatchTable>{
+
+                    // Ctrl+X,<key> table (meta key)
+                    { Keys.Backspace, MakeKeyHandler(BackwardKillInput,    "BackwardKillInput") },
+                    { Keys.CtrlE,     MakeKeyHandler(ViEditVisually,       "ViEditVisually") },
+                    { Keys.CtrlU,     MakeKeyHandler(Undo,                 "Undo") },
+                    { Keys.CtrlX,     MakeKeyHandler(ExchangePointAndMark, "ExchangePointAndMark") },
+                }) },
+
                 { Keys.CtrlW,                  MakeKeyHandler(UnixWordRubout,            "UnixWordRubout") },
                 { Keys.CtrlY,                  MakeKeyHandler(Yank,                      "Yank") },
                 { Keys.CtrlAt,                 MakeKeyHandler(SetMark,                   "SetMark") },
@@ -344,7 +514,7 @@ void SetDefaultEmacsBindings()
                 { Keys.AltU,                   MakeKeyHandler(UpcaseWord,                "UpcaseWord") },
                 { Keys.AltL,                   MakeKeyHandler(DowncaseWord,              "DowncaseWord") },
                 { Keys.AltC,                   MakeKeyHandler(CapitalizeWord,            "CapitalizeWord") },
-            };
+            });
 
             // Some bindings are not available on certain platforms
             if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -365,35 +535,6 @@ void SetDefaultEmacsBindings()
             {
                 _dispatchTable.Add(Keys.AltSpace,     MakeKeyHandler(SetMark,               "SetMark"));
             }
-
-            _chordDispatchTable = new Dictionary<PSKeyInfo, Dictionary<PSKeyInfo, KeyHandler>>
-            {
-                // Escape,<key> table (meta key)
-                [Keys.Escape] = new Dictionary<PSKeyInfo, KeyHandler>
-                {
-                    { Keys.B,         MakeKeyHandler(BackwardWord,     "BackwardWord") },
-                    { Keys.D,         MakeKeyHandler(KillWord,         "KillWord")},
-                    { Keys.F,         MakeKeyHandler(ForwardWord,      "ForwardWord")},
-                    { Keys.R,         MakeKeyHandler(RevertLine,       "RevertLine")},
-                    { Keys.Y,         MakeKeyHandler(YankPop,          "YankPop")},
-                    { Keys.U,         MakeKeyHandler(UpcaseWord,       "UpcaseWord") },
-                    { Keys.L,         MakeKeyHandler(DowncaseWord,     "DowncaseWord") },
-                    { Keys.C,         MakeKeyHandler(CapitalizeWord,   "CapitalizeWord") },
-                    { Keys.CtrlY,     MakeKeyHandler(YankNthArg,       "YankNthArg")},
-                    { Keys.Backspace, MakeKeyHandler(BackwardKillWord, "BackwardKillWord")},
-                    { Keys.Period,    MakeKeyHandler(YankLastArg,      "YankLastArg")},
-                    { Keys.Underbar,  MakeKeyHandler(YankLastArg,      "YankLastArg")},
-                },
-
-                // Ctrl+X,<key> table
-                [Keys.CtrlX] = new Dictionary<PSKeyInfo, KeyHandler>
-                {
-                    { Keys.Backspace, MakeKeyHandler(BackwardKillInput,    "BackwardKillInput") },
-                    { Keys.CtrlE,     MakeKeyHandler(ViEditVisually,       "ViEditVisually") },
-                    { Keys.CtrlU,     MakeKeyHandler(Undo,                 "Undo") },
-                    { Keys.CtrlX,     MakeKeyHandler(ExchangePointAndMark, "ExchangePointAndMark") },
-                }
-            };
         }
 
         /// <summary>
@@ -429,7 +570,6 @@ public static KeyHandlerGroup GetDisplayGrouping(string function)
             case nameof(DeletePreviousLines):
             case nameof(DeleteRelativeLines):
             case nameof(DeleteToEnd):
-            case nameof(DeleteWord):
             case nameof(DowncaseWord):
             case nameof(ForwardDeleteInput):
             case nameof(ForwardDeleteLine):
@@ -588,7 +728,6 @@ public static KeyHandlerGroup GetDisplayGrouping(string function)
             case nameof(ViEditVisually):
             case nameof(ViExit):
             case nameof(ViInsertMode):
-            case nameof(ViDGChord):
             case nameof(WhatIsKey):
             case nameof(ShowCommandHelp):
             case nameof(ShowParameterHelp):
@@ -623,6 +762,10 @@ public static KeyHandlerGroup GetDisplayGrouping(string function)
             case nameof(SelectCommandArgument):
                 return KeyHandlerGroup.Selection;
 
+            case nameof(ViDeleteInnerWord):
+            case nameof(DeleteWord):
+                return KeyHandlerGroup.TextObjects;
+
             default:
                 return KeyHandlerGroup.Custom;
             }
@@ -682,54 +825,74 @@ public static void WhatIsKey(ConsoleKeyInfo? key = null, object arg = null)
         {
             _singleton._statusLinePrompt = "what-is-key: ";
             _singleton.Render();
-            var toLookup = ReadKey();
+
+            var buffer = ReadKeyChord();
+
+            _singleton.ClearStatusMessage(render: false);
+
+            var console = _singleton._console;
+            // Don't overwrite any of the line - so move to first line after the end of our buffer.
+            var point = _singleton.EndOfBufferPosition();
+            console.SetCursorPosition(point.X, point.Y);
+            console.Write("\n");
+
+            console.WriteLine(buffer.ToString());
+            InvokePrompt(key: null, arg: console.CursorTop);
+        }
+
+        private static StringBuilder ReadKeyChord()
+        {
             var buffer = new StringBuilder();
-            _singleton._dispatchTable.TryGetValue(toLookup, out var keyHandler);
+
+            var toLookup = ReadKey();
+
             buffer.Append(toLookup.KeyStr);
-            if (keyHandler != null)
+
+            if (_singleton._dispatchTable.TryGetValue(toLookup, out var handlerOrChordDispatchTable))
             {
-                if (keyHandler.BriefDescription == "ChordFirstKey")
+                KeyHandler keyHandler = null;
+
+                while (handlerOrChordDispatchTable != null && !handlerOrChordDispatchTable.TryGetKeyHandler(out keyHandler))
                 {
-                    if (_singleton._chordDispatchTable.TryGetValue(toLookup, out var secondKeyDispatchTable))
+                    toLookup = ReadKey();
+
+                    buffer.Append(",");
+                    buffer.Append(toLookup.KeyStr);
+
+                    var secondKeyDispatchTable = (ChordDispatchTable)handlerOrChordDispatchTable;
+                    secondKeyDispatchTable.TryGetValue(toLookup, out handlerOrChordDispatchTable);
+                }
+
+                if (keyHandler != null)
+                {
+                    buffer.Append(": ");
+                    buffer.Append(keyHandler.BriefDescription);
+                    if (!string.IsNullOrWhiteSpace(keyHandler.LongDescription))
                     {
-                        toLookup = ReadKey();
-                        secondKeyDispatchTable.TryGetValue(toLookup, out keyHandler);
-                        buffer.Append(",");
-                        buffer.Append(toLookup.KeyStr);
+                        buffer.Append(" - ");
+                        buffer.Append(keyHandler.LongDescription);
                     }
                 }
-            }
-            buffer.Append(": ");
-            if (keyHandler != null)
-            {
-                buffer.Append(keyHandler.BriefDescription);
-                if (!string.IsNullOrWhiteSpace(keyHandler.LongDescription))
+                else
                 {
-                    buffer.Append(" - ");
-                    buffer.Append(keyHandler.LongDescription);
+                    buffer.Append(": ");
+                    buffer.Append(PSReadLineResources.KeyIsUnbound);
                 }
             }
             else if (toLookup.KeyChar != 0)
             {
+                buffer.Append(": ");
                 buffer.Append("SelfInsert");
                 buffer.Append(" - ");
                 buffer.Append(PSReadLineResources.SelfInsertDescription);
             }
             else
             {
+                buffer.Append(": ");
                 buffer.Append(PSReadLineResources.KeyIsUnbound);
             }
 
-            _singleton.ClearStatusMessage(render: false);
-
-            var console = _singleton._console;
-            // Don't overwrite any of the line - so move to first line after the end of our buffer.
-            var point = _singleton.EndOfBufferPosition();
-            console.SetCursorPosition(point.X, point.Y);
-            console.Write("\n");
-
-            console.WriteLine(buffer.ToString());
-            InvokePrompt(key: null, arg: console.CursorTop);
+            return buffer;
         }
     }
 }
diff --git a/PSReadLine/KeyBindings.vi.cs b/PSReadLine/KeyBindings.vi.cs
index 5a47c660..0e7084aa 100644
--- a/PSReadLine/KeyBindings.vi.cs
+++ b/PSReadLine/KeyBindings.vi.cs
@@ -37,65 +37,55 @@ internal static ConsoleColor AlternateBackground(ConsoleColor bg)
 
         private int _normalCursorSize = 10;
 
-        private static Dictionary<PSKeyInfo, KeyHandler> _viInsKeyMap;
-        private static Dictionary<PSKeyInfo, KeyHandler> _viCmdKeyMap;
-        private static Dictionary<PSKeyInfo, KeyHandler> _viChordDTable;
-        private static Dictionary<PSKeyInfo, KeyHandler> _viChordGTable;
-        private static Dictionary<PSKeyInfo, KeyHandler> _viChordCTable;
-        private static Dictionary<PSKeyInfo, KeyHandler> _viChordYTable;
-        private static Dictionary<PSKeyInfo, KeyHandler> _viChordDGTable;
-
-        private static Dictionary<PSKeyInfo, KeyHandler> _viChordTextObjectsTable;
-
-        private static Dictionary<PSKeyInfo, Dictionary<PSKeyInfo, KeyHandler>> _viCmdChordTable;
-        private static Dictionary<PSKeyInfo, Dictionary<PSKeyInfo, KeyHandler>> _viInsChordTable;
+        private static ChordDispatchTable _viInsKeyMap;
+        private static ChordDispatchTable _viCmdKeyMap;
 
         /// <summary>
         /// Sets up the key bindings for vi operations.
         /// </summary>
         private void SetDefaultViBindings()
         {
-            _viInsKeyMap = new Dictionary<PSKeyInfo, KeyHandler>
+            _viInsKeyMap = MakeChordDispatchTable(new Dictionary<PSKeyInfo, KeyHandlerOrChordDispatchTable>
             {
-                { Keys.Enter,           MakeKeyHandler(AcceptLine,             "AcceptLine" ) },
-                { Keys.CtrlD,           MakeKeyHandler(ViAcceptLineOrExit,     "ViAcceptLineOrExit" ) },
-                { Keys.ShiftEnter,      MakeKeyHandler(AddLine,                "AddLine") },
-                { Keys.Escape,          MakeKeyHandler(ViCommandMode,          "ViCommandMode") },
-                { Keys.LeftArrow,       MakeKeyHandler(ViBackwardChar,         "ViBackwardChar") },
-                { Keys.RightArrow,      MakeKeyHandler(ViForwardChar,          "ViForwardChar") },
-                { Keys.CtrlLeftArrow,   MakeKeyHandler(BackwardWord,           "BackwardWord") },
-                { Keys.CtrlRightArrow,  MakeKeyHandler(NextWord,               "NextWord") },
-                { Keys.UpArrow,         MakeKeyHandler(PreviousHistory,        "PreviousHistory") },
-                { Keys.DownArrow,       MakeKeyHandler(NextHistory,            "NextHistory") },
-                { Keys.Home,            MakeKeyHandler(BeginningOfLine,        "BeginningOfLine") },
-                { Keys.End,             MakeKeyHandler(EndOfLine,              "EndOfLine") },
-                { Keys.Delete,          MakeKeyHandler(DeleteChar,             "DeleteChar") },
-                { Keys.Backspace,       MakeKeyHandler(BackwardDeleteChar,     "BackwardDeleteChar") },
-                { Keys.CtrlSpace,       MakeKeyHandler(PossibleCompletions,    "PossibleCompletions") },
-                { Keys.Tab,             MakeKeyHandler(ViTabCompleteNext,      "ViTabCompleteNext") },
-                { Keys.ShiftTab,        MakeKeyHandler(ViTabCompletePrevious,  "ViTabCompletePrevious") },
-                { Keys.CtrlU,           MakeKeyHandler(BackwardDeleteInput,    "BackwardDeleteInput") },
-                { Keys.CtrlV,           MakeKeyHandler(Paste,                  "Paste") },
-                { Keys.CtrlC,           MakeKeyHandler(CancelLine,             "CancelLine") },
-                { Keys.CtrlL,           MakeKeyHandler(ClearScreen,            "ClearScreen") },
-                { Keys.CtrlT,           MakeKeyHandler(SwapCharacters,         "SwapCharacters") },
-                { Keys.CtrlY,           MakeKeyHandler(Redo,                   "Redo") },
-                { Keys.CtrlZ,           MakeKeyHandler(Undo,                   "Undo") },
-                { Keys.CtrlBackspace,   MakeKeyHandler(BackwardKillWord,       "BackwardKillWord") },
-                { Keys.CtrlEnd,         MakeKeyHandler(ForwardDeleteInput,     "ForwardDeleteInput") },
-                { Keys.CtrlHome,        MakeKeyHandler(BackwardDeleteInput,    "BackwardDeleteInput") },
-                { Keys.CtrlRBracket,    MakeKeyHandler(GotoBrace,              "GotoBrace") },
-                { Keys.F3,              MakeKeyHandler(CharacterSearch,        "CharacterSearch") },
-                { Keys.ShiftF3,         MakeKeyHandler(CharacterSearchBackward,"CharacterSearchBackward") },
-                { Keys.CtrlAltQuestion, MakeKeyHandler(ShowKeyBindings,        "ShowKeyBindings") },
-                { Keys.CtrlR,           MakeKeyHandler(ReverseSearchHistory,   "ReverseSearchHistory") },
-                { Keys.CtrlS,           MakeKeyHandler(ForwardSearchHistory,   "ForwardSearchHistory") },
-                { Keys.CtrlG,           MakeKeyHandler(Abort,                  "Abort") },
-                { Keys.AltH,            MakeKeyHandler(ShowParameterHelp,      "ShowParameterHelp") },
-                { Keys.F1,              MakeKeyHandler(ShowCommandHelp,        "ShowCommandHelp") },
-                { Keys.F2,              MakeKeyHandler(SwitchPredictionView,   "SwitchPredictionView") },
-                { Keys.F4,              MakeKeyHandler(ShowFullPredictionTooltip, "ShowFullPredictionTooltip") },
-            };
+                { Keys.Enter,           MakeKeyHandler(AcceptLine,                  "AcceptLine" ) },
+                { Keys.CtrlD,           MakeKeyHandler(ViAcceptLineOrExit,          "ViAcceptLineOrExit" ) },
+                { Keys.ShiftEnter,      MakeKeyHandler(AddLine,                     "AddLine") },
+                { Keys.Escape,          MakeKeyHandler(ViCommandMode,               "ViCommandMode") },
+                { Keys.LeftArrow,       MakeKeyHandler(ViBackwardChar,              "ViBackwardChar") },
+                { Keys.RightArrow,      MakeKeyHandler(ViForwardChar,               "ViForwardChar") },
+                { Keys.CtrlLeftArrow,   MakeKeyHandler(BackwardWord,                "BackwardWord") },
+                { Keys.CtrlRightArrow,  MakeKeyHandler(NextWord,                    "NextWord") },
+                { Keys.UpArrow,         MakeKeyHandler(PreviousHistory,             "PreviousHistory") },
+                { Keys.DownArrow,       MakeKeyHandler(NextHistory,                 "NextHistory") },
+                { Keys.Home,            MakeKeyHandler(BeginningOfLine,             "BeginningOfLine") },
+                { Keys.End,             MakeKeyHandler(EndOfLine,                   "EndOfLine") },
+                { Keys.Delete,          MakeKeyHandler(DeleteChar,                  "DeleteChar") },
+                { Keys.Backspace,       MakeKeyHandler(BackwardDeleteChar,          "BackwardDeleteChar") },
+                { Keys.CtrlSpace,       MakeKeyHandler(PossibleCompletions,         "PossibleCompletions") },
+                { Keys.Tab,             MakeKeyHandler(ViTabCompleteNext,           "ViTabCompleteNext") },
+                { Keys.ShiftTab,        MakeKeyHandler(ViTabCompletePrevious,       "ViTabCompletePrevious") },
+                { Keys.CtrlU,           MakeKeyHandler(BackwardDeleteInput,         "BackwardDeleteInput") },
+                { Keys.CtrlV,           MakeKeyHandler(Paste,                       "Paste") },
+                { Keys.CtrlC,           MakeKeyHandler(CancelLine,                  "CancelLine") },
+                { Keys.CtrlL,           MakeKeyHandler(ClearScreen,                 "ClearScreen") },
+                { Keys.CtrlT,           MakeKeyHandler(SwapCharacters,              "SwapCharacters") },
+                { Keys.CtrlY,           MakeKeyHandler(Redo,                        "Redo") },
+                { Keys.CtrlZ,           MakeKeyHandler(Undo,                        "Undo") },
+                { Keys.CtrlBackspace,   MakeKeyHandler(BackwardKillWord,            "BackwardKillWord") },
+                { Keys.CtrlEnd,         MakeKeyHandler(ForwardDeleteInput,          "ForwardDeleteInput") },
+                { Keys.CtrlHome,        MakeKeyHandler(BackwardDeleteInput,         "BackwardDeleteInput") },
+                { Keys.CtrlRBracket,    MakeKeyHandler(GotoBrace,                   "GotoBrace") },
+                { Keys.F3,              MakeKeyHandler(CharacterSearch,             "CharacterSearch") },
+                { Keys.ShiftF3,         MakeKeyHandler(CharacterSearchBackward,     "CharacterSearchBackward") },
+                { Keys.CtrlAltQuestion, MakeKeyHandler(ShowKeyBindings,             "ShowKeyBindings") },
+                { Keys.CtrlR,           MakeKeyHandler(ReverseSearchHistory,        "ReverseSearchHistory") },
+                { Keys.CtrlS,           MakeKeyHandler(ForwardSearchHistory,        "ForwardSearchHistory") },
+                { Keys.CtrlG,           MakeKeyHandler(Abort,                       "Abort") },
+                { Keys.AltH,            MakeKeyHandler(ShowParameterHelp,           "ShowParameterHelp") },
+                { Keys.F1,              MakeKeyHandler(ShowCommandHelp,             "ShowCommandHelp") },
+                { Keys.F2,              MakeKeyHandler(SwitchPredictionView,        "SwitchPredictionView") },
+                { Keys.F4,              MakeKeyHandler(ShowFullPredictionTooltip,   "ShowFullPredictionTooltip") },
+            });
 
             // Some bindings are not available on certain platforms
             if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -103,221 +93,208 @@ private void SetDefaultViBindings()
                 _viInsKeyMap.Add(Keys.CtrlDelete, MakeKeyHandler(KillWord, "KillWord"));
             }
 
-            _viCmdKeyMap = new Dictionary<PSKeyInfo, KeyHandler>
+            _viCmdKeyMap = MakeChordDispatchTable(new Dictionary<PSKeyInfo, KeyHandlerOrChordDispatchTable>
             {
-                { Keys.Enter,           MakeKeyHandler(ViAcceptLine,         "ViAcceptLine") },
-                { Keys.CtrlD,           MakeKeyHandler(ViAcceptLineOrExit,   "ViAcceptLineOrExit") },
-                { Keys.ShiftEnter,      MakeKeyHandler(AddLine,              "AddLine") },
-                { Keys.Escape,          MakeKeyHandler(Ding,                 "Ignore") },
-                { Keys.LeftArrow,       MakeKeyHandler(ViBackwardChar,       "ViBackwardChar") },
-                { Keys.RightArrow,      MakeKeyHandler(ViForwardChar,        "ViForwardChar") },
-                { Keys.Space,           MakeKeyHandler(ViForwardChar,        "ViForwardChar") },
-                { Keys.CtrlLeftArrow,   MakeKeyHandler(BackwardWord,         "BackwardWord") },
-                { Keys.CtrlRightArrow,  MakeKeyHandler(NextWord,             "NextWord") },
-                { Keys.UpArrow,         MakeKeyHandler(PreviousHistory,      "PreviousHistory") },
-                { Keys.DownArrow,       MakeKeyHandler(NextHistory,          "NextHistory") },
-                { Keys.Home,            MakeKeyHandler(BeginningOfLine,      "BeginningOfLine") },
-                { Keys.End,             MakeKeyHandler(MoveToEndOfLine,      "MoveToEndOfLine") },
-                { Keys.Delete,          MakeKeyHandler(DeleteChar,           "DeleteChar") },
-                { Keys.Backspace,       MakeKeyHandler(ViBackwardChar,       "ViBackwardChar") },
-                { Keys.CtrlSpace,       MakeKeyHandler(PossibleCompletions,  "PossibleCompletions") },
-                { Keys.Tab,             MakeKeyHandler(TabCompleteNext,      "TabCompleteNext") },
-                { Keys.ShiftTab,        MakeKeyHandler(TabCompletePrevious,  "TabCompletePrevious") },
-                { Keys.CtrlV,           MakeKeyHandler(Paste,                "Paste") },
-                { Keys.CtrlC,           MakeKeyHandler(CancelLine,           "CancelLine") },
-                { Keys.CtrlL,           MakeKeyHandler(ClearScreen,          "ClearScreen") },
-                { Keys.CtrlT,           MakeKeyHandler(SwapCharacters,       "SwapCharacters") },
-                { Keys.CtrlU,           MakeKeyHandler(BackwardDeleteInput,  "BackwardDeleteInput") },
-                { Keys.CtrlW,           MakeKeyHandler(BackwardDeleteWord,   "BackwardDeleteWord") },
-                { Keys.CtrlY,           MakeKeyHandler(Redo,                 "Redo") },
-                { Keys.CtrlZ,           MakeKeyHandler(Undo,                 "Undo") },
-                { Keys.CtrlBackspace,   MakeKeyHandler(BackwardKillWord,     "BackwardKillWord") },
-                { Keys.CtrlEnd,         MakeKeyHandler(ForwardDeleteInput,   "ForwardDeleteInput") },
-                { Keys.CtrlHome,        MakeKeyHandler(BackwardDeleteInput,  "BackwardDeleteInput") },
-                { Keys.CtrlRBracket,    MakeKeyHandler(GotoBrace,            "GotoBrace") },
-                { Keys.F3,              MakeKeyHandler(CharacterSearch,      "CharacterSearch") },
-                { Keys.ShiftF3,         MakeKeyHandler(CharacterSearchBackward, "CharacterSearchBackward") },
-                { Keys.A,               MakeKeyHandler(ViInsertWithAppend,   "ViInsertWithAppend") },
-                { Keys.B,               MakeKeyHandler(ViBackwardWord,       "ViBackwardWord") },
-                { Keys.C,               MakeKeyHandler(ViChord,              "ChordFirstKey") },
-                { Keys.D,               MakeKeyHandler(ViChord,              "ChordFirstKey") },
-                { Keys.E,               MakeKeyHandler(NextWordEnd,          "NextWordEnd") },
-                { Keys.F,               MakeKeyHandler(SearchChar,           "SearchChar") },
-                { Keys.G,               MakeKeyHandler(ViChord,              "ChordFirstKey") },
-                { Keys.H,               MakeKeyHandler(ViBackwardChar,       "ViBackwardChar") },
-                { Keys.I,               MakeKeyHandler(ViInsertMode,         "ViInsertMode") },
-                { Keys.J,               MakeKeyHandler(NextHistory,          "NextHistory") },
-                { Keys.K,               MakeKeyHandler(PreviousHistory,      "PreviousHistory") },
-                { Keys.L,               MakeKeyHandler(ViForwardChar,        "ViForwardChar") },
-                { Keys.M,               MakeKeyHandler(Ding,                 "Ignore") },
-                { Keys.N,               MakeKeyHandler(RepeatSearch,         "RepeatSearch") },
-                { Keys.O,               MakeKeyHandler(ViAppendLine,         "ViAppendLine") },
-                { Keys.P,               MakeKeyHandler(PasteAfter,           "PasteAfter") },
-                { Keys.Q,               MakeKeyHandler(Ding,                 "Ignore") },
-                { Keys.R,               MakeKeyHandler(ReplaceCharInPlace,   "ReplaceCharInPlace") },
-                { Keys.S,               MakeKeyHandler(ViInsertWithDelete,   "ViInsertWithDelete") },
-                { Keys.T,               MakeKeyHandler(SearchCharWithBackoff,"SearchCharWithBackoff") },
-                { Keys.U,               MakeKeyHandler(Undo,                 "Undo") },
-                { Keys.V,               MakeKeyHandler(ViEditVisually,       "ViEditVisually") },
-                { Keys.W,               MakeKeyHandler(ViNextWord,           "ViNextWord") },
-                { Keys.X,               MakeKeyHandler(DeleteChar,           "DeleteChar") },
-                { Keys.Y,               MakeKeyHandler(ViChord,              "ChordFirstKey") },
-                { Keys.Z,               MakeKeyHandler(Ding,                 "Ignore") },
-                { Keys.ucA,             MakeKeyHandler(ViInsertAtEnd,        "ViInsertAtEnd") },
-                { Keys.ucB,             MakeKeyHandler(ViBackwardGlob,       "ViBackwardGlob") },
-                { Keys.ucC,             MakeKeyHandler(ViReplaceToEnd,       "ViReplaceToEnd") },
-                { Keys.ucD,             MakeKeyHandler(DeleteToEnd,          "DeleteToEnd") },
-                { Keys.ucE,             MakeKeyHandler(ViEndOfGlob,          "ViEndOfGlob") },
-                { Keys.ucF,             MakeKeyHandler(SearchCharBackward,   "SearchCharBackward") },
-                { Keys.ucG,             MakeKeyHandler(MoveToLastLine,       "MoveToLastLine") },
-                { Keys.ucH,             MakeKeyHandler(Ding,                 "Ignore") },
-                { Keys.ucI,             MakeKeyHandler(ViInsertAtBegining,   "ViInsertAtBegining") },
-                { Keys.ucJ,             MakeKeyHandler(ViJoinLines,          "ViJoinLines") },
-                { Keys.ucK,             MakeKeyHandler(Ding,                 "Ignore") },
-                { Keys.ucL,             MakeKeyHandler(Ding,                 "Ignore") },
-                { Keys.ucM,             MakeKeyHandler(Ding,                 "Ignore") },
-                { Keys.ucN,             MakeKeyHandler(RepeatSearchBackward, "RepeatSearchBackward") },
-                { Keys.ucO,             MakeKeyHandler(ViInsertLine,         "ViInsertLine") },
-                { Keys.ucP,             MakeKeyHandler(PasteBefore,          "PasteBefore") },
-                { Keys.ucQ,             MakeKeyHandler(Ding,                 "Ignore") },
-                { Keys.ucR,             MakeKeyHandler(ViReplaceUntilEsc,    "ViReplaceUntilEsc") },
-                { Keys.ucS,             MakeKeyHandler(ViReplaceLine,        "ViReplaceLine") },
-                { Keys.ucT,             MakeKeyHandler(SearchCharBackwardWithBackoff, "SearchCharBackwardWithBackoff") },
-                { Keys.ucU,             MakeKeyHandler(UndoAll,              "UndoAll") },
-                { Keys.ucV,             MakeKeyHandler(Ding,                 "Ignore") },
-                { Keys.ucW,             MakeKeyHandler(ViNextGlob,           "ViNextGlob") },
-                { Keys.ucX,             MakeKeyHandler(BackwardDeleteChar,   "BackwardDeleteChar") },
-                { Keys.ucY,             MakeKeyHandler(Ding,                 "Ignore") },
-                { Keys.ucZ,             MakeKeyHandler(Ding,                 "Ignore") },
-                { Keys._0,              MakeKeyHandler(DigitArgument,        "DigitArgument") },
-                { Keys._1,              MakeKeyHandler(DigitArgument,        "DigitArgument") },
-                { Keys._2,              MakeKeyHandler(DigitArgument,        "DigitArgument") },
-                { Keys._3,              MakeKeyHandler(DigitArgument,        "DigitArgument") },
-                { Keys._4,              MakeKeyHandler(DigitArgument,        "DigitArgument") },
-                { Keys._5,              MakeKeyHandler(DigitArgument,        "DigitArgument") },
-                { Keys._6,              MakeKeyHandler(DigitArgument,        "DigitArgument") },
-                { Keys._7,              MakeKeyHandler(DigitArgument,        "DigitArgument") },
-                { Keys._8,              MakeKeyHandler(DigitArgument,        "DigitArgument") },
-                { Keys._9,              MakeKeyHandler(DigitArgument,        "DigitArgument") },
-                { Keys.Dollar,          MakeKeyHandler(MoveToEndOfLine,      "MoveToEndOfLine") },
-                { Keys.Percent,         MakeKeyHandler(ViGotoBrace,          "ViGotoBrace") },
-                { Keys.Pound,           MakeKeyHandler(PrependAndAccept,     "PrependAndAccept") },
-                { Keys.Pipe,            MakeKeyHandler(GotoColumn,           "GotoColumn") },
-                { Keys.Uphat,           MakeKeyHandler(GotoFirstNonBlankOfLine, "GotoFirstNonBlankOfLine") },
-                { Keys.Underbar,        MakeKeyHandler(GotoFirstNonBlankOfLine, "GotoFirstNonBlankOfLine") },
-                { Keys.Tilde,           MakeKeyHandler(InvertCase,           "InvertCase") },
-                { Keys.Slash,           MakeKeyHandler(ViSearchHistoryBackward, "ViSearchHistoryBackward") },
-                { Keys.Question,        MakeKeyHandler(SearchForward,        "SearchForward") },
-                { Keys.CtrlR,           MakeKeyHandler(ReverseSearchHistory, "ReverseSearchHistory") },
-                { Keys.CtrlS,           MakeKeyHandler(ForwardSearchHistory, "ForwardSearchHistory") },
-                { Keys.CtrlG,           MakeKeyHandler(Abort,                "Abort") },
-                { Keys.Plus,            MakeKeyHandler(NextHistory,          "NextHistory") },
-                { Keys.Minus,           MakeKeyHandler(PreviousHistory,      "PreviousHistory") },
-                { Keys.Period,          MakeKeyHandler(RepeatLastCommand,    "RepeatLastCommand") },
-                { Keys.Semicolon,       MakeKeyHandler(RepeatLastCharSearch, "RepeatLastCharSearch") },
-                { Keys.Comma,           MakeKeyHandler(RepeatLastCharSearchBackwards, "RepeatLastCharSearchBackwards") },
-                { Keys.AltH,            MakeKeyHandler(ShowParameterHelp,     "ShowParameterHelp") },
-                { Keys.F1,              MakeKeyHandler(ShowCommandHelp,       "ShowCommandHelp") },
-            };
 
-            // Some bindings are not available on certain platforms
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-            {
-                _viCmdKeyMap.Add(Keys.CtrlDelete, MakeKeyHandler(KillWord, "KillWord"));
-            }
+                { Keys.Enter,           MakeKeyHandler(ViAcceptLine,                        "ViAcceptLine") },
+                { Keys.CtrlD,           MakeKeyHandler(ViAcceptLineOrExit,                  "ViAcceptLineOrExit") },
+                { Keys.ShiftEnter,      MakeKeyHandler(AddLine,                             "AddLine") },
+                { Keys.Escape,          MakeKeyHandler(Ding,                                "Ignore") },
+                { Keys.LeftArrow,       MakeKeyHandler(ViBackwardChar,                      "ViBackwardChar") },
+                { Keys.RightArrow,      MakeKeyHandler(ViForwardChar,                       "ViForwardChar") },
+                { Keys.Space,           MakeKeyHandler(ViForwardChar,                       "ViForwardChar") },
+                { Keys.CtrlLeftArrow,   MakeKeyHandler(BackwardWord,                        "BackwardWord") },
+                { Keys.CtrlRightArrow,  MakeKeyHandler(NextWord,                            "NextWord") },
+                { Keys.UpArrow,         MakeKeyHandler(PreviousHistory,                     "PreviousHistory") },
+                { Keys.DownArrow,       MakeKeyHandler(NextHistory,                         "NextHistory") },
+                { Keys.Home,            MakeKeyHandler(BeginningOfLine,                     "BeginningOfLine") },
+                { Keys.End,             MakeKeyHandler(MoveToEndOfLine,                     "MoveToEndOfLine") },
+                { Keys.Delete,          MakeKeyHandler(DeleteChar,                          "DeleteChar") },
+                { Keys.Backspace,       MakeKeyHandler(ViBackwardChar,                      "ViBackwardChar") },
+                { Keys.CtrlSpace,       MakeKeyHandler(PossibleCompletions,                 "PossibleCompletions") },
+                { Keys.Tab,             MakeKeyHandler(TabCompleteNext,                     "TabCompleteNext") },
+                { Keys.ShiftTab,        MakeKeyHandler(TabCompletePrevious,                 "TabCompletePrevious") },
+                { Keys.CtrlV,           MakeKeyHandler(Paste,                               "Paste") },
+                { Keys.CtrlC,           MakeKeyHandler(CancelLine,                          "CancelLine") },
+                { Keys.CtrlL,           MakeKeyHandler(ClearScreen,                         "ClearScreen") },
+                { Keys.CtrlT,           MakeKeyHandler(SwapCharacters,                      "SwapCharacters") },
+                { Keys.CtrlU,           MakeKeyHandler(BackwardDeleteInput,                 "BackwardDeleteInput") },
+                { Keys.CtrlW,           MakeKeyHandler(BackwardDeleteWord,                  "BackwardDeleteWord") },
+                { Keys.CtrlY,           MakeKeyHandler(Redo,                                "Redo") },
+                { Keys.CtrlZ,           MakeKeyHandler(Undo,                                "Undo") },
+                { Keys.CtrlBackspace,   MakeKeyHandler(BackwardKillWord,                    "BackwardKillWord") },
+                { Keys.CtrlEnd,         MakeKeyHandler(ForwardDeleteInput,                  "ForwardDeleteInput") },
+                { Keys.CtrlHome,        MakeKeyHandler(BackwardDeleteInput,                 "BackwardDeleteInput") },
+                { Keys.CtrlRBracket,    MakeKeyHandler(GotoBrace,                           "GotoBrace") },
+                { Keys.F3,              MakeKeyHandler(CharacterSearch,                     "CharacterSearch") },
+                { Keys.ShiftF3,         MakeKeyHandler(CharacterSearchBackward,             "CharacterSearchBackward") },
+                { Keys.A,               MakeKeyHandler(ViInsertWithAppend,                  "ViInsertWithAppend") },
+                { Keys.B,               MakeKeyHandler(ViBackwardWord,                      "ViBackwardWord") },
+                { Keys.C,               MakeViChordDispatchTable(new Dictionary<PSKeyInfo, KeyHandlerOrChordDispatchTable>{
 
-            _viChordDTable = new Dictionary<PSKeyInfo, KeyHandler>
-            {
-                { Keys.D,               MakeKeyHandler( DeleteLine,                   "DeleteLine") },
-                { Keys.Underbar,        MakeKeyHandler( DeleteLine,                   "DeleteLine") },
-                { Keys.Dollar,          MakeKeyHandler( DeleteToEnd,                  "DeleteToEnd") },
-                { Keys.B,               MakeKeyHandler( BackwardDeleteWord,           "BackwardDeleteWord") },
-                { Keys.ucB,             MakeKeyHandler( ViBackwardDeleteGlob,         "ViBackwardDeleteGlob") },
-                { Keys.W,               MakeKeyHandler( DeleteWord,                   "DeleteWord") },
-                { Keys.ucW,             MakeKeyHandler( ViDeleteGlob,                 "ViDeleteGlob") },
-                { Keys.E,               MakeKeyHandler( DeleteEndOfWord,              "DeleteEndOfWord") },
-                { Keys.G,               MakeKeyHandler( ViDGChord,                    "ViDGChord") },
-                { Keys.ucG,             MakeKeyHandler( DeleteEndOfBuffer,            "DeleteEndOfBuffer") },
-                { Keys.ucE,             MakeKeyHandler( ViDeleteEndOfGlob,            "ViDeleteEndOfGlob") },
-                { Keys.H,               MakeKeyHandler( BackwardDeleteChar,           "BackwardDeleteChar") },
-                { Keys.I,               MakeKeyHandler( ViChordDeleteTextObject,      "ChordViTextObject") },               
-                { Keys.J,               MakeKeyHandler( DeleteNextLines,              "DeleteNextLines") },
-                { Keys.K,               MakeKeyHandler( DeletePreviousLines,          "DeletePreviousLines") },
-                { Keys.L,               MakeKeyHandler( DeleteChar,                   "DeleteChar") },
-                { Keys.Space,           MakeKeyHandler( DeleteChar,                   "DeleteChar") },
-                { Keys._0,              MakeKeyHandler( BackwardDeleteLine,           "BackwardDeleteLine") },
-                { Keys.Uphat,           MakeKeyHandler( DeleteLineToFirstChar,        "DeleteLineToFirstChar") },
-                { Keys.Percent,         MakeKeyHandler( ViDeleteBrace,                "ViDeleteBrace") },
-                { Keys.F,               MakeKeyHandler( ViDeleteToChar,               "ViDeleteToChar") },
-                { Keys.ucF,             MakeKeyHandler( ViDeleteToCharBackward,       "ViDeleteToCharBackward") },
-                { Keys.T,               MakeKeyHandler( ViDeleteToBeforeChar,         "ViDeleteToBeforeChar") },
-                { Keys.ucT,             MakeKeyHandler( ViDeleteToBeforeCharBackward, "ViDeleteToBeforeCharBackward") },
-            };
+                    { Keys.C,           MakeKeyHandler( ViReplaceLine,                      "ViReplaceLine") },
+                    { Keys.Dollar,      MakeKeyHandler( ViReplaceToEnd,                     "ViReplaceToEnd") },
+                    { Keys.B,           MakeKeyHandler( ViBackwardReplaceWord,              "ViBackwardReplaceWord") },
+                    { Keys.ucB,         MakeKeyHandler( ViBackwardReplaceGlob,              "ViBackwardReplaceGlob") },
+                    { Keys.W,           MakeKeyHandler( ViReplaceWord,                      "ViReplaceWord") },
+                    { Keys.ucW,         MakeKeyHandler( ViReplaceGlob,                      "ViReplaceGlob") },
+                    { Keys.E,           MakeKeyHandler( ViReplaceEndOfWord,                 "ViReplaceEndOfWord") },
+                    { Keys.ucE,         MakeKeyHandler( ViReplaceEndOfGlob,                 "ViReplaceEndOfGlob") },
+                    { Keys.H,           MakeKeyHandler( BackwardReplaceChar,                "BackwardReplaceChar") },
+                    { Keys.L,           MakeKeyHandler( ReplaceChar,                        "ReplaceChar") },
+                    { Keys.Space,       MakeKeyHandler( ReplaceChar,                        "ReplaceChar") },
+                    { Keys._0,          MakeKeyHandler( ViBackwardReplaceLine,              "ViBackwardReplaceLine") },
+                    { Keys.Uphat,       MakeKeyHandler( ViBackwardReplaceLineToFirstChar,   "ViBackwardReplaceLineToFirstChar") },
+                    { Keys.Percent,     MakeKeyHandler( ViReplaceBrace,                     "ViReplaceBrace") },
+                    { Keys.F,           MakeKeyHandler( ViReplaceToChar,                    "ViReplaceToChar") },
+                    { Keys.ucF,         MakeKeyHandler( ViReplaceToCharBackward,            "ViReplaceToCharBackward") },
+                    { Keys.T,           MakeKeyHandler( ViReplaceToBeforeChar,              "ViReplaceToBeforeChar") },
+                    { Keys.ucT,         MakeKeyHandler( ViReplaceToBeforeCharBackward,      "ViReplaceToBeforeCharBackward") },
+                }) },
 
-            _viChordCTable = new Dictionary<PSKeyInfo, KeyHandler>
-            {
-                { Keys.C,               MakeKeyHandler( ViReplaceLine,                    "ViReplaceLine") },
-                { Keys.Dollar,          MakeKeyHandler( ViReplaceToEnd,                   "ViReplaceToEnd") },
-                { Keys.B,               MakeKeyHandler( ViBackwardReplaceWord,            "ViBackwardReplaceWord") },
-                { Keys.ucB,             MakeKeyHandler( ViBackwardReplaceGlob,            "ViBackwardReplaceGlob") },
-                { Keys.W,               MakeKeyHandler( ViReplaceWord,                    "ViReplaceWord") },
-                { Keys.ucW,             MakeKeyHandler( ViReplaceGlob,                    "ViReplaceGlob") },
-                { Keys.E,               MakeKeyHandler( ViReplaceEndOfWord,               "ViReplaceEndOfWord") },
-                { Keys.ucE,             MakeKeyHandler( ViReplaceEndOfGlob,               "ViReplaceEndOfGlob") },
-                { Keys.H,               MakeKeyHandler( BackwardReplaceChar,              "BackwardReplaceChar") },
-                { Keys.L,               MakeKeyHandler( ReplaceChar,                      "ReplaceChar") },
-                { Keys.Space,           MakeKeyHandler( ReplaceChar,                      "ReplaceChar") },
-                { Keys._0,              MakeKeyHandler( ViBackwardReplaceLine,            "ViBackwardReplaceLine") },
-                { Keys.Uphat,           MakeKeyHandler( ViBackwardReplaceLineToFirstChar, "ViBackwardReplaceLineToFirstChar") },
-                { Keys.Percent,         MakeKeyHandler( ViReplaceBrace,                   "ViReplaceBrace") },
-                { Keys.F,               MakeKeyHandler( ViReplaceToChar,                  "ViReplaceToChar") },
-                { Keys.ucF,             MakeKeyHandler( ViReplaceToCharBackward,          "ViReplaceToCharBackward") },
-                { Keys.T,               MakeKeyHandler( ViReplaceToBeforeChar,            "ViReplaceToBeforeChar") },
-                { Keys.ucT,             MakeKeyHandler( ViReplaceToBeforeCharBackward,    "ViReplaceToBeforeCharBackward") },
-            };
+                { Keys.D,               MakeViChordDispatchTable(new Dictionary<PSKeyInfo, KeyHandlerOrChordDispatchTable>{
 
-            _viChordGTable = new Dictionary<PSKeyInfo, KeyHandler>
-            {
-                { Keys.G,               MakeKeyHandler( MoveToFirstLine,                    "MoveToFirstLine") },
-            };
+                    { Keys.D,           MakeKeyHandler( DeleteLine,                         "DeleteLine") },
+                    { Keys.Underbar,    MakeKeyHandler( DeleteLine,                         "DeleteLine") },
+                    { Keys.Dollar,      MakeKeyHandler( DeleteToEnd,                        "DeleteToEnd") },
+                    { Keys.B,           MakeKeyHandler( BackwardDeleteWord,                 "BackwardDeleteWord") },
+                    { Keys.ucB,         MakeKeyHandler( ViBackwardDeleteGlob,               "ViBackwardDeleteGlob") },
+                    { Keys.W,           MakeKeyHandler( DeleteWord,                         "DeleteWord") },
+                    { Keys.ucW,         MakeKeyHandler( ViDeleteGlob,                       "ViDeleteGlob") },
+                    { Keys.E,           MakeKeyHandler( DeleteEndOfWord,                    "DeleteEndOfWord") },
+                    { Keys.G,           MakeChordDispatchTable(new Dictionary<PSKeyInfo, KeyHandlerOrChordDispatchTable>{
 
-            _viChordYTable = new Dictionary<PSKeyInfo, KeyHandler>
-            {
-                { Keys.Y,               MakeKeyHandler( ViYankLine,            "ViYankLine") },
-                { Keys.Dollar,          MakeKeyHandler( ViYankToEndOfLine,     "ViYankToEndOfLine") },
-                { Keys.B,               MakeKeyHandler( ViYankPreviousWord,    "ViYankPreviousWord") },
-                { Keys.ucB,             MakeKeyHandler( ViYankPreviousGlob,    "ViYankPreviousGlob") },
-                { Keys.W,               MakeKeyHandler( ViYankNextWord,        "ViYankNextWord") },
-                { Keys.ucW,             MakeKeyHandler( ViYankNextGlob,        "ViYankNextGlob") },
-                { Keys.E,               MakeKeyHandler( ViYankEndOfWord,       "ViYankEndOfWord") },
-                { Keys.ucE,             MakeKeyHandler( ViYankEndOfGlob,       "ViYankEndOfGlob") },
-                { Keys.H,               MakeKeyHandler( ViYankLeft,            "ViYankLeft") },
-                { Keys.L,               MakeKeyHandler( ViYankRight,           "ViYankRight") },
-                { Keys.Space,           MakeKeyHandler( ViYankRight,           "ViYankRight") },
-                { Keys._0,              MakeKeyHandler( ViYankBeginningOfLine, "ViYankBeginningOfLine") },
-                { Keys.Uphat,           MakeKeyHandler( ViYankToFirstChar,     "ViYankToFirstChar") },
-                { Keys.Percent,         MakeKeyHandler( ViYankPercent,         "ViYankPercent") },
-            };
+                        { Keys.G,       MakeKeyHandler( DeleteRelativeLines,                "DeleteRelativeLines") },
+                    }) },
 
-            _viChordTextObjectsTable = new Dictionary<PSKeyInfo, KeyHandler>
-            {
-                { Keys.W,               MakeKeyHandler(ViHandleTextObject,     "WordTextObject")},
-            };
-            
-            _viChordDGTable = new Dictionary<PSKeyInfo, KeyHandler>
-            {
-                { Keys.G,               MakeKeyHandler( DeleteRelativeLines,   "DeleteRelativeLines") },
-            };
+                    { Keys.ucG,         MakeKeyHandler( DeleteEndOfBuffer,                  "DeleteEndOfBuffer") },
+                    { Keys.ucE,         MakeKeyHandler( ViDeleteEndOfGlob,                  "ViDeleteEndOfGlob") },
+                    { Keys.H,           MakeKeyHandler( BackwardDeleteChar,                 "BackwardDeleteChar") },
+                    { Keys.I,           MakeChordDispatchTable(new Dictionary<PSKeyInfo, KeyHandlerOrChordDispatchTable>{
 
-            _viCmdChordTable = new Dictionary<PSKeyInfo, Dictionary<PSKeyInfo, KeyHandler>>();
-            _viInsChordTable = new Dictionary<PSKeyInfo, Dictionary<PSKeyInfo, KeyHandler>>();
+                        { Keys.W,       MakeKeyHandler(ViDeleteInnerWord,                   "ViDeleteInnerWord")},
+                    }) },
+
+                    { Keys.J,           MakeKeyHandler( DeleteNextLines,                    "DeleteNextLines") },
+                    { Keys.K,           MakeKeyHandler( DeletePreviousLines,                "DeletePreviousLines") },
+                    { Keys.L,           MakeKeyHandler( DeleteChar,                         "DeleteChar") },
+                    { Keys.Space,       MakeKeyHandler( DeleteChar,                         "DeleteChar") },
+                    { Keys._0,          MakeKeyHandler( BackwardDeleteLine,                 "BackwardDeleteLine") },
+                    { Keys.Uphat,       MakeKeyHandler( DeleteLineToFirstChar,              "DeleteLineToFirstChar") },
+                    { Keys.Percent,     MakeKeyHandler( ViDeleteBrace,                      "ViDeleteBrace") },
+                    { Keys.F,           MakeKeyHandler( ViDeleteToChar,                     "ViDeleteToChar") },
+                    { Keys.ucF,         MakeKeyHandler( ViDeleteToCharBackward,             "ViDeleteToCharBackward") },
+                    { Keys.T,           MakeKeyHandler( ViDeleteToBeforeChar,               "ViDeleteToBeforeChar") },
+                    { Keys.ucT,         MakeKeyHandler( ViDeleteToBeforeCharBackward,       "ViDeleteToBeforeCharBackward") }
+                })},
+
+                { Keys.E,               MakeKeyHandler(NextWordEnd,                         "NextWordEnd") },
+                { Keys.F,               MakeKeyHandler(SearchChar,                          "SearchChar") },
+                { Keys.G,               MakeChordDispatchTable(new Dictionary<PSKeyInfo, KeyHandlerOrChordDispatchTable>{
+
+                    { Keys.G,           MakeKeyHandler( MoveToFirstLine,                    "MoveToFirstLine") },
+                }) },
+
+                { Keys.H,               MakeKeyHandler(ViBackwardChar,                      "ViBackwardChar") },
+                { Keys.I,               MakeKeyHandler(ViInsertMode,                        "ViInsertMode") },
+                { Keys.J,               MakeKeyHandler(NextHistory,                         "NextHistory") },
+                { Keys.K,               MakeKeyHandler(PreviousHistory,                     "PreviousHistory") },
+                { Keys.L,               MakeKeyHandler(ViForwardChar,                       "ViForwardChar") },
+                { Keys.M,               MakeKeyHandler(Ding,                                "Ignore") },
+                { Keys.N,               MakeKeyHandler(RepeatSearch,                        "RepeatSearch") },
+                { Keys.O,               MakeKeyHandler(ViAppendLine,                        "ViAppendLine") },
+                { Keys.P,               MakeKeyHandler(PasteAfter,                          "PasteAfter") },
+                { Keys.Q,               MakeKeyHandler(Ding,                                "Ignore") },
+                { Keys.R,               MakeKeyHandler(ReplaceCharInPlace,                  "ReplaceCharInPlace") },
+                { Keys.S,               MakeKeyHandler(ViInsertWithDelete,                  "ViInsertWithDelete") },
+                { Keys.T,               MakeKeyHandler(SearchCharWithBackoff,               "SearchCharWithBackoff") },
+                { Keys.U,               MakeKeyHandler(Undo,                                "Undo") },
+                { Keys.V,               MakeKeyHandler(ViEditVisually,                      "ViEditVisually") },
+                { Keys.W,               MakeKeyHandler(ViNextWord,                          "ViNextWord") },
+                { Keys.X,               MakeKeyHandler(DeleteChar,                          "DeleteChar") },
+                { Keys.Y,               MakeViChordDispatchTable(new Dictionary<PSKeyInfo, KeyHandlerOrChordDispatchTable>{
+
+                    { Keys.Y,           MakeKeyHandler( ViYankLine,                         "ViYankLine") },
+                    { Keys.Dollar,      MakeKeyHandler( ViYankToEndOfLine,                  "ViYankToEndOfLine") },
+                    { Keys.B,           MakeKeyHandler( ViYankPreviousWord,                 "ViYankPreviousWord") },
+                    { Keys.ucB,         MakeKeyHandler( ViYankPreviousGlob,                 "ViYankPreviousGlob") },
+                    { Keys.W,           MakeKeyHandler( ViYankNextWord,                     "ViYankNextWord") },
+                    { Keys.ucW,         MakeKeyHandler( ViYankNextGlob,                     "ViYankNextGlob") },
+                    { Keys.E,           MakeKeyHandler( ViYankEndOfWord,                    "ViYankEndOfWord") },
+                    { Keys.ucE,         MakeKeyHandler( ViYankEndOfGlob,                    "ViYankEndOfGlob") },
+                    { Keys.H,           MakeKeyHandler( ViYankLeft,                         "ViYankLeft") },
+                    { Keys.L,           MakeKeyHandler( ViYankRight,                        "ViYankRight") },
+                    { Keys.Space,       MakeKeyHandler( ViYankRight,                        "ViYankRight") },
+                    { Keys._0,          MakeKeyHandler( ViYankBeginningOfLine,              "ViYankBeginningOfLine") },
+                    { Keys.Uphat,       MakeKeyHandler( ViYankToFirstChar,                  "ViYankToFirstChar") },
+                    { Keys.Percent,     MakeKeyHandler( ViYankPercent,                      "ViYankPercent") },
+                }) },
+
+                { Keys.Z,               MakeKeyHandler(Ding,                                "Ignore") },
+                { Keys.ucA,             MakeKeyHandler(ViInsertAtEnd,                       "ViInsertAtEnd") },
+                { Keys.ucB,             MakeKeyHandler(ViBackwardGlob,                      "ViBackwardGlob") },
+                { Keys.ucC,             MakeKeyHandler(ViReplaceToEnd,                      "ViReplaceToEnd") },
+                { Keys.ucD,             MakeKeyHandler(DeleteToEnd,                         "DeleteToEnd") },
+                { Keys.ucE,             MakeKeyHandler(ViEndOfGlob,                         "ViEndOfGlob") },
+                { Keys.ucF,             MakeKeyHandler(SearchCharBackward,                  "SearchCharBackward") },
+                { Keys.ucG,             MakeKeyHandler(MoveToLastLine,                      "MoveToLastLine") },
+                { Keys.ucH,             MakeKeyHandler(Ding,                                "Ignore") },
+                { Keys.ucI,             MakeKeyHandler(ViInsertAtBegining,                  "ViInsertAtBegining") },
+                { Keys.ucJ,             MakeKeyHandler(ViJoinLines,                         "ViJoinLines") },
+                { Keys.ucK,             MakeKeyHandler(Ding,                                "Ignore") },
+                { Keys.ucL,             MakeKeyHandler(Ding,                                "Ignore") },
+                { Keys.ucM,             MakeKeyHandler(Ding,                                "Ignore") },
+                { Keys.ucN,             MakeKeyHandler(RepeatSearchBackward,                "RepeatSearchBackward") },
+                { Keys.ucO,             MakeKeyHandler(ViInsertLine,                        "ViInsertLine") },
+                { Keys.ucP,             MakeKeyHandler(PasteBefore,                         "PasteBefore") },
+                { Keys.ucQ,             MakeKeyHandler(Ding,                                "Ignore") },
+                { Keys.ucR,             MakeKeyHandler(ViReplaceUntilEsc,                   "ViReplaceUntilEsc") },
+                { Keys.ucS,             MakeKeyHandler(ViReplaceLine,                       "ViReplaceLine") },
+                { Keys.ucT,             MakeKeyHandler(SearchCharBackwardWithBackoff,       "SearchCharBackwardWithBackoff") },
+                { Keys.ucU,             MakeKeyHandler(UndoAll,                             "UndoAll") },
+                { Keys.ucV,             MakeKeyHandler(Ding,                                "Ignore") },
+                { Keys.ucW,             MakeKeyHandler(ViNextGlob,                          "ViNextGlob") },
+                { Keys.ucX,             MakeKeyHandler(BackwardDeleteChar,                  "BackwardDeleteChar") },
+                { Keys.ucY,             MakeKeyHandler(Ding,                                "Ignore") },
+                { Keys.ucZ,             MakeKeyHandler(Ding,                                "Ignore") },
+                { Keys._0,              MakeKeyHandler(DigitArgument,                       "DigitArgument") },
+                { Keys._1,              MakeKeyHandler(DigitArgument,                       "DigitArgument") },
+                { Keys._2,              MakeKeyHandler(DigitArgument,                       "DigitArgument") },
+                { Keys._3,              MakeKeyHandler(DigitArgument,                       "DigitArgument") },
+                { Keys._4,              MakeKeyHandler(DigitArgument,                       "DigitArgument") },
+                { Keys._5,              MakeKeyHandler(DigitArgument,                       "DigitArgument") },
+                { Keys._6,              MakeKeyHandler(DigitArgument,                       "DigitArgument") },
+                { Keys._7,              MakeKeyHandler(DigitArgument,                       "DigitArgument") },
+                { Keys._8,              MakeKeyHandler(DigitArgument,                       "DigitArgument") },
+                { Keys._9,              MakeKeyHandler(DigitArgument,                       "DigitArgument") },
+                { Keys.Dollar,          MakeKeyHandler(MoveToEndOfLine,                     "MoveToEndOfLine") },
+                { Keys.Percent,         MakeKeyHandler(ViGotoBrace,                         "ViGotoBrace") },
+                { Keys.Pound,           MakeKeyHandler(PrependAndAccept,                    "PrependAndAccept") },
+                { Keys.Pipe,            MakeKeyHandler(GotoColumn,                          "GotoColumn") },
+                { Keys.Uphat,           MakeKeyHandler(GotoFirstNonBlankOfLine,             "GotoFirstNonBlankOfLine") },
+                { Keys.Underbar,        MakeKeyHandler(GotoFirstNonBlankOfLine,             "GotoFirstNonBlankOfLine") },
+                { Keys.Tilde,           MakeKeyHandler(InvertCase,                          "InvertCase") },
+                { Keys.Slash,           MakeKeyHandler(ViSearchHistoryBackward,             "ViSearchHistoryBackward") },
+                { Keys.Question,        MakeKeyHandler(SearchForward,                       "SearchForward") },
+                { Keys.CtrlR,           MakeKeyHandler(ReverseSearchHistory,                "ReverseSearchHistory") },
+                { Keys.CtrlS,           MakeKeyHandler(ForwardSearchHistory,                "ForwardSearchHistory") },
+                { Keys.CtrlG,           MakeKeyHandler(Abort,                               "Abort") },
+                { Keys.Plus,            MakeKeyHandler(NextHistory,                         "NextHistory") },
+                { Keys.Minus,           MakeKeyHandler(PreviousHistory,                     "PreviousHistory") },
+                { Keys.Period,          MakeKeyHandler(RepeatLastCommand,                   "RepeatLastCommand") },
+                { Keys.Semicolon,       MakeKeyHandler(RepeatLastCharSearch,                "RepeatLastCharSearch") },
+                { Keys.Comma,           MakeKeyHandler(RepeatLastCharSearchBackwards,       "RepeatLastCharSearchBackwards") },
+                { Keys.AltH,            MakeKeyHandler(ShowParameterHelp,                   "ShowParameterHelp") },
+                { Keys.F1,              MakeKeyHandler(ShowCommandHelp,                     "ShowCommandHelp") },
+            });
+
+            // Some bindings are not available on certain platforms
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                _viCmdKeyMap.Add(Keys.CtrlDelete, MakeKeyHandler(KillWord, "KillWord"));
+            }
 
             _dispatchTable = _viInsKeyMap;
-            _chordDispatchTable = _viInsChordTable;
-            _viCmdChordTable[Keys.G] = _viChordGTable;
-            _viCmdChordTable[Keys.D] = _viChordDTable;
-            _viCmdChordTable[Keys.C] = _viChordCTable;
-            _viCmdChordTable[Keys.Y] = _viChordYTable;
 
             _normalCursorSize = _console.CursorSize;
             if ((_normalCursorSize < 1) || (_normalCursorSize > 100))
diff --git a/PSReadLine/Movement.cs b/PSReadLine/Movement.cs
index da52230e..ffa0a4a9 100644
--- a/PSReadLine/Movement.cs
+++ b/PSReadLine/Movement.cs
@@ -370,7 +370,7 @@ private static bool CheckIsBound(Action<ConsoleKeyInfo?, object> action)
         {
             foreach (var entry in _singleton._dispatchTable)
             {
-                if (entry.Value.Action == action)
+                if (entry.Value.TryGetKeyHandler(out var handler) && handler.Action == action)
                     return true;
             }
             return false;
diff --git a/PSReadLine/Options.cs b/PSReadLine/Options.cs
index 7485154b..cd920bb2 100644
--- a/PSReadLine/Options.cs
+++ b/PSReadLine/Options.cs
@@ -7,6 +7,7 @@
 using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
+using System.Linq;
 using System.Management.Automation;
 using System.Reflection;
 using System.Runtime.InteropServices;
@@ -68,10 +69,6 @@ private void SetOptionsInternal(SetPSReadLineOption options)
             if (options._editMode.HasValue)
             {
                 Options.EditMode = options.EditMode;
-
-                // Switching/resetting modes - clear out chord dispatch table
-                _chordDispatchTable.Clear();
-
                 SetDefaultBindings(Options.EditMode);
             }
             if (options._showToolTips.HasValue)
@@ -192,20 +189,34 @@ private void SetKeyHandlerInternal(string[] keys, Action<ConsoleKeyInfo?, object
             foreach (var key in keys)
             {
                 var chord = ConsoleKeyChordConverter.Convert(key);
-                var firstKey = PSKeyInfo.FromConsoleKeyInfo(chord[0]);
-                if (chord.Length == 1)
-                {
-                    _dispatchTable[firstKey] = MakeKeyHandler(handler, briefDescription, longDescription, scriptBlock);
-                }
-                else
+                var chordHandler = MakeKeyHandler(handler, briefDescription, longDescription, scriptBlock);
+
+                var chordDispatchTable = _dispatchTable;
+
+                for (var index = 0; index < chord.Length; index++)
                 {
-                    _dispatchTable[firstKey] = MakeKeyHandler(Chord, "ChordFirstKey");
-                    if (!_chordDispatchTable.TryGetValue(firstKey, out var secondDispatchTable))
+                    var anchorKey = PSKeyInfo.FromConsoleKeyInfo(chord[index]);
+
+                    var createDispatchTable = chord.Length - index > 1;
+                    if (createDispatchTable)
                     {
-                        secondDispatchTable = new Dictionary<PSKeyInfo, KeyHandler>();
-                        _chordDispatchTable[firstKey] = secondDispatchTable;
+                        var dispatchTableExists =
+                            chordDispatchTable.ContainsKey(anchorKey) &&
+                            !chordDispatchTable[anchorKey].TryGetKeyHandler(out var _)
+                            ;
+
+                        if (!dispatchTableExists)
+                        {
+                            chordDispatchTable[anchorKey] = new ChordDispatchTable(
+                                Enumerable.Empty<KeyValuePair<PSKeyInfo, KeyHandlerOrChordDispatchTable>>());
+                        }
+
+                        chordDispatchTable = (ChordDispatchTable)chordDispatchTable[anchorKey];
+                    }
+                    else
+                    {
+                        chordDispatchTable[anchorKey] = chordHandler;
                     }
-                    secondDispatchTable[PSKeyInfo.FromConsoleKeyInfo(chord[1])] = MakeKeyHandler(handler, briefDescription, longDescription, scriptBlock);
                 }
             }
         }
@@ -214,21 +225,23 @@ private void RemoveKeyHandlerInternal(string[] keys)
         {
             foreach (var key in keys)
             {
-                var chord = ConsoleKeyChordConverter.Convert(key);
-                var firstKey = PSKeyInfo.FromConsoleKeyInfo(chord[0]);
-                if (chord.Length == 1)
-                {
-                    _dispatchTable.Remove(firstKey);
-                }
-                else
+                var consoleKeyChord = ConsoleKeyChordConverter.Convert(key);
+                var dispatchTable = _singleton._dispatchTable;
+
+                for (var index = 0; index < consoleKeyChord.Length; index++)
                 {
-                    if (_chordDispatchTable.TryGetValue(firstKey, out var secondDispatchTable))
+                    var chordKey = PSKeyInfo.FromConsoleKeyInfo(consoleKeyChord[index]);
+                    if (!dispatchTable.TryGetValue(chordKey, out var handlerOrChordDispatchTable))
+                        continue;
+
+                    if (handlerOrChordDispatchTable.TryGetKeyHandler(out var keyHandler))
                     {
-                        secondDispatchTable.Remove(PSKeyInfo.FromConsoleKeyInfo(chord[1]));
-                        if (secondDispatchTable.Count == 0)
-                        {
-                            _dispatchTable.Remove(firstKey);
-                        }
+                        dispatchTable.Remove(chordKey);
+                    }
+
+                    else
+                    {
+                        dispatchTable = (ChordDispatchTable)handlerOrChordDispatchTable;
                     }
                 }
             }
@@ -321,92 +334,20 @@ public static void RemoveKeyHandler(string[] key)
         {
             var boundFunctions = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 
-            foreach (var entry in _singleton._dispatchTable)
-            {
-                if (entry.Value.BriefDescription == "Ignore"
-                    || entry.Value.BriefDescription == "ChordFirstKey")
-                {
-                    continue;
-                }
-                boundFunctions.Add(entry.Value.BriefDescription);
-                if (includeBound)
-                {
-                    yield return new PowerShell.KeyHandler
-                    {
-                        Key = entry.Key.KeyStr,
-                        Function = entry.Value.BriefDescription,
-                        Description = entry.Value.LongDescription,
-                        Group = GetDisplayGrouping(entry.Value.BriefDescription),
-                    };
-                }
-            }
+            var templates = new List<(ChordDispatchTable DispatchTable, string[] Surrounds)> {
+                (_singleton._dispatchTable, new[]{ "", "", }),
+            };
 
-            // Added to support vi command mode mappings
             if (_singleton._options.EditMode == EditMode.Vi)
             {
-                foreach (var entry in _viCmdKeyMap)
-                {
-                    if (entry.Value.BriefDescription == "Ignore"
-                        || entry.Value.BriefDescription == "ChordFirstKey")
-                    {
-                        continue;
-                    }
-                    boundFunctions.Add(entry.Value.BriefDescription);
-                    if (includeBound)
-                    {
-                        yield return new PowerShell.KeyHandler
-                        {
-                            Key = "<" + entry.Key.KeyStr + ">",
-                            Function = entry.Value.BriefDescription,
-                            Description = entry.Value.LongDescription,
-                            Group = GetDisplayGrouping(entry.Value.BriefDescription),
-                        };
-                    }
-                }
+                templates.Add((_viCmdKeyMap, new[] { "<", ">" }));
             }
 
-            foreach( var entry in _singleton._chordDispatchTable )
+            foreach (var template in templates)
             {
-                foreach( var secondEntry in entry.Value )
-                {
-                    boundFunctions.Add( secondEntry.Value.BriefDescription );
-                    if (includeBound)
-                    {
-                        yield return new PowerShell.KeyHandler
-                        {
-                            Key = entry.Key.KeyStr + "," + secondEntry.Key.KeyStr,
-                            Function = secondEntry.Value.BriefDescription,
-                            Description = secondEntry.Value.LongDescription,
-                            Group = GetDisplayGrouping(secondEntry.Value.BriefDescription),
-                        };
-                    }
-                }
-            }
-
-            // Added to support vi command mode chorded mappings
-            if (_singleton._options.EditMode == EditMode.Vi)
-            {
-                foreach (var entry in _viCmdChordTable)
-                {
-                    foreach (var secondEntry in entry.Value)
-                    {
-                        if (secondEntry.Value.BriefDescription == "Ignore")
-                        {
-                            continue;
-                        }
-                        boundFunctions.Add(secondEntry.Value.BriefDescription);
-                        if (includeBound)
-                        {
-                            yield return new PowerShell.KeyHandler
-                            {
-                                Key = "<" + entry.Key.KeyStr + "," + secondEntry.Key.KeyStr + ">",
-                                Function = secondEntry.Value.BriefDescription,
-                                Description = secondEntry.Value.LongDescription,
-                                Group = GetDisplayGrouping(secondEntry.Value.BriefDescription),
-                            };
-                        }
-                    }
-                }
+                var handlers = GetKeyHandlers("", template.Surrounds, template.DispatchTable, includeBound, ref boundFunctions);
+                foreach (var handler in handlers)
+                    yield return handler;
             }
 
             if (includeUnbound)
@@ -414,13 +355,13 @@ public static void RemoveKeyHandler(string[] key)
                 // SelfInsert isn't really unbound, but we don't want UI to show it that way
                 boundFunctions.Add("SelfInsert");
 
-                var methods = typeof (PSConsoleReadLine).GetMethods(BindingFlags.Public | BindingFlags.Static);
+                var methods = typeof(PSConsoleReadLine).GetMethods(BindingFlags.Public | BindingFlags.Static);
                 foreach (var method in methods)
                 {
                     var parameters = method.GetParameters();
                     if (parameters.Length != 2 ||
-                        parameters[0].ParameterType != typeof (ConsoleKeyInfo?) ||
-                        parameters[1].ParameterType != typeof (object))
+                        parameters[0].ParameterType != typeof(ConsoleKeyInfo?) ||
+                        parameters[1].ParameterType != typeof(object))
                     {
                         continue;
                     }
@@ -439,94 +380,114 @@ public static void RemoveKeyHandler(string[] key)
             }
         }
 
+        private static PowerShell.KeyHandler[] GetKeyHandlers(
+            string chordPrefix,
+            string[] surrounds,
+            ChordDispatchTable dispatchTable,
+            bool includeBound,
+            ref HashSet<string> boundFunctions
+        )
+        {
+            var keyHandlers = new List<PowerShell.KeyHandler>();
+
+            foreach (var entry in dispatchTable)
+            {
+                var handlerOrChordDispatchTable = entry.Value;
+
+                if (handlerOrChordDispatchTable.TryGetKeyHandler(out var keyHandler))
+                {
+                    boundFunctions.Add(keyHandler.BriefDescription);
+                    if (includeBound)
+                    {
+                        var handlerKey = chordPrefix.Length == 0
+                            ? entry.Key.KeyStr
+                            : chordPrefix.Substring(1) + "," + entry.Key.KeyStr
+                            ;
+
+                        keyHandlers.Add(new PowerShell.KeyHandler
+                        {
+                            Key = surrounds[0] + handlerKey + surrounds[1],
+                            Function = keyHandler.BriefDescription,
+                            Description = keyHandler.LongDescription,
+                            Group = GetDisplayGrouping(keyHandler.BriefDescription),
+                        });
+                    }
+                }
+
+                else
+                {
+                    keyHandlers.AddRange(
+                        GetKeyHandlers(
+                            chordPrefix + "," + entry.Key.KeyStr,
+                            surrounds,
+                            (ChordDispatchTable)handlerOrChordDispatchTable,
+                            includeBound,
+                            ref boundFunctions
+                        ));
+                }
+            }
+
+            return keyHandlers.ToArray();
+        }
+
         /// <summary>
         /// Return key handlers bound to specified chords.
         /// </summary>
         /// <returns></returns>
         public static IEnumerable<PowerShell.KeyHandler> GetKeyHandlers(string[] Chord)
         {
-            var boundFunctions = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
-
             if (Chord == null || Chord.Length == 0)
             {
                 yield break;
             }
 
-            foreach (string Key in Chord)
+            foreach (string key in Chord)
             {
-                ConsoleKeyInfo[] consoleKeyChord = ConsoleKeyChordConverter.Convert(Key);
-                PSKeyInfo firstKey = PSKeyInfo.FromConsoleKeyInfo(consoleKeyChord[0]);
+                ConsoleKeyInfo[] consoleKeyChord = ConsoleKeyChordConverter.Convert(key);
 
-                if (_singleton._dispatchTable.TryGetValue(firstKey, out KeyHandler entry))
-                {
-                    if (consoleKeyChord.Length == 1)
-                    {
-                        yield return new PowerShell.KeyHandler
-                        {
-                            Key = firstKey.KeyStr,
-                            Function = entry.BriefDescription,
-                            Description = entry.LongDescription,
-                            Group = GetDisplayGrouping(entry.BriefDescription),
-                        };
-                    }
-                    else
-                    {
-                        PSKeyInfo secondKey = PSKeyInfo.FromConsoleKeyInfo(consoleKeyChord[1]);
-                        if (_singleton._chordDispatchTable.TryGetValue(firstKey, out var secondDispatchTable) &&
-                            secondDispatchTable.TryGetValue(secondKey, out entry))
-                        {
-                            yield return new PowerShell.KeyHandler
-                            {
-                                Key = firstKey.KeyStr + "," + secondKey.KeyStr,
-                                Function = entry.BriefDescription,
-                                Description = entry.LongDescription,
-                                Group = GetDisplayGrouping(entry.BriefDescription),
-                            };
-                        }
-                    }
-                }
+                foreach (var handler in GetKeyHandlers(_singleton._dispatchTable, consoleKeyChord))
+                    yield return handler;
 
                 // If in Vi mode, also check Vi's command mode list.
                 if (_singleton._options.EditMode == EditMode.Vi)
                 {
-                    if (_viCmdKeyMap.TryGetValue(firstKey, out entry))
+                    foreach (var handler in GetKeyHandlers(_viCmdKeyMap, consoleKeyChord, new[] { "<", ">" }))
+                        yield return handler;
+                }
+            }
+        }
+
+        private static IEnumerable<PowerShell.KeyHandler> GetKeyHandlers(
+            ChordDispatchTable dispatchTable,
+            ConsoleKeyInfo[] consoleKeyChord,
+            string[] surrounds = null
+        )
+        {
+            surrounds ??= new[] { "", "" };
+            var handlerKey = "";
+
+            for (var index = 0; index < consoleKeyChord.Length; index++)
+            {
+                var chordKey = PSKeyInfo.FromConsoleKeyInfo(consoleKeyChord[index]);
+                if (!dispatchTable.TryGetValue(chordKey, out var handlerOrChordDispatchTable))
+                    continue;
+
+                handlerKey += "," + chordKey.KeyStr;
+
+                if (handlerOrChordDispatchTable.TryGetKeyHandler(out var keyHandler))
+                {
+                    yield return new PowerShell.KeyHandler
                     {
-                        if (consoleKeyChord.Length == 1)
-                        {
-                            if (entry.BriefDescription == "Ignore")
-                            {
-                                continue;
-                            }
-
-                            yield return new PowerShell.KeyHandler
-                            {
-                                Key = "<" + firstKey.KeyStr + ">",
-                                Function = entry.BriefDescription,
-                                Description = entry.LongDescription,
-                                Group = GetDisplayGrouping(entry.BriefDescription),
-                            };
-                        }
-                        else
-                        {
-                            PSKeyInfo secondKey = PSKeyInfo.FromConsoleKeyInfo(consoleKeyChord[1]);
-                            if (_viCmdChordTable.TryGetValue(firstKey, out var secondDispatchTable) &&
-                                secondDispatchTable.TryGetValue(secondKey, out entry))
-                            {
-                                if (entry.BriefDescription == "Ignore")
-                                {
-                                    continue;
-                                }
-
-                                yield return new PowerShell.KeyHandler
-                                {
-                                    Key = "<" + firstKey.KeyStr + "," + secondKey.KeyStr + ">",
-                                    Function = entry.BriefDescription,
-                                    Description = entry.LongDescription,
-                                    Group = GetDisplayGrouping(entry.BriefDescription),
-                                };
-                            }
-                        }
-                    }
+                        Key = surrounds[0] + handlerKey.Substring(1) + surrounds[1],
+                        Function = keyHandler.BriefDescription,
+                        Description = keyHandler.LongDescription,
+                        Group = GetDisplayGrouping(keyHandler.BriefDescription),
+                    };
+                }
+
+                else
+                {
+                    dispatchTable = (ChordDispatchTable)handlerOrChordDispatchTable;
                 }
             }
         }
diff --git a/PSReadLine/PSReadLineResources.Designer.cs b/PSReadLine/PSReadLineResources.Designer.cs
index ac672d3f..e102801c 100644
--- a/PSReadLine/PSReadLineResources.Designer.cs
+++ b/PSReadLine/PSReadLineResources.Designer.cs
@@ -368,6 +368,15 @@ internal static string CustomActionDescription {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Text objects functions.
+        /// </summary>
+        internal static string TextObjectsGrouping {
+            get {
+                return ResourceManager.GetString("TextObjectsGrouping", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to User defined functions.
         /// </summary>
@@ -1618,6 +1627,15 @@ internal static string ViDeleteGlobDescription {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Delete the content inside the current word..
+        /// </summary>
+        internal static string ViDeleteInnerWordDescription {
+            get {
+                return ResourceManager.GetString("ViDeleteInnerWordDescription", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Deletes until given character..
         /// </summary>
diff --git a/PSReadLine/PSReadLineResources.resx b/PSReadLine/PSReadLineResources.resx
index e619e7c0..9d65cb22 100644
--- a/PSReadLine/PSReadLineResources.resx
+++ b/PSReadLine/PSReadLineResources.resx
@@ -648,6 +648,9 @@ If there are other parse errors, unresolved commands, or incorrect parameters, s
   <data name="ViReplaceGlobDescription" xml:space="preserve">
     <value>Delete to the beginning of the next word, as delimited by white space, and enter insert mode.</value>
   </data>
+  <data name="ViDeleteInnerWordDescription" xml:space="preserve">
+    <value>Delete the content inside and including the current word</value>
+  </data>
   <data name="ViReplaceEndOfWordDescription" xml:space="preserve">
     <value>Delete to the end of the word, as delimited by white space and common delimiters, and enter insert mode.</value>
   </data>
@@ -801,6 +804,9 @@ Or not saving history with:
   <data name="SearchGrouping" xml:space="preserve">
     <value>Search functions</value>
   </data>
+  <data name="TextObjectsGrouping" xml:space="preserve">
+    <value>Text objects functions</value>
+  </data>
   <data name="CustomGrouping" xml:space="preserve">
     <value>User defined functions</value>
   </data>
diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs
index 02a0455b..d07e9c43 100644
--- a/PSReadLine/ReadLine.cs
+++ b/PSReadLine/ReadLine.cs
@@ -448,7 +448,8 @@ public static string ReadLine(
                         sb.Append(' ');
                         sb.Append(_lastNKeys[i].KeyStr);
 
-                        if (_singleton._dispatchTable.TryGetValue(_lastNKeys[i], out var handler) &&
+                        if (_singleton._dispatchTable.TryGetValue(_lastNKeys[i], out var handlerOrChordDispatchTable) &&
+                            handlerOrChordDispatchTable.TryGetKeyHandler(out var handler) &&
                             "AcceptLine".Equals(handler.BriefDescription, StringComparison.OrdinalIgnoreCase))
                         {
                             // Make it a little easier to see the keys
@@ -629,47 +630,114 @@ void CallPossibleExternalApplication(Action action)
             CallPossibleExternalApplication<object>(() => { action(); return null; });
         }
 
-        void ProcessOneKey(PSKeyInfo key, Dictionary<PSKeyInfo, KeyHandler> dispatchTable, bool ignoreIfNoAction, object arg)
+        void ProcessOneKey(PSKeyInfo key, ChordDispatchTable dispatchTable, bool ignoreIfNoAction, object arg)
+            => ProcessKeyChord(key, dispatchTable, ignoreIfNoAction, arg);
+        void ProcessKeyChord(PSKeyInfo key, ChordDispatchTable dispatchTable, bool ignoreIfNoAction, object arg)
         {
-            var consoleKey = key.AsConsoleKeyInfo();
+            KeyHandlerOrChordDispatchTable handlerOrChordDispatchTable = null;
 
-            // Our dispatch tables are built as much as possible in a portable way, so for example,
-            // we avoid depending on scan codes like ConsoleKey.Oem6 and instead look at the
-            // PSKeyInfo.Key. We also want to ignore the shift state as that may differ on
-            // different keyboard layouts.
-            //
-            // That said, we first look up exactly what we get from Console.ReadKey - that will fail
-            // most of the time, and when it does, we normalize the key.
-            if (!dispatchTable.TryGetValue(key, out var handler))
+            do
             {
-                // If we see a control character where Ctrl wasn't used but shift was, treat that like
-                // shift hadn't be pressed.  This cleanly allows Shift+Backspace without adding a key binding.
-                if (key.Shift && !key.Control && !key.Alt)
+                var consoleKey = key.AsConsoleKeyInfo();
+
+                // Our dispatch tables are built as much as possible in a portable way, so for example,
+                // we avoid depending on scan codes like ConsoleKey.Oem6 and instead look at the
+                // PSKeyInfo.Key. We also want to ignore the shift state as that may differ on
+                // different keyboard layouts.
+                //
+                // That said, we first look up exactly what we get from Console.ReadKey - that will fail
+                // most of the time, and when it does, we normalize the key.
+                if (!dispatchTable.TryGetValue(key, out handlerOrChordDispatchTable))
                 {
-                    var c = consoleKey.KeyChar;
-                    if (c != '\0' && char.IsControl(c))
+                    // If we see a control character where Ctrl wasn't used but shift was, treat that like
+                    // shift hadn't been pressed. This cleanly allows Shift+Backspace without adding a key binding.
+                    if (key.Shift && !key.Control && !key.Alt)
                     {
-                        key = PSKeyInfo.From(consoleKey.Key);
-                        dispatchTable.TryGetValue(key, out handler);
+                        var c = consoleKey.KeyChar;
+                        if (c != '\0' && char.IsControl(c))
+                        {
+                            key = PSKeyInfo.From(consoleKey.Key);
+                            dispatchTable.TryGetValue(key, out handlerOrChordDispatchTable);
+                        }
                     }
                 }
-            }
 
-            if (handler != null)
-            {
-                if (handler.ScriptBlock != null)
+                if (handlerOrChordDispatchTable != null && handlerOrChordDispatchTable.TryGetKeyHandler(out var handler))
+                {
+                    // invoke key handler
+
+                    if (handler.ScriptBlock != null)
+                    {
+                        CallPossibleExternalApplication(() => handler.Action(consoleKey, arg));
+                    }
+                    else
+                    {
+                        handler.Action(consoleKey, arg);
+                    }
+
+                    // key chords are processed mostly by looping over each key
+                    // in a loop and identifying their dispatch tables until
+                    // the dispatch table is null.
+                    //
+                    // nullify dispatch table to prevent infinite looping
+
+                    handlerOrChordDispatchTable = null;
+                }
+
+                // the current key is part of a key chord
+                // read the next key and select the dispatch
+                // table for the next iteration of the loop
+
+                else if (handlerOrChordDispatchTable != null)
+                {
+                    key = ReadKey();
+
+                    dispatchTable = (ChordDispatchTable)handlerOrChordDispatchTable;
+                    ignoreIfNoAction = true;
+                }
+
+                else if (handlerOrChordDispatchTable == null && dispatchTable.InViMode && IsNumeric(key) && arg == null)
+                {
+                    var argBuffer = _singleton._statusBuffer;
+                    argBuffer.Clear();
+                    _singleton._statusLinePrompt = "digit-argument: ";
+
+                    while (IsNumeric(key))
+                    {
+                        argBuffer.Append(key.KeyChar);
+                        _singleton.Render();
+                        key = ReadKey();
+                    }
+                    var numericArg = int.Parse(argBuffer.ToString());
+                    if (dispatchTable.TryGetValue(key, out var keyHhandler))
+                    {
+                        ProcessOneKey(key, dispatchTable, ignoreIfNoAction: true, arg: numericArg);
+                    }
+                    else
+                    {
+                        Ding();
+                    }
+                    argBuffer.Clear();
+                    _singleton.ClearStatusMessage(render: true);
+
+                    // MUST exit from recursive call
+
+                    return;
+                }
+
+                // insert unmapped keys
+
+                else if (!ignoreIfNoAction)
                 {
-                    CallPossibleExternalApplication(() => handler.Action(consoleKey, arg));
+                    SelfInsert(consoleKey, arg);
                 }
+
                 else
                 {
-                    handler.Action(consoleKey, arg);
+                    Ding(key.AsConsoleKeyInfo(), arg);
                 }
-            }
-            else if (!ignoreIfNoAction)
-            {
-                SelfInsert(consoleKey, arg);
-            }
+
+            } while (handlerOrChordDispatchTable != null);
         }
 
         static PSConsoleReadLine()
@@ -910,20 +978,6 @@ private void DelayedOneTimeInitialize()
             _readKeyThread.Start();
         }
 
-        private static void Chord(ConsoleKeyInfo? key = null, object arg = null)
-        {
-            if (!key.HasValue)
-            {
-                throw new ArgumentNullException(nameof(key));
-            }
-
-            if (_singleton._chordDispatchTable.TryGetValue(PSKeyInfo.FromConsoleKeyInfo(key.Value), out var secondKeyDispatchTable))
-            {
-                var secondKey = ReadKey();
-                _singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: arg);
-            }
-        }
-
         /// <summary>
         /// Abort current action, e.g. incremental history search.
         /// </summary>
@@ -965,7 +1019,9 @@ public static void DigitArgument(ConsoleKeyInfo? key = null, object arg = null)
             while (true)
             {
                 var nextKey = ReadKey();
-                if (_singleton._dispatchTable.TryGetValue(nextKey, out var handler))
+                if (_singleton._dispatchTable.TryGetValue(nextKey, out var handlerOrChordDispatchTable) &&
+                    handlerOrChordDispatchTable.TryGetKeyHandler(out var handler)
+                )
                 {
                     if (handler.Action == DigitArgument)
                     {
diff --git a/PSReadLine/ReadLine.vi.cs b/PSReadLine/ReadLine.vi.cs
index 571f06f5..3bc740d5 100644
--- a/PSReadLine/ReadLine.vi.cs
+++ b/PSReadLine/ReadLine.vi.cs
@@ -443,7 +443,6 @@ public static void ViCommandMode(ConsoleKeyInfo? key = null, object arg = null)
                 _singleton._groupUndoHelper.EndGroup();
             }
             _singleton._dispatchTable = _viCmdKeyMap;
-            _singleton._chordDispatchTable = _viCmdChordTable;
             ViBackwardChar();
             _singleton.ViIndicateCommandMode();
         }
@@ -454,7 +453,6 @@ public static void ViCommandMode(ConsoleKeyInfo? key = null, object arg = null)
         public static void ViInsertMode(ConsoleKeyInfo? key = null, object arg = null)
         {
             _singleton._dispatchTable = _viInsKeyMap;
-            _singleton._chordDispatchTable = _viInsChordTable;
             _singleton.ViIndicateInsertMode();
         }
 
@@ -479,15 +477,12 @@ public static void ViInsertMode(ConsoleKeyInfo? key = null, object arg = null)
         internal static IDisposable UseViCommandModeTables()
         {
             var oldDispatchTable = _singleton._dispatchTable;
-            var oldChordDispatchTable = _singleton._chordDispatchTable;
 
             _singleton._dispatchTable = _viCmdKeyMap;
-            _singleton._chordDispatchTable = _viCmdChordTable;
 
             return new Disposable(() =>
            {
                _singleton._dispatchTable = oldDispatchTable;
-               _singleton._chordDispatchTable = oldChordDispatchTable;
            });
         }
 
@@ -497,15 +492,12 @@ internal static IDisposable UseViCommandModeTables()
         internal static IDisposable UseViInsertModeTables()
         {
             var oldDispatchTable = _singleton._dispatchTable;
-            var oldChordDispatchTable = _singleton._chordDispatchTable;
 
             _singleton._dispatchTable = _viInsKeyMap;
-            _singleton._chordDispatchTable = _viInsChordTable;
 
             return new Disposable(() =>
            {
                _singleton._dispatchTable = oldDispatchTable;
-               _singleton._chordDispatchTable = oldChordDispatchTable;
            });
         }
 
@@ -1089,73 +1081,6 @@ public static void RepeatLastCommand(ConsoleKeyInfo? key = null, object arg = nu
             Ding();
         }
 
-        /// <summary>
-        /// Chords in vi needs special handling because a numeric argument can be input between the 1st and 2nd key.
-        /// </summary>
-        private static void ViChord(ConsoleKeyInfo? key = null, object arg = null)
-        {
-            if (!key.HasValue)
-            {
-                throw new ArgumentNullException(nameof(key));
-            }
-            if (arg != null)
-            {
-                Chord(key, arg);
-                return;
-            }
-
-            if (_singleton._chordDispatchTable.TryGetValue(PSKeyInfo.FromConsoleKeyInfo(key.Value), out var secondKeyDispatchTable))
-            {
-                ViChordHandler(secondKeyDispatchTable, arg);
-            }
-        }
-
-        private static void ViChordHandler(Dictionary<PSKeyInfo, KeyHandler> secondKeyDispatchTable, object arg = null)
-        {
-            var secondKey = ReadKey();
-            if (secondKeyDispatchTable.TryGetValue(secondKey, out var handler))
-            {
-                _singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: arg);
-            }
-            else if (!IsNumeric(secondKey))
-            {
-                _singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: arg);
-            }
-            else
-            {
-                var argBuffer = _singleton._statusBuffer;
-                argBuffer.Clear();
-                _singleton._statusLinePrompt = "digit-argument: ";
-                while (IsNumeric(secondKey))
-                {
-                    argBuffer.Append(secondKey.KeyChar);
-                    _singleton.Render();
-                    secondKey = ReadKey();
-                }
-                int numericArg = int.Parse(argBuffer.ToString());
-                if (secondKeyDispatchTable.TryGetValue(secondKey, out handler))
-                {
-                    _singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: numericArg);
-                }
-                else
-                {
-                    Ding();
-                }
-                argBuffer.Clear();
-                _singleton.ClearStatusMessage(render: true);
-            }
-        }
-
-        private static void ViDGChord(ConsoleKeyInfo? key = null, object arg = null)
-        {
-            if (!key.HasValue)
-            {
-                throw new ArgumentNullException(nameof(key));
-            }
-
-            ViChordHandler(_viChordDGTable, arg);
-        }
-
         private static bool IsNumeric(PSKeyInfo key)
         {
             return key.KeyChar >= '0' && key.KeyChar <= '9' && !key.Control && !key.Alt;
@@ -1195,7 +1120,11 @@ public static void ViDigitArgumentInChord(ConsoleKeyInfo? key = null, object arg
             while (true)
             {
                 var nextKey = ReadKey();
-                if (_singleton._dispatchTable.TryGetValue(nextKey, out var handler) && handler.Action == DigitArgument)
+                if (
+                    _singleton._dispatchTable.TryGetValue(nextKey, out var handlerOrChordDispatchTable) &&
+                    handlerOrChordDispatchTable.TryGetKeyHandler(out var handler) &&
+                    handler.Action == DigitArgument
+                )
                 {
                     if (nextKey == Keys.Minus)
                     {
diff --git a/PSReadLine/TextObjects.Vi.cs b/PSReadLine/TextObjects.Vi.cs
index ea9810fb..2fe0b59f 100644
--- a/PSReadLine/TextObjects.Vi.cs
+++ b/PSReadLine/TextObjects.Vi.cs
@@ -1,98 +1,13 @@
 using System;
-using System.Collections.Generic;
 
 namespace Microsoft.PowerShell
 {
     public partial class PSConsoleReadLine
     {
-        internal enum TextObjectOperation
-        {
-            None,
-            Change,
-            Delete,
-        }
-
-        internal enum TextObjectSpan
-        {
-            None,
-            Around,
-            Inner,
-        }
-
-        private TextObjectOperation _textObjectOperation = TextObjectOperation.None;
-        private TextObjectSpan _textObjectSpan = TextObjectSpan.None;
-
-        private readonly Dictionary<TextObjectOperation, Dictionary<TextObjectSpan, KeyHandler>> _textObjectHandlers = new()
-        {
-            [TextObjectOperation.Delete] = new() { [TextObjectSpan.Inner] = MakeKeyHandler(ViDeleteInnerWord, "ViDeleteInnerWord") },
-        };
-
-        private void ViChordDeleteTextObject(ConsoleKeyInfo? key = null, object arg = null)
-        {
-            _textObjectOperation = TextObjectOperation.Delete;
-            ViChordTextObject(key, arg);
-        }
-
-        private void ViChordTextObject(ConsoleKeyInfo? key = null, object arg = null)
-        {
-            if (!key.HasValue)
-            {
-                ResetTextObjectState();
-                throw new ArgumentNullException(nameof(key));
-            }
-
-            _textObjectSpan = GetRequestedTextObjectSpan(key.Value);
-
-            // Handle text object
-            var textObjectKey = ReadKey();
-            if (_viChordTextObjectsTable.TryGetValue(textObjectKey, out _))
-            {
-                _singleton.ProcessOneKey(textObjectKey, _viChordTextObjectsTable, ignoreIfNoAction: true, arg: arg);
-            }
-            else
-            {
-                ResetTextObjectState();
-                Ding();
-            }
-        }
-
-        private TextObjectSpan GetRequestedTextObjectSpan(ConsoleKeyInfo key)
-        {
-            if (key.KeyChar == 'i')
-            {
-                return TextObjectSpan.Inner;
-            }
-            else if (key.KeyChar == 'a')
-            {
-                return TextObjectSpan.Around;
-            }
-            else
-            {
-                System.Diagnostics.Debug.Assert(false);
-                throw new NotSupportedException();
-            }
-        }
-
-        private static void ViHandleTextObject(ConsoleKeyInfo? key = null, object arg = null)
-        {
-            if (!_singleton._textObjectHandlers.TryGetValue(_singleton._textObjectOperation, out var textObjectHandler) ||
-                !textObjectHandler.TryGetValue(_singleton._textObjectSpan, out var handler))
-            {
-                ResetTextObjectState();
-                Ding();
-                return;
-            }
-
-            handler.Action(key, arg);
-        }
-
-        private static void ResetTextObjectState()
-        {
-            _singleton._textObjectOperation = TextObjectOperation.None;
-            _singleton._textObjectSpan = TextObjectSpan.None;
-        }
-
-        private static void ViDeleteInnerWord(ConsoleKeyInfo? key = null, object arg = null)
+        /// <summary>
+        /// Delete the content inside and including the current word
+        /// </summary>
+        public static void ViDeleteInnerWord(ConsoleKeyInfo? key = null, object arg = null)
         {
             var delimiters = _singleton.Options.WordDelimiters;
 
diff --git a/test/KeyInfoTest.cs b/test/KeyInfoTest.cs
index ce737fd3..9ff5838d 100644
--- a/test/KeyInfoTest.cs
+++ b/test/KeyInfoTest.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Linq;
 using Microsoft.PowerShell;
 using Xunit;
 
@@ -7,6 +8,7 @@ namespace Test
     public class KeyInfo
     {
         private const ConsoleModifiers NoModifiers = 0;
+        private const ConsoleModifiers CtrlShift = ConsoleModifiers.Control | ConsoleModifiers.Shift;
 
         [Fact]
         public void KeyInfoConverterSimpleCharLiteral()
@@ -109,6 +111,22 @@ void VerifyOne(string input, ConsoleModifiers m)
             }
         }
 
+        [Fact]
+        public void KeyInfoConverter_ThreeKeyChords()
+        {
+            var chord = ConsoleKeyChordConverter.Convert("Ctrl+K,Ctrl+P,x");
+            Assert.Equal(3, chord.Length);
+
+            Assert.Equal(CtrlShift, chord[0].Modifiers);
+            Assert.Equal(Enum.Parse(typeof(ConsoleKey), "K"), chord[0].Key);
+
+            Assert.Equal(CtrlShift, chord[1].Modifiers);
+            Assert.Equal(Enum.Parse(typeof(ConsoleKey), "P"), chord[1].Key);
+
+            Assert.Equal(NoModifiers, chord[2].Modifiers);
+            Assert.Equal(Enum.Parse(typeof(ConsoleKey), "X"), chord[2].Key);
+        }
+
         [Fact]
         public void KeyInfoConverterErrors()
         {
diff --git a/test/OptionsTest.VI.cs b/test/OptionsTest.VI.cs
index 38025bc4..c974d6d4 100644
--- a/test/OptionsTest.VI.cs
+++ b/test/OptionsTest.VI.cs
@@ -37,6 +37,31 @@ public void ViGetKeyHandlers()
             {
                 Assert.Equal("<d,0>", handler.Key);
             }
+
+            handlers = PSConsoleReadLine.GetKeyHandlers(Chord: new string[] { "d,i,w" });
+            Assert.NotEmpty(handlers);
+            foreach (var handler in handlers)
+            {
+                Assert.Equal("<d,i,w>", handler.Key);
+            }
+        }
+
+        [SkippableFact]
+        public void ViRemoveKeyHandler()
+        {
+            TestSetup(KeyMode.Vi);
+
+            using var disposable = PSConsoleReadLine.UseViCommandModeTables();
+
+            PSConsoleReadLine.RemoveKeyHandler(new string[] { "d,0" });
+
+            var handlers = PSConsoleReadLine.GetKeyHandlers(Chord: new string[] { "d,0" });
+            Assert.Empty(handlers);
+
+            PSConsoleReadLine.RemoveKeyHandler(new string[] { "d,i,w" });
+
+            handlers = PSConsoleReadLine.GetKeyHandlers(Chord: new string[] { "d,i,w" });
+            Assert.Empty(handlers);
         }
     }
 }
diff --git a/test/OptionsTest.cs b/test/OptionsTest.cs
index 67e36538..21343c03 100644
--- a/test/OptionsTest.cs
+++ b/test/OptionsTest.cs
@@ -56,11 +56,11 @@ public void ContinuationPrompt()
         }
 
         [SkippableFact]
-        public void GetKeyHandlers()
+        public void GetKeyHandlers_Unbound()
         {
             System.Collections.Generic.IEnumerable<Microsoft.PowerShell.KeyHandler> handlers;
 
-            foreach (var keymode in new[] {KeyMode.Cmd, KeyMode.Emacs})
+            foreach (var keymode in new[] { KeyMode.Cmd, KeyMode.Emacs })
             {
                 TestSetup(keymode);
 
@@ -85,15 +85,17 @@ public void GetKeyHandlers()
                     Assert.Equal("Home", handler.Key);
                 }
             }
+        }
+
+        [SkippableFact]
+        public void GetKeyHandlers_Emacs()
+        {
+            System.Collections.Generic.IEnumerable<Microsoft.PowerShell.KeyHandler> handlers;
 
             TestSetup(KeyMode.Emacs);
             
             handlers = PSConsoleReadLine.GetKeyHandlers(Chord: new string[] { "ctrl+x" });
-            Assert.NotEmpty(handlers);
-            foreach (var handler in handlers)
-            {
-                Assert.Equal("Ctrl+x", handler.Key);
-            }
+            Assert.Empty(handlers);
 
             handlers = PSConsoleReadLine.GetKeyHandlers(Chord: new string[] { "ctrl+x,ctrl+e" });
             Assert.NotEmpty(handlers);