diff --git a/src/documents/document/tests/point.rs b/src/documents/document/tests/point.rs
index 79ecf8a..e759255 100644
--- a/src/documents/document/tests/point.rs
+++ b/src/documents/document/tests/point.rs
@@ -197,8 +197,9 @@ fn a_secondary_press_in_a_quote_lands_past_its_mark(cx: &mut TestAppContext) {
let events = heard(&document, cx);
let bounds = cx.debug_bounds("markdown-block-0").expect("drawn");
let (body, _) = sizes(cx);
- let x = bounds.left() + px(31.0) + body * 0.6 * 7.25;
- press_at(x, bounds.top() + px(10.0), MouseButton::Right, cx);
+ // "beta" draws on its own line.
+ let x = bounds.left() + px(31.0) + body * 0.6 * 1.25;
+ press_at(x, bounds.top() + px(10.0) + body * 1.6, MouseButton::Right, cx);
let at = source.find("eta").unwrap();
assert_eq!(*events.borrow(), [target(at, TargetBlock::Quote)]);
}
diff --git a/src/documents/tree/blocks.rs b/src/documents/tree/blocks.rs
index cc1a995..3644656 100644
--- a/src/documents/tree/blocks.rs
+++ b/src/documents/tree/blocks.rs
@@ -194,7 +194,7 @@ mod tests {
let bold = 2..3;
assert_eq!(
(line.text.as_str(), &line.styles[..]),
- ("a b c", &[(bold, Style::Strong)][..])
+ ("a b\nc", &[(bold, Style::Strong)][..])
);
}
diff --git a/src/documents/tree/html_block.rs b/src/documents/tree/html_block.rs
index ff9245c..5f7aacd 100644
--- a/src/documents/tree/html_block.rs
+++ b/src/documents/tree/html_block.rs
@@ -197,7 +197,7 @@ mod tests {
other => panic!("a paragraph, not {other:?}"),
})
.collect();
- assert_eq!(lines, [""]);
+ assert_eq!(lines, [""]);
let inline = html("a c").expect("text");
assert_eq!(inline.text, "a c");
assert_eq!(html("
\na < b {
notes: HashMap,
/// Every heading, as `[toc]` lists them.
pub headings: Vec,
+ /// Soft breaks as lines, as the screen draws them.
+ lines: bool,
}
/// An open element, as HTML names it.
@@ -122,6 +124,8 @@ struct Run {
verbatim: bool,
/// In a table cell, whose code reads `\|` as `|`.
table: bool,
+ /// Soft breaks as lines, not spaces.
+ lines: bool,
/// The current DOM text node's shortcodes.
shortcodes: emoji::Shortcodes,
/// White space owed, and the open elements before it.
@@ -159,8 +163,9 @@ impl Run {
}
fn push_plain(&mut self, shown: &str) {
- let soft = shown.replace("\r\n", " ").replace(['\r', '\n'], " ");
- self.text.text.push_str(&soft);
+ let soft = if self.lines { "\n" } else { " " };
+ let shown = shown.replace("\r\n", soft).replace(['\r', '\n'], soft);
+ self.text.text.push_str(&shown);
}
}
@@ -175,8 +180,17 @@ pub fn parse(markdown: &str) -> Vec {
}
impl<'a> Reader<'a> {
- /// A reader of `root`, parsed from `source`.
+ /// A reader of `root`, parsed from `source`, as the screen draws it.
pub fn new(source: &'a str, root: &mdast::Node) -> Self {
+ Self::reading(source, root, true)
+ }
+
+ /// The same, soft breaks joined as GFM output joins them.
+ pub fn joined(source: &'a str, root: &mdast::Node) -> Self {
+ Self::reading(source, root, false)
+ }
+
+ fn reading(source: &'a str, root: &mdast::Node, lines: bool) -> Self {
let mut nodes = Vec::new();
walk(root, &mut nodes);
let (mut urls, mut notes, mut headings) = (HashMap::new(), HashMap::new(), Vec::new());
@@ -199,6 +213,7 @@ impl<'a> Reader<'a> {
urls,
notes,
headings: Vec::new(),
+ lines: false,
};
reader.headings = headings
.into_iter()
@@ -208,6 +223,8 @@ impl<'a> Reader<'a> {
at,
})
.collect();
+ // Titles read on one line; the body as asked.
+ reader.lines = lines;
reader
}
@@ -236,7 +253,7 @@ impl<'a> Reader<'a> {
match node {
mdast::Node::Paragraph(paragraph) => self.paragraph(node, ¶graph.children, out),
mdast::Node::Heading(heading) => {
- let (mut run, mut after) = (Run::default(), Vec::new());
+ let (mut run, mut after) = (self.run(), Vec::new());
for child in &heading.children {
self.inline_into(child, &mut run, &mut Images::After(&mut after));
}
@@ -310,7 +327,7 @@ impl<'a> Reader<'a> {
}
let mut run = Run {
table: matches!(node, mdast::Node::TableCell(_)),
- ..Run::default()
+ ..self.run()
};
for child in children {
self.inline_into(child, &mut run, &mut Images::Split(out));
@@ -318,6 +335,13 @@ impl<'a> Reader<'a> {
text_block(run.cut(), out);
}
+ fn run(&self) -> Run {
+ Run {
+ lines: self.lines,
+ ..Run::default()
+ }
+ }
+
/// A footnote's number, by its first reference.
pub fn note(&self, identifier: &str) -> Option {
self.notes.get(identifier).copied()
@@ -336,7 +360,7 @@ impl<'a> Reader<'a> {
/// Inline text where images draw as their alt text.
pub fn inline(&self, nodes: &[mdast::Node]) -> Inline {
- let mut run = Run::default();
+ let mut run = self.run();
for node in nodes {
self.inline_into(node, &mut run, &mut Images::Alt);
}
diff --git a/src/documents/tree/tests.rs b/src/documents/tree/tests.rs
index b8f867e..25896f6 100644
--- a/src/documents/tree/tests.rs
+++ b/src/documents/tree/tests.rs
@@ -68,20 +68,27 @@ fn an_image_splits_its_paragraph_and_references_resolve() {
}
#[test]
-fn a_soft_break_reads_as_a_space_and_a_hard_one_as_a_line() {
- for (source, shown) in [
- ("one\ntwo \nthree", "one two\nthree"),
- ("one\r\ntwo \r\nthree", "one two\nthree"),
- ("one\rtwo \rthree", "one two\nthree"),
- ] {
+fn a_soft_break_draws_as_a_line_and_joins_for_output() {
+ for source in ["one\ntwo \nthree", "one\r\ntwo \r\nthree", "one\rtwo \rthree"] {
let nodes = parse(source);
assert!(
- matches!(&nodes[0], Node::Paragraph(text) if text.text == shown),
- "{source:?} reads {nodes:?}"
+ matches!(&nodes[0], Node::Paragraph(text) if text.text == "one\ntwo\nthree"),
+ "{source:?} draws {nodes:?}"
);
+ let root = super::super::markdown::tree(source);
+ let paragraph = &root.children().expect("blocks")[0];
+ let joined = Reader::joined(source, &root).inline(paragraph.children().expect("inlines"));
+ assert_eq!(joined.text, "one two\nthree", "{source:?} joins");
}
}
+#[test]
+fn a_heading_title_reads_on_one_line() {
+ let source = "one\ntwo\n===\n";
+ let root = super::super::markdown::tree(source);
+ assert_eq!(Reader::new(source, &root).headings[0].title, "one two");
+}
+
#[test]
fn definitions_nested_in_quotes_and_lists_resolve() {
for source in [
@@ -385,9 +392,9 @@ fn the_first_shortcode_decides_its_text_node() {
("a.:x.y: :smile:", "a.:x.y: š"),
("x\t:smile: y:smile:", "x\t:smile: y:smile:"),
("x\u{a0}:smile: y:smile:", "x\u{a0}:smile: y:smile:"),
- ("x\n:smile: y:smile:", "x :smile: y:smile:"),
+ ("x\n:smile: y:smile:", "x\n:smile: y:smile:"),
("*x*:smile: :smile:", "xš š"),
- ("*x*\n:smile: y:smile:", "x š yš"),
+ ("*x*\n:smile: y:smile:", "x\nš yš"),
("x:smile: `c` :smile:", "x:smile: c š"),
("x:smile: :smile:", "x:smile: š"),
("x:smile:s :smile:", "x:smile:s š"),