Newer
Older
navi-1 / clients / terminal / tui / renderers / artifact.py
@Eugene Sukhodolskiy Eugene Sukhodolskiy 2 days ago 2 KB Navi Code TUI: complete Phase 4
"""Renderer for code artifact messages."""

from __future__ import annotations

from rich.console import RenderableType
from rich.panel import Panel
from rich.syntax import Syntax
from rich.text import Text

from clients.terminal.tui.themes import get_active_theme

from .base import ContentRenderer


def _theme_aware_code_theme(theme_name: str) -> str:
    """Pick a Pygments code theme that matches the Navi theme brightness."""
    return "dracula" if theme_name == "gnexus-dark" else "github-light"


class ArtifactRenderer(ContentRenderer):
    """Render a file-like artifact with syntax highlighting."""

    def accepts(self, msg: dict) -> bool:
        return msg.get("type") == "artifact"

    def render(self, msg: dict) -> RenderableType:
        theme = get_active_theme()
        path = msg.get("path", "artifact")
        language = msg.get("language") or _guess_language(path)
        content = msg.get("content", "")

        if not content.strip():
            return Panel(
                Text("(empty artifact)", style=theme.text_dim.hex),
                title=path,
                title_align="left",
                border_style=theme.border.hex,
            )

        code_theme = _theme_aware_code_theme(theme.name)
        syntax = Syntax(
            content,
            language,
            theme=code_theme,
            background_color=theme.surface.hex,
            line_numbers=True,
            word_wrap=True,
        )
        return Panel(
            syntax,
            title=f"{path} [{language}]",
            title_align="left",
            border_style=theme.tool_border.hex,
        )


def _guess_language(path: str) -> str:
    """Best-effort language tag from a file path."""
    mapping = {
        ".py": "python",
        ".js": "javascript",
        ".ts": "typescript",
        ".tsx": "tsx",
        ".jsx": "jsx",
        ".go": "go",
        ".rs": "rust",
        ".c": "c",
        ".cpp": "cpp",
        ".h": "c",
        ".java": "java",
        ".kt": "kotlin",
        ".sh": "bash",
        ".zsh": "bash",
        ".bash": "bash",
        ".md": "markdown",
        ".json": "json",
        ".yaml": "yaml",
        ".yml": "yaml",
        ".toml": "toml",
        ".html": "html",
        ".css": "css",
        ".scss": "scss",
        ".sql": "sql",
        ".dockerfile": "dockerfile",
        ".txt": "text",
        ".env": "bash",
    }
    lower = path.lower()
    for ext, lang in mapping.items():
        if lower.endswith(ext):
            return lang
    return "text"