markdown: Fix double borders in Markdown and Markdown Preview tables (#44991)

Smit Barmase created

Improves upon https://github.com/zed-industries/zed/pull/42674

Before:

<img width="520" height="202" alt="image"
src="https://github.com/user-attachments/assets/efb1650b-4c0e-4424-8d9b-90de80c72df2"
/> <img width="157" height="211" alt="image"
src="https://github.com/user-attachments/assets/cf4605f3-88e5-4724-ad2b-1219ed04a945"
/>

After:

<img width="529" height="208" alt="image"
src="https://github.com/user-attachments/assets/382fd523-a3d9-4700-a8df-c339419fc6dc"
/>
<img width="133" height="208" alt="image"
src="https://github.com/user-attachments/assets/f22b72d9-d416-47f9-92af-ea1de6fb5583"
/>



Release Notes:

- Fixed an issue where Markdown tables would sometimes show double
borders.

Change summary

crates/markdown/src/markdown.rs                  | 82 +++++++++++++----
crates/markdown_preview/src/markdown_renderer.rs | 12 +
2 files changed, 69 insertions(+), 25 deletions(-)

Detailed changes

crates/markdown/src/markdown.rs 🔗

@@ -1063,9 +1063,7 @@ impl Element for MarkdownElement {
                         }
                         MarkdownTag::MetadataBlock(_) => {}
                         MarkdownTag::Table(alignments) => {
-                            builder.table_alignments = alignments.clone();
-                            builder.table_row_index = 0;
-                            builder.in_table_head = false;
+                            builder.table.start(alignments.clone());
 
                             let column_count = alignments.len();
                             builder.push_div(
@@ -1081,7 +1079,7 @@ impl Element for MarkdownElement {
                                     })
                                     .size_full()
                                     .mb_2()
-                                    .border_1()
+                                    .border(px(1.5))
                                     .border_color(cx.theme().colors().border)
                                     .rounded_sm()
                                     .overflow_hidden(),
@@ -1090,21 +1088,24 @@ impl Element for MarkdownElement {
                             );
                         }
                         MarkdownTag::TableHead => {
-                            builder.in_table_head = true;
+                            builder.table.start_head();
                             builder.push_text_style(TextStyleRefinement {
                                 font_weight: Some(FontWeight::SEMIBOLD),
                                 ..Default::default()
                             });
                         }
-                        MarkdownTag::TableRow => {}
+                        MarkdownTag::TableRow => {
+                            builder.table.start_row();
+                        }
                         MarkdownTag::TableCell => {
-                            let is_header = builder.in_table_head;
-                            let row_index = builder.table_row_index;
+                            let is_header = builder.table.in_head;
+                            let row_index = builder.table.row_index;
+                            let col_index = builder.table.col_index;
 
                             builder.push_div(
                                 div()
-                                    .min_w_0()
-                                    .border(px(0.5))
+                                    .when(col_index > 0, |this| this.border_l_1())
+                                    .when(row_index > 0, |this| this.border_t_1())
                                     .border_color(cx.theme().colors().border)
                                     .px_1()
                                     .py_0p5()
@@ -1227,19 +1228,18 @@ impl Element for MarkdownElement {
                     }
                     MarkdownTagEnd::Table => {
                         builder.pop_div();
-                        builder.table_alignments.clear();
-                        builder.in_table_head = false;
-                        builder.table_row_index = 0;
+                        builder.table.end();
                     }
                     MarkdownTagEnd::TableHead => {
                         builder.pop_text_style();
-                        builder.in_table_head = false;
+                        builder.table.end_head();
                     }
                     MarkdownTagEnd::TableRow => {
-                        builder.table_row_index += 1;
+                        builder.table.end_row();
                     }
                     MarkdownTagEnd::TableCell => {
                         builder.pop_div();
+                        builder.table.end_cell();
                     }
                     _ => log::debug!("unsupported markdown tag end: {:?}", tag),
                 },
@@ -1494,6 +1494,50 @@ impl ParentElement for AnyDiv {
     }
 }
 
+#[derive(Default)]
+struct TableState {
+    alignments: Vec<Alignment>,
+    in_head: bool,
+    row_index: usize,
+    col_index: usize,
+}
+
+impl TableState {
+    fn start(&mut self, alignments: Vec<Alignment>) {
+        self.alignments = alignments;
+        self.in_head = false;
+        self.row_index = 0;
+        self.col_index = 0;
+    }
+
+    fn end(&mut self) {
+        self.alignments.clear();
+        self.in_head = false;
+        self.row_index = 0;
+        self.col_index = 0;
+    }
+
+    fn start_head(&mut self) {
+        self.in_head = true;
+    }
+
+    fn end_head(&mut self) {
+        self.in_head = false;
+    }
+
+    fn start_row(&mut self) {
+        self.col_index = 0;
+    }
+
+    fn end_row(&mut self) {
+        self.row_index += 1;
+    }
+
+    fn end_cell(&mut self) {
+        self.col_index += 1;
+    }
+}
+
 struct MarkdownElementBuilder {
     div_stack: Vec<AnyDiv>,
     rendered_lines: Vec<RenderedLine>,
@@ -1505,9 +1549,7 @@ struct MarkdownElementBuilder {
     text_style_stack: Vec<TextStyleRefinement>,
     code_block_stack: Vec<Option<Arc<Language>>>,
     list_stack: Vec<ListStackEntry>,
-    table_alignments: Vec<Alignment>,
-    in_table_head: bool,
-    table_row_index: usize,
+    table: TableState,
     syntax_theme: Arc<SyntaxTheme>,
 }
 
@@ -1543,9 +1585,7 @@ impl MarkdownElementBuilder {
             text_style_stack: Vec::new(),
             code_block_stack: Vec::new(),
             list_stack: Vec::new(),
-            table_alignments: Vec::new(),
-            in_table_head: false,
-            table_row_index: 0,
+            table: TableState::default(),
             syntax_theme,
         }
     }

crates/markdown_preview/src/markdown_renderer.rs 🔗

@@ -9,7 +9,7 @@ use gpui::{
     AbsoluteLength, AnyElement, App, AppContext as _, ClipboardItem, Context, Div, Element,
     ElementId, Entity, HighlightStyle, Hsla, ImageSource, InteractiveText, IntoElement, Keystroke,
     Modifiers, ParentElement, Render, Resource, SharedString, Styled, StyledText, TextStyle,
-    WeakEntity, Window, div, img, rems,
+    WeakEntity, Window, div, img, px, rems,
 };
 use settings::Settings;
 use std::{
@@ -521,7 +521,8 @@ fn render_markdown_table(parsed: &ParsedMarkdownTable, cx: &mut RenderContext) -
                 .children(render_markdown_text(&cell.children, cx))
                 .px_2()
                 .py_1()
-                .border_1()
+                .when(col_idx > 0, |this| this.border_l_1())
+                .when(row_idx > 0, |this| this.border_t_1())
                 .border_color(cx.border_color)
                 .when(cell.is_header, |this| {
                     this.bg(cx.title_bar_background_color)
@@ -551,7 +552,8 @@ fn render_markdown_table(parsed: &ParsedMarkdownTable, cx: &mut RenderContext) -
             }
 
             let empty_cell = div()
-                .border_1()
+                .when(col_idx > 0, |this| this.border_l_1())
+                .when(row_idx > 0, |this| this.border_t_1())
                 .border_color(cx.border_color)
                 .when(row_idx % 2 == 1, |this| this.bg(cx.panel_background_color));
 
@@ -568,8 +570,10 @@ fn render_markdown_table(parsed: &ParsedMarkdownTable, cx: &mut RenderContext) -
             div()
                 .grid()
                 .grid_cols(max_column_count as u16)
-                .border_1()
+                .border(px(1.5))
                 .border_color(cx.border_color)
+                .rounded_sm()
+                .overflow_hidden()
                 .children(cells),
         )
         .into_any()