keymap_ui: Show edit icon on hovered and selected row (cherry-pick #34630) (#34635)

Cherry-picked keymap_ui: Show edit icon on hovered and selected row
(#34630)

Closes #ISSUE

Improves the behavior of the edit icon in the far left column of the
keymap UI table. It is now shown in both the selected and the hovered
row as an indicator that the row is editable in this configuration. When
hovered a row can be double clicked or the edit icon can be clicked, and
when selected it can be edited via keyboard shortcuts. Additionally, the
edit icon and all other hover tooltips will now disappear when the table
is navigated via keyboard shortcuts.

<details><summary>Video</summary>




https://github.com/user-attachments/assets/6584810f-4c6d-4e6f-bdca-25b16c920cfc

</details>

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Co-authored-by: Ben Kunkle <ben@zed.dev>
This commit is contained in:
gcp-cherry-pick-bot[bot] 2025-07-17 12:21:49 -04:00 committed by GitHub
parent ec7d6631a4
commit 484e39dcba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -268,6 +268,7 @@ struct KeymapEditor {
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>, context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
previous_edit: Option<PreviousEdit>, previous_edit: Option<PreviousEdit>,
humanized_action_names: HashMap<&'static str, SharedString>, humanized_action_names: HashMap<&'static str, SharedString>,
show_hover_menus: bool,
} }
enum PreviousEdit { enum PreviousEdit {
@ -357,6 +358,7 @@ impl KeymapEditor {
previous_edit: None, previous_edit: None,
humanized_action_names, humanized_action_names,
search_query_debounce: None, search_query_debounce: None,
show_hover_menus: true,
}; };
this.on_keymap_changed(cx); this.on_keymap_changed(cx);
@ -825,6 +827,7 @@ impl KeymapEditor {
} }
fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) { fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
self.show_hover_menus = false;
if let Some(selected) = self.selected_index { if let Some(selected) = self.selected_index {
let selected = selected + 1; let selected = selected + 1;
if selected >= self.matches.len() { if selected >= self.matches.len() {
@ -845,6 +848,7 @@ impl KeymapEditor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.show_hover_menus = false;
if let Some(selected) = self.selected_index { if let Some(selected) = self.selected_index {
if selected == 0 { if selected == 0 {
return; return;
@ -870,6 +874,7 @@ impl KeymapEditor {
_window: &mut Window, _window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.show_hover_menus = false;
if self.matches.get(0).is_some() { if self.matches.get(0).is_some() {
self.selected_index = Some(0); self.selected_index = Some(0);
self.scroll_to_item(0, ScrollStrategy::Center, cx); self.scroll_to_item(0, ScrollStrategy::Center, cx);
@ -878,6 +883,7 @@ impl KeymapEditor {
} }
fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) { fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
self.show_hover_menus = false;
if self.matches.last().is_some() { if self.matches.last().is_some() {
let index = self.matches.len() - 1; let index = self.matches.len() - 1;
self.selected_index = Some(index); self.selected_index = Some(index);
@ -892,6 +898,7 @@ impl KeymapEditor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.show_hover_menus = false;
let Some((keybind, keybind_index)) = self.selected_keybind_and_index() else { let Some((keybind, keybind_index)) = self.selected_keybind_and_index() else {
return; return;
}; };
@ -1169,6 +1176,9 @@ impl Render for KeymapEditor {
.p_2() .p_2()
.gap_1() .gap_1()
.bg(theme.colors().editor_background) .bg(theme.colors().editor_background)
.on_mouse_move(cx.listener(|this, _, _window, _cx| {
this.show_hover_menus = true;
}))
.child( .child(
v_flex() v_flex()
.p_2() .p_2()
@ -1322,9 +1332,9 @@ impl Render for KeymapEditor {
let binding = &this.keybindings[candidate_id]; let binding = &this.keybindings[candidate_id];
let action_name = binding.action_name; let action_name = binding.action_name;
let icon = (this.filter_state != FilterState::Conflicts let icon = if this.filter_state != FilterState::Conflicts
&& this.has_conflict(index)) && this.has_conflict(index)
.then(|| { {
base_button_style(index, IconName::Warning) base_button_style(index, IconName::Warning)
.icon_color(Color::Warning) .icon_color(Color::Warning)
.tooltip(|window, cx| { .tooltip(|window, cx| {
@ -1352,21 +1362,34 @@ impl Render for KeymapEditor {
} }
}, },
)) ))
}) .into_any_element()
.unwrap_or_else(|| { } else {
base_button_style(index, IconName::Pencil) base_button_style(index, IconName::Pencil)
.visible_on_hover(row_group_id(index)) .visible_on_hover(
.tooltip(Tooltip::for_action_title( if this.selected_index == Some(index) {
"Edit Keybinding", "".into()
&EditBinding, } else if this.show_hover_menus {
)) row_group_id(index)
} else {
"never-show".into()
},
)
.when(
this.show_hover_menus && !context_menu_deployed,
|this| {
this.tooltip(Tooltip::for_action_title(
"Edit Keybinding",
&EditBinding,
))
},
)
.on_click(cx.listener(move |this, _, window, cx| { .on_click(cx.listener(move |this, _, window, cx| {
this.select_index(index, cx); this.select_index(index, cx);
this.open_edit_keybinding_modal(false, window, cx); this.open_edit_keybinding_modal(false, window, cx);
cx.stop_propagation(); cx.stop_propagation();
})) }))
}) .into_any_element()
.into_any_element(); };
let action = div() let action = div()
.id(("keymap action", index)) .id(("keymap action", index))
@ -1384,20 +1407,24 @@ impl Render for KeymapEditor {
.into_any_element() .into_any_element()
} }
}) })
.when(!context_menu_deployed, |this| { .when(
this.tooltip({ !context_menu_deployed && this.show_hover_menus,
let action_name = binding.action_name; |this| {
let action_docs = binding.action_docs; this.tooltip({
move |_, cx| { let action_name = binding.action_name;
let action_tooltip = Tooltip::new(action_name); let action_docs = binding.action_docs;
let action_tooltip = match action_docs { move |_, cx| {
Some(docs) => action_tooltip.meta(docs), let action_tooltip =
None => action_tooltip, Tooltip::new(action_name);
}; let action_tooltip = match action_docs {
cx.new(|_| action_tooltip).into() Some(docs) => action_tooltip.meta(docs),
} None => action_tooltip,
}) };
}) cx.new(|_| action_tooltip).into()
}
})
},
)
.into_any_element(); .into_any_element();
let keystrokes = binding.ui_key_binding.clone().map_or( let keystrokes = binding.ui_key_binding.clone().map_or(
binding.keystroke_text.clone().into_any_element(), binding.keystroke_text.clone().into_any_element(),
@ -1422,13 +1449,18 @@ impl Render for KeymapEditor {
div() div()
.id(("keymap context", index)) .id(("keymap context", index))
.child(context.clone()) .child(context.clone())
.when(is_local && !context_menu_deployed, |this| { .when(
this.tooltip(Tooltip::element({ is_local
move |_, _| { && !context_menu_deployed
context.clone().into_any_element() && this.show_hover_menus,
} |this| {
})) this.tooltip(Tooltip::element({
}) move |_, _| {
context.clone().into_any_element()
}
}))
},
)
.into_any_element() .into_any_element()
}, },
); );