markdown_preview: Stylize links using accented text color (#39149)

Bartosz Kaszubowski created

# How

Emphasize links in Markdown Preview text using accented text color. 

> [!note]
> I have chosen the accent color for links since it was looking fine
with all bundled by default themes, but I'm happy to alter the color to
use different theme value, if you have better candidates.

Release Notes:

- Stylize links using accented text color in Markdown Preview

# Preview

### Before

<img width="1606" height="1066" alt="Screenshot 2025-09-29 at 22 19 38"
src="https://github.com/user-attachments/assets/59b6ee72-4523-42fb-a468-9c694d30b5df"
/>

### After
<img width="1652" height="1066" alt="Screenshot 2025-09-29 at 22 18 20"
src="https://github.com/user-attachments/assets/e00e3742-6435-4c1d-aaaa-e6332719db17"
/>
<img width="1652" height="1066" alt="Screenshot 2025-09-29 at 22 18 47"
src="https://github.com/user-attachments/assets/a1b76f4a-c4d2-4ca8-ae3c-fc4dc5d55e01"
/>

**Release notes**

<img width="2090" height="582" alt="Screenshot 2025-09-29 at 22 36 33"
src="https://github.com/user-attachments/assets/81d6df12-83bd-4794-b71e-5a1fd40f0140"
/>
<img width="2090" height="582" alt="Screenshot 2025-09-29 at 22 40 41"
src="https://github.com/user-attachments/assets/aa820767-b82b-42a5-aa5b-b0d3d22ac5e3"
/>

Change summary

crates/markdown_preview/src/markdown_elements.rs | 19 ++++++++++++++++-
crates/markdown_preview/src/markdown_parser.rs   |  4 +-
crates/markdown_preview/src/markdown_renderer.rs |  5 +++
3 files changed, 23 insertions(+), 5 deletions(-)

Detailed changes

crates/markdown_preview/src/markdown_elements.rs 🔗

@@ -1,5 +1,5 @@
 use gpui::{
-    DefiniteLength, FontStyle, FontWeight, HighlightStyle, SharedString, StrikethroughStyle,
+    DefiniteLength, FontStyle, FontWeight, HighlightStyle, Hsla, SharedString, StrikethroughStyle,
     UnderlineStyle, px,
 };
 use language::HighlightId;
@@ -175,7 +175,11 @@ pub enum MarkdownHighlight {
 
 impl MarkdownHighlight {
     /// Converts this [`MarkdownHighlight`] to a [`HighlightStyle`].
-    pub fn to_highlight_style(&self, theme: &theme::SyntaxTheme) -> Option<HighlightStyle> {
+    pub fn to_highlight_style(
+        &self,
+        theme: &theme::SyntaxTheme,
+        link_color: Hsla,
+    ) -> Option<HighlightStyle> {
         match self {
             MarkdownHighlight::Style(style) => {
                 let mut highlight = HighlightStyle::default();
@@ -202,6 +206,15 @@ impl MarkdownHighlight {
                     highlight.font_weight = Some(style.weight);
                 }
 
+                if style.link {
+                    highlight.underline = Some(UnderlineStyle {
+                        thickness: px(1.),
+                        color: Some(link_color),
+                        ..Default::default()
+                    });
+                    highlight.color = Some(link_color);
+                }
+
                 Some(highlight)
             }
 
@@ -221,6 +234,8 @@ pub struct MarkdownHighlightStyle {
     pub strikethrough: bool,
     /// The weight of the text.
     pub weight: FontWeight,
+    /// Whether the text should be stylized as link.
+    pub link: bool,
 }
 
 /// A parsed region in a Markdown document.

crates/markdown_preview/src/markdown_parser.rs 🔗

@@ -261,7 +261,7 @@ impl<'a> MarkdownParser<'a> {
                             code: false,
                             link: Some(link),
                         });
-                        style.underline = true;
+                        style.link = true;
                         prev_len
                     } else {
                         // Manually scan for links
@@ -329,7 +329,7 @@ impl<'a> MarkdownParser<'a> {
                         highlights.push((
                             prev_len..text.len(),
                             MarkdownHighlight::Style(MarkdownHighlightStyle {
-                                underline: true,
+                                link: true,
                                 ..Default::default()
                             }),
                         ));

crates/markdown_preview/src/markdown_renderer.rs 🔗

@@ -53,6 +53,7 @@ pub struct RenderContext {
     border_color: Hsla,
     element_background_color: Hsla,
     text_color: Hsla,
+    link_color: Hsla,
     window_rem_size: Pixels,
     text_muted_color: Hsla,
     code_block_background_color: Hsla,
@@ -87,6 +88,7 @@ impl RenderContext {
             border_color: theme.colors().border,
             element_background_color: theme.colors().element_background,
             text_color: theme.colors().text,
+            link_color: theme.colors().text_accent,
             window_rem_size: window.rem_size(),
             text_muted_color: theme.colors().text_muted,
             code_block_background_color: theme.colors().surface_background,
@@ -656,6 +658,7 @@ fn render_markdown_text(parsed_new: &MarkdownParagraph, cx: &mut RenderContext)
     let workspace_clone = cx.workspace.clone();
     let code_span_bg_color = cx.code_span_background_color;
     let text_style = cx.text_style.clone();
+    let link_color = cx.link_color;
 
     for parsed_region in parsed_new {
         match parsed_region {
@@ -665,7 +668,7 @@ fn render_markdown_text(parsed_new: &MarkdownParagraph, cx: &mut RenderContext)
                 let highlights = gpui::combine_highlights(
                     parsed.highlights.iter().filter_map(|(range, highlight)| {
                         highlight
-                            .to_highlight_style(&syntax_theme)
+                            .to_highlight_style(&syntax_theme, link_color)
                             .map(|style| (range.clone(), style))
                     }),
                     parsed.regions.iter().zip(&parsed.region_ranges).filter_map(