Created
August 6, 2026 22:58
-
-
Save acidnine/d8056c708bd373525977390b384ccadd to your computer and use it in GitHub Desktop.
XED Plugin Selection Stats
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [Plugin] | |
| Loader=python3 | |
| Module=selection_stats | |
| IAge=3 | |
| Name=Selection Stats | |
| Description=Shows rows and character count in the status bar when text is selected. | |
| Authors=Acidnine; Gemini / ChatGPT / Claude | |
| Copyright=Copyright © 2026 Acidnine | |
| Website=https://vault21.net/ | |
| Version=1.0.0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from gi.repository import GObject, Xed, Gtk | |
| class SelectionStatsPlugin(GObject.Object, Xed.WindowActivatable): | |
| window = GObject.property(type=Xed.Window) | |
| def __init__(self): | |
| GObject.Object.__init__(self) | |
| self.label = None | |
| self.handler_ids = [] | |
| self.column_signal_id = None | |
| def do_activate(self): | |
| # Retrieve the statusbar from the current XED window | |
| statusbar = self.window.get_statusbar() | |
| # Create a dedicated label for our selection statistics | |
| self.label = Gtk.Label() | |
| self.label.set_no_show_all(True) | |
| self.label.hide() | |
| # Pack the label to the right/end of the status bar | |
| statusbar.pack_end(self.label, False, False, 8) | |
| # Connect bindings | |
| self._connect_buffer() | |
| def do_deactivate(self): | |
| self._disconnect_buffer() | |
| if self.label: | |
| self.label.destroy() | |
| self.label = None | |
| def do_update_state(self): | |
| # Refresh bindings when switching tabs or active views | |
| self._disconnect_buffer() | |
| self._connect_buffer() | |
| self.update_stats() | |
| def _connect_buffer(self): | |
| view = self.window.get_active_view() | |
| if view: | |
| buffer = view.get_buffer() | |
| # Listen to text selection / cursor movement marks | |
| hid = buffer.connect('mark-set', self.on_mark_set) | |
| self.handler_ids.append((buffer, hid)) | |
| # Independently check if column_edit plugin is active on this view | |
| helper = getattr(view, 'column_edit_helper', None) or getattr(getattr(view, 'get_plugin_data', lambda: None)(), 'helper', None) | |
| # Safer fallback: dynamically locate helper bound to view via attributes if attached | |
| if not helper and hasattr(view, '_column_edit_helper'): | |
| helper = view._column_edit_helper | |
| # Hook into column edit signal dynamically if present | |
| for plugin_inst in getattr(view, '_plugin_instances', []): | |
| if hasattr(plugin_inst, 'helper'): | |
| helper = plugin_inst.helper | |
| break | |
| # Universal lookup via standard dynamic attribute injection if column_edit exposes it | |
| if hasattr(view, 'plugin_column_helper'): | |
| helper = view.plugin_column_helper | |
| # Let's bind directly to column_edit helper if found safely | |
| if helper and hasattr(helper, 'connect'): | |
| self.column_signal_id = helper.connect('column-selection-changed', self.on_column_selection_changed) | |
| def _disconnect_buffer(self): | |
| view = self.window.get_active_view() | |
| if view: | |
| # Clean up dynamic column signal if connected | |
| for plugin_inst in getattr(view, '_plugin_instances', []): | |
| if hasattr(plugin_inst, 'helper') and hasattr(plugin_inst.helper, 'disconnect'): | |
| try: | |
| plugin_inst.helper.disconnect(self.column_signal_id) | |
| except Exception: | |
| pass | |
| if hasattr(view, 'plugin_column_helper') and hasattr(view.plugin_column_helper, 'disconnect'): | |
| try: | |
| view.plugin_column_helper.disconnect(self.column_signal_id) | |
| except Exception: | |
| pass | |
| for obj, hid in self.handler_ids: | |
| try: | |
| obj.disconnect(hid) | |
| except Exception: | |
| pass | |
| self.handler_ids = [] | |
| self.column_signal_id = None | |
| def on_mark_set(self, buffer, location, mark): | |
| if mark.get_name() in ('selection-bound', 'insert'): | |
| # Only update native stats if column edit isn't currently active/overriding | |
| view = self.window.get_active_view() | |
| if view and getattr(view, '_column_mode_active', False): | |
| return | |
| self.update_stats() | |
| def on_column_selection_changed(self, helper, rows, chars): | |
| if not self.label: | |
| return | |
| view = self.window.get_active_view() | |
| if view: | |
| view._column_mode_active = (rows > 0 and chars > 0) | |
| if rows <= 0 or chars <= 0: | |
| # Fall back to standard stats check if column selection cleared | |
| self.update_stats() | |
| return | |
| self.label.set_text(f"Sel: {rows} rows, {chars} chars (Column)") | |
| self.label.show() | |
| def update_stats(self): | |
| if not self.label: | |
| return | |
| view = self.window.get_active_view() | |
| if not view: | |
| self.label.hide() | |
| return | |
| buffer = view.get_buffer() | |
| if not buffer.get_has_selection(): | |
| self.label.hide() | |
| return | |
| bounds = buffer.get_selection_bounds() | |
| if not bounds or len(bounds) != 2: | |
| self.label.hide() | |
| return | |
| start, end = bounds | |
| text = buffer.get_text(start, end, include_hidden_chars=False) | |
| if not text: | |
| self.label.hide() | |
| return | |
| start_line = start.get_line() | |
| end_line = end.get_line() | |
| rows = (end_line - start_line) + 1 | |
| chars = len(text) | |
| self.label.set_text(f"Sel: {rows} rows, {chars} chars") | |
| self.label.show() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment