Repository: Aksh-Bansal-dev/markdown-editor
Pull request #7: fix nested lists parsing
Author: login: Aksh-Bansal-dev
Created: 2025-05-05T16:48:20Z | Merged: 2025-05-05T17:57:48Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Improved support for nested lists in markdown-like syntax, allowing for correct rendering of nested and sibling list structures.

- **Bug Fixes**
  - Adjusted Tab key behavior in textareas to insert two spaces instead of a tab character.

- **Tests**
  - Added new test cases to verify correct parsing of nested and invalidly-indented lists.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---
## EntelligenceAI PR Summary 
 - Refactored list parsing in src/parser.rs for nested list support and better whitespace handling
- Added unit tests in src/lib.rs for nested and invalid list scenarios
- Updated pkg/markdown_parser_bg.wasm to include new parser logic
- Modified index.js to insert spaces instead of tabs on textarea Tab keypress 




Diff:
diff --git a/index.js b/index.js
index ba84a70..b058ea4 100644
--- a/index.js
+++ b/index.js
@@ -9,12 +9,12 @@ input.addEventListener("keydown", function (e) {
     var start = this.selectionStart;
     var end = this.selectionEnd;
 
-    // set textarea value to: text before caret + tab + text after caret
+    // Set textarea value to: text before caret + two spaces + text after caret
     this.value =
-      this.value.substring(0, start) + "\t" + this.value.substring(end);
+      this.value.substring(0, start) + "  " + this.value.substring(end);
 
-    // put caret at right position again
-    this.selectionStart = this.selectionEnd = start + 1;
+    // Move caret past both inserted spaces 
+    this.selectionStart = this.selectionEnd = start + 2;
   }
 });
 
diff --git a/pkg/markdown_parser_bg.wasm b/pkg/markdown_parser_bg.wasm
index f9c6a0b..4ebd28e 100644
Binary files a/pkg/markdown_parser_bg.wasm and b/pkg/markdown_parser_bg.wasm differ
diff --git a/src/lib.rs b/src/lib.rs
index 7245c77..4a9cbd6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -52,4 +52,28 @@ mod tests {
         let result = parse("hello **world*");
         assert_eq!(result, "hello **world*");
     }
+
+    #[test]
+    fn it_parses_nested_list() {
+        let result = parse("- hello\n  - bye");
+        assert_eq!(result, "<ul><li>hello<ul><li>bye</li></ul></li></ul>");
+    }
+
+    #[test]
+    fn it_parses_invalid_nested_list() {
+        let result = parse("- hello\n - bye");
+        assert_eq!(result, "<ul><li>hello</li><li>bye</li></ul>");
+    }
+
+    #[test]
+    fn it_parses_nested_list_2() {
+        let result = parse("- hello\n  - bye\n- go");
+        assert_eq!(result, "<ul><li>hello<ul><li>bye</li></ul></li><li>go</li></ul>");
+    }
+
+    #[test]
+    fn it_parses_nested_list_with_incomplete_list() {
+        let result = parse("- hello\n  -");
+        assert_eq!(result, "<ul><li>hello</li>  -</ul>");
+    }
 }
