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

class App(Gtk.Window):
    def __init__(self):
        super().__init__(title='Regex Tester')
        self.set_default_size(600, 500)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        vbox.set_margin_top(8); vbox.set_margin_bottom(8)
        vbox.set_margin_start(8); vbox.set_margin_end(8)
        self.add(vbox)
        grid = Gtk.Grid(column_spacing=6, row_spacing=6)
        grid.attach(Gtk.Label(label='Pattern:', xalign=0), 0, 0, 1, 1)
        self.pattern = Gtk.Entry()
        self.pattern.set_placeholder_text(r'e.g.  \d+  or  [A-Z]\w+')
        self.pattern.connect('changed', self.update)
        grid.attach(self.pattern, 1, 0, 2, 1)
        self.flags_case = Gtk.CheckButton(label='Case insensitive')
        self.flags_case.connect('toggled', self.update)
        grid.attach(self.flags_case, 1, 1, 1, 1)
        self.flags_multi = Gtk.CheckButton(label='Multiline')
        self.flags_multi.connect('toggled', self.update)
        grid.attach(self.flags_multi, 2, 1, 1, 1)
        vbox.pack_start(grid, False, False, 0)
        vbox.add(Gtk.Label(label='Test string:', xalign=0))
        sw = Gtk.ScrolledWindow(); sw.set_size_request(-1, 180)
        self.test_tv = Gtk.TextView()
        self.test_tv.set_wrap_mode(Gtk.WrapMode.WORD)
        self.test_tv.get_buffer().connect('changed', self.update)
        self.test_tv.get_buffer().set_text('Hello World 2024\nFoo Bar 42\nTest 123 test')
        sw.add(self.test_tv)
        vbox.pack_start(sw, False, False, 0)
        self.status = Gtk.Label(label='', xalign=0)
        vbox.pack_start(self.status, False, False, 0)
        vbox.add(Gtk.Label(label='Matches:', xalign=0))
        sw2 = Gtk.ScrolledWindow()
        self.matches_tv = Gtk.TextView(); self.matches_tv.set_editable(False)
        self.matches_tv.override_font(Pango.FontDescription('Monospace 10'))
        sw2.add(self.matches_tv)
        vbox.pack_start(sw2, True, True, 0)
        # highlight tags
        self.hl_tag = self.test_tv.get_buffer().create_tag('match',
            background='yellow', foreground='black')
    def update(self, *_):
        buf = self.test_tv.get_buffer()
        text = buf.get_text(buf.get_start_iter(), buf.get_end_iter(), False)
        # clear highlights
        buf.remove_tag(self.hl_tag, buf.get_start_iter(), buf.get_end_iter())
        pat = self.pattern.get_text()
        if not pat:
            self.status.set_text('Enter a pattern')
            self.matches_tv.get_buffer().set_text('')
            return
        try:
            flags = 0
            if self.flags_case.get_active(): flags |= re.IGNORECASE
            if self.flags_multi.get_active(): flags |= re.MULTILINE
            matches = list(re.finditer(pat, text, flags))
            # highlight
            for m in matches:
                start = buf.get_iter_at_offset(m.start())
                end = buf.get_iter_at_offset(m.end())
                buf.apply_tag(self.hl_tag, start, end)
            # show results
            result_lines = [f'{len(matches)} match(es) found\n']
            for i, m in enumerate(matches[:50]):
                result_lines.append(f'Match {i+1}: pos {m.start()}-{m.end()}  "{m.group()}"')
                if m.groups():
                    for j, g in enumerate(m.groups()):
                        result_lines.append(f'  Group {j+1}: "{g}"')
            self.matches_tv.get_buffer().set_text('\n'.join(result_lines))
            self.status.set_markup(f'<span foreground="green"><b>{len(matches)} match(es)</b></span>')
        except re.error as e:
            self.status.set_markup(f'<span foreground="red">Regex error: {e}</span>')
            self.matches_tv.get_buffer().set_text('')

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