From e6b8b30e2218dbe806a2787a668186e5b7130490 Mon Sep 17 00:00:00 2001 From: David Alecrim <35930364+davidalecrim1@users.noreply.github.com> Date: Thu, 7 May 2026 09:26:13 -0300 Subject: [PATCH] markdown: Improve table cell alignment (#53465) ## Summary Markdown preview tables kept text pinned to the top of a row when a neighboring cell contained a taller image. This made mixed text-and-image tables look unbalanced and inconsistent with common editor behavior. This change makes table text stay visually centered within taller rows so Markdown tables are easier to scan and match expected rendering more closely. ## Before / After | Before | After | | --- | --- | | Screenshot 2026-04-08 at 19 55 50 | Screenshot 2026-04-08 at 21 47
31 | ## References Inspired by comparing this with VS Code preview Screenshot 2026-04-08 at 21 54 06 Release Notes: - Improved Markdown preview table cells to vertically center content in tall rows and respect column alignment from the table header. --------- Co-authored-by: Smit Barmase --- crates/markdown/src/html/html_rendering.rs | 31 +++++++++- crates/markdown/src/markdown.rs | 68 +++++++++++++++++----- 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/crates/markdown/src/html/html_rendering.rs b/crates/markdown/src/html/html_rendering.rs index 27e9b70e8e80ab86fb5f773cf873a3f1960d595e..af46dfe2b2c0398c255b18907273b7b6ee73ea77 100644 --- a/crates/markdown/src/html/html_rendering.rs +++ b/crates/markdown/src/html/html_rendering.rs @@ -1,6 +1,8 @@ use std::ops::Range; -use gpui::{App, FontStyle, FontWeight, StrikethroughStyle, TextStyleRefinement, UnderlineStyle}; +use gpui::{ + App, FontStyle, FontWeight, StrikethroughStyle, TextAlign, TextStyleRefinement, UnderlineStyle, +}; use pulldown_cmark::Alignment; use ui::prelude::*; @@ -245,14 +247,24 @@ impl MarkdownElement { } let max_span = max_column_count.saturating_sub(column_index); + let text_align = match cell.alignment { + Alignment::Left => TextAlign::Left, + Alignment::Center => TextAlign::Center, + Alignment::Right => TextAlign::Right, + _ => self.style.base_text_style.text_align, + }; + let mut cell_div = div() .col_span(cell.col_span.min(max_span) as u16) .row_span(cell.row_span.min(total_rows - row_index) as u16) + .flex() + .flex_col() .when(column_index > 0, |this| this.border_l_1()) .when(row_index > 0, |this| this.border_t_1()) .border_color(cx.theme().colors().border) .px_2() .py_1() + .h_full() .when(cell.is_header, |this| { this.bg(cx.theme().colors().title_bar_background) }) @@ -266,7 +278,22 @@ impl MarkdownElement { _ => cell_div, }; + builder.push_text_style(TextStyleRefinement { + text_align: Some(text_align), + ..Default::default() + }); builder.push_div(cell_div, &table.source_range, markdown_end); + builder.push_div( + div() + .flex() + .flex_col() + .flex_1() + .w_full() + .justify_center() + .text_align(text_align), + &table.source_range, + markdown_end, + ); self.render_html_paragraph( &cell.children, source_allocator, @@ -275,6 +302,8 @@ impl MarkdownElement { markdown_end, ); builder.pop_div(); + builder.pop_div(); + builder.pop_text_style(); for row_offset in 0..cell.row_span { for column_offset in 0..cell.col_span { diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index dce9633c87b050c65163530081f865c8cb8a21b8..937e38c39509cc580f2f5a571339ce0dced83efb 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -2000,20 +2000,49 @@ impl Element for MarkdownElement { let is_header = builder.table.in_head; let row_index = builder.table.row_index; let col_index = builder.table.col_index; + let alignment = builder.table.alignments.get(col_index).copied(); + let text_align = match alignment { + Some(Alignment::Left) => TextAlign::Left, + Some(Alignment::Center) => TextAlign::Center, + Some(Alignment::Right) => TextAlign::Right, + _ => self.style.base_text_style.text_align, + }; + + let mut cell_div = div() + .flex() + .flex_col() + .h_full() + .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() + .when(is_header, |this| { + this.bg(cx.theme().colors().title_bar_background) + }) + .when(!is_header && row_index % 2 == 1, |this| { + this.bg(cx.theme().colors().panel_background) + }); + + cell_div = match alignment { + Some(Alignment::Center) => cell_div.items_center(), + Some(Alignment::Right) => cell_div.items_end(), + _ => cell_div, + }; + builder.push_text_style(TextStyleRefinement { + text_align: Some(text_align), + ..Default::default() + }); + builder.push_div(cell_div, range, markdown_end); builder.push_div( div() - .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() - .when(is_header, |this| { - this.bg(cx.theme().colors().title_bar_background) - }) - .when(!is_header && row_index % 2 == 1, |this| { - this.bg(cx.theme().colors().panel_background) - }), + .flex() + .flex_col() + .flex_1() + .w_full() + .justify_center() + .text_align(text_align), range, markdown_end, ); @@ -2113,6 +2142,8 @@ impl Element for MarkdownElement { MarkdownTagEnd::TableCell => { builder.replace_pending_checkbox(self.on_checkbox_toggle.clone()); builder.pop_div(); + builder.pop_div(); + builder.pop_text_style(); builder.table.end_cell(); } MarkdownTagEnd::FootnoteDefinition => { @@ -2702,7 +2733,7 @@ impl MarkdownElementBuilder { ) .fill(); - let element = if let Some(on_toggle) = on_toggle { + let checkbox = if let Some(on_toggle) = on_toggle { checkbox .on_click(move |_state, window, cx| { on_toggle(marker_source.clone(), !checked, window, cx); @@ -2711,7 +2742,18 @@ impl MarkdownElementBuilder { } else { checkbox.visualization_only(true).into_any_element() }; - self.div_stack.last_mut().unwrap().extend([element]); + + let mut checkbox_container = h_flex().w_full(); + checkbox_container = match self.text_style().text_align { + TextAlign::Left => checkbox_container.justify_start(), + TextAlign::Center => checkbox_container.justify_center(), + TextAlign::Right => checkbox_container.justify_end(), + }; + + self.div_stack + .last_mut() + .unwrap() + .extend([checkbox_container.child(checkbox).into_any_element()]); } fn source_range_for_rendered(&self, rendered: &Range) -> Option> {