#!/usr/bin/env python3 """ Utilities for generating unified diffs from Edit tool operations. """ import difflib from typing import Dict, Any def generate_unified_diff(old_string: str, new_string: str, file_path: str = "") -> str: """ Generate a unified diff from old and new strings. Args: old_string: Original content new_string: Modified content file_path: Optional file path for context Returns: Unified diff string with line numbers and +/- markers """ old_lines = old_string.splitlines(keepends=True) new_lines = new_string.splitlines(keepends=True) # Generate unified diff diff = difflib.unified_diff( old_lines, new_lines, fromfile=f"a/{file_path}" if file_path else "a/file", tofile=f"b/{file_path}" if file_path else "b/file", lineterm="", ) return "".join(diff) def format_edit_as_diff(tool_input: Dict[str, Any]) -> str: """ Format an Edit tool's input as a syntax-highlighted diff with CSS classes. Args: tool_input: The input dict from an Edit tool_use Returns: HTML-formatted diff block with red/green highlighting """ file_path = tool_input.get("file_path", "") old_string = tool_input.get("old_string", "") new_string = tool_input.get("new_string", "") replace_all = tool_input.get("replace_all", False) # Get relative path for display display_path = file_path if file_path.startswith("/"): # Try to shorten to just the filename and parent dir parts = file_path.split("/") if len(parts) > 2: display_path = "/".join(parts[-2:]) # Generate diff diff_output = generate_unified_diff(old_string, new_string, display_path) # Build output with custom HTML wrapper for styling output = f"**📝 Edit File:** `{display_path}`" if replace_all: output += " _(replace all occurrences)_" output += "\n\n" # Output clean diff syntax wrapped in div for CSS targeting output += '
\n\n```diff\n' output += diff_output output += '\n```\n\n
' return output def format_edit_as_inline(tool_input: Dict[str, Any]) -> str: """ Format an Edit tool's input as inline before/after blocks (fallback). Args: tool_input: The input dict from an Edit tool_use Returns: Markdown-formatted before/after comparison """ file_path = tool_input.get("file_path", "") old_string = tool_input.get("old_string", "") new_string = tool_input.get("new_string", "") display_path = file_path if file_path.startswith("/"): parts = file_path.split("/") if len(parts) > 2: display_path = "/".join(parts[-2:]) output = f"**📝 Edit File:** `{display_path}`\n\n" output += "
\nView Changes\n\n" output += "**Before:**\n```\n" + old_string + "\n```\n\n" output += "**After:**\n```\n" + new_string + "\n```\n\n" output += "
" return output def detect_language_from_path(file_path: str) -> str: """ Detect programming language from file extension. Args: file_path: Path to file Returns: Language identifier for syntax highlighting """ ext_map = { ".py": "python", ".js": "javascript", ".ts": "typescript", ".tsx": "tsx", ".jsx": "jsx", ".java": "java", ".cpp": "cpp", ".c": "c", ".h": "c", ".hpp": "cpp", ".rs": "rust", ".go": "go", ".rb": "ruby", ".php": "php", ".swift": "swift", ".kt": "kotlin", ".scala": "scala", ".sh": "bash", ".bash": "bash", ".zsh": "zsh", ".fish": "fish", ".ps1": "powershell", ".yaml": "yaml", ".yml": "yaml", ".json": "json", ".xml": "xml", ".html": "html", ".css": "css", ".scss": "scss", ".sass": "sass", ".md": "markdown", ".mdx": "mdx", ".sql": "sql", ".r": "r", ".m": "matlab", ".vim": "vim", } # Get extension for ext, lang in ext_map.items(): if file_path.endswith(ext): return lang return ""