Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #3778: Decouples Command from KeyBindings #3880

Merged
merged 42 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
a7d5392
WIP: Refactored CommandContext
tig Dec 5, 2024
76c0ab8
WIP: Refactored Mouse stuff.
tig Dec 5, 2024
e6054f7
WIP: Builds. No worky.
tig Dec 5, 2024
dd36aa5
ICommandContext -> separate file
tig Dec 5, 2024
80db8e6
ICommandContext -> separate file
tig Dec 5, 2024
53d7449
Unit tests pass. Most things actually work, but not all
tig Dec 5, 2024
65cf641
Fixed CheckBox issue
tig Dec 5, 2024
334cb68
Merge branch 'v2_develop' into v2_3778-Command-Decoupling
tig Dec 5, 2024
dc47125
API docsz'
tig Dec 6, 2024
3e8d916
API docsz'
tig Dec 6, 2024
f6b65c6
Added mouse support. update charmap
tig Dec 6, 2024
7d4f4e4
Tweaks
tig Dec 6, 2024
b443d52
More refactoring. Application scope is gone.
tig Dec 6, 2024
0c7d1ae
More refactoring. Broke CM
tig Dec 6, 2024
0586c30
Fixed ContextMenu
tig Dec 6, 2024
7360683
Prepping to combine keybinding classes
tig Dec 6, 2024
725df05
Prepping to combine keybinding classes 2
tig Dec 6, 2024
3edcf64
Fixed unit tests
tig Dec 7, 2024
3e6e758
Merge branch 'v2_develop' into v2_3778-Command-Decoupling
tig Dec 7, 2024
5a0b350
Merged v2_develop.
tig Dec 7, 2024
f673ef3
Doc updates. Code cleanup
tig Dec 8, 2024
0b72db2
Doc updates.
tig Dec 8, 2024
0f13757
Combined KeyBinding classes
tig Dec 8, 2024
921d125
TOOD
tig Dec 8, 2024
7e289f0
KeyBindings cleanup
tig Dec 8, 2024
98244b7
KeyBindings cleanup
tig Dec 8, 2024
e502a13
MouseBindings tests
tig Dec 8, 2024
babc6fe
Tweaked ColorPicker16
tig Dec 9, 2024
e6aeada
Tweaked HexView
tig Dec 9, 2024
ed67eda
Doc tweak
tig Dec 9, 2024
9f4d30d
Unit test tweak
tig Dec 9, 2024
bf88795
Super rough sketch of what generics solution would look like
tznind Dec 9, 2024
9002acf
WIP move more to bind base class
tznind Dec 9, 2024
c18cff2
make keybindings share base
tznind Dec 9, 2024
d15b302
Equality comparer support
tznind Dec 9, 2024
23344ba
Incorporated tznind's stuff
tig Dec 10, 2024
2b8884a
Finished integrated tznind's work.
tig Dec 10, 2024
1180b2f
Code cleanup
tig Dec 10, 2024
9563ec9
Key -> nullable enable
tig Dec 10, 2024
97d4c0a
Refactored RadioGroup to just use Commands
tig Dec 10, 2024
ea74179
Refactored RadioGroup to just use Commands
tig Dec 10, 2024
63b3ebf
Fixed RadioGroup unit test that was previosly bogus
tig Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Terminal.Gui/Application/Application.Initialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ internal static void InternalInit (
}
}

AddApplicationKeyBindings ();
AddKeyBindings ();

// Start the process of configuration management.
// Note that we end up calling LoadConfigurationFromAllSources
Expand Down
119 changes: 39 additions & 80 deletions Terminal.Gui/Application/Application.Keyboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,17 @@ public static bool RaiseKeyDownEvent (Key key)

