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)

<img width="717" height="110" alt="SCR-20260423-bhtn"
src="https://github.com/user-attachments/assets/ea20b592-55c3-4e92-9c2e-ff821efacdd8"
/>

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
This commit is contained in:
Joseph T. Lyons 2026-04-23 08:04:17 -04:00 committed by GitHub
parent 96eb05166f
commit d92c12e86e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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<String> {
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<String> {
@ -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"));
}
}