Skip to content

Command is too tightly coupled with KeyBindings #3778

@tig

Description

@tig

In

I found View.KeyBindings, View.AddCommand, and View.InvokeCommand, etc... to work really well. However, I also realized the implementation is way too coupled with KeyBindings`. Specifically, there are many cases where commands need to be invoked from mouse events.

In fact, I now believe the correct fix for

is to build the mouse equvalent of View.KeyBindings: View.MouseBindings so devs could do:

AddCommand (Command.Context, ShowContextMenu);

KeyBindings.Add (Application.ContextKey, Command.Context);

MouseBindings.Add (MouseFlags.Button3Clicked, Command.Context); // btn3 is right

To address this I'd like to refactor KeyBindings to be generic (KeyBindings<Key>, KeyBindings<MouseFlags>,?) and make the Command API not have any KeyBinding specific knowledge. For example,:

public bool? InvokeCommand (Command command, Key? key = null, KeyBinding? keyBinding = null)...

would become

```cs
public bool? InvokeCommand<BindingType> (Command command, BindingType binding)

In #3677 I tweaked CommandContext to be able to hold arbitrary context data:

public record struct CommandContext
{
    /// <summary>
    ///     Initializes a new instance of <see cref="CommandContext"/> with the specified <see cref="Command"/>,
    /// </summary>
    /// <param name="command"></param>
    /// <param name="key"></param>
    /// <param name="keyBinding"></param>
    /// <param name="data"></param>
    public CommandContext (Command command, Key? key, KeyBinding? keyBinding = null, object? data = null)
    {
        Command = command;
        Key = key;
        KeyBinding = keyBinding;
        Data = data;
    }

    /// <summary>
    ///     The <see cref="Command"/> that is being invoked.
    /// </summary>
    public Command Command { get; set; }

    /// <summary>
    ///     The <see cref="Key"/> that is being invoked. This is the key that was pressed to invoke the <see cref="Command"/>.
    /// </summary>
    public Key? Key { get; set; }

    /// <summary>
    /// The KeyBinding that was used to invoke the <see cref="Command"/>, if any.
    /// </summary>
    public KeyBinding? KeyBinding { get; set; }

    /// <summary>
    ///     Arbitrary data.
    /// </summary>
    public object? Data { get; set; }
}

This struct needs to be like this instead:

/// <summary>
///     Provides context for a <see cref="Command"/> that is being invoked.
/// </summary>
/// <remarks>
///     <para>
///         To define a <see cref="Command"/> that is invoked with context,
///         use <see cref="View.AddCommand(Command,Func{CommandContext,System.Nullable{bool}})"/>.
///     </para>
/// </remarks>
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
public record struct CommandContext <BindingType>
{
    /// <summary>
    ///     Initializes a new instance of <see cref="CommandContext"/> with the specified <see cref="Command"/>,
    /// </summary>
    /// <param name="command"></param>
    /// <param name="key"></param>
    /// <param name="binding"></param>
    /// <param name="data"></param>
    public CommandContext (Command command, BindingType? binding, object? data = null)
    {
        Command = command;
        Binding = binding;
        Data = data;
    }

    /// <summary>
    ///     The <see cref="Command"/> that is being invoked.
    /// </summary>
    public Command Command { get; set; }

    /// <summary>
    /// The keyboard or mouse minding that was used to invoke the <see cref="Command"/>, if any.
    /// </summary>
    public BindingType? Binding { get; set; }

    /// <summary>
    ///     Arbitrary data.
    /// </summary>
    public object? Data { get; set; }
}

Metadata

Metadata

Assignees

Labels

designIssues regarding Terminal.Gui design (bugs, guidelines, debates, etc...)

Type

No type

Projects

Status

✅ Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions