Add tab tooltips

Joseph Lyons created

Change summary

crates/diagnostics/src/diagnostics.rs     |  5 +
crates/editor/src/items.rs                | 17 ++++
crates/feedback/src/feedback_editor.rs    |  5 +
crates/gpui/src/elements/tooltip.rs       | 13 ++-
crates/search/src/project_search.rs       |  5 +
crates/terminal_view/src/terminal_view.rs |  5 +
crates/welcome/src/welcome.rs             |  6 +
crates/workspace/src/item.rs              |  8 ++
crates/workspace/src/pane.rs              | 86 +++++++++++++++---------
crates/workspace/src/shared_screen.rs     |  8 ++
10 files changed, 119 insertions(+), 39 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics.rs 🔗

@@ -24,6 +24,7 @@ use settings::Settings;
 use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
+    borrow::Cow,
     cmp::Ordering,
     ops::Range,
     path::PathBuf,
@@ -531,6 +532,10 @@ impl Item for ProjectDiagnosticsEditor {
             .update(cx, |editor, cx| editor.navigate(data, cx))
     }
 
+    fn tab_tooltip_text<'a>(&'a self, _: &'a AppContext) -> Option<Cow<'a, str>> {
+        Some("Project Diagnostics".into())
+    }
+
     fn is_dirty(&self, cx: &AppContext) -> bool {
         self.excerpts.read(cx).is_dirty(cx)
     }

crates/editor/src/items.rs 🔗

@@ -514,6 +514,23 @@ impl Item for Editor {
         }
     }
 
