From 53c4f4039c5a6556b3cb8d8f1584e81f409e77fd Mon Sep 17 00:00:00 2001
From: Dmitriy Pleshevskiy <dmitriy@ideascup.me>
Date: Sun, 12 Jun 2022 01:28:56 +0300
Subject: [PATCH] par(md): add softbreak

---
 par/md.test.ts | 14 +++++++++++++-
 par/md.ts      | 48 ++++++++++++++++++++++++++++++++----------------
 2 files changed, 45 insertions(+), 17 deletions(-)

diff --git a/par/md.test.ts b/par/md.test.ts
index 45a24ba..1de1d6c 100644
--- a/par/md.test.ts
+++ b/par/md.test.ts
@@ -52,7 +52,6 @@ Deno.test({
 
 Deno.test({
   name: "should parse ATX header if line contains additional spaces",
-  only: true,
   fn: () => {
     const par = new MarkdownParser();
     assertEquals(ren.render(par.parse(" # hello")), "<h1>hello</h1>");
@@ -101,3 +100,16 @@ Deno.test({
     assertEquals(ren.render(par.parse("hello")), "<p>hello</p>");
   },
 });
+
+Deno.test({
+  name: "should parse paragraph with softbreak",
+  fn: () => {
+    const par = new MarkdownParser();
+
+    const input = `\
+hello
+world`;
+
+    assertEquals(ren.render(par.parse(input)), "<p>hello world</p>");
+  },
+});
diff --git a/par/md.ts b/par/md.ts
index 0cc3e1c..dd4ea9d 100644
--- a/par/md.ts
+++ b/par/md.ts
@@ -34,11 +34,15 @@ function DocChild(content: AstDocumentChild): Elem {
 }
 
 function Heading(ast: AstAtxHeading): Elem {
-  return new Elem(`h${ast.level}`, {}, ast.content.map(Text));
+  return new Elem(`h${ast.level}`, {}, ast.content.map(InlineContent));
 }
 
 function Paragraph(ast: AstParagraph): Elem {
-  return new Elem("p", {}, ast.content.map(Text));
+  return new Elem("p", {}, ast.content.map(InlineContent));
+}
+
+function InlineContent(ast: AstInlineContent): TextNode {
+  return Text(ast);
 }
 
 function Text(ast: AstText): TextNode {
@@ -92,13 +96,22 @@ function parseParagraph(ast: AstDocument, readStr: string): string | null {
   };
   ast.content.push(paragraph);
 
-  const paragraphInlineContent = readStr.includes("\n")
-    ? readStr.slice(0, readStr.indexOf("\n") + 1)
-    : readStr;
+  let paragraphInlineContent = "";
+  while (!RE_EMPTY_LINE.test(readStr)) {
+    console.log({ readStr });
+    paragraphInlineContent += readStr.includes("\n")
+      ? readStr.slice(0, readStr.indexOf("\n") + 1)
+      : readStr;
+    readStr = readStr.slice(paragraphInlineContent.length);
+  }
 
-  parseInlineContent(paragraph, paragraphInlineContent);
+  console.log({ paragraphInlineContent, readStr });
 
-  return readStr.slice(paragraphInlineContent.length);
+  if (paragraphInlineContent.length) {
+    parseInlineContent(paragraph, paragraphInlineContent);
+  }
+
+  return readStr;
 }
 
 function parseInlineContent(
@@ -107,11 +120,11 @@ function parseInlineContent(
 ): string | null {
   if (!readStr.length) return null;
 
-  const text: AstText = {
-    kind: AstKind.Text,
-    content: readStr.trim(),
-  };
-  ast.content.push(text);
+  const parts = readStr.split("\n").filter(Boolean).map(
+    (textPart): AstText => ({ kind: AstKind.Text, content: textPart }),
+  );
+
+  ast.content = parts;
 
   return readStr;
 }
@@ -121,16 +134,19 @@ function parseInlineContent(
 type AstDocument = BaseAstItem<AstKind.Document, AstDocumentChild[]>;
 type AstDocumentChild = AstAtxHeading | AstParagraph;
 
-interface AstAtxHeading extends BaseAstItem<AstKind.AtxHeading, AstText[]> {
+interface AstAtxHeading
+  extends BaseAstItem<AstKind.AtxHeading, AstInlineContent[]> {
   level: HeadingLevel;
 }
 
-type AstParagraph = BaseAstItem<AstKind.Paragraph, AstText[]>;
+type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
+
+type AstParagraph = BaseAstItem<AstKind.Paragraph, AstInlineContent[]>;
+
+type AstInlineContent = AstText;
 
 type AstText = BaseAstItem<AstKind.Text, string>;
 
-type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
-
 interface BaseAstItem<K extends AstKind, Cont> {
   kind: K;
   content: Cont;