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
57 changes: 43 additions & 14 deletions src/markdown/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,47 @@ pub(super) struct BlockLayout {
}

pub(super) fn block_prefix(
in_bq: bool,
blockquote_depth: usize,
theme: &MarkdownTheme,
marker_color: Option<Color>,
) -> Vec<Span<'static>> {
if in_bq {
let color = marker_color.unwrap_or(theme.blockquote_marker);
vec![Span::styled("▏ ", Style::default().fg(color))]
} else {
vec![]
if blockquote_depth == 0 {
return vec![];
}
let default_color = theme.blockquote_marker;
let outer_color = marker_color.unwrap_or(default_color);
let outer_style = Style::default().fg(outer_color);
let inner_style = Style::default().fg(default_color);
let mut spans = Vec::with_capacity(blockquote_depth);
spans.push(Span::styled("▏ ", outer_style));
for _ in 1..blockquote_depth {
spans.push(Span::styled("▏ ", inner_style));
}
spans
}

fn is_blockquote_blank_text(plain: &str) -> bool {
!plain.is_empty() && plain.chars().all(|c| c == '▏' || c.is_whitespace())
}

pub(super) fn pop_trailing_blockquote_gap(lines: &mut Vec<Line<'static>>) {
if lines
.last()
.is_some_and(|line| is_blockquote_blank_text(&super::width::line_plain_text(line)))
{
lines.pop();
}
}

pub(super) fn push_wrapped_blockquote_lines(
lines: &mut Vec<Line<'static>>,
body_spans: &mut Vec<Span<'static>>,
blockquote_depth: usize,
render_width: usize,
theme: &MarkdownTheme,
marker_color: Option<Color>,
) {
let prefix = block_prefix(true, theme, marker_color);
let prefix = block_prefix(blockquote_depth, theme, marker_color);
push_wrapped_prefixed_lines(lines, body_spans, prefix.clone(), prefix, render_width);
}

