keymap_ui: Show existing keystrokes as placeholders in edit modal (#34307)

Closes #ISSUE

Previously, the keystroke input would be empty, even when editing an
existing binding. This meant you had to re-enter the bindings even if
you just wanted to edit the context. Now, the existing keystrokes are
rendered as a placeholder, are re-shown if newly entered keystrokes are
cleared, and will be returned from the `KeystrokeInput::keystrokes()`
method if no new keystrokes were entered.

Additionally fixed a bug in `KeymapFile::update_keybinding` where
semantically identical contexts would be treated as unequal due to
formatting differences.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
This commit is contained in:
Ben Kunkle 2025-07-11 13:07:04 -05:00 committed by GitHub
parent 6f6c2915b2
commit 0797f7b66e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 54 additions and 13 deletions

View file

@ -783,8 +783,12 @@ impl KeymapFile {
target: &KeybindUpdateTarget<'a>, target: &KeybindUpdateTarget<'a>,
target_action_value: &Value, target_action_value: &Value,
) -> Option<(usize, &'b str)> { ) -> Option<(usize, &'b str)> {
let target_context_parsed =
KeyBindingContextPredicate::parse(target.context.unwrap_or("")).ok();
for (index, section) in keymap.sections().enumerate() { for (index, section) in keymap.sections().enumerate() {
if section.context != target.context.unwrap_or("") { let section_context_parsed =
KeyBindingContextPredicate::parse(&section.context).ok();
if section_context_parsed != target_context_parsed {
continue; continue;
} }
if section.use_key_equivalents != target.use_key_equivalents { if section.use_key_equivalents != target.use_key_equivalents {
@ -835,6 +839,7 @@ pub enum KeybindUpdateOperation<'a> {
}, },
} }
#[derive(Debug)]
pub struct KeybindUpdateTarget<'a> { pub struct KeybindUpdateTarget<'a> {
pub context: Option<&'a str>, pub context: Option<&'a str>,
pub keystrokes: &'a [Keystroke], pub keystrokes: &'a [Keystroke],

View file

@ -283,7 +283,7 @@ impl KeymapEditor {
let table_interaction_state = TableInteractionState::new(window, cx); let table_interaction_state = TableInteractionState::new(window, cx);
let keystroke_editor = cx.new(|cx| { let keystroke_editor = cx.new(|cx| {
let mut keystroke_editor = KeystrokeInput::new(window, cx); let mut keystroke_editor = KeystrokeInput::new(None, window, cx);
keystroke_editor.highlight_on_focus = false; keystroke_editor.highlight_on_focus = false;
keystroke_editor keystroke_editor
}); });
@ -632,6 +632,11 @@ impl KeymapEditor {
Box::new(EditBinding), Box::new(EditBinding),
) )
.action("Create", Box::new(CreateBinding)) .action("Create", Box::new(CreateBinding))
.action_disabled_when(
selected_binding_is_unbound,
"Delete",
Box::new(DeleteBinding),
)
.action("Copy action", Box::new(CopyAction)) .action("Copy action", Box::new(CopyAction))
.action_disabled_when( .action_disabled_when(
selected_binding_has_no_context, selected_binding_has_no_context,
@ -1298,7 +1303,16 @@ impl KeybindingEditorModal {
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> Self { ) -> Self {
let keybind_editor = cx.new(|cx| KeystrokeInput::new(window, cx)); let keybind_editor = cx.new(|cx| {
KeystrokeInput::new(
editing_keybind
.ui_key_binding
.as_ref()
.map(|keybinding| keybinding.keystrokes.clone()),
window,
cx,
)
});
let context_editor = cx.new(|cx| { let context_editor = cx.new(|cx| {
let mut editor = Editor::single_line(window, cx); let mut editor = Editor::single_line(window, cx);
@ -1802,6 +1816,7 @@ async fn remove_keybinding(
struct KeystrokeInput { struct KeystrokeInput {
keystrokes: Vec<Keystroke>, keystrokes: Vec<Keystroke>,
placeholder_keystrokes: Option<Vec<Keystroke>>,
highlight_on_focus: bool, highlight_on_focus: bool,
focus_handle: FocusHandle, focus_handle: FocusHandle,
intercept_subscription: Option<Subscription>, intercept_subscription: Option<Subscription>,
@ -1809,7 +1824,11 @@ struct KeystrokeInput {
} }
impl KeystrokeInput { impl KeystrokeInput {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { fn new(
placeholder_keystrokes: Option<Vec<Keystroke>>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let focus_handle = cx.focus_handle(); let focus_handle = cx.focus_handle();
let _focus_subscriptions = [ let _focus_subscriptions = [
cx.on_focus_in(&focus_handle, window, Self::on_focus_in), cx.on_focus_in(&focus_handle, window, Self::on_focus_in),
@ -1817,6 +1836,7 @@ impl KeystrokeInput {
]; ];
Self { Self {
keystrokes: Vec::new(), keystrokes: Vec::new(),
placeholder_keystrokes,
highlight_on_focus: true, highlight_on_focus: true,
focus_handle, focus_handle,
intercept_subscription: None, intercept_subscription: None,
@ -1904,6 +1924,11 @@ impl KeystrokeInput {
} }
fn keystrokes(&self) -> &[Keystroke] { fn keystrokes(&self) -> &[Keystroke] {
if let Some(placeholders) = self.placeholder_keystrokes.as_ref()
&& self.keystrokes.is_empty()
{
return placeholders;
}
if self if self
.keystrokes .keystrokes
.last() .last()
@ -1913,6 +1938,25 @@ impl KeystrokeInput {
} }
return &self.keystrokes; return &self.keystrokes;
} }
fn render_keystrokes(&self) -> impl Iterator<Item = Div> {
let (keystrokes, color) = if let Some(placeholders) = self.placeholder_keystrokes.as_ref()
&& self.keystrokes.is_empty()
{
(placeholders, Color::Placeholder)
} else {
(&self.keystrokes, Color::Default)
};
keystrokes.iter().map(move |keystroke| {
h_flex().children(ui::render_keystroke(
keystroke,
Some(color),
Some(rems(0.875).into()),
ui::PlatformStyle::platform(),
false,
))
})
}
} }
impl EventEmitter<()> for KeystrokeInput {} impl EventEmitter<()> for KeystrokeInput {}
@ -1958,15 +2002,7 @@ impl Render for KeystrokeInput {
.justify_center() .justify_center()
.flex_wrap() .flex_wrap()
.gap(ui::DynamicSpacing::Base04.rems(cx)) .gap(ui::DynamicSpacing::Base04.rems(cx))
.children(self.keystrokes.iter().map(|keystroke| { .children(self.render_keystrokes()),
h_flex().children(ui::render_keystroke(
keystroke,
None,
Some(rems(0.875).into()),
ui::PlatformStyle::platform(),
false,
))
})),
) )
.child( .child(
h_flex() h_flex()