Skip to main content

fret_runtime/
commands.rs

1use std::{collections::HashMap, sync::Arc};
2
3use crate::{CommandId, DefaultKeybinding, WhenExpr};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6/// A command that should be forwarded to the host OS when supported (e.g. menu integration).
7pub enum OsAction {
8    Cut,
9    Copy,
10    Paste,
11    SelectAll,
12    Undo,
13    Redo,
14}
15
16#[derive(Default)]
17/// Registry of known commands and their metadata.
18///
19/// Apps typically populate this during startup and use it to drive keybinding resolution,
20/// command palettes, and menu surfaces.
21pub struct CommandRegistry {
22    commands: HashMap<CommandId, CommandMeta>,
23}
24
25#[derive(Debug, Clone)]
26/// Metadata describing a command (title, keybindings, visibility, routing scope).
27pub struct CommandMeta {
28    pub title: Arc<str>,
29    pub description: Option<Arc<str>>,
30    pub category: Option<Arc<str>>,
31    pub keywords: Vec<Arc<str>>,
32    pub default_keybindings: Vec<DefaultKeybinding>,
33    pub when: Option<WhenExpr>,
34    pub os_action: Option<OsAction>,
35    pub scope: CommandScope,
36    pub hidden: bool,
37    pub repeatable: bool,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41/// Routing scope for a command.
42pub enum CommandScope {
43    /// Routed through the focused UI node (with bubbling), per ADR 0020.
44    Widget,
45    /// Handled at the window/app driver boundary (e.g. create/close windows, toggle overlays).
46    Window,
47    /// Global, cross-window command handled by the app.
48    App,
49}
50
51impl CommandMeta {
52    pub fn new(title: impl Into<Arc<str>>) -> Self {
53        Self {
54            title: title.into(),
55            description: None,
56            category: None,
57            keywords: Vec::new(),
58            default_keybindings: Vec::new(),
59            when: None,
60            os_action: None,
61            scope: CommandScope::Window,
62            hidden: false,
63            repeatable: false,
64        }
65    }
66
67    pub fn with_description(mut self, description: impl Into<Arc<str>>) -> Self {
68        self.description = Some(description.into());
69        self
70    }
71
72    pub fn with_category(mut self, category: impl Into<Arc<str>>) -> Self {
73        self.category = Some(category.into());
74        self
75    }
76
77    pub fn with_keywords(
78        mut self,
79        keywords: impl IntoIterator<Item = impl Into<Arc<str>>>,
80    ) -> Self {
81        self.keywords = keywords.into_iter().map(Into::into).collect();
82        self
83    }
84
85    pub fn with_default_keybindings(
86        mut self,
87        bindings: impl IntoIterator<Item = DefaultKeybinding>,
88    ) -> Self {
89        self.default_keybindings = bindings.into_iter().collect();
90        self
91    }
92
93    pub fn with_when(mut self, when: WhenExpr) -> Self {
94        self.when = Some(when);
95        self
96    }
97
98    pub fn with_os_action(mut self, os_action: OsAction) -> Self {
99        self.os_action = Some(os_action);
100        self
101    }
102
103    pub fn with_scope(mut self, scope: CommandScope) -> Self {
104        self.scope = scope;
105        self
106    }
107
108    pub fn hidden(mut self) -> Self {
109        self.hidden = true;
110        self
111    }
112
113    pub fn repeatable(mut self) -> Self {
114        self.repeatable = true;
115        self
116    }
117}
118
119impl CommandRegistry {
120    pub fn register(&mut self, id: CommandId, meta: CommandMeta) {
121        self.commands.insert(id, meta);
122    }
123
124    pub fn get(&self, id: CommandId) -> Option<&CommandMeta> {
125        self.commands.get(&id)
126    }
127
128    pub fn iter(&self) -> impl Iterator<Item = (&CommandId, &CommandMeta)> {
129        self.commands.iter()
130    }
131}