Skip to content

Commit 8becc84

Browse files
committed
Enhance list rendering in md2term.py to support proper nesting and indentation for ordered and unordered lists. Update tests in test_md2term.py to validate nested lists, deeply nested lists, and mixed list types, ensuring accurate output formatting. Update snapshots to reflect changes in rendering behavior.
1 parent 4c3f662 commit 8becc84

File tree

3 files changed

+253
-33
lines changed

3 files changed

+253
-33
lines changed

md2term.py

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -300,11 +300,14 @@ def _render_callout(self, content: str, callout_info: Dict[str, str]) -> None:
300300
)
301301
self.console.print(panel)
302302

303-
def _render_list(self, token: Dict[str, Any]) -> None:
304-
"""Render ordered or unordered lists."""
303+
def _render_list(self, token: Dict[str, Any], indent_level: int = 0) -> None:
304+
"""Render ordered or unordered lists with proper nesting support."""
305305
ordered = token["attrs"].get("ordered", False)
306306
start = token["attrs"].get("start", 1)
307307

308+
# Calculate indentation for this level
309+
indent = " " * indent_level # 2 spaces per level
310+
308311
for i, item in enumerate(token["children"]):
309312
if ordered:
310313
marker = f"{start + i}."
@@ -313,45 +316,55 @@ def _render_list(self, token: Dict[str, Any]) -> None:
313316
marker = "•"
314317
marker_style = "bold yellow"
315318

316-
# Create the marker text
317-
marker_text = Text(marker, style=marker_style)
319+
# Process the list item children
320+
has_paragraph = False
321+
nested_lists = []
322+
paragraph_content = Text()
318323

319-
# Render list item content as Rich Text objects
320-
content_text = Text()
321324
for child in item["children"]:
322325
if child["type"] == "paragraph":
323-
# For paragraphs, render inline tokens directly
326+
has_paragraph = True
327+
# Render inline tokens for the paragraph
324328
for inline_child in child["children"]:
325329
if inline_child["type"] == "text":
326-
content_text.append(inline_child["raw"])
330+
paragraph_content.append(inline_child["raw"])
327331
else:
328332
# Render other inline tokens
329333
inline_text = self._render_inline_tokens([inline_child])
330-
content_text.append_text(inline_text)
334+
paragraph_content.append_text(inline_text)
335+
elif child["type"] == "list":
336+
# Store nested lists to render after the main content
337+
nested_lists.append(child)
331338
else:
332-
# For other types, fall back to the old method
333-
old_console = self.console
334-
buffer = StringIO()
335-
temp_console = Console(
336-
file=buffer,
337-
width=self.console.size.width - 4,
338-
force_terminal=True,
339-
color_system="256",
340-
legacy_windows=False,
341-
)
342-
self.console = temp_console
343-
self._render_token(child)
344-
self.console = old_console
345-
content_text.append(buffer.getvalue().rstrip())
346-
347-
# Combine marker and content
348-
line_text = Text()
349-
line_text.append_text(marker_text)
350-
line_text.append(" ")
351-
line_text.append_text(content_text)
352-
353-
# Print the complete line
354-
self.console.print(line_text)
339+
# Handle other block elements (shouldn't happen in well-formed markdown)
340+
if not has_paragraph:
341+
# If no paragraph, treat as direct content
342+
old_console = self.console
343+
buffer = StringIO()
344+
temp_console = Console(
345+
file=buffer,
346+
width=self.console.size.width - len(indent) - 4,
347+
force_terminal=True,
348+
color_system="256",
349+
legacy_windows=False,
350+
)
351+
self.console = temp_console
352+
self._render_token(child)
353+
self.console = old_console
354+
paragraph_content.append(buffer.getvalue().rstrip())
355+
356+
# Render the main list item line
357+
if has_paragraph or paragraph_content.plain:
358+
line_text = Text()
359+
line_text.append(indent)
360+
line_text.append(marker, style=marker_style)
361+
line_text.append(" ")
362+
line_text.append_text(paragraph_content)
363+
self.console.print(line_text)
364+
365+
# Render any nested lists with increased indentation
366+
for nested_list in nested_lists:
367+
self._render_list(nested_list, indent_level + 1)
355368

