fix(render): suppress blank lines above rendered table
This commit is contained in:
+49
-11
@@ -473,6 +473,7 @@ enum TableState {
|
|||||||
|
|
||||||
enum TableAction {
|
enum TableAction {
|
||||||
Consumed(String),
|
Consumed(String),
|
||||||
|
ConsumedSilent,
|
||||||
FlushAndContinue(String),
|
FlushAndContinue(String),
|
||||||
Passthrough,
|
Passthrough,
|
||||||
}
|
}
|
||||||
@@ -532,7 +533,7 @@ impl MarkdownRender {
|
|||||||
|
|
||||||
pub fn render(&mut self, text: &str) -> String {
|
pub fn render(&mut self, text: &str) -> String {
|
||||||
text.split('\n')
|
text.split('\n')
|
||||||
.map(|line| self.render_line_mut(line))
|
.filter_map(|line| self.render_line_mut(line))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("\n")
|
.join("\n")
|
||||||
}
|
}
|
||||||
@@ -548,7 +549,7 @@ impl MarkdownRender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_line_mut(&mut self, line: &str) -> String {
|
fn render_line_mut(&mut self, line: &str) -> Option<String> {
|
||||||
let (line_type, line_kind, code_syntax, is_code) = self.check_line(line);
|
let (line_type, line_kind, code_syntax, is_code) = self.check_line(line);
|
||||||
|
|
||||||
let table_prefix = if self.options.raw_markdown {
|
let table_prefix = if self.options.raw_markdown {
|
||||||
@@ -563,7 +564,12 @@ impl MarkdownRender {
|
|||||||
TableAction::Consumed(s) => {
|
TableAction::Consumed(s) => {
|
||||||
self.prev_line_type = line_type;
|
self.prev_line_type = line_type;
|
||||||
self.code_syntax = code_syntax;
|
self.code_syntax = code_syntax;
|
||||||
return s;
|
return Some(s);
|
||||||
|
}
|
||||||
|
TableAction::ConsumedSilent => {
|
||||||
|
self.prev_line_type = line_type;
|
||||||
|
self.code_syntax = code_syntax;
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
TableAction::FlushAndContinue(s) => Some(s),
|
TableAction::FlushAndContinue(s) => Some(s),
|
||||||
TableAction::Passthrough => None,
|
TableAction::Passthrough => None,
|
||||||
@@ -580,10 +586,10 @@ impl MarkdownRender {
|
|||||||
self.prev_line_type = line_type;
|
self.prev_line_type = line_type;
|
||||||
self.code_syntax = code_syntax;
|
self.code_syntax = code_syntax;
|
||||||
|
|
||||||
match table_prefix {
|
Some(match table_prefix {
|
||||||
Some(prefix) => format!("{prefix}\n{output}"),
|
Some(prefix) => format!("{prefix}\n{output}"),
|
||||||
None => output,
|
None => output,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_as_paragraph(&self, line: &str) -> String {
|
fn render_as_paragraph(&self, line: &str) -> String {
|
||||||
@@ -594,7 +600,7 @@ impl MarkdownRender {
|
|||||||
match (self.table_state.take(), kind) {
|
match (self.table_state.take(), kind) {
|
||||||
(None, LineKind::TableRow) => {
|
(None, LineKind::TableRow) => {
|
||||||
self.table_state = Some(TableState::PendingHeader(line.to_string()));
|
self.table_state = Some(TableState::PendingHeader(line.to_string()));
|
||||||
TableAction::Consumed(String::new())
|
TableAction::ConsumedSilent
|
||||||
}
|
}
|
||||||
(None, LineKind::TableSeparator) => TableAction::Passthrough,
|
(None, LineKind::TableSeparator) => TableAction::Passthrough,
|
||||||
(None, _) => TableAction::Passthrough,
|
(None, _) => TableAction::Passthrough,
|
||||||
@@ -606,7 +612,7 @@ impl MarkdownRender {
|
|||||||
alignments,
|
alignments,
|
||||||
rows: Vec::new(),
|
rows: Vec::new(),
|
||||||
});
|
});
|
||||||
TableAction::Consumed(String::new())
|
TableAction::ConsumedSilent
|
||||||
}
|
}
|
||||||
(Some(TableState::PendingHeader(header_line)), LineKind::TableRow) => {
|
(Some(TableState::PendingHeader(header_line)), LineKind::TableRow) => {
|
||||||
let a = self.render_as_paragraph(&header_line);
|
let a = self.render_as_paragraph(&header_line);
|
||||||
@@ -631,7 +637,7 @@ impl MarkdownRender {
|
|||||||
alignments,
|
alignments,
|
||||||
rows,
|
rows,
|
||||||
});
|
});
|
||||||
TableAction::Consumed(String::new())
|
TableAction::ConsumedSilent
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
Some(TableState::Active {
|
Some(TableState::Active {
|
||||||
@@ -1617,11 +1623,11 @@ std::error::Error>> {
|
|||||||
let options = RenderOptions::default();
|
let options = RenderOptions::default();
|
||||||
let mut render = MarkdownRender::init(options).unwrap();
|
let mut render = MarkdownRender::init(options).unwrap();
|
||||||
let header = render.render_line_mut("| A | B |");
|
let header = render.render_line_mut("| A | B |");
|
||||||
assert!(header.is_empty(), "header row silently buffered");
|
assert!(header.is_none(), "header row silently consumed");
|
||||||
let sep = render.render_line_mut("|---|---|");
|
let sep = render.render_line_mut("|---|---|");
|
||||||
assert!(sep.is_empty(), "separator silently buffered");
|
assert!(sep.is_none(), "separator silently consumed");
|
||||||
let data = render.render_line_mut("| 1 | 2 |");
|
let data = render.render_line_mut("| 1 | 2 |");
|
||||||
assert!(data.is_empty(), "data row silently buffered");
|
assert!(data.is_none(), "data row silently consumed");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1889,6 +1895,38 @@ std::error::Error>> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn table_render_does_not_emit_blank_lines_from_silent_accumulation() {
|
||||||
|
let options = RenderOptions::default();
|
||||||
|
let mut render = MarkdownRender::init(options).unwrap();
|
||||||
|
let text = "## Head\n\n\
|
||||||
|
| A | B |\n\
|
||||||
|
|---|---|\n\
|
||||||
|
| 1 | 2 |\n\
|
||||||
|
| 3 | 4 |\n\
|
||||||
|
| 5 | 6 |\n\
|
||||||
|
| 7 | 8 |\n\
|
||||||
|
| 9 | 10 |\n\n\
|
||||||
|
trailing\n";
|
||||||
|
let output = render.render(text);
|
||||||
|
let tail = render.finalize();
|
||||||
|
let combined = format!("{output}{tail}");
|
||||||
|
let mut consecutive_blank = 0usize;
|
||||||
|
let mut max_consecutive_blank = 0usize;
|
||||||
|
for line in combined.split('\n') {
|
||||||
|
if line.is_empty() {
|
||||||
|
consecutive_blank += 1;
|
||||||
|
max_consecutive_blank = max_consecutive_blank.max(consecutive_blank);
|
||||||
|
} else {
|
||||||
|
consecutive_blank = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
max_consecutive_blank <= 1,
|
||||||
|
"at most one blank line between blocks, got {max_consecutive_blank}: {combined:?}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn table_tolerates_column_count_mismatch() {
|
fn table_tolerates_column_count_mismatch() {
|
||||||
let options = RenderOptions::default();
|
let options = RenderOptions::default();
|
||||||
|
|||||||
@@ -132,7 +132,9 @@ async fn markdown_stream_inner(
|
|||||||
let text = format!("{buffer}{text}");
|
let text = format!("{buffer}{text}");
|
||||||
let (head, tail) = split_line_tail(&text);
|
let (head, tail) = split_line_tail(&text);
|
||||||
let output = render.render(head);
|
let output = render.render(head);
|
||||||
print_block(writer, &output, columns)?;
|
if !output.is_empty() {
|
||||||
|
print_block(writer, &output, columns)?;
|
||||||
|
}
|
||||||
buffer = tail.to_string();
|
buffer = tail.to_string();
|
||||||
} else {
|
} else {
|
||||||
buffer = format!("{buffer}{text}");
|
buffer = format!("{buffer}{text}");
|
||||||
|
|||||||
Reference in New Issue
Block a user