Thread RP_DEBUG-driven logging through input, detection, rendering, and pager decisions so CLI behavior is inspectable without polluting stdout. Add focused coverage for debug wiring, env parsing, and stderr-only output.
111 lines
3.4 KiB
Python
111 lines
3.4 KiB
Python
"""Core rendering module for rp."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from rich.console import Console, RenderableType
|
|
from rich.json import JSON as RichJSON
|
|
from rich.markdown import Markdown
|
|
from rich.padding import Padding
|
|
from rich.syntax import Syntax
|
|
|
|
from rp.debug import DebugLogger, log_debug
|
|
from rp.pager import MousePager
|
|
|
|
|
|
@dataclass
|
|
class RenderOptions:
|
|
"""Rendering options for file display.
|
|
|
|
Attributes:
|
|
theme: Pygments color theme name.
|
|
line_numbers: Show line numbers. ``None`` enables them for code only.
|
|
pager: Use pager for output. ``None`` auto-detects terminal output.
|
|
debug: Optional debug logger for render and pager decisions.
|
|
"""
|
|
|
|
theme: str = "monokai"
|
|
line_numbers: bool | None = None
|
|
pager: bool | None = None
|
|
debug: DebugLogger | None = None
|
|
|
|
|
|
_MARKDOWN_TYPES: set[str] = {"markdown", "md"}
|
|
_JSON_TYPES: set[str] = {"json"}
|
|
|
|
|
|
def render(
|
|
content: str, file_type: str, console: Console, options: RenderOptions
|
|
) -> None:
|
|
"""Render content to the console with syntax highlighting.
|
|
|
|
Renders Markdown with Rich Markdown, JSON with Rich JSON with a Syntax
|
|
fallback, and all other types with Pygments Syntax highlighting.
|
|
|
|
Args:
|
|
content: File content to render.
|
|
file_type: Pygments lexer name.
|
|
console: Rich console to render to.
|
|
options: Rendering options (theme, line numbers, pager).
|
|
"""
|
|
line_numbers = options.line_numbers
|
|
if line_numbers is None:
|
|
line_numbers = file_type not in _MARKDOWN_TYPES and file_type not in _JSON_TYPES
|
|
log_debug(
|
|
options.debug,
|
|
f"render: auto-set line_numbers={line_numbers} for {file_type!r}",
|
|
)
|
|
|
|
use_pager = options.pager
|
|
if use_pager is None:
|
|
import sys
|
|
|
|
stdout_is_tty = sys.stdout.isatty()
|
|
use_pager = stdout_is_tty
|
|
log_debug(
|
|
options.debug,
|
|
f"pager: auto-detected {'enabled' if use_pager else 'disabled'} "
|
|
f"because stdout is {'a' if stdout_is_tty else 'not a'} TTY",
|
|
)
|
|
else:
|
|
log_debug(
|
|
options.debug,
|
|
f"pager: explicitly {'enabled' if use_pager else 'disabled'}",
|
|
)
|
|
|
|
renderable: RenderableType
|
|
if file_type in _MARKDOWN_TYPES:
|
|
renderable = Markdown(content)
|
|
elif file_type in _JSON_TYPES:
|
|
try:
|
|
renderable = RichJSON(content)
|
|
except (SyntaxError, ValueError):
|
|
log_debug(options.debug, "render: invalid JSON, falling back to syntax")
|
|
renderable = Syntax(
|
|
content, "json", theme=options.theme, line_numbers=line_numbers
|
|
)
|
|
else:
|
|
try:
|
|
renderable = Syntax(
|
|
content, file_type, theme=options.theme, line_numbers=line_numbers
|
|
)
|
|
except Exception:
|
|
log_debug(
|
|
options.debug,
|
|
f"render: unknown lexer {file_type!r}, falling back to plain text",
|
|
)
|
|
renderable = Syntax(
|
|
content, "text", theme=options.theme, line_numbers=line_numbers
|
|
)
|
|
|
|
padded = Padding(renderable, (1, 2))
|
|
|
|
if use_pager:
|
|
log_debug(options.debug, "pager: opening pager")
|
|
with console.pager(pager=MousePager(debug=options.debug), styles=True):
|
|
console.print(padded)
|
|
else:
|
|
log_debug(options.debug, "pager: writing directly to the console")
|
|
console.print(padded)
|