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