more organization
This commit is contained in:
+46
-56
@@ -10,6 +10,10 @@ from imgui_bundle import imgui_md, imgui, immapp, imgui_color_text_edit as ed
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Callable
|
||||
|
||||
from src import theme_2
|
||||
|
||||
from src.markdown_table import parse_tables, render_table
|
||||
|
||||
|
||||
def _get_language_id(name: str):
|
||||
"""Get a language identifier for ImGuiColorTextEdit.
|
||||
@@ -41,28 +45,24 @@ def _set_editor_language(editor, lang_obj) -> None:
|
||||
1.92.801+: editor.set_language(obj). 1.92.5: editor.set_language_definition(obj).
|
||||
No-op when lang_obj is None (used to skip the call for unknown languages).
|
||||
"""
|
||||
if lang_obj is None:
|
||||
return
|
||||
if hasattr(editor, "set_language"):
|
||||
editor.set_language(lang_obj)
|
||||
elif hasattr(editor, "set_language_definition"):
|
||||
editor.set_language_definition(lang_obj)
|
||||
if lang_obj is None: return
|
||||
|
||||
if hasattr(editor, "set_language"): editor.set_language(lang_obj)
|
||||
elif hasattr(editor, "set_language_definition"): editor.set_language_definition(lang_obj)
|
||||
|
||||
class MarkdownRenderer:
|
||||
"""
|
||||
|
||||
|
||||
Hybrid Markdown renderer that uses imgui_md for text/headers
|
||||
and ImGuiColorTextEdit for syntax-highlighted code blocks.
|
||||
Hybrid Markdown renderer that uses imgui_md for text/headers
|
||||
and ImGuiColorTextEdit for syntax-highlighted code blocks.
|
||||
"""
|
||||
def __init__(self):
|
||||
"""
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
[C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
|
||||
"""
|
||||
self.options = imgui_md.MarkdownOptions()
|
||||
# Base path for fonts (Inter family)
|
||||
self.options.font_options.font_base_path = "fonts/Inter"
|
||||
self.options.font_options.regular_size = 18.0
|
||||
self.options.font_options.regular_size = 18.0
|
||||
|
||||
# Configure callbacks
|
||||
self.options.callbacks.on_open_link = self._on_open_link
|
||||
@@ -80,22 +80,21 @@ class MarkdownRenderer:
|
||||
# Apply the current theme's syntax palette on construction so new
|
||||
# editors we create pick up the right colors. The renderer is re-created
|
||||
# when the theme changes (see theme_2 module-load behavior).
|
||||
from src import theme_2
|
||||
palette_id = theme_2.get_syntax_palette_for_theme(theme_2.get_current_palette())
|
||||
theme_2.apply_syntax_palette(palette_id)
|
||||
|
||||
# Language mapping for ImGuiColorTextEdit
|
||||
self._lang_map = {
|
||||
"python": _get_language_id("python"),
|
||||
"py": _get_language_id("python"),
|
||||
"json": _get_language_id("json"),
|
||||
"cpp": _get_language_id("cpp"),
|
||||
"c++": _get_language_id("cpp"),
|
||||
"c": _get_language_id("c"),
|
||||
"lua": _get_language_id("lua"),
|
||||
"sql": _get_language_id("sql"),
|
||||
"cs": _get_language_id("cs"),
|
||||
"c#": _get_language_id("cs"),
|
||||
"py": _get_language_id("python"),
|
||||
"json": _get_language_id("json"),
|
||||
"cpp": _get_language_id("cpp"),
|
||||
"c++": _get_language_id("cpp"),
|
||||
"c": _get_language_id("c"),
|
||||
"lua": _get_language_id("lua"),
|
||||
"sql": _get_language_id("sql"),
|
||||
"cs": _get_language_id("cs"),
|
||||
"c#": _get_language_id("cs"),
|
||||
}
|
||||
|
||||
def _on_open_link(self, url: str) -> None:
|
||||
@@ -119,28 +118,24 @@ class MarkdownRenderer:
|
||||
|
||||
def render(self, text: str, context_id: str = "default") -> None:
|
||||
"""
|
||||
|
||||
Render Markdown text with code block interception and GFM table substitution.
|
||||
[C: src/theme_2.py:render_post_fx, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_active, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_inactive, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_alert_pulsing_render, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_disabled, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_render]
|
||||
Render Markdown text with code block interception and GFM table substitution.
|
||||
[C: src/theme_2.py:render_post_fx, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_active, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_inactive, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_alert_pulsing_render, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_disabled, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_render]
|
||||
"""
|
||||
if not text:
|
||||
return
|
||||
from src.markdown_table import parse_tables, render_table
|
||||
if not text: return
|
||||
text = self._normalize_bullet_delimiters(text)
|
||||
text = self._normalize_nested_list_endings(text)
|
||||
text = self._normalize_list_continuations(text)
|
||||
blocks = parse_tables(text)
|
||||
lines = text.splitlines(keepends=True)
|
||||
if not lines:
|
||||
return
|
||||
if not lines: return
|
||||
|
||||
table_at_line: dict[int, int] = {b.span[0]: i for i, b in enumerate(blocks)}
|
||||
table_end: dict[int, int] = {b.span[0]: b.span[1] for i, b in enumerate(blocks)}
|
||||
table_at_line: dict[int, int] = {b.span[0]: i for i, b in enumerate(blocks)}
|
||||
table_end: dict[int, int] = {b.span[0]: b.span[1] for i, b in enumerate(blocks)}
|
||||
|
||||
md_buf: list[str] = []
|
||||
md_buf: list[str] = []
|
||||
code_buf: list[str] = []
|
||||
block_idx = 0
|
||||
in_fence = False
|
||||
block_idx = 0
|
||||
in_fence = False
|
||||
fence_marker = ""
|
||||
|
||||
def flush_md() -> None:
|
||||
@@ -161,11 +156,11 @@ class MarkdownRenderer:
|
||||
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
line = lines[i]
|
||||
stripped = line.lstrip().rstrip("\r\n")
|
||||
if stripped.startswith("```"):
|
||||
if not in_fence:
|
||||
in_fence = True
|
||||
in_fence = True
|
||||
fence_marker = stripped[:3]
|
||||
flush_md()
|
||||
code_buf.append(line)
|
||||
@@ -213,7 +208,6 @@ class MarkdownRenderer:
|
||||
(we cannot subclass the C++ imgui_md class).
|
||||
[C: src/markdown_helper.py:MarkdownRenderer.render]
|
||||
"""
|
||||
import re
|
||||
return re.sub(r"(?m)^([ \t]*)\*[ \t]+", r"\1- ", text)
|
||||
|
||||
def _normalize_nested_list_endings(self, text: str) -> str:
|
||||
@@ -226,7 +220,6 @@ class MarkdownRenderer:
|
||||
paragraph break. Cannot fix the upstream C++ from Python.
|
||||
[C: src/markdown_helper.py:MarkdownRenderer.render]
|
||||
"""
|
||||
import re
|
||||
lines = text.split("\n")
|
||||
out: list[str] = []
|
||||
for i, line in enumerate(lines):
|
||||
@@ -261,17 +254,16 @@ class MarkdownRenderer:
|
||||
a single list item. Acceptable for our use case.
|
||||
[C: src.markdown_helper:MarkdownRenderer.render]
|
||||
"""
|
||||
import re
|
||||
lines = text.split("\n")
|
||||
out: list[str] = []
|
||||
prev_was_list = False
|
||||
prev_indent = 0
|
||||
prev_indent = 0
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip():
|
||||
out.append(line)
|
||||
if re.match(r"^\s*[-*+\d]\s+", line):
|
||||
prev_was_list = True
|
||||
prev_indent = len(line) - len(line.lstrip())
|
||||
prev_indent = len(line) - len(line.lstrip())
|
||||
else:
|
||||
prev_was_list = False
|
||||
continue
|
||||
@@ -285,11 +277,10 @@ class MarkdownRenderer:
|
||||
out.append(line)
|
||||
prev_was_list = False
|
||||
continue
|
||||
next_line = lines[j]
|
||||
curr_indent = len(next_line) - len(next_line.lstrip())
|
||||
next_line = lines[j]
|
||||
curr_indent = len(next_line) - len(next_line.lstrip())
|
||||
is_next_list = bool(re.match(r"^\s*[-*+\d]", next_line))
|
||||
if curr_indent > prev_indent and not is_next_list:
|
||||
continue
|
||||
if curr_indent > prev_indent and not is_next_list: continue
|
||||
out.append(line)
|
||||
prev_was_list = False
|
||||
return "\n".join(out)
|
||||
@@ -300,7 +291,7 @@ class MarkdownRenderer:
|
||||
|
||||
def _render_code_block(self, block: str, context_id: str, block_idx: int) -> None:
|
||||
"""Render a code block using TextEditor for syntax highlighting."""
|
||||
lines = block.strip('`').split('\n')
|
||||
lines = block.strip('`').split('\n')
|
||||
lang_tag = lines[0].strip().lower() if lines else ""
|
||||
|
||||
# Heuristic to separate lang tag from code
|
||||
@@ -333,10 +324,10 @@ class MarkdownRenderer:
|
||||
if p_id is not None:
|
||||
editor.set_palette(p_id)
|
||||
|
||||
self._editor_cache[cache_key] = editor
|
||||
self._editor_cache[cache_key] = editor
|
||||
self._editor_lang_cache[cache_key] = None
|
||||
|
||||
editor = self._editor_cache[cache_key]
|
||||
editor = self._editor_cache[cache_key]
|
||||
current_lang = self._editor_lang_cache[cache_key]
|
||||
|
||||
# Sync text and language. None means "no language set" (skip the call).
|
||||
@@ -354,10 +345,10 @@ class MarkdownRenderer:
|
||||
self._editor_lang_cache[cache_key] = lang_tag
|
||||
|
||||
# Dynamic height calculation
|
||||
line_count = code.count('\n') + 1
|
||||
line_count = code.count('\n') + 1
|
||||
line_height = imgui.get_text_line_height()
|
||||
height = (line_count * line_height) + 20
|
||||
height = min(max(height, 40), 500)
|
||||
height = (line_count * line_height) + 20
|
||||
height = min(max(height, 40), 500)
|
||||
|
||||
editor.render(f"##code_{context_id}_{block_idx}", a_size=imgui.ImVec2(0, height))
|
||||
|
||||
@@ -386,13 +377,12 @@ _renderer: Optional[MarkdownRenderer] = None
|
||||
|
||||
def get_renderer() -> MarkdownRenderer:
|
||||
global _renderer
|
||||
if _renderer is None:
|
||||
_renderer = MarkdownRenderer()
|
||||
if _renderer is None: _renderer = MarkdownRenderer()
|
||||
return _renderer
|
||||
|
||||
def render(text: str, context_id: str = "default") -> None:
|
||||
"""
|
||||
[C: src/theme_2.py:render_post_fx, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_active, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_inactive, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_alert_pulsing_render, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_disabled, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_render]
|
||||
[C: src/theme_2.py:render_post_fx, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_active, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_inactive, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_alert_pulsing_render, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_disabled, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_render]
|
||||
"""
|
||||
get_renderer().render(text, context_id)
|
||||
|
||||
@@ -400,4 +390,4 @@ def render_unindented(text: str) -> None:
|
||||
get_renderer().render_unindented(text)
|
||||
|
||||
def render_code(code: str, lang: str = "", context_id: str = "default", block_idx: int = 0) -> None:
|
||||
get_renderer().render_code(code, lang, context_id, block_idx)
|
||||
get_renderer().render_code(code, lang, context_id, block_idx)
|
||||
|
||||
Reference in New Issue
Block a user