mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-02 07:04:33 +00:00
markdown: Add support for HTML table column align attribute (#41163)
This PR allows you to define `align="right"` for example to change the default alignment on **HTML** table columns. This PR also refactors where we store the alignments in order to make it so you can define it column based instead of only row based. See that the `Revenue` column is left aligned instead of the default `centered`. **Result** <img width="1161" height="177" alt="Screenshot 2025-10-25 at 11 01 38" src="https://github.com/user-attachments/assets/94bda4f0-00c1-4726-a3bd-99b3f2573ef5" /> **Code example** ```HTML <table> <tr> <th rowspan="2">Region</th> <th colspan="2" align="left">Revenue</th> <th rowspan="2">Growth</th> </tr> <tr> <th>Q2 2024</th> <th>Q3 2024</th> </tr> <tr> <td>North America</td> <td>$2.8M</td> <td>$2.4B</td> <td>+85,614%</td> </tr> <tr> <td>Europe</td> <td>$1.2M</td> <td>$1.9B</td> <td>+158,233%</td> </tr> <tr> <td>Asia-Pacific</td> <td>$0.5M</td> <td>$1.4B</td> <td>+279,900%</td> </tr> </table> ``` Release Notes: - markdown preview: Add support for `HTML` table column `align` attribute
This commit is contained in:
parent
986ca19516
commit
79ef10bfc3
3 changed files with 172 additions and 40 deletions
|
|
@ -106,7 +106,6 @@ pub struct ParsedMarkdownTable {
|
|||
pub source_range: Range<usize>,
|
||||
pub header: Vec<ParsedMarkdownTableRow>,
|
||||
pub body: Vec<ParsedMarkdownTableRow>,
|
||||
pub column_alignments: Vec<ParsedMarkdownTableAlignment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
|
|
@ -126,6 +125,7 @@ pub struct ParsedMarkdownTableColumn {
|
|||
pub row_span: usize,
|
||||
pub is_header: bool,
|
||||
pub children: MarkdownParagraph,
|
||||
pub alignment: ParsedMarkdownTableAlignment,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
|||
|
|
@ -466,7 +466,10 @@ impl<'a> MarkdownParser<'a> {
|
|||
let mut body = vec![];
|
||||
let mut row_columns = vec![];
|
||||
let mut in_header = true;
|
||||
let column_alignments = alignment.iter().map(Self::convert_alignment).collect();
|
||||
let column_alignments = alignment
|
||||
.iter()
|
||||
.map(Self::convert_alignment)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
loop {
|
||||
if self.eof() {
|
||||
|
|
@ -489,6 +492,10 @@ impl<'a> MarkdownParser<'a> {
|
|||
row_span: 1,
|
||||
is_header: in_header,
|
||||
children: cell_contents,
|
||||
alignment: column_alignments
|
||||
.get(row_columns.len())
|
||||
.copied()
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
Event::End(TagEnd::TableHead) | Event::End(TagEnd::TableRow) => {
|
||||
|
|
@ -515,7 +522,6 @@ impl<'a> MarkdownParser<'a> {
|
|||
source_range,
|
||||
header,
|
||||
body,
|
||||
column_alignments,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -988,6 +994,8 @@ impl<'a> MarkdownParser<'a> {
|
|||
let mut children = MarkdownParagraph::new();
|
||||
self.consume_paragraph(source_range, node, &mut children);
|
||||
|
||||
let is_header = matches!(name.local, local_name!("th"));
|
||||
|
||||
Some(ParsedMarkdownTableColumn {
|
||||
col_span: std::cmp::max(
|
||||
Self::attr_value(attrs, local_name!("colspan"))
|
||||
|
|
@ -1001,8 +1009,22 @@ impl<'a> MarkdownParser<'a> {
|
|||
.unwrap_or(1),
|
||||
1,
|
||||
),
|
||||
is_header: matches!(name.local, local_name!("th")),
|
||||
is_header,
|
||||
children,
|
||||
alignment: Self::attr_value(attrs, local_name!("align"))
|
||||
.and_then(|align| match align.as_str() {
|
||||
"left" => Some(ParsedMarkdownTableAlignment::Left),
|
||||
"center" => Some(ParsedMarkdownTableAlignment::Center),
|
||||
"right" => Some(ParsedMarkdownTableAlignment::Right),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
if is_header {
|
||||
ParsedMarkdownTableAlignment::Center
|
||||
} else {
|
||||
ParsedMarkdownTableAlignment::default()
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
|
|
@ -1155,7 +1177,6 @@ impl<'a> MarkdownParser<'a> {
|
|||
Some(ParsedMarkdownTable {
|
||||
source_range,
|
||||
body: body_rows,
|
||||
column_alignments: Vec::default(),
|
||||
header: header_rows,
|
||||
})
|
||||
} else {
|
||||
|
|
@ -1653,17 +1674,53 @@ mod tests {
|
|||
children: vec![ParsedMarkdownElement::Table(table(
|
||||
0..366,
|
||||
vec![row(vec![
|
||||
column(1, 1, true, text("Id", 0..366)),
|
||||
column(1, 1, true, text("Name ", 0..366))
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
true,
|
||||
text("Id", 0..366),
|
||||
ParsedMarkdownTableAlignment::Center
|
||||
),
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
true,
|
||||
text("Name ", 0..366),
|
||||
ParsedMarkdownTableAlignment::Center
|
||||
)
|
||||
])],
|
||||
vec![
|
||||
row(vec![
|
||||
column(1, 1, false, text("1", 0..366)),
|
||||
column(1, 1, false, text("Chris", 0..366))
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
text("1", 0..366),
|
||||
ParsedMarkdownTableAlignment::None
|
||||
),
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
text("Chris", 0..366),
|
||||
ParsedMarkdownTableAlignment::None
|
||||
)
|
||||
]),
|
||||
row(vec![
|
||||
column(1, 1, false, text("2", 0..366)),
|
||||
column(1, 1, false, text("Dennis", 0..366))
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
text("2", 0..366),
|
||||
ParsedMarkdownTableAlignment::None
|
||||
),
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
text("Dennis", 0..366),
|
||||
ParsedMarkdownTableAlignment::None
|
||||
)
|
||||
]),
|
||||
],
|
||||
))],
|
||||
|
|
@ -1697,12 +1754,36 @@ mod tests {
|
|||
vec![],
|
||||
vec![
|
||||
row(vec![
|
||||
column(1, 1, false, text("1", 0..240)),
|
||||
column(1, 1, false, text("Chris", 0..240))
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
text("1", 0..240),
|
||||
ParsedMarkdownTableAlignment::None
|
||||
),
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
text("Chris", 0..240),
|
||||
ParsedMarkdownTableAlignment::None
|
||||
)
|
||||
]),
|
||||
row(vec![
|
||||
column(1, 1, false, text("2", 0..240)),
|
||||
column(1, 1, false, text("Dennis", 0..240))
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
text("2", 0..240),
|
||||
ParsedMarkdownTableAlignment::None
|
||||
),
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
text("Dennis", 0..240),
|
||||
ParsedMarkdownTableAlignment::None
|
||||
)
|
||||
]),
|
||||
],
|
||||
))],
|
||||
|
|
@ -1730,8 +1811,20 @@ mod tests {
|
|||
children: vec![ParsedMarkdownElement::Table(table(
|
||||
0..150,
|
||||
vec![row(vec![
|
||||
column(1, 1, true, text("Id", 0..150)),
|
||||
column(1, 1, true, text("Name", 0..150))
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
true,
|
||||
text("Id", 0..150),
|
||||
ParsedMarkdownTableAlignment::Center
|
||||
),
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
true,
|
||||
text("Name", 0..150),
|
||||
ParsedMarkdownTableAlignment::Center
|
||||
)
|
||||
])],
|
||||
vec![],
|
||||
))],
|
||||
|
|
@ -1915,8 +2008,20 @@ Some other content
|
|||
let expected_table = table(
|
||||
0..48,
|
||||
vec![row(vec![
|
||||
column(1, 1, true, text("Header 1", 1..11)),
|
||||
column(1, 1, true, text("Header 2", 12..22)),
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
true,
|
||||
text("Header 1", 1..11),
|
||||
ParsedMarkdownTableAlignment::None,
|
||||
),
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
true,
|
||||
text("Header 2", 12..22),
|
||||
ParsedMarkdownTableAlignment::None,
|
||||
),
|
||||
])],
|
||||
vec![],
|
||||
);
|
||||
|
|
@ -1938,17 +2043,53 @@ Some other content
|
|||
let expected_table = table(
|
||||
0..95,
|
||||
vec![row(vec![
|
||||
column(1, 1, true, text("Header 1", 1..11)),
|
||||
column(1, 1, true, text("Header 2", 12..22)),
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
true,
|
||||
text("Header 1", 1..11),
|
||||
ParsedMarkdownTableAlignment::None,
|
||||
),
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
true,
|
||||
text("Header 2", 12..22),
|
||||
ParsedMarkdownTableAlignment::None,
|
||||
),
|
||||
])],
|
||||
vec![
|
||||
row(vec![
|
||||
column(1, 1, false, text("Cell 1", 49..59)),
|
||||
column(1, 1, false, text("Cell 2", 60..70)),
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
text("Cell 1", 49..59),
|
||||
ParsedMarkdownTableAlignment::None,
|
||||
),
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
text("Cell 2", 60..70),
|
||||
ParsedMarkdownTableAlignment::None,
|
||||
),
|
||||
]),
|
||||
row(vec![
|
||||
column(1, 1, false, text("Cell 3", 73..83)),
|
||||
column(1, 1, false, text("Cell 4", 84..94)),
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
text("Cell 3", 73..83),
|
||||
ParsedMarkdownTableAlignment::None,
|
||||
),
|
||||
column(
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
text("Cell 4", 84..94),
|
||||
ParsedMarkdownTableAlignment::None,
|
||||
),
|
||||
]),
|
||||
],
|
||||
);
|
||||
|
|
@ -2410,7 +2551,6 @@ fn main() {
|
|||
body: Vec<ParsedMarkdownTableRow>,
|
||||
) -> ParsedMarkdownTable {
|
||||
ParsedMarkdownTable {
|
||||
column_alignments: Vec::new(),
|
||||
source_range,
|
||||
header,
|
||||
body,
|
||||
|
|
@ -2426,12 +2566,14 @@ fn main() {
|
|||
row_span: usize,
|
||||
is_header: bool,
|
||||
children: MarkdownParagraph,
|
||||
alignment: ParsedMarkdownTableAlignment,
|
||||
) -> ParsedMarkdownTableColumn {
|
||||
ParsedMarkdownTableColumn {
|
||||
col_span,
|
||||
row_span,
|
||||
is_header,
|
||||
children,
|
||||
alignment,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -497,7 +497,7 @@ fn render_markdown_table(parsed: &ParsedMarkdownTable, cx: &mut RenderContext) -
|
|||
for (row_idx, row) in parsed.header.iter().chain(parsed.body.iter()).enumerate() {
|
||||
let mut col_idx = 0;
|
||||
|
||||
for (cell_idx, cell) in row.columns.iter().enumerate() {
|
||||
for cell in row.columns.iter() {
|
||||
// Skip columns occupied by row-spanning cells from previous rows
|
||||
while col_idx < max_column_count && grid_occupied[row_idx][col_idx] {
|
||||
col_idx += 1;
|
||||
|
|
@ -507,19 +507,7 @@ fn render_markdown_table(parsed: &ParsedMarkdownTable, cx: &mut RenderContext) -
|
|||
break;
|
||||
}
|
||||
|
||||
let alignment = parsed
|
||||
.column_alignments
|
||||
.get(cell_idx)
|
||||
.copied()
|
||||
.unwrap_or_else(|| {
|
||||
if cell.is_header {
|
||||
ParsedMarkdownTableAlignment::Center
|
||||
} else {
|
||||
ParsedMarkdownTableAlignment::None
|
||||
}
|
||||
});
|
||||
|
||||
let container = match alignment {
|
||||
let container = match cell.alignment {
|
||||
ParsedMarkdownTableAlignment::Left | ParsedMarkdownTableAlignment::None => div(),
|
||||
ParsedMarkdownTableAlignment::Center => v_flex().items_center(),
|
||||
ParsedMarkdownTableAlignment::Right => v_flex().items_end(),
|
||||
|
|
@ -917,6 +905,7 @@ mod tests {
|
|||
row_span,
|
||||
is_header: false,
|
||||
children,
|
||||
alignment: ParsedMarkdownTableAlignment::None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -930,6 +919,7 @@ mod tests {
|
|||
row_span,
|
||||
is_header: false,
|
||||
children,
|
||||
alignment: ParsedMarkdownTableAlignment::None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue