From d92c12e86e47aac71e1ee9f4a6b02ca529f73c97 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 23 Apr 2026 08:04:17 -0400 Subject: [PATCH] Fix docs preprocessor preferring parameterized keybindings over exact matches (#54593) This PR fixes a bug with the docs pre-processor where it could match on parameterized versions of key binds before exact matches. I noticed this bug in the latest [parallel agent docs:](https://zed.dev/docs/ai/parallel-agents#switching-threads) SCR-20260423-bhtn Here, the preprocessor is matching on the parameterized version first: ```json "ctrl-tab": "agents_sidebar::ToggleThreadSwitcher", "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }], ``` This bug surfaced in some other places in the docs as well: - https://zed.dev/docs/ai/agent-panel#multiple-threads - https://zed.dev/docs/finding-navigating#project-search - https://zed.dev/docs/finding-navigating#quick-reference - https://zed.dev/docs/migrate/webstorm#no-indexing - https://zed.dev/docs/migrate/webstorm#differences-in-user-interfaces Self-Review Checklist: - [X] I've reviewed my own diff for quality, security, and reliability - [N/A] Unsafe blocks (if any) have justifying comments - [N/A] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [X] Tests cover the new/changed behavior - [X] Performance impact has been considered and is acceptable Release Notes: - N/A --- crates/docs_preprocessor/src/main.rs | 94 +++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 9 deletions(-) diff --git a/crates/docs_preprocessor/src/main.rs b/crates/docs_preprocessor/src/main.rs index 3eaae6a1d48..6655d86d25c 100644 --- a/crates/docs_preprocessor/src/main.rs +++ b/crates/docs_preprocessor/src/main.rs @@ -336,17 +336,28 @@ fn is_missing_action(name: &str) -> bool { actions_available() && find_action_by_name(name).is_none() } -// Find the binding in reverse order, as the last binding takes precedence. +// Find the last binding (in keymap order) for the given action. +// Exact action matches are preferred over parameterized variants. fn find_binding_in_keymap(keymap: &KeymapFile, action: &str) -> Option { - keymap.sections().rev().find_map(|section| { - section.bindings().rev().find_map(|(keystroke, a)| { - if name_for_action(a.to_string()) == action { - Some(keystroke.to_string()) - } else { - None - } + let find = |predicate: &dyn Fn(&str) -> bool| { + keymap.sections().rev().find_map(|section| { + section.bindings().rev().find_map(|(keystroke, a)| { + if predicate(&a.to_string()) { + Some(keystroke.to_string()) + } else { + None + } + }) }) - }) + }; + + // Look for exact match + if let Some(binding) = find(&|a| a == action) { + return Some(binding); + } + + // Look for parameterized match + find(&|a| name_for_action(a.to_string()) == action) } fn find_binding(os: Os, action: &str) -> Option { @@ -872,3 +883,68 @@ fn keymap_schema_for_actions( &deprecation_messages, ) } + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_find_binding_prefers_exact_match_over_parameterized() { + let keymap: KeymapFile = serde_json::from_value(json!([ + { + "bindings": { + "ctrl-tab": "agents_sidebar::ToggleThreadSwitcher", + "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }] + } + } + ])) + .unwrap(); + + let binding = find_binding_in_keymap(&keymap, "agents_sidebar::ToggleThreadSwitcher"); + assert_eq!(binding.as_deref(), Some("ctrl-tab")); + } + + #[test] + fn test_find_binding_falls_back_to_parameterized_match() { + let keymap: KeymapFile = serde_json::from_value(json!([ + { + "bindings": { + "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }] + } + } + ])) + .unwrap(); + + let binding = find_binding_in_keymap(&keymap, "agents_sidebar::ToggleThreadSwitcher"); + assert_eq!(binding.as_deref(), Some("ctrl-shift-tab")); + } + + #[test] + fn test_find_binding_prefers_exact_match_regardless_of_order() { + let keymap: KeymapFile = serde_json::from_value(json!([ + { + "bindings": { + "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }], + "ctrl-tab": "agents_sidebar::ToggleThreadSwitcher" + } + } + ])) + .unwrap(); + + let binding = find_binding_in_keymap(&keymap, "agents_sidebar::ToggleThreadSwitcher"); + assert_eq!(binding.as_deref(), Some("ctrl-tab")); + } + + #[test] + fn test_find_binding_later_section_overrides_earlier() { + let keymap: KeymapFile = serde_json::from_value(json!([ + { "bindings": { "ctrl-a": "some::Action" } }, + { "bindings": { "ctrl-b": "some::Action" } } + ])) + .unwrap(); + + let binding = find_binding_in_keymap(&keymap, "some::Action"); + assert_eq!(binding.as_deref(), Some("ctrl-b")); + } +}