wrap markdown at render time (#4506)

This results in correctly indenting list items with long lines.

<img width="1006" height="251" alt="Screenshot 2025-09-30 at 10 00
48 AM"
src="https://github.com/user-attachments/assets/0a076cf6-ca3c-4efb-b3af-dc07617cdb6f"
/>
This commit is contained in:
Jeremy Rose
2025-09-30 16:13:55 -07:00
committed by GitHub
parent 9c259737d3
commit 01e6503672
8 changed files with 347 additions and 103 deletions

View File

@@ -8,13 +8,15 @@ use crate::markdown;
pub(crate) struct MarkdownStreamCollector {
buffer: String,
committed_line_count: usize,
width: Option<usize>,
}
impl MarkdownStreamCollector {
pub fn new() -> Self {
pub fn new(width: Option<usize>) -> Self {
Self {
buffer: String::new(),
committed_line_count: 0,
width,
}
}
@@ -40,7 +42,7 @@ impl MarkdownStreamCollector {
return Vec::new();
};
let mut rendered: Vec<Line<'static>> = Vec::new();
markdown::append_markdown(&source, &mut rendered, config);
markdown::append_markdown(&source, self.width, &mut rendered, config);
let mut complete_line_count = rendered.len();
if complete_line_count > 0
&& crate::render::line_utils::is_blank_line_spaces_only(
@@ -81,7 +83,7 @@ impl MarkdownStreamCollector {
tracing::trace!("markdown finalize (raw source):\n---\n{source}\n---");
let mut rendered: Vec<Line<'static>> = Vec::new();
markdown::append_markdown(&source, &mut rendered, config);
markdown::append_markdown(&source, self.width, &mut rendered, config);
let out = if self.committed_line_count >= rendered.len() {
Vec::new()
@@ -101,7 +103,7 @@ pub(crate) fn simulate_stream_markdown_for_tests(
finalize: bool,
config: &Config,
) -> Vec<Line<'static>> {
let mut collector = MarkdownStreamCollector::new();
let mut collector = MarkdownStreamCollector::new(None);
let mut out = Vec::new();
for d in deltas {
collector.push_delta(d);
@@ -136,7 +138,7 @@ mod tests {
#[test]
fn no_commit_until_newline() {
let cfg = test_config();
let mut c = super::MarkdownStreamCollector::new();
let mut c = super::MarkdownStreamCollector::new(None);
c.push_delta("Hello, world");
let out = c.commit_complete_lines(&cfg);
assert!(out.is_empty(), "should not commit without newline");
@@ -148,7 +150,7 @@ mod tests {
#[test]
fn finalize_commits_partial_line() {
let cfg = test_config();
let mut c = super::MarkdownStreamCollector::new();
let mut c = super::MarkdownStreamCollector::new(None);
c.push_delta("Line without newline");
let out = c.finalize_and_drain(&cfg);
assert_eq!(out.len(), 1);
@@ -277,7 +279,7 @@ mod tests {
// Stream a paragraph line, then a heading on the next line.
// Expect two distinct rendered lines: "Hello." and "Heading".
let mut c = super::MarkdownStreamCollector::new();
let mut c = super::MarkdownStreamCollector::new(None);
c.push_delta("Hello.\n");
let out1 = c.commit_complete_lines(&cfg);
let s1: Vec<String> = out1
@@ -335,7 +337,7 @@ mod tests {
// Paragraph without trailing newline, then a chunk that starts with the newline
// and the heading text, then a final newline. The collector should first commit
// only the paragraph line, and later commit the heading as its own line.
let mut c = super::MarkdownStreamCollector::new();
let mut c = super::MarkdownStreamCollector::new(None);
c.push_delta("Sounds good!");
// No commit yet
assert!(c.commit_complete_lines(&cfg).is_empty());
@@ -380,7 +382,7 @@ mod tests {
// Sanity check raw markdown rendering for a simple line does not produce spurious extras.
let mut rendered: Vec<ratatui::text::Line<'static>> = Vec::new();
crate::markdown::append_markdown("Hello.\n", &mut rendered, &cfg);
crate::markdown::append_markdown("Hello.\n", None, &mut rendered, &cfg);
let rendered_strings: Vec<String> = rendered
.iter()
.map(|l| {
@@ -442,7 +444,7 @@ mod tests {
let streamed_str = lines_to_plain_strings(&streamed);
let mut rendered_all: Vec<ratatui::text::Line<'static>> = Vec::new();
crate::markdown::append_markdown(input, &mut rendered_all, &cfg);
crate::markdown::append_markdown(input, None, &mut rendered_all, &cfg);
let rendered_all_str = lines_to_plain_strings(&rendered_all);
assert_eq!(
@@ -552,7 +554,7 @@ mod tests {
let full: String = deltas.iter().copied().collect();
let mut rendered_all: Vec<ratatui::text::Line<'static>> = Vec::new();
crate::markdown::append_markdown(&full, &mut rendered_all, &cfg);
crate::markdown::append_markdown(&full, None, &mut rendered_all, &cfg);
let rendered_all_strs = lines_to_plain_strings(&rendered_all);
assert_eq!(
@@ -641,7 +643,7 @@ mod tests {
// Compute a full render for diagnostics only.
let full: String = deltas.iter().copied().collect();
let mut rendered_all: Vec<ratatui::text::Line<'static>> = Vec::new();
crate::markdown::append_markdown(&full, &mut rendered_all, &cfg);
crate::markdown::append_markdown(&full, None, &mut rendered_all, &cfg);
// Also assert exact expected plain strings for clarity.
let expected = vec![
@@ -669,7 +671,7 @@ mod tests {
let streamed_strs = lines_to_plain_strings(&streamed);
let full: String = deltas.iter().copied().collect();
let mut rendered: Vec<ratatui::text::Line<'static>> = Vec::new();
crate::markdown::append_markdown(&full, &mut rendered, &cfg);
crate::markdown::append_markdown(&full, None, &mut rendered, &cfg);
let rendered_strs = lines_to_plain_strings(&rendered);
assert_eq!(streamed_strs, rendered_strs, "full:\n---\n{full}\n---");
}