diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 6b3bb89b..48656f8d 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -1091,7 +1091,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(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..f1e996a1 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 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)) + 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, /// Search functions Search, + /// Text objects functions + TextObjects, /// User defined functions 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 action, string }; } - private Dictionary _dispatchTable; - private Dictionary> _chordDispatchTable; + static ChordDispatchTable MakeChordDispatchTable(IEnumerable> chordDispatchTable) + => new(chordDispatchTable); + static ChordDispatchTable MakeViChordDispatchTable(IEnumerable> chordDispatchTable) + => new ViChordDispatchTable(chordDispatchTable); + + private ChordDispatchTable _dispatchTable; + + private class ChordDispatchTable : IDictionary + { + private readonly IDictionary _dispatchTable; + private readonly bool _viMode; + + public ChordDispatchTable(IEnumerable> dispatchTable) + : this(dispatchTable, false) + { } + + protected ChordDispatchTable(IEnumerable> 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 Keys + => _dispatchTable.Keys; + + public ICollection 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 item) + => _dispatchTable.Add(item); + + public void Clear() + => _dispatchTable.Clear(); + + public bool Contains(KeyValuePair item) + => _dispatchTable.Contains(item); + + public bool ContainsKey(PSKeyInfo key) + => _dispatchTable.ContainsKey(key); + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + => _dispatchTable.CopyTo(array, arrayIndex); + + public IEnumerator> GetEnumerator() + => _dispatchTable.GetEnumerator(); + + public bool Remove(PSKeyInfo key) + => _dispatchTable.Remove(key); + + public bool Remove(KeyValuePair 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(); + } + + /// + /// Represents a that + /// handles vi-specific key bindings like handling digit + /// arguments after the first key. + /// + private sealed class ViChordDispatchTable : ChordDispatchTable + { + public ViChordDispatchTable(IEnumerable> dispatchTable) + : base(dispatchTable, viMode: true) + { + } + } + + private sealed class KeyHandlerOrChordDispatchTable + { + private readonly KeyHandler _keyHandler = null; + private readonly ChordDispatchTable _chordDispatchTable = null; + + /// + /// Initialize a new instance of the class + /// that acts as a key handler. + /// + /// + public KeyHandlerOrChordDispatchTable(KeyHandler keyHandler) + { + _keyHandler = keyHandler; + } + + /// + /// Initialize a new instance of the class + /// that acts as a chord dispatch table. + /// + /// + public KeyHandlerOrChordDispatchTable(IEnumerable> 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; + } + } /// /// Helper to set bindings based on EditMode @@ -176,7 +324,7 @@ void SetDefaultBindings(EditMode editMode) void SetDefaultWindowsBindings() { - _dispatchTable = new Dictionary + _dispatchTable = MakeChordDispatchTable(new Dictionary { { 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>(); } void SetDefaultEmacsBindings() { - _dispatchTable = new Dictionary + _dispatchTable = MakeChordDispatchTable(new Dictionary { { 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{ + + // Escape, 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{ + + // Ctrl+X, 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> - { - // Escape, table (meta key) - [Keys.Escape] = new Dictionary - { - { 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, table - [Keys.CtrlX] = new Dictionary - { - { Keys.Backspace, MakeKeyHandler(BackwardKillInput, "BackwardKillInput") }, - { Keys.CtrlE, MakeKeyHandler(ViEditVisually, "ViEditVisually") }, - { Keys.CtrlU, MakeKeyHandler(Undo, "Undo") }, - { Keys.CtrlX, MakeKeyHandler(ExchangePointAndMark, "ExchangePointAndMark") }, - } - }; } /// @@ -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 _viInsKeyMap; - private static Dictionary _viCmdKeyMap; - private static Dictionary _viChordDTable; - private static Dictionary _viChordGTable; - private static Dictionary _viChordCTable; - private static Dictionary _viChordYTable; - private static Dictionary _viChordDGTable; - - private static Dictionary _viChordTextObjectsTable; - - private static Dictionary> _viCmdChordTable; - private static Dictionary> _viInsChordTable; + private static ChordDispatchTable _viInsKeyMap; + private static ChordDispatchTable _viCmdKeyMap; /// /// Sets up the key bindings for vi operations. /// private void SetDefaultViBindings() { - _viInsKeyMap = new Dictionary + _viInsKeyMap = MakeChordDispatchTable(new Dictionary { - { 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 + _viCmdKeyMap = MakeChordDispatchTable(new Dictionary { - { 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{ - _viChordDTable = new Dictionary - { - { 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 - { - { 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{ - _viChordGTable = new Dictionary - { - { 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{ - _viChordYTable = new Dictionary - { - { 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 - { - { Keys.W, MakeKeyHandler(ViHandleTextObject, "WordTextObject")}, - }; - - _viChordDGTable = new Dictionary - { - { 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{ - _viCmdChordTable = new Dictionary>(); - _viInsChordTable = new Dictionary>(); + { 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{ + + { 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{ + + { 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 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 1; + if (createDispatchTable) { - secondDispatchTable = new Dictionary(); - _chordDispatchTable[firstKey] = secondDispatchTable; + var dispatchTableExists = + chordDispatchTable.ContainsKey(anchorKey) && + !chordDispatchTable[anchorKey].TryGetKeyHandler(out var _) + ; + + if (!dispatchTableExists) + { + chordDispatchTable[anchorKey] = new ChordDispatchTable( + Enumerable.Empty>()); + } + + 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(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 boundFunctions + ) + { + var keyHandlers = new List(); + + 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(); + } + /// /// Return key handlers bound to specified chords. /// /// public static IEnumerable GetKeyHandlers(string[] Chord) { - var boundFunctions = new HashSet(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 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 { } } + /// + /// Looks up a localized string similar to Text objects functions. + /// + internal static string TextObjectsGrouping { + get { + return ResourceManager.GetString("TextObjectsGrouping", resourceCulture); + } + } + /// /// Looks up a localized string similar to User defined functions. /// @@ -1618,6 +1627,15 @@ internal static string ViDeleteGlobDescription { } } + /// + /// Looks up a localized string similar to Delete the content inside the current word.. + /// + internal static string ViDeleteInnerWordDescription { + get { + return ResourceManager.GetString("ViDeleteInnerWordDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Deletes until given character.. /// 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 Delete to the beginning of the next word, as delimited by white space, and enter insert mode. + + Delete the content inside and including the current word + Delete to the end of the word, as delimited by white space and common delimiters, and enter insert mode. @@ -801,6 +804,9 @@ Or not saving history with: Search functions + + Text objects functions + User defined functions diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs index c06120db..f0ff4077 100644 --- a/PSReadLine/ReadLine.cs +++ b/PSReadLine/ReadLine.cs @@ -422,7 +422,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 @@ -603,47 +604,114 @@ void CallPossibleExternalApplication(Action action) CallPossibleExternalApplication(() => { action(); return null; }); } - void ProcessOneKey(PSKeyInfo key, Dictionary 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() @@ -877,20 +945,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); - } - } - /// /// Abort current action, e.g. incremental history search. /// @@ -932,7 +986,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(); } - /// - /// Chords in vi needs special handling because a numeric argument can be input between the 1st and 2nd key. - /// - 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 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> _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) + /// + /// Delete the content inside and including the current word + /// + 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("", handler.Key); } + + handlers = PSConsoleReadLine.GetKeyHandlers(Chord: new string[] { "d,i,w" }); + Assert.NotEmpty(handlers); + foreach (var handler in handlers) + { + Assert.Equal("", 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 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 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);