Expand All @@ -56,7 +77,14 @@ pub(super) fn flush_wrapped_spans(
marker_color: Option<Color>,
) {
if blockquote_depth > 0 && item_stack.is_empty() {
push_wrapped_blockquote_lines(lines, spans, render_width, theme, marker_color);
push_wrapped_blockquote_lines(
lines,
spans,
blockquote_depth,
render_width,
theme,
marker_color,
);
} else if !item_stack.is_empty() {
let first_prefix = list_item_prefix(
blockquote_depth,
Expand Down Expand Up @@ -88,10 +116,11 @@ pub(super) fn trim_paragraph_gap_before_block(
lines: &mut Vec<Line<'static>>,
last_block: LastBlock,
) {
if last_block == LastBlock::Paragraph
&& lines
.last()
.is_some_and(|line| super::width::line_plain_text(line).is_empty())
if matches!(last_block, LastBlock::Paragraph | LastBlock::Blockquote)
&& lines.last().is_some_and(|line| {
let plain = super::width::line_plain_text(line);
plain.is_empty() || is_blockquote_blank_text(&plain)
})
{
lines.pop();
}
Expand Down Expand Up @@ -184,7 +213,7 @@ pub(super) fn push_code_block_lines(
None,
)
} else if ctx.blockquote_depth > 0 {
block_prefix(true, ctx.theme_colors, None)
block_prefix(ctx.blockquote_depth, ctx.theme_colors, None)
} else {
Vec::new()
};
Expand Down Expand Up @@ -312,7 +341,7 @@ pub(super) fn push_special_block_lines<F: Fn(&str) -> Vec<Span<'static>>>(
let prefix = if !item_stack.is_empty() {
list_item_prefix(blockquote_depth, list_stack, item_stack, theme, None)
} else if blockquote_depth > 0 {
block_prefix(true, theme, None)
block_prefix(blockquote_depth, theme, None)
} else {
Vec::new()
};
Expand Down
6 changes: 3 additions & 3 deletions src/markdown/lists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ pub(super) fn list_item_prefix(
) -> Vec<Span<'static>> {
let in_bq = blockquote_depth > 0;
let Some(item) = item_stack.last_mut() else {
return block_prefix(in_bq, theme, marker_color);
return block_prefix(blockquote_depth, theme, marker_color);
};

let bq_is_outer = in_bq && blockquote_depth == item.opened_at_bq_depth;

let mut prefix = Vec::new();

if bq_is_outer {
prefix.extend(block_prefix(in_bq, theme, marker_color));
prefix.extend(block_prefix(blockquote_depth, theme, marker_color));
}

if item.marker_emitted {
Expand Down Expand Up @@ -87,7 +87,7 @@ pub(super) fn list_item_prefix(
}

if in_bq && !bq_is_outer {
prefix.extend(block_prefix(in_bq, theme, marker_color));
prefix.extend(block_prefix(blockquote_depth, theme, marker_color));
}

prefix
Expand Down
60 changes: 52 additions & 8 deletions src/markdown/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ use std::{
use toc::{normalize_toc, TocEntry};

use blocks::{
flush_wrapped_spans, push_code_block_lines, push_heading_lines, push_latex_block_lines,
push_mermaid_block_lines, push_rule_line, trim_paragraph_gap_before_block, BlockLayout,
CodeBlockRenderContext, EmbeddedBlockCtx, CODE_BLOCK_GUTTER,
block_prefix, flush_wrapped_spans, pop_trailing_blockquote_gap, push_code_block_lines,
push_heading_lines, push_latex_block_lines, push_mermaid_block_lines, push_rule_line,
trim_paragraph_gap_before_block, BlockLayout, CodeBlockRenderContext, EmbeddedBlockCtx,
CODE_BLOCK_GUTTER,
};
use fences::normalize_code_fences;
use links::build_link_spans;
Expand All @@ -63,6 +64,7 @@ const LINK_MARKER: &str = "#";
enum LastBlock {
Other,
Paragraph,
Blockquote,
}

pub(crate) fn hash_str(text: &str) -> u64 {
Expand Down Expand Up @@ -157,7 +159,22 @@ fn end_paragraph(
theme,
marker_color,
);
lines.push(Line::from(""));
let gap = if blockquote_depth > 0 {
if !item_stack.is_empty() {
Line::from(list_item_prefix(
blockquote_depth,
list_stack,
item_stack,
theme,
marker_color,
))
} else {
Line::from(block_prefix(blockquote_depth, theme, marker_color))
}
} else {
Line::from("")
};
lines.push(gap);
}

#[allow(clippy::too_many_arguments)]
Expand Down Expand Up @@ -195,13 +212,21 @@ fn end_blockquote(
marker_color: Option<Color>,
) {
if !spans.is_empty() {
let color = marker_color.unwrap_or(theme.blockquote_marker);
let mut all = vec![Span::styled("▏ ", Style::default().fg(color))];
let mut all = block_prefix(*blockquote_depth, theme, marker_color);
all.append(spans);
lines.push(Line::from(all));
}
pop_trailing_blockquote_gap(lines);
*blockquote_depth = blockquote_depth.saturating_sub(1);
lines.push(Line::from(""));
if *blockquote_depth == 0 {
lines.push(Line::from(""));
} else {
lines.push(Line::from(block_prefix(
*blockquote_depth,
theme,
marker_color,
)));
}
}

fn alert_icon_label(kind: BlockQuoteKind) -> (&'static str, &'static str) {
Expand Down Expand Up @@ -364,6 +389,7 @@ pub(crate) fn parse_markdown_with_width(
let mut last_block = LastBlock::Other;
let mut link_urls: Vec<String> = Vec::new();
let mut blockquote_color: Option<Color> = None;
let mut prev_event_end: usize = 0;

let normalized = normalize_code_fences(src);
let line_starts = compute_line_starts(&normalized);
Expand All @@ -385,6 +411,7 @@ pub(crate) fn parse_markdown_with_width(
} else {
state.mark_all_new(lines.len());
}
prev_event_end = range.end;
continue;
}
if handle_inline_style_event(
Expand All @@ -395,6 +422,7 @@ pub(crate) fn parse_markdown_with_width(
blockquote_depth,
&mut link_urls,
) {
prev_event_end = range.end;
continue;
}

Expand Down Expand Up @@ -538,6 +566,21 @@ pub(crate) fn parse_markdown_with_width(
) {
wraps = true;
}
let source_between = normalized.get(prev_event_end..range.start).unwrap_or("");
let has_explicit_gap = source_between.split_inclusive('\n').any(|line| {
if !line.ends_with('\n') {
return false;
}
let content = line.trim_end_matches('\n').trim_start();
content.starts_with('>')
&& content.get(1..).is_none_or(|rest| rest.trim().is_empty())
});
if last_block == LastBlock::Paragraph
&& (!item_stack.is_empty() || blockquote_depth > 0)
&& !has_explicit_gap
{
trim_paragraph_gap_before_block(&mut lines, last_block);
}
blockquote_depth += 1;
if let Some(k) = kind {
let color = alert_color(k, theme_colors);
Expand Down Expand Up @@ -566,7 +609,7 @@ pub(crate) fn parse_markdown_with_width(
blockquote_color.take(),
);
wraps = true;
last_block = LastBlock::Other;
last_block = LastBlock::Blockquote;
}
MdEvent::Start(Tag::List(start)) => {
if !item_stack.is_empty() && !spans.is_empty() {
Expand Down Expand Up @@ -680,6 +723,7 @@ pub(crate) fn parse_markdown_with_width(
} else {
state.mark_all_new(lines.len());
}
prev_event_end = range.end;
}

if !spans.is_empty() {
Expand Down
Loading
Loading