#!/usr/bin/python3
import gi, difflib, os
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk

class App(Gtk.Window):
    def __init__(self):
        super().__init__(title='Diff Viewer')
        self.set_default_size(800, 560)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        vbox.set_margin_top(6); vbox.set_margin_bottom(6)
        vbox.set_margin_start(6); vbox.set_margin_end(6)
        self.add(vbox)
        # file choosers
        file_box = Gtk.Box(spacing=8)
        self.file1_btn = Gtk.Button(label='File A...')
        self.file1_btn.connect('clicked', self.pick_file, 'A')
        file_box.pack_start(self.file1_btn, False, False, 0)
        self.file1_lbl = Gtk.Label(label='(or type in left panel)', xalign=0)
        file_box.pack_start(self.file1_lbl, True, True, 0)
        self.file2_btn = Gtk.Button(label='File B...')
        self.file2_btn.connect('clicked', self.pick_file, 'B')
        file_box.pack_start(self.file2_btn, False, False, 0)
        self.file2_lbl = Gtk.Label(label='(or type in right panel)', xalign=0)
        file_box.pack_start(self.file2_lbl, True, True, 0)
        diff_btn = Gtk.Button(label='Diff ▶')
        diff_btn.connect('clicked', self.run_diff)
        file_box.pack_start(diff_btn, False, False, 0)
        vbox.pack_start(file_box, False, False, 0)
        # side-by-side panels
        hpaned = Gtk.Paned()
        from gi.repository import Pango
        sw1 = Gtk.ScrolledWindow(); self.tv1 = Gtk.TextView()
        self.tv1.override_font(Pango.FontDescription('Monospace 9'))
        self.tv1.set_wrap_mode(Gtk.WrapMode.NONE)
        sw1.add(self.tv1); hpaned.add1(sw1)
        sw2 = Gtk.ScrolledWindow(); self.tv2 = Gtk.TextView()
        self.tv2.override_font(Pango.FontDescription('Monospace 9'))
        self.tv2.set_wrap_mode(Gtk.WrapMode.NONE)
        sw2.add(self.tv2); hpaned.add2(sw2)
        vbox.pack_start(hpaned, True, True, 0)
        # diff output
        vbox.add(Gtk.Label(label='Unified Diff:', xalign=0))
        sw3 = Gtk.ScrolledWindow(); sw3.set_size_request(-1, 180)
        self.diff_tv = Gtk.TextView(); self.diff_tv.set_editable(False)
        self.diff_tv.override_font(Pango.FontDescription('Monospace 9'))
        sw3.add(self.diff_tv)
        vbox.pack_start(sw3, False, False, 0)
        # tags
        buf = self.diff_tv.get_buffer()
        self.add_tag = buf.create_tag('add', foreground='#00aa00')
        self.del_tag = buf.create_tag('del', foreground='#cc0000')
        self.hdr_tag = buf.create_tag('hdr', foreground='#0000cc', weight=700)
    def pick_file(self, _, which):
        dlg = Gtk.FileChooserDialog(title=f'Open File {which}', parent=self,
            action=Gtk.FileChooserAction.OPEN)
        dlg.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                        Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
        if dlg.run() == Gtk.ResponseType.OK:
            path = dlg.get_filename()
            tv = self.tv1 if which == 'A' else self.tv2
            lbl = self.file1_lbl if which == 'A' else self.file2_lbl
            with open(path, 'r', errors='replace') as f: content = f.read()
            tv.get_buffer().set_text(content)
            lbl.set_text(os.path.basename(path))
        dlg.destroy()
    def run_diff(self, *_):
        def get_text(tv):
            buf = tv.get_buffer()
            return buf.get_text(buf.get_start_iter(), buf.get_end_iter(), False)
        a = get_text(self.tv1).splitlines(keepends=True)
        b = get_text(self.tv2).splitlines(keepends=True)
        diff = list(difflib.unified_diff(a, b, fromfile='File A', tofile='File B'))
        buf = self.diff_tv.get_buffer()
        buf.set_text('')
        it = buf.get_end_iter()
        for line in diff:
            tag = None
            if line.startswith('+++') or line.startswith('---'): tag = self.hdr_tag
            elif line.startswith('@@'): tag = self.hdr_tag
            elif line.startswith('+'): tag = self.add_tag
            elif line.startswith('-'): tag = self.del_tag
            if tag: buf.insert_with_tags(it, line, tag)
            else: buf.insert(it, line)
        if not diff:
            buf.set_text('Files are identical.')

win = App()
win.connect('destroy', Gtk.main_quit)
win.show_all()
Gtk.main()
