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, 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_json::{
23 append_top_level_array_value_in_json_text, parse_json_with_comments,
24 replace_top_level_array_value_in_json_text,
25};
26
27pub trait KeyBindingValidator: Send + Sync {
28 fn action_type_id(&self) -> TypeId;
29 fn validate(&self, binding: &KeyBinding) -> Result<(), MarkdownString>;
30}
31
32pub struct KeyBindingValidatorRegistration(pub fn() -> Box<dyn KeyBindingValidator>);
33
34inventory::collect!(KeyBindingValidatorRegistration);
35
36pub(crate) static KEY_BINDING_VALIDATORS: LazyLock<BTreeMap<TypeId, Box<dyn KeyBindingValidator>>> =
37 LazyLock::new(|| {
38 let mut validators = BTreeMap::new();
39 for validator_registration in inventory::iter::<KeyBindingValidatorRegistration> {
40 let validator = validator_registration.0();
41 validators.insert(validator.action_type_id(), validator);
42 }
43 validators
44 });
45
46// Note that the doc comments on these are shown by json-language-server when editing the keymap, so
47// they should be considered user-facing documentation. Documentation is not handled well with
48// schemars-0.8 - when there are newlines, it is rendered as plaintext (see
49// https://github.com/GREsau/schemars/issues/38#issuecomment-2282883519). So for now these docs
50// avoid newlines.
51//
52// TODO: Update to schemars-1.0 once it's released, and add more docs as newlines would be
53// supported. Tracking issue is https://github.com/GREsau/schemars/issues/112.
54
55/// Keymap configuration consisting of sections. Each section may have a context predicate which
56/// determines whether its bindings are used.
57#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
58#[serde(transparent)]
59pub struct KeymapFile(Vec<KeymapSection>);
60
61/// Keymap section which binds keystrokes to actions.
62#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
63pub struct KeymapSection {
64 /// Determines when these bindings are active. When just a name is provided, like `Editor` or
65 /// `Workspace`, the bindings will be active in that context. Boolean expressions like `X && Y`,
66 /// `X || Y`, `!X` are also supported. Some more complex logic including checking OS and the
67 /// current file extension are also supported - see [the
68 /// documentation](https://zed.dev/docs/key-bindings#contexts) for more details.
69 #[serde(default)]
70 pub context: String,
71 /// This option enables specifying keys based on their position on a QWERTY keyboard, by using
72 /// position-equivalent mappings for some non-QWERTY keyboards. This is currently only supported
73 /// on macOS. See the documentation for more details.
74 #[serde(default)]
75 use_key_equivalents: bool,
76 /// This keymap section's bindings, as a JSON object mapping keystrokes to actions. The
77 /// keystrokes key is a string representing a sequence of keystrokes to type, where the
78 /// keystrokes are separated by whitespace. Each keystroke is a sequence of modifiers (`ctrl`,
79 /// `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) followed by a key, separated by `-`. The
80 /// order of bindings does matter. When the same keystrokes are bound at the same context depth,
81 /// the binding that occurs later in the file is preferred. For displaying keystrokes in the UI,
82 /// the later binding for the same action is preferred.
83 #[serde(default)]
84 bindings: Option<IndexMap<String, KeymapAction>>,
85 #[serde(flatten)]
86 unrecognized_fields: IndexMap<String, Value>,
87 // This struct intentionally uses permissive types for its fields, rather than validating during
88 // deserialization. The purpose of this is to allow loading the portion of the keymap that doesn't
89 // have errors. The downside of this is that the errors are not reported with line+column info.
90 // Unfortunately the implementations of the `Spanned` types for preserving this information are
91 // highly inconvenient (`serde_spanned`) and in some cases don't work at all here
92 // (`json_spanned_>value`). Serde should really have builtin support for this.
93}
94
95impl KeymapSection {
96 pub fn bindings(&self) -> impl DoubleEndedIterator<Item = (&String, &KeymapAction)> {
97 self.bindings.iter().flatten()
98 }
99}
100
101/// Keymap action as a JSON value, since it can either be null for no action, or the name of the
102/// action, or an array of the name of the action and the action input.
103///
104/// Unlike the other json types involved in keymaps (including actions), this doc-comment will not
105/// be included in the generated JSON schema, as it manually defines its `JsonSchema` impl. The
106/// actual schema used for it is automatically generated in `KeymapFile::generate_json_schema`.
107#[derive(Debug, Deserialize, Default, Clone)]
108#[serde(transparent)]
109pub struct KeymapAction(Value);
110
111impl std::fmt::Display for KeymapAction {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 match &self.0 {
114 Value::String(s) => write!(f, "{}", s),
115 Value::Array(arr) => {
116 let strings: Vec<String> = arr.iter().map(|v| v.to_string()).collect();
117 write!(f, "{}", strings.join(", "))
118 }
119 _ => write!(f, "{}", self.0),
120 }
121 }
122}
123
124impl JsonSchema for KeymapAction {
125 /// This is used when generating the JSON schema for the `KeymapAction` type, so that it can
126 /// reference the keymap action schema.
127 fn schema_name() -> Cow<'static, str> {
128 "KeymapAction".into()
129 }
130
131 /// This schema will be replaced with the full action schema in
132 /// `KeymapFile::generate_json_schema`.
133 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
134 json_schema!(true)
135 }
136}
137
138#[derive(Debug)]
139#[must_use]
140pub enum KeymapFileLoadResult {
141 Success {
142 key_bindings: Vec<KeyBinding>,
143 },
144 SomeFailedToLoad {
145 key_bindings: Vec<KeyBinding>,
146 error_message: MarkdownString,
147 },
148 JsonParseFailure {
149 error: anyhow::Error,
150 },
151}
152
153impl KeymapFile {
154 pub fn parse(content: &str) -> anyhow::Result<Self> {
155 if content.trim().is_empty() {
156 return Ok(Self(Vec::new()));
157 }
158 parse_json_with_comments::<Self>(content)
159 }
160
161 pub fn load_asset(
162 asset_path: &str,
163 source: Option<KeybindSource>,
164 cx: &App,
165 ) -> anyhow::Result<Vec<KeyBinding>> {
166 match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
167 KeymapFileLoadResult::Success { mut key_bindings } => match source {
168 Some(source) => Ok({
169 for key_binding in &mut key_bindings {
170 key_binding.set_meta(source.meta());
171 }
172 key_bindings
173 }),
174 None => Ok(key_bindings),
175 },
176 KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => {
177 anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}",)
178 }
179 KeymapFileLoadResult::JsonParseFailure { error } => {
180 anyhow::bail!("JSON parse error in built-in keymap \"{asset_path}\": {error}")
181 }
182 }
183 }
184
185 pub fn load_asset_allow_partial_failure(
186 asset_path: &str,
187 cx: &App,
188 ) -> anyhow::Result<Vec<KeyBinding>> {
189 match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
190 KeymapFileLoadResult::SomeFailedToLoad {
191 key_bindings,
192 error_message,
193 ..
194 } if key_bindings.is_empty() => {
195 anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}",)
196 }
197 KeymapFileLoadResult::Success { key_bindings, .. }
198 | KeymapFileLoadResult::SomeFailedToLoad { key_bindings, .. } => Ok(key_bindings),
199 KeymapFileLoadResult::JsonParseFailure { error } => {
200 anyhow::bail!("JSON parse error in built-in keymap \"{asset_path}\": {error}")
201 }
202 }
203 }
204
205 #[cfg(feature = "test-support")]
206 pub fn load_panic_on_failure(content: &str, cx: &App) -> Vec<KeyBinding> {
207 match Self::load(content, cx) {
208 KeymapFileLoadResult::Success { key_bindings, .. } => key_bindings,
209 KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => {
210 panic!("{error_message}");
211 }
212 KeymapFileLoadResult::JsonParseFailure { error } => {
213 panic!("JSON parse error: {error}");
214 }
215 }
216 }
217
218 pub fn load(content: &str, cx: &App) -> KeymapFileLoadResult {
219 let keymap_file = match Self::parse(content) {
220 Ok(keymap_file) => keymap_file,
221 Err(error) => {
222 return KeymapFileLoadResult::JsonParseFailure { error };
223 }
224 };
225
226 // Accumulate errors in order to support partial load of user keymap in the presence of
227 // errors in context and binding parsing.
228 let mut errors = Vec::new();
229 let mut key_bindings = Vec::new();
230
231 for KeymapSection {
232 context,
233 use_key_equivalents,
234 bindings,
235 unrecognized_fields,
236 } in keymap_file.0.iter()
237 {
238 let context_predicate: Option<Rc<KeyBindingContextPredicate>> = if context.is_empty() {
239 None
240 } else {
241 match KeyBindingContextPredicate::parse(context) {
242 Ok(context_predicate) => Some(context_predicate.into()),
243 Err(err) => {
244 // Leading space is to separate from the message indicating which section
245 // the error occurred in.
246 errors.push((
247 context,
248 format!(" Parse error in section `context` field: {}", err),
249 ));
250 continue;
251 }
252 }
253 };
254
255 let mut section_errors = String::new();
256
257 if !unrecognized_fields.is_empty() {
258 write!(
259 section_errors,
260 "\n\n - Unrecognized fields: {}",
261 MarkdownInlineCode(&format!("{:?}", unrecognized_fields.keys()))
262 )
263 .unwrap();
264 }
265
266 if let Some(bindings) = bindings {
267 for (keystrokes, action) in bindings {
268 let result = Self::load_keybinding(
269 keystrokes,
270 action,
271 context_predicate.clone(),
272 *use_key_equivalents,
273 cx,
274 );
275 match result {
276 Ok(key_binding) => {
277 key_bindings.push(key_binding);
278 }
279 Err(err) => {
280 let mut lines = err.lines();
281 let mut indented_err = lines.next().unwrap().to_string();
282 for line in lines {
283 indented_err.push_str(" ");
284 indented_err.push_str(line);
285 indented_err.push_str("\n");
286 }
287 write!(
288 section_errors,
289 "\n\n- In binding {}, {indented_err}",
290 MarkdownInlineCode(&format!("\"{}\"", keystrokes))
291 )
292 .unwrap();
293 }
294 }
295 }
296 }
297
298 if !section_errors.is_empty() {
299 errors.push((context, section_errors))
300 }
301 }
302
303 if errors.is_empty() {
304 KeymapFileLoadResult::Success { key_bindings }
305 } else {
306 let mut error_message = "Errors in user keymap file.".to_owned();
307
308 for (context, section_errors) in errors {
309 if context.is_empty() {
310 let _ = write!(error_message, "\nIn section without context predicate:");
311 } else {
312 let _ = write!(
313 error_message,
314 "\nIn section with {}:",
315 MarkdownInlineCode(&format!("context = \"{}\"", context))
316 );
317 }
318 let _ = write!(error_message, "{section_errors}");
319 }
320
321 KeymapFileLoadResult::SomeFailedToLoad {
322 key_bindings,
323 error_message: MarkdownString(error_message),
324 }
325 }
326 }
327
328 fn load_keybinding(
329 keystrokes: &str,
330 action: &KeymapAction,
331 context: Option<Rc<KeyBindingContextPredicate>>,
332 use_key_equivalents: bool,
333 cx: &App,
334 ) -> std::result::Result<KeyBinding, String> {
335 let (action, action_input_string) = Self::build_keymap_action(action, cx)?;
336
337 let key_binding = match KeyBinding::load(
338 keystrokes,
339 action,
340 context,
341 use_key_equivalents,
342 action_input_string.map(SharedString::from),
343 cx.keyboard_mapper().as_ref(),
344 ) {
345 Ok(key_binding) => key_binding,
346 Err(InvalidKeystrokeError { keystroke }) => {
347 return Err(format!(
348 "invalid keystroke {}. {}",
349 MarkdownInlineCode(&format!("\"{}\"", &keystroke)),
350 KEYSTROKE_PARSE_EXPECTED_MESSAGE
351 ));
352 }
353 };
354
355 if let Some(validator) = KEY_BINDING_VALIDATORS.get(&key_binding.action().type_id()) {
356 match validator.validate(&key_binding) {
357 Ok(()) => Ok(key_binding),
358 Err(error) => Err(error.0),
359 }
360 } else {
361 Ok(key_binding)
362 }
363 }
364
365 pub fn parse_action(
366 action: &KeymapAction,
367 ) -> Result<Option<(&String, Option<&Value>)>, String> {
368 let name_and_input = match &action.0 {
369 Value::Array(items) => {
370 if items.len() != 2 {
371 return Err(format!(
372 "expected two-element array of `[name, input]`. \
373 Instead found {}.",
374 MarkdownInlineCode(&action.0.to_string())
375 ));
376 }
377 let serde_json::Value::String(ref name) = items[0] else {
378 return Err(format!(
379 "expected two-element array of `[name, input]`, \
380 but the first element is not a string in {}.",
381 MarkdownInlineCode(&action.0.to_string())
382 ));
383 };
384 Some((name, Some(&items[1])))
385 }
386 Value::String(name) => Some((name, None)),
387 Value::Null => None,
388 _ => {
389 return Err(format!(
390 "expected two-element array of `[name, input]`. \
391 Instead found {}.",
392 MarkdownInlineCode(&action.0.to_string())
393 ));
394 }
395 };
396 Ok(name_and_input)
397 }
398
399 fn build_keymap_action(
400 action: &KeymapAction,
401 cx: &App,
402 ) -> std::result::Result<(Box<dyn Action>, Option<String>), String> {
403 let (build_result, action_input_string) = match Self::parse_action(action)? {
404 Some((name, action_input)) if name.as_str() == ActionSequence::name_for_type() => {
405 match action_input {
406 Some(action_input) => (
407 ActionSequence::build_sequence(action_input.clone(), cx),
408 None,
409 ),
410 None => (Err(ActionSequence::expected_array_error()), None),
411 }
412 }
413 Some((name, Some(action_input))) => {
414 let action_input_string = action_input.to_string();
415 (
416 cx.build_action(name, Some(action_input.clone())),
417 Some(action_input_string),
418 )
419 }
420 Some((name, None)) => (cx.build_action(name, None), None),
421 None => (Ok(NoAction.boxed_clone()), None),
422 };
423
424 let action = match build_result {
425 Ok(action) => action,
426 Err(ActionBuildError::NotFound { name }) => {
427 return Err(format!(
428 "didn't find an action named {}.",
429 MarkdownInlineCode(&format!("\"{}\"", &name))
430 ));
431 }
432 Err(ActionBuildError::BuildError { name, error }) => match action_input_string {
433 Some(action_input_string) => {
434 return Err(format!(
435 "can't build {} action from input value {}: {}",
436 MarkdownInlineCode(&format!("\"{}\"", &name)),
437 MarkdownInlineCode(&action_input_string),
438 MarkdownEscaped(&error.to_string())
439 ));
440 }
441 None => {
442 return Err(format!(
443 "can't build {} action - it requires input data via [name, input]: {}",
444 MarkdownInlineCode(&format!("\"{}\"", &name)),
445 MarkdownEscaped(&error.to_string())
446 ));
447 }
448 },
449 };
450
451 Ok((action, action_input_string))
452 }
453
454 /// Creates a JSON schema generator, suitable for generating json schemas
455 /// for actions
456 pub fn action_schema_generator() -> schemars::SchemaGenerator {
457 schemars::generate::SchemaSettings::draft2019_09()
458 .with_transform(AllowTrailingCommas)
459 .into_generator()
460 }
461
462 pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value {
463 // instead of using DefaultDenyUnknownFields, actions typically use
464 // `#[serde(deny_unknown_fields)]` so that these cases are reported as parse failures. This
465 // is because the rest of the keymap will still load in these cases, whereas other settings
466 // files would not.
467 let mut generator = Self::action_schema_generator();
468
469 let action_schemas = cx.action_schemas(&mut generator);
470 let action_documentation = cx.action_documentation();
471 let deprecations = cx.deprecated_actions_to_preferred_actions();
472 let deprecation_messages = cx.action_deprecation_messages();
473 KeymapFile::generate_json_schema(
474 generator,
475 action_schemas,
476 action_documentation,
477 deprecations,
478 deprecation_messages,
479 )
480 }
481
482 pub fn generate_json_schema_from_inventory() -> Value {
483 let mut generator = Self::action_schema_generator();
484
485 let mut action_schemas = Vec::new();
486 let mut documentation = HashMap::default();
487 let mut deprecations = HashMap::default();
488 let mut deprecation_messages = HashMap::default();
489
490 for action_data in generate_list_of_all_registered_actions() {
491 let schema = (action_data.json_schema)(&mut generator);
492 action_schemas.push((action_data.name, schema));
493
494 if let Some(doc) = action_data.documentation {
495 documentation.insert(action_data.name, doc);
496 }
497 if let Some(msg) = action_data.deprecation_message {
498 deprecation_messages.insert(action_data.name, msg);
499 }
500 for &alias in action_data.deprecated_aliases {
501 deprecations.insert(alias, action_data.name);
502
503 let alias_schema = (action_data.json_schema)(&mut generator);
504 action_schemas.push((alias, alias_schema));
505 }
506 }
507
508 KeymapFile::generate_json_schema(
509 generator,
510 action_schemas,
511 &documentation,
512 &deprecations,
513 &deprecation_messages,
514 )
515 }
516
517 pub fn get_action_schema_by_name(
518 action_name: &str,
519 generator: &mut schemars::SchemaGenerator,
520 ) -> Option<schemars::Schema> {
521 for action_data in generate_list_of_all_registered_actions() {
522 if action_data.name == action_name {
523 return (action_data.json_schema)(generator);
524 }
525 for &alias in action_data.deprecated_aliases {
526 if alias == action_name {
527 return (action_data.json_schema)(generator);
528 }
529 }
530 }
531 None
532 }
533
534 pub fn generate_json_schema<'a>(
535 mut generator: schemars::SchemaGenerator,
536 action_schemas: Vec<(&'a str, Option<schemars::Schema>)>,
537 action_documentation: &HashMap<&'a str, &'a str>,
538 deprecations: &HashMap<&'a str, &'a str>,
539 deprecation_messages: &HashMap<&'a str, &'a str>,
540 ) -> serde_json::Value {
541 fn add_deprecation(schema: &mut schemars::Schema, message: String) {
542 schema.insert(
543 // deprecationMessage is not part of the JSON Schema spec, but
544 // json-language-server recognizes it.
545 "deprecationMessage".to_string(),
546 Value::String(message),
547 );
548 }
549
550 fn add_deprecation_preferred_name(schema: &mut schemars::Schema, new_name: &str) {
551 add_deprecation(schema, format!("Deprecated, use {new_name}"));
552 }
553
554 fn add_description(schema: &mut schemars::Schema, description: &str) {
555 schema.insert(
556 "description".to_string(),
557 Value::String(description.to_string()),
558 );
559 }
560
561 let empty_object = json_schema!({
562 "type": "object"
563 });
564
565 // This is a workaround for a json-language-server issue where it matches the first
566 // alternative that matches the value's shape and uses that for documentation.
567 //
568 // In the case of the array validations, it would even provide an error saying that the name
569 // must match the name of the first alternative.
570 let mut empty_action_name = json_schema!({
571 "type": "string",
572 "const": ""
573 });
574 let no_action_message = "No action named this.";
575 add_description(&mut empty_action_name, no_action_message);
576 add_deprecation(&mut empty_action_name, no_action_message.to_string());
577 let empty_action_name_with_input = json_schema!({
578 "type": "array",
579 "items": [
580 empty_action_name,
581 true
582 ],
583 "minItems": 2,
584 "maxItems": 2
585 });
586 let mut keymap_action_alternatives = vec![empty_action_name, empty_action_name_with_input];
587
588 let mut empty_schema_action_names = vec![];
589 for (name, action_schema) in action_schemas.into_iter() {
590 let deprecation = if name == NoAction.name() {
591 Some("null")
592 } else {
593 deprecations.get(name).copied()
594 };
595
596 // Add an alternative for plain action names.
597 let mut plain_action = json_schema!({
598 "type": "string",
599 "const": name
600 });
601 if let Some(message) = deprecation_messages.get(name) {
602 add_deprecation(&mut plain_action, message.to_string());
603 } else if let Some(new_name) = deprecation {
604 add_deprecation_preferred_name(&mut plain_action, new_name);
605 }
606 let description = action_documentation.get(name);
607 if let Some(description) = &description {
608 add_description(&mut plain_action, description);
609 }
610 keymap_action_alternatives.push(plain_action);
611
612 // Add an alternative for actions with data specified as a [name, data] array.
613 //
614 // When a struct with no deserializable fields is added by deriving `Action`, an empty
615 // object schema is produced. The action should be invoked without data in this case.
616 if let Some(schema) = action_schema
617 && schema != empty_object
618 {
619 let mut matches_action_name = json_schema!({
620 "const": name
621 });
622 if let Some(description) = &description {
623 add_description(&mut matches_action_name, description);
624 }
625 if let Some(message) = deprecation_messages.get(name) {
626 add_deprecation(&mut matches_action_name, message.to_string());
627 } else if let Some(new_name) = deprecation {
628 add_deprecation_preferred_name(&mut matches_action_name, new_name);
629 }
630 let action_with_input = json_schema!({
631 "type": "array",
632 "items": [matches_action_name, schema],
633 "minItems": 2,
634 "maxItems": 2
635 });
636 keymap_action_alternatives.push(action_with_input);
637 } else {
638 empty_schema_action_names.push(name);
639 }
640 }
641
642 if !empty_schema_action_names.is_empty() {
643 let action_names = json_schema!({ "enum": empty_schema_action_names });
644 let no_properties_allowed = json_schema!({
645 "type": "object",
646 "additionalProperties": false
647 });
648 let mut actions_with_empty_input = json_schema!({
649 "type": "array",
650 "items": [action_names, no_properties_allowed],
651 "minItems": 2,
652 "maxItems": 2
653 });
654 add_deprecation(
655 &mut actions_with_empty_input,
656 "This action does not take input - just the action name string should be used."
657 .to_string(),
658 );
659 keymap_action_alternatives.push(actions_with_empty_input);
660 }
661
662 // Placing null first causes json-language-server to default assuming actions should be
663 // null, so place it last.
664 keymap_action_alternatives.push(json_schema!({
665 "type": "null"
666 }));
667
668 // The `KeymapSection` schema will reference the `KeymapAction` schema by name, so setting
669 // the definition of `KeymapAction` results in the full action schema being used.
670 generator.definitions_mut().insert(
671 KeymapAction::schema_name().to_string(),
672 json!({
673 "anyOf": keymap_action_alternatives
674 }),
675 );
676
677 generator.root_schema_for::<KeymapFile>().to_value()
678 }
679
680 pub fn sections(&self) -> impl DoubleEndedIterator<Item = &KeymapSection> {
681 self.0.iter()
682 }
683
684 pub async fn load_keymap_file(fs: &Arc<dyn Fs>) -> Result<String> {
685 match fs.load(paths::keymap_file()).await {
686 result @ Ok(_) => result,
687 Err(err) => {
688 if let Some(e) = err.downcast_ref::<std::io::Error>()
689 && e.kind() == std::io::ErrorKind::NotFound
690 {
691 return Ok(crate::initial_keymap_content().to_string());
692 }
693 Err(err)
694 }
695 }
696 }
697
698 pub fn update_keybinding<'a>(
699 mut operation: KeybindUpdateOperation<'a>,
700 mut keymap_contents: String,
701 tab_size: usize,
702 keyboard_mapper: &dyn gpui::PlatformKeyboardMapper,
703 ) -> Result<String> {
704 // When replacing a non-user binding's keystroke, we need to also suppress the old
705 // default so it doesn't continue showing under the old keystroke.
706 let mut old_keystroke_suppression: Option<(Option<String>, String)> = None;
707
708 match operation {
709 // if trying to replace a keybinding that is not user-defined, treat it as an add operation
710 KeybindUpdateOperation::Replace {
711 target_keybind_source: target_source,
712 source,
713 target,
714 } if target_source != KeybindSource::User => {
715 if target.keystrokes_unparsed() != source.keystrokes_unparsed() {
716 old_keystroke_suppression = Some((
717 target.context.map(String::from),
718 target.keystrokes_unparsed(),
719 ));
720 }
721 operation = KeybindUpdateOperation::Add {
722 source,
723 from: Some(target),
724 };
725 }
726 // if trying to remove a keybinding that is not user-defined, treat it as creating a binding
727 // that binds it to `zed::NoAction`
728 KeybindUpdateOperation::Remove {
729 target,
730 target_keybind_source,
731 } if target_keybind_source != KeybindSource::User => {
732 let mut source = target.clone();
733 source.action_name = gpui::NoAction.name();
734 source.action_arguments.take();
735 operation = KeybindUpdateOperation::Add {
736 source,
737 from: Some(target),
738 };
739 }
740 _ => {}
741 }
742
743 // Sanity check that keymap contents are valid, even though we only use it for Replace.
744 // We don't want to modify the file if it's invalid.
745 let keymap = Self::parse(&keymap_contents).context("Failed to parse keymap")?;
746
747 if let KeybindUpdateOperation::Remove { target, .. } = operation {
748 let target_action_value = target
749 .action_value()
750 .context("Failed to generate target action JSON value")?;
751 let Some((index, keystrokes_str)) =
752 find_binding(&keymap, &target, &target_action_value, keyboard_mapper)
753 else {
754 anyhow::bail!("Failed to find keybinding to remove");
755 };
756 let is_only_binding = keymap.0[index]
757 .bindings
758 .as_ref()
759 .is_none_or(|bindings| bindings.len() == 1);
760 let key_path: &[&str] = if is_only_binding {
761 &[]
762 } else {
763 &["bindings", keystrokes_str]
764 };
765 let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
766 &keymap_contents,
767 key_path,
768 None,
769 None,
770 index,
771 tab_size,
772 );
773 keymap_contents.replace_range(replace_range, &replace_value);
774 return Ok(keymap_contents);
775 }
776
777 if let KeybindUpdateOperation::Replace { source, target, .. } = operation {
778 let target_action_value = target
779 .action_value()
780 .context("Failed to generate target action JSON value")?;
781 let source_action_value = source
782 .action_value()
783 .context("Failed to generate source action JSON value")?;
784
785 if let Some((index, keystrokes_str)) =
786 find_binding(&keymap, &target, &target_action_value, keyboard_mapper)
787 {
788 if target.context == source.context {
789 // if we are only changing the keybinding (common case)
790 // not the context, etc. Then just update the binding in place
791
792 let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
793 &keymap_contents,
794 &["bindings", keystrokes_str],
795 Some(&source_action_value),
796 Some(&source.keystrokes_unparsed()),
797 index,
798 tab_size,
799 );
800 keymap_contents.replace_range(replace_range, &replace_value);
801
802 return Ok(keymap_contents);
803 } else if keymap.0[index]
804 .bindings
805 .as_ref()
806 .is_none_or(|bindings| bindings.len() == 1)
807 {
808 // if we are replacing the only binding in the section,
809 // just update the section in place, updating the context
810 // and the binding
811
812 let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
813 &keymap_contents,
814 &["bindings", keystrokes_str],
815 Some(&source_action_value),
816 Some(&source.keystrokes_unparsed()),
817 index,
818 tab_size,
819 );
820 keymap_contents.replace_range(replace_range, &replace_value);
821
822 let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
823 &keymap_contents,
824 &["context"],
825 source.context.map(Into::into).as_ref(),
826 None,
827 index,
828 tab_size,
829 );
830 keymap_contents.replace_range(replace_range, &replace_value);
831 return Ok(keymap_contents);
832 } else {
833 // if we are replacing one of multiple bindings in a section
834 // with a context change, remove the existing binding from the
835 // section, then treat this operation as an add operation of the
836 // new binding with the updated context.
837
838 let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
839 &keymap_contents,
840 &["bindings", keystrokes_str],
841 None,
842 None,
843 index,
844 tab_size,
845 );
846 keymap_contents.replace_range(replace_range, &replace_value);
847 operation = KeybindUpdateOperation::Add {
848 source,
849 from: Some(target),
850 };
851 }
852 } else {
853 log::warn!(
854 "Failed to find keybinding to update `{:?} -> {}` creating new binding for `{:?} -> {}` instead",
855 target.keystrokes,
856 target_action_value,
857 source.keystrokes,
858 source_action_value,
859 );
860 operation = KeybindUpdateOperation::Add {
861 source,
862 from: Some(target),
863 };
864 }
865 }
866
867 if let KeybindUpdateOperation::Add {
868 source: keybinding,
869 from,
870 } = operation
871 {
872 let mut value = serde_json::Map::with_capacity(4);
873 if let Some(context) = keybinding.context {
874 value.insert("context".to_string(), context.into());
875 }
876 let use_key_equivalents = from.and_then(|from| {
877 let action_value = from.action_value().context("Failed to serialize action value. `use_key_equivalents` on new keybinding may be incorrect.").log_err()?;
878 let (index, _) = find_binding(&keymap, &from, &action_value, keyboard_mapper)?;
879 Some(keymap.0[index].use_key_equivalents)
880 }).unwrap_or(false);
881 if use_key_equivalents {
882 value.insert("use_key_equivalents".to_string(), true.into());
883 }
884
885 value.insert("bindings".to_string(), {
886 let mut bindings = serde_json::Map::new();
887 let action = keybinding.action_value()?;
888 bindings.insert(keybinding.keystrokes_unparsed(), action);
889 bindings.into()
890 });
891
892 let (replace_range, replace_value) = append_top_level_array_value_in_json_text(
893 &keymap_contents,
894 &value.into(),
895 tab_size,
896 );
897 keymap_contents.replace_range(replace_range, &replace_value);
898 }
899
900 // If we converted a Replace to Add because the target was a non-user binding,
901 // and the keystroke changed, suppress the old default keystroke with a NoAction
902 // binding so it doesn't continue appearing under the old keystroke.
903 if let Some((context, old_keystrokes)) = old_keystroke_suppression {
904 let mut value = serde_json::Map::with_capacity(2);
905 if let Some(context) = context {
906 value.insert("context".to_string(), context.into());
907 }
908 value.insert("bindings".to_string(), {
909 let mut bindings = serde_json::Map::new();
910 bindings.insert(old_keystrokes, Value::Null);
911 bindings.into()
912 });
913 let (replace_range, replace_value) = append_top_level_array_value_in_json_text(
914 &keymap_contents,
915 &value.into(),
916 tab_size,
917 );
918 keymap_contents.replace_range(replace_range, &replace_value);
919 }
920
921 return Ok(keymap_contents);
922
923 fn find_binding<'a, 'b>(
924 keymap: &'b KeymapFile,
925 target: &KeybindUpdateTarget<'a>,
926 target_action_value: &Value,
927 keyboard_mapper: &dyn gpui::PlatformKeyboardMapper,
928 ) -> Option<(usize, &'b str)> {
929 let target_context_parsed =
930 KeyBindingContextPredicate::parse(target.context.unwrap_or("")).ok();
931 for (index, section) in keymap.sections().enumerate() {
932 let section_context_parsed =
933 KeyBindingContextPredicate::parse(§ion.context).ok();
934 if section_context_parsed != target_context_parsed {
935 continue;
936 }
937 let Some(bindings) = §ion.bindings else {
938 continue;
939 };
940 for (keystrokes_str, action) in bindings {
941 let Ok(keystrokes) = keystrokes_str
942 .split_whitespace()
943 .map(|source| {
944 let keystroke = Keystroke::parse(source)?;
945 Ok(KeybindingKeystroke::new_with_mapper(
946 keystroke,
947 false,
948 keyboard_mapper,
949 ))
950 })
951 .collect::<Result<Vec<_>, InvalidKeystrokeError>>()
952 else {
953 continue;
954 };
955 if keystrokes.len() != target.keystrokes.len()
956 || !keystrokes
957 .iter()
958 .zip(target.keystrokes)
959 .all(|(a, b)| a.inner().should_match(b))
960 {
961 continue;
962 }
963 if &action.0 != target_action_value {
964 continue;
965 }
966 return Some((index, keystrokes_str));
967 }
968 }
969 None
970 }
971 }
972}
973
974#[derive(Clone, Debug)]
975pub enum KeybindUpdateOperation<'a> {
976 Replace {
977 /// Describes the keybind to create
978 source: KeybindUpdateTarget<'a>,
979 /// Describes the keybind to remove
980 target: KeybindUpdateTarget<'a>,
981 target_keybind_source: KeybindSource,
982 },
983 Add {
984 source: KeybindUpdateTarget<'a>,
985 from: Option<KeybindUpdateTarget<'a>>,
986 },
987 Remove {
988 target: KeybindUpdateTarget<'a>,
989 target_keybind_source: KeybindSource,
990 },
991}
992
993impl KeybindUpdateOperation<'_> {
994 pub fn generate_telemetry(
995 &self,
996 ) -> (
997 // The keybind that is created
998 String,
999 // The keybinding that was removed
1000 String,
1001 // The source of the keybinding
1002 String,
1003 ) {
1004 let (new_binding, removed_binding, source) = match &self {
1005 KeybindUpdateOperation::Replace {
1006 source,
1007 target,
1008 target_keybind_source,
1009 } => (Some(source), Some(target), Some(*target_keybind_source)),
1010 KeybindUpdateOperation::Add { source, .. } => (Some(source), None, None),
1011 KeybindUpdateOperation::Remove {
1012 target,
1013 target_keybind_source,
1014 } => (None, Some(target), Some(*target_keybind_source)),
1015 };
1016
1017 let new_binding = new_binding
1018 .map(KeybindUpdateTarget::telemetry_string)
1019 .unwrap_or("null".to_owned());
1020 let removed_binding = removed_binding
1021 .map(KeybindUpdateTarget::telemetry_string)
1022 .unwrap_or("null".to_owned());
1023
1024 let source = source
1025 .as_ref()
1026 .map(KeybindSource::name)
1027 .map(ToOwned::to_owned)
1028 .unwrap_or("null".to_owned());
1029
1030 (new_binding, removed_binding, source)
1031 }
1032}
1033
1034impl<'a> KeybindUpdateOperation<'a> {
1035 pub fn add(source: KeybindUpdateTarget<'a>) -> Self {
1036 Self::Add { source, from: None }
1037 }
1038}
1039
1040#[derive(Debug, Clone)]
1041pub struct KeybindUpdateTarget<'a> {
1042 pub context: Option<&'a str>,
1043 pub keystrokes: &'a [KeybindingKeystroke],
1044 pub action_name: &'a str,
1045 pub action_arguments: Option<&'a str>,
1046}
1047
1048impl<'a> KeybindUpdateTarget<'a> {
1049 fn action_value(&self) -> Result<Value> {
1050 if self.action_name == gpui::NoAction.name() {
1051 return Ok(Value::Null);
1052 }
1053 let action_name: Value = self.action_name.into();
1054 let value = match self.action_arguments {
1055 Some(args) if !args.is_empty() => {
1056 let args = serde_json::from_str::<Value>(args)
1057 .context("Failed to parse action arguments as JSON")?;
1058 serde_json::json!([action_name, args])
1059 }
1060 _ => action_name,
1061 };
1062 Ok(value)
1063 }
1064
1065 fn keystrokes_unparsed(&self) -> String {
1066 let mut keystrokes = String::with_capacity(self.keystrokes.len() * 8);
1067 for keystroke in self.keystrokes {
1068 // The reason use `keystroke.unparse()` instead of `keystroke.inner.unparse()`
1069 // here is that, we want the user to use `ctrl-shift-4` instead of `ctrl-$`
1070 // by default on Windows.
1071 keystrokes.push_str(&keystroke.unparse());
1072 keystrokes.push(' ');
1073 }
1074 keystrokes.pop();
1075 keystrokes
1076 }
1077
1078 fn telemetry_string(&self) -> String {
1079 format!(
1080 "action_name: {}, context: {}, action_arguments: {}, keystrokes: {}",
1081 self.action_name,
1082 self.context.unwrap_or("global"),
1083 self.action_arguments.unwrap_or("none"),
1084 self.keystrokes_unparsed()
1085 )
1086 }
1087}
1088
1089#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug)]
1090pub enum KeybindSource {
1091 User,
1092 Vim,
1093 Base,
1094 #[default]
1095 Default,
1096 Unknown,
1097}
1098
1099impl KeybindSource {
1100 const BASE: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Base as u32);
1101 const DEFAULT: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Default as u32);
1102 const VIM: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Vim as u32);
1103 const USER: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::User as u32);
1104
1105 pub fn name(&self) -> &'static str {
1106 match self {
1107 KeybindSource::User => "User",
1108 KeybindSource::Default => "Default",
1109 KeybindSource::Base => "Base",
1110 KeybindSource::Vim => "Vim",
1111 KeybindSource::Unknown => "Unknown",
1112 }
1113 }
1114
1115 pub fn meta(&self) -> KeyBindingMetaIndex {
1116 match self {
1117 KeybindSource::User => Self::USER,
1118 KeybindSource::Default => Self::DEFAULT,
1119 KeybindSource::Base => Self::BASE,
1120 KeybindSource::Vim => Self::VIM,
1121 KeybindSource::Unknown => KeyBindingMetaIndex(*self as u32),
1122 }
1123 }
1124
1125 pub fn from_meta(index: KeyBindingMetaIndex) -> Self {
1126 match index {
1127 Self::USER => KeybindSource::User,
1128 Self::BASE => KeybindSource::Base,
1129 Self::DEFAULT => KeybindSource::Default,
1130 Self::VIM => KeybindSource::Vim,
1131 _ => KeybindSource::Unknown,
1132 }
1133 }
1134}
1135
1136impl From<KeyBindingMetaIndex> for KeybindSource {
1137 fn from(index: KeyBindingMetaIndex) -> Self {
1138 Self::from_meta(index)
1139 }
1140}
1141
1142impl From<KeybindSource> for KeyBindingMetaIndex {
1143 fn from(source: KeybindSource) -> Self {
1144 source.meta()
1145 }
1146}
1147
1148/// Runs a sequence of actions. Does not wait for asynchronous actions to complete before running
1149/// the next action. Currently only works in workspace windows.
1150///
1151/// This action is special-cased in keymap parsing to allow it to access `App` while parsing, so
1152/// that it can parse its input actions.
1153pub struct ActionSequence(pub Vec<Box<dyn Action>>);
1154
1155register_action!(ActionSequence);
1156
1157impl ActionSequence {
1158 fn build_sequence(
1159 value: Value,
1160 cx: &App,
1161 ) -> std::result::Result<Box<dyn Action>, ActionBuildError> {
1162 match value {
1163 Value::Array(values) => {
1164 let actions = values
1165 .into_iter()
1166 .enumerate()
1167 .map(|(index, action)| {
1168 match KeymapFile::build_keymap_action(&KeymapAction(action), cx) {
1169 Ok((action, _)) => Ok(action),
1170 Err(err) => {
1171 return Err(ActionBuildError::BuildError {
1172 name: Self::name_for_type().to_string(),
1173 error: anyhow::anyhow!(
1174 "error at sequence index {index}: {err}"
1175 ),
1176 });
1177 }
1178 }
1179 })
1180 .collect::<Result<Vec<_>, _>>()?;
1181 Ok(Box::new(Self(actions)))
1182 }
1183 _ => Err(Self::expected_array_error()),
1184 }
1185 }
1186
1187 fn expected_array_error() -> ActionBuildError {
1188 ActionBuildError::BuildError {
1189 name: Self::name_for_type().to_string(),
1190 error: anyhow::anyhow!("expected array of actions"),
1191 }
1192 }
1193}
1194
1195impl Action for ActionSequence {
1196 fn name(&self) -> &'static str {
1197 Self::name_for_type()
1198 }
1199
1200 fn name_for_type() -> &'static str
1201 where
1202 Self: Sized,
1203 {
1204 "action::Sequence"
1205 }
1206
1207 fn partial_eq(&self, action: &dyn Action) -> bool {
1208 action
1209 .as_any()
1210 .downcast_ref::<Self>()
1211 .map_or(false, |other| {
1212 self.0.len() == other.0.len()
1213 && self
1214 .0
1215 .iter()
1216 .zip(other.0.iter())
1217 .all(|(a, b)| a.partial_eq(b.as_ref()))
1218 })
1219 }
1220
1221 fn boxed_clone(&self) -> Box<dyn Action> {
1222 Box::new(ActionSequence(
1223 self.0
1224 .iter()
1225 .map(|action| action.boxed_clone())
1226 .collect::<Vec<_>>(),
1227 ))
1228 }
1229
1230 fn build(_value: Value) -> Result<Box<dyn Action>> {
1231 Err(anyhow::anyhow!(
1232 "{} cannot be built directly",
1233 Self::name_for_type()
1234 ))
1235 }
1236
1237 fn action_json_schema(generator: &mut schemars::SchemaGenerator) -> Option<schemars::Schema> {
1238 let keymap_action_schema = generator.subschema_for::<KeymapAction>();
1239 Some(json_schema!({
1240 "type": "array",
1241 "items": keymap_action_schema
1242 }))
1243 }
1244
1245 fn deprecated_aliases() -> &'static [&'static str] {
1246 &[]
1247 }
1248
1249 fn deprecation_message() -> Option<&'static str> {
1250 None
1251 }
1252
1253 fn documentation() -> Option<&'static str> {
1254 Some(
1255 "Runs a sequence of actions.\n\n\
1256 NOTE: This does **not** wait for asynchronous actions to complete before running the next action.",
1257 )
1258 }
1259}
1260
1261#[cfg(test)]
1262mod tests {
1263 use gpui::{DummyKeyboardMapper, KeybindingKeystroke, Keystroke};
1264 use unindent::Unindent;
1265
1266 use crate::{
1267 KeybindSource, KeymapFile,
1268 keymap_file::{KeybindUpdateOperation, KeybindUpdateTarget},
1269 };
1270
1271 #[test]
1272 fn can_deserialize_keymap_with_trailing_comma() {
1273 let json = indoc::indoc! {"[
1274 // Standard macOS bindings
1275 {
1276 \"bindings\": {
1277 \"up\": \"menu::SelectPrevious\",
1278 },
1279 },
1280 ]
1281 "
1282 };
1283 KeymapFile::parse(json).unwrap();
1284 }
1285
1286 #[track_caller]
1287 fn check_keymap_update(
1288 input: impl ToString,
1289 operation: KeybindUpdateOperation,
1290 expected: impl ToString,
1291 ) {
1292 let result = KeymapFile::update_keybinding(
1293 operation,
1294 input.to_string(),
1295 4,
1296 &gpui::DummyKeyboardMapper,
1297 )
1298 .expect("Update succeeded");
1299 pretty_assertions::assert_eq!(expected.to_string(), result);
1300 }
1301
1302 #[track_caller]
1303 fn parse_keystrokes(keystrokes: &str) -> Vec<KeybindingKeystroke> {
1304 keystrokes
1305 .split(' ')
1306 .map(|s| {
1307 KeybindingKeystroke::new_with_mapper(
1308 Keystroke::parse(s).expect("Keystrokes valid"),
1309 false,
1310 &DummyKeyboardMapper,
1311 )
1312 })
1313 .collect()
1314 }
1315
1316 #[test]
1317 fn keymap_update() {
1318 zlog::init_test();
1319
1320 check_keymap_update(
1321 "[]",
1322 KeybindUpdateOperation::add(KeybindUpdateTarget {
1323 keystrokes: &parse_keystrokes("ctrl-a"),
1324 action_name: "zed::SomeAction",
1325 context: None,
1326 action_arguments: None,
1327 }),
1328 r#"[
1329 {
1330 "bindings": {
1331 "ctrl-a": "zed::SomeAction"
1332 }
1333 }
1334 ]"#
1335 .unindent(),
1336 );
1337
1338 check_keymap_update(
1339 "[]",
1340 KeybindUpdateOperation::add(KeybindUpdateTarget {
1341 keystrokes: &parse_keystrokes("\\ a"),
1342 action_name: "zed::SomeAction",
1343 context: None,
1344 action_arguments: None,
1345 }),
1346 r#"[
1347 {
1348 "bindings": {
1349 "\\ a": "zed::SomeAction"
1350 }
1351 }
1352 ]"#
1353 .unindent(),
1354 );
1355
1356 check_keymap_update(
1357 "[]",
1358 KeybindUpdateOperation::add(KeybindUpdateTarget {
1359 keystrokes: &parse_keystrokes("ctrl-a"),
1360 action_name: "zed::SomeAction",
1361 context: None,
1362 action_arguments: Some(""),
1363 }),
1364 r#"[
1365 {
1366 "bindings": {
1367 "ctrl-a": "zed::SomeAction"
1368 }
1369 }
1370 ]"#
1371 .unindent(),
1372 );
1373
1374 check_keymap_update(
1375 r#"[
1376 {
1377 "bindings": {
1378 "ctrl-a": "zed::SomeAction"
1379 }
1380 }
1381 ]"#
1382 .unindent(),
1383 KeybindUpdateOperation::add(KeybindUpdateTarget {
1384 keystrokes: &parse_keystrokes("ctrl-b"),
1385 action_name: "zed::SomeOtherAction",
1386 context: None,
1387 action_arguments: None,
1388 }),
1389 r#"[
1390 {
1391 "bindings": {
1392 "ctrl-a": "zed::SomeAction"
1393 }
1394 },
1395 {
1396 "bindings": {
1397 "ctrl-b": "zed::SomeOtherAction"
1398 }
1399 }
1400 ]"#
1401 .unindent(),
1402 );
1403
1404 check_keymap_update(
1405 r#"[
1406 {
1407 "bindings": {
1408 "ctrl-a": "zed::SomeAction"
1409 }
1410 }
1411 ]"#
1412 .unindent(),
1413 KeybindUpdateOperation::add(KeybindUpdateTarget {
1414 keystrokes: &parse_keystrokes("ctrl-b"),
1415 action_name: "zed::SomeOtherAction",
1416 context: None,
1417 action_arguments: Some(r#"{"foo": "bar"}"#),
1418 }),
1419 r#"[
1420 {
1421 "bindings": {
1422 "ctrl-a": "zed::SomeAction"
1423 }
1424 },
1425 {
1426 "bindings": {
1427 "ctrl-b": [
1428 "zed::SomeOtherAction",
1429 {
1430 "foo": "bar"
1431 }
1432 ]
1433 }
1434 }
1435 ]"#
1436 .unindent(),
1437 );
1438
1439 check_keymap_update(
1440 r#"[
1441 {
1442 "bindings": {
1443 "ctrl-a": "zed::SomeAction"
1444 }
1445 }
1446 ]"#
1447 .unindent(),
1448 KeybindUpdateOperation::add(KeybindUpdateTarget {
1449 keystrokes: &parse_keystrokes("ctrl-b"),
1450 action_name: "zed::SomeOtherAction",
1451 context: Some("Zed > Editor && some_condition = true"),
1452 action_arguments: Some(r#"{"foo": "bar"}"#),
1453 }),
1454 r#"[
1455 {
1456 "bindings": {
1457 "ctrl-a": "zed::SomeAction"
1458 }
1459 },
1460 {
1461 "context": "Zed > Editor && some_condition = true",
1462 "bindings": {
1463 "ctrl-b": [
1464 "zed::SomeOtherAction",
1465 {
1466 "foo": "bar"
1467 }
1468 ]
1469 }
1470 }
1471 ]"#
1472 .unindent(),
1473 );
1474
1475 check_keymap_update(
1476 r#"[
1477 {
1478 "bindings": {
1479 "ctrl-a": "zed::SomeAction"
1480 }
1481 }
1482 ]"#
1483 .unindent(),
1484 KeybindUpdateOperation::Replace {
1485 target: KeybindUpdateTarget {
1486 keystrokes: &parse_keystrokes("ctrl-a"),
1487 action_name: "zed::SomeAction",
1488 context: None,
1489 action_arguments: None,
1490 },
1491 source: KeybindUpdateTarget {
1492 keystrokes: &parse_keystrokes("ctrl-b"),
1493 action_name: "zed::SomeOtherAction",
1494 context: None,
1495 action_arguments: Some(r#"{"foo": "bar"}"#),
1496 },
1497 target_keybind_source: KeybindSource::Base,
1498 },
1499 r#"[
1500 {
1501 "bindings": {
1502 "ctrl-a": "zed::SomeAction"
1503 }
1504 },
1505 {
1506 "bindings": {
1507 "ctrl-b": [
1508 "zed::SomeOtherAction",
1509 {
1510 "foo": "bar"
1511 }
1512 ]
1513 }
1514 },
1515 {
1516 "bindings": {
1517 "ctrl-a": null
1518 }
1519 }
1520 ]"#
1521 .unindent(),
1522 );
1523
1524 // Replacing a non-user binding without changing the keystroke should
1525 // not produce a NoAction suppression entry.
1526 check_keymap_update(
1527 r#"[
1528 {
1529 "bindings": {
1530 "ctrl-a": "zed::SomeAction"
1531 }
1532 }
1533 ]"#
1534 .unindent(),
1535 KeybindUpdateOperation::Replace {
1536 target: KeybindUpdateTarget {
1537 keystrokes: &parse_keystrokes("ctrl-a"),
1538 action_name: "zed::SomeAction",
1539 context: None,
1540 action_arguments: None,
1541 },
1542 source: KeybindUpdateTarget {
1543 keystrokes: &parse_keystrokes("ctrl-a"),
1544 action_name: "zed::SomeOtherAction",
1545 context: None,
1546 action_arguments: None,
1547 },
1548 target_keybind_source: KeybindSource::Base,
1549 },
1550 r#"[
1551 {
1552 "bindings": {
1553 "ctrl-a": "zed::SomeAction"
1554 }
1555 },
1556 {
1557 "bindings": {
1558 "ctrl-a": "zed::SomeOtherAction"
1559 }
1560 }
1561 ]"#
1562 .unindent(),
1563 );
1564
1565 // Replacing a non-user binding with a context and a keystroke change
1566 // should produce a suppression entry that preserves the context.
1567 check_keymap_update(
1568 r#"[
1569 {
1570 "context": "SomeContext",
1571 "bindings": {
1572 "ctrl-a": "zed::SomeAction"
1573 }
1574 }
1575 ]"#
1576 .unindent(),
1577 KeybindUpdateOperation::Replace {
1578 target: KeybindUpdateTarget {
1579 keystrokes: &parse_keystrokes("ctrl-a"),
1580 action_name: "zed::SomeAction",
1581 context: Some("SomeContext"),
1582 action_arguments: None,
1583 },
1584 source: KeybindUpdateTarget {
1585 keystrokes: &parse_keystrokes("ctrl-b"),
1586 action_name: "zed::SomeOtherAction",
1587 context: Some("SomeContext"),
1588 action_arguments: None,
1589 },
1590 target_keybind_source: KeybindSource::Default,
1591 },
1592 r#"[
1593 {
1594 "context": "SomeContext",
1595 "bindings": {
1596 "ctrl-a": "zed::SomeAction"
1597 }
1598 },
1599 {
1600 "context": "SomeContext",
1601 "bindings": {
1602 "ctrl-b": "zed::SomeOtherAction"
1603 }
1604 },
1605 {
1606 "context": "SomeContext",
1607 "bindings": {
1608 "ctrl-a": null
1609 }
1610 }
1611 ]"#
1612 .unindent(),
1613 );
1614
1615 check_keymap_update(
1616 r#"[
1617 {
1618 "bindings": {
1619 "a": "zed::SomeAction"
1620 }
1621 }
1622 ]"#
1623 .unindent(),
1624 KeybindUpdateOperation::Replace {
1625 target: KeybindUpdateTarget {
1626 keystrokes: &parse_keystrokes("a"),
1627 action_name: "zed::SomeAction",
1628 context: None,
1629 action_arguments: None,
1630 },
1631 source: KeybindUpdateTarget {
1632 keystrokes: &parse_keystrokes("ctrl-b"),
1633 action_name: "zed::SomeOtherAction",
1634 context: None,
1635 action_arguments: Some(r#"{"foo": "bar"}"#),
1636 },
1637 target_keybind_source: KeybindSource::User,
1638 },
1639 r#"[
1640 {
1641 "bindings": {
1642 "ctrl-b": [
1643 "zed::SomeOtherAction",
1644 {
1645 "foo": "bar"
1646 }
1647 ]
1648 }
1649 }
1650 ]"#
1651 .unindent(),
1652 );
1653
1654 check_keymap_update(
1655 r#"[
1656 {
1657 "bindings": {
1658 "\\ a": "zed::SomeAction"
1659 }
1660 }
1661 ]"#
1662 .unindent(),
1663 KeybindUpdateOperation::Replace {
1664 target: KeybindUpdateTarget {
1665 keystrokes: &parse_keystrokes("\\ a"),
1666 action_name: "zed::SomeAction",
1667 context: None,
1668 action_arguments: None,
1669 },
1670 source: KeybindUpdateTarget {
1671 keystrokes: &parse_keystrokes("\\ b"),
1672 action_name: "zed::SomeOtherAction",
1673 context: None,
1674 action_arguments: Some(r#"{"foo": "bar"}"#),
1675 },
1676 target_keybind_source: KeybindSource::User,
1677 },
1678 r#"[
1679 {
1680 "bindings": {
1681 "\\ b": [
1682 "zed::SomeOtherAction",
1683 {
1684 "foo": "bar"
1685 }
1686 ]
1687 }
1688 }
1689 ]"#
1690 .unindent(),
1691 );
1692
1693 check_keymap_update(
1694 r#"[
1695 {
1696 "bindings": {
1697 "\\ a": "zed::SomeAction"
1698 }
1699 }
1700 ]"#
1701 .unindent(),
1702 KeybindUpdateOperation::Replace {
1703 target: KeybindUpdateTarget {
1704 keystrokes: &parse_keystrokes("\\ a"),
1705 action_name: "zed::SomeAction",
1706 context: None,
1707 action_arguments: None,
1708 },
1709 source: KeybindUpdateTarget {
1710 keystrokes: &parse_keystrokes("\\ a"),
1711 action_name: "zed::SomeAction",
1712 context: None,
1713 action_arguments: None,
1714 },
1715 target_keybind_source: KeybindSource::User,
1716 },
1717 r#"[
1718 {
1719 "bindings": {
1720 "\\ a": "zed::SomeAction"
1721 }
1722 }
1723 ]"#
1724 .unindent(),
1725 );
1726
1727 check_keymap_update(
1728 r#"[
1729 {
1730 "bindings": {
1731 "ctrl-a": "zed::SomeAction"
1732 }
1733 }
1734 ]"#
1735 .unindent(),
1736 KeybindUpdateOperation::Replace {
1737 target: KeybindUpdateTarget {
1738 keystrokes: &parse_keystrokes("ctrl-a"),
1739 action_name: "zed::SomeNonexistentAction",
1740 context: None,
1741 action_arguments: None,
1742 },
1743 source: KeybindUpdateTarget {
1744 keystrokes: &parse_keystrokes("ctrl-b"),
1745 action_name: "zed::SomeOtherAction",
1746 context: None,
1747 action_arguments: None,
1748 },
1749 target_keybind_source: KeybindSource::User,
1750 },
1751 r#"[
1752 {
1753 "bindings": {
1754 "ctrl-a": "zed::SomeAction"
1755 }
1756 },
1757 {
1758 "bindings": {
1759 "ctrl-b": "zed::SomeOtherAction"
1760 }
1761 }
1762 ]"#
1763 .unindent(),
1764 );
1765
1766 check_keymap_update(
1767 r#"[
1768 {
1769 "bindings": {
1770 // some comment
1771 "ctrl-a": "zed::SomeAction"
1772 // some other comment
1773 }
1774 }
1775 ]"#
1776 .unindent(),
1777 KeybindUpdateOperation::Replace {
1778 target: KeybindUpdateTarget {
1779 keystrokes: &parse_keystrokes("ctrl-a"),
1780 action_name: "zed::SomeAction",
1781 context: None,
1782 action_arguments: None,
1783 },
1784 source: KeybindUpdateTarget {
1785 keystrokes: &parse_keystrokes("ctrl-b"),
1786 action_name: "zed::SomeOtherAction",
1787 context: None,
1788 action_arguments: Some(r#"{"foo": "bar"}"#),
1789 },
1790 target_keybind_source: KeybindSource::User,
1791 },
1792 r#"[
1793 {
1794 "bindings": {
1795 // some comment
1796 "ctrl-b": [
1797 "zed::SomeOtherAction",
1798 {
1799 "foo": "bar"
1800 }
1801 ]
1802 // some other comment
1803 }
1804 }
1805 ]"#
1806 .unindent(),
1807 );
1808
1809 check_keymap_update(
1810 r#"[
1811 {
1812 "context": "SomeContext",
1813 "bindings": {
1814 "a": "foo::bar",
1815 "b": "baz::qux",
1816 }
1817 }
1818 ]"#
1819 .unindent(),
1820 KeybindUpdateOperation::Replace {
1821 target: KeybindUpdateTarget {
1822 keystrokes: &parse_keystrokes("a"),
1823 action_name: "foo::bar",
1824 context: Some("SomeContext"),
1825 action_arguments: None,
1826 },
1827 source: KeybindUpdateTarget {
1828 keystrokes: &parse_keystrokes("c"),
1829 action_name: "foo::baz",
1830 context: Some("SomeOtherContext"),
1831 action_arguments: None,
1832 },
1833 target_keybind_source: KeybindSource::User,
1834 },
1835 r#"[
1836 {
1837 "context": "SomeContext",
1838 "bindings": {
1839 "b": "baz::qux",
1840 }
1841 },
1842 {
1843 "context": "SomeOtherContext",
1844 "bindings": {
1845 "c": "foo::baz"
1846 }
1847 }
1848 ]"#
1849 .unindent(),
1850 );
1851
1852 check_keymap_update(
1853 r#"[
1854 {
1855 "context": "SomeContext",
1856 "bindings": {
1857 "a": "foo::bar",
1858 }
1859 }
1860 ]"#
1861 .unindent(),
1862 KeybindUpdateOperation::Replace {
1863 target: KeybindUpdateTarget {
1864 keystrokes: &parse_keystrokes("a"),
1865 action_name: "foo::bar",
1866 context: Some("SomeContext"),
1867 action_arguments: None,
1868 },
1869 source: KeybindUpdateTarget {
1870 keystrokes: &parse_keystrokes("c"),
1871 action_name: "foo::baz",
1872 context: Some("SomeOtherContext"),
1873 action_arguments: None,
1874 },
1875 target_keybind_source: KeybindSource::User,
1876 },
1877 r#"[
1878 {
1879 "context": "SomeOtherContext",
1880 "bindings": {
1881 "c": "foo::baz",
1882 }
1883 }
1884 ]"#
1885 .unindent(),
1886 );
1887
1888 check_keymap_update(
1889 r#"[
1890 {
1891 "context": "SomeContext",
1892 "bindings": {
1893 "a": "foo::bar",
1894 "c": "foo::baz",
1895 }
1896 },
1897 ]"#
1898 .unindent(),
1899 KeybindUpdateOperation::Remove {
1900 target: KeybindUpdateTarget {
1901 context: Some("SomeContext"),
1902 keystrokes: &parse_keystrokes("a"),
1903 action_name: "foo::bar",
1904 action_arguments: None,
1905 },
1906 target_keybind_source: KeybindSource::User,
1907 },
1908 r#"[
1909 {
1910 "context": "SomeContext",
1911 "bindings": {
1912 "c": "foo::baz",
1913 }
1914 },
1915 ]"#
1916 .unindent(),
1917 );
1918
1919 check_keymap_update(
1920 r#"[
1921 {
1922 "context": "SomeContext",
1923 "bindings": {
1924 "\\ a": "foo::bar",
1925 "c": "foo::baz",
1926 }
1927 },
1928 ]"#
1929 .unindent(),
1930 KeybindUpdateOperation::Remove {
1931 target: KeybindUpdateTarget {
1932 context: Some("SomeContext"),
1933 keystrokes: &parse_keystrokes("\\ a"),
1934 action_name: "foo::bar",
1935 action_arguments: None,
1936 },
1937 target_keybind_source: KeybindSource::User,
1938 },
1939 r#"[
1940 {
1941 "context": "SomeContext",
1942 "bindings": {
1943 "c": "foo::baz",
1944 }
1945 },
1946 ]"#
1947 .unindent(),
1948 );
1949
1950 check_keymap_update(
1951 r#"[
1952 {
1953 "context": "SomeContext",
1954 "bindings": {
1955 "a": ["foo::bar", true],
1956 "c": "foo::baz",
1957 }
1958 },
1959 ]"#
1960 .unindent(),
1961 KeybindUpdateOperation::Remove {
1962 target: KeybindUpdateTarget {
1963 context: Some("SomeContext"),
1964 keystrokes: &parse_keystrokes("a"),
1965 action_name: "foo::bar",
1966 action_arguments: Some("true"),
1967 },
1968 target_keybind_source: KeybindSource::User,
1969 },
1970 r#"[
1971 {
1972 "context": "SomeContext",
1973 "bindings": {
1974 "c": "foo::baz",
1975 }
1976 },
1977 ]"#
1978 .unindent(),
1979 );
1980
1981 check_keymap_update(
1982 r#"[
1983 {
1984 "context": "SomeContext",
1985 "bindings": {
1986 "b": "foo::baz",
1987 }
1988 },
1989 {
1990 "context": "SomeContext",
1991 "bindings": {
1992 "a": ["foo::bar", true],
1993 }
1994 },
1995 {
1996 "context": "SomeContext",
1997 "bindings": {
1998 "c": "foo::baz",
1999 }
2000 },
2001 ]"#
2002 .unindent(),
2003 KeybindUpdateOperation::Remove {
2004 target: KeybindUpdateTarget {
2005 context: Some("SomeContext"),
2006 keystrokes: &parse_keystrokes("a"),
2007 action_name: "foo::bar",
2008 action_arguments: Some("true"),
2009 },
2010 target_keybind_source: KeybindSource::User,
2011 },
2012 r#"[
2013 {
2014 "context": "SomeContext",
2015 "bindings": {
2016 "b": "foo::baz",
2017 }
2018 },
2019 {
2020 "context": "SomeContext",
2021 "bindings": {
2022 "c": "foo::baz",
2023 }
2024 },
2025 ]"#
2026 .unindent(),
2027 );
2028 check_keymap_update(
2029 r#"[
2030 {
2031 "context": "SomeOtherContext",
2032 "use_key_equivalents": true,
2033 "bindings": {
2034 "b": "foo::bar",
2035 }
2036 },
2037 ]"#
2038 .unindent(),
2039 KeybindUpdateOperation::Add {
2040 source: KeybindUpdateTarget {
2041 context: Some("SomeContext"),
2042 keystrokes: &parse_keystrokes("a"),
2043 action_name: "foo::baz",
2044 action_arguments: Some("true"),
2045 },
2046 from: Some(KeybindUpdateTarget {
2047 context: Some("SomeOtherContext"),
2048 keystrokes: &parse_keystrokes("b"),
2049 action_name: "foo::bar",
2050 action_arguments: None,
2051 }),
2052 },
2053 r#"[
2054 {
2055 "context": "SomeOtherContext",
2056 "use_key_equivalents": true,
2057 "bindings": {
2058 "b": "foo::bar",
2059 }
2060 },
2061 {
2062 "context": "SomeContext",
2063 "use_key_equivalents": true,
2064 "bindings": {
2065 "a": [
2066 "foo::baz",
2067 true
2068 ]
2069 }
2070 }
2071 ]"#
2072 .unindent(),
2073 );
2074
2075 check_keymap_update(
2076 r#"[
2077 {
2078 "context": "SomeOtherContext",
2079 "use_key_equivalents": true,
2080 "bindings": {
2081 "b": "foo::bar",
2082 }
2083 },
2084 ]"#
2085 .unindent(),
2086 KeybindUpdateOperation::Remove {
2087 target: KeybindUpdateTarget {
2088 context: Some("SomeContext"),
2089 keystrokes: &parse_keystrokes("a"),
2090 action_name: "foo::baz",
2091 action_arguments: Some("true"),
2092 },
2093 target_keybind_source: KeybindSource::Default,
2094 },
2095 r#"[
2096 {
2097 "context": "SomeOtherContext",
2098 "use_key_equivalents": true,
2099 "bindings": {
2100 "b": "foo::bar",
2101 }
2102 },
2103 {
2104 "context": "SomeContext",
2105 "bindings": {
2106 "a": null
2107 }
2108 }
2109 ]"#
2110 .unindent(),
2111 );
2112 }
2113
2114 #[test]
2115 fn test_keymap_remove() {
2116 zlog::init_test();
2117
2118 check_keymap_update(
2119 r#"
2120 [
2121 {
2122 "context": "Editor",
2123 "bindings": {
2124 "cmd-k cmd-u": "editor::ConvertToUpperCase",
2125 "cmd-k cmd-l": "editor::ConvertToLowerCase",
2126 "cmd-[": "pane::GoBack",
2127 }
2128 },
2129 ]
2130 "#,
2131 KeybindUpdateOperation::Remove {
2132 target: KeybindUpdateTarget {
2133 context: Some("Editor"),
2134 keystrokes: &parse_keystrokes("cmd-k cmd-l"),
2135 action_name: "editor::ConvertToLowerCase",
2136 action_arguments: None,
2137 },
2138 target_keybind_source: KeybindSource::User,
2139 },
2140 r#"
2141 [
2142 {
2143 "context": "Editor",
2144 "bindings": {
2145 "cmd-k cmd-u": "editor::ConvertToUpperCase",
2146 "cmd-[": "pane::GoBack",
2147 }
2148 },
2149 ]
2150 "#,
2151 );
2152 }
2153}