"""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)