Skip to content

A Command built from a CommandId with surrounding whitespace registers but can never be found, bound or unregistered #123

Description

@matt-edmondson

What's wrong

The two Command constructors store the id differently:

  • Command(string id, …) trims the id.
  • Command(CommandId id, …) (Keybinding/Models/Command.cs:18-23) stores Id = id as given.

CommandRegistry.RegisterCommand keys its dictionary on command.Id untrimmed (Keybinding/Services/CommandRegistry.cs, TryAdd(command.Id, …)). But IsCommandRegistered, GetCommand and UnregisterCommand all trim the id they are given before looking it up. So a command whose CommandId has leading or trailing whitespace is stored under a key that no lookup can ever produce.

CommandId's documented pattern (^[a-z0-9]+(\.[a-z0-9]+)*$) is not enforced either, so CommandId.Create(" file.save ") succeeds.

Reproduction

This was executed as a scratch MSTest:

reg.RegisterCommand(new Command(CommandId.Create(" file.save "), CommandName.Create("Save"))); // true
reg.IsCommandRegistered("file.save");                 // False
reg.IsCommandRegistered(" file.save ");               // False (lookup trims, stored key doesn't)
reg.GetAllCommands();                                 // contains "[ file.save ]"
svc.BindChord(" file.save ", Chord.Parse("Ctrl+S"));  // False
reg.UnregisterCommand(" file.save ");                 // False

After a save and reload, the repository rebuilds the command with the string constructor, so the id comes back trimmed. The in-memory state and the reloaded state disagree.

Why it matters

Command ids often come from configuration files or plugin manifests, where stray whitespace is common. The registration looks successful and the command shows up in listings, but it can never be bound, executed or removed, and nothing reports an error.

Suggested fix / acceptance criteria

  • Normalize in one place: trim in the Command(CommandId, …) constructor, or key CommandRegistry on command.Id.ToString().Trim().
  • new Command(CommandId.Create(" file.save "), …) then behaves exactly like new Command(" file.save ", …).
  • Enforcing CommandId's regex is not recommended as the fix: the similar NoteName pattern would reject + and ,, which the Ctrl++ / Ctrl+, support relies on.

Activity

  1. matt-edmondson commented on Sep 26, 2026

    @matt-edmondson
    ContributorAuthor

    Triage


    Generated by Claude Code

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

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions