SXXXXXXX_CodeBridge/codebridge/gui/diff_viewer.py
2025-12-23 10:32:22 +01:00

112 lines
4.4 KiB
Python

import tkinter as tk
from tkinter import ttk, messagebox
import difflib
import os
import shutil
class DiffViewer(tk.Toplevel):
"""
Side-by-side diff viewer for comparing two files.
"""
def __init__(self, parent, source_path: str, dest_path: str, relative_path: str):
super().__init__(parent)
self.source_path = source_path
self.dest_path = dest_path
self.relative_path = relative_path
self.title(f"Diff - {relative_path}")
self.geometry("1200x800")
self._setup_ui()
self._load_and_compare()
def _setup_ui(self) -> None:
"""
Initializes the side-by-side layout and buttons.
"""
# Toolbar
toolbar = ttk.Frame(self, padding=5)
toolbar.pack(fill="x", side="top")
copy_all_btn = ttk.Button(toolbar, text="⤴ Copy Source to Destination", command=self._copy_source_to_dest)
copy_all_btn.pack(side="left", padx=5)
# Main diff container
main_frame = ttk.Frame(self)
main_frame.pack(fill="both", expand=True, padx=5, pady=5)
# Left pane (Source)
self.left_text = tk.Text(main_frame, wrap="none", undo=True)
self.left_text.pack(side="left", fill="both", expand=True)
# Right pane (Destination)
self.right_text = tk.Text(main_frame, wrap="none", undo=True)
self.right_text.pack(side="left", fill="both", expand=True)
# Scrollbar for both
self.v_scroll = ttk.Scrollbar(main_frame, orient="vertical", command=self._sync_scroll)
self.v_scroll.pack(side="right", fill="y")
self.left_text.config(yscrollcommand=self.v_scroll.set)
self.right_text.config(yscrollcommand=self.v_scroll.set)
# Colors for diff
self.left_text.tag_config("removed", background="#ffeef0")
self.right_text.tag_config("added", background="#e6ffed")
self.left_text.tag_config("changed", background="#fffbdd")
self.right_text.tag_config("changed", background="#fffbdd")
def _sync_scroll(self, *args) -> None:
"""
Synchronizes vertical scrolling between both text widgets.
"""
self.left_text.yview(*args)
self.right_text.yview(*args)
def _load_and_compare(self) -> None:
"""
Reads files and highlights differences.
"""
try:
with open(self.source_path, "r", encoding="utf-8") as f:
source_lines = f.readlines()
# Handle case where destination file might not exist (Added files)
dest_lines = []
if os.path.exists(self.dest_path):
with open(self.dest_path, "r", encoding="utf-8") as f:
dest_lines = f.readlines()
diff = difflib.ndiff(dest_lines, source_lines)
self._display_diff(diff)
except Exception as e:
messagebox.showerror("Error", f"Could not read files: {e}")
self.destroy()
def _display_diff(self, diff_generator) -> None:
"""
Parses the diff and populates the text widgets with highlighting.
"""
self.left_text.config(state="normal")
self.right_text.config(state="normal")
for line in diff_generator:
code = line[:2]
content = line[2:]
if code == " ":
self.left_text.insert("end", content)
self.right_text.insert("end", content)
elif code == "- ":
self.left_text.insert("end", content, "removed")
self.right_text.insert("end", "\n")
elif code == "+ ":
self.left_text.insert("end", "\n")
self.right_text.insert("end", content, "added")
elif code == "? ":
continue
self.left_text.config(state="disabled")
self.right_text.config(state="disabled")
def _copy_source_to_dest(self) -> None:
"""
Overwrites the destination file with the source file content.
"""
confirm = messagebox.askyesno("Confirm", "Overwrite destination file with source content?")
if confirm:
try:
os.makedirs(os.path.dirname(self.dest_path), exist_ok=True)
shutil.copy2(self.source_path, self.dest_path)
messagebox.showinfo("Success", "File copied successfully.")
self.destroy()
except Exception as e:
messagebox.showerror("Error", f"Failed to copy file: {e}")