Skip to content

Commit 7676f89

Browse files
authored
Merge pull request #3880 from tig/v2_3778-Command-Decoupling
Fixes #3778: Decouples `Command` from `KeyBindings`
2 parents 62641c8 + 63b3ebf commit 7676f89

File tree

99 files changed

+2880
-2657
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+2880
-2657
lines changed

Terminal.Gui/Application/Application.Initialization.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ internal static void InternalInit (
9292
}
9393
}
9494

95-
AddApplicationKeyBindings ();
95+
AddKeyBindings ();
9696

9797
// Start the process of configuration management.
9898
// Note that we end up calling LoadConfigurationFromAllSources

Terminal.Gui/Application/Application.Keyboard.cs

+39-80
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,17 @@ public static bool RaiseKeyDownEvent (Key key)
4545

4646
// Invoke any Application-scoped KeyBindings.
4747
// The first view that handles the key will stop the loop.
48-
foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.Bindings.Where (b => b.Key == key.KeyCode))
48+
// foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.GetBindings (key))
49+
if (KeyBindings.TryGet (key, out KeyBinding binding))
4950
{
50-
if (binding.Value.BoundView is { })
51+
if (binding.Target is { })
5152
{
52-
if (!binding.Value.BoundView.Enabled)
53+
if (!binding.Target.Enabled)
5354
{
5455
return false;
5556
}
5657

57-
bool? handled = binding.Value.BoundView?.InvokeCommands (binding.Value.Commands, binding.Key, binding.Value);
58+
bool? handled = binding.Target?.InvokeCommands (binding.Commands, binding);
5859

5960
if (handled != null && (bool)handled)
6061
{
@@ -63,16 +64,17 @@ public static bool RaiseKeyDownEvent (Key key)
6364
}
6465
else
6566
{
66-
if (!KeyBindings.TryGet (key, KeyBindingScope.Application, out KeyBinding appBinding))
67+
// BUGBUG: this seems unneeded.
68+
if (!KeyBindings.TryGet (key, out KeyBinding keybinding))
6769
{
68-
continue;
70+
return false;
6971
}
7072

7173
bool? toReturn = null;
7274

73-
foreach (Command command in appBinding.Commands)
75+
foreach (Command command in keybinding.Commands)
7476
{
75-
toReturn = InvokeCommand (command, key, appBinding);
77+
toReturn = InvokeCommand (command, key, keybinding);
7678
}
7779

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

8284
return false;
8385

84-
static bool? InvokeCommand (Command command, Key key, KeyBinding appBinding)
86+
static bool? InvokeCommand (Command command, Key key, KeyBinding binding)
8587
{
86-
if (!CommandImplementations!.ContainsKey (command))
88+
if (!_commandImplementations!.ContainsKey (command))
8789
{
8890
throw new NotSupportedException (
8991
@$"A KeyBinding was set up for the command {command} ({key}) but that command is not supported by Application."
9092
);
9193
}
9294

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

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

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

156159
#region Application-scoped KeyBindings
157160

158-
static Application () { AddApplicationKeyBindings (); }
161+
static Application () { AddKeyBindings (); }
159162

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

163-
internal static void AddApplicationKeyBindings ()
166+
internal static void AddKeyBindings ()
164167
{
165-
CommandImplementations = new ();
168+
_commandImplementations.Clear ();
166169

167170
// Things this view knows how to do
168171
AddCommand (
@@ -231,83 +234,40 @@ internal static void AddApplicationKeyBindings ()
231234
return false;
232235
});
233236

234-
KeyBindings.Clear ();
235-
236237
// Resources/config.json overrides
238+
QuitKey = Key.Esc;
237239
NextTabKey = Key.Tab;
238240
PrevTabKey = Key.Tab.WithShift;
239241
NextTabGroupKey = Key.F6;
240242
PrevTabGroupKey = Key.F6.WithShift;
241-
QuitKey = Key.Esc;
242243
ArrangeKey = Key.F5.WithCtrl;
243244

244-
KeyBindings.Add (QuitKey, KeyBindingScope.Application, Command.Quit);
245-
246-
KeyBindings.Add (Key.CursorRight, KeyBindingScope.Application, Command.NextTabStop);
247-
KeyBindings.Add (Key.CursorDown, KeyBindingScope.Application, Command.NextTabStop);
248-
KeyBindings.Add (Key.CursorLeft, KeyBindingScope.Application, Command.PreviousTabStop);
249-
KeyBindings.Add (Key.CursorUp, KeyBindingScope.Application, Command.PreviousTabStop);
250-
KeyBindings.Add (NextTabKey, KeyBindingScope.Application, Command.NextTabStop);
251-
KeyBindings.Add (PrevTabKey, KeyBindingScope.Application, Command.PreviousTabStop);
245+
// Need to clear after setting the above to ensure actually clear
246+
// because set_QuitKey etc.. may call Add
247+
KeyBindings.Clear ();
252248

253-
KeyBindings.Add (NextTabGroupKey, KeyBindingScope.Application, Command.NextTabGroup);
254-
KeyBindings.Add (PrevTabGroupKey, KeyBindingScope.Application, Command.PreviousTabGroup);
249+
KeyBindings.Add (QuitKey, Command.Quit);
250+
KeyBindings.Add (NextTabKey, Command.NextTabStop);
251+
KeyBindings.Add (PrevTabKey, Command.PreviousTabStop);
252+
KeyBindings.Add (NextTabGroupKey, Command.NextTabGroup);
253+
KeyBindings.Add (PrevTabGroupKey, Command.PreviousTabGroup);
254+
KeyBindings.Add (ArrangeKey, Command.Edit);
255255

256-
KeyBindings.Add (ArrangeKey, KeyBindingScope.Application, Command.Edit);
256+
KeyBindings.Add (Key.CursorRight, Command.NextTabStop);
257+
KeyBindings.Add (Key.CursorDown, Command.NextTabStop);
258+
KeyBindings.Add (Key.CursorLeft, Command.PreviousTabStop);
259+
KeyBindings.Add (Key.CursorUp, Command.PreviousTabStop);
257260

258261
// TODO: Refresh Key should be configurable
259-
KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh);
262+
KeyBindings.Add (Key.F5, Command.Refresh);
260263

261264
// TODO: Suspend Key should be configurable
262265
if (Environment.OSVersion.Platform == PlatformID.Unix)
263266
{
264-
KeyBindings.Add (Key.Z.WithCtrl, KeyBindingScope.Application, Command.Suspend);
265-
}
266-
}
267-
268-
/// <summary>
269-
/// Gets the list of Views that have <see cref="KeyBindingScope.Application"/> key bindings.
270-
/// </summary>
271-
/// <remarks>
272-
/// This is an internal method used by the <see cref="View"/> class to add Application key bindings.
273-
/// </remarks>
274-
/// <returns>The list of Views that have Application-scoped key bindings.</returns>
275-
internal static List<KeyBinding> GetViewKeyBindings ()
276-
{
277-
// Get the list of views that do not have Application-scoped key bindings
278-
return KeyBindings.Bindings
279-
.Where (kv => kv.Value.Scope != KeyBindingScope.Application)
280-
.Select (kv => kv.Value)
281-
.Distinct ()
282-
.ToList ();
283-
}
284-
285-
private static void ReplaceKey (Key oldKey, Key newKey)
286-
{
287-
if (KeyBindings.Bindings.Count == 0)
288-
{
289-
return;
290-
}
291-
292-
if (newKey == Key.Empty)
293-
{
294-
KeyBindings.Remove (oldKey);
295-
}
296-
else
297-
{
298-
if (KeyBindings.TryGet(oldKey, out KeyBinding binding))
299-
{
300-
KeyBindings.Remove (oldKey);
301-
KeyBindings.Add (newKey, binding);
302-
}
303-
else
304-
{
305-
KeyBindings.Add (newKey, binding);
306-
}
267+
KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
307268
}
308269
}
309270

310-
311271
#endregion Application-scoped KeyBindings
312272

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

331291
/// <summary>
332292
/// Commands for Application.
333293
/// </summary>
334-
private static Dictionary<Command, View.CommandImplementation>? CommandImplementations { get; set; }
335-
294+
private static readonly Dictionary<Command, View.CommandImplementation> _commandImplementations = new ();
336295
}

Terminal.Gui/Application/Application.Mouse.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ internal static void RaiseMouseEvent (MouseEventArgs mouseEvent)
184184
}
185185

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

