BareGit

Fix parsing of paragraphs following lists and add code comments

- Fixed a bug where paragraphs immediately following a list were incorrectly nested.

- Added a test case 'ListFollowedByParagraph' to verify the fix.

- Added descriptive comments to the reference link pre-processing and emphasis parsing logic.
Author: MetroWind <chris.corsair@gmail.com>
Date: Sat Mar 21 22:32:31 2026 -0700
Commit: 9c3ca14dc578a6a6eaf8e018275ad1f13d987606

Changes

diff --git a/src/block_parser.cpp b/src/block_parser.cpp
index a83df52..0f83f36 100644
--- a/src/block_parser.cpp
+++ b/src/block_parser.cpp
@@ -577,8 +577,17 @@ void BlockParser::process_line(const std::string& line)
         return;
     }
 
+    // If the current tip is a List, we must close it because a List can only
+    // contain ListItems. A paragraph encountered here indicates the list has
+    // ended.
+    if(tip->type == BlockType::List)
+    {
+        close_unmatched_blocks(open_blocks.size() - 2);
+        tip = open_blocks.back().block;
+    }
+
     if(tip->type == BlockType::Document || tip->type == BlockType::Quote ||
-       tip->type == BlockType::List || tip->type == BlockType::ListItem)
+       tip->type == BlockType::ListItem)
     {
         auto p = std::make_unique<Block>(BlockType::Paragraph);
         Block* p_ptr = p.get();
diff --git a/src/macrodown.cpp b/src/macrodown.cpp
index 568381b..f98ec87 100644
--- a/src/macrodown.cpp
+++ b/src/macrodown.cpp
@@ -17,6 +17,9 @@ MacroDown::MacroDown()
 
 std::unique_ptr<Node> MacroDown::parse(const std::string& input)
 {
+    // Inline pre-processing to handle Link Reference Definitions.
+    // Example: [1]: http://b.org
+    // These lines are extracted and stored in a map.
     std::string processed_input;
     std::map<std::string, std::string> link_refs;
     std::regex link_def_re(R"(^[ \t]*\[([^\]]+)\]:[ \t]*([^\s]+)[ \t]*\r?$)");
@@ -33,14 +36,17 @@ std::unique_ptr<Node> MacroDown::parse(const std::string& input)
         }
         else
         {
+            // Reconstruct the input without the definition lines.
             if(!first) processed_input += "\n";
             processed_input += line;
             first = false;
         }
     }
 
+    // Substitute reference-style links [text][id] with inline-style [text](url).
     for(const auto& pair : link_refs)
     {
+        // Escape the ID for use in regex.
         std::string id_escaped = std::regex_replace(pair.first, std::regex(R"([-[\]{}()*+?.,\^$|#\s])"), R"(\$&)");
         std::regex ref_link_re(R"(\[([^\]]+)\]\[)" + id_escaped + R"(\])");
         processed_input = std::regex_replace(processed_input, ref_link_re, "[$1](" + pair.second + ")");
diff --git a/src/parser.cpp b/src/parser.cpp
index ad8a47a..33084db 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -489,8 +489,8 @@ private:
         nodes_.push_back(std::make_unique<Node>(std::move(macro)));
     }
 
-    // Handles emphasis (*em*) and strong emphasis (**strong**).
-    // Recursively parses the content.
+    // Handles emphasis (*em* or _em_) and strong emphasis (**strong** or
+    // __strong__). Recursively parses the content.
     bool handleEmphasis()
     {
         if(input32_[pos_] == '*' || input32_[pos_] == '_')
diff --git a/tests/test_macrodown.cpp b/tests/test_macrodown.cpp
index 653371f..383ecdb 100644
--- a/tests/test_macrodown.cpp
+++ b/tests/test_macrodown.cpp
@@ -111,6 +111,24 @@ TEST(MacroDownTest, CommonMarkCode)
     EXPECT_EQ(md.render(*md.parse(indented_input)), indented_expected);
 }
 
+// Verify that a paragraph following a list correctly closes the list block.
+TEST(MacroDownTest, ListFollowedByParagraph)
+{
+    MacroDown md;
+    std::string input = "First paragraph\n\n- aaa\n- bbb\n\nSecond paragraph";
+    std::string expected = "<p>First paragraph</p>\n"
+                           "<ul>\n"
+                           "<li>\n"
+                           "<p>aaa</p>\n"
+                           "</li>\n"
+                           "<li>\n"
+                           "<p>bbb</p>\n"
+                           "</li>\n"
+                           "</ul>\n"
+                           "<p>Second paragraph</p>\n";
+    EXPECT_EQ(md.render(*md.parse(input)), expected);
+}
+
 TEST(MacroDownTest, MixedContent)
 {
     MacroDown md;