Files
rich-print/src/rp/render.py
Yunxiao Xu 93c777cf6a Add opt-in debug logging across the CLI pipeline
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.
2026-04-03 19:09:24 -07:00

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)