189189
if (deepestViewUnderMouse is Adornment adornment)
190190
{

Terminal.Gui/Application/Application.Navigation.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static Key NextTabGroupKey
2222
{
2323
if (_nextTabGroupKey != value)
2424
{
25-
ReplaceKey (_nextTabGroupKey, value);
25+
KeyBindings.Replace (_nextTabGroupKey, value);
2626
_nextTabGroupKey = value;
2727
}
2828
}
@@ -37,7 +37,7 @@ public static Key NextTabKey
3737
{
3838
if (_nextTabKey != value)
3939
{
40-
ReplaceKey (_nextTabKey, value);
40+
KeyBindings.Replace (_nextTabKey, value);
4141
_nextTabKey = value;
4242
}
4343
}
@@ -66,7 +66,7 @@ public static Key PrevTabGroupKey
6666
{
6767
if (_prevTabGroupKey != value)
6868
{
69-
ReplaceKey (_prevTabGroupKey, value);
69+
KeyBindings.Replace (_prevTabGroupKey, value);
7070
_prevTabGroupKey = value;
7171
}
7272
}
@@ -78,10 +78,10 @@ public static Key PrevTabKey
7878
{
7979
get => _prevTabKey;
8080
set
81-
{
81+
{
8282
if (_prevTabKey != value)
8383
{
84-
ReplaceKey (_prevTabKey, value);
84+
KeyBindings.Replace (_prevTabKey, value);
8585
_prevTabKey = value;
8686
}
8787
}

Terminal.Gui/Application/Application.Run.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public static Key QuitKey
1919
{
2020
if (_quitKey != value)
2121
{
22-
ReplaceKey (_quitKey, value);
22+
KeyBindings.Replace (_quitKey, value);
2323
_quitKey = value;
2424
}
2525
}
@@ -37,7 +37,7 @@ public static Key ArrangeKey
3737
{
3838
if (_arrangeKey != value)
3939
{
40-
ReplaceKey (_arrangeKey, value);
40+
KeyBindings.Replace (_arrangeKey, value);
4141
_arrangeKey = value;
4242
}
4343
}

Terminal.Gui/Application/Application.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ internal static void ResetState (bool ignoreDisposed = false)
214214

215215
ClearScreenNextIteration = false;
216216

217-
AddApplicationKeyBindings ();
217+
KeyBindings.Clear ();
218+
AddKeyBindings ();
218219

219220
// Reset synchronization context to allow the user to run async/await,
220221
// as the main loop has been ended, the synchronization context from

Terminal.Gui/ConsoleDrivers/AnsiEscapeSequenceRequest.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class AnsiEscapeSequenceRequest : AnsiEscapeSequence
2121

2222

2323
/// <summary>
24-
/// Sends the <see cref="Request"/> to the raw output stream of the current <see cref="ConsoleDriver"/>.
24+
/// Sends the <see cref="AnsiEscapeSequence.Request"/> to the raw output stream of the current <see cref="ConsoleDriver"/>.
2525
/// Only call this method from the main UI thread. You should use <see cref="AnsiRequestScheduler"/> if
2626
/// sending many requests.
2727
/// </summary>

Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ internal void ProcessInputAfterParsing (WindowsConsole.InputRecord inputEvent)
509509
case WindowsConsole.EventType.Mouse:
510510
MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent);
511511

512-
if (me is null || me.Flags == MouseFlags.None)
512+
if (/*me is null ||*/ me.Flags == MouseFlags.None)
513513
{
514514
break;
515515
}

0 commit comments

Comments
 (0)