"""Renderer for unified diff messages."""
from __future__ import annotations
import re
from rich.console import RenderableType
from rich.panel import Panel
from rich.text import Text
from clients.terminal.tui.themes import Theme, get_active_theme
from .base import ContentRenderer
# ``{marker} {num}│ {content}`` — the line-number prefix the server
# (``navi/tools/filesystem.py:_number_diff``) prepends to each diff content line.
# A fixed space follows the marker, then the right-aligned number and ``│ ``.
_DIFF_NUM_RE = re.compile(r"^ (\d+)│ ?(.*)$", re.DOTALL)
def _diff_marker_style(marker: str, theme: Theme) -> str:
if marker == "+":
return theme.success.hex
if marker == "-":
return theme.error.hex
return theme.text.hex
def highlight_unified_diff(text: str, theme: Theme) -> Text:
"""Color a unified-diff string: ``+`` green, ``-`` red, ``@@`` dim, rest text.
When a content line carries a server-prefixed line number
(``{marker} {num}│ {content}``), the ``{marker} {num}│`` column is rendered
dim and only the content takes the marker color — so the number reads as
meta, not as part of the added/removed text. Lines without the prefix (e.g.
the standalone ``diff`` event, or older callers) are colored whole, as before.
Shared by the standalone ``diff`` event renderer and the ``filesystem``
tool-call renderer (``edit_lines`` / ``smart_edit`` / ``diff`` actions embed
a unified diff as plain text in ``ToolResult.output``).
"""
out = Text()
for idx, line in enumerate(text.splitlines()):
if idx:
out.append("\n")
if line.startswith("@@"):
out.append(line, style=theme.text_dim.hex)
continue
if line.startswith("+++") or line.startswith("---"):
out.append(line, style=theme.text.hex)
continue
marker = line[0] if line else ""
rest = line[1:]
m = _DIFF_NUM_RE.match(rest)
if m:
out.append(f"{marker} {m.group(1)}│ ", style=theme.text_dim.hex)
out.append(m.group(2), style=_diff_marker_style(marker, theme))
else:
out.append(line, style=_diff_marker_style(marker, theme))
return out
class DiffRenderer(ContentRenderer):
"""Render a unified diff with added/removed line highlighting."""
def accepts(self, msg: dict) -> bool:
return msg.get("type") == "diff"
def render(self, msg: dict) -> RenderableType:
theme = get_active_theme()
content = msg.get("content", "")
old_label = msg.get("old_label", "---")
new_label = msg.get("new_label", "+++")
highlighted = highlight_unified_diff(content, theme)
return Panel(
highlighted,
title=f"diff: {old_label} → {new_label}",
title_align="left",
border_style=theme.border.hex,
)