+    fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option<Cow<'a, str>> {
+        let file_path = self
+            .buffer()
+            .read(cx)
+            .as_singleton()?
+            .read(cx)
+            .file()
+            .and_then(|f| f.as_local())?
+            .abs_path(cx);
+
+        let file_path = util::paths::compact(&file_path)
+            .to_string_lossy()
+            .to_string();
+
+        Some(file_path.into())
+    }
+
     fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
         match path_for_buffer(&self.buffer, detail, true, cx)? {
             Cow::Borrowed(path) => Some(path.to_string_lossy()),

crates/feedback/src/feedback_editor.rs 🔗

@@ -1,5 +1,6 @@
 use std::{
     any::TypeId,
+    borrow::Cow,
     ops::{Range, RangeInclusive},
     sync::Arc,
 };
@@ -248,6 +249,10 @@ impl Entity for FeedbackEditor {
 }
 
 impl Item for FeedbackEditor {
+    fn tab_tooltip_text<'a>(&'a self, _: &'a AppContext) -> Option<Cow<'a, str>> {
+        Some("Send Feedback".into())
+    }
+
     fn tab_content(&self, _: Option<usize>, style: &theme::Tab, _: &AppContext) -> ElementBox {
         Flex::row()
             .with_child(

crates/gpui/src/elements/tooltip.rs 🔗

@@ -39,7 +39,7 @@ pub struct TooltipStyle {
     pub container: ContainerStyle,
     pub text: TextStyle,
     keystroke: KeystrokeStyle,
-    pub max_text_width: f32,
+    pub max_text_width: Option<f32>,
 }
 
 #[derive(Clone, Deserialize, Default)]
@@ -140,9 +140,14 @@ impl Tooltip {
     ) -> impl Element {
         Flex::row()
             .with_child({
-                let text = Text::new(text, style.text)
-                    .constrained()
-                    .with_max_width(style.max_text_width);
+                let text = if let Some(max_text_width) = style.max_text_width {
+                    Text::new(text, style.text)
+                        .constrained()
+                        .with_max_width(max_text_width)
+                } else {
+                    Text::new(text, style.text).constrained()
+                };
+
                 if measure {
                     text.flex(1., false).boxed()
                 } else {

crates/search/src/project_search.rs 🔗

@@ -22,6 +22,7 @@ use settings::Settings;
 use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
+    borrow::Cow,
     mem,
     ops::Range,
     path::PathBuf,
@@ -225,6 +226,10 @@ impl View for ProjectSearchView {
 }
 
 impl Item for ProjectSearchView {
+    fn tab_tooltip_text<'a>(&'a self, cx: &'a AppContext) -> Option<Cow<'a, str>> {
+        Some(self.query_editor.read(cx).text(cx).into())
+    }
+
     fn act_as_type<'a>(
         &'a self,
         type_id: TypeId,

crates/terminal_view/src/terminal_view.rs 🔗

@@ -3,6 +3,7 @@ pub mod terminal_button;
 pub mod terminal_element;
 
 use std::{
+    borrow::Cow,
     ops::RangeInclusive,
     path::{Path, PathBuf},
     time::Duration,
@@ -543,6 +544,10 @@ impl View for TerminalView {
 }
 
 impl Item for TerminalView {
+    fn tab_tooltip_text<'a>(&'a self, cx: &'a AppContext) -> Option<Cow<'a, str>> {
+        Some(self.terminal().read(cx).title().into())
+    }
+
     fn tab_content(
         &self,
         _detail: Option<usize>,

crates/welcome/src/welcome.rs 🔗

@@ -1,6 +1,6 @@
 mod base_keymap_picker;
 
-use std::sync::Arc;
+use std::{borrow::Cow, sync::Arc};
 
 use db::kvp::KEY_VALUE_STORE;
 use gpui::{
@@ -198,6 +198,10 @@ impl WelcomePage {
 }
 
 impl Item for WelcomePage {
+    fn tab_tooltip_text<'a>(&'a self, _: &'a AppContext) -> Option<Cow<'a, str>> {
+        Some("Welcome to Zed!".into())
+    }
+
     fn tab_content(
         &self,
         _detail: Option<usize>,

crates/workspace/src/item.rs 🔗

@@ -44,6 +44,9 @@ pub trait Item: View {
     fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
         false
     }
+    fn tab_tooltip_text<'a>(&'a self, _: &'a AppContext) -> Option<Cow<'a, str>> {
+        None
+    }
     fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
         None
     }
@@ -162,6 +165,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
         cx: &mut AppContext,
         handler: Box<dyn Fn(ItemEvent, &mut AppContext)>,
     ) -> gpui::Subscription;
+    fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option<Cow<'a, str>>;
     fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
     fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
         -> ElementBox;
@@ -248,6 +252,10 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
         })
     }
 
+    fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option<Cow<'a, str>> {
+        self.read(cx).tab_tooltip_text(cx)
+    }
+
     fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
         self.read(cx).tab_description(detail, cx)
     }

crates/workspace/src/pane.rs 🔗

@@ -1386,6 +1386,9 @@ impl Pane {
                         let detail = detail.clone();
 
                         let theme = cx.global::<Settings>().theme.clone();
+                        let mut tooltip_theme = theme.tooltip.clone();
+                        tooltip_theme.max_text_width = None;
+                        let tab_tooltip_text = item.tab_tooltip_text(cx).map(|a| a.to_string());
 
                         move |mouse_state, cx| {
                             let tab_style =
@@ -1393,39 +1396,56 @@ impl Pane {
                             let hovered = mouse_state.hovered();
 
                             enum Tab {}
-                            MouseEventHandler::<Tab>::new(ix, cx, |_, cx| {
-                                Self::render_tab(
-                                    &item,
-                                    pane.clone(),
-                                    ix == 0,
-                                    detail,
-                                    hovered,
-                                    tab_style,
-                                    cx,
-                                )
-                            })
-                            .on_down(MouseButton::Left, move |_, cx| {
-                                cx.dispatch_action(ActivateItem(ix));
-                            })
-                            .on_click(MouseButton::Middle, {
-                                let item = item.clone();
-                                let pane = pane.clone();
-                                move |_, cx: &mut EventContext| {
-                                    cx.dispatch_action(CloseItemById {
-                                        item_id: item.id(),
-                                        pane: pane.clone(),
-                                    })
-                                }
-                            })
-                            .on_down(MouseButton::Right, move |e, cx| {
-                                let item = item.clone();
-                                cx.dispatch_action(DeployTabContextMenu {
-                                    position: e.position,
-                                    item_id: item.id(),
-                                    pane: pane.clone(),
-                                });
-                            })
-                            .boxed()
+                            let mouse_event_handler =
+                                MouseEventHandler::<Tab>::new(ix, cx, |_, cx| {
+                                    Self::render_tab(
+                                        &item,
+                                        pane.clone(),
+                                        ix == 0,
+                                        detail,
+                                        hovered,
+                                        tab_style,
+                                        cx,
+                                    )
+                                })
+                                .on_down(MouseButton::Left, move |_, cx| {
+                                    cx.dispatch_action(ActivateItem(ix));
+                                })
+                                .on_click(MouseButton::Middle, {
+                                    let item = item.clone();
+                                    let pane = pane.clone();
+                                    move |_, cx: &mut EventContext| {
+                                        cx.dispatch_action(CloseItemById {
+                                            item_id: item.id(),
+                                            pane: pane.clone(),
+                                        })
+                                    }
+                                })
+                                .on_down(
+                                    MouseButton::Right,
+                                    move |e, cx| {
+                                        let item = item.clone();
+                                        cx.dispatch_action(DeployTabContextMenu {
+                                            position: e.position,
+                                            item_id: item.id(),
+                                            pane: pane.clone(),
+                                        });
+                                    },
+                                );
+
+                            if let Some(tab_tooltip_text) = tab_tooltip_text {
+                                return mouse_event_handler
+                                    .with_tooltip::<Self, _>(
+                                        ix,
+                                        tab_tooltip_text,
+                                        None,
+                                        tooltip_theme,
+                                        cx,
+                                    )
+                                    .boxed();
+                            }
+
+                            mouse_event_handler.boxed()
                         }
                     });
 

crates/workspace/src/shared_screen.rs 🔗

@@ -13,7 +13,10 @@ use gpui::{
 };
 use settings::Settings;
 use smallvec::SmallVec;
-use std::sync::{Arc, Weak};
+use std::{
+    borrow::Cow,
+    sync::{Arc, Weak},
+};
 
 pub enum Event {
     Close,
@@ -92,6 +95,9 @@ impl View for SharedScreen {
 }
 
 impl Item for SharedScreen {
+    fn tab_tooltip_text<'a>(&'a self, _: &'a AppContext) -> Option<Cow<'a, str>> {
+        Some(format!("{}'s screen", self.user.github_login).into())
+    }
     fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
         if let Some(nav_history) = self.nav_history.as_ref() {
             nav_history.push::<()>(None, cx);