#nullable enable namespace Terminal.Gui; /// /// Provides a collection of objects bound to a . /// /// /// /// public class KeyBindings : Bindings { /// Initializes a new instance bound to . public KeyBindings (View? target) :base((commands,key)=> new KeyBinding (commands)) { Target = target; } /// Adds a to the collection. /// /// /// If has no Commands or is invalid. public void Add (Key key, KeyBinding binding) { if (!key.IsValid) { throw new ArgumentException (nameof (key)); } if (binding.Commands is { Length: 0 }) { throw new ArgumentException (nameof (binding)); } if (TryGet (key, out KeyBinding _)) { throw new InvalidOperationException (@$"A key binding for {key} exists ({binding})."); //Bindings [key] = binding; } if (Target is { }) { binding.Target = Target; } // IMPORTANT: Add a COPY of the key. This is needed because ConfigurationManager.Apply uses DeepMemberWiseCopy // IMPORTANT: update the memory referenced by the key, and Dictionary uses caching for performance, and thus // IMPORTANT: Apply will update the Dictionary with the new key, but the old key will still be in the dictionary. // IMPORTANT: See the ConfigurationManager.Illustrate_DeepMemberWiseCopy_Breaks_Dictionary test for details. _bindings.Add (new (key), binding); } #pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// /// Adds a new key combination that will trigger the commands in (if supported by the /// View - see ). /// /// /// If the key is already bound to a different array of s it will be rebound /// . /// /// /// /// Commands are only ever applied to the current (i.e. this feature cannot be used to switch /// focus to another view and perform multiple commands there). /// /// The key to check. /// /// The command to invoked on the when is pressed. When /// multiple commands are provided,they will be applied in sequence. The bound strike will be /// consumed if any took effect. /// /// If is empty. #pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved public void Add (Key key, params Command [] commands) { Add (key, new KeyBinding (commands)); } /// /// /// Adds a new key combination that will trigger the commands in on the View /// specified by . /// /// /// If the key is already bound to a different array of s it will be rebound /// . /// /// /// /// /// The key to check. /// The View the commands will be invoked on. If , the key will be bound to . /// /// The command to invoked on the when is pressed. When /// multiple commands are provided,they will be applied in sequence. The bound strike will be /// consumed if any took effect. /// public void Add (Key key, View? target, params Command [] commands) { KeyBinding binding = new (commands, target); Add (key, binding); } private readonly Dictionary _bindings = new (new KeyEqualityComparer ()); /// /// Gets the bindings. /// /// public IEnumerable> GetBindings () { return _bindings; } /// /// Gets the keys that are bound. /// /// public IEnumerable GetBoundKeys () { return _bindings.Keys; } /// /// The view that the are bound to. /// /// /// If the KeyBindings object is being used for Application.KeyBindings. /// public View? Target { get; init; } /// Removes all objects from the collection. public void Clear () { _bindings.Clear (); } /// /// Removes all key bindings that trigger the given command set. Views can have multiple different keys bound to /// the same command sets and this method will clear all of them. /// /// public void Clear (params Command [] command) { KeyValuePair [] kvps = _bindings .Where (kvp => kvp.Value.Commands.SequenceEqual (command)) .ToArray (); foreach (KeyValuePair kvp in kvps) { Remove (kvp.Key); } } /// Gets the for the specified . /// /// public KeyBinding Get (Key key) { if (TryGet (key, out KeyBinding binding)) { return binding; } throw new InvalidOperationException ($"Key {key} is not bound."); } /// Gets the array of s bound to if it exists. /// The key to check. /// /// The array of s if is bound. An empty array /// if not. /// public Command [] GetCommands (Key key) { if (TryGet (key, out KeyBinding bindings)) { return bindings.Commands; } return []; } /// Gets the first Key bound to the set of commands specified by . /// The set of commands to search. /// /// The first bound to the set of commands specified by . /// if the set of caommands was not found. /// public Key? GetKeyFromCommands (params Command [] commands) { return _bindings.FirstOrDefault (a => a.Value.Commands.SequenceEqual (commands)).Key; } /// Gets Keys bound to the set of commands specified by . /// The set of commands to search. /// /// The s bound to the set of commands specified by . An empty list if the /// set of caommands was not found. /// public IEnumerable GetKeysFromCommands (params Command [] commands) { return _bindings.Where (a => a.Value.Commands.SequenceEqual (commands)).Select (a => a.Key); } /// Removes a from the collection. /// public void Remove (Key key) { if (!TryGet (key, out KeyBinding _)) { return; } _bindings.Remove (key); } /// Replaces the commands already bound to a key. /// /// /// If the key is not already bound, it will be added. /// /// /// The key bound to the command to be replaced. /// The set of commands to replace the old ones with. public void ReplaceCommands (Key key, params Command [] newCommands) { if (TryGet (key, out KeyBinding binding)) { Remove (key); Add (key, newCommands); } else { Add (key, newCommands); } } /// Replaces a key combination already bound to a set of s. /// /// The key to be replaced. /// The new key to be used. If no action will be taken. public void ReplaceKey (Key oldKey, Key newKey) { if (!newKey.IsValid) { throw new InvalidOperationException ($"Key {newKey} is is not valid."); } if (newKey == Key.Empty) { Remove (oldKey); return; } if (TryGet (oldKey, out KeyBinding binding)) { Remove (oldKey); Add (newKey, binding); } else { Add (newKey, binding); } } /// Gets the commands bound with the specified Key. /// /// The key to check. /// /// When this method returns, contains the commands bound with the specified Key, if the Key is /// found; otherwise, null. This parameter is passed uninitialized. /// /// if the Key is bound; otherwise . public bool TryGet (Key key, out KeyBinding binding) { binding = new ([], null); if (key.IsValid) { return _bindings.TryGetValue (key, out binding); } return false; } }