Skip to content

Instantly share code, notes, and snippets.

@acidnine
Created August 6, 2026 22:58
Show Gist options
  • Select an option

  • Save acidnine/d8056c708bd373525977390b384ccadd to your computer and use it in GitHub Desktop.

Select an option

Save acidnine/d8056c708bd373525977390b384ccadd to your computer and use it in GitHub Desktop.
XED Plugin Selection Stats
[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
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