356369
def _render_thematic_break(self) -> None:
357370
"""Render a horizontal rule."""

tests/__snapshots__/test_md2term.ambr

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
# ---
3535
# name: TestCLIInterface.test_cli_version
3636
'''
37-
main, version 0.1.0
37+
main, version 1.0.1
3838

3939
'''
4040
# ---
@@ -135,6 +135,24 @@
135135

136136
'''
137137
# ---
138+
# name: TestMarkdownFeatures.test_cheesecake_recipe
139+
'''
140+
Classic New York Style Cheesecake
141+
142+
Ingredients:
143+
144+
• Crust:
145+
• 2 cups graham cracker crumbs (about 40 full crackers)
146+
• 1/3 cup granulated sugar
147+
• 1/2 cup unsalted butter, melted
148+
• Cheese Filling:
149+
• 4 packages (8 oz each) cream cheese, at room temperature
150+
• 1 cup granulated sugar
151+
• 1 tablespoon vanilla extract
152+
• 4 large eggs
153+
154+
'''
155+
# ---
138156
# name: TestMarkdownFeatures.test_code_blocks_bash
139157
'''
140158
╭──────────────────────────────────────────────────────────────────────────────╮
@@ -374,6 +392,20 @@
374392

375393
'''
376394
# ---
395+
# name: TestMarkdownFeatures.test_deeply_nested_lists
396+
'''
397+
• Level 1 item
398+
• Level 2 item
399+
• Level 3 item
400+
• Level 4 item
401+
• Another level 3 item
402+
• Another level 2 item
403+
• Another level 1 item
404+
• Level 2 under second item
405+
• Level 3 under second item
406+
407+
'''
408+
# ---
377409
# name: TestMarkdownFeatures.test_headings_all_levels
378410
'''
379411
────────────────────────────────────────────────────────────────────────────────
@@ -414,6 +446,20 @@
414446

415447
'''
416448
# ---
449+
# name: TestMarkdownFeatures.test_mixed_list_types
450+
'''
451+
1. First ordered item
452+
• Unordered sub-item
453+
• Another unordered sub-item
454+
1. Nested ordered item
455+
2. Another nested ordered item
456+
2. Second ordered item
457+
• Mixed content here
458+
• Deep nesting
459+
1. Very deep ordered item
460+
461+
'''
462+
# ---
417463
# name: TestMarkdownFeatures.test_multiple_blockquotes
418464
'''
419465
│ This is a blockquote with some important information.
@@ -422,6 +468,41 @@
422468

423469
'''
424470
# ---
471+
# name: TestMarkdownFeatures.test_nested_lists
472+
'''
473+
Classic New York Style Cheesecake
474+
475+
Ingredients:
476+
477+
• Crust:
478+
• 2 cups graham cracker crumbs (about 40 full crackers)
479+
• 1/3 cup granulated sugar
480+
• 1/2 cup unsalted butter, melted
481+
• Cheese Filling:
482+
• 4 packages (8 oz each) cream cheese, at room temperature
483+
• 1 cup granulated sugar
484+
• 1 tablespoon vanilla extract
485+
• 4 large eggs
486+
487+
Instructions:
488+
489+
1. Prepare the crust:
490+
1. Preheat oven to 350°F (175°C)
491+
2. Mix graham cracker crumbs and sugar
492+
3. Add melted butter and mix well
493+
4. Press into bottom of springform pan
494+
2. Make the filling:
495+
1. Beat cream cheese until smooth
496+
2. Gradually add sugar
497+
3. Add vanilla extract
498+
4. Beat in eggs one at a time
499+
3. Bake and cool:
500+
1. Pour filling over crust
501+
2. Bake for 50-60 minutes
502+
3. Cool completely before serving
503+
504+
'''
505+
# ---
425506
# name: TestMarkdownFeatures.test_ordered_lists
426507
'''
427508
1. First numbered item

tests/test_md2term.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,132 @@ def test_ordered_lists(self, snapshot):
202202
result = output.getvalue()
203203
assert result == snapshot
204204