// Invoke any Application-scoped KeyBindings.
// The first view that handles the key will stop the loop.
foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.Bindings.Where (b => b.Key == key.KeyCode))
// foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.GetBindings (key))
if (KeyBindings.TryGet (key, out KeyBinding binding))
{
if (binding.Value.BoundView is { })
if (binding.Target is { })
{
if (!binding.Value.BoundView.Enabled)
if (!binding.Target.Enabled)
{
return false;
}

bool? handled = binding.Value.BoundView?.InvokeCommands (binding.Value.Commands, binding.Key, binding.Value);
bool? handled = binding.Target?.InvokeCommands (binding.Commands, binding);

if (handled != null && (bool)handled)
{
Expand All @@ -63,16 +64,17 @@ public static bool RaiseKeyDownEvent (Key key)
}
else
{
if (!KeyBindings.TryGet (key, KeyBindingScope.Application, out KeyBinding appBinding))
// BUGBUG: this seems unneeded.
if (!KeyBindings.TryGet (key, out KeyBinding keybinding))
{
continue;
return false;
}

bool? toReturn = null;

foreach (Command command in appBinding.Commands)
foreach (Command command in keybinding.Commands)
{
toReturn = InvokeCommand (command, key, appBinding);
toReturn = InvokeCommand (command, key, keybinding);
}

return toReturn ?? true;
Expand All @@ -81,18 +83,18 @@ public static bool RaiseKeyDownEvent (Key key)

return false;

static bool? InvokeCommand (Command command, Key key, KeyBinding appBinding)
static bool? InvokeCommand (Command command, Key key, KeyBinding binding)
{
if (!CommandImplementations!.ContainsKey (command))
if (!_commandImplementations!.ContainsKey (command))
{
throw new NotSupportedException (
@$"A KeyBinding was set up for the command {command} ({key}) but that command is not supported by Application."
);
}

if (CommandImplementations.TryGetValue (command, out View.CommandImplementation? implementation))
if (_commandImplementations.TryGetValue (command, out View.CommandImplementation? implementation))
{
var context = new CommandContext (command, key, appBinding); // Create the context here
CommandContext<KeyBinding> context = new (command, binding); // Create the context here

return implementation (context);
}
Expand All @@ -116,7 +118,8 @@ public static bool RaiseKeyDownEvent (Key key)
public static event EventHandler<Key>? KeyDown;

/// <summary>
/// Called when the user releases a key (by the <see cref="IConsoleDriver"/>). Raises the cancelable <see cref="KeyUp"/>
/// Called when the user releases a key (by the <see cref="IConsoleDriver"/>). Raises the cancelable
/// <see cref="KeyUp"/>
/// event
/// then calls <see cref="View.NewKeyUpEvent"/> on all top level views. Called after <see cref="RaiseKeyDownEvent"/>.
/// </summary>
Expand Down Expand Up @@ -155,14 +158,14 @@ public static bool RaiseKeyUpEvent (Key key)

#region Application-scoped KeyBindings

static Application () { AddApplicationKeyBindings (); }
static Application () { AddKeyBindings (); }

/// <summary>Gets the Application-scoped key bindings.</summary>
public static KeyBindings KeyBindings { get; internal set; } = new ();
public static KeyBindings KeyBindings { get; internal set; } = new (null);

internal static void AddApplicationKeyBindings ()
internal static void AddKeyBindings ()
{
CommandImplementations = new ();
_commandImplementations.Clear ();

// Things this view knows how to do
AddCommand (
Expand Down Expand Up @@ -231,83 +234,40 @@ internal static void AddApplicationKeyBindings ()
return false;
});

KeyBindings.Clear ();

// Resources/config.json overrides
QuitKey = Key.Esc;
NextTabKey = Key.Tab;
PrevTabKey = Key.Tab.WithShift;
NextTabGroupKey = Key.F6;
PrevTabGroupKey = Key.F6.WithShift;
QuitKey = Key.Esc;
ArrangeKey = Key.F5.WithCtrl;

KeyBindings.Add (QuitKey, KeyBindingScope.Application, Command.Quit);

KeyBindings.Add (Key.CursorRight, KeyBindingScope.Application, Command.NextTabStop);
KeyBindings.Add (Key.CursorDown, KeyBindingScope.Application, Command.NextTabStop);
KeyBindings.Add (Key.CursorLeft, KeyBindingScope.Application, Command.PreviousTabStop);
KeyBindings.Add (Key.CursorUp, KeyBindingScope.Application, Command.PreviousTabStop);
KeyBindings.Add (NextTabKey, KeyBindingScope.Application, Command.NextTabStop);
KeyBindings.Add (PrevTabKey, KeyBindingScope.Application, Command.PreviousTabStop);
// Need to clear after setting the above to ensure actually clear
// because set_QuitKey etc.. may call Add
KeyBindings.Clear ();

KeyBindings.Add (NextTabGroupKey, KeyBindingScope.Application, Command.NextTabGroup);
KeyBindings.Add (PrevTabGroupKey, KeyBindingScope.Application, Command.PreviousTabGroup);
KeyBindings.Add (QuitKey, Command.Quit);
KeyBindings.Add (NextTabKey, Command.NextTabStop);
KeyBindings.Add (PrevTabKey, Command.PreviousTabStop);
KeyBindings.Add (NextTabGroupKey, Command.NextTabGroup);
KeyBindings.Add (PrevTabGroupKey, Command.PreviousTabGroup);
KeyBindings.Add (ArrangeKey, Command.Edit);

KeyBindings.Add (ArrangeKey, KeyBindingScope.Application, Command.Edit);
KeyBindings.Add (Key.CursorRight, Command.NextTabStop);
KeyBindings.Add (Key.CursorDown, Command.NextTabStop);
KeyBindings.Add (Key.CursorLeft, Command.PreviousTabStop);
KeyBindings.Add (Key.CursorUp, Command.PreviousTabStop);

// TODO: Refresh Key should be configurable
KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh);
KeyBindings.Add (Key.F5, Command.Refresh);

// TODO: Suspend Key should be configurable
if (Environment.OSVersion.Platform == PlatformID.Unix)
{
KeyBindings.Add (Key.Z.WithCtrl, KeyBindingScope.Application, Command.Suspend);
}
}

/// <summary>
/// Gets the list of Views that have <see cref="KeyBindingScope.Application"/> key bindings.
/// </summary>
/// <remarks>
/// This is an internal method used by the <see cref="View"/> class to add Application key bindings.
/// </remarks>
/// <returns>The list of Views that have Application-scoped key bindings.</returns>
internal static List<KeyBinding> GetViewKeyBindings ()
{
// Get the list of views that do not have Application-scoped key bindings
return KeyBindings.Bindings
.Where (kv => kv.Value.Scope != KeyBindingScope.Application)
.Select (kv => kv.Value)
.Distinct ()
.ToList ();
}

private static void ReplaceKey (Key oldKey, Key newKey)
{
if (KeyBindings.Bindings.Count == 0)
{
return;
}

if (newKey == Key.Empty)
{
KeyBindings.Remove (oldKey);
}
else
{
if (KeyBindings.TryGet(oldKey, out KeyBinding binding))
{
KeyBindings.Remove (oldKey);
KeyBindings.Add (newKey, binding);
}
else
{
KeyBindings.Add (newKey, binding);
}
KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
}
}


#endregion Application-scoped KeyBindings

/// <summary>
Expand All @@ -321,16 +281,15 @@ private static void ReplaceKey (Key oldKey, Key newKey)
/// </summary>
/// <remarks>
/// <para>
/// This version of AddCommand is for commands that do not require a <see cref="CommandContext"/>.
/// This version of AddCommand is for commands that do not require a <see cref="ICommandContext"/>.
/// </para>
/// </remarks>
/// <param name="command">The command.</param>
/// <param name="f">The function.</param>
private static void AddCommand (Command command, Func<bool?> f) { CommandImplementations! [command] = ctx => f (); }
private static void AddCommand (Command command, Func<bool?> f) { _commandImplementations! [command] = ctx => f (); }

/// <summary>
/// Commands for Application.
/// </summary>
private static Dictionary<Command, View.CommandImplementation>? CommandImplementations { get; set; }

private static readonly Dictionary<Command, View.CommandImplementation> _commandImplementations = new ();
}
2 changes: 1 addition & 1 deletion Terminal.Gui/Application/Application.Mouse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ internal static void RaiseMouseEvent (MouseEventArgs mouseEvent)
}

