import gi from gi.repository import Adw from gi.repository import Gtk, GObject, Gio import re class MarkdownTable: def __init__(self): self.headers = [] self.rows = Gio.ListStore() self.alignments = [] def __repr__(self): table_repr = 'Headers: {}\n'.format(self.headers) table_repr += 'Alignments: {}\n'.format(self.alignments) table_repr += 'Rows:\n' for row in self.rows: table_repr += ' | '.join(row) + '\n' return table_repr class Row(GObject.GObject): def __init__(self, _values): super().__init__() self.values = _values def get_column_value(self, index): return self.values[index] class TableWidget(Gtk.Frame): __gtype_name__ = 'TableWidget' def __init__(self, markdown): super().__init__() self.table = MarkdownTable() self.set_halign(Gtk.Align.START) self.table_widget = Gtk.ColumnView( show_column_separators=True, show_row_separators=True, reorderable=False, ) scrolled_window = Gtk.ScrolledWindow( vscrollbar_policy=Gtk.PolicyType.NEVER, propagate_natural_width=True ) self.set_child(scrolled_window) try: self.parse_markdown_table(markdown) self.make_table() scrolled_window.set_child(self.table_widget) except: label = Gtk.Label( label=markdown.lstrip('\n').rstrip('\n'), selectable=True, margin_top=6, margin_bottom=6, margin_start=6, margin_end=6 ) scrolled_window.set_child(label) def parse_markdown_table(self, markdown_text): # Define regex patterns for matching the table components header_pattern = r'^\|(.+?)\|$' separator_pattern = r'^\|(\s*[:-]+:?\s*\|)+$' row_pattern = r'^\|(.+?)\|$' # Split the text into lines lines = markdown_text.strip().split('\n') # Extract headers header_match = re.match(header_pattern, lines[0], re.MULTILINE) if header_match: headers = [header.strip() for header in header_match.group(1).replace("*", "").split('|') if header.strip()] self.table.headers = headers # Extract alignments separator_match = re.match(separator_pattern, lines[1], re.MULTILINE) if separator_match: alignments = [] separator_columns = lines[1].replace(" ", "").split('|')[1:-1] for sep in separator_columns: if ':' in sep: if sep.startswith('-') and sep.endswith(':'): alignments.append(1) elif sep.startswith(':') and sep.endswith('-'): alignments.append(0) else: alignments.append(0.5) else: alignments.append(0) # Default alignment is start self.table.alignments = alignments # Extract rows for line in lines[2:]: row_match = re.match(row_pattern, line, re.MULTILINE) if row_match: rows = line.split('|')[1:-1] row = Row(rows) self.table.rows.append(row) def make_table(self): def _on_factory_setup(_factory, list_item, align): label = Gtk.Label(xalign=align, ellipsize=3, selectable=True) list_item.set_child(label) def _on_factory_bind(_factory, list_item, index): label_widget = list_item.get_child() row = list_item.get_item() label_widget.set_label(row.get_column_value(index)) for index, column_name in enumerate(self.table.headers): column = Gtk.ColumnViewColumn(title=column_name, expand=True) factory = Gtk.SignalListItemFactory() factory.connect("setup", _on_factory_setup, self.table.alignments[index]) factory.connect("bind", _on_factory_bind, index) column.set_factory(factory) self.table_widget.append_column(column) selection = Gtk.NoSelection.new(model=self.table.rows) self.table_widget.set_model(model=selection)