Skip to content

Commands and keybindings

A command is a named, invokable action. Implementations are resolved per invocation from the DI container, so dependencies are constructor parameters and there is no context bag threaded through.

csharp
[Command("app.settings", DisplayName = "Settings", Icon = "Settings", Category = "Shell",
    AllowWhenTextInputFocused = true)]
public sealed class OpenSettingsCommand(ShellViewModel shell) : IAlloyCommand
{
    public Task ExecuteAsync() { shell.SettingsOpen = true; return Task.CompletedTask; }
}

[Command] publishes the metadata menus, toolbars, the palette and the shortcut editor need: DisplayName, Category, Icon (a glyph id), IsUndoable, RequiresConfirmation, ShowInToolbar, and AllowWhenTextInputFocused, which lets a chord fire while a text box has focus. IConditionalCommand adds CanExecute for commands gated on runtime state; the dispatcher checks it and surfaces read it for enable/disable.

Registry and dispatcher

CommandRegistry maps a name to a CommandDescriptor and the implementation type. Register explicitly, or scan an assembly:

csharp
Commands.Register<OpenSettingsCommand>();
Commands.RegisterFromAssembly(typeof(App).Assembly);

KeyboardDispatcher inverts the keymap into gesture to command name, resolves the command through DI, and invokes it. The keymap is a delegate, so the dispatcher is decoupled from any preferences backend: apps merge built-in defaults with user overrides and call Rebuild when those change. RunByNameAsync is the path toolbars and the palette use through ICommandSurface.

Gestures are strings parsed by GestureParser, which wraps Avalonia's parser with Mod+ sugar resolving to Cmd on macOS and Ctrl elsewhere, so a keymap in preferences.json is portable across platforms.

csharp
protected override IReadOnlyDictionary<string, IReadOnlyList<string>> BuildKeymap() =>
    new Dictionary<string, IReadOnlyList<string>>
    {
        ["app.settings"] = ["Mod+OemComma"],
        ["app.quit"] = ["Mod+Q"],
        ["ShowLogViewer"] = ["Shift+F8"],
    };

The palette

CommandPaletteViewModel is a filtered, keyboard-driven list over a fixed set of PaletteItem: type to narrow, arrows to move, Enter to run. CommandPaletteView is its surface, an overlay card with a search box and result list, and owns the keyboard because arrows and Enter must be claimed while focus is in the text box.

It is deliberately not tied to the registry. FromRegistry builds one over your commands, but the same control is the natural scoped picker, "Move to project...", where the list is a handful of one kind of thing no registry knows about. Scoped callers pass their own Placeholder.

The shortcut editor

KeyboardShortcutsEditor binds to a KeyboardShortcutsViewModel, one row per registered command, each row a list of recordable gesture chips. Press a chord into a chip to bind it, Escape to unbind. The view model resolves conflicts by moving the gesture off whatever command held it, with a toast if one is wired.

Everything app-specific goes through IKeymapStore: the effective gestures for a command, the user override if one exists else the defaults, and where overrides persist. Mutations are silent by design; batch several edits, then RaiseChanged once so the preferences chokepoint sees one change and triggers a single save and dispatcher rebuild.

HotkeyBox is the chip on its own: a click-and-press field that stores the captured chord as a Mod+ string.

Released under the MIT License.