Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix incorrect checkbox placement in Markdown preview #19383

Merged
merged 3 commits into from
Oct 21, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 61 additions & 23 deletions crates/markdown_preview/src/markdown_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ struct MarkdownParser<'a> {
language_registry: Option<Arc<LanguageRegistry>>,
}

struct MarkdownListItem {
content: Vec<ParsedMarkdownElement>,
item_type: ParsedMarkdownListItemType,
}

impl Default for MarkdownListItem {
fn default() -> Self {
Self {
content: Vec::new(),
item_type: ParsedMarkdownListItemType::Unordered,
}
}
}

impl<'a> MarkdownParser<'a> {
fn new(
tokens: Vec<(Event<'a>, Range<usize>)>,
Expand Down Expand Up @@ -479,9 +493,8 @@ impl<'a> MarkdownParser<'a> {
let (_, list_source_range) = self.previous().unwrap();

let mut items = Vec::new();
let mut items_stack = vec![Vec::new()];
let mut items_stack = vec![MarkdownListItem::default()];
let mut depth = 1;
let mut task_item = None;
let mut order = order;
let mut order_stack = Vec::new();

Expand Down Expand Up @@ -521,8 +534,9 @@ impl<'a> MarkdownParser<'a> {
start_item_range = source_range.clone();

self.cursor += 1;
items_stack.push(Vec::new());
items_stack.push(MarkdownListItem::default());

let mut task_list = None;
// Check for task list marker (`- [ ]` or `- [x]`)
if let Some(event) = self.current_event() {
// If there is a linebreak in between two list items the task list marker will actually be the first element of the paragraph
Expand All @@ -531,7 +545,7 @@ impl<'a> MarkdownParser<'a> {
}

if let Some((Event::TaskListMarker(checked), range)) = self.current() {
task_item = Some((*checked, range.clone()));
task_list = Some((*checked, range.clone()));
self.cursor += 1;
}
}
Expand All @@ -543,13 +557,21 @@ impl<'a> MarkdownParser<'a> {
let text = self.parse_text(false, Some(range.clone()));
let block = ParsedMarkdownElement::Paragraph(text);
if let Some(content) = items_stack.last_mut() {
content.push(block);
let item_type = if let Some((checked, range)) = task_list {
ParsedMarkdownListItemType::Task(checked, range)
} else if let Some(order) = order {
ParsedMarkdownListItemType::Ordered(order)
} else {
ParsedMarkdownListItemType::Unordered
};
content.item_type = item_type;
content.content.push(block);
}
} else {
let block = self.parse_block().await;
if let Some(block) = block {
if let Some(content) = items_stack.last_mut() {
content.extend(block);
if let Some(list_item) = items_stack.last_mut() {
list_item.content.extend(block);
}
}
}
Expand All @@ -563,19 +585,11 @@ impl<'a> MarkdownParser<'a> {
Event::End(TagEnd::Item) => {
self.cursor += 1;

let item_type = if let Some((checked, range)) = task_item {
ParsedMarkdownListItemType::Task(checked, range)
} else if let Some(order) = order {
ParsedMarkdownListItemType::Ordered(order)
} else {
ParsedMarkdownListItemType::Unordered
};

if let Some(current) = order {
order = Some(current + 1);
}

if let Some(content) = items_stack.pop() {
if let Some(list_item) = items_stack.pop() {
let source_range = source_ranges
.remove(&depth)
.unwrap_or(start_item_range.clone());
Expand All @@ -584,9 +598,9 @@ impl<'a> MarkdownParser<'a> {
let source_range = source_range.start..source_range.end - 1;
let item = ParsedMarkdownElement::ListItem(ParsedMarkdownListItem {
source_range,
content,
content: list_item.content,
depth,
item_type,
item_type: list_item.item_type,
});

if let Some(index) = insertion_indices.get(&depth) {
Expand All @@ -596,8 +610,6 @@ impl<'a> MarkdownParser<'a> {
items.push(item);
}
}

task_item = None;
}
_ => {
if depth == 0 {
Expand All @@ -607,10 +619,10 @@ impl<'a> MarkdownParser<'a> {
// 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 {
if let Some(items_stack) = items_stack.last_mut() {
if let Some(list_item) = 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);
list_item.content.extend(block);
continue;
}
}
Expand Down Expand Up @@ -726,7 +738,6 @@ mod tests {
use gpui::BackgroundExecutor;
use language::{tree_sitter_rust, HighlightId, Language, LanguageConfig, LanguageMatcher};
use pretty_assertions::assert_eq;

use ParsedMarkdownListItemType::*;

async fn parse(input: &str) -> ParsedMarkdown {
Expand Down Expand Up @@ -960,6 +971,33 @@ Some other content
);
}

#[gpui::test]
async fn test_list_with_indented_task() {
let parsed = parse(
"\
- [ ] TODO
- [x] Checked
- Unordered
1. Number 1
1. Number 2
1. Number A
",
)
.await;

assert_eq!(
parsed.children,
vec![
list_item(0..12, 1, Task(false, 2..5), vec![p("TODO", 6..10)]),
list_item(13..26, 2, Task(true, 15..18), vec![p("Checked", 19..26)]),
list_item(29..40, 2, Unordered, vec![p("Unordered", 31..40)]),
list_item(43..54, 2, Ordered(1), vec![p("Number 1", 46..54)]),
list_item(57..68, 2, Ordered(2), vec![p("Number 2", 60..68)]),
list_item(69..80, 1, Ordered(1), vec![p("Number A", 72..80)]),
],
);
}

#[gpui::test]
async fn test_list_with_linebreak_is_handled_correctly() {
let parsed = parse(
Expand Down
Loading