This document proposes a command system for XenoAtom.Terminal.UI that unifies:
Ctrl+Z, Ctrl+R, Ctrl+F, …)CommandBar / “key hints” control)The goal is to make “what can I do right now?” obvious without sacrificing performance or the existing retained-mode + binding model.
Ctrl+K then Ctrl+P)TextArea: Find/Replace; TextEditorBase: Undo/Redo)Ctrl+Q: quit; F12: debug overlay; Ctrl+P: command palette)CommandBar (key hints row)F1), if desiredICommand framework.Existing related types:
TerminalApp.DispatchKeyEvent(...) traverses FocusedElement → Parent and resolves matching Command instances
registered on each visual (Visual.Commands), then falls back to app/global commands (TerminalApp.GlobalCommands).Visual.KeyDownEvent (so shortcuts behave consistently even if controls don’t handle
KeyDownEvent).Visual.AddKeyBinding(KeyGesture, Action) is still available, but it is implemented as a hidden Command
(Presentation = None) to keep all shortcut routing centralized in the command system.MenuBar and CommandPalette exist and represent “actions” in custom waysThe command system should build on this rather than replace everything immediately.
KeyGesture (built on XenoAtom.Terminal input model).Ctrl+K then P).Ctrl+K) while the system waits for subsequent strokes.Visual instance.TerminalApp (or another app-level registry).Introduce a new type (name can vary, examples below use Command):
public sealed class Command
{
public required string Id { get; init; }
// User-facing label. Markup is supported (Theme tokens should work).
public required string LabelMarkup { get; init; }
// Optional longer description used by tooltips / help dialogs.
public string? DescriptionMarkup { get; init; }
// Optional gesture to display + execute from keyboard.
public KeyGesture? Gesture { get; init; }
// Optional multi-stroke shortcut (e.g. Ctrl+K then Ctrl+P).
// When set, Gesture must be null.
public KeySequence? Sequence { get; init; }
public CommandImportance Importance { get; init; } = CommandImportance.Secondary;
public CommandPresentation Presentation { get; init; } = CommandPresentation.CommandBar;
// Execute/can-execute are target-aware so a single static command definition can be reused.
public required Action<Visual> Execute { get; init; }
public Func<Visual, bool>? CanExecute { get; init; }
public Func<Visual, bool>? IsVisible { get; init; }
}
public enum CommandImportance
{
Primary,
Secondary,
Tertiary,
}
[Flags]
public enum CommandPresentation
{
None = 0,
CommandBar = 1,
CommandPalette = 2,
Menu = 4,
ContextMenu = 8,
}
Notes:
LabelMarkup / DescriptionMarkup are strings for ergonomics; they can be rendered by Markup.Execute and CanExecute take a Visual parameter to avoid per-instance closures (typical controls can use method groups).Gesture and Sequence are mutually exclusive (one command has a single shortcut representation).TerminalChar.CtrlA) for
KeyGesture(char, TerminalModifiers) because terminals commonly emit control characters rather than the printable
letter (see existing usage throughout the codebase).KeySequenceIntroduce a small type to represent sequences and to keep formatting/parsing centralized:
public readonly record struct KeySequence
{
public KeySequence(params KeyGesture[] gestures);
public ReadOnlySpan<KeyGesture> Gestures { get; }
public override string ToString(); // e.g. "Ctrl+K Ctrl+P"
public static bool TryParse(ReadOnlySpan<char> text, out KeySequence sequence);
}
Notes:
KeyGesture.ToString() for each stroke.Add an internal list of commands per Visual and optionally per TerminalApp.
Proposed Visual API:
public abstract partial class Visual
{
public IReadOnlyList<Command> Commands { get; }
public void AddCommand(Command command);
public bool RemoveCommand(string id);
}
Proposed TerminalApp API:
public sealed partial class TerminalApp
{
public IReadOnlyList<Command> GlobalCommands { get; }
public void AddGlobalCommand(Command command);
public bool RemoveGlobalCommand(string id);
}
Lookup algorithm for UI surfaces:
FocusedElement (or HoveredElement for context menus) and walk up Parent.IsVisible == null || IsVisible(target))Id (and optionally by gesture).Importance then stable insertion orderGesture routing should remain consistent with today’s behavior:
FocusedElement → Parent.CanExecute is true:
This is implemented by resolving Command instances during TerminalApp.DispatchKeyEvent(...), using the same focus-walk
(FocusedElement → Parent) and then falling back to TerminalApp.GlobalCommands.
Key sequences must be supported in v1 (e.g. Emacs-style Ctrl+K followed by another key).
When a key event arrives:
Gesture) using the existing focus-walk resolution (focused → parents → global).To keep single-key bindings simple and predictable:
This avoids “wait for timeout to disambiguate” behavior.
Esc, orThe timeout should be configurable (likely on TerminalAppOptions in the future).
While a prefix is active, command discovery UI should adapt:
CommandBar shows only commands that:
Ctrl+K …) to explain the current state.KeyBindingXenoAtom.Terminal.UI implements Option B: Command is the single model for shortcuts and discoverability.
Visual.AddKeyBinding(...) remains for compatibility and ergonomics, but it registers an internal/hidden Command
(so the command router remains the source of truth).KeyBinding data structure for routing.CommandBar control (key hints)CommandBar is a lightweight control that displays the most relevant commands for the current context (focused element),
similar to “key hints”:
Ctrl+F Find | Ctrl+H Replace | Ctrl+Z Undo | Ctrl+R Redo | Ctrl+Q Quit
public sealed class CommandBar : Visual
{
[Bindable] public partial CommandImportance MinImportance { get; set; } = CommandImportance.Secondary;
[Bindable] public partial bool ShowDisabled { get; set; }
[Bindable] public partial bool IncludeGlobalCommands { get; set; } = true;
[Bindable] public partial int MaxItems { get; set; } = 6;
}
Behavior:
App.FocusedElement changes and refreshes accordingly.Add CommandBarStyle:
KeyStyle (foreground/background/border for the keycap)LabelStyleDisabledKeyStyle / DisabledLabelStyleSeparatorText (e.g. two spaces, |, double pipe, etc.)Markup usage:
LabelMarkup should be parsed via MarkupTextParser with the current theme tokens.CommandPalette should be able to show all commands that have CommandPresentation.CommandPalette.
Longer term:
CommandPaletteItem with Command (palette entries can be commands)Menu items and context menu entries should be able to reference a Command:
LabelMarkupCanExecuteExecuteContext menus:
CommandPresentation.ContextMenu.This creates a cohesive path:
Focus on “discoverable” shortcuts that users benefit from seeing:
TextBox, TextArea, MaskedInput, NumberBox<T>)Register commands (typically local):
Ctrl+Z)Ctrl+R)Ctrl+A) (if supported)Ctrl+F / Ctrl+H) where applicableCtrl+F)Enter / Shift+Enter) could be included if helpfulRegister at TerminalApp:
Ctrl+Q in fullscreen; Esc in inline, if kept)F12)Ctrl+P)CanExecute / IsVisible may read bindable properties (e.g. TextEditorBase.CanUndo).
CommandBar renders/evaluates these, dependency tracking should naturally trigger re-render when these values change.UnsafeList<Command> or pooled lists)CommandBar can cache parsed Markup visuals per command instance if necessary, or parse lazily.IdCommandBar:
TextArea exposes undo/redo/find/replace commands in contextCommand + per-visual registration (Visual.AddCommand) and app/global registration.CommandBar with styling + demo integration (ControlsDemo footer).