Skip to content

ExecuteChord says it "executes a command" but runs nothing, and the library has no way to attach a handler to a command #223

Open

Description

@matt-edmondson

What's wrong

The contract says ExecuteChord executes the bound command. Keybinding/Contracts/IKeybindingService.cs:149-162:

/// Executes a command if it has a chord binding in the active profile
/// <returns>The command ID that was executed, null if no binding found</returns>
public string? ExecuteChord(Chord chord);

The implementation only looks up the command ID, and its own comment says so. Keybinding/Services/KeybindingService.cs:209-223:

string? commandId = FindRegisteredCommand(profileId, chord);

// In a real implementation, this would trigger command execution
// For now, we just return the command ID that would be executed
return commandId;

This is now the same code as FindCommandByChord (:257-267), because both have called FindRegisteredCommand since #159. Neither Command, ICommandRegistry nor IKeybindingService has an event or takes a delegate. So there is no way to give a command behaviour that ExecuteChord could run.

Why it matters

An app that sends key presses to manager.Keybindings.ExecuteChord(chord), as the name and XML docs invite, never runs anything when the user presses Ctrl+S. The non-null return value ("the command ID that was executed") makes the call look like it worked, so nothing points to the missing dispatch. Callers also face two public methods with identical behaviour and no guidance on which to use.

Repro, run against main (89f14b9). It binds file.save to Ctrl+S, calls both methods, then reflects over the three types for events and delegate-typed parameters:

ExecuteChord(Ctrl+S) = file.save
FindCommandByChord(Ctrl+S) = file.save
  Command: events=[] delegate-params=[]
  ICommandRegistry: events=[] delegate-params=[]
  IKeybindingService: events=[] delegate-params=[]

Suggested fix

Choose one:

  1. Make execution real (preferred). Let callers attach behaviour to a command, for example ICommandRegistry.RegisterHandler(string commandId, Action handler) (or a Func<CancellationToken, Task> variant), and/or raise a ChordExecuted event. ExecuteChord then invokes the handler of the resolved command, and returns the ID only if a handler ran (or reports "bound but no handler" distinctly).
  2. Make the contract honest. Reword ExecuteChord's docs to "resolves the command bound to the chord; does not execute it", and mark it [Obsolete] in favour of FindCommandByChord.

Acceptance criteria

  • Either a test shows ExecuteChord invoking a registered handler, and returning null or false when the bound command has no handler, or ExecuteChord is documented as lookup-only and marked obsolete.
  • The "In a real implementation… For now…" comment is gone.
  • README / USAGE_EXAMPLES no longer imply that ExecuteChord performs the command when it doesn't.

Activity

  1. matt-edmondson commented on Nov 5, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    Category: Feature Request (with a contract/doc bug component)
    Priority: Medium

    Assessment: The documentation for ExecuteChord says it executes the bound command. In fact it does exactly what FindCommandByChord does, and the library has no handler or event mechanism, so there is nothing it could execute. The non-null return value makes the call look like it worked, which misleads callers. Nothing is corrupted, though, and the workaround (dispatching on the returned ID yourself) is easy. Option 2 in the issue, making the contract honest, is a quick fix. Option 1, adding handler registration, is the larger feature and changes the API design.

    Suggested area / assignment: IKeybindingService / ICommandRegistry API surface. Before anyone starts, the maintainer should decide between the two options, because option 1 adds public API.

    Related: #135 (multi-chord phrases can't be bound or executed) touches the same execution path. If handler dispatch is added, design it so that phrase execution can use it too. #145 is another case of documented behaviour having no effect.

    In progress? No open PR addresses this. The open PRs #219 and #220 are unrelated.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions