Skip to content
Merged
Show file tree
Hide file tree
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
60 changes: 49 additions & 11 deletions src/markdown/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,33 @@ fn end_paragraph(
lines.push(Line::from(""));
}

#[allow(clippy::too_many_arguments)]
fn flush_pending_inline_if_any(
lines: &mut Vec<Line<'static>>,
spans: &mut Vec<Span<'static>>,
blockquote_depth: usize,
list_stack: &[ListKind],
item_stack: &mut [ItemState],
render_width: usize,
theme: &MarkdownTheme,
marker_color: Option<Color>,
) -> bool {
if spans.is_empty() {
return false;
}
flush_wrapped_spans(
lines,
spans,
blockquote_depth,
list_stack,
item_stack,
render_width,
theme,
marker_color,
);
true
}

fn end_blockquote(
lines: &mut Vec<Line<'static>>,
spans: &mut Vec<Span<'static>>,
Expand Down Expand Up @@ -405,17 +432,16 @@ pub(crate) fn parse_markdown_with_width(
last_block = LastBlock::Paragraph;
}
MdEvent::Start(Tag::CodeBlock(kind)) => {
if !spans.is_empty() {
flush_wrapped_spans(
&mut lines,
&mut spans,
blockquote_depth,
&list_stack,
&mut item_stack,
render_width,
theme_colors,
blockquote_color,
);
if flush_pending_inline_if_any(
&mut lines,
&mut spans,
blockquote_depth,
&list_stack,
&mut item_stack,
render_width,
theme_colors,
blockquote_color,
) {
wraps = true;
}
start_code_block(
Expand Down Expand Up @@ -499,6 +525,18 @@ pub(crate) fn parse_markdown_with_width(
push_inline_code_span(&mut spans, text.as_ref(), theme_colors);
}
MdEvent::Start(Tag::BlockQuote(kind)) => {
if flush_pending_inline_if_any(
&mut lines,
&mut spans,
blockquote_depth,
&list_stack,
&mut item_stack,
render_width,
theme_colors,
blockquote_color,
) {
wraps = true;
}
blockquote_depth += 1;
if let Some(k) = kind {
let color = alert_color(k, theme_colors);
Expand Down
103 changes: 103 additions & 0 deletions src/tests/markdown_lists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,106 @@ fn code_block_after_continuation_line_in_tight_list_item_renders_below_it() {
"expected continuation line directly after first line"
);
}

#[test]
fn code_block_in_list_item_has_same_layout_loose_and_tight() {
let (ss, theme) = test_assets();
let loose_md = "- one\n\n ```\n three\n ```\n";
let tight_md = "- one\n ```\n three\n ```\n";
let (loose_lines, _, _, _) =
parse_markdown(loose_md, &ss, &theme, &test_md_theme(), false, true).into();
let (tight_lines, _, _, _) =
parse_markdown(tight_md, &ss, &theme, &test_md_theme(), false, true).into();
let loose = rendered_non_empty_lines(&loose_lines);
let tight = rendered_non_empty_lines(&tight_lines);

let loose_one = loose
.iter()
.position(|l| l.contains("one"))
.expect("missing 'one' in loose rendering");
let loose_three = loose
.iter()
.position(|l| l.contains("three"))
.expect("missing 'three' in loose rendering");
let tight_one = tight
.iter()
.position(|l| l.contains("one"))
.expect("missing 'one' in tight rendering");
let tight_three = tight
.iter()
.position(|l| l.contains("three"))
.expect("missing 'three' in tight rendering");

assert_eq!(
loose_three - loose_one,
tight_three - tight_one,
"loose and tight paths should place the code block at the same distance from the item line"
);
}

#[test]
fn blockquote_after_continuation_line_in_tight_list_item_renders_below_it() {
let (ss, theme) = test_assets();
let md = "- one\n two\n > quote\n";
let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into();
let rendered = rendered_non_empty_lines(&lines);

let one_idx = rendered
.iter()
.position(|line| line.contains("one"))
.expect("missing first list line");
let two_idx = rendered
.iter()
.position(|line| line.contains("two"))
.expect("missing continuation line");
let quote_idx = rendered
.iter()
.position(|line| line.contains("quote"))
.expect("missing blockquote line");

assert!(
one_idx < two_idx && two_idx < quote_idx,
"expected blockquote below continuation line, got one={one_idx}, two={two_idx}, quote={quote_idx}"
);
}

#[test]
fn nested_list_after_continuation_line_in_tight_list_item_renders_below_it() {
let (ss, theme) = test_assets();
let md = "- one\n two\n - nested\n";
let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into();
let rendered = rendered_non_empty_lines(&lines);

let one_idx = rendered
.iter()
.position(|line| line.contains("one"))
.expect("missing first list line");
let two_idx = rendered
.iter()
.position(|line| line.contains("two"))
.expect("missing continuation line");
let nested_idx = rendered
.iter()
.position(|line| line.contains("nested"))
.expect("missing nested list line");

assert!(
one_idx < two_idx && two_idx < nested_idx,
"expected nested list below continuation line, got one={one_idx}, two={two_idx}, nested={nested_idx}"
);
}

#[test]
fn html_block_after_continuation_line_in_tight_list_item_documented_behavior() {
let (ss, theme) = test_assets();
let md = "- one\n two\n <div>html</div>\n";
let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into();
let rendered = rendered_non_empty_lines(&lines);

assert!(rendered.iter().any(|line| line.contains("one")));
assert!(rendered.iter().any(|line| line.contains("two")));
assert!(
!rendered.iter().any(|line| line.contains("html")),
"documented behavior: raw HTML blocks are dropped from the rendered output"
);
}
Loading