Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:43:34.715Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

action.rs

291 lines · 10.9 KB · rust
1use std::borrow::Cow;
2use std::fmt::{Display, Formatter, Result};
3
4use collections::HashMap;
5use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use settings_macros::MergeFrom;
9
10/// The value of an entry in `command_aliases`, either a registered action name
11/// or an arbitrary string, for example, a vim command like `"w"` or `"wq"`.
12///
13/// This is a separate type from `ActionName` on purpose, as `ActionName` is
14/// also used by keymap bindings, which only accept registered action names, and
15/// we don't want to weaken those guarantees and accept any arbitrary string.
16#[derive(Serialize, Deserialize, Default, MergeFrom, Clone, Debug, PartialEq)]
17#[serde(transparent)]
18pub struct CommandAliasTarget(String);
19
20impl CommandAliasTarget {
21    pub fn new(name: impl Into<String>) -> Self {
22        Self(name.into())
23    }
24
25    /// Build the runtime schema, an `anyOf` whose entries are the same as the
26    /// ones produced by [`ActionName::build_schema`] plus a final fallback for
27    /// arbitrary strings that aren't registered actions.
28    pub fn build_schema(
29        action_names: &[&str],
30        action_documentation: &HashMap<&str, &str>,
31        deprecations: &HashMap<&str, &str>,
32        deprecation_messages: &HashMap<&str, &str>,
33    ) -> Schema {
34        let action_schema = ActionName::build_schema(
35            action_names.iter().copied(),
36            action_documentation,
37            deprecations,
38            deprecation_messages,
39        );
40
41        let mut alternatives = action_schema
42            .to_value()
43            .get("anyOf")
44            .and_then(Value::as_array)
45            .cloned()
46            .unwrap_or_default();
47
48        let mut exclude = action_names
49            .iter()
50            .map(|name| serde_json::json!({ "const": *name }))
51            .collect::<Vec<Value>>();
52
53        // Since we accept any arbitrary string, besides the list of registered
54        // action names, we'll add any string that uses a `::` pattern to the
55        // list of exclusions, ensuring we display a warning in that case,
56        // otherwise the user might be led into thinking they've typed a valid
57        // action name when that's not the case.
58        exclude.push(serde_json::json!({ "pattern": "::" }));
59
60        alternatives.push(serde_json::json!({
61            "type": "string",
62            "not": { "anyOf": exclude }
63        }));
64
65        json_schema!({ "anyOf": alternatives })
66    }
67}
68
69impl Display for CommandAliasTarget {
70    fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
71        write!(formatter, "{}", self.0)
72    }
73}
74
75impl AsRef<str> for CommandAliasTarget {
76    fn as_ref(&self) -> &str {
77        &self.0
78    }
79}
80
81impl JsonSchema for CommandAliasTarget {
82    fn schema_name() -> Cow<'static, str> {
83        "CommandAliasTarget".into()
84    }
85
86    /// Returns `true` as a placeholder.
87    ///
88    /// The real schema, an `anyOf` of every registered action name with action
89    /// documentation and deprecation metadata, as well as any arbitrary string,
90    /// cannot be produced here because `JsonSchema::json_schema` receives no
91    /// runtime context. It is instead built by call sites that do have access
92    /// to the GPUI action registry using [`CommandAliasTarget::build_schema`].
93    fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
94        json_schema!(true)
95    }
96}
97
98/// The name of a registered GPUI action, serialized as a plain JSON string, for
99/// example, "editor::Cancel"` or `"workspace::CloseActiveItem"`.
100///
101/// This newtype exists so that settings fields like `command_aliases`, or the
102/// keymap file bindings, can request JSON-schema auto completion over the set
103/// of actions known at runtime.
104#[derive(Serialize, Deserialize, Default, MergeFrom, Clone, Debug, PartialEq)]
105#[serde(transparent)]
106pub struct ActionName(String);
107
108/// Small helper function to populate the schema's `deprecationMessage` field with the
109/// provided deprecation message.
110fn add_deprecation(schema: &mut Schema, message: String) {
111    schema.insert("deprecationMessage".into(), Value::String(message));
112}
113
114/// Small helper function to populate the schema's `description` field with the
115/// provided description.
116fn add_description(schema: &mut Schema, description: &str) {
117    schema.insert("description".into(), Value::String(description.to_string()));
118}
119
120impl ActionName {
121    pub fn new(name: impl Into<String>) -> Self {
122        Self(name.into())
123    }
124
125    /// Build the JSON schema to be used for `$defs/ActionName`, basically an
126    /// `anyOf` of all of the available actions with per-action documentation
127    /// and deprecation metadata attached.
128    pub fn build_schema<'a>(
129        action_names: impl IntoIterator<Item = &'a str>,
130        action_documentation: &HashMap<&str, &str>,
131        deprecations: &HashMap<&str, &str>,
132        deprecation_messages: &HashMap<&str, &str>,
133    ) -> Schema {
134        let mut alternatives = Vec::new();
135
136        for action_name in action_names {
137            let mut entry = json_schema!({
138                "type": "string",
139                "const": action_name
140            });
141
142            if let Some(message) = deprecation_messages.get(action_name) {
143                add_deprecation(&mut entry, message.to_string());
144            } else if let Some(new_name) = deprecations.get(action_name) {
145                add_deprecation(&mut entry, format!("Deprecated, use {new_name}"));
146            }
147
148            if let Some(description) = action_documentation.get(action_name) {
149                add_description(&mut entry, description);
150            }
151
152            alternatives.push(entry);
153        }
154
155        json_schema!({ "anyOf": alternatives })
156    }
157}
158
159impl Display for ActionName {
160    fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
161        write!(formatter, "{}", self.0)
162    }
163}
164
165impl AsRef<str> for ActionName {
166    fn as_ref(&self) -> &str {
167        &self.0
168    }
169}
170
171impl JsonSchema for ActionName {
172    /// The name under which this type should be stored in a generator's `$defs`
173    /// map when schemars encounters it during schema generation.
174    /// Keeping it stable as `"ActionName"` lets consumers reference it by
175    /// `#/$defs/ActionName` and lets [`util::schemars::replace_subschema`] look
176    /// it up at runtime to swap in the real schema.
177    fn schema_name() -> Cow<'static, str> {
178        "ActionName".into()
179    }
180
181    /// Returns `true` as a placeholder.
182    ///
183    /// The real schema, an `anyOf` of every registered action name with action
184    /// documentation and deprecation metadata, cannot be produced here because
185    /// `JsonSchema::json_schema` receives no runtime context. It is instead
186    /// built by call sites that do have access to the GPUI action registry
187    /// using [`ActionName::build_schema`].
188    fn json_schema(_: &mut SchemaGenerator) -> Schema {
189        json_schema!(true)
190    }
191}
192
193/// A GPUI action together with its input data, serialized as a two-element JSON
194/// array of the form `["namespace::Name", { ... }]`, for example,
195/// `["pane::ActivateItem", { "index": 0 }]`.
196#[derive(Deserialize, Default)]
197#[serde(transparent)]
198pub struct ActionWithArguments(pub Value);
199
200impl JsonSchema for ActionWithArguments {
201    /// The name under which this type should be stored in a generator's `$defs`
202    /// map when schemars encounters it during schema generation.
203    /// Keeping it stable as `"ActionWithArguments"` lets consumers reference it
204    /// by `#/$defs/ActionWithArguments` and lets
205    /// [`util::schemars::replace_subschema`] look it up at runtime to swap in
206    /// the real schema.
207    fn schema_name() -> Cow<'static, str> {
208        "ActionWithArguments".into()
209    }
210
211    /// Returns `true` as a placeholder.
212    ///
213    /// The real schema, an `anyOf` of every registered action name that
214    /// supports arguments, with action documentation and deprecation metadata,
215    /// cannot be produced here because `JsonSchema::json_schema` receives no
216    /// runtime context. At the time of writing, it is instead built by
217    /// [`KeymapFile::generate_json_schema`], where all of the runtime
218    /// information is available.
219    fn json_schema(_: &mut SchemaGenerator) -> Schema {
220        json_schema!(true)
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227
228    #[test]
229    fn build_schema_produces_anyof_of_consts_per_name() {
230        let mut action_documentation = HashMap::default();
231        let mut deprecations = HashMap::default();
232        let mut deprecation_messages = HashMap::default();
233        action_documentation.insert("editor::Cancel", "Cancel the current operation.");
234        deprecations.insert("workspace::CloseCurrentItem", "workspace::CloseActiveItem");
235        deprecation_messages.insert("editor::Explode", "DO NOT USE!");
236
237        let schema = ActionName::build_schema(
238            [
239                "editor::Cancel",
240                "editor::Explode",
241                "workspace::CloseCurrentItem",
242                "workspace::CloseActiveItem",
243            ],
244            &action_documentation,
245            &deprecations,
246            &deprecation_messages,
247        );
248
249        let value = schema.to_value();
250        let values = value
251            .pointer("/anyOf")
252            .and_then(|v| v.as_array())
253            .expect("anyOf should be present");
254        assert_eq!(values.len(), 4);
255
256        let (name, schema_type, description) = (
257            values[0].get("const").and_then(Value::as_str),
258            values[0].get("type").and_then(Value::as_str),
259            values[0].get("description").and_then(Value::as_str),
260        );
261        assert_eq!(name, Some("editor::Cancel"));
262        assert_eq!(schema_type, Some("string"));
263        assert_eq!(description, Some("Cancel the current operation."));
264
265        let (name, schema_type, message) = (
266            values[1].get("const").and_then(Value::as_str),
267            values[1].get("type").and_then(Value::as_str),
268            values[1].get("deprecationMessage").and_then(Value::as_str),
269        );
270        assert_eq!(name, Some("editor::Explode"));
271        assert_eq!(schema_type, Some("string"));
272        assert_eq!(message, Some("DO NOT USE!"));
273
274        let (name, schema_type, message) = (
275            values[2].get("const").and_then(Value::as_str),
276            values[2].get("type").and_then(Value::as_str),
277            values[2].get("deprecationMessage").and_then(Value::as_str),
278        );
279        assert_eq!(name, Some("workspace::CloseCurrentItem"));
280        assert_eq!(schema_type, Some("string"));
281        assert_eq!(message, Some("Deprecated, use workspace::CloseActiveItem"));
282
283        let (name, schema_type) = (
284            values[3].get("const").and_then(Value::as_str),
285            values[3].get("type").and_then(Value::as_str),
286        );
287        assert_eq!(name, Some("workspace::CloseActiveItem"));
288        assert_eq!(schema_type, Some("string"));
289    }
290}
291
Served at tenant.openagents/omega Member data and write actions are omitted.