Prefer later bindings in keymap section for display in UI (#23378)

Closes #23015

Release Notes:

- Improved which keybindings are selected for display. Now later entries
within `bindings` will take precedence. The default keymaps have been
updated accordingly.
This commit is contained in:
Michael Sloan 2025-01-20 16:20:15 -07:00 committed by GitHub
parent 919703e6a8
commit aacd80ee4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 186 additions and 196 deletions

View file

@ -32,8 +32,9 @@ impl PreprocessorContext {
_ => return None,
};
keymap.sections().find_map(|section| {
section.bindings().find_map(|(keystroke, a)| {
// Find the binding in reverse order, as the last binding takes precedence.
keymap.sections().rev().find_map(|section| {
section.bindings().rev().find_map(|(keystroke, a)| {
if a.to_string() == action {
Some(keystroke.to_string())
} else {

View file

@ -393,8 +393,8 @@ impl DispatchTree {
false
}
/// Returns key bindings that invoke an action on the currently focused element, in precedence
/// order (reverse of the order they were added to the keymap).
/// Returns key bindings that invoke an action on the currently focused element. Bindings are
/// returned in the order they were added. For display, the last binding should take precedence.
pub fn bindings_for_action(
&self,
action: &dyn Action,

View file

@ -67,7 +67,8 @@ impl Keymap {
self.bindings.iter()
}
/// Iterate over all bindings for the given action, in the order they were added.
/// Iterate over all bindings for the given action, in the order they were added. For display,
/// the last binding should take precedence.
pub fn bindings_for_action<'a>(
&'a self,
action: &'a dyn Action,

View file

@ -292,6 +292,7 @@ impl MacPlatform {
} => {
let keystrokes = keymap
.bindings_for_action(action.as_ref())
.rev()
.next()
.map(|binding| binding.keystrokes());

View file

@ -3090,8 +3090,7 @@ impl<'a> WindowContext<'a> {
/// binding for the action (last binding added to the keymap).
pub fn keystroke_text_for(&self, action: &dyn Action) -> String {
self.bindings_for_action(action)
.into_iter()
.next()
.last()
.map(|binding| {
binding
.keystrokes()
@ -3744,8 +3743,8 @@ impl<'a> WindowContext<'a> {
actions
}
/// Returns key bindings that invoke an action on the currently focused element, in precedence
/// order (reverse of the order they were added to the keymap).
/// Returns key bindings that invoke an action on the currently focused element. Bindings are
/// returned in the order they were added. For display, the last binding should take precedence.
pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
self.window
.rendered_frame
@ -3757,15 +3756,15 @@ impl<'a> WindowContext<'a> {
}
/// Returns key bindings that invoke the given action on the currently focused element, without
/// checking context. Bindings are returned returned in precedence order (reverse of the order
/// they were added to the keymap).
/// checking context. Bindings are returned in the order they were added. For display, the last
/// binding should take precedence.
pub fn all_bindings_for_input(&self, input: &[Keystroke]) -> Vec<KeyBinding> {
RefCell::borrow(&self.keymap).all_bindings_for_input(input)
}
/// Returns any bindings that would invoke an action on the given focus handle if it were
/// focused. Bindings are returned returned in precedence order (reverse of the order
/// they were added to the keymap).
/// focused. Bindings are returned in the order they were added. For display, the last binding
/// should take precedence.
pub fn bindings_for_action_in(
&self,
action: &dyn Action,

View file

@ -2,7 +2,7 @@ use std::rc::Rc;
use crate::{settings_store::parse_json_with_comments, SettingsAssets};
use anyhow::anyhow;
use collections::{BTreeMap, HashMap, IndexMap};
use collections::{HashMap, IndexMap};
use gpui::{
Action, ActionBuildError, AppContext, InvalidKeystrokeError, KeyBinding,
KeyBindingContextPredicate, NoAction, SharedString, KEYSTROKE_PARSE_EXPECTED_MESSAGE,
@ -50,9 +50,12 @@ pub struct KeymapSection {
/// This keymap section's bindings, as a JSON object mapping keystrokes to actions. The
/// keystrokes key is a string representing a sequence of keystrokes to type, where the
/// keystrokes are separated by whitespace. Each keystroke is a sequence of modifiers (`ctrl`,
/// `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) followed by a key, separated by `-`.
/// `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) followed by a key, separated by `-`. The
/// order of bindings does matter. When the same keystrokes are bound at the same context depth,
/// the binding that occurs later in the file is preferred. For displaying keystrokes in the UI,
/// the later binding for the same action is preferred.
#[serde(default)]
bindings: Option<BTreeMap<String, KeymapAction>>,
bindings: Option<IndexMap<String, KeymapAction>>,
#[serde(flatten)]
unrecognized_fields: IndexMap<String, Value>,
// This struct intentionally uses permissive types for its fields, rather than validating during
@ -64,7 +67,7 @@ pub struct KeymapSection {
}
impl KeymapSection {
pub fn bindings(&self) -> impl Iterator<Item = (&String, &KeymapAction)> {
pub fn bindings(&self) -> impl DoubleEndedIterator<Item = (&String, &KeymapAction)> {
self.bindings.iter().flatten()
}
}
@ -235,8 +238,7 @@ impl KeymapFile {
}
if let Some(bindings) = bindings {
for binding in bindings {
let (keystrokes, action) = binding;
for (keystrokes, action) in bindings {
let result = Self::load_keybinding(
keystrokes,
action,
@ -548,7 +550,7 @@ impl KeymapFile {
serde_json::to_value(root_schema).unwrap()
}
pub fn sections(&self) -> impl Iterator<Item = &KeymapSection> {
pub fn sections(&self) -> impl DoubleEndedIterator<Item = &KeymapSection> {
self.0.iter()
}
}

View file

@ -19,7 +19,7 @@ impl KeyBinding {
/// Returns the highest precedence keybinding for an action. This is the last binding added to
/// the keymap. User bindings are added after built-in bindings so that they take precedence.
pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
let key_binding = cx.bindings_for_action(action).last().cloned()?;
let key_binding = cx.bindings_for_action(action).into_iter().rev().next()?;
Some(Self::new(key_binding))
}
@ -29,7 +29,11 @@ impl KeyBinding {
focus: &FocusHandle,
cx: &mut WindowContext,
) -> Option<Self> {
let key_binding = cx.bindings_for_action_in(action, focus).last().cloned()?;
let key_binding = cx
.bindings_for_action_in(action, focus)
.into_iter()
.rev()
.next()?;
Some(Self::new(key_binding))
}
@ -198,7 +202,8 @@ impl KeyIcon {
/// Returns a textual representation of the key binding for the given [`Action`].
pub fn text_for_action(action: &dyn Action, cx: &WindowContext) -> Option<String> {
let key_binding = cx.bindings_for_action(action).last().cloned()?;
let bindings = cx.bindings_for_action(action);
let key_binding = bindings.last()?;
Some(text_for_key_binding(key_binding, PlatformStyle::platform()))
}
@ -209,13 +214,14 @@ pub fn text_for_action_in(
focus: &FocusHandle,
cx: &mut WindowContext,
) -> Option<String> {
let key_binding = cx.bindings_for_action_in(action, focus).last().cloned()?;
let bindings = cx.bindings_for_action_in(action, focus);
let key_binding = bindings.last()?;
Some(text_for_key_binding(key_binding, PlatformStyle::platform()))
}
/// Returns a textual representation of the given key binding for the specified platform.
pub fn text_for_key_binding(
key_binding: gpui::KeyBinding,
key_binding: &gpui::KeyBinding,
platform_style: PlatformStyle,
) -> String {
key_binding