diff --git a/Cargo.lock b/Cargo.lock index 6302e78434600aa8309e2d85e454f400a43b8029..d358f28f84117c75d87b1395ef51a348ed98ca36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5902,6 +5902,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-recursion 1.0.5", + "collections", "editor", "gpui", "language", diff --git a/crates/markdown_preview/Cargo.toml b/crates/markdown_preview/Cargo.toml index 3a46d2b46df0ccd8a22cfafe7a91ba617effb85b..e1e514a63ed9b179e7dcf275d4a308baffe7f243 100644 --- a/crates/markdown_preview/Cargo.toml +++ b/crates/markdown_preview/Cargo.toml @@ -17,6 +17,7 @@ test-support = [] [dependencies] anyhow.workspace = true async-recursion.workspace = true +collections.workspace = true editor.workspace = true gpui.workspace = true language.workspace = true diff --git a/crates/markdown_preview/src/markdown_elements.rs b/crates/markdown_preview/src/markdown_elements.rs index 75dc0bcbf7963a97adc958cceb812a8c3595cfa4..08d25216a04c7721039950d89085e7082922aeda 100644 --- a/crates/markdown_preview/src/markdown_elements.rs +++ b/crates/markdown_preview/src/markdown_elements.rs @@ -8,8 +8,7 @@ use std::{fmt::Display, ops::Range, path::PathBuf}; #[cfg_attr(test, derive(PartialEq))] pub enum ParsedMarkdownElement { Heading(ParsedMarkdownHeading), - /// An ordered or unordered list of items. - List(ParsedMarkdownList), + ListItem(ParsedMarkdownListItem), Table(ParsedMarkdownTable), BlockQuote(ParsedMarkdownBlockQuote), CodeBlock(ParsedMarkdownCodeBlock), @@ -22,7 +21,7 @@ impl ParsedMarkdownElement { pub fn source_range(&self) -> Range { match self { Self::Heading(heading) => heading.source_range.clone(), - Self::List(list) => list.source_range.clone(), + Self::ListItem(list_item) => list_item.source_range.clone(), Self::Table(table) => table.source_range.clone(), Self::BlockQuote(block_quote) => block_quote.source_range.clone(), Self::CodeBlock(code_block) => code_block.source_range.clone(), @@ -30,6 +29,10 @@ impl ParsedMarkdownElement { Self::HorizontalRule(range) => range.clone(), } } + + pub fn is_list_item(&self) -> bool { + matches!(self, Self::ListItem(_)) + } } #[derive(Debug)] @@ -38,20 +41,14 @@ pub struct ParsedMarkdown { pub children: Vec, } -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq))] -pub struct ParsedMarkdownList { - pub source_range: Range, - pub children: Vec, -} - #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] pub struct ParsedMarkdownListItem { + pub source_range: Range, /// How many indentations deep this item is. pub depth: u16, pub item_type: ParsedMarkdownListItemType, - pub contents: Vec>, + pub content: Vec, } #[derive(Debug)] @@ -129,7 +126,7 @@ impl ParsedMarkdownTableRow { #[cfg_attr(test, derive(PartialEq))] pub struct ParsedMarkdownBlockQuote { pub source_range: Range, - pub children: Vec>, + pub children: Vec, } #[derive(Debug)] diff --git a/crates/markdown_preview/src/markdown_parser.rs b/crates/markdown_preview/src/markdown_parser.rs index fc37d3e3f5f56a11ede2ac564502556a12995e55..27a5e53245afc0f7cfd7f30fabf92d4f26c598e6 100644 --- a/crates/markdown_preview/src/markdown_parser.rs +++ b/crates/markdown_preview/src/markdown_parser.rs @@ -1,5 +1,6 @@ use crate::markdown_elements::*; use async_recursion::async_recursion; +use collections::FxHashMap; use gpui::FontWeight; use language::LanguageRegistry; use pulldown_cmark::{Alignment, Event, Options, Parser, Tag, TagEnd}; @@ -98,20 +99,22 @@ impl<'a> MarkdownParser<'a> { async fn parse_document(mut self) -> Self { while !self.eof() { if let Some(block) = self.parse_block().await { - self.parsed.push(block); + self.parsed.extend(block); } } self } - async fn parse_block(&mut self) -> Option { + #[async_recursion] + async fn parse_block(&mut self) -> Option> { let (current, source_range) = self.current().unwrap(); + let source_range = source_range.clone(); match current { Event::Start(tag) => match tag { Tag::Paragraph => { self.cursor += 1; - let text = self.parse_text(false); - Some(ParsedMarkdownElement::Paragraph(text)) + let text = self.parse_text(false, Some(source_range)); + Some(vec![ParsedMarkdownElement::Paragraph(text)]) } Tag::Heading { level, @@ -122,24 +125,24 @@ impl<'a> MarkdownParser<'a> { let level = *level; self.cursor += 1; let heading = self.parse_heading(level); - Some(ParsedMarkdownElement::Heading(heading)) + Some(vec![ParsedMarkdownElement::Heading(heading)]) } Tag::Table(alignment) => { let alignment = alignment.clone(); self.cursor += 1; let table = self.parse_table(alignment); - Some(ParsedMarkdownElement::Table(table)) + Some(vec![ParsedMarkdownElement::Table(table)]) } Tag::List(order) => { let order = *order; self.cursor += 1; - let list = self.parse_list(1, order).await; - Some(ParsedMarkdownElement::List(list)) + let list = self.parse_list(order).await; + Some(list) } Tag::BlockQuote => { self.cursor += 1; let block_quote = self.parse_block_quote().await; - Some(ParsedMarkdownElement::BlockQuote(block_quote)) + Some(vec![ParsedMarkdownElement::BlockQuote(block_quote)]) } Tag::CodeBlock(kind) => { let language = match kind { @@ -156,7 +159,7 @@ impl<'a> MarkdownParser<'a> { self.cursor += 1; let code_block = self.parse_code_block(language).await; - Some(ParsedMarkdownElement::CodeBlock(code_block)) + Some(vec![ParsedMarkdownElement::CodeBlock(code_block)]) } _ => { self.cursor += 1; @@ -166,7 +169,7 @@ impl<'a> MarkdownParser<'a> { Event::Rule => { let source_range = source_range.clone(); self.cursor += 1; - Some(ParsedMarkdownElement::HorizontalRule(source_range)) + Some(vec![ParsedMarkdownElement::HorizontalRule(source_range)]) } _ => { self.cursor += 1; @@ -175,9 +178,16 @@ impl<'a> MarkdownParser<'a> { } } - fn parse_text(&mut self, should_complete_on_soft_break: bool) -> ParsedMarkdownText { - let (_current, source_range) = self.previous().unwrap(); - let source_range = source_range.clone(); + fn parse_text( + &mut self, + should_complete_on_soft_break: bool, + source_range: Option>, + ) -> ParsedMarkdownText { + let source_range = source_range.unwrap_or_else(|| { + self.current() + .map(|(_, range)| range.clone()) + .unwrap_or_default() + }); let mut text = String::new(); let mut bold_depth = 0; @@ -379,7 +389,7 @@ impl<'a> MarkdownParser<'a> { fn parse_heading(&mut self, level: pulldown_cmark::HeadingLevel) -> ParsedMarkdownHeading { let (_event, source_range) = self.previous().unwrap(); let source_range = source_range.clone(); - let text = self.parse_text(true); + let text = self.parse_text(true, None); // Advance past the heading end tag self.cursor += 1; @@ -415,7 +425,8 @@ impl<'a> MarkdownParser<'a> { break; } - let (current, _source_range) = self.current().unwrap(); + let (current, source_range) = self.current().unwrap(); + let source_range = source_range.clone(); match current { Event::Start(Tag::TableHead) | Event::Start(Tag::TableRow) @@ -424,7 +435,7 @@ impl<'a> MarkdownParser<'a> { } Event::Start(Tag::TableCell) => { self.cursor += 1; - let cell_contents = self.parse_text(false); + let cell_contents = self.parse_text(false, Some(source_range)); current_row.push(cell_contents); } Event::End(TagEnd::TableHead) | Event::End(TagEnd::TableRow) => { @@ -465,35 +476,53 @@ impl<'a> MarkdownParser<'a> { } } - #[async_recursion] - async fn parse_list(&mut self, depth: u16, order: Option) -> ParsedMarkdownList { - let (_event, source_range) = self.previous().unwrap(); - let source_range = source_range.clone(); - let mut children = vec![]; - let mut inside_list_item = false; - let mut order = order; + async fn parse_list(&mut self, order: Option) -> Vec { + let (_, list_source_range) = self.previous().unwrap(); + + let mut items = Vec::new(); + let mut items_stack = vec![Vec::new()]; + let mut depth = 1; let mut task_item = None; + let mut order = order; + let mut order_stack = Vec::new(); - let mut current_list_items: Vec> = vec![]; + let mut insertion_indices = FxHashMap::default(); + let mut source_ranges = FxHashMap::default(); + let mut start_item_range = list_source_range.clone(); while !self.eof() { - let (current, _source_range) = self.current().unwrap(); + let (current, source_range) = self.current().unwrap(); match current { - Event::Start(Tag::List(order)) => { - let order = *order; - self.cursor += 1; + Event::Start(Tag::List(new_order)) => { + if items_stack.last().is_some() && !insertion_indices.contains_key(&depth) { + insertion_indices.insert(depth, items.len()); + } + + // We will use the start of the nested list as the end for the current item's range, + // because we don't care about the hierarchy of list items + if !source_ranges.contains_key(&depth) { + source_ranges.insert(depth, start_item_range.start..source_range.start); + } - let inner_list = self.parse_list(depth + 1, order).await; - let block = ParsedMarkdownElement::List(inner_list); - current_list_items.push(Box::new(block)); + order_stack.push(order); + order = *new_order; + self.cursor += 1; + depth += 1; } Event::End(TagEnd::List(_)) => { + order = order_stack.pop().flatten(); self.cursor += 1; - break; + depth -= 1; + + if depth == 0 { + break; + } } Event::Start(Tag::Item) => { + start_item_range = source_range.clone(); + self.cursor += 1; - inside_list_item = true; + items_stack.push(Vec::new()); // Check for task list marker (`- [ ]` or `- [x]`) if let Some(event) = self.current_event() { @@ -508,17 +537,21 @@ impl<'a> MarkdownParser<'a> { } } - if let Some(event) = self.current_event() { + if let Some((event, range)) = self.current() { // This is a plain list item. // For example `- some text` or `1. [Docs](./docs.md)` if MarkdownParser::is_text_like(event) { - let text = self.parse_text(false); + let text = self.parse_text(false, Some(range.clone())); let block = ParsedMarkdownElement::Paragraph(text); - current_list_items.push(Box::new(block)); + if let Some(content) = items_stack.last_mut() { + content.push(block); + } } else { let block = self.parse_block().await; if let Some(block) = block { - current_list_items.push(Box::new(block)); + if let Some(content) = items_stack.last_mut() { + content.extend(block); + } } } } @@ -543,34 +576,55 @@ impl<'a> MarkdownParser<'a> { order = Some(current + 1); } - let contents = std::mem::replace(&mut current_list_items, vec![]); + if let Some(content) = items_stack.pop() { + let source_range = source_ranges + .remove(&depth) + .unwrap_or(start_item_range.clone()); + + // We need to remove the last character of the source range, because it includes the newline character + let source_range = source_range.start..source_range.end - 1; + let item = ParsedMarkdownElement::ListItem(ParsedMarkdownListItem { + source_range, + content, + depth, + item_type, + }); - children.push(ParsedMarkdownListItem { - contents, - depth, - item_type, - }); + if let Some(index) = insertion_indices.get(&depth) { + items.insert(*index, item); + insertion_indices.remove(&depth); + } else { + items.push(item); + } + } - inside_list_item = false; task_item = None; } _ => { - if !inside_list_item { + if depth == 0 { break; } - + // This can only happen if a list item starts with more then one paragraph, + // or the list item contains blocks that should be rendered after the nested list items let block = self.parse_block().await; if let Some(block) = block { - current_list_items.push(Box::new(block)); + if let Some(items_stack) = items_stack.last_mut() { + // If we did not insert any nested items yet (in this case insertion index is set), we can append the block to the current list item + if !insertion_indices.contains_key(&depth) { + items_stack.extend(block); + continue; + } + } + + // Otherwise we need to insert the block after all the nested items + // that have been parsed so far + items.extend(block); } } } } - ParsedMarkdownList { - source_range, - children, - } + items } #[async_recursion] @@ -579,13 +633,13 @@ impl<'a> MarkdownParser<'a> { let source_range = source_range.clone(); let mut nested_depth = 1; - let mut children: Vec> = vec![]; + let mut children: Vec = vec![]; while !self.eof() { let block = self.parse_block().await; if let Some(block) = block { - children.push(Box::new(block)); + children.extend(block); } else { break; } @@ -674,7 +728,6 @@ mod tests { use language::{tree_sitter_rust, HighlightId, Language, LanguageConfig, LanguageMatcher}; use pretty_assertions::assert_eq; - use ParsedMarkdownElement::*; use ParsedMarkdownListItemType::*; async fn parse(input: &str) -> ParsedMarkdown { @@ -688,9 +741,9 @@ mod tests { assert_eq!( parsed.children, vec![ - h1(text("Heading one", 0..14), 0..14), - h2(text("Heading two", 14..29), 14..29), - h3(text("Heading three", 29..46), 29..46), + h1(text("Heading one", 2..13), 0..14), + h2(text("Heading two", 17..28), 14..29), + h3(text("Heading three", 33..46), 29..46), ] ); } @@ -711,7 +764,7 @@ mod tests { assert_eq!( parsed.children, - vec![h1(text("Zed", 0..6), 0..6), p("The editor", 6..16),] + vec![h1(text("Zed", 2..5), 0..6), p("The editor", 6..16),] ); } @@ -881,14 +934,11 @@ Some other content assert_eq!( parsed.children, - vec![list( - vec![ - list_item(1, Unordered, vec![p("Item 1", 0..9)]), - list_item(1, Unordered, vec![p("Item 2", 9..18)]), - list_item(1, Unordered, vec![p("Item 3", 18..27)]), - ], - 0..27 - ),] + vec![ + list_item(0..8, 1, Unordered, vec![p("Item 1", 2..8)]), + list_item(9..17, 1, Unordered, vec![p("Item 2", 11..17)]), + list_item(18..26, 1, Unordered, vec![p("Item 3", 20..26)]), + ], ); } @@ -904,13 +954,10 @@ Some other content assert_eq!( parsed.children, - vec![list( - vec![ - list_item(1, Task(false, 2..5), vec![p("TODO", 2..5)]), - list_item(1, Task(true, 13..16), vec![p("Checked", 13..16)]), - ], - 0..25 - ),] + vec![ + list_item(0..10, 1, Task(false, 2..5), vec![p("TODO", 6..10)]), + list_item(11..24, 1, Task(true, 13..16), vec![p("Checked", 17..24)]), + ], ); } @@ -927,13 +974,10 @@ Some other content assert_eq!( parsed.children, - vec![list( - vec![ - list_item(1, Task(false, 2..5), vec![p("Task 1", 2..5)]), - list_item(1, Task(true, 16..19), vec![p("Task 2", 16..19)]), - ], - 0..27 - ),] + vec![ + list_item(0..13, 1, Task(false, 2..5), vec![p("Task 1", 6..12)]), + list_item(14..26, 1, Task(true, 16..19), vec![p("Task 2", 20..26)]), + ], ); } @@ -965,84 +1009,21 @@ Some other content assert_eq!( parsed.children, vec![ - list( - vec![ - list_item(1, Unordered, vec![p("Item 1", 0..9)]), - list_item(1, Unordered, vec![p("Item 2", 9..18)]), - list_item(1, Unordered, vec![p("Item 3", 18..28)]), - ], - 0..28 - ), - list( - vec![ - list_item(1, Ordered(1), vec![p("Hello", 28..37)]), - list_item( - 1, - Ordered(2), - vec![ - p("Two", 37..56), - list( - vec![list_item(2, Ordered(1), vec![p("Three", 47..56)]),], - 47..56 - ), - ] - ), - list_item(1, Ordered(3), vec![p("Four", 56..64)]), - list_item(1, Ordered(4), vec![p("Five", 64..73)]), - ], - 28..73 - ), - list( - vec![ - list_item( - 1, - Unordered, - vec![ - p("First", 73..155), - list( - vec![ - list_item( - 2, - Ordered(1), - vec![ - p("Hello", 83..141), - list( - vec![list_item( - 3, - Ordered(1), - vec![ - p("Goodbyte", 97..141), - list( - vec![ - list_item( - 4, - Unordered, - vec![p("Inner", 117..125)] - ), - list_item( - 4, - Unordered, - vec![p("Inner", 133..141)] - ), - ], - 117..141 - ) - ] - ),], - 97..141 - ) - ] - ), - list_item(2, Ordered(2), vec![p("Goodbyte", 143..155)]), - ], - 83..155 - ) - ] - ), - list_item(1, Unordered, vec![p("Last", 155..162)]), - ], - 73..162 - ), + list_item(0..8, 1, Unordered, vec![p("Item 1", 2..8)]), + list_item(9..17, 1, Unordered, vec![p("Item 2", 11..17)]), + list_item(18..27, 1, Unordered, vec![p("Item 3", 20..26)]), + list_item(28..36, 1, Ordered(1), vec![p("Hello", 31..36)]), + list_item(37..46, 1, Ordered(2), vec![p("Two", 40..43),]), + list_item(47..55, 2, Ordered(1), vec![p("Three", 50..55)]), + list_item(56..63, 1, Ordered(3), vec![p("Four", 59..63)]), + list_item(64..72, 1, Ordered(4), vec![p("Five", 67..71)]), + list_item(73..82, 1, Unordered, vec![p("First", 75..80)]), + list_item(83..96, 2, Ordered(1), vec![p("Hello", 86..91)]), + list_item(97..116, 3, Ordered(1), vec![p("Goodbyte", 100..108)]), + list_item(117..124, 4, Unordered, vec![p("Inner", 119..124)]), + list_item(133..140, 4, Unordered, vec![p("Inner", 135..140)]), + list_item(143..154, 2, Ordered(2), vec![p("Goodbyte", 146..154)]), + list_item(155..161, 1, Unordered, vec![p("Last", 157..161)]), ] ); } @@ -1053,23 +1034,49 @@ Some other content "\ * This is a list item with two paragraphs. - This is the second paragraph in the list item.", + This is the second paragraph in the list item. +", ) .await; assert_eq!( parsed.children, - vec![list( - vec![list_item( - 1, - Unordered, - vec![ - p("This is a list item with two paragraphs.", 4..45), - p("This is the second paragraph in the list item.", 50..96) - ], - ),], + vec![list_item( 0..96, - ),] + 1, + Unordered, + vec![ + p("This is a list item with two paragraphs.", 4..44), + p("This is the second paragraph in the list item.", 50..97) + ], + ),], + ); + } + + #[gpui::test] + async fn test_nested_list_with_paragraph_inside() { + let parsed = parse( + "\ +1. a + 1. b + 1. c + + text + + 1. d +", + ) + .await; + + assert_eq!( + parsed.children, + vec![ + list_item(0..7, 1, Ordered(1), vec![p("a", 3..4)],), + list_item(8..20, 2, Ordered(1), vec![p("b", 12..13),],), + list_item(21..27, 3, Ordered(1), vec![p("c", 25..26),],), + p("text", 32..37), + list_item(41..46, 2, Ordered(1), vec![p("d", 45..46),],), + ], ); } @@ -1086,14 +1093,11 @@ Some other content assert_eq!( parsed.children, - vec![list( - vec![ - list_item(1, Unordered, vec![p("code", 0..9)],), - list_item(1, Unordered, vec![p("bold", 9..20)]), - list_item(1, Unordered, vec![p("link", 20..50)],) - ], - 0..50, - ),] + vec![ + list_item(0..8, 1, Unordered, vec![p("code", 2..8)]), + list_item(9..19, 1, Unordered, vec![p("bold", 11..19)]), + list_item(20..49, 1, Unordered, vec![p("link", 22..49)],) + ], ); } @@ -1127,7 +1131,7 @@ Some other content parsed.children, vec![block_quote( vec![ - h1(text("Heading", 2..12), 2..12), + h1(text("Heading", 4..11), 2..12), p("More text", 14..26), p("More text", 30..40) ], @@ -1157,7 +1161,7 @@ More text block_quote( vec![ p("A", 2..4), - block_quote(vec![h1(text("B", 10..14), 10..14)], 8..14), + block_quote(vec![h1(text("B", 12..13), 10..14)], 8..14), p("C", 18..20) ], 0..20 @@ -1279,7 +1283,7 @@ fn main() { ) -> ParsedMarkdownElement { ParsedMarkdownElement::BlockQuote(ParsedMarkdownBlockQuote { source_range, - children: children.into_iter().map(Box::new).collect(), + children, }) } @@ -1297,26 +1301,18 @@ fn main() { }) } - fn list( - children: Vec, - source_range: Range, - ) -> ParsedMarkdownElement { - List(ParsedMarkdownList { - source_range, - children, - }) - } - fn list_item( + source_range: Range, depth: u16, item_type: ParsedMarkdownListItemType, - contents: Vec, - ) -> ParsedMarkdownListItem { - ParsedMarkdownListItem { + content: Vec, + ) -> ParsedMarkdownElement { + ParsedMarkdownElement::ListItem(ParsedMarkdownListItem { + source_range, item_type, depth, - contents: contents.into_iter().map(Box::new).collect(), - } + content, + }) } fn table( diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index 4f8bb784752c41d3f224505869a4ea393db37435..e57e4da7290029d0f97fe32846051f28b4d1691c 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -15,6 +15,7 @@ use ui::prelude::*; use workspace::item::{Item, ItemHandle, TabContentParams}; use workspace::{Pane, Workspace}; +use crate::markdown_elements::ParsedMarkdownElement; use crate::OpenPreviewToTheSide; use crate::{ markdown_elements::ParsedMarkdown, @@ -180,9 +181,14 @@ impl MarkdownPreviewView { let block = contents.children.get(ix).unwrap(); let rendered_block = render_markdown_block(block, &mut render_cx); + let should_apply_padding = Self::should_apply_padding_between( + block, + contents.children.get(ix + 1), + ); + div() .id(ix) - .pb_3() + .when(should_apply_padding, |this| this.pb_3()) .group("markdown-block") .on_click(cx.listener(move |this, event: &ClickEvent, cx| { if event.down.click_count == 2 { @@ -404,7 +410,7 @@ impl MarkdownPreviewView { let Range { start, end } = block.source_range(); // Check if the cursor is between the last block and the current block - if last_end > cursor && cursor < start { + if last_end <= cursor && cursor < start { block_index = Some(i.saturating_sub(1)); break; } @@ -423,6 +429,13 @@ impl MarkdownPreviewView { block_index.unwrap_or_default() } + + fn should_apply_padding_between( + current_block: &ParsedMarkdownElement, + next_block: Option<&ParsedMarkdownElement>, + ) -> bool { + !(current_block.is_list_item() && next_block.map(|b| b.is_list_item()).unwrap_or(false)) + } } impl FocusableView for MarkdownPreviewView { diff --git a/crates/markdown_preview/src/markdown_renderer.rs b/crates/markdown_preview/src/markdown_renderer.rs index da86e772e50e545496d2719de942cb9043997a42..4de654e46ace9904463f3f002650eb895149666e 100644 --- a/crates/markdown_preview/src/markdown_renderer.rs +++ b/crates/markdown_preview/src/markdown_renderer.rs @@ -1,7 +1,8 @@ use crate::markdown_elements::{ HeadingLevel, Link, ParsedMarkdown, ParsedMarkdownBlockQuote, ParsedMarkdownCodeBlock, - ParsedMarkdownElement, ParsedMarkdownHeading, ParsedMarkdownList, ParsedMarkdownListItemType, - ParsedMarkdownTable, ParsedMarkdownTableAlignment, ParsedMarkdownTableRow, ParsedMarkdownText, + ParsedMarkdownElement, ParsedMarkdownHeading, ParsedMarkdownListItem, + ParsedMarkdownListItemType, ParsedMarkdownTable, ParsedMarkdownTableAlignment, + ParsedMarkdownTableRow, ParsedMarkdownText, }; use gpui::{ div, px, rems, AbsoluteLength, AnyElement, DefiniteLength, Div, Element, ElementId, @@ -110,7 +111,7 @@ pub fn render_markdown_block(block: &ParsedMarkdownElement, cx: &mut RenderConte match block { Paragraph(text) => render_markdown_paragraph(text, cx), Heading(heading) => render_markdown_heading(heading, cx), - List(list) => render_markdown_list(list, cx), + ListItem(list_item) => render_markdown_list_item(list_item, cx), Table(table) => render_markdown_table(table, cx), BlockQuote(block_quote) => render_markdown_block_quote(block_quote, cx), CodeBlock(code_block) => render_markdown_code_block(code_block, cx), @@ -146,79 +147,77 @@ fn render_markdown_heading(parsed: &ParsedMarkdownHeading, cx: &mut RenderContex .into_any() } -fn render_markdown_list(parsed: &ParsedMarkdownList, cx: &mut RenderContext) -> AnyElement { +fn render_markdown_list_item( + parsed: &ParsedMarkdownListItem, + cx: &mut RenderContext, +) -> AnyElement { use ParsedMarkdownListItemType::*; - let mut items = vec![]; - for item in &parsed.children { - let padding = rems((item.depth - 1) as f32 * 0.25); - - let bullet = match &item.item_type { - Ordered(order) => format!("{}.", order).into_any_element(), - Unordered => "•".into_any_element(), - Task(checked, range) => div() - .id(cx.next_id(range)) - .mt(px(3.)) - .child( - Checkbox::new( - "checkbox", - if *checked { - Selection::Selected - } else { - Selection::Unselected - }, - ) - .when_some( - cx.checkbox_clicked_callback.clone(), - |this, callback| { - this.on_click({ - let range = range.clone(); - move |selection, cx| { - let checked = match selection { - Selection::Selected => true, - Selection::Unselected => false, - _ => return, - }; - - if cx.modifiers().secondary() { - callback(checked, range.clone(), cx); - } + let padding = rems((parsed.depth - 1) as f32); + + let bullet = match &parsed.item_type { + Ordered(order) => format!("{}.", order).into_any_element(), + Unordered => "•".into_any_element(), + Task(checked, range) => div() + .id(cx.next_id(range)) + .mt(px(3.)) + .child( + Checkbox::new( + "checkbox", + if *checked { + Selection::Selected + } else { + Selection::Unselected + }, + ) + .when_some( + cx.checkbox_clicked_callback.clone(), + |this, callback| { + this.on_click({ + let range = range.clone(); + move |selection, cx| { + let checked = match selection { + Selection::Selected => true, + Selection::Unselected => false, + _ => return, + }; + + if cx.modifiers().secondary() { + callback(checked, range.clone(), cx); } - }) - }, - ), + } + }) + }, + ), + ) + .hover(|s| s.cursor_pointer()) + .tooltip(|cx| { + let secondary_modifier = Keystroke { + key: "".to_string(), + modifiers: Modifiers::secondary_key(), + ime_key: None, + }; + Tooltip::text( + format!("{}-click to toggle the checkbox", secondary_modifier), + cx, ) - .hover(|s| s.cursor_pointer()) - .tooltip(|cx| { - let secondary_modifier = Keystroke { - key: "".to_string(), - modifiers: Modifiers::secondary_key(), - ime_key: None, - }; - Tooltip::text( - format!("{}-click to toggle the checkbox", secondary_modifier), - cx, - ) - }) - .into_any_element(), - }; - let bullet = div().mr_2().child(bullet); - - let contents: Vec = item - .contents - .iter() - .map(|c| render_markdown_block(c.as_ref(), cx)) - .collect(); + }) + .into_any_element(), + }; + let bullet = div().mr_2().child(bullet); - let item = h_flex() - .pl(DefiniteLength::Absolute(AbsoluteLength::Rems(padding))) - .items_start() - .children(vec![bullet, div().children(contents).pr_4().w_full()]); + let contents: Vec = parsed + .content + .iter() + .map(|c| render_markdown_block(c, cx)) + .collect(); - items.push(item); - } + let item = h_flex() + .pl(DefiniteLength::Absolute(AbsoluteLength::Rems(padding))) + .items_start() + .children(vec![bullet, div().children(contents).pr_4().w_full()]); - cx.with_common_p(div()).children(items).into_any() + cx.with_common_p(item).into_any() } fn render_markdown_table(parsed: &ParsedMarkdownTable, cx: &mut RenderContext) -> AnyElement {