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