import re from dataclasses import dataclass from imgui_bundle import imgui, imgui_md _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 row in block.rows: imgui.table_next_row() for c in row: imgui.table_next_column() imgui_md.render(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