markdown preview: Add link tooltips (#10161)

Bennet Bo Fenner created

Adds tooltips to the markdown preview, similar to how its done for
`RichText`


https://github.com/zed-industries/zed/assets/53836821/523519d4-e392-46ef-9fe0-6692871b317d

Release Notes:

- Added tooltips when hovering over links inside the markdown preview

Change summary

crates/markdown_preview/src/markdown_elements.rs | 26 +++++++++++++++--
crates/markdown_preview/src/markdown_renderer.rs | 19 +++++++++++-
2 files changed, 39 insertions(+), 6 deletions(-)

Detailed changes

crates/markdown_preview/src/markdown_elements.rs 🔗

@@ -2,7 +2,7 @@ use gpui::{
     px, FontStyle, FontWeight, HighlightStyle, SharedString, StrikethroughStyle, UnderlineStyle,
 };
 use language::HighlightId;
-use std::{ops::Range, path::PathBuf};
+use std::{fmt::Display, ops::Range, path::PathBuf};
 
 #[derive(Debug)]
 #[cfg_attr(test, derive(PartialEq))]
@@ -226,7 +226,9 @@ pub enum Link {
     },
     /// A link to a path on the filesystem.
     Path {
-        /// The path to the item.
+        /// The path as provided in the Markdown document.
+        display_path: PathBuf,
+        /// The absolute path to the item.
         path: PathBuf,
     },
 }
@@ -239,16 +241,32 @@ impl Link {
 
         let path = PathBuf::from(&text);
         if path.is_absolute() && path.exists() {
-            return Some(Link::Path { path });
+            return Some(Link::Path {
+                display_path: path.clone(),
+                path,
+            });
         }
 
         if let Some(file_location_directory) = file_location_directory {
+            let display_path = path;
             let path = file_location_directory.join(text);
             if path.exists() {
-                return Some(Link::Path { path });
+                return Some(Link::Path { display_path, path });
             }
         }
 
         None
     }
 }
+
+impl Display for Link {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Link::Web { url } => write!(f, "{}", url),
+            Link::Path {
+                display_path,
+                path: _,
+            } => write!(f, "{}", display_path.display()),
+        }
+    }
+}

crates/markdown_preview/src/markdown_renderer.rs 🔗

@@ -13,7 +13,7 @@ use std::{
     sync::Arc,
 };
 use theme::{ActiveTheme, SyntaxTheme};
-use ui::{h_flex, v_flex, Checkbox, Selection};
+use ui::{h_flex, v_flex, Checkbox, LinkPreview, Selection};
 use workspace::Workspace;
 
 pub struct RenderContext {
@@ -328,11 +328,26 @@ fn render_markdown_text(parsed: &ParsedMarkdownText, cx: &mut RenderContext) ->
         element_id,
         StyledText::new(parsed.contents.clone()).with_highlights(&cx.text_style, highlights),
     )
+    .tooltip({
+        let links = links.clone();
+        let link_ranges = link_ranges.clone();
+        move |idx, cx| {
+            for (ix, range) in link_ranges.iter().enumerate() {
+                if range.contains(&idx) {
+                    return Some(LinkPreview::new(&links[ix].to_string(), cx));
+                }
+            }
+            None
+        }
+    })
     .on_click(
         link_ranges,
         move |clicked_range_ix, window_cx| match &links[clicked_range_ix] {
             Link::Web { url } => window_cx.open_url(url),
-            Link::Path { path } => {
+            Link::Path {
+                path,
+                display_path: _,
+            } => {
                 if let Some(workspace) = &workspace {
                     _ = workspace.update(window_cx, |workspace, cx| {
                         workspace.open_abs_path(path.clone(), false, cx).detach();