Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:57:56.701Z 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

keymap_file.rs

2803 lines · 100.0 KB · rust
1use anyhow::{Context as _, Result};
2use collections::{BTreeMap, HashMap, IndexMap};
3use fs::Fs;
4use gpui::{
5    Action, ActionBuildError, App, InvalidKeystrokeError, KEYSTROKE_PARSE_EXPECTED_MESSAGE,
6    KeyBinding, KeyBindingContextPredicate, KeyBindingMetaIndex, KeybindingKeystroke, Keystroke,
7    NoAction, SharedString, Unbind, generate_list_of_all_registered_actions, register_action,
8};
9use schemars::{JsonSchema, json_schema};
10use serde::Deserialize;
11use serde_json::{Value, json};
12use std::borrow::Cow;
13use std::{any::TypeId, fmt::Write, rc::Rc, sync::Arc, sync::LazyLock};
14use util::ResultExt as _;
15use util::{
16    asset_str,
17    markdown::{MarkdownEscaped, MarkdownInlineCode, MarkdownString},
18    schemars::AllowTrailingCommas,
19};
20
21use crate::SettingsAssets;
22use settings_content::{ActionName, ActionWithArguments};
23use settings_json::{
24    append_top_level_array_value_in_json_text, parse_json_with_comments,
25    replace_top_level_array_value_in_json_text,
26};
27
28pub trait KeyBindingValidator: Send + Sync {
29    fn action_type_id(&self) -> TypeId;
30    fn validate(&self, binding: &KeyBinding) -> Result<(), MarkdownString>;
31}
32
33pub struct KeyBindingValidatorRegistration(pub fn() -> Box<dyn KeyBindingValidator>);
34
35inventory::collect!(KeyBindingValidatorRegistration);
36
37pub(crate) static KEY_BINDING_VALIDATORS: LazyLock<BTreeMap<TypeId, Box<dyn KeyBindingValidator>>> =
38    LazyLock::new(|| {
39        let mut validators = BTreeMap::new();
40        for validator_registration in inventory::iter::<KeyBindingValidatorRegistration> {
41            let validator = validator_registration.0();
42            validators.insert(validator.action_type_id(), validator);
43        }
44        validators
45    });
46
47// Note that the doc comments on these are shown by json-language-server when editing the keymap, so
48// they should be considered user-facing documentation. Documentation is not handled well with
49// schemars-0.8 - when there are newlines, it is rendered as plaintext (see
50// https://github.com/GREsau/schemars/issues/38#issuecomment-2282883519). So for now these docs
51// avoid newlines.
52//
53// TODO: Update to schemars-1.0 once it's released, and add more docs as newlines would be
54// supported. Tracking issue is https://github.com/GREsau/schemars/issues/112.
55
56/// Keymap configuration consisting of sections. Each section may have a context predicate which
57/// determines whether its bindings are used.
58#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
59#[serde(transparent)]
60pub struct KeymapFile(Vec<KeymapSection>);
61
62/// Keymap section which binds keystrokes to actions.
63#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
64pub struct KeymapSection {
65    /// Determines when these bindings are active. When just a name is provided, like `Editor` or
66    /// `Workspace`, the bindings will be active in that context. Boolean expressions like `X && Y`,
67    /// `X || Y`, `!X` are also supported. Some more complex logic including checking OS and the
68    /// current file extension are also supported - see [the
69    /// documentation](https://zed.dev/docs/key-bindings#contexts) for more details.
70    #[serde(default)]
71    pub context: String,
72    /// This option enables specifying keys based on their position on a QWERTY keyboard, by using
73    /// position-equivalent mappings for some non-QWERTY keyboards. This is currently only supported
74    /// on macOS. See the documentation for more details.
75    #[serde(default)]
76    use_key_equivalents: bool,
77    /// This keymap section's unbindings, as a JSON object mapping keystrokes to actions. These are
78    /// parsed before `bindings`, so bindings later in the same section can still take precedence.
79    #[serde(default)]
80    unbind: Option<IndexMap<String, UnbindTargetAction>>,
81    /// This keymap section's bindings, as a JSON object mapping keystrokes to actions. The
82    /// keystrokes key is a string representing a sequence of keystrokes to type, where the
83    /// keystrokes are separated by whitespace. Each keystroke is a sequence of modifiers (`ctrl`,
84    /// `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) followed by a key, separated by `-`. The
85    /// order of bindings does matter. When the same keystrokes are bound at the same context depth,
86    /// the binding that occurs later in the file is preferred. For displaying keystrokes in the UI,
87    /// the later binding for the same action is preferred.
88    #[serde(default)]
89    bindings: Option<IndexMap<String, KeymapAction>>,
90    #[serde(flatten)]
91    unrecognized_fields: IndexMap<String, Value>,
92    // This struct intentionally uses permissive types for its fields, rather than validating during
93    // deserialization. The purpose of this is to allow loading the portion of the keymap that doesn't
94    // have errors. The downside of this is that the errors are not reported with line+column info.
95    // Unfortunately the implementations of the `Spanned` types for preserving this information are
96    // highly inconvenient (`serde_spanned`) and in some cases don't work at all here
97    // (`json_spanned_>value`). Serde should really have builtin support for this.
98}
99
100impl KeymapSection {
101    pub fn bindings(&self) -> impl DoubleEndedIterator<Item = (&String, &KeymapAction)> {
102        self.bindings.iter().flatten()
103    }
104}
105
106/// Keymap action as a JSON value, since it can either be null for no action, or the name of the
107/// action, or an array of the name of the action and the action input.
108///
109/// Unlike the other json types involved in keymaps (including actions), this doc-comment will not
110/// be included in the generated JSON schema, as it manually defines its `JsonSchema` impl. The
111/// actual schema used for it is automatically generated in `KeymapFile::generate_json_schema`.
112#[derive(Debug, Deserialize, Default, Clone)]
113#[serde(transparent)]
114pub struct KeymapAction(Value);
115
116impl std::fmt::Display for KeymapAction {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        match &self.0 {
119            Value::String(s) => write!(f, "{}", s),
120            Value::Array(arr) => {
121                let strings: Vec<String> = arr.iter().map(|v| v.to_string()).collect();
122                write!(f, "{}", strings.join(", "))
123            }
124            _ => write!(f, "{}", self.0),
125        }
126    }
127}
128
129impl JsonSchema for KeymapAction {
130    /// This is used when generating the JSON schema for the `KeymapAction` type, so that it can
131    /// reference the keymap action schema.
132    fn schema_name() -> Cow<'static, str> {
133        "KeymapAction".into()
134    }
135
136    /// This schema will be replaced with the full action schema in
137    /// `KeymapFile::generate_json_schema`.
138    fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
139        json_schema!(true)
140    }
141}
142
143#[derive(Debug, Deserialize, Default, Clone)]
144#[serde(transparent)]
145pub struct UnbindTargetAction(Value);
146
147impl JsonSchema for UnbindTargetAction {
148    fn schema_name() -> Cow<'static, str> {
149        "UnbindTargetAction".into()
150    }
151
152    fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
153        json_schema!(true)
154    }
155}
156
157#[derive(Debug)]
158#[must_use]
159pub enum KeymapFileLoadResult {
160    Success {
161        key_bindings: Vec<KeyBinding>,
162    },
163    SomeFailedToLoad {
164        key_bindings: Vec<KeyBinding>,
165        error_message: MarkdownString,
166    },
167    JsonParseFailure {
168        error: anyhow::Error,
169    },
170}
171
172impl KeymapFile {
173    pub fn parse(content: &str) -> anyhow::Result<Self> {
174        if content.trim().is_empty() {
175            return Ok(Self(Vec::new()));
176        }
177        parse_json_with_comments::<Self>(content)
178    }
179
180    pub fn load_asset(
181        asset_path: &str,
182        source: Option<KeybindSource>,
183        cx: &App,
184    ) -> anyhow::Result<Vec<KeyBinding>> {
185        match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
186            KeymapFileLoadResult::Success { mut key_bindings } => match source {
187                Some(source) => Ok({
188                    for key_binding in &mut key_bindings {
189                        key_binding.set_meta(source.meta());
190                    }
191                    key_bindings
192                }),
193                None => Ok(key_bindings),
194            },
195            KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => {
196                anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}",)
197            }
198            KeymapFileLoadResult::JsonParseFailure { error } => {
199                anyhow::bail!("JSON parse error in built-in keymap \"{asset_path}\": {error}")
200            }
201        }
202    }
203
204    pub fn load_asset_allow_partial_failure(
205        asset_path: &str,
206        cx: &App,
207    ) -> anyhow::Result<Vec<KeyBinding>> {
208        match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
209            KeymapFileLoadResult::SomeFailedToLoad {
210                key_bindings,
211                error_message,
212                ..
213            } if key_bindings.is_empty() => {
214                anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}",)
215            }
216            KeymapFileLoadResult::Success { key_bindings, .. }
217            | KeymapFileLoadResult::SomeFailedToLoad { key_bindings, .. } => Ok(key_bindings),
218            KeymapFileLoadResult::JsonParseFailure { error } => {
219                anyhow::bail!("JSON parse error in built-in keymap \"{asset_path}\": {error}")
220            }
221        }
222    }
223
224    #[cfg(any(test, feature = "test-support"))]
225    pub fn load_asset_cached(asset_path: &str, cx: &App) -> anyhow::Result<Vec<KeyBinding>> {
226        static CACHED: std::sync::OnceLock<KeymapFile> = std::sync::OnceLock::new();
227        let keymap = CACHED
228            .get_or_init(|| Self::parse(asset_str::<SettingsAssets>(asset_path).as_ref()).unwrap());
229        match keymap.load_keymap(cx) {
230            KeymapFileLoadResult::SomeFailedToLoad {
231                key_bindings,
232                error_message,
233                ..
234            } if key_bindings.is_empty() => {
235                anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}")
236            }
237            KeymapFileLoadResult::Success { key_bindings, .. }
238            | KeymapFileLoadResult::SomeFailedToLoad { key_bindings, .. } => Ok(key_bindings),
239            KeymapFileLoadResult::JsonParseFailure { error } => {
240                anyhow::bail!("JSON parse error in built-in keymap \"{asset_path}\": {error}")
241            }
242        }
243    }
244
245    #[cfg(feature = "test-support")]
246    pub fn load_panic_on_failure(content: &str, cx: &App) -> Vec<KeyBinding> {
247        match Self::load(content, cx) {
248            KeymapFileLoadResult::Success { key_bindings, .. } => key_bindings,
249            KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => {
250                panic!("{error_message}");
251            }
252            KeymapFileLoadResult::JsonParseFailure { error } => {
253                panic!("JSON parse error: {error}");
254            }
255        }
256    }
257
258    pub fn load(content: &str, cx: &App) -> KeymapFileLoadResult {
259        let keymap_file = match Self::parse(content) {
260            Ok(keymap_file) => keymap_file,
261            Err(error) => {
262                return KeymapFileLoadResult::JsonParseFailure { error };
263            }
264        };
265        keymap_file.load_keymap(cx)
266    }
267
268    pub fn load_keymap(&self, cx: &App) -> KeymapFileLoadResult {
269        // Accumulate errors in order to support partial load of user keymap in the presence of
270        // errors in context and binding parsing.
271        let mut errors = Vec::new();
272        let mut key_bindings = Vec::new();
273
274        for KeymapSection {
275            context,
276            use_key_equivalents,
277            unbind,
278            bindings,
279            unrecognized_fields,
280        } in self.0.iter()
281        {
282            let context_predicate: Option<Rc<KeyBindingContextPredicate>> = if context.is_empty() {
283                None
284            } else {
285                match KeyBindingContextPredicate::parse(context) {
286                    Ok(context_predicate) => Some(context_predicate.into()),
287                    Err(err) => {
288                        // Leading space is to separate from the message indicating which section
289                        // the error occurred in.
290                        errors.push((
291                            context.clone(),
292                            format!(" Parse error in section `context` field: {}", err),
293                        ));
294                        continue;
295                    }
296                }
297            };
298
299            let mut section_errors = String::new();
300
301            if !unrecognized_fields.is_empty() {
302                write!(
303                    section_errors,
304                    "\n\n - Unrecognized fields: {}",
305                    MarkdownInlineCode(&format!("{:?}", unrecognized_fields.keys()))
306                )
307                .unwrap();
308            }
309
310            if let Some(unbind) = unbind {
311                for (keystrokes, action) in unbind {
312                    let result = Self::load_unbinding(
313                        keystrokes,
314                        action,
315                        context_predicate.clone(),
316                        *use_key_equivalents,
317                        cx,
318                    );
319                    match result {
320                        Ok(key_binding) => {
321                            key_bindings.push(key_binding);
322                        }
323                        Err(err) => {
324                            let mut lines = err.lines();
325                            let mut indented_err = lines.next().unwrap().to_string();
326                            for line in lines {
327                                indented_err.push_str("  ");
328                                indented_err.push_str(line);
329                                indented_err.push_str("\n");
330                            }
331                            write!(
332                                section_errors,
333                                "\n\n- In unbind {}, {indented_err}",
334                                MarkdownInlineCode(&format!("\"{}\"", keystrokes))
335                            )
336                            .unwrap();
337                        }
338                    }
339                }
340            }
341
342            if let Some(bindings) = bindings {
343                for (keystrokes, action) in bindings {
344                    let result = Self::load_keybinding(
345                        keystrokes,
346                        action,
347                        context_predicate.clone(),
348                        *use_key_equivalents,
349                        cx,
350                    );
351                    match result {
352                        Ok(key_binding) => {
353                            key_bindings.push(key_binding);
354                        }
355                        Err(err) => {
356                            let mut lines = err.lines();
357                            let mut indented_err = lines.next().unwrap().to_string();
358                            for line in lines {
359                                indented_err.push_str("  ");
360                                indented_err.push_str(line);
361                                indented_err.push_str("\n");
362                            }
363                            write!(
364                                section_errors,
365                                "\n\n- In binding {}, {indented_err}",
366                                MarkdownInlineCode(&format!("\"{}\"", keystrokes))
367                            )
368                            .unwrap();
369                        }
370                    }
371                }
372            }
373
374            if !section_errors.is_empty() {
375                errors.push((context.clone(), section_errors))
376            }
377        }
378
379        if errors.is_empty() {
380            KeymapFileLoadResult::Success { key_bindings }
381        } else {
382            let mut error_message = "Errors in user keymap file.".to_owned();
383
384            for (context, section_errors) in errors {
385                if context.is_empty() {
386                    let _ = write!(error_message, "\nIn section without context predicate:");
387                } else {
388                    let _ = write!(
389                        error_message,
390                        "\nIn section with {}:",
391                        MarkdownInlineCode(&format!("context = \"{}\"", context))
392                    );
393                }
394                let _ = write!(error_message, "{section_errors}");
395            }
396
397            KeymapFileLoadResult::SomeFailedToLoad {
398                key_bindings,
399                error_message: MarkdownString(error_message),
400            }
401        }
402    }
403
404    fn load_keybinding(
405        keystrokes: &str,
406        action: &KeymapAction,
407        context: Option<Rc<KeyBindingContextPredicate>>,
408        use_key_equivalents: bool,
409        cx: &App,
410    ) -> std::result::Result<KeyBinding, String> {
411        Self::load_keybinding_action_value(keystrokes, &action.0, context, use_key_equivalents, cx)
412    }
413
414    fn load_keybinding_action_value(
415        keystrokes: &str,
416        action: &Value,
417        context: Option<Rc<KeyBindingContextPredicate>>,
418        use_key_equivalents: bool,
419        cx: &App,
420    ) -> std::result::Result<KeyBinding, String> {
421        let (action, action_input_string) = Self::build_keymap_action_value(action, cx)?;
422
423        let key_binding = match KeyBinding::load(
424            keystrokes,
425            action,
426            context,
427            use_key_equivalents,
428            action_input_string.map(SharedString::from),
429            cx.keyboard_mapper().as_ref(),
430        ) {
431            Ok(key_binding) => key_binding,
432            Err(InvalidKeystrokeError { keystroke }) => {
433                return Err(format!(
434                    "invalid keystroke {}. {}",
435                    MarkdownInlineCode(&format!("\"{}\"", &keystroke)),
436                    KEYSTROKE_PARSE_EXPECTED_MESSAGE
437                ));
438            }
439        };
440
441        if let Some(validator) = KEY_BINDING_VALIDATORS.get(&key_binding.action().type_id()) {
442            match validator.validate(&key_binding) {
443                Ok(()) => Ok(key_binding),
444                Err(error) => Err(error.0),
445            }
446        } else {
447            Ok(key_binding)
448        }
449    }
450
451    fn load_unbinding(
452        keystrokes: &str,
453        action: &UnbindTargetAction,
454        context: Option<Rc<KeyBindingContextPredicate>>,
455        use_key_equivalents: bool,
456        cx: &App,
457    ) -> std::result::Result<KeyBinding, String> {
458        let key_binding = Self::load_keybinding_action_value(
459            keystrokes,
460            &action.0,
461            context,
462            use_key_equivalents,
463            cx,
464        )?;
465
466        if key_binding.action().partial_eq(&NoAction) {
467            return Err("expected action name string or [name, input] array.".to_string());
468        }
469
470        if key_binding.action().name() == Unbind::name_for_type() {
471            return Err(format!(
472                "can't use {} as an unbind target.",
473                MarkdownInlineCode(&format!("\"{}\"", Unbind::name_for_type()))
474            ));
475        }
476
477        KeyBinding::load(
478            keystrokes,
479            Box::new(Unbind(key_binding.action().name().into())),
480            key_binding.predicate(),
481            use_key_equivalents,
482            key_binding.action_input(),
483            cx.keyboard_mapper().as_ref(),
484        )
485        .map_err(|InvalidKeystrokeError { keystroke }| {
486            format!(
487                "invalid keystroke {}. {}",
488                MarkdownInlineCode(&format!("\"{}\"", &keystroke)),
489                KEYSTROKE_PARSE_EXPECTED_MESSAGE
490            )
491        })
492    }
493
494    pub fn parse_action(
495        action: &KeymapAction,
496    ) -> Result<Option<(&String, Option<&Value>)>, String> {
497        Self::parse_action_value(&action.0)
498    }
499
500    fn parse_action_value(action: &Value) -> Result<Option<(&String, Option<&Value>)>, String> {
501        let name_and_input = match action {
502            Value::Array(items) => {
503                if items.len() != 2 {
504                    return Err(format!(
505                        "expected two-element array of `[name, input]`. \
506                        Instead found {}.",
507                        MarkdownInlineCode(&action.to_string())
508                    ));
509                }
510                let serde_json::Value::String(ref name) = items[0] else {
511                    return Err(format!(
512                        "expected two-element array of `[name, input]`, \
513                        but the first element is not a string in {}.",
514                        MarkdownInlineCode(&action.to_string())
515                    ));
516                };
517                Some((name, Some(&items[1])))
518            }
519            Value::String(name) => Some((name, None)),
520            Value::Null => None,
521            _ => {
522                return Err(format!(
523                    "expected two-element array of `[name, input]`. \
524                    Instead found {}.",
525                    MarkdownInlineCode(&action.to_string())
526                ));
527            }
528        };
529        Ok(name_and_input)
530    }
531
532    fn build_keymap_action(
533        action: &KeymapAction,
534        cx: &App,
535    ) -> std::result::Result<(Box<dyn Action>, Option<String>), String> {
536        Self::build_keymap_action_value(&action.0, cx)
537    }
538
539    fn build_keymap_action_value(
540        action: &Value,
541        cx: &App,
542    ) -> std::result::Result<(Box<dyn Action>, Option<String>), String> {
543        let (build_result, action_input_string) = match Self::parse_action_value(action)? {
544            Some((name, action_input)) if name.as_str() == ActionSequence::name_for_type() => {
545                match action_input {
546                    Some(action_input) => (
547                        ActionSequence::build_sequence(action_input.clone(), cx),
548                        None,
549                    ),
550                    None => (Err(ActionSequence::expected_array_error()), None),
551                }
552            }
553            Some((name, Some(action_input))) => {
554                let action_input_string = action_input.to_string();
555                (
556                    cx.build_action(name, Some(action_input.clone())),
557                    Some(action_input_string),
558                )
559            }
560            Some((name, None)) => (cx.build_action(name, None), None),
561            None => (Ok(NoAction.boxed_clone()), None),
562        };
563
564        let action = match build_result {
565            Ok(action) => action,
566            Err(ActionBuildError::NotFound { name }) => {
567                return Err(format!(
568                    "didn't find an action named {}.",
569                    MarkdownInlineCode(&format!("\"{}\"", &name))
570                ));
571            }
572            Err(ActionBuildError::BuildError { name, error }) => match action_input_string {
573                Some(action_input_string) => {
574                    return Err(format!(
575                        "can't build {} action from input value {}: {}",
576                        MarkdownInlineCode(&format!("\"{}\"", &name)),
577                        MarkdownInlineCode(&action_input_string),
578                        MarkdownEscaped(&error.to_string())
579                    ));
580                }
581                None => {
582                    return Err(format!(
583                        "can't build {} action - it requires input data via [name, input]: {}",
584                        MarkdownInlineCode(&format!("\"{}\"", &name)),
585                        MarkdownEscaped(&error.to_string())
586                    ));
587                }
588            },
589        };
590
591        Ok((action, action_input_string))
592    }
593
594    /// Creates a JSON schema generator, suitable for generating json schemas
595    /// for actions
596    pub fn action_schema_generator() -> schemars::SchemaGenerator {
597        schemars::generate::SchemaSettings::draft2019_09()
598            .with_transform(AllowTrailingCommas)
599            .into_generator()
600    }
601
602    pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value {
603        // instead of using DefaultDenyUnknownFields, actions typically use
604        // `#[serde(deny_unknown_fields)]` so that these cases are reported as parse failures. This
605        // is because the rest of the keymap will still load in these cases, whereas other settings
606        // files would not.
607        let mut generator = Self::action_schema_generator();
608
609        let action_schemas = cx.action_schemas(&mut generator);
610        let action_documentation = cx.action_documentation();
611        let deprecations = cx.deprecated_actions_to_preferred_actions();
612        let deprecation_messages = cx.action_deprecation_messages();
613        KeymapFile::generate_json_schema(
614            generator,
615            action_schemas,
616            action_documentation,
617            deprecations,
618            deprecation_messages,
619        )
620    }
621
622    pub fn generate_json_schema_from_inventory() -> Value {
623        let mut generator = Self::action_schema_generator();
624
625        let mut action_schemas = Vec::new();
626        let mut documentation = HashMap::default();
627        let mut deprecations = HashMap::default();
628        let mut deprecation_messages = HashMap::default();
629
630        for action_data in generate_list_of_all_registered_actions() {
631            let schema = (action_data.json_schema)(&mut generator);
632            action_schemas.push((action_data.name, schema));
633
634            if let Some(doc) = action_data.documentation {
635                documentation.insert(action_data.name, doc);
636            }
637            if let Some(msg) = action_data.deprecation_message {
638                deprecation_messages.insert(action_data.name, msg);
639            }
640            for &alias in action_data.deprecated_aliases {
641                deprecations.insert(alias, action_data.name);
642
643                let alias_schema = (action_data.json_schema)(&mut generator);
644                action_schemas.push((alias, alias_schema));
645            }
646        }
647
648        KeymapFile::generate_json_schema(
649            generator,
650            action_schemas,
651            &documentation,
652            &deprecations,
653            &deprecation_messages,
654        )
655    }
656
657    pub fn get_action_schema_by_name(
658        action_name: &str,
659        generator: &mut schemars::SchemaGenerator,
660    ) -> Option<schemars::Schema> {
661        for action_data in generate_list_of_all_registered_actions() {
662            if action_data.name == action_name {
663                return (action_data.json_schema)(generator);
664            }
665            for &alias in action_data.deprecated_aliases {
666                if alias == action_name {
667                    return (action_data.json_schema)(generator);
668                }
669            }
670        }
671        None
672    }
673
674    pub fn generate_json_schema<'a>(
675        mut generator: schemars::SchemaGenerator,
676        action_schemas: Vec<(&'a str, Option<schemars::Schema>)>,
677        action_documentation: &HashMap<&'a str, &'a str>,
678        deprecations: &HashMap<&'a str, &'a str>,
679        deprecation_messages: &HashMap<&'a str, &'a str>,
680    ) -> serde_json::Value {
681        fn add_deprecation(schema: &mut schemars::Schema, message: String) {
682            schema.insert(
683                // deprecationMessage is not part of the JSON Schema spec, but
684                // json-language-server recognizes it.
685                "deprecationMessage".to_string(),
686                Value::String(message),
687            );
688        }
689
690        fn add_deprecation_preferred_name(schema: &mut schemars::Schema, new_name: &str) {
691            add_deprecation(schema, format!("Deprecated, use {new_name}"));
692        }
693
694        fn add_description(schema: &mut schemars::Schema, description: &str) {
695            schema.insert(
696                "description".to_string(),
697                Value::String(description.to_string()),
698            );
699        }
700
701        let empty_object = json_schema!({
702            "type": "object"
703        });
704
705        // This is a workaround for a json-language-server issue where it matches the first
706        // alternative that matches the value's shape and uses that for documentation.
707        //
708        // In the case of the array validations, it would even provide an error saying that the name
709        // must match the name of the first alternative.
710        let mut empty_action_name = json_schema!({
711            "type": "string",
712            "const": ""
713        });
714        let no_action_message = "No action named this.";
715        add_description(&mut empty_action_name, no_action_message);
716        add_deprecation(&mut empty_action_name, no_action_message.to_string());
717        let empty_action_name_with_input = json_schema!({
718            "type": "array",
719            "items": [
720                empty_action_name,
721                true
722            ],
723            "minItems": 2,
724            "maxItems": 2
725        });
726
727        let mut keymap_deprecations = deprecations.clone();
728        keymap_deprecations.insert(NoAction.name(), "null");
729        let action_name_schema = ActionName::build_schema(
730            action_schemas.iter().map(|(name, _)| *name),
731            action_documentation,
732            &keymap_deprecations,
733            deprecation_messages,
734        );
735
736        let mut action_with_arguments_alternatives = vec![empty_action_name_with_input.clone()];
737        let mut unbind_target_action_alternatives =
738            vec![empty_action_name, empty_action_name_with_input];
739
740        let mut empty_schema_action_names = vec![];
741        let mut empty_schema_unbind_target_action_names = vec![];
742        for (name, action_schema) in action_schemas.into_iter() {
743            let deprecation = if name == NoAction.name() {
744                Some("null")
745            } else {
746                deprecations.get(name).copied()
747            };
748
749            let include_in_unbind_target_schema =
750                name != NoAction.name() && name != Unbind::name_for_type();
751
752            // Add an alternative for plain action names.
753            let mut plain_action = json_schema!({
754                "type": "string",
755                "const": name
756            });
757            if let Some(message) = deprecation_messages.get(name) {
758                add_deprecation(&mut plain_action, message.to_string());
759            } else if let Some(new_name) = deprecation {
760                add_deprecation_preferred_name(&mut plain_action, new_name);
761            }
762            let description = action_documentation.get(name);
763            if let Some(description) = &description {
764                add_description(&mut plain_action, description);
765            }
766            if include_in_unbind_target_schema {
767                unbind_target_action_alternatives.push(plain_action);
768            }
769
770            // Add an alternative for actions with data specified as a [name, data] array.
771            //
772            // When a struct with no deserializable fields is added by deriving `Action`, an empty
773            // object schema is produced. The action should be invoked without data in this case.
774            if let Some(schema) = action_schema
775                && schema != empty_object
776            {
777                let mut matches_action_name = json_schema!({
778                    "const": name
779                });
780                if let Some(description) = &description {
781                    add_description(&mut matches_action_name, description);
782                }
783                if let Some(message) = deprecation_messages.get(name) {
784                    add_deprecation(&mut matches_action_name, message.to_string());
785                } else if let Some(new_name) = deprecation {
786                    add_deprecation_preferred_name(&mut matches_action_name, new_name);
787                }
788                let action_with_input = json_schema!({
789                    "type": "array",
790                    "items": [matches_action_name, schema],
791                    "minItems": 2,
792                    "maxItems": 2
793                });
794                action_with_arguments_alternatives.push(action_with_input.clone());
795                if include_in_unbind_target_schema {
796                    unbind_target_action_alternatives.push(action_with_input);
797                }
798            } else {
799                empty_schema_action_names.push(name);
800                if include_in_unbind_target_schema {
801                    empty_schema_unbind_target_action_names.push(name);
802                }
803            }
804        }
805
806        if !empty_schema_action_names.is_empty() {
807            let action_names = json_schema!({ "enum": empty_schema_action_names });
808            let no_properties_allowed = json_schema!({
809                "type": "object",
810                "additionalProperties": false
811            });
812            let mut actions_with_empty_input = json_schema!({
813                "type": "array",
814                "items": [action_names, no_properties_allowed],
815                "minItems": 2,
816                "maxItems": 2
817            });
818            add_deprecation(
819                &mut actions_with_empty_input,
820                "This action does not take input - just the action name string should be used."
821                    .to_string(),
822            );
823            action_with_arguments_alternatives.push(actions_with_empty_input);
824        }
825
826        if !empty_schema_unbind_target_action_names.is_empty() {
827            let action_names = json_schema!({ "enum": empty_schema_unbind_target_action_names });
828            let no_properties_allowed = json_schema!({
829                "type": "object",
830                "additionalProperties": false
831            });
832            let mut actions_with_empty_input = json_schema!({
833                "type": "array",
834                "items": [action_names, no_properties_allowed],
835                "minItems": 2,
836                "maxItems": 2
837            });
838            add_deprecation(
839                &mut actions_with_empty_input,
840                "This action does not take input - just the action name string should be used."
841                    .to_string(),
842            );
843            unbind_target_action_alternatives.push(actions_with_empty_input);
844        }
845
846        generator.definitions_mut().insert(
847            ActionName::schema_name().to_string(),
848            action_name_schema.to_value(),
849        );
850        generator.definitions_mut().insert(
851            ActionWithArguments::schema_name().to_string(),
852            json!({ "anyOf": action_with_arguments_alternatives }),
853        );
854
855        generator.definitions_mut().insert(
856            KeymapAction::schema_name().to_string(),
857            json!({ "anyOf": [
858                { "$ref": format!("#/$defs/{}", ActionName::schema_name().to_string()) },
859                { "$ref": format!("#/$defs/{}", ActionWithArguments::schema_name().to_string()) },
860                { "type": "null" }
861            ] }),
862        );
863        generator.definitions_mut().insert(
864            UnbindTargetAction::schema_name().to_string(),
865            json!({
866                "anyOf": unbind_target_action_alternatives
867            }),
868        );
869
870        generator.root_schema_for::<KeymapFile>().to_value()
871    }
872
873    pub fn sections(&self) -> impl DoubleEndedIterator<Item = &KeymapSection> {
874        self.0.iter()
875    }
876
877    pub async fn load_keymap_file(fs: &Arc<dyn Fs>) -> Result<String> {
878        match fs.load(paths::keymap_file()).await {
879            result @ Ok(_) => result,
880            Err(err) => {
881                if let Some(e) = err.downcast_ref::<std::io::Error>()
882                    && e.kind() == std::io::ErrorKind::NotFound
883                {
884                    return Ok(crate::initial_keymap_content().to_string());
885                }
886                Err(err)
887            }
888        }
889    }
890
891    pub fn update_keybinding<'a>(
892        mut operation: KeybindUpdateOperation<'a>,
893        mut keymap_contents: String,
894        tab_size: usize,
895        keyboard_mapper: &dyn gpui::PlatformKeyboardMapper,
896        deprecated_aliases: &HashMap<&'static str, &'static str>,
897    ) -> Result<String> {
898        // When replacing or removing a non-user binding, we may need to write an unbind entry
899        // to suppress the original default binding.
900        let mut suppression_unbind: Option<KeybindUpdateTarget<'_>> = None;
901
902        match &operation {
903            // if trying to replace a keybinding that is not user-defined, treat it as an add operation
904            KeybindUpdateOperation::Replace {
905                target_keybind_source: target_source,
906                source,
907                target,
908            } if *target_source != KeybindSource::User => {
909                if target.keystrokes_unparsed() != source.keystrokes_unparsed() {
910                    suppression_unbind = Some(target.clone());
911                }
912                operation = KeybindUpdateOperation::Add {
913                    source: source.clone(),
914                    from: Some(target.clone()),
915                };
916            }
917            // if trying to remove a keybinding that is not user-defined, treat it as creating an
918            // unbind entry for the removed action
919            KeybindUpdateOperation::Remove {
920                target,
921                target_keybind_source,
922            } if *target_keybind_source != KeybindSource::User => {
923                suppression_unbind = Some(target.clone());
924            }
925            _ => {}
926        }
927
928        // Sanity check that keymap contents are valid, even though we only use it for Replace.
929        // We don't want to modify the file if it's invalid.
930        let keymap = Self::parse(&keymap_contents).context("Failed to parse keymap")?;
931
932        if let KeybindUpdateOperation::Remove {
933            target,
934            target_keybind_source,
935        } = &operation
936        {
937            if *target_keybind_source == KeybindSource::User {
938                let target_action_value = target
939                    .action_value()
940                    .context("Failed to generate target action JSON value")?;
941                let Some(binding_location) = find_binding(
942                    &keymap,
943                    target,
944                    &target_action_value,
945                    keyboard_mapper,
946                    deprecated_aliases,
947                ) else {
948                    anyhow::bail!("Failed to find keybinding to remove");
949                };
950                let is_only_binding = binding_location.is_only_entry_in_section(&keymap);
951                let key_path: &[&str] = if is_only_binding {
952                    &[]
953                } else {
954                    &[
955                        binding_location.kind.key_path(),
956                        binding_location.keystrokes_str,
957                    ]
958                };
959                let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
960                    &keymap_contents,
961                    key_path,
962                    None,
963                    None,
964                    binding_location.index,
965                    tab_size,
966                );
967                keymap_contents.replace_range(replace_range, &replace_value);
968
969                return Ok(keymap_contents);
970            }
971        }
972
973        if let KeybindUpdateOperation::Replace { source, target, .. } = operation {
974            let target_action_value = target
975                .action_value()
976                .context("Failed to generate target action JSON value")?;
977            let source_action_value = source
978                .action_value()
979                .context("Failed to generate source action JSON value")?;
980
981            if let Some(binding_location) = find_binding(
982                &keymap,
983                &target,
984                &target_action_value,
985                keyboard_mapper,
986                deprecated_aliases,
987            ) {
988                if target.context == source.context {
989                    // if we are only changing the keybinding (common case)
990                    // not the context, etc. Then just update the binding in place
991
992                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
993                        &keymap_contents,
994                        &[
995                            binding_location.kind.key_path(),
996                            binding_location.keystrokes_str,
997                        ],
998                        Some(&source_action_value),
999                        Some(&source.keystrokes_unparsed()),
1000                        binding_location.index,
1001                        tab_size,
1002                    );
1003                    keymap_contents.replace_range(replace_range, &replace_value);
1004
1005                    return Ok(keymap_contents);
1006                } else if binding_location.is_only_entry_in_section(&keymap) {
1007                    // if we are replacing the only binding in the section,
1008                    // just update the section in place, updating the context
1009                    // and the binding
1010
1011                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
1012                        &keymap_contents,
1013                        &[
1014                            binding_location.kind.key_path(),
1015                            binding_location.keystrokes_str,
1016                        ],
1017                        Some(&source_action_value),
1018                        Some(&source.keystrokes_unparsed()),
1019                        binding_location.index,
1020                        tab_size,
1021                    );
1022                    keymap_contents.replace_range(replace_range, &replace_value);
1023
1024                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
1025                        &keymap_contents,
1026                        &["context"],
1027                        source.context.map(Into::into).as_ref(),
1028                        None,
1029                        binding_location.index,
1030                        tab_size,
1031                    );
1032                    keymap_contents.replace_range(replace_range, &replace_value);
1033                    return Ok(keymap_contents);
1034                } else {
1035                    // if we are replacing one of multiple bindings in a section
1036                    // with a context change, remove the existing binding from the
1037                    // section, then treat this operation as an add operation of the
1038                    // new binding with the updated context.
1039
1040                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
1041                        &keymap_contents,
1042                        &[
1043                            binding_location.kind.key_path(),
1044                            binding_location.keystrokes_str,
1045                        ],
1046                        None,
1047                        None,
1048                        binding_location.index,
1049                        tab_size,
1050                    );
1051                    keymap_contents.replace_range(replace_range, &replace_value);
1052                    operation = KeybindUpdateOperation::Add {
1053                        source,
1054                        from: Some(target),
1055                    };
1056                }
1057            } else {
1058                log::warn!(
1059                    "Failed to find keybinding to update `{:?} -> {}` creating new binding for `{:?} -> {}` instead",
1060                    target.keystrokes,
1061                    target_action_value,
1062                    source.keystrokes,
1063                    source_action_value,
1064                );
1065                operation = KeybindUpdateOperation::Add {
1066                    source,
1067                    from: Some(target),
1068                };
1069            }
1070        }
1071
1072        if let KeybindUpdateOperation::Add {
1073            source: keybinding,
1074            from,
1075        } = operation
1076        {
1077            let mut value = serde_json::Map::with_capacity(4);
1078            if let Some(context) = keybinding.context {
1079                value.insert("context".to_string(), context.into());
1080            }
1081            let use_key_equivalents = from.and_then(|from| {
1082                let action_value = from.action_value().context("Failed to serialize action value. `use_key_equivalents` on new keybinding may be incorrect.").log_err()?;
1083                let binding_location =
1084                    find_binding(&keymap, &from, &action_value, keyboard_mapper, deprecated_aliases)?;
1085                Some(keymap.0[binding_location.index].use_key_equivalents)
1086            }).unwrap_or(false);
1087            if use_key_equivalents {
1088                value.insert("use_key_equivalents".to_string(), true.into());
1089            }
1090
1091            value.insert("bindings".to_string(), {
1092                let mut bindings = serde_json::Map::new();
1093                let action = keybinding.action_value()?;
1094                bindings.insert(keybinding.keystrokes_unparsed(), action);
1095                bindings.into()
1096            });
1097
1098            let (replace_range, replace_value) = append_top_level_array_value_in_json_text(
1099                &keymap_contents,
1100                &value.into(),
1101                tab_size,
1102            );
1103            keymap_contents.replace_range(replace_range, &replace_value);
1104        }
1105
1106        if let Some(suppression_unbind) = suppression_unbind {
1107            let mut value = serde_json::Map::with_capacity(2);
1108            if let Some(context) = suppression_unbind.context {
1109                value.insert("context".to_string(), context.into());
1110            }
1111            value.insert("unbind".to_string(), {
1112                let mut unbind = serde_json::Map::new();
1113                unbind.insert(
1114                    suppression_unbind.keystrokes_unparsed(),
1115                    suppression_unbind.action_value()?,
1116                );
1117                unbind.into()
1118            });
1119            let (replace_range, replace_value) = append_top_level_array_value_in_json_text(
1120                &keymap_contents,
1121                &value.into(),
1122                tab_size,
1123            );
1124            keymap_contents.replace_range(replace_range, &replace_value);
1125        }
1126
1127        return Ok(keymap_contents);
1128
1129        fn find_binding<'a, 'b>(
1130            keymap: &'b KeymapFile,
1131            target: &KeybindUpdateTarget<'a>,
1132            target_action_value: &Value,
1133            keyboard_mapper: &dyn gpui::PlatformKeyboardMapper,
1134            deprecated_aliases: &HashMap<&'static str, &'static str>,
1135        ) -> Option<BindingLocation<'b>> {
1136            let target_context_parsed =
1137                KeyBindingContextPredicate::parse(target.context.unwrap_or("")).ok();
1138            for (index, section) in keymap.sections().enumerate() {
1139                let section_context_parsed =
1140                    KeyBindingContextPredicate::parse(&section.context).ok();
1141                if section_context_parsed != target_context_parsed {
1142                    continue;
1143                }
1144
1145                if let Some(binding_location) = find_binding_in_entries(
1146                    section.bindings.as_ref(),
1147                    BindingKind::Binding,
1148                    index,
1149                    target,
1150                    target_action_value,
1151                    keyboard_mapper,
1152                    deprecated_aliases,
1153                    |action| &action.0,
1154                ) {
1155                    return Some(binding_location);
1156                }
1157
1158                if let Some(binding_location) = find_binding_in_entries(
1159                    section.unbind.as_ref(),
1160                    BindingKind::Unbind,
1161                    index,
1162                    target,
1163                    target_action_value,
1164                    keyboard_mapper,
1165                    deprecated_aliases,
1166                    |action| &action.0,
1167                ) {
1168                    return Some(binding_location);
1169                }
1170            }
1171            None
1172        }
1173
1174        fn find_binding_in_entries<'a, 'b, T>(
1175            entries: Option<&'b IndexMap<String, T>>,
1176            kind: BindingKind,
1177            index: usize,
1178            target: &KeybindUpdateTarget<'a>,
1179            target_action_value: &Value,
1180            keyboard_mapper: &dyn gpui::PlatformKeyboardMapper,
1181            deprecated_aliases: &HashMap<&'static str, &'static str>,
1182            action_value: impl Fn(&T) -> &Value,
1183        ) -> Option<BindingLocation<'b>> {
1184            let entries = entries?;
1185            for (keystrokes_str, action) in entries {
1186                let Ok(keystrokes) = keystrokes_str
1187                    .split_whitespace()
1188                    .map(|source| {
1189                        let keystroke = Keystroke::parse(source)?;
1190                        Ok(KeybindingKeystroke::new_with_mapper(
1191                            keystroke,
1192                            false,
1193                            keyboard_mapper,
1194                        ))
1195                    })
1196                    .collect::<Result<Vec<_>, InvalidKeystrokeError>>()
1197                else {
1198                    continue;
1199                };
1200                if keystrokes.len() != target.keystrokes.len()
1201                    || !keystrokes
1202                        .iter()
1203                        .zip(target.keystrokes)
1204                        .all(|(a, b)| a.inner().should_match(b))
1205                {
1206                    continue;
1207                }
1208                if !action_value_matches_target(
1209                    action_value(action),
1210                    target_action_value,
1211                    deprecated_aliases,
1212                ) {
1213                    continue;
1214                }
1215                return Some(BindingLocation {
1216                    index,
1217                    kind,
1218                    keystrokes_str,
1219                });
1220            }
1221            None
1222        }
1223
1224        /// Compares a keymap file entry's action value against the target action
1225        /// value. The target is built from a loaded binding's canonical action
1226        /// name, while the file entry may still use a deprecated alias.
1227        fn action_value_matches_target(
1228            action_value: &Value,
1229            target_action_value: &Value,
1230            deprecated_aliases: &HashMap<&'static str, &'static str>,
1231        ) -> bool {
1232            if action_value == target_action_value {
1233                return true;
1234            }
1235            let (name, arguments) = match action_value {
1236                Value::String(name) => (name.as_str(), None),
1237                Value::Array(items) => match items.as_slice() {
1238                    [Value::String(name), arguments] => (name.as_str(), Some(arguments)),
1239                    _ => return false,
1240                },
1241                _ => return false,
1242            };
1243            let Some(&preferred_name) = deprecated_aliases.get(name) else {
1244                return false;
1245            };
1246            match target_action_value {
1247                Value::String(target_name) => target_name == preferred_name && arguments.is_none(),
1248                Value::Array(items) => match items.as_slice() {
1249                    [Value::String(target_name), target_arguments] => {
1250                        target_name == preferred_name && arguments == Some(target_arguments)
1251                    }
1252                    _ => false,
1253                },
1254                _ => false,
1255            }
1256        }
1257
1258        #[derive(Copy, Clone)]
1259        enum BindingKind {
1260            Binding,
1261            Unbind,
1262        }
1263
1264        impl BindingKind {
1265            fn key_path(self) -> &'static str {
1266                match self {
1267                    Self::Binding => "bindings",
1268                    Self::Unbind => "unbind",
1269                }
1270            }
1271        }
1272
1273        struct BindingLocation<'a> {
1274            index: usize,
1275            kind: BindingKind,
1276            keystrokes_str: &'a str,
1277        }
1278
1279        impl BindingLocation<'_> {
1280            fn is_only_entry_in_section(&self, keymap: &KeymapFile) -> bool {
1281                let section = &keymap.0[self.index];
1282                let binding_count = section.bindings.as_ref().map_or(0, IndexMap::len);
1283                let unbind_count = section.unbind.as_ref().map_or(0, IndexMap::len);
1284                binding_count + unbind_count == 1
1285            }
1286        }
1287    }
1288}
1289
1290#[derive(Clone, Debug)]
1291pub enum KeybindUpdateOperation<'a> {
1292    Replace {
1293        /// Describes the keybind to create
1294        source: KeybindUpdateTarget<'a>,
1295        /// Describes the keybind to remove
1296        target: KeybindUpdateTarget<'a>,
1297        target_keybind_source: KeybindSource,
1298    },
1299    Add {
1300        source: KeybindUpdateTarget<'a>,
1301        from: Option<KeybindUpdateTarget<'a>>,
1302    },
1303    Remove {
1304        target: KeybindUpdateTarget<'a>,
1305        target_keybind_source: KeybindSource,
1306    },
1307}
1308
1309impl KeybindUpdateOperation<'_> {
1310    pub fn generate_telemetry(
1311        &self,
1312    ) -> (
1313        // The keybind that is created
1314        String,
1315        // The keybinding that was removed
1316        String,
1317        // The source of the keybinding
1318        String,
1319    ) {
1320        let (new_binding, removed_binding, source) = match &self {
1321            KeybindUpdateOperation::Replace {
1322                source,
1323                target,
1324                target_keybind_source,
1325            } => (Some(source), Some(target), Some(*target_keybind_source)),
1326            KeybindUpdateOperation::Add { source, .. } => (Some(source), None, None),
1327            KeybindUpdateOperation::Remove {
1328                target,
1329                target_keybind_source,
1330            } => (None, Some(target), Some(*target_keybind_source)),
1331        };
1332
1333        let new_binding = new_binding
1334            .map(KeybindUpdateTarget::telemetry_string)
1335            .unwrap_or("null".to_owned());
1336        let removed_binding = removed_binding
1337            .map(KeybindUpdateTarget::telemetry_string)
1338            .unwrap_or("null".to_owned());
1339
1340        let source = source
1341            .as_ref()
1342            .map(KeybindSource::name)
1343            .map(ToOwned::to_owned)
1344            .unwrap_or("null".to_owned());
1345
1346        (new_binding, removed_binding, source)
1347    }
1348}
1349
1350impl<'a> KeybindUpdateOperation<'a> {
1351    pub fn add(source: KeybindUpdateTarget<'a>) -> Self {
1352        Self::Add { source, from: None }
1353    }
1354}
1355
1356#[derive(Debug, Clone)]
1357pub struct KeybindUpdateTarget<'a> {
1358    pub context: Option<&'a str>,
1359    pub keystrokes: &'a [KeybindingKeystroke],
1360    pub action_name: &'a str,
1361    pub action_arguments: Option<&'a str>,
1362}
1363
1364impl<'a> KeybindUpdateTarget<'a> {
1365    fn action_value(&self) -> Result<Value> {
1366        if self.action_name == gpui::NoAction.name() {
1367            return Ok(Value::Null);
1368        }
1369        let action_name: Value = self.action_name.into();
1370        let value = match self.action_arguments {
1371            Some(args) if !args.is_empty() => {
1372                let args = serde_json::from_str::<Value>(args)
1373                    .context("Failed to parse action arguments as JSON")?;
1374                serde_json::json!([action_name, args])
1375            }
1376            _ => action_name,
1377        };
1378        Ok(value)
1379    }
1380
1381    fn keystrokes_unparsed(&self) -> String {
1382        let mut keystrokes = String::with_capacity(self.keystrokes.len() * 8);
1383        for keystroke in self.keystrokes {
1384            // The reason use `keystroke.unparse()` instead of `keystroke.inner.unparse()`
1385            // here is that, we want the user to use `ctrl-shift-4` instead of `ctrl-$`
1386            // by default on Windows.
1387            keystrokes.push_str(&keystroke.unparse());
1388            keystrokes.push(' ');
1389        }
1390        keystrokes.pop();
1391        keystrokes
1392    }
1393
1394    fn telemetry_string(&self) -> String {
1395        format!(
1396            "action_name: {}, context: {}, action_arguments: {}, keystrokes: {}",
1397            self.action_name,
1398            self.context.unwrap_or("global"),
1399            self.action_arguments.unwrap_or("none"),
1400            self.keystrokes_unparsed()
1401        )
1402    }
1403}
1404
1405#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug)]
1406pub enum KeybindSource {
1407    User,
1408    Vim,
1409    Base,
1410    #[default]
1411    Default,
1412    Unknown,
1413}
1414
1415impl KeybindSource {
1416    const BASE: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Base as u32);
1417    const DEFAULT: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Default as u32);
1418    const VIM: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Vim as u32);
1419    const USER: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::User as u32);
1420
1421    pub fn name(&self) -> &'static str {
1422        match self {
1423            KeybindSource::User => "User",
1424            KeybindSource::Default => "Default",
1425            KeybindSource::Base => "Base",
1426            KeybindSource::Vim => "Vim",
1427            KeybindSource::Unknown => "Unknown",
1428        }
1429    }
1430
1431    pub fn meta(&self) -> KeyBindingMetaIndex {
1432        match self {
1433            KeybindSource::User => Self::USER,
1434            KeybindSource::Default => Self::DEFAULT,
1435            KeybindSource::Base => Self::BASE,
1436            KeybindSource::Vim => Self::VIM,
1437            KeybindSource::Unknown => KeyBindingMetaIndex(*self as u32),
1438        }
1439    }
1440
1441    pub fn from_meta(index: KeyBindingMetaIndex) -> Self {
1442        match index {
1443            Self::USER => KeybindSource::User,
1444            Self::BASE => KeybindSource::Base,
1445            Self::DEFAULT => KeybindSource::Default,
1446            Self::VIM => KeybindSource::Vim,
1447            _ => KeybindSource::Unknown,
1448        }
1449    }
1450}
1451
1452impl From<KeyBindingMetaIndex> for KeybindSource {
1453    fn from(index: KeyBindingMetaIndex) -> Self {
1454        Self::from_meta(index)
1455    }
1456}
1457
1458impl From<KeybindSource> for KeyBindingMetaIndex {
1459    fn from(source: KeybindSource) -> Self {
1460        source.meta()
1461    }
1462}
1463
1464/// Runs a sequence of actions. Does not wait for asynchronous actions to complete before running
1465/// the next action. Currently only works in workspace windows.
1466///
1467/// This action is special-cased in keymap parsing to allow it to access `App` while parsing, so
1468/// that it can parse its input actions.
1469pub struct ActionSequence(pub Vec<Box<dyn Action>>);
1470
1471register_action!(ActionSequence);
1472
1473impl ActionSequence {
1474    fn build_sequence(
1475        value: Value,
1476        cx: &App,
1477    ) -> std::result::Result<Box<dyn Action>, ActionBuildError> {
1478        match value {
1479            Value::Array(values) => {
1480                let actions = values
1481                    .into_iter()
1482                    .enumerate()
1483                    .map(|(index, action)| {
1484                        match KeymapFile::build_keymap_action(&KeymapAction(action), cx) {
1485                            Ok((action, _)) => Ok(action),
1486                            Err(err) => {
1487                                return Err(ActionBuildError::BuildError {
1488                                    name: Self::name_for_type().to_string(),
1489                                    error: anyhow::anyhow!(
1490                                        "error at sequence index {index}: {err}"
1491                                    ),
1492                                });
1493                            }
1494                        }
1495                    })
1496                    .collect::<Result<Vec<_>, _>>()?;
1497                Ok(Box::new(Self(actions)))
1498            }
1499            _ => Err(Self::expected_array_error()),
1500        }
1501    }
1502
1503    fn expected_array_error() -> ActionBuildError {
1504        ActionBuildError::BuildError {
1505            name: Self::name_for_type().to_string(),
1506            error: anyhow::anyhow!("expected array of actions"),
1507        }
1508    }
1509}
1510
1511impl Action for ActionSequence {
1512    fn name(&self) -> &'static str {
1513        Self::name_for_type()
1514    }
1515
1516    fn name_for_type() -> &'static str
1517    where
1518        Self: Sized,
1519    {
1520        "action::Sequence"
1521    }
1522
1523    fn partial_eq(&self, action: &dyn Action) -> bool {
1524        action
1525            .as_any()
1526            .downcast_ref::<Self>()
1527            .map_or(false, |other| {
1528                self.0.len() == other.0.len()
1529                    && self
1530                        .0
1531                        .iter()
1532                        .zip(other.0.iter())
1533                        .all(|(a, b)| a.partial_eq(b.as_ref()))
1534            })
1535    }
1536
1537    fn boxed_clone(&self) -> Box<dyn Action> {
1538        Box::new(ActionSequence(
1539            self.0
1540                .iter()
1541                .map(|action| action.boxed_clone())
1542                .collect::<Vec<_>>(),
1543        ))
1544    }
1545
1546    fn build(_value: Value) -> Result<Box<dyn Action>> {
1547        Err(anyhow::anyhow!(
1548            "{} cannot be built directly",
1549            Self::name_for_type()
1550        ))
1551    }
1552
1553    fn action_json_schema(generator: &mut schemars::SchemaGenerator) -> Option<schemars::Schema> {
1554        let keymap_action_schema = generator.subschema_for::<KeymapAction>();
1555        Some(json_schema!({
1556            "type": "array",
1557            "items": keymap_action_schema
1558        }))
1559    }
1560
1561    fn deprecated_aliases() -> &'static [&'static str] {
1562        &[]
1563    }
1564
1565    fn deprecation_message() -> Option<&'static str> {
1566        None
1567    }
1568
1569    fn documentation() -> Option<&'static str> {
1570        Some(
1571            "Runs a sequence of actions.\n\n\
1572            NOTE: This does **not** wait for asynchronous actions to complete before running the next action.",
1573        )
1574    }
1575}
1576
1577#[cfg(test)]
1578mod tests {
1579    use collections::HashMap;
1580    use gpui::{Action, App, DummyKeyboardMapper, KeybindingKeystroke, Keystroke, Unbind};
1581    use serde_json::Value;
1582    use unindent::Unindent;
1583
1584    use crate::{
1585        KeybindSource, KeymapFile,
1586        keymap_file::{KeybindUpdateOperation, KeybindUpdateTarget},
1587    };
1588
1589    gpui::actions!(test_keymap_file, [StringAction, InputAction]);
1590
1591    #[test]
1592    fn can_deserialize_keymap_with_trailing_comma() {
1593        let json = indoc::indoc! {"[
1594              // Standard macOS bindings
1595              {
1596                \"bindings\": {
1597                  \"up\": \"menu::SelectPrevious\",
1598                },
1599              },
1600            ]
1601                  "
1602        };
1603        KeymapFile::parse(json).unwrap();
1604    }
1605
1606    #[gpui::test]
1607    fn keymap_section_unbinds_are_loaded_before_bindings(cx: &mut App) {
1608        let key_bindings = match KeymapFile::load(
1609            indoc::indoc! {r#"
1610                [
1611                    {
1612                        "unbind": {
1613                            "ctrl-a": "test_keymap_file::StringAction",
1614                            "ctrl-b": ["test_keymap_file::InputAction", {}]
1615                        },
1616                        "bindings": {
1617                            "ctrl-c": "test_keymap_file::StringAction"
1618                        }
1619                    }
1620                ]
1621            "#},
1622            cx,
1623        ) {
1624            crate::keymap_file::KeymapFileLoadResult::Success { key_bindings } => key_bindings,
1625            crate::keymap_file::KeymapFileLoadResult::SomeFailedToLoad {
1626                error_message, ..
1627            } => {
1628                panic!("{error_message}");
1629            }
1630            crate::keymap_file::KeymapFileLoadResult::JsonParseFailure { error } => {
1631                panic!("JSON parse error: {error}");
1632            }
1633        };
1634
1635        assert_eq!(key_bindings.len(), 3);
1636        assert!(
1637            key_bindings[0]
1638                .action()
1639                .partial_eq(&Unbind("test_keymap_file::StringAction".into()))
1640        );
1641        assert_eq!(key_bindings[0].action_input(), None);
1642        assert!(
1643            key_bindings[1]
1644                .action()
1645                .partial_eq(&Unbind("test_keymap_file::InputAction".into()))
1646        );
1647        assert_eq!(
1648            key_bindings[1]
1649                .action_input()
1650                .as_ref()
1651                .map(ToString::to_string),
1652            Some("{}".to_string())
1653        );
1654        assert_eq!(
1655            key_bindings[2].action().name(),
1656            "test_keymap_file::StringAction"
1657        );
1658    }
1659
1660    #[gpui::test]
1661    fn keymap_unbind_loads_valid_target_action_with_input(cx: &mut App) {
1662        let key_bindings = match KeymapFile::load(
1663            indoc::indoc! {r#"
1664                [
1665                    {
1666                        "unbind": {
1667                            "ctrl-a": ["test_keymap_file::InputAction", {}]
1668                        }
1669                    }
1670                ]
1671            "#},
1672            cx,
1673        ) {
1674            crate::keymap_file::KeymapFileLoadResult::Success { key_bindings } => key_bindings,
1675            other => panic!("expected Success, got {other:?}"),
1676        };
1677
1678        assert_eq!(key_bindings.len(), 1);
1679        assert!(
1680            key_bindings[0]
1681                .action()
1682                .partial_eq(&Unbind("test_keymap_file::InputAction".into()))
1683        );
1684        assert_eq!(
1685            key_bindings[0]
1686                .action_input()
1687                .as_ref()
1688                .map(ToString::to_string),
1689            Some("{}".to_string())
1690        );
1691    }
1692
1693    #[gpui::test]
1694    fn keymap_unbind_rejects_null(cx: &mut App) {
1695        match KeymapFile::load(
1696            indoc::indoc! {r#"
1697                [
1698                    {
1699                        "unbind": {
1700                            "ctrl-a": null
1701                        }
1702                    }
1703                ]
1704            "#},
1705            cx,
1706        ) {
1707            crate::keymap_file::KeymapFileLoadResult::SomeFailedToLoad {
1708                key_bindings,
1709                error_message,
1710            } => {
1711                assert!(key_bindings.is_empty());
1712                assert!(
1713                    error_message
1714                        .0
1715                        .contains("expected action name string or [name, input] array.")
1716                );
1717            }
1718            other => panic!("expected SomeFailedToLoad, got {other:?}"),
1719        }
1720    }
1721
1722    #[gpui::test]
1723    fn keymap_unbind_rejects_unbind_action(cx: &mut App) {
1724        match KeymapFile::load(
1725            indoc::indoc! {r#"
1726                [
1727                    {
1728                        "unbind": {
1729                            "ctrl-a": ["omega::Unbind", "test_keymap_file::StringAction"]
1730                        }
1731                    }
1732                ]
1733            "#},
1734            cx,
1735        ) {
1736            crate::keymap_file::KeymapFileLoadResult::SomeFailedToLoad {
1737                key_bindings,
1738                error_message,
1739            } => {
1740                assert!(key_bindings.is_empty());
1741                assert!(
1742                    error_message
1743                        .0
1744                        .contains("can't use `\"omega::Unbind\"` as an unbind target.")
1745                );
1746            }
1747            other => panic!("expected SomeFailedToLoad, got {other:?}"),
1748        }
1749    }
1750
1751    #[test]
1752    fn keymap_schema_for_unbind_excludes_null_and_unbind_action() {
1753        fn schema_allows(schema: &Value, expected: &Value) -> bool {
1754            match schema {
1755                Value::Object(object) => {
1756                    if object.get("const") == Some(expected) {
1757                        return true;
1758                    }
1759                    if object.get("type") == Some(&Value::String("null".to_string()))
1760                        && expected == &Value::Null
1761                    {
1762                        return true;
1763                    }
1764                    object.values().any(|value| schema_allows(value, expected))
1765                }
1766                Value::Array(items) => items.iter().any(|value| schema_allows(value, expected)),
1767                _ => false,
1768            }
1769        }
1770
1771        let schema = KeymapFile::generate_json_schema_from_inventory();
1772        let unbind_schema = schema
1773            .pointer("/$defs/UnbindTargetAction")
1774            .expect("missing UnbindTargetAction schema");
1775
1776        assert!(!schema_allows(unbind_schema, &Value::Null));
1777        assert!(!schema_allows(
1778            unbind_schema,
1779            &Value::String(Unbind::name_for_type().to_string())
1780        ));
1781        assert!(schema_allows(
1782            unbind_schema,
1783            &Value::String("test_keymap_file::StringAction".to_string())
1784        ));
1785        assert!(schema_allows(
1786            unbind_schema,
1787            &Value::String("test_keymap_file::InputAction".to_string())
1788        ));
1789    }
1790
1791    #[track_caller]
1792    fn check_keymap_update(
1793        input: impl ToString,
1794        operation: KeybindUpdateOperation,
1795        expected: impl ToString,
1796    ) {
1797        check_keymap_update_with_deprecated_aliases(input, operation, expected, &[]);
1798    }
1799
1800    #[track_caller]
1801    fn check_keymap_update_with_deprecated_aliases(
1802        input: impl ToString,
1803        operation: KeybindUpdateOperation,
1804        expected: impl ToString,
1805        deprecated_aliases: &[(&'static str, &'static str)],
1806    ) {
1807        let deprecated_aliases = HashMap::from_iter(deprecated_aliases.iter().copied());
1808        let result = KeymapFile::update_keybinding(
1809            operation,
1810            input.to_string(),
1811            4,
1812            &gpui::DummyKeyboardMapper,
1813            &deprecated_aliases,
1814        )
1815        .expect("Update succeeded");
1816        pretty_assertions::assert_eq!(expected.to_string(), result);
1817    }
1818
1819    #[track_caller]
1820    fn parse_keystrokes(keystrokes: &str) -> Vec<KeybindingKeystroke> {
1821        keystrokes
1822            .split(' ')
1823            .map(|s| {
1824                KeybindingKeystroke::new_with_mapper(
1825                    Keystroke::parse(s).expect("Keystrokes valid"),
1826                    false,
1827                    &DummyKeyboardMapper,
1828                )
1829            })
1830            .collect()
1831    }
1832
1833    #[test]
1834    fn keymap_update() {
1835        zlog::init_test();
1836
1837        check_keymap_update(
1838            "[]",
1839            KeybindUpdateOperation::add(KeybindUpdateTarget {
1840                keystrokes: &parse_keystrokes("ctrl-a"),
1841                action_name: "zed::SomeAction",
1842                context: None,
1843                action_arguments: None,
1844            }),
1845            r#"[
1846                {
1847                    "bindings": {
1848                        "ctrl-a": "zed::SomeAction"
1849                    }
1850                }
1851            ]"#
1852            .unindent(),
1853        );
1854
1855        check_keymap_update(
1856            "[]",
1857            KeybindUpdateOperation::add(KeybindUpdateTarget {
1858                keystrokes: &parse_keystrokes("\\ a"),
1859                action_name: "zed::SomeAction",
1860                context: None,
1861                action_arguments: None,
1862            }),
1863            r#"[
1864                {
1865                    "bindings": {
1866                        "\\ a": "zed::SomeAction"
1867                    }
1868                }
1869            ]"#
1870            .unindent(),
1871        );
1872
1873        check_keymap_update(
1874            "[]",
1875            KeybindUpdateOperation::add(KeybindUpdateTarget {
1876                keystrokes: &parse_keystrokes("ctrl-a"),
1877                action_name: "zed::SomeAction",
1878                context: None,
1879                action_arguments: Some(""),
1880            }),
1881            r#"[
1882                {
1883                    "bindings": {
1884                        "ctrl-a": "zed::SomeAction"
1885                    }
1886                }
1887            ]"#
1888            .unindent(),
1889        );
1890
1891        check_keymap_update(
1892            r#"[
1893                {
1894                    "bindings": {
1895                        "ctrl-a": "zed::SomeAction"
1896                    }
1897                }
1898            ]"#
1899            .unindent(),
1900            KeybindUpdateOperation::add(KeybindUpdateTarget {
1901                keystrokes: &parse_keystrokes("ctrl-b"),
1902                action_name: "zed::SomeOtherAction",
1903                context: None,
1904                action_arguments: None,
1905            }),
1906            r#"[
1907                {
1908                    "bindings": {
1909                        "ctrl-a": "zed::SomeAction"
1910                    }
1911                },
1912                {
1913                    "bindings": {
1914                        "ctrl-b": "zed::SomeOtherAction"
1915                    }
1916                }
1917            ]"#
1918            .unindent(),
1919        );
1920
1921        check_keymap_update(
1922            r#"[
1923                {
1924                    "bindings": {
1925                        "ctrl-a": "zed::SomeAction"
1926                    }
1927                }
1928            ]"#
1929            .unindent(),
1930            KeybindUpdateOperation::add(KeybindUpdateTarget {
1931                keystrokes: &parse_keystrokes("ctrl-b"),
1932                action_name: "zed::SomeOtherAction",
1933                context: None,
1934                action_arguments: Some(r#"{"foo": "bar"}"#),
1935            }),
1936            r#"[
1937                {
1938                    "bindings": {
1939                        "ctrl-a": "zed::SomeAction"
1940                    }
1941                },
1942                {
1943                    "bindings": {
1944                        "ctrl-b": [
1945                            "zed::SomeOtherAction",
1946                            {
1947                                "foo": "bar"
1948                            }
1949                        ]
1950                    }
1951                }
1952            ]"#
1953            .unindent(),
1954        );
1955
1956        check_keymap_update(
1957            r#"[
1958                {
1959                    "bindings": {
1960                        "ctrl-a": "zed::SomeAction"
1961                    }
1962                }
1963            ]"#
1964            .unindent(),
1965            KeybindUpdateOperation::add(KeybindUpdateTarget {
1966                keystrokes: &parse_keystrokes("ctrl-b"),
1967                action_name: "zed::SomeOtherAction",
1968                context: Some("Zed > Editor && some_condition = true"),
1969                action_arguments: Some(r#"{"foo": "bar"}"#),
1970            }),
1971            r#"[
1972                {
1973                    "bindings": {
1974                        "ctrl-a": "zed::SomeAction"
1975                    }
1976                },
1977                {
1978                    "context": "Zed > Editor && some_condition = true",
1979                    "bindings": {
1980                        "ctrl-b": [
1981                            "zed::SomeOtherAction",
1982                            {
1983                                "foo": "bar"
1984                            }
1985                        ]
1986                    }
1987                }
1988            ]"#
1989            .unindent(),
1990        );
1991
1992        check_keymap_update(
1993            r#"[
1994                {
1995                    "bindings": {
1996                        "ctrl-a": "zed::SomeAction"
1997                    }
1998                }
1999            ]"#
2000            .unindent(),
2001            KeybindUpdateOperation::Replace {
2002                target: KeybindUpdateTarget {
2003                    keystrokes: &parse_keystrokes("ctrl-a"),
2004                    action_name: "zed::SomeAction",
2005                    context: None,
2006                    action_arguments: None,
2007                },
2008                source: KeybindUpdateTarget {
2009                    keystrokes: &parse_keystrokes("ctrl-b"),
2010                    action_name: "zed::SomeOtherAction",
2011                    context: None,
2012                    action_arguments: Some(r#"{"foo": "bar"}"#),
2013                },
2014                target_keybind_source: KeybindSource::Base,
2015            },
2016            r#"[
2017                {
2018                    "bindings": {
2019                        "ctrl-a": "zed::SomeAction"
2020                    }
2021                },
2022                {
2023                    "bindings": {
2024                        "ctrl-b": [
2025                            "zed::SomeOtherAction",
2026                            {
2027                                "foo": "bar"
2028                            }
2029                        ]
2030                    }
2031                },
2032                {
2033                    "unbind": {
2034                        "ctrl-a": "zed::SomeAction"
2035                    }
2036                }
2037            ]"#
2038            .unindent(),
2039        );
2040
2041        // Replacing a non-user binding without changing the keystroke should
2042        // not produce an unbind suppression entry.
2043        check_keymap_update(
2044            r#"[
2045                {
2046                    "bindings": {
2047                        "ctrl-a": "zed::SomeAction"
2048                    }
2049                }
2050            ]"#
2051            .unindent(),
2052            KeybindUpdateOperation::Replace {
2053                target: KeybindUpdateTarget {
2054                    keystrokes: &parse_keystrokes("ctrl-a"),
2055                    action_name: "zed::SomeAction",
2056                    context: None,
2057                    action_arguments: None,
2058                },
2059                source: KeybindUpdateTarget {
2060                    keystrokes: &parse_keystrokes("ctrl-a"),
2061                    action_name: "zed::SomeOtherAction",
2062                    context: None,
2063                    action_arguments: None,
2064                },
2065                target_keybind_source: KeybindSource::Base,
2066            },
2067            r#"[
2068                {
2069                    "bindings": {
2070                        "ctrl-a": "zed::SomeAction"
2071                    }
2072                },
2073                {
2074                    "bindings": {
2075                        "ctrl-a": "zed::SomeOtherAction"
2076                    }
2077                }
2078            ]"#
2079            .unindent(),
2080        );
2081
2082        // Replacing a non-user binding with a context and a keystroke change
2083        // should produce a suppression entry that preserves the context.
2084        check_keymap_update(
2085            r#"[
2086                {
2087                    "context": "SomeContext",
2088                    "bindings": {
2089                        "ctrl-a": "zed::SomeAction"
2090                    }
2091                }
2092            ]"#
2093            .unindent(),
2094            KeybindUpdateOperation::Replace {
2095                target: KeybindUpdateTarget {
2096                    keystrokes: &parse_keystrokes("ctrl-a"),
2097                    action_name: "zed::SomeAction",
2098                    context: Some("SomeContext"),
2099                    action_arguments: None,
2100                },
2101                source: KeybindUpdateTarget {
2102                    keystrokes: &parse_keystrokes("ctrl-b"),
2103                    action_name: "zed::SomeOtherAction",
2104                    context: Some("SomeContext"),
2105                    action_arguments: None,
2106                },
2107                target_keybind_source: KeybindSource::Default,
2108            },
2109            r#"[
2110                {
2111                    "context": "SomeContext",
2112                    "bindings": {
2113                        "ctrl-a": "zed::SomeAction"
2114                    }
2115                },
2116                {
2117                    "context": "SomeContext",
2118                    "bindings": {
2119                        "ctrl-b": "zed::SomeOtherAction"
2120                    }
2121                },
2122                {
2123                    "context": "SomeContext",
2124                    "unbind": {
2125                        "ctrl-a": "zed::SomeAction"
2126                    }
2127                }
2128            ]"#
2129            .unindent(),
2130        );
2131
2132        check_keymap_update(
2133            r#"[
2134                {
2135                    "bindings": {
2136                        "a": "zed::SomeAction"
2137                    }
2138                }
2139            ]"#
2140            .unindent(),
2141            KeybindUpdateOperation::Replace {
2142                target: KeybindUpdateTarget {
2143                    keystrokes: &parse_keystrokes("a"),
2144                    action_name: "zed::SomeAction",
2145                    context: None,
2146                    action_arguments: None,
2147                },
2148                source: KeybindUpdateTarget {
2149                    keystrokes: &parse_keystrokes("ctrl-b"),
2150                    action_name: "zed::SomeOtherAction",
2151                    context: None,
2152                    action_arguments: Some(r#"{"foo": "bar"}"#),
2153                },
2154                target_keybind_source: KeybindSource::User,
2155            },
2156            r#"[
2157                {
2158                    "bindings": {
2159                        "ctrl-b": [
2160                            "zed::SomeOtherAction",
2161                            {
2162                                "foo": "bar"
2163                            }
2164                        ]
2165                    }
2166                }
2167            ]"#
2168            .unindent(),
2169        );
2170
2171        check_keymap_update(
2172            r#"[
2173                {
2174                    "bindings": {
2175                        "\\ a": "zed::SomeAction"
2176                    }
2177                }
2178            ]"#
2179            .unindent(),
2180            KeybindUpdateOperation::Replace {
2181                target: KeybindUpdateTarget {
2182                    keystrokes: &parse_keystrokes("\\ a"),
2183                    action_name: "zed::SomeAction",
2184                    context: None,
2185                    action_arguments: None,
2186                },
2187                source: KeybindUpdateTarget {
2188                    keystrokes: &parse_keystrokes("\\ b"),
2189                    action_name: "zed::SomeOtherAction",
2190                    context: None,
2191                    action_arguments: Some(r#"{"foo": "bar"}"#),
2192                },
2193                target_keybind_source: KeybindSource::User,
2194            },
2195            r#"[
2196                {
2197                    "bindings": {
2198                        "\\ b": [
2199                            "zed::SomeOtherAction",
2200                            {
2201                                "foo": "bar"
2202                            }
2203                        ]
2204                    }
2205                }
2206            ]"#
2207            .unindent(),
2208        );
2209
2210        check_keymap_update(
2211            r#"[
2212                {
2213                    "bindings": {
2214                        "\\ a": "zed::SomeAction"
2215                    }
2216                }
2217            ]"#
2218            .unindent(),
2219            KeybindUpdateOperation::Replace {
2220                target: KeybindUpdateTarget {
2221                    keystrokes: &parse_keystrokes("\\ a"),
2222                    action_name: "zed::SomeAction",
2223                    context: None,
2224                    action_arguments: None,
2225                },
2226                source: KeybindUpdateTarget {
2227                    keystrokes: &parse_keystrokes("\\ a"),
2228                    action_name: "zed::SomeAction",
2229                    context: None,
2230                    action_arguments: None,
2231                },
2232                target_keybind_source: KeybindSource::User,
2233            },
2234            r#"[
2235                {
2236                    "bindings": {
2237                        "\\ a": "zed::SomeAction"
2238                    }
2239                }
2240            ]"#
2241            .unindent(),
2242        );
2243
2244        check_keymap_update(
2245            r#"[
2246                {
2247                    "bindings": {
2248                        "ctrl-a": "zed::SomeAction"
2249                    }
2250                }
2251            ]"#
2252            .unindent(),
2253            KeybindUpdateOperation::Replace {
2254                target: KeybindUpdateTarget {
2255                    keystrokes: &parse_keystrokes("ctrl-a"),
2256                    action_name: "zed::SomeNonexistentAction",
2257                    context: None,
2258                    action_arguments: None,
2259                },
2260                source: KeybindUpdateTarget {
2261                    keystrokes: &parse_keystrokes("ctrl-b"),
2262                    action_name: "zed::SomeOtherAction",
2263                    context: None,
2264                    action_arguments: None,
2265                },
2266                target_keybind_source: KeybindSource::User,
2267            },
2268            r#"[
2269                {
2270                    "bindings": {
2271                        "ctrl-a": "zed::SomeAction"
2272                    }
2273                },
2274                {
2275                    "bindings": {
2276                        "ctrl-b": "zed::SomeOtherAction"
2277                    }
2278                }
2279            ]"#
2280            .unindent(),
2281        );
2282
2283        check_keymap_update(
2284            r#"[
2285                {
2286                    "bindings": {
2287                        // some comment
2288                        "ctrl-a": "zed::SomeAction"
2289                        // some other comment
2290                    }
2291                }
2292            ]"#
2293            .unindent(),
2294            KeybindUpdateOperation::Replace {
2295                target: KeybindUpdateTarget {
2296                    keystrokes: &parse_keystrokes("ctrl-a"),
2297                    action_name: "zed::SomeAction",
2298                    context: None,
2299                    action_arguments: None,
2300                },
2301                source: KeybindUpdateTarget {
2302                    keystrokes: &parse_keystrokes("ctrl-b"),
2303                    action_name: "zed::SomeOtherAction",
2304                    context: None,
2305                    action_arguments: Some(r#"{"foo": "bar"}"#),
2306                },
2307                target_keybind_source: KeybindSource::User,
2308            },
2309            r#"[
2310                {
2311                    "bindings": {
2312                        // some comment
2313                        "ctrl-b": [
2314                            "zed::SomeOtherAction",
2315                            {
2316                                "foo": "bar"
2317                            }
2318                        ]
2319                        // some other comment
2320                    }
2321                }
2322            ]"#
2323            .unindent(),
2324        );
2325
2326        check_keymap_update(
2327            r#"[
2328                {
2329                    "context": "SomeContext",
2330                    "bindings": {
2331                        "a": "foo::bar",
2332                        "b": "baz::qux",
2333                    }
2334                }
2335            ]"#
2336            .unindent(),
2337            KeybindUpdateOperation::Replace {
2338                target: KeybindUpdateTarget {
2339                    keystrokes: &parse_keystrokes("a"),
2340                    action_name: "foo::bar",
2341                    context: Some("SomeContext"),
2342                    action_arguments: None,
2343                },
2344                source: KeybindUpdateTarget {
2345                    keystrokes: &parse_keystrokes("c"),
2346                    action_name: "foo::baz",
2347                    context: Some("SomeOtherContext"),
2348                    action_arguments: None,
2349                },
2350                target_keybind_source: KeybindSource::User,
2351            },
2352            r#"[
2353                {
2354                    "context": "SomeContext",
2355                    "bindings": {
2356                        "b": "baz::qux",
2357                    }
2358                },
2359                {
2360                    "context": "SomeOtherContext",
2361                    "bindings": {
2362                        "c": "foo::baz"
2363                    }
2364                }
2365            ]"#
2366            .unindent(),
2367        );
2368
2369        check_keymap_update(
2370            r#"[
2371                {
2372                    "context": "SomeContext",
2373                    "bindings": {
2374                        "a": "foo::bar",
2375                    }
2376                }
2377            ]"#
2378            .unindent(),
2379            KeybindUpdateOperation::Replace {
2380                target: KeybindUpdateTarget {
2381                    keystrokes: &parse_keystrokes("a"),
2382                    action_name: "foo::bar",
2383                    context: Some("SomeContext"),
2384                    action_arguments: None,
2385                },
2386                source: KeybindUpdateTarget {
2387                    keystrokes: &parse_keystrokes("c"),
2388                    action_name: "foo::baz",
2389                    context: Some("SomeOtherContext"),
2390                    action_arguments: None,
2391                },
2392                target_keybind_source: KeybindSource::User,
2393            },
2394            r#"[
2395                {
2396                    "context": "SomeOtherContext",
2397                    "bindings": {
2398                        "c": "foo::baz",
2399                    }
2400                }
2401            ]"#
2402            .unindent(),
2403        );
2404
2405        check_keymap_update(
2406            r#"[
2407                {
2408                    "context": "SomeContext",
2409                    "bindings": {
2410                        "a": "foo::bar",
2411                        "c": "foo::baz",
2412                    }
2413                },
2414            ]"#
2415            .unindent(),
2416            KeybindUpdateOperation::Remove {
2417                target: KeybindUpdateTarget {
2418                    context: Some("SomeContext"),
2419                    keystrokes: &parse_keystrokes("a"),
2420                    action_name: "foo::bar",
2421                    action_arguments: None,
2422                },
2423                target_keybind_source: KeybindSource::User,
2424            },
2425            r#"[
2426                {
2427                    "context": "SomeContext",
2428                    "bindings": {
2429                        "c": "foo::baz",
2430                    }
2431                },
2432            ]"#
2433            .unindent(),
2434        );
2435
2436        check_keymap_update(
2437            r#"[
2438                {
2439                    "context": "SomeContext",
2440                    "bindings": {
2441                        "\\ a": "foo::bar",
2442                        "c": "foo::baz",
2443                    }
2444                },
2445            ]"#
2446            .unindent(),
2447            KeybindUpdateOperation::Remove {
2448                target: KeybindUpdateTarget {
2449                    context: Some("SomeContext"),
2450                    keystrokes: &parse_keystrokes("\\ a"),
2451                    action_name: "foo::bar",
2452                    action_arguments: None,
2453                },
2454                target_keybind_source: KeybindSource::User,
2455            },
2456            r#"[
2457                {
2458                    "context": "SomeContext",
2459                    "bindings": {
2460                        "c": "foo::baz",
2461                    }
2462                },
2463            ]"#
2464            .unindent(),
2465        );
2466
2467        check_keymap_update(
2468            r#"[
2469                {
2470                    "context": "SomeContext",
2471                    "bindings": {
2472                        "a": ["foo::bar", true],
2473                        "c": "foo::baz",
2474                    }
2475                },
2476            ]"#
2477            .unindent(),
2478            KeybindUpdateOperation::Remove {
2479                target: KeybindUpdateTarget {
2480                    context: Some("SomeContext"),
2481                    keystrokes: &parse_keystrokes("a"),
2482                    action_name: "foo::bar",
2483                    action_arguments: Some("true"),
2484                },
2485                target_keybind_source: KeybindSource::User,
2486            },
2487            r#"[
2488                {
2489                    "context": "SomeContext",
2490                    "bindings": {
2491                        "c": "foo::baz",
2492                    }
2493                },
2494            ]"#
2495            .unindent(),
2496        );
2497
2498        check_keymap_update(
2499            r#"[
2500                {
2501                    "context": "SomeContext",
2502                    "bindings": {
2503                        "b": "foo::baz",
2504                    }
2505                },
2506                {
2507                    "context": "SomeContext",
2508                    "bindings": {
2509                        "a": ["foo::bar", true],
2510                    }
2511                },
2512                {
2513                    "context": "SomeContext",
2514                    "bindings": {
2515                        "c": "foo::baz",
2516                    }
2517                },
2518            ]"#
2519            .unindent(),
2520            KeybindUpdateOperation::Remove {
2521                target: KeybindUpdateTarget {
2522                    context: Some("SomeContext"),
2523                    keystrokes: &parse_keystrokes("a"),
2524                    action_name: "foo::bar",
2525                    action_arguments: Some("true"),
2526                },
2527                target_keybind_source: KeybindSource::User,
2528            },
2529            r#"[
2530                {
2531                    "context": "SomeContext",
2532                    "bindings": {
2533                        "b": "foo::baz",
2534                    }
2535                },
2536                {
2537                    "context": "SomeContext",
2538                    "bindings": {
2539                        "c": "foo::baz",
2540                    }
2541                },
2542            ]"#
2543            .unindent(),
2544        );
2545        check_keymap_update(
2546            r#"[
2547                {
2548                    "context": "SomeOtherContext",
2549                    "use_key_equivalents": true,
2550                    "bindings": {
2551                        "b": "foo::bar",
2552                    }
2553                },
2554            ]"#
2555            .unindent(),
2556            KeybindUpdateOperation::Add {
2557                source: KeybindUpdateTarget {
2558                    context: Some("SomeContext"),
2559                    keystrokes: &parse_keystrokes("a"),
2560                    action_name: "foo::baz",
2561                    action_arguments: Some("true"),
2562                },
2563                from: Some(KeybindUpdateTarget {
2564                    context: Some("SomeOtherContext"),
2565                    keystrokes: &parse_keystrokes("b"),
2566                    action_name: "foo::bar",
2567                    action_arguments: None,
2568                }),
2569            },
2570            r#"[
2571                {
2572                    "context": "SomeOtherContext",
2573                    "use_key_equivalents": true,
2574                    "bindings": {
2575                        "b": "foo::bar",
2576                    }
2577                },
2578                {
2579                    "context": "SomeContext",
2580                    "use_key_equivalents": true,
2581                    "bindings": {
2582                        "a": [
2583                            "foo::baz",
2584                            true
2585                        ]
2586                    }
2587                }
2588            ]"#
2589            .unindent(),
2590        );
2591
2592        check_keymap_update(
2593            r#"[
2594                {
2595                    "context": "SomeOtherContext",
2596                    "use_key_equivalents": true,
2597                    "bindings": {
2598                        "b": "foo::bar",
2599                    }
2600                },
2601            ]"#
2602            .unindent(),
2603            KeybindUpdateOperation::Remove {
2604                target: KeybindUpdateTarget {
2605                    context: Some("SomeContext"),
2606                    keystrokes: &parse_keystrokes("a"),
2607                    action_name: "foo::baz",
2608                    action_arguments: Some("true"),
2609                },
2610                target_keybind_source: KeybindSource::Default,
2611            },
2612            r#"[
2613                {
2614                    "context": "SomeOtherContext",
2615                    "use_key_equivalents": true,
2616                    "bindings": {
2617                        "b": "foo::bar",
2618                    }
2619                },
2620                {
2621                    "context": "SomeContext",
2622                    "unbind": {
2623                        "a": [
2624                            "foo::baz",
2625                            true
2626                        ]
2627                    }
2628                }
2629            ]"#
2630            .unindent(),
2631        );
2632    }
2633
2634    #[test]
2635    fn test_keymap_remove_duplicate_binding() {
2636        zlog::init_test();
2637
2638        // Repro: user created two identical bindings via the keymap editor UI,
2639        // resulting in two sections with the same keystrokes and action.
2640        // Deleting one of them should remove exactly one section.
2641        check_keymap_update(
2642            r#"
2643            [
2644              {
2645                "bindings": {
2646                  "alt-cmd-shift-c": "workspace::CopyRelativePath"
2647                }
2648              },
2649              {
2650                "bindings": {
2651                  "alt-cmd-shift-c": "workspace::CopyRelativePath"
2652                }
2653              }
2654            ]
2655            "#
2656            .unindent(),
2657            KeybindUpdateOperation::Remove {
2658                target: KeybindUpdateTarget {
2659                    context: None,
2660                    keystrokes: &parse_keystrokes("alt-cmd-shift-c"),
2661                    action_name: "workspace::CopyRelativePath",
2662                    action_arguments: None,
2663                },
2664                target_keybind_source: KeybindSource::User,
2665            },
2666            r#"
2667            [
2668              {
2669                "bindings": {
2670                  "alt-cmd-shift-c": "workspace::CopyRelativePath"
2671                }
2672              }
2673            ]
2674            "#
2675            .unindent(),
2676        );
2677    }
2678
2679    #[test]
2680    fn test_keymap_update_deprecated_alias_binding() {
2681        zlog::init_test();
2682
2683        // The keymap file entry uses a deprecated alias of the action, while the
2684        // update target uses the canonical name (as reported by the loaded binding).
2685        let deprecated_aliases = &[("editor::CopyRelativePath", "workspace::CopyRelativePath")];
2686
2687        check_keymap_update_with_deprecated_aliases(
2688            r#"
2689            [
2690              {
2691                "bindings": {
2692                  "alt-cmd-shift-c": "editor::CopyRelativePath",
2693                  "a": "foo::Bar"
2694                }
2695              }
2696            ]
2697            "#
2698            .unindent(),
2699            KeybindUpdateOperation::Remove {
2700                target: KeybindUpdateTarget {
2701                    context: None,
2702                    keystrokes: &parse_keystrokes("alt-cmd-shift-c"),
2703                    action_name: "workspace::CopyRelativePath",
2704                    action_arguments: None,
2705                },
2706                target_keybind_source: KeybindSource::User,
2707            },
2708            r#"
2709            [
2710              {
2711                "bindings": {
2712                  "a": "foo::Bar"
2713                }
2714              }
2715            ]
2716            "#
2717            .unindent(),
2718            deprecated_aliases,
2719        );
2720
2721        // Editing an alias entry should update it in place (rewriting it with the
2722        // canonical name) instead of falling back to appending a new binding.
2723        check_keymap_update_with_deprecated_aliases(
2724            r#"
2725            [
2726              {
2727                "bindings": {
2728                  "alt-cmd-shift-c": "editor::CopyRelativePath"
2729                }
2730              }
2731            ]
2732            "#
2733            .unindent(),
2734            KeybindUpdateOperation::Replace {
2735                target: KeybindUpdateTarget {
2736                    context: None,
2737                    keystrokes: &parse_keystrokes("alt-cmd-shift-c"),
2738                    action_name: "workspace::CopyRelativePath",
2739                    action_arguments: None,
2740                },
2741                source: KeybindUpdateTarget {
2742                    context: None,
2743                    keystrokes: &parse_keystrokes("ctrl-alt-c"),
2744                    action_name: "workspace::CopyRelativePath",
2745                    action_arguments: None,
2746                },
2747                target_keybind_source: KeybindSource::User,
2748            },
2749            r#"
2750            [
2751              {
2752                "bindings": {
2753                  "ctrl-alt-c": "workspace::CopyRelativePath"
2754                }
2755              }
2756            ]
2757            "#
2758            .unindent(),
2759            deprecated_aliases,
2760        );
2761    }
2762
2763    #[test]
2764    fn test_keymap_remove() {
2765        zlog::init_test();
2766
2767        check_keymap_update(
2768            r#"
2769            [
2770              {
2771                "context": "Editor",
2772                "bindings": {
2773                  "cmd-k cmd-u": "editor::ConvertToUpperCase",
2774                  "cmd-k cmd-l": "editor::ConvertToLowerCase",
2775                  "cmd-[": "pane::GoBack",
2776                }
2777              },
2778            ]
2779            "#,
2780            KeybindUpdateOperation::Remove {
2781                target: KeybindUpdateTarget {
2782                    context: Some("Editor"),
2783                    keystrokes: &parse_keystrokes("cmd-k cmd-l"),
2784                    action_name: "editor::ConvertToLowerCase",
2785                    action_arguments: None,
2786                },
2787                target_keybind_source: KeybindSource::User,
2788            },
2789            r#"
2790            [
2791              {
2792                "context": "Editor",
2793                "bindings": {
2794                  "cmd-k cmd-u": "editor::ConvertToUpperCase",
2795                  "cmd-[": "pane::GoBack",
2796                }
2797              },
2798            ]
2799            "#,
2800        );
2801    }
2802}
2803
Served at tenant.openagents/omega Member data and write actions are omitted.