diff --git a/clients/terminal/tui/renderers/__init__.py b/clients/terminal/tui/renderers/__init__.py index f0acc07..cf33a5d 100644 --- a/clients/terminal/tui/renderers/__init__.py +++ b/clients/terminal/tui/renderers/__init__.py @@ -4,7 +4,7 @@ from .base import ContentRenderer from .registry import RendererRegistry -from . import message, tool, thinking, error, markdown_content, plain, diff, artifact, status, planning, subagent, todo, turn_meta, summary +from . import message, tool, thinking, error, markdown_content, plain, diff, artifact, status, planning, subagent, todo, turn_meta, summary, filesystem def default_registry() -> RendererRegistry: @@ -19,6 +19,9 @@ reg.register(todo.TodoStartedRenderer()) reg.register(todo.TodoResultRenderer()) reg.register(tool.ToolStartedRenderer()) + # filesystem-specific result renderer must be checked before the generic + # ToolResultRenderer (first accepting renderer wins). + reg.register(filesystem.FilesystemToolResultRenderer()) reg.register(tool.ToolResultRenderer()) reg.register(error.ErrorRenderer()) reg.register(status.StatusRenderer()) diff --git a/clients/terminal/tui/renderers/diff.py b/clients/terminal/tui/renderers/diff.py index 7f99592..1935c67 100644 --- a/clients/terminal/tui/renderers/diff.py +++ b/clients/terminal/tui/renderers/diff.py @@ -6,11 +6,33 @@ from rich.panel import Panel from rich.text import Text -from clients.terminal.tui.themes import get_active_theme +from clients.terminal.tui.themes import Theme, get_active_theme from .base import ContentRenderer +def highlight_unified_diff(text: str, theme: Theme) -> Text: + """Color a unified-diff string: ``+`` green, ``-`` red, ``@@`` dim, rest text. + + 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("+") and not line.startswith("+++"): + out.append(line, style=theme.success.hex) + elif line.startswith("-") and not line.startswith("---"): + out.append(line, style=theme.error.hex) + elif line.startswith("@@"): + out.append(line, style=theme.text_dim.hex) + else: + out.append(line, style=theme.text.hex) + return out + + class DiffRenderer(ContentRenderer): """Render a unified diff with added/removed line highlighting.""" @@ -23,23 +45,11 @@ old_label = msg.get("old_label", "---") new_label = msg.get("new_label", "+++") - lines = content.splitlines() - highlighted = Text() - for idx, line in enumerate(lines): - if idx: - highlighted.append("\n") - if line.startswith("+") and not line.startswith("+++"): - highlighted.append(line, style=theme.success.hex) - elif line.startswith("-") and not line.startswith("---"): - highlighted.append(line, style=theme.error.hex) - elif line.startswith("@@"): - highlighted.append(line, style=theme.text_dim.hex) - else: - highlighted.append(line, style=theme.text.hex) + highlighted = highlight_unified_diff(content, theme) return Panel( highlighted, title=f"diff: {old_label} → {new_label}", title_align="left", border_style=theme.border.hex, - ) + ) \ No newline at end of file diff --git a/clients/terminal/tui/renderers/filesystem.py b/clients/terminal/tui/renderers/filesystem.py new file mode 100644 index 0000000..9b92031 --- /dev/null +++ b/clients/terminal/tui/renderers/filesystem.py @@ -0,0 +1,208 @@ +"""Styled renderer for ``filesystem`` tool-call results. + +The filesystem tool embeds structured output (unified diffs, directory listings, +grep matches) as plain text in ``ToolResult.output``. This renderer detects the +action via ``args.action`` and restyles the output — diff highlighting, listing +layout, grep match highlighting — without touching the tool or the WebSocket +protocol. Unknown / not-yet-styled actions fall back to the generic plain +rendering used by :class:`ToolResultRenderer`. +""" + +from __future__ import annotations + +import re + +from rich.box import ROUNDED +from rich.console import RenderableType, Group +from rich.padding import Padding +from rich.panel import Panel +from rich.text import Text + +from clients.terminal.tui.themes import Theme, get_active_theme + +from .base import ContentRenderer +from .diff import highlight_unified_diff + +# Actions whose output embeds a unified diff (edit_lines / smart_edit prepend an +# "Applied …" summary line; diff is the raw unified diff). +_DIFF_ACTIONS = {"diff", "edit_lines", "smart_edit"} + +# List entry prefixes emitted by filesystem._list. +_LIST_DIR_RE = re.compile(r"^d\s+(?P.+?)/\s*(?:\((?P\d+) items\))?\s*$") +_LIST_FILE_RE = re.compile(r"^\s{3}(?P.+?)\s{2,}(?P\S+)\s{2,}(?P