diff --git a/src/parser.rs b/src/parser.rs
index a6c7802..0ac54c4 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -31,16 +31,7 @@ impl Parser {
     fn parse_line(&mut self) -> String {
         match self.next_char() {
             '#' => self.parse_title(),
-            '-' => {
-                if self.pos + 1 < self.input.len()
-                    && (self.input.chars().nth(self.pos + 1).unwrap() == ' '
-                        || self.input.chars().nth(self.pos + 1).unwrap() == '\t')
-                {
-                    self.parse_list()
-                } else {
-                    self.parse_text(false)
-                }
-            }
+            '-' => self.parse_list(0),
             '\n' => self.parse_newline(),
             _ => self.parse_text(false),
         }
@@ -96,50 +87,60 @@ impl Parser {
         create_html_element("p".to_string(), "".to_string())
     }
 
-    fn parse_list(&mut self) -> String {
+    fn parse_list(&mut self, dep: usize) -> String {
         let mut res = String::new();
-        res.push_str("<ul>");
-        loop {
-            let mut dep = 0;
-            while !self.end_of_line() && (self.next_char() == ' ' || self.next_char() == '\t') {
-                dep += 1;
-                self.consume_char();
+        let mut list_initialized = false;
+        while !self.end_of_line() {
+            let mut spaces: usize = 0;
+            while self.pos + spaces < self.input.len()
+                && self.input.chars().nth(self.pos + spaces).unwrap() == ' '
+            {
+                spaces += 1;
             }
-            dep /= 2;
 
-            if self.pos < self.input.len() - 1
-                && self.next_char() == '-'
-                && (self.input.chars().nth(self.pos + 1).unwrap() == ' '
-                    || self.input.chars().nth(self.pos + 1).unwrap() == '\t')
+            if self.pos + spaces >= self.input.len()
+                || self.input.chars().nth(self.pos + spaces).unwrap() != '-'
             {
-                for _ in 0..dep {
+                res.push_str(self.parse_text(false).as_str())
+            } else if spaces / 2 == dep
+                && self.pos + spaces + 2 <= self.input.len()
+                && self.input[spaces + self.pos..spaces + self.pos + 2] == String::from("- ")
+            {
+                if !list_initialized {
                     res.push_str("<ul>");
+                    list_initialized = true;
                 }
+                self.consume_whitespace();
                 self.consume_char();
                 self.consume_whitespace();
 
                 let text = self.parse_text(false);
                 res.push_str(&create_html_element("li".to_string(), text));
-
-                for _ in 0..dep {
-                    res.push_str("</ul>");
+            } else if spaces / 2 > dep {
+                let child_html = self.parse_list(dep + 1);
+                let mut close_li_tag = false;
+                if res.ends_with("</li>") && child_html.starts_with("<ul>") {
+                    res.truncate(res.len() - 5);
+                    close_li_tag = true;
+                }
+                res.push_str(child_html.as_str());
+                if close_li_tag {
+                    res.push_str("</li>");
                 }
+            } else if self.input.chars().nth(self.pos + spaces).unwrap() == '-' && spaces / 2 == dep
+            {
+                res.push_str(self.parse_text(false).as_str())
             } else {
                 break;
             }
-
-            if self.next_char() != '\n'
-                || self.pos >= self.input.len() - 1
-                || (self.input.chars().nth(self.pos + 1).unwrap() != '-'
-                    && self.input.chars().nth(self.pos + 1).unwrap() != '\t'
-                    && self.input.chars().nth(self.pos + 1).unwrap() != ' ')
-            {
-                break;
+            if self.next_char() == '\n' {
+                self.consume_char();
             }
-            self.consume_char(); // \n
         }
 
-        res.push_str("</ul>");
+        if list_initialized {
+            res.push_str("</ul>");
+        }
         res
     }
 


Reviews:
user: login: entelligence-ai-pr-reviews[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-05T16:49:52Z
user: login: entelligence-ai-pr-reviews[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-05T16:49:53Z
user: login: entelligence-ai-pr-reviews[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-05T16:49:54Z
user: login: entelligence-ai-pr-reviews[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-05T16:49:55Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>index.js (1)</summary><blockquote>

`12-18`: _🛠️ Refactor suggestion_

**Caret offset & comment inconsistent with new “two-spaces” behavior**

1. The inline comment (#12 – 13) still mentions “tab” even though we now insert two spaces.  
2. After inserting two spaces the caret is only advanced by **one** character (`start + 1`).  
   Users will therefore land between the two spaces instead of after them, which is
   surprising and differs from typical editors that indent by two spaces.

```diff
-    // set textarea value to: text before caret + tab + text after caret
+    // set textarea value to: text before caret + two spaces + text after caret
...
-    this.selectionStart = this.selectionEnd = start + 1;
+    // Move caret past both inserted spaces
+    this.selectionStart = this.selectionEnd = start + 2;
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 4e7ef20df48b8d11d1c4745058f11857c31878ed and 090d9f430ad77839a50b48a6385c40298ee31a0c.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `pkg/markdown_parser_bg.wasm` is excluded by `!**/*.wasm`

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `index.js` (1 hunks)
* `src/lib.rs` (1 hunks)
* `src/parser.rs` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>src/lib.rs (1)</summary><blockquote>

<details>
<summary>pkg/markdown_parser.js (1)</summary>

* `parse` (80-93)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>src/lib.rs (1)</summary>

`56-72`: **Tests assert non-standard list structure**

The expected HTML strings place the nested `<ul>` outside the parent `<li>`.
If you adopt the semantic fix proposed for `parse_list`, the correct output
would be:

`<ul><li>hello<ul><li>bye</li></ul></li></ul>`  
likewise for the other tests.

Please adjust the assertions once the parser emits valid HTML.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-05T16:52:00Z

Review comments:
diff_hunk: @@ -11,7 +11,7 @@ input.addEventListener("keydown", function (e) {
 
     // set textarea value to: text before caret + tab + text after caret
     this.value =
-      this.value.substring(0, start) + "\t" + this.value.substring(end);
+      this.value.substring(0, start) + "  " + this.value.substring(end);
 
     // put caret at right position again
     this.selectionStart = this.selectionEnd = start + 1;
path: index.js
user: login: entelligence-ai-pr-reviews[bot]
body: **Correctness**: The cursor position after tab insertion is incorrect. The code sets cursor position to `start + 1` but now inserts 2 spaces instead of a tab, which should place the cursor at `start + 2`.

<!-- suggestion_start -->
<details>
<summary><strong>📝 Committable Code Suggestion</strong></summary>

> ‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

```suggestion
this.value =
      this.value.substring(0, start) + "  " + this.value.substring(end);

    // put caret at right position again
    this.selectionStart = this.selectionEnd = start + 2;
```
</details>
<!-- suggestion_end -->

created_at: 2025-05-05T16:49:52Z
author_association: NONE
diff_hunk: @@ -52,4 +52,22 @@ mod tests {
         let result = parse("hello **world*");
         assert_eq!(result, "hello **world*");
     }
+
+    #[test]
+    fn it_parses_nested_list() {
+        let result = parse("- hello\n  - bye");
+        assert_eq!(result, "<ul><li>hello</li><ul><li>bye</li></ul></ul>");
path: src/lib.rs
user: login: entelligence-ai-pr-reviews[bot]
body: **Correctness**: The HTML structure for nested lists is invalid. Nested `<ul>` elements should be placed inside the parent `<li>` element, not as siblings to it.


created_at: 2025-05-05T16:49:53Z
author_association: NONE
diff_hunk: @@ -52,4 +52,22 @@ mod tests {
         let result = parse("hello **world*");
         assert_eq!(result, "hello **world*");
     }
+
+    #[test]
+    fn it_parses_nested_list() {
+        let result = parse("- hello\n  - bye");
+        assert_eq!(result, "<ul><li>hello</li><ul><li>bye</li></ul></ul>");
+    }
+
+    #[test]
+    fn it_parses_invalid_nested_list() {
+        let result = parse("- hello\n - bye");
+        assert_eq!(result, "<ul><li>hello</li><li>bye</li></ul>");
+    }
+
+    #[test]
+    fn it_parses_nested_list_2() {
+        let result = parse("- hello\n  - bye\n- go");
+        assert_eq!(result, "<ul><li>hello</li><ul><li>bye</li></ul><li>go</li></ul>");
path: src/lib.rs
user: login: entelligence-ai-pr-reviews[bot]
body: **Correctness**: The HTML structure for nested lists with multiple items is invalid. The nested `<ul>` should be inside the parent `<li>` and the subsequent list item should be at the same level as the first.


created_at: 2025-05-05T16:49:54Z
author_association: NONE
diff_hunk: @@ -96,50 +87,49 @@ impl Parser {
         create_html_element("p".to_string(), "".to_string())
     }
 
-    fn parse_list(&mut self) -> String {
+    fn parse_list(&mut self, dep: usize) -> String {
         let mut res = String::new();
-        res.push_str("<ul>");
-        loop {
-            let mut dep = 0;
-            while !self.end_of_line() && (self.next_char() == ' ' || self.next_char() == '\t') {
-                dep += 1;
-                self.consume_char();
+        let mut list_initialized = false;
+        while !self.end_of_line() {
+            let mut spaces: usize = 0;
+            while self.pos + spaces < self.input.len()
+                && self.input.chars().nth(self.pos + spaces).unwrap() == ' '
+            {
+                spaces += 1;
             }
-            dep /= 2;
 
-            if self.pos < self.input.len() - 1
-                && self.next_char() == '-'
-                && (self.input.chars().nth(self.pos + 1).unwrap() == ' '
-                    || self.input.chars().nth(self.pos + 1).unwrap() == '\t')
+            if self.pos + spaces + 1 > self.input.len()
+                || self.input.chars().nth(self.pos + spaces).unwrap() != '-'
+            {
+                break;
+            } else if spaces / 2 == dep
+                && self.input[spaces + self.pos..spaces + self.pos + 2] == String::from("- ")
             {
-                for _ in 0..dep {
+                if !list_initialized {
                     res.push_str("<ul>");
+                    list_initialized = true;
                 }
+                self.consume_whitespace();
                 self.consume_char();
                 self.consume_whitespace();
 
                 let text = self.parse_text(false);
                 res.push_str(&create_html_element("li".to_string(), text));
-
-                for _ in 0..dep {
-                    res.push_str("</ul>");
-                }
+            } else if spaces / 2 > dep {
+                res.push_str(self.parse_list(dep + 1).as_str());
+            } else if self.next_char() == '-' && dep == 0 {
+                res.push_str(self.parse_text(false).as_str())
             } else {
                 break;
             }
-
-            if self.next_char() != '\n'
-                || self.pos >= self.input.len() - 1
-                || (self.input.chars().nth(self.pos + 1).unwrap() != '-'
-                    && self.input.chars().nth(self.pos + 1).unwrap() != '\t'
-                    && self.input.chars().nth(self.pos + 1).unwrap() != ' ')
-            {
-                break;
+            if self.next_char() == '\n' {
+                self.consume_char();
             }
-            self.consume_char(); // \n
         }
 
-        res.push_str("</ul>");
+        if list_initialized {
+            res.push_str("</ul>");
+        }
         res
     }
path: src/parser.rs
user: login: entelligence-ai-pr-reviews[bot]
body: **Correctness**: The `parse_list` function now correctly handles nested lists by recursively calling itself with an incremented depth parameter, fixing the previous implementation that didn't properly handle nested list structures.


created_at: 2025-05-05T16:49:55Z
author_association: NONE
diff_hunk: @@ -96,50 +87,49 @@ impl Parser {
         create_html_element("p".to_string(), "".to_string())
     }
 
-    fn parse_list(&mut self) -> String {
+    fn parse_list(&mut self, dep: usize) -> String {
         let mut res = String::new();
-        res.push_str("<ul>");
-        loop {
-            let mut dep = 0;
-            while !self.end_of_line() && (self.next_char() == ' ' || self.next_char() == '\t') {
-                dep += 1;
-                self.consume_char();
+        let mut list_initialized = false;
+        while !self.end_of_line() {
+            let mut spaces: usize = 0;
+            while self.pos + spaces < self.input.len()
+                && self.input.chars().nth(self.pos + spaces).unwrap() == ' '
+            {
+                spaces += 1;
             }
-            dep /= 2;
 
-            if self.pos < self.input.len() - 1
-                && self.next_char() == '-'
-                && (self.input.chars().nth(self.pos + 1).unwrap() == ' '
-                    || self.input.chars().nth(self.pos + 1).unwrap() == '\t')
+            if self.pos + spaces + 1 > self.input.len()
+                || self.input.chars().nth(self.pos + spaces).unwrap() != '-'
+            {
+                break;
+            } else if spaces / 2 == dep
+                && self.input[spaces + self.pos..spaces + self.pos + 2] == String::from("- ")
             {
-                for _ in 0..dep {
+                if !list_initialized {
                     res.push_str("<ul>");
+                    list_initialized = true;
                 }
+                self.consume_whitespace();
                 self.consume_char();
                 self.consume_whitespace();
 
                 let text = self.parse_text(false);
                 res.push_str(&create_html_element("li".to_string(), text));
-
-                for _ in 0..dep {
-                    res.push_str("</ul>");
-                }
+            } else if spaces / 2 > dep {
+                res.push_str(self.parse_list(dep + 1).as_str());
+            } else if self.next_char() == '-' && dep == 0 {
+                res.push_str(self.parse_text(false).as_str())
             } else {
                 break;
             }
-
-            if self.next_char() != '\n'
-                || self.pos >= self.input.len() - 1
-                || (self.input.chars().nth(self.pos + 1).unwrap() != '-'
-                    && self.input.chars().nth(self.pos + 1).unwrap() != '\t'
-                    && self.input.chars().nth(self.pos + 1).unwrap() != ' ')
-            {
-                break;
+            if self.next_char() == '\n' {
+                self.consume_char();
             }
-            self.consume_char(); // \n
         }
 
-        res.push_str("</ul>");
+        if list_initialized {
+            res.push_str("</ul>");
+        }
         res
     }
path: src/parser.rs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Nested `<ul>` is emitted outside the preceding `<li>`**

The algorithm appends a nested `<ul>` **after** closing the parent `<li>`
(`…</li><ul>…`) instead of placing it *inside* the list-item  
(`…<li>hello<ul>…</ul></li>`).  
This differs from standard HTML semantics and affects styling/accessibility.
Consider emitting the child list *within* the `<li>` that owns it:

```diff
-                let text = self.parse_text(false);
-                res.push_str(&create_html_element("li".to_string(), text));
+                let text = self.parse_text(false);
+                // Open <li>, then possibly recurse, finally close </li>
+                res.push_str("<li>");
+                res.push_str(&text);
```
…and close the `</li>` either
when indentation decreases or right before appending `</ul>`.

(This change will also require updating the expected strings in tests.)



> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 761f8b6
created_at: 2025-05-05T16:51:59Z
author_association: NONE
diff_hunk: @@ -96,50 +87,49 @@ impl Parser {
         create_html_element("p".to_string(), "".to_string())
     }
 
-    fn parse_list(&mut self) -> String {
+    fn parse_list(&mut self, dep: usize) -> String {
         let mut res = String::new();
-        res.push_str("<ul>");
-        loop {
-            let mut dep = 0;
-            while !self.end_of_line() && (self.next_char() == ' ' || self.next_char() == '\t') {
-                dep += 1;
-                self.consume_char();
+        let mut list_initialized = false;
+        while !self.end_of_line() {
+            let mut spaces: usize = 0;
+            while self.pos + spaces < self.input.len()
+                && self.input.chars().nth(self.pos + spaces).unwrap() == ' '
+            {
+                spaces += 1;
             }
-            dep /= 2;
 
-            if self.pos < self.input.len() - 1
-                && self.next_char() == '-'
-                && (self.input.chars().nth(self.pos + 1).unwrap() == ' '
-                    || self.input.chars().nth(self.pos + 1).unwrap() == '\t')
+            if self.pos + spaces + 1 > self.input.len()
+                || self.input.chars().nth(self.pos + spaces).unwrap() != '-'
+            {
+                break;
+            } else if spaces / 2 == dep
+                && self.input[spaces + self.pos..spaces + self.pos + 2] == String::from("- ")
             {
path: src/parser.rs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Possible out-of-bounds slice when line ends with a lone ‘-’**

`self.input[spaces + self.pos .. spaces + self.pos + 2]` assumes there are
at least two bytes after `self.pos + spaces`.  
If the document ends with a single “-” (e.g. `"-"` or `"-\n"`), this slice
will panic at runtime.

```diff
-            if self.pos + spaces + 1 > self.input.len()
+            // Need room for “- ” (dash + space) before slicing two bytes
+            if self.pos + spaces + 2 > self.input.len()
                 || self.input.chars().nth(self.pos + spaces).unwrap() != '-'
 ```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
            // Need room for "- " (dash + space) before slicing two bytes
            if self.pos + spaces + 2 > self.input.len()
                || self.input.chars().nth(self.pos + spaces).unwrap() != '-'
            {
                break;
            } else if spaces / 2 == dep
                && self.input[spaces + self.pos..spaces + self.pos + 2] == String::from("- ")
            {
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 3bab0da
created_at: 2025-05-05T16:51:59Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-05T16:48:27Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes update the Tab key handling in a textarea input to insert two spaces instead of a tab character, adjusting the caret position accordingly. In the markdown-like parser, the list parsing logic was refactored from a non-recursive, flat approach to a recursive method with explicit depth tracking, improving handling of nested lists and indentation. Four new test cases were added to the test suite to verify nested list parsing scenarios, including proper nesting, invalid indentation, sibling list items, and incomplete nested list items. The parser method signature was updated to include the depth parameter.

## Changes

| File(s)                | Change Summary                                                                                                          |
|------------------------|------------------------------------------------------------------------------------------------------------------------|
| index.js               | Modified Tab key handling in textarea: inserts two spaces instead of a tab character, with updated caret positioning.   |
| src/lib.rs             | Added four new test cases covering nested list parsing edge cases in the markdown parser test suite.                    |
| src/parser.rs          | Refactored list parsing logic to recursive, depth-aware approach; updated method signature to include depth parameter.  |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant Textarea
    participant EventHandler

    User->>Textarea: Presses Tab key
    Textarea->>EventHandler: onKeyDown(Tab)
    EventHandler->>Textarea: Insert two spaces at cursor
    EventHandler->>Textarea: Adjust caret position
```

```mermaid
sequenceDiagram
    participant Parser
    participant Input
    participant Output

    loop For each line
        Parser->>Input: Check line start for '-'
        Parser->>Parser: parse_list(dep)
        alt Indentation matches current depth
            Parser->>Output: Add <li> with content
            alt Next line has greater indentation
                Parser->>Parser: parse_list(dep + 1) (nested list)
                Parser->>Output: Add nested <ul> inside <li>
            end
        else Indentation less or no list
            Parser->>Output: Close current <ul> and return
        end
    end
```

## Possibly related PRs

- Aksh-Bansal-dev/markdown-editor#3: Adds basic unit tests for list and heading parsing without changing parsing logic; related by testing list parsing but differs in scope and implementation.

## Poem

> Two spaces hop where tabs once lay,  
> The parser learns to nest and play.  
> Lists within lists, indented just right,  
> Tests ensure the output’s bright.  
> Rabbits rejoice in code so neat—  
> Nested wonders, a whitespace treat!  
> 🐇✨

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 3bab0da86a8363decac9de576c7ff71799f3782a and 761f8b61c38df8aea6fc8b1a4ccbe3bef7bb913b.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `pkg/markdown_parser_bg.wasm` is excluded by `!**/*.wasm`

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `src/lib.rs` (1 hunks)
* `src/parser.rs` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* src/lib.rs
* src/parser.rs

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Aksh-Bansal-dev/markdown-editor&utm_content=7):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: entelligence-ai-pr-reviews[bot]
created_at: 2025-05-05T16:49:48Z
body: ## Review Summary

Skipped posting 3 drafted comments based on your review threshold. Feel free to update them [here](https://www.entelligence.ai/pull-requests).

<details>
<summary>Draft Comments</summary>

<details>
<summary>index.js:7-7</summary>

```
Equality check uses loose equality (`==`) instead of strict equality (`===`), which could lead to unexpected behavior with type coercion.
```

Scores:
- Production Impact: 3
- Fix Specificity: 5
- Urgency Impact: 3
- Total Score: 11

Reason for filtering: The comment meets the threshold for inclusion

Analysis: Using loose equality (==) instead of strict equality (===) in JavaScript can lead to unexpected behavior due to type coercion. While this specific case comparing with a string literal is less likely to cause major issues, it's still a potential source of bugs. The fix is extremely clear and directly applicable. The urgency is medium as this won't immediately break functionality but should be addressed in the near term. The total score meets the minimum threshold of 11 for inclusion.
</details>

<details>
<summary>src/parser.rs:120-121</summary>

```
The condition on line 120-121 handles a special case where a dash is encountered at depth 0 but doesn't match list syntax, treating it as regular text instead of incorrectly as a list item.
```

Scores:
- Production Impact: 4
- Fix Specificity: 3
- Urgency Impact: 4
- Total Score: 11

Reason for filtering: The comment meets the required threshold for inclusion

Analysis: This comment identifies a correctness issue in parsing logic that could cause incorrect rendering of text with dashes. The bug description clearly explains the special case being handled, which prevents misinterpreting regular text as list items. While no specific fix is provided, the context is clear enough to understand the approach. This issue would likely cause visible formatting problems in production, affecting user experience, and should be addressed promptly.
</details>

<details>
<summary>src/parser.rs:92-111</summary>

```
The `list_initialized` flag ensures that HTML `<ul>` tags are only added when actual list items are found, preventing empty list tags from being generated in the output.
```

Scores:
- Production Impact: 3
- Fix Specificity: 4
- Urgency Impact: 3
- Total Score: 10

Reason for filtering: 

Analysis: The comment describes a correctness issue where empty HTML list tags could be generated without proper initialization checks. This has moderate production impact (3) as it could cause incorrect HTML rendering but not crash the application. The fix is quite specific (4) as it explains the purpose of the list_initialized flag and how it prevents empty tags. The urgency is medium (3) as improper HTML generation should be fixed but won't immediately break functionality. With a total score of 10, this comment falls below the threshold of 11 required for medium priority filtering.
</details>

</details>


author_association: NONE
user: login: entelligence-ai-pr-reviews[bot]
created_at: 2025-05-05T16:49:51Z
body: ## Walkthrough
This pull request refactors the Markdown parser to improve support for nested unordered lists, enhances whitespace handling, and increases test coverage for list parsing scenarios. The WebAssembly binary is updated to reflect these parser improvements. Additionally, the textarea Tab key handler is modified to insert spaces instead of tab characters, aligning with common indentation practices.

## Changes
| File(s) | Summary |
| --- | --- |
| index.js | Changed textarea Tab key handler to insert two spaces instead of a tab character. |
| pkg/markdown_parser_bg.wasm | Updated compiled WebAssembly binary to reflect parser changes. |
| src/lib.rs | Added three new unit tests for nested and invalidly nested list parsing. |
| src/parser.rs | Refactored list parsing logic to support nested lists, improved whitespace handling, and simplified code structure. |

## Sequence Diagram
This diagram shows the interactions between components:

```mermaid
sequenceDiagram
    participant Input
    participant Parser
    participant ParserMethods

    Input->>Parser: keydown event
    Parser->>ParserMethods: parse_line()
    ParserMethods-->>Parser: parsed line

    loop For each line
        Parser->>ParserMethods: next_char()
        alt next_char == '#'
            ParserMethods-->>Parser: parse_title()
        else next_char == '-'
            ParserMethods->>ParserMethods: parse_list(0)
            ParserMethods-->>Parser: parsed list
        else next_char == '\n'
            ParserMethods-->>Parser: parse_newline()
        else
            ParserMethods-->>Parser: parse_text(false)
        end
    end

    ParserMethods->>ParserMethods: parse_list(dep)
        loop For each line
            ParserMethods->>ParserMethods: count spaces
            alt spaces/2 == dep && line starts with "- "
                ParserMethods-->>ParserMethods: create_html_element("li", text)
            else spaces/2 > dep
                ParserMethods-->>ParserMethods: parse_list(dep+1)
            else dep == 0 && line starts with "-"
                ParserMethods-->>ParserMethods: parse_text(false)
            else
                break
            end
        end
        opt list_initialized
            ParserMethods-->>ParserMethods: append "</ul>"
        end
    end

    Parser-->>Input: Rendered HTML
```


<details>
<summary>Entelligence.ai can learn from your feedback. Simply add 👍 / 👎 emojis to teach it your preferences. More shortcuts below</summary>

### **Emoji Descriptions:**
- ⚠️ **Potential Issue** - May require further investigation.
- 🔒 **Security Vulnerability** - Fix to ensure system safety.
- 💻 **Code Improvement** - Suggestions to enhance code quality.
- 🔨 **Refactor Suggestion** - Recommendations for restructuring code.
- ℹ️ **Others** - General comments and information.

### **Interact with the Bot:**
- Send a message or request using the format:
`@bot + *your message*`
```plaintext
Example: @bot Can you suggest improvements for this code?
```
- Help the Bot learn by providing feedback on its responses.
`@bot + *feedback*`
```plaintext
Example: @bot Do not comment on `save_auth` function !
```

</details>

author_association: NONE