205+
def test_nested_lists(self, snapshot):
206+
"""Test nested lists with proper indentation."""
207+
markdown = """### Classic New York Style Cheesecake
208+
209+
#### Ingredients:
210+
- **Crust:**
211+
- 2 cups graham cracker crumbs (about 40 full crackers)
212+
- 1/3 cup granulated sugar
213+
- 1/2 cup unsalted butter, melted
214+
215+
- **Cheese Filling:**
216+
- 4 packages (8 oz each) cream cheese, at room temperature
217+
- 1 cup granulated sugar
218+
- 1 tablespoon vanilla extract
219+
- 4 large eggs
220+
221+
#### Instructions:
222+
1. **Prepare the crust:**
223+
1. Preheat oven to 350°F (175°C)
224+
2. Mix graham cracker crumbs and sugar
225+
3. Add melted butter and mix well
226+
4. Press into bottom of springform pan
227+
228+
2. **Make the filling:**
229+
1. Beat cream cheese until smooth
230+
2. Gradually add sugar
231+
3. Add vanilla extract
232+
4. Beat in eggs one at a time
233+
234+
3. **Bake and cool:**
235+
1. Pour filling over crust
236+
2. Bake for 50-60 minutes
237+
3. Cool completely before serving"""
238+
239+
output = io.StringIO()
240+
console = create_test_console(output)
241+
renderer = TerminalRenderer(console)
242+
243+
import mistune
244+
245+
markdown_parser = mistune.create_markdown(renderer=None)
246+
tokens = markdown_parser(markdown)
247+
renderer.render(tokens)
248+
249+
result = output.getvalue()
250+
assert result == snapshot
251+
252+
def test_deeply_nested_lists(self, snapshot):
253+
"""Test deeply nested lists (3+ levels)."""
254+
markdown = """- Level 1 item
255+
- Level 2 item
256+
- Level 3 item
257+
- Level 4 item
258+
- Another level 3 item
259+
- Another level 2 item
260+
- Another level 1 item
261+
- Level 2 under second item
262+
- Level 3 under second item"""
263+
264+
output = io.StringIO()
265+
console = create_test_console(output)
266+
renderer = TerminalRenderer(console)
267+
268+
import mistune
269+
270+
markdown_parser = mistune.create_markdown(renderer=None)
271+
tokens = markdown_parser(markdown)
272+
renderer.render(tokens)
273+
274+
result = output.getvalue()
275+
assert result == snapshot
276+
277+
def test_mixed_list_types(self, snapshot):
278+
"""Test mixing ordered and unordered lists."""
279+
markdown = """1. First ordered item
280+
- Unordered sub-item
281+
- Another unordered sub-item
282+
1. Nested ordered item
283+
2. Another nested ordered item
284+
2. Second ordered item
285+
- Mixed content here
286+
- Deep nesting
287+
1. Very deep ordered item"""
288+
289+
output = io.StringIO()
290+
console = create_test_console(output)
291+
renderer = TerminalRenderer(console)
292+
293+
import mistune
294+
295+
markdown_parser = mistune.create_markdown(renderer=None)
296+
tokens = markdown_parser(markdown)
297+
renderer.render(tokens)
298+
299+
result = output.getvalue()
300+
assert result == snapshot
301+
302+
def test_cheesecake_recipe(self, snapshot):
303+
"""Test the original cheesecake recipe example that had nested list issues."""
304+
markdown = """### Classic New York Style Cheesecake
305+
306+
#### Ingredients:
307+
- **Crust:**
308+
- 2 cups graham cracker crumbs (about 40 full crackers)
309+
- 1/3 cup granulated sugar
310+
- 1/2 cup unsalted butter, melted
311+
312+
- **Cheese Filling:**
313+
- 4 packages (8 oz each) cream cheese, at room temperature
314+
- 1 cup granulated sugar
315+
- 1 tablespoon vanilla extract
316+
- 4 large eggs"""
317+
318+
output = io.StringIO()
319+
console = create_test_console(output)
320+
renderer = TerminalRenderer(console)
321+
322+
import mistune
323+
324+
markdown_parser = mistune.create_markdown(renderer=None)
325+
tokens = markdown_parser(markdown)
326+
renderer.render(tokens)
327+
328+
result = output.getvalue()
329+
assert result == snapshot
330+
205331
def test_blockquotes(self, snapshot):
206332
"""Test blockquotes with formatting."""
207333
markdown = """> This is a blockquote with some important information.

0 commit comments

Comments
 (0)