// Create a view-relative mouse event to send to the view that is under the mouse.
MouseEventArgs? viewMouseEvent;
MouseEventArgs viewMouseEvent;

if (deepestViewUnderMouse is Adornment adornment)
{
Expand Down
10 changes: 5 additions & 5 deletions Terminal.Gui/Application/Application.Navigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static Key NextTabGroupKey
{
if (_nextTabGroupKey != value)
{
ReplaceKey (_nextTabGroupKey, value);
KeyBindings.Replace (_nextTabGroupKey, value);
_nextTabGroupKey = value;
}
}
Expand All @@ -37,7 +37,7 @@ public static Key NextTabKey
{
if (_nextTabKey != value)
{
ReplaceKey (_nextTabKey, value);
KeyBindings.Replace (_nextTabKey, value);
_nextTabKey = value;
}
}
Expand Down Expand Up @@ -66,7 +66,7 @@ public static Key PrevTabGroupKey
{
if (_prevTabGroupKey != value)
{
ReplaceKey (_prevTabGroupKey, value);
KeyBindings.Replace (_prevTabGroupKey, value);
_prevTabGroupKey = value;
}
}
Expand All @@ -78,10 +78,10 @@ public static Key PrevTabKey
{
get => _prevTabKey;
set
{
{
if (_prevTabKey != value)
{
ReplaceKey (_prevTabKey, value);
KeyBindings.Replace (_prevTabKey, value);
_prevTabKey = value;
}
}
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/Application/Application.Run.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static Key QuitKey
{
if (_quitKey != value)
{
ReplaceKey (_quitKey, value);
KeyBindings.Replace (_quitKey, value);
_quitKey = value;
}
}
Expand All @@ -37,7 +37,7 @@ public static Key ArrangeKey
{
if (_arrangeKey != value)
{
ReplaceKey (_arrangeKey, value);
KeyBindings.Replace (_arrangeKey, value);
_arrangeKey = value;
}
}
Expand Down
3 changes: 2 additions & 1 deletion Terminal.Gui/Application/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ internal static void ResetState (bool ignoreDisposed = false)

ClearScreenNextIteration = false;

AddApplicationKeyBindings ();
KeyBindings.Clear ();
AddKeyBindings ();

// Reset synchronization context to allow the user to run async/await,
// as the main loop has been ended, the synchronization context from
Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/ConsoleDrivers/AnsiEscapeSequenceRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class AnsiEscapeSequenceRequest : AnsiEscapeSequence


/// <summary>
/// Sends the <see cref="Request"/> to the raw output stream of the current <see cref="ConsoleDriver"/>.
/// Sends the <see cref="AnsiEscapeSequence.Request"/> to the raw output stream of the current <see cref="ConsoleDriver"/>.
/// Only call this method from the main UI thread. You should use <see cref="AnsiRequestScheduler"/> if
/// sending many requests.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ internal void ProcessInputAfterParsing (WindowsConsole.InputRecord inputEvent)
case WindowsConsole.EventType.Mouse:
MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent);

if (me is null || me.Flags == MouseFlags.None)
if (/*me is null ||*/ me.Flags == MouseFlags.None)
{
break;
}
Expand Down
Loading
Loading