Add Micron renderer #3
@@ -239,10 +239,20 @@ class MicronParser:
|
||||
if spans:
|
||||
is_art = MicronParser._is_ascii_art("".join(span.text for span in spans))
|
||||
font_size = 12 * self.ascii_art_scale if is_art else None
|
||||
return [ft.Text(spans=spans, text_align=state["align"], selectable=True, font_family="monospace", size=font_size)]
|
||||
is_art = MicronParser._is_ascii_art(line)
|
||||
font_size = 12 * self.ascii_art_scale if is_art else None
|
||||
return [ft.Text(line, text_align=state["align"], selectable=True, font_family="monospace", size=font_size)]
|
||||
text_control = ft.Text(spans=spans, text_align=state["align"], selectable=True, enable_interactive_selection=True, expand=True, font_family="monospace", size=font_size)
|
||||
else:
|
||||
is_art = MicronParser._is_ascii_art(line)
|
||||
font_size = 12 * self.ascii_art_scale if is_art else None
|
||||
text_control = ft.Text(line, text_align=state["align"], selectable=True, enable_interactive_selection=True, expand=True, font_family="monospace", size=font_size)
|
||||
|
||||
if state["depth"] > 0:
|
||||
indent_em = (state["depth"] - 1) * 1.2
|
||||
text_control = ft.Container(
|
||||
content=text_control,
|
||||
margin=ft.margin.only(left=indent_em * 16),
|
||||
)
|
||||
|
||||
return [text_control]
|
||||
|
||||
@staticmethod
|
||||
def _create_span(text: str, style: dict) -> ft.TextSpan:
|
||||
@@ -375,6 +385,7 @@ class MicronParser:
|
||||
no_wrap=True,
|
||||
overflow=ft.TextOverflow.CLIP,
|
||||
selectable=False,
|
||||
enable_interactive_selection=False,
|
||||
size=font_size,
|
||||
)
|
||||
|
||||
@@ -477,6 +488,8 @@ class MicronParser:
|
||||
size=font_size,
|
||||
),
|
||||
selectable=True,
|
||||
enable_interactive_selection=True,
|
||||
expand=True,
|
||||
font_family="monospace",
|
||||
)
|
||||
|
||||
@@ -547,8 +560,10 @@ def render_micron(content: str, ascii_art_scale: float = 0.75) -> ft.Control:
|
||||
merged_controls.append(ft.Text(
|
||||
combined_text,
|
||||
style=style,
|
||||
text_align=text_align if current_style and text_align else None,
|
||||
text_align=text_align if current_style and text_align else "left",
|
||||
selectable=True,
|
||||
enable_interactive_selection=True,
|
||||
expand=True,
|
||||
font_family="monospace",
|
||||
))
|
||||
current_text_parts = []
|
||||
@@ -587,8 +602,11 @@ def render_micron(content: str, ascii_art_scale: float = 0.75) -> ft.Control:
|
||||
|
||||
flush_text_parts()
|
||||
|
||||
return ft.ListView(
|
||||
controls=merged_controls,
|
||||
spacing=2,
|
||||
return ft.Container(
|
||||
content=ft.ListView(
|
||||
controls=merged_controls,
|
||||
spacing=2,
|
||||
expand=True,
|
||||
),
|
||||
expand=True,
|
||||
)
|
||||
|
||||
@@ -67,14 +67,14 @@ class TestMicronRenderer:
|
||||
content = "# Heading\n\nSome content"
|
||||
result = render_micron(content)
|
||||
|
||||
# Should return a ListView containing controls
|
||||
assert isinstance(result, ft.ListView)
|
||||
assert result.expand is True
|
||||
assert result.spacing == 2
|
||||
# Should return a Container with ListView
|
||||
assert isinstance(result, ft.Container)
|
||||
assert isinstance(result.content, ft.ListView)
|
||||
assert result.content.spacing == 2
|
||||
|
||||
# Should contain controls
|
||||
assert len(result.controls) > 0
|
||||
for control in result.controls:
|
||||
assert len(result.content.controls) > 0
|
||||
for control in result.content.controls:
|
||||
assert control.selectable is True
|
||||
assert control.font_family == "monospace"
|
||||
|
||||
@@ -83,9 +83,8 @@ class TestMicronRenderer:
|
||||
content = ""
|
||||
result = render_micron(content)
|
||||
|
||||
# Should return a ListView
|
||||
assert isinstance(result, ft.ListView)
|
||||
assert result.expand is True
|
||||
# Should return a Container
|
||||
assert isinstance(result, ft.Container)
|
||||
|
||||
# May contain empty controls
|
||||
|
||||
@@ -94,14 +93,13 @@ class TestMicronRenderer:
|
||||
content = "Unicode content: 你好 🌍 αβγ"
|
||||
result = render_micron(content)
|
||||
|
||||
# Should return a ListView
|
||||
assert isinstance(result, ft.ListView)
|
||||
assert result.expand is True
|
||||
# Should return a Container
|
||||
assert isinstance(result, ft.Container)
|
||||
|
||||
# Should contain Text controls with the content
|
||||
assert len(result.controls) > 0
|
||||
assert len(result.content.controls) > 0
|
||||
all_text = ""
|
||||
for control in result.controls:
|
||||
for control in result.content.controls:
|
||||
assert isinstance(control, ft.Text)
|
||||
# Extract text from the merged control
|
||||
if hasattr(control, "value") and control.value:
|
||||
@@ -115,11 +113,11 @@ class TestMicronRenderer:
|
||||
content = "> Level 1\n>> Level 2\n>>> Level 3"
|
||||
result = render_micron(content)
|
||||
|
||||
assert isinstance(result, ft.ListView)
|
||||
assert len(result.controls) == 3
|
||||
assert isinstance(result, ft.Container)
|
||||
assert len(result.content.controls) == 3
|
||||
|
||||
# Check that headings are wrapped in containers with backgrounds
|
||||
for control in result.controls:
|
||||
for control in result.content.controls:
|
||||
assert isinstance(control, ft.Container)
|
||||
assert control.bgcolor is not None # Should have background color
|
||||
assert control.width == float("inf") # Should be full width
|
||||
@@ -129,12 +127,12 @@ class TestMicronRenderer:
|
||||
content = "`!Bold text!` and `_underline_` and `*italic*`"
|
||||
result = render_micron(content)
|
||||
|
||||
assert isinstance(result, ft.ListView)
|
||||
assert len(result.controls) >= 1
|
||||
assert isinstance(result, ft.Container)
|
||||
assert len(result.content.controls) >= 1
|
||||
|
||||
# Should produce some text content
|
||||
all_text = ""
|
||||
for control in result.controls:
|
||||
for control in result.content.controls:
|
||||
if hasattr(control, "value") and control.value:
|
||||
all_text += control.value
|
||||
|
||||
@@ -145,12 +143,12 @@ class TestMicronRenderer:
|
||||
content = "`FffRed text` and `B00Blue background`"
|
||||
result = render_micron(content)
|
||||
|
||||
assert isinstance(result, ft.ListView)
|
||||
assert len(result.controls) >= 1
|
||||
assert isinstance(result, ft.Container)
|
||||
assert len(result.content.controls) >= 1
|
||||
|
||||
# Should produce some text content (color codes may consume characters)
|
||||
all_text = ""
|
||||
for control in result.controls:
|
||||
for control in result.content.controls:
|
||||
if hasattr(control, "value") and control.value:
|
||||
all_text += control.value
|
||||
|
||||
@@ -161,24 +159,26 @@ class TestMicronRenderer:
|
||||
content = "`cCentered text`"
|
||||
result = render_micron(content)
|
||||
|
||||
assert isinstance(result, ft.ListView)
|
||||
assert len(result.controls) == 1
|
||||
assert isinstance(result, ft.Container)
|
||||
assert len(result.content.controls) >= 1
|
||||
|
||||
control = result.controls[0]
|
||||
assert isinstance(control, ft.Text)
|
||||
# Note: alignment may be consumed by 'c' in "Centered"
|
||||
# Just check that we have some text
|
||||
assert len(control.value) > 0
|
||||
# Should have some text content
|
||||
all_text = ""
|
||||
for control in result.content.controls:
|
||||
if hasattr(control, "value") and control.value:
|
||||
all_text += control.value
|
||||
|
||||
assert len(all_text) > 0
|
||||
|
||||
def test_render_micron_comments(self):
|
||||
"""Test that comments are ignored."""
|
||||
content = "# This is a comment\nVisible text"
|
||||
result = render_micron(content)
|
||||
|
||||
assert isinstance(result, ft.ListView)
|
||||
assert isinstance(result, ft.Container)
|
||||
# Should only contain the visible text, not the comment
|
||||
all_text = ""
|
||||
for control in result.controls:
|
||||
for control in result.content.controls:
|
||||
if isinstance(control, ft.Text) and hasattr(control, "value") and control.value:
|
||||
all_text += control.value
|
||||
|
||||
@@ -190,11 +190,11 @@ class TestMicronRenderer:
|
||||
content = "> Main section\n>> Subsection\n>>> Sub-subsection"
|
||||
result = render_micron(content)
|
||||
|
||||
assert isinstance(result, ft.ListView)
|
||||
assert len(result.controls) == 3
|
||||
assert isinstance(result, ft.Container)
|
||||
assert len(result.content.controls) == 3
|
||||
|
||||
# Check indentation increases with depth
|
||||
for i, control in enumerate(result.controls):
|
||||
for i, control in enumerate(result.content.controls):
|
||||
assert isinstance(control, ft.Container)
|
||||
# The inner container should have margin for indentation
|
||||
inner_container = control.content
|
||||
@@ -211,12 +211,12 @@ class TestMicronRenderer:
|
||||
content = f"Normal text\n{ascii_art}\nMore text"
|
||||
result = render_micron(content)
|
||||
|
||||
assert isinstance(result, ft.ListView)
|
||||
assert isinstance(result, ft.Container)
|
||||
# Text gets merged into single control due to text merging
|
||||
assert len(result.controls) >= 1
|
||||
assert len(result.content.controls) >= 1
|
||||
# Should contain the ASCII art content
|
||||
all_text = ""
|
||||
for control in result.controls:
|
||||
for control in result.content.controls:
|
||||
if isinstance(control, ft.Text) and hasattr(control, "value"):
|
||||
all_text += control.value
|
||||
assert "┌───┐" in all_text
|
||||
@@ -227,10 +227,10 @@ class TestMicronRenderer:
|
||||
content = "`=Literal mode`\n# This should be visible\n`=Back to normal`"
|
||||
result = render_micron(content)
|
||||
|
||||
assert isinstance(result, ft.ListView)
|
||||
assert isinstance(result, ft.Container)
|
||||
# Should contain the processed content (literal mode may not be fully implemented)
|
||||
all_text = ""
|
||||
for control in result.controls:
|
||||
for control in result.content.controls:
|
||||
if isinstance(control, ft.Text) and hasattr(control, "value") and control.value:
|
||||
all_text += control.value
|
||||
|
||||
@@ -242,20 +242,9 @@ class TestMicronRenderer:
|
||||
content = "Text above\n-\nText below"
|
||||
result = render_micron(content)
|
||||
|
||||
assert isinstance(result, ft.ListView)
|
||||
# Should contain a Divider control
|
||||
divider_found = False
|
||||
for control in result.controls:
|
||||
if isinstance(control, ft.Container):
|
||||
# Skip containers
|
||||
continue
|
||||
if hasattr(control, "__class__") and "Divider" in control.__class__.__name__:
|
||||
divider_found = True
|
||||
break
|
||||
|
||||
# Note: The current implementation may not create Divider objects
|
||||
# This test documents the expected behavior
|
||||
assert len(result.controls) >= 2 # At least text above and below
|
||||
assert isinstance(result, ft.Container)
|
||||
# Should contain controls for text
|
||||
assert len(result.content.controls) >= 2
|
||||
|
||||
def test_render_micron_complex_formatting(self):
|
||||
"""Test complex combination of micron formatting."""
|
||||
@@ -269,27 +258,29 @@ Final paragraph."""
|
||||
|
||||
result = render_micron(content)
|
||||
|
||||
assert isinstance(result, ft.ListView)
|
||||
assert len(result.controls) >= 3 # Should have multiple elements
|
||||
assert isinstance(result, ft.Container)
|
||||
assert len(result.content.controls) >= 3 # Should have multiple elements
|
||||
|
||||
# Check for heading containers
|
||||
heading_containers = [c for c in result.controls if isinstance(c, ft.Container)]
|
||||
heading_containers = [c for c in result.content.controls if isinstance(c, ft.Container)]
|
||||
assert len(heading_containers) >= 2 # At least 2 headings
|
||||
|
||||
# Check that we have some text content
|
||||
def extract_all_text(control):
|
||||
"""Recursively extract text from control and its children."""
|
||||
text = ""
|
||||
if hasattr(control, "value"):
|
||||
if hasattr(control, "value") and control.value:
|
||||
text += control.value
|
||||
elif hasattr(control, "_Control__attrs") and "value" in control._Control__attrs:
|
||||
text += control._Control__attrs["value"][0]
|
||||
elif hasattr(control, "spans") and control.spans:
|
||||
text += "".join(span.text for span in control.spans)
|
||||
elif hasattr(control, "content"):
|
||||
text += extract_all_text(control.content)
|
||||
return text
|
||||
|
||||
all_text = ""
|
||||
for control in result.controls:
|
||||
for control in result.content.controls:
|
||||
all_text += extract_all_text(control)
|
||||
|
||||
assert "Heading" in all_text
|
||||
@@ -307,9 +298,9 @@ class TestRendererComparison:
|
||||
plaintext_result = render_plaintext(content)
|
||||
micron_result = render_micron(content)
|
||||
|
||||
# Plaintext returns Text, Micron returns ListView
|
||||
# Plaintext returns Text, Micron returns Container
|
||||
assert isinstance(plaintext_result, ft.Text)
|
||||
assert isinstance(micron_result, ft.ListView)
|
||||
assert isinstance(micron_result, ft.Container)
|
||||
|
||||
def test_renderers_preserve_content(self):
|
||||
"""Test that both renderers preserve the original content."""
|
||||
@@ -320,9 +311,9 @@ class TestRendererComparison:
|
||||
|
||||
assert plaintext_result.value == content
|
||||
|
||||
# For micron result (ListView), extract text from merged controls
|
||||
# For micron result (Container), extract text from merged controls
|
||||
micron_text = ""
|
||||
for control in micron_result.controls:
|
||||
for control in micron_result.content.controls:
|
||||
if isinstance(control, ft.Text):
|
||||
# Extract text from the merged control
|
||||
if hasattr(control, "value") and control.value:
|
||||
@@ -344,12 +335,12 @@ class TestRendererComparison:
|
||||
assert plaintext_result.font_family == "monospace"
|
||||
assert plaintext_result.expand is True
|
||||
|
||||
# For micron result (ListView), check properties
|
||||
assert micron_result.expand is True
|
||||
assert micron_result.spacing == 2
|
||||
# For micron result (Container), check properties
|
||||
assert isinstance(micron_result.content, ft.ListView)
|
||||
assert micron_result.content.spacing == 2
|
||||
|
||||
# Check that all Text controls in the column have the expected properties
|
||||
for control in micron_result.controls:
|
||||
for control in micron_result.content.controls:
|
||||
if isinstance(control, ft.Text):
|
||||
assert control.selectable is True
|
||||
assert control.font_family == "monospace"
|
||||
|
||||
Reference in New Issue
Block a user