From d1a6c5d494c8e6debbc0e174d00b0ab14f67f3c1 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Fri, 11 Jul 2025 09:54:08 -0500 Subject: [PATCH] keymap_ui: Hover tooltip for context (#34290) Closes #ISSUE Ideally the tooltip would only appear if the context was overflowing it's column, but for now, we just unconditionally show a tooltip so that long contexts can be seen. This PR also includes a change to the tooltip element, allowing for tooltips with non-text contents which is used here for syntax highlighting Release Notes: - N/A *or* Added/Fixed/Improved ... Co-authored-by: Anthony --- crates/settings_ui/src/keybindings.rs | 24 ++++++--- crates/ui/src/components/tooltip.rs | 72 ++++++++++++++++++++++----- 2 files changed, 77 insertions(+), 19 deletions(-) diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index c3d6ae7c3019c1c9138111e8431ef2089ce7abb5..4053d41669916fcee749159cbaf9fe465cdecd2c 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -1015,12 +1015,24 @@ impl Render for KeymapEditor { } } }; - let context = binding - .context - .clone() - .map_or(gpui::Empty.into_any_element(), |context| { - context.into_any_element() - }); + let context = binding.context.clone().map_or( + gpui::Empty.into_any_element(), + |context| { + let is_local = context.local().is_some(); + + div() + .id(("keymap context", index)) + .child(context.clone()) + .when(is_local, |this| { + this.tooltip(Tooltip::element({ + move |_, _| { + context.clone().into_any_element() + } + })) + }) + .into_any_element() + }, + ); let source = binding .source .clone() diff --git a/crates/ui/src/components/tooltip.rs b/crates/ui/src/components/tooltip.rs index 647b700c377b4ca6816924e592f698937646b6b8..18c9decc59f6f68b92542fbd56b6fae916195bfd 100644 --- a/crates/ui/src/components/tooltip.rs +++ b/crates/ui/src/components/tooltip.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use gpui::{Action, AnyElement, AnyView, AppContext as _, FocusHandle, IntoElement, Render}; use settings::Settings; use theme::ThemeSettings; @@ -7,15 +9,36 @@ use crate::{Color, KeyBinding, Label, LabelSize, StyledExt, h_flex, v_flex}; #[derive(RegisterComponent)] pub struct Tooltip { - title: SharedString, + title: Title, meta: Option, key_binding: Option, } +#[derive(Clone, IntoElement)] +enum Title { + Str(SharedString), + Callback(Rc AnyElement>), +} + +impl From for Title { + fn from(value: SharedString) -> Self { + Title::Str(value) + } +} + +impl RenderOnce for Title { + fn render(self, window: &mut Window, cx: &mut App) -> impl gpui::IntoElement { + match self { + Title::Str(title) => title.into_any_element(), + Title::Callback(element) => element(window, cx), + } + } +} + impl Tooltip { pub fn simple(title: impl Into, cx: &mut App) -> AnyView { cx.new(|_| Self { - title: title.into(), + title: Title::Str(title.into()), meta: None, key_binding: None, }) @@ -26,7 +49,7 @@ impl Tooltip { let title = title.into(); move |_, cx| { cx.new(|_| Self { - title: title.clone(), + title: title.clone().into(), meta: None, key_binding: None, }) @@ -34,15 +57,15 @@ impl Tooltip { } } - pub fn for_action_title>( - title: Title, + pub fn for_action_title>( + title: T, action: &dyn Action, - ) -> impl Fn(&mut Window, &mut App) -> AnyView + use { + ) -> impl Fn(&mut Window, &mut App) -> AnyView + use<T> { let title = title.into(); let action = action.boxed_clone(); move |window, cx| { cx.new(|cx| Self { - title: title.clone(), + title: Title::Str(title.clone()), meta: None, key_binding: KeyBinding::for_action(action.as_ref(), window, cx), }) @@ -60,7 +83,7 @@ impl Tooltip { let focus_handle = focus_handle.clone(); move |window, cx| { cx.new(|cx| Self { - title: title.clone(), + title: Title::Str(title.clone()), meta: None, key_binding: KeyBinding::for_action_in(action.as_ref(), &focus_handle, window, cx), }) @@ -75,7 +98,7 @@ impl Tooltip { cx: &mut App, ) -> AnyView { cx.new(|cx| Self { - title: title.into(), + title: Title::Str(title.into()), meta: None, key_binding: KeyBinding::for_action(action, window, cx), }) @@ -90,7 +113,7 @@ impl Tooltip { cx: &mut App, ) -> AnyView { cx.new(|cx| Self { - title: title.into(), + title: title.into().into(), meta: None, key_binding: KeyBinding::for_action_in(action, focus_handle, window, cx), }) @@ -105,7 +128,7 @@ impl Tooltip { cx: &mut App, ) -> AnyView { cx.new(|cx| Self { - title: title.into(), + title: title.into().into(), meta: Some(meta.into()), key_binding: action.and_then(|action| KeyBinding::for_action(action, window, cx)), }) @@ -121,7 +144,7 @@ impl Tooltip { cx: &mut App, ) -> AnyView { cx.new(|cx| Self { - title: title.into(), + title: title.into().into(), meta: Some(meta.into()), key_binding: action .and_then(|action| KeyBinding::for_action_in(action, focus_handle, window, cx)), @@ -131,12 +154,35 @@ impl Tooltip { pub fn new(title: impl Into<SharedString>) -> Self { Self { - title: title.into(), + title: title.into().into(), meta: None, key_binding: None, } } + pub fn new_element(title: impl Fn(&mut Window, &mut App) -> AnyElement + 'static) -> Self { + Self { + title: Title::Callback(Rc::new(title)), + meta: None, + key_binding: None, + } + } + + pub fn element( + title: impl Fn(&mut Window, &mut App) -> AnyElement + 'static, + ) -> impl Fn(&mut Window, &mut App) -> AnyView { + let title = Title::Callback(Rc::new(title)); + move |_, cx| { + let title = title.clone(); + cx.new(|_| Self { + title: title, + meta: None, + key_binding: None, + }) + .into() + } + } + pub fn meta(mut self, meta: impl Into<SharedString>) -> Self { self.meta = Some(meta.into()); self