afa2f31e11
ROOT CAUSE: src/markdown_table.py:render_table was missing imgui.table_setup_column() calls. In ImGui, columns MUST be configured via table_setup_column before table_headers_row is called. Without it, the table has no defined columns, causing cells to render at overlapping Y positions. This manifested as text overlap in the Discussion Hub's read_mode entries (e.g., 'swc2 -> gte_sw' overlapping the line above it). FIX: Call imgui.table_setup_column(h, TableColumnFlags_.width_stretch) for each header BEFORE table_headers_row(). Each column now has a defined width (stretch = fills available space) and cells render correctly without overlap. Tests: - New test_markdown_table_columns.py asserts setup_column is called once per column and table_next_column is called for each cell. - 16/16 broad regression pass (test_markdown_table, test_markdown_table_render, test_markdown_render_robust, test_gen_send_empty_context, test_gui_fast_render)
74 lines
2.0 KiB
Python
74 lines
2.0 KiB
Python
import re
|
|
from dataclasses import dataclass
|
|
from imgui_bundle import imgui
|
|
|
|
_TABLE_SEPARATOR = re.compile(r"^\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$")
|
|
|
|
def render_table(block: "TableBlock") -> None:
|
|
"""Render a GFM table block via imgui.begin_table.
|
|
[C: src/markdown_helper.py:MarkdownRenderer.render]
|
|
"""
|
|
n_cols = len(block.headers)
|
|
if n_cols == 0: return
|
|
flags = imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable
|
|
if not imgui.begin_table("md_table", n_cols, flags): return
|
|
for h in block.headers:
|
|
imgui.table_setup_column(h, imgui.TableColumnFlags_.width_stretch)
|
|
imgui.table_headers_row()
|
|
for h in block.headers:
|
|
imgui.table_next_column()
|
|
imgui.text(h)
|
|
for row in block.rows:
|
|
imgui.table_next_row()
|
|
for c in row:
|
|
imgui.table_next_column()
|
|
imgui.text(c)
|
|
imgui.end_table()
|
|
|
|
@dataclass(frozen=True)
|
|
class TableBlock:
|
|
"""Frozen GFM table block.
|
|
[C: src/markdown_helper.py:MarkdownRenderer.render]
|
|
"""
|
|
headers: list[str]
|
|
rows: list[list[str]]
|
|
span: tuple[int, int]
|
|
|
|
def _split_row(line: str) -> list[str]:
|
|
line = line.strip()
|
|
if line.startswith("|"): line = line[1:]
|
|
if line.endswith("|"): line = line[:-1]
|
|
return [c.strip() for c in line.split("|")]
|
|
|
|
def _is_table_at(lines: list[str], i: int) -> bool:
|
|
if i + 1 >= len(lines): return False
|
|
if "|" not in lines[i]: return False
|
|
return bool(_TABLE_SEPARATOR.match(lines[i + 1]))
|
|
|
|
def parse_tables(text: str) -> list[TableBlock]:
|
|
lines = text.splitlines()
|
|
in_fence = False
|
|
blocks: list[TableBlock] = []
|
|
i = 0
|
|
while i < len(lines):
|
|
line = lines[i]
|
|
if line.strip().startswith("```"):
|
|
in_fence = not in_fence
|
|
i += 1
|
|
continue
|
|
if in_fence:
|
|
i += 1
|
|
continue
|
|
if _is_table_at(lines, i):
|
|
headers = _split_row(lines[i])
|
|
j = i + 2
|
|
rows: list[list[str]] = []
|
|
while j < len(lines) and "|" in lines[j] and not _TABLE_SEPARATOR.match(lines[j]):
|
|
rows.append(_split_row(lines[j]))
|
|
j += 1
|
|
blocks.append(TableBlock(headers=headers, rows=rows, span=(i, j)))
|
|
i = j
|
|
continue
|
|
i += 1
|
|
return blocks
|