use anyhow::{Context as _, Result};use collections::{BTreeMap, HashMap, IndexMap};use fs::Fs;use gpui::{ Action, ActionBuildError, App, InvalidKeystrokeError, KEYSTROKE_PARSE_EXPECTED_MESSAGE, KeyBinding, KeyBindingContextPredicate, KeyBindingMetaIndex, KeybindingKeystroke, Keystroke, NoAction, SharedString, generate_list_of_all_registered_actions, register_action,};use schemars::{JsonSchema, json_schema};use serde::Deserialize;use serde_json::{Value, json};use std::borrow::Cow;use std::{any::TypeId, fmt::Write, rc::Rc, sync::Arc, sync::LazyLock};use util::ResultExt as _;use util::{ asset_str, markdown::{MarkdownEscaped, MarkdownInlineCode, MarkdownString}, schemars::AllowTrailingCommas,}; use crate::SettingsAssets;use settings_json::{ append_top_level_array_value_in_json_text, parse_json_with_comments, replace_top_level_array_value_in_json_text,}; pub trait KeyBindingValidator: Send + Sync { fn action_type_id(&self) -> TypeId; fn validate(&self, binding: &KeyBinding) -> Result<(), MarkdownString>;} pub struct KeyBindingValidatorRegistration(pub fn() -> Box<dyn KeyBindingValidator>); inventory::collect!(KeyBindingValidatorRegistration); pub(crate) static KEY_BINDING_VALIDATORS: LazyLock<BTreeMap<TypeId, Box<dyn KeyBindingValidator>>> = LazyLock::new(|| { let mut validators = BTreeMap::new(); for validator_registration in inventory::iter::<KeyBindingValidatorRegistration> { let validator = validator_registration.0(); validators.insert(validator.action_type_id(), validator); } validators }); // Note that the doc comments on these are shown by json-language-server when editing the keymap, so// they should be considered user-facing documentation. Documentation is not handled well with// schemars-0.8 - when there are newlines, it is rendered as plaintext (see// https://github.com/GREsau/schemars/issues/38#issuecomment-2282883519). So for now these docs// avoid newlines.//// TODO: Update to schemars-1.0 once it's released, and add more docs as newlines would be// supported. Tracking issue is https://github.com/GREsau/schemars/issues/112. /// Keymap configuration consisting of sections. Each section may have a context predicate which/// determines whether its bindings are used.#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]#[serde(transparent)]pub struct KeymapFile(Vec<KeymapSection>); /// Keymap section which binds keystrokes to actions.#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]pub struct KeymapSection { /// Determines when these bindings are active. When just a name is provided, like `Editor` or /// `Workspace`, the bindings will be active in that context. Boolean expressions like `X && Y`, /// `X || Y`, `!X` are also supported. Some more complex logic including checking OS and the /// current file extension are also supported - see [the /// documentation](https://zed.dev/docs/key-bindings#contexts) for more details. #[serde(default)] pub context: String, /// This option enables specifying keys based on their position on a QWERTY keyboard, by using /// position-equivalent mappings for some non-QWERTY keyboards. This is currently only supported /// on macOS. See the documentation for more details. #[serde(default)] use_key_equivalents: bool, /// This keymap section's bindings, as a JSON object mapping keystrokes to actions. The /// keystrokes key is a string representing a sequence of keystrokes to type, where the /// keystrokes are separated by whitespace. Each keystroke is a sequence of modifiers (`ctrl`, /// `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) followed by a key, separated by `-`. The /// order of bindings does matter. When the same keystrokes are bound at the same context depth, /// the binding that occurs later in the file is preferred. For displaying keystrokes in the UI, /// the later binding for the same action is preferred. #[serde(default)] bindings: Option<IndexMap<String, KeymapAction>>, #[serde(flatten)] unrecognized_fields: IndexMap<String, Value>, // This struct intentionally uses permissive types for its fields, rather than validating during // deserialization. The purpose of this is to allow loading the portion of the keymap that doesn't // have errors. The downside of this is that the errors are not reported with line+column info. // Unfortunately the implementations of the `Spanned` types for preserving this information are // highly inconvenient (`serde_spanned`) and in some cases don't work at all here // (`json_spanned_>value`). Serde should really have builtin support for this.} impl KeymapSection { pub fn bindings(&self) -> impl DoubleEndedIterator<Item = (&String, &KeymapAction)> { self.bindings.iter().flatten() }} /// Keymap action as a JSON value, since it can either be null for no action, or the name of the/// action, or an array of the name of the action and the action input.////// Unlike the other json types involved in keymaps (including actions), this doc-comment will not/// be included in the generated JSON schema, as it manually defines its `JsonSchema` impl. The/// actual schema used for it is automatically generated in `KeymapFile::generate_json_schema`.#[derive(Debug, Deserialize, Default, Clone)]#[serde(transparent)]pub struct KeymapAction(Value); impl std::fmt::Display for KeymapAction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.0 { Value::String(s) => write!(f, "{}", s), Value::Array(arr) => { let strings: Vec<String> = arr.iter().map(|v| v.to_string()).collect(); write!(f, "{}", strings.join(", ")) } _ => write!(f, "{}", self.0), } }} impl JsonSchema for KeymapAction { /// This is used when generating the JSON schema for the `KeymapAction` type, so that it can /// reference the keymap action schema. fn schema_name() -> Cow<'static, str> { "KeymapAction".into() } /// This schema will be replaced with the full action schema in /// `KeymapFile::generate_json_schema`. fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { json_schema!(true) }} #[derive(Debug)]#[must_use]pub enum KeymapFileLoadResult { Success { key_bindings: Vec<KeyBinding>, }, SomeFailedToLoad { key_bindings: Vec<KeyBinding>, error_message: MarkdownString, }, JsonParseFailure { error: anyhow::Error, },} impl KeymapFile { pub fn parse(content: &str) -> anyhow::Result<Self> { if content.trim().is_empty() { return Ok(Self(Vec::new())); } parse_json_with_comments::<Self>(content) } pub fn load_asset( asset_path: &str, source: Option<KeybindSource>, cx: &App, ) -> anyhow::Result<Vec<KeyBinding>> { match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) { KeymapFileLoadResult::Success { mut key_bindings } => match source { Some(source) => Ok({ for key_binding in &mut key_bindings { key_binding.set_meta(source.meta()); } key_bindings }), None => Ok(key_bindings), }, KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => { anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}",) } KeymapFileLoadResult::JsonParseFailure { error } => { anyhow::bail!("JSON parse error in built-in keymap \"{asset_path}\": {error}") } } } pub fn load_asset_allow_partial_failure( asset_path: &str, cx: &App, ) -> anyhow::Result<Vec<KeyBinding>> { match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) { KeymapFileLoadResult::SomeFailedToLoad { key_bindings, error_message, .. } if key_bindings.is_empty() => { anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}",) } KeymapFileLoadResult::Success { key_bindings, .. } | KeymapFileLoadResult::SomeFailedToLoad { key_bindings, .. } => Ok(key_bindings), KeymapFileLoadResult::JsonParseFailure { error } => { anyhow::bail!("JSON parse error in built-in keymap \"{asset_path}\": {error}") } } } #[cfg(feature = "test-support")] pub fn load_panic_on_failure(content: &str, cx: &App) -> Vec<KeyBinding> { match Self::load(content, cx) { KeymapFileLoadResult::Success { key_bindings, .. } => key_bindings, KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => { panic!("{error_message}"); } KeymapFileLoadResult::JsonParseFailure { error } => { panic!("JSON parse error: {error}"); } } } pub fn load(content: &str, cx: &App) -> KeymapFileLoadResult { let keymap_file = match Self::parse(content) { Ok(keymap_file) => keymap_file, Err(error) => { return KeymapFileLoadResult::JsonParseFailure { error }; } }; // Accumulate errors in order to support partial load of user keymap in the presence of // errors in context and binding parsing. let mut errors = Vec::new(); let mut key_bindings = Vec::new(); for KeymapSection { context, use_key_equivalents, bindings, unrecognized_fields, } in keymap_file.0.iter() { let context_predicate: Option<Rc<KeyBindingContextPredicate>> = if context.is_empty() { None } else { match KeyBindingContextPredicate::parse(context) { Ok(context_predicate) => Some(context_predicate.into()), Err(err) => { // Leading space is to separate from the message indicating which section // the error occurred in. errors.push(( context, format!(" Parse error in section `context` field: {}", err), )); continue; } } }; let mut section_errors = String::new(); if !unrecognized_fields.is_empty() { write!( section_errors, "\n\n - Unrecognized fields: {}", MarkdownInlineCode(&format!("{:?}", unrecognized_fields.keys())) ) .unwrap(); } if let Some(bindings) = bindings { for (keystrokes, action) in bindings { let result = Self::load_keybinding( keystrokes, action, context_predicate.clone(), *use_key_equivalents, cx, ); match result { Ok(key_binding) => { key_bindings.push(key_binding); } Err(err) => { let mut lines = err.lines(); let mut indented_err = lines.next().unwrap().to_string(); for line in lines { indented_err.push_str(" "); indented_err.push_str(line); indented_err.push_str("\n"); } write!( section_errors, "\n\n- In binding {}, {indented_err}", MarkdownInlineCode(&format!("\"{}\"", keystrokes)) ) .unwrap(); } } } } if !section_errors.is_empty() { errors.push((context, section_errors)) } } if errors.is_empty() { KeymapFileLoadResult::Success { key_bindings } } else { let mut error_message = "Errors in user keymap file.".to_owned(); for (context, section_errors) in errors { if context.is_empty() { let _ = write!(error_message, "\nIn section without context predicate:"); } else { let _ = write!( error_message, "\nIn section with {}:", MarkdownInlineCode(&format!("context = \"{}\"", context)) ); } let _ = write!(error_message, "{section_errors}"); } KeymapFileLoadResult::SomeFailedToLoad { key_bindings, error_message: MarkdownString(error_message), } } } fn load_keybinding( keystrokes: &str, action: &KeymapAction, context: Option<Rc<KeyBindingContextPredicate>>, use_key_equivalents: bool, cx: &App, ) -> std::result::Result<KeyBinding, String> { let (action, action_input_string) = Self::build_keymap_action(action, cx)?; let key_binding = match KeyBinding::load( keystrokes, action, context, use_key_equivalents, action_input_string.map(SharedString::from), cx.keyboard_mapper().as_ref(), ) { Ok(key_binding) => key_binding, Err(InvalidKeystrokeError { keystroke }) => { return Err(format!( "invalid keystroke {}. {}", MarkdownInlineCode(&format!("\"{}\"", &keystroke)), KEYSTROKE_PARSE_EXPECTED_MESSAGE )); } }; if let Some(validator) = KEY_BINDING_VALIDATORS.get(&key_binding.action().type_id()) { match validator.validate(&key_binding) { Ok(()) => Ok(key_binding), Err(error) => Err(error.0), } } else { Ok(key_binding) } } pub fn parse_action( action: &KeymapAction, ) -> Result<Option<(&String, Option<&Value>)>, String> { let name_and_input = match &action.0 { Value::Array(items) => { if items.len() != 2 { return Err(format!( "expected two-element array of `[name, input]`. \ Instead found {}.", MarkdownInlineCode(&action.0.to_string()) )); } let serde_json::Value::String(ref name) = items[0] else { return Err(format!( "expected two-element array of `[name, input]`, \ but the first element is not a string in {}.", MarkdownInlineCode(&action.0.to_string()) )); }; Some((name, Some(&items[1]))) } Value::String(name) => Some((name, None)), Value::Null => None, _ => { return Err(format!( "expected two-element array of `[name, input]`. \ Instead found {}.", MarkdownInlineCode(&action.0.to_string()) )); } }; Ok(name_and_input) } fn build_keymap_action( action: &KeymapAction, cx: &App, ) -> std::result::Result<(Box<dyn Action>, Option<String>), String> { let (build_result, action_input_string) = match Self::parse_action(action)? { Some((name, action_input)) if name.as_str() == ActionSequence::name_for_type() => { match action_input { Some(action_input) => ( ActionSequence::build_sequence(action_input.clone(), cx), None, ), None => (Err(ActionSequence::expected_array_error()), None), } } Some((name, Some(action_input))) => { let action_input_string = action_input.to_string(); ( cx.build_action(name, Some(action_input.clone())), Some(action_input_string), ) } Some((name, None)) => (cx.build_action(name, None), None), None => (Ok(NoAction.boxed_clone()), None), }; let action = match build_result { Ok(action) => action, Err(ActionBuildError::NotFound { name }) => { return Err(format!( "didn't find an action named {}.", MarkdownInlineCode(&format!("\"{}\"", &name)) )); } Err(ActionBuildError::BuildError { name, error }) => match action_input_string { Some(action_input_string) => { return Err(format!( "can't build {} action from input value {}: {}", MarkdownInlineCode(&format!("\"{}\"", &name)), MarkdownInlineCode(&action_input_string), MarkdownEscaped(&error.to_string()) )); } None => { return Err(format!( "can't build {} action - it requires input data via [name, input]: {}", MarkdownInlineCode(&format!("\"{}\"", &name)), MarkdownEscaped(&error.to_string()) )); } }, }; Ok((action, action_input_string)) } /// Creates a JSON schema generator, suitable for generating json schemas /// for actions pub fn action_schema_generator() -> schemars::SchemaGenerator { schemars::generate::SchemaSettings::draft2019_09() .with_transform(AllowTrailingCommas) .into_generator() } pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value { // instead of using DefaultDenyUnknownFields, actions typically use // `#[serde(deny_unknown_fields)]` so that these cases are reported as parse failures. This // is because the rest of the keymap will still load in these cases, whereas other settings // files would not. let mut generator = Self::action_schema_generator(); let action_schemas = cx.action_schemas(&mut generator); let action_documentation = cx.action_documentation(); let deprecations = cx.deprecated_actions_to_preferred_actions(); let deprecation_messages = cx.action_deprecation_messages(); KeymapFile::generate_json_schema( generator, action_schemas, action_documentation, deprecations, deprecation_messages, ) } pub fn generate_json_schema_from_inventory() -> Value { let mut generator = Self::action_schema_generator(); let mut action_schemas = Vec::new(); let mut documentation = HashMap::default(); let mut deprecations = HashMap::default(); let mut deprecation_messages = HashMap::default(); for action_data in generate_list_of_all_registered_actions() { let schema = (action_data.json_schema)(&mut generator); action_schemas.push((action_data.name, schema)); if let Some(doc) = action_data.documentation { documentation.insert(action_data.name, doc); } if let Some(msg) = action_data.deprecation_message { deprecation_messages.insert(action_data.name, msg); } for &alias in action_data.deprecated_aliases {Lines 1–500 of 2153. Load more
use anyhow::{Context as _, Result}; use collections::{BTreeMap, HashMap, IndexMap}; use fs::Fs; use gpui::{ Action, ActionBuildError, App, InvalidKeystrokeError, KEYSTROKE_PARSE_EXPECTED_MESSAGE, KeyBinding, KeyBindingContextPredicate, KeyBindingMetaIndex, KeybindingKeystroke, Keystroke, NoAction, SharedString, generate_list_of_all_registered_actions, register_action, }; use schemars::{JsonSchema, json_schema}; use serde::Deserialize; use serde_json::{Value, json}; use std::borrow::Cow; use std::{any::TypeId, fmt::Write, rc::Rc, sync::Arc, sync::LazyLock}; use util::ResultExt as _; use util::{ asset_str, markdown::{MarkdownEscaped, MarkdownInlineCode, MarkdownString}, schemars::AllowTrailingCommas, }; use crate::SettingsAssets; use settings_json::{ append_top_level_array_value_in_json_text, parse_json_with_comments, replace_top_level_array_value_in_json_text, }; pub trait KeyBindingValidator: Send + Sync { fn action_type_id(&self) -> TypeId; fn validate(&self, binding: &KeyBinding) -> Result<(), MarkdownString>; } pub struct KeyBindingValidatorRegistration(pub fn() -> Box<dyn KeyBindingValidator>); inventory::collect!(KeyBindingValidatorRegistration); pub(crate) static KEY_BINDING_VALIDATORS: LazyLock<BTreeMap<TypeId, Box<dyn KeyBindingValidator>>> = LazyLock::new(|| { let mut validators = BTreeMap::new(); for validator_registration in inventory::iter::<KeyBindingValidatorRegistration> { let validator = validator_registration.0(); validators.insert(validator.action_type_id(), validator); } validators }); // Note that the doc comments on these are shown by json-language-server when editing the keymap, so // they should be considered user-facing documentation. Documentation is not handled well with // schemars-0.8 - when there are newlines, it is rendered as plaintext (see // https://github.com/GREsau/schemars/issues/38#issuecomment-2282883519). So for now these docs // avoid newlines. // // TODO: Update to schemars-1.0 once it's released, and add more docs as newlines would be // supported. Tracking issue is https://github.com/GREsau/schemars/issues/112. /// Keymap configuration consisting of sections. Each section may have a context predicate which /// determines whether its bindings are used. #[derive(Debug, Deserialize, Default, Clone, JsonSchema)] #[serde(transparent)] pub struct KeymapFile(Vec<KeymapSection>); /// Keymap section which binds keystrokes to actions. #[derive(Debug, Deserialize, Default, Clone, JsonSchema)] pub struct KeymapSection { /// Determines when these bindings are active. When just a name is provided, like `Editor` or /// `Workspace`, the bindings will be active in that context. Boolean expressions like `X && Y`, /// `X || Y`, `!X` are also supported. Some more complex logic including checking OS and the /// current file extension are also supported - see [the /// documentation](https://zed.dev/docs/key-bindings#contexts) for more details. #[serde(default)] pub context: String, /// This option enables specifying keys based on their position on a QWERTY keyboard, by using /// position-equivalent mappings for some non-QWERTY keyboards. This is currently only supported /// on macOS. See the documentation for more details. #[serde(default)] use_key_equivalents: bool, /// This keymap section's bindings, as a JSON object mapping keystrokes to actions. The /// keystrokes key is a string representing a sequence of keystrokes to type, where the /// keystrokes are separated by whitespace. Each keystroke is a sequence of modifiers (`ctrl`, /// `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) followed by a key, separated by `-`. The /// order of bindings does matter. When the same keystrokes are bound at the same context depth, /// the binding that occurs later in the file is preferred. For displaying keystrokes in the UI, /// the later binding for the same action is preferred. #[serde(default)] bindings: Option<IndexMap<String, KeymapAction>>, #[serde(flatten)] unrecognized_fields: IndexMap<String, Value>, // This struct intentionally uses permissive types for its fields, rather than validating during // deserialization. The purpose of this is to allow loading the portion of the keymap that doesn't // have errors. The downside of this is that the errors are not reported with line+column info. // Unfortunately the implementations of the `Spanned` types for preserving this information are // highly inconvenient (`serde_spanned`) and in some cases don't work at all here // (`json_spanned_>value`). Serde should really have builtin support for this. } impl KeymapSection { pub fn bindings(&self) -> impl DoubleEndedIterator<Item = (&String, &KeymapAction)> { self.bindings.iter().flatten() } } /// Keymap action as a JSON value, since it can either be null for no action, or the name of the /// action, or an array of the name of the action and the action input. /// /// Unlike the other json types involved in keymaps (including actions), this doc-comment will not /// be included in the generated JSON schema, as it manually defines its `JsonSchema` impl. The /// actual schema used for it is automatically generated in `KeymapFile::generate_json_schema`. #[derive(Debug, Deserialize, Default, Clone)] #[serde(transparent)] pub struct KeymapAction(Value); impl std::fmt::Display for KeymapAction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.0 { Value::String(s) => write!(f, "{}", s), Value::Array(arr) => { let strings: Vec<String> = arr.iter().map(|v| v.to_string()).collect(); write!(f, "{}", strings.join(", ")) } _ => write!(f, "{}", self.0), } } } impl JsonSchema for KeymapAction { /// This is used when generating the JSON schema for the `KeymapAction` type, so that it can /// reference the keymap action schema. fn schema_name() -> Cow<'static, str> { "KeymapAction".into() } /// This schema will be replaced with the full action schema in /// `KeymapFile::generate_json_schema`. fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { json_schema!(true) } } #[derive(Debug)] #[must_use] pub enum KeymapFileLoadResult { Success { key_bindings: Vec<KeyBinding>, }, SomeFailedToLoad { key_bindings: Vec<KeyBinding>, error_message: MarkdownString, }, JsonParseFailure { error: anyhow::Error, }, } impl KeymapFile { pub fn parse(content: &str) -> anyhow::Result<Self> { if content.trim().is_empty() { return Ok(Self(Vec::new())); } parse_json_with_comments::<Self>(content) } pub fn load_asset( asset_path: &str, source: Option<KeybindSource>, cx: &App, ) -> anyhow::Result<Vec<KeyBinding>> { match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) { KeymapFileLoadResult::Success { mut key_bindings } => match source { Some(source) => Ok({ for key_binding in &mut key_bindings { key_binding.set_meta(source.meta()); } key_bindings }), None => Ok(key_bindings), }, KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => { anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}",) } KeymapFileLoadResult::JsonParseFailure { error } => { anyhow::bail!("JSON parse error in built-in keymap \"{asset_path}\": {error}") } } } pub fn load_asset_allow_partial_failure( asset_path: &str, cx: &App, ) -> anyhow::Result<Vec<KeyBinding>> { match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) { KeymapFileLoadResult::SomeFailedToLoad { key_bindings, error_message, .. } if key_bindings.is_empty() => { anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}",) } KeymapFileLoadResult::Success { key_bindings, .. } | KeymapFileLoadResult::SomeFailedToLoad { key_bindings, .. } => Ok(key_bindings), KeymapFileLoadResult::JsonParseFailure { error } => { anyhow::bail!("JSON parse error in built-in keymap \"{asset_path}\": {error}") } } } #[cfg(feature = "test-support")] pub fn load_panic_on_failure(content: &str, cx: &App) -> Vec<KeyBinding> { match Self::load(content, cx) { KeymapFileLoadResult::Success { key_bindings, .. } => key_bindings, KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => { panic!("{error_message}"); } KeymapFileLoadResult::JsonParseFailure { error } => { panic!("JSON parse error: {error}"); } } } pub fn load(content: &str, cx: &App) -> KeymapFileLoadResult { let keymap_file = match Self::parse(content) { Ok(keymap_file) => keymap_file, Err(error) => { return KeymapFileLoadResult::JsonParseFailure { error }; } }; // Accumulate errors in order to support partial load of user keymap in the presence of // errors in context and binding parsing. let mut errors = Vec::new(); let mut key_bindings = Vec::new(); for KeymapSection { context, use_key_equivalents, bindings, unrecognized_fields, } in keymap_file.0.iter() { let context_predicate: Option<Rc<KeyBindingContextPredicate>> = if context.is_empty() { None } else { match KeyBindingContextPredicate::parse(context) { Ok(context_predicate) => Some(context_predicate.into()), Err(err) => { // Leading space is to separate from the message indicating which section // the error occurred in. errors.push(( context, format!(" Parse error in section `context` field: {}", err), )); continue; } } }; let mut section_errors = String::new(); if !unrecognized_fields.is_empty() { write!( section_errors, "\n\n - Unrecognized fields: {}", MarkdownInlineCode(&format!("{:?}", unrecognized_fields.keys())) ) .unwrap(); } if let Some(bindings) = bindings { for (keystrokes, action) in bindings { let result = Self::load_keybinding( keystrokes, action, context_predicate.clone(), *use_key_equivalents, cx, ); match result { Ok(key_binding) => { key_bindings.push(key_binding); } Err(err) => { let mut lines = err.lines(); let mut indented_err = lines.next().unwrap().to_string(); for line in lines { indented_err.push_str(" "); indented_err.push_str(line); indented_err.push_str("\n"); } write!( section_errors, "\n\n- In binding {}, {indented_err}", MarkdownInlineCode(&format!("\"{}\"", keystrokes)) ) .unwrap(); } } } } if !section_errors.is_empty() { errors.push((context, section_errors)) } } if errors.is_empty() { KeymapFileLoadResult::Success { key_bindings } } else { let mut error_message = "Errors in user keymap file.".to_owned(); for (context, section_errors) in errors { if context.is_empty() { let _ = write!(error_message, "\nIn section without context predicate:"); } else { let _ = write!( error_message, "\nIn section with {}:", MarkdownInlineCode(&format!("context = \"{}\"", context)) ); } let _ = write!(error_message, "{section_errors}"); } KeymapFileLoadResult::SomeFailedToLoad { key_bindings, error_message: MarkdownString(error_message), } } } fn load_keybinding( keystrokes: &str, action: &KeymapAction, context: Option<Rc<KeyBindingContextPredicate>>, use_key_equivalents: bool, cx: &App, ) -> std::result::Result<KeyBinding, String> { let (action, action_input_string) = Self::build_keymap_action(action, cx)?; let key_binding = match KeyBinding::load( keystrokes, action, context, use_key_equivalents, action_input_string.map(SharedString::from), cx.keyboard_mapper().as_ref(), ) { Ok(key_binding) => key_binding, Err(InvalidKeystrokeError { keystroke }) => { return Err(format!( "invalid keystroke {}. {}", MarkdownInlineCode(&format!("\"{}\"", &keystroke)), KEYSTROKE_PARSE_EXPECTED_MESSAGE )); } }; if let Some(validator) = KEY_BINDING_VALIDATORS.get(&key_binding.action().type_id()) { match validator.validate(&key_binding) { Ok(()) => Ok(key_binding), Err(error) => Err(error.0), } } else { Ok(key_binding) } } pub fn parse_action( action: &KeymapAction, ) -> Result<Option<(&String, Option<&Value>)>, String> { let name_and_input = match &action.0 { Value::Array(items) => { if items.len() != 2 { return Err(format!( "expected two-element array of `[name, input]`. \ Instead found {}.", MarkdownInlineCode(&action.0.to_string()) )); } let serde_json::Value::String(ref name) = items[0] else { return Err(format!( "expected two-element array of `[name, input]`, \ but the first element is not a string in {}.", MarkdownInlineCode(&action.0.to_string()) )); }; Some((name, Some(&items[1]))) } Value::String(name) => Some((name, None)), Value::Null => None, _ => { return Err(format!( "expected two-element array of `[name, input]`. \ Instead found {}.", MarkdownInlineCode(&action.0.to_string()) )); } }; Ok(name_and_input) } fn build_keymap_action( action: &KeymapAction, cx: &App, ) -> std::result::Result<(Box<dyn Action>, Option<String>), String> { let (build_result, action_input_string) = match Self::parse_action(action)? { Some((name, action_input)) if name.as_str() == ActionSequence::name_for_type() => { match action_input { Some(action_input) => ( ActionSequence::build_sequence(action_input.clone(), cx), None, ), None => (Err(ActionSequence::expected_array_error()), None), } } Some((name, Some(action_input))) => { let action_input_string = action_input.to_string(); ( cx.build_action(name, Some(action_input.clone())), Some(action_input_string), ) } Some((name, None)) => (cx.build_action(name, None), None), None => (Ok(NoAction.boxed_clone()), None), }; let action = match build_result { Ok(action) => action, Err(ActionBuildError::NotFound { name }) => { return Err(format!( "didn't find an action named {}.", MarkdownInlineCode(&format!("\"{}\"", &name)) )); } Err(ActionBuildError::BuildError { name, error }) => match action_input_string { Some(action_input_string) => { return Err(format!( "can't build {} action from input value {}: {}", MarkdownInlineCode(&format!("\"{}\"", &name)), MarkdownInlineCode(&action_input_string), MarkdownEscaped(&error.to_string()) )); } None => { return Err(format!( "can't build {} action - it requires input data via [name, input]: {}", MarkdownInlineCode(&format!("\"{}\"", &name)), MarkdownEscaped(&error.to_string()) )); } }, }; Ok((action, action_input_string)) } /// Creates a JSON schema generator, suitable for generating json schemas /// for actions pub fn action_schema_generator() -> schemars::SchemaGenerator { schemars::generate::SchemaSettings::draft2019_09() .with_transform(AllowTrailingCommas) .into_generator() } pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value { // instead of using DefaultDenyUnknownFields, actions typically use // `#[serde(deny_unknown_fields)]` so that these cases are reported as parse failures. This // is because the rest of the keymap will still load in these cases, whereas other settings // files would not. let mut generator = Self::action_schema_generator(); let action_schemas = cx.action_schemas(&mut generator); let action_documentation = cx.action_documentation(); let deprecations = cx.deprecated_actions_to_preferred_actions(); let deprecation_messages = cx.action_deprecation_messages(); KeymapFile::generate_json_schema( generator, action_schemas, action_documentation, deprecations, deprecation_messages, ) } pub fn generate_json_schema_from_inventory() -> Value { let mut generator = Self::action_schema_generator(); let mut action_schemas = Vec::new(); let mut documentation = HashMap::default(); let mut deprecations = HashMap::default(); let mut deprecation_messages = HashMap::default(); for action_data in generate_list_of_all_registered_actions() { let schema = (action_data.json_schema)(&mut generator); action_schemas.push((action_data.name, schema)); if let Some(doc) = action_data.documentation { documentation.insert(action_data.name, doc); } if let Some(msg) = action_data.deprecation_message { deprecation_messages.insert(action_data.name, msg); } for &alias in action_data.deprecated_aliases {