feat(update): huge changes

This commit is contained in:
sankalp committed 2025-04-25 06:33:51 +05:45
1 parent 53c077106f
commit 6b9b419aa9
98 files changed
+9636 -5575

No files matched your search

+540
View File
@@ -0,0 +1,540 @@
import subprocess
import os
from fabric.widgets.box import Box
from fabric.widgets.label import Label
from fabric.widgets.button import Button
from fabric.widgets.entry import Entry
from fabric.widgets.scrolledwindow import ScrolledWindow
from fabric.utils import idle_add, remove_handler, exec_shell_command_async
from gi.repository import GLib, Gdk, Gtk
import utils.icons as icons
import config.data as data
class TmuxManager(Box):
def __init__(self, **kwargs):
super().__init__(
name="tmux-manager",
visible=False,
all_visible=False,
**kwargs,
)
self.launcher = kwargs["launcher"]
self.selected_index = -1 # Track the selected item index
self._arranger_handler: int = 0
self.viewport = Box(name="viewport", spacing=4, orientation="v")
self.session_name_entry = Entry(
name="session-name-entry",
placeholder="Create Tmux Session...",
h_expand=True,
on_activate=lambda entry, *_: self.create_session(entry.get_text()),
on_key_press_event=self.on_entry_key_press,
)
self.session_name_entry.props.xalign = 0.5
self.scrolled_window = ScrolledWindow(
name="scrolled-window",
spacing=10,
min_content_size=(450, 105),
max_content_size=(450, 105),
child=self.viewport,
)
self.header_box = Box(
name="header_box",
spacing=10,
orientation="h",
children=[
Button(
name="new-session-button",
child=Label(name="new-session-label", markup=icons.add),
tooltip_text="Create New Session",
on_clicked=lambda *_: self.create_session(self.session_name_entry.get_text()),
),
self.session_name_entry,
],
)
self.tmux_box = Box(
name="tmux-box",
spacing=10,
h_expand=True,
orientation="v",
children=[
self.header_box,
self.scrolled_window,
],
)
self.add(self.tmux_box)
self.show_all()
def close_manager(self):
"""Close the tmux manager"""
self.viewport.children = []
self.selected_index = -1 # Reset selection
self.launcher.close()
def open_manager(self):
"""Open the tmux manager and refresh sessions"""
self.viewport.set_visible(True)
self.refresh_sessions()
self.session_name_entry.set_text("")
self.session_name_entry.grab_focus()
def refresh_sessions(self):
"""Get tmux sessions and populate the viewport"""
remove_handler(self._arranger_handler) if self._arranger_handler else None
self.viewport.children = []
self.selected_index = -1 # Clear selection when viewport changes
# Get tmux sessions
sessions = self.get_tmux_sessions()
if not sessions:
# Create a container box to better center the message
container = Box(
name="no-tmux-container",
orientation="v",
h_align="center",
v_align="center",
h_expand=True,
v_expand=True
)
# Show a message if no sessions
label = Label(
name="no-tmux",
markup=icons.terminal,
h_align="center",
v_align="center",
)
container.add(label)
self.viewport.add(container)
return
# Add session slots to viewport
for session in sessions:
self.viewport.add(self.create_session_slot(session))
def get_tmux_sessions(self):
"""Get list of tmux sessions"""
try:
result = subprocess.run(
["tmux", "list-sessions", "-F", "#{session_name}"],
capture_output=True,
text=True
)
if result.returncode == 0:
return [s.strip() for s in result.stdout.strip().split('\n') if s.strip()]
return []
except Exception as e:
print(f"Error getting tmux sessions: {e}")
return []
def create_session_slot(self, session_name):
"""Create a button for a tmux session"""
# Create an entry for inline editing (initially hidden)
name_entry = Entry(
name="session-name-entry",
text=session_name,
visible=False,
on_activate=lambda entry, *_: self.finish_rename(button, session_name, entry),
on_key_press_event=self.on_rename_key_press,
)
# Create the label showing the session name
name_label = Label(
name="app-label",
label=session_name,
ellipsization="end",
v_align="center",
h_align="center",
)
# Session slot content box
slot_box = Box(
name="slot-box",
orientation="h",
spacing=10,
children=[
Label(
name="tmux-icon",
markup=icons.terminal, # Use existing terminal icon
),
name_label,
name_entry,
],
)
button = Button(
name="slot-button", # reuse existing CSS styling
child=slot_box,
tooltip_text=f"Attach to session: {session_name}",
on_clicked=lambda *_: self.attach_to_session(session_name),
can_focus=True, # Ensure the button can receive focus
)
# Add double-click handler to start renaming
button.connect("button-press-event", self.on_session_click, session_name, name_label, name_entry)
# Add key press handler for 'r' to rename
button.connect("key-press-event", self.on_slot_key_press, session_name, name_label, name_entry)
# Store reference to entry and label in button for later access
button.name_entry = name_entry
button.name_label = name_label
button.session_name = session_name
return button
def on_session_click(self, button, event, session_name, label, entry):
"""Handle clicks on session buttons"""
# Handle double-click to rename
if event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS and event.button == 1:
self.start_rename(button, session_name, label, entry)
return True
# Handle right click for context menu
elif event.button == 3:
menu = Gtk.Menu()
# Rename option
rename_item = Gtk.MenuItem(label="Rename")
rename_item.connect("activate", lambda _: self.start_rename(button, session_name, label, entry))
menu.append(rename_item)
# Kill option
kill_item = Gtk.MenuItem(label="Kill Session")
kill_item.connect("activate", lambda _: self.kill_session(session_name))
menu.append(kill_item)
menu.show_all()
menu.popup_at_pointer(event)
return True
return False
def start_rename(self, button, session_name, label, entry):
"""Start inline renaming of a session"""
# Hide label, show entry
label.set_visible(False)
entry.set_visible(True)
# Focus entry and select all text
entry.grab_focus()
entry.select_region(0, -1)
# Mark button as being edited
button.get_style_context().add_class("editing")
def finish_rename(self, button, old_name, entry):
"""Finish renaming a session"""
new_name = entry.get_text().strip()
# Only rename if the name changed and isn't empty
if new_name and new_name != old_name:
self.rename_session(old_name, new_name)
# Reset UI state
self.cancel_rename(button)
def cancel_rename(self, button):
"""Cancel renaming operation"""
# Restore original view
button.name_entry.set_visible(False)
button.name_label.set_visible(True)
# Remove editing style
button.get_style_context().remove_class("editing")
# Return focus to session name entry
self.session_name_entry.grab_focus()
def on_rename_key_press(self, entry, event):
"""Handle key presses in the rename entry"""
if event.keyval == Gdk.KEY_Escape:
# Find the parent button
parent = entry.get_parent()
while parent and not isinstance(parent, Button):
parent = parent.get_parent()
if parent:
self.cancel_rename(parent)
return True
return False
def on_session_right_click(self, button, event, session_name):
"""Handle right-click on a session button to show context menu"""
if event.button == 3: # Right click
menu = Gtk.Menu()
# Rename option
rename_item = Gtk.MenuItem(label="Rename")
rename_item.connect("activate", lambda _: self.start_rename(
button,
session_name,
button.name_label,
button.name_entry
))
menu.append(rename_item)
# Kill option
kill_item = Gtk.MenuItem(label="Kill Session")
kill_item.connect("activate", lambda _: self.kill_session(session_name))
menu.append(kill_item)
menu.show_all()
menu.popup_at_pointer(event)
return True
return False
def on_entry_key_press(self, widget, event):
"""Handle key press events in the entry"""
if event.keyval == Gdk.KEY_Escape:
self.close_manager()
return True
# Custom navigation with UP/DOWN keys removed
return False
def scroll_to_selected(self, button):
"""Scroll to ensure the selected button is visible"""
def scroll():
adj = self.scrolled_window.get_vadjustment()
alloc = button.get_allocation()
if alloc.height == 0:
return False # Retry if allocation isn't ready
y = alloc.y
height = alloc.height
page_size = adj.get_page_size()
current_value = adj.get_value()
# Calculate visible boundaries
visible_top = current_value
visible_bottom = current_value + page_size
if y < visible_top:
# Item above viewport - align to top
adj.set_value(y)
elif y + height > visible_bottom:
# Item below viewport - align to bottom
new_value = y + height - page_size
adj.set_value(new_value)
# No action if already fully visible
return False
GLib.idle_add(scroll)
def create_session(self, session_name):
"""Create a new tmux session"""
if not session_name:
# Get existing session names
existing_sessions = self.get_tmux_sessions()
# Find the next available number
counter = 0
while str(counter) in existing_sessions:
counter += 1
session_name = str(counter)
try:
# Clean the session name (replace spaces with underscores)
clean_name = session_name.strip().replace(" ", "_")
# Create session
subprocess.run(
["tmux", "new-session", "-d", "-s", clean_name],
check=True
)
# Refresh the session list
self.refresh_sessions()
# Clear entry
self.session_name_entry.set_text("")
# Launch a terminal and attach to this session
terminal_cmd = self.get_terminal_command(f"tmux attach-session -t {clean_name}")
exec_shell_command_async(terminal_cmd)
# Close manager
self.close_manager()
except Exception as e:
print(f"Error creating tmux session: {e}")
def attach_to_session(self, session_name):
"""Attach to an existing tmux session"""
try:
# Launch a terminal and attach to this session
terminal_cmd = self.get_terminal_command(f"tmux attach-session -t {session_name}")
exec_shell_command_async(terminal_cmd)
self.close_manager()
except Exception as e:
print(f"Error attaching to tmux session: {e}")
def get_terminal_command(self, cmd):
"""Get terminal command based on configured terminal or available terminals"""
# First try to use the configured terminal command
if hasattr(data, 'TERMINAL_COMMAND') and data.TERMINAL_COMMAND:
parts = data.TERMINAL_COMMAND.split()
terminal = parts[0]
try:
# Check if the configured terminal is available
subprocess.run(["which", terminal], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return f"{data.TERMINAL_COMMAND} {cmd}"
except subprocess.CalledProcessError:
# If configured terminal is not available, fall back to defaults
pass
# Fallback to checking available terminals
terminals = [
("kitty", f"kitty -e {cmd}"),
("alacritty", f"alacritty -e {cmd}"),
("foot", f"foot {cmd}"),
("gnome-terminal", f"gnome-terminal -- {cmd}"),
("konsole", f"konsole -e {cmd}"),
("xfce4-terminal", f"xfce4-terminal -e '{cmd}'"),
]
for term, term_cmd in terminals:
try:
# Check if terminal is available
subprocess.run(["which", term], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return term_cmd
except subprocess.CalledProcessError:
continue
# Default fallback
return f"kitty -e {cmd}"
def rename_session_dialog(self, old_name):
"""Show dialog to rename a session"""
dialog = Gtk.Dialog(
title="Rename Session",
transient_for=None,
flags=0
)
dialog.add_buttons(
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK
)
content_area = dialog.get_content_area()
entry = Gtk.Entry()
entry.set_text(old_name)
entry.set_activates_default(True)
content_area.add(entry)
dialog.set_default_response(Gtk.ResponseType.OK)
dialog.show_all()
response = dialog.run()
if response == Gtk.ResponseType.OK:
new_name = entry.get_text()
if new_name and new_name != old_name:
self.rename_session(old_name, new_name)
dialog.destroy()
def rename_session(self, old_name, new_name):
"""Rename a tmux session"""
try:
# Clean the session name (replace spaces with underscores)
clean_name = new_name.strip().replace(" ", "_")
# Rename session
subprocess.run(
["tmux", "rename-session", "-t", old_name, clean_name],
check=True
)
# Refresh the session list
self.refresh_sessions()
except Exception as e:
print(f"Error renaming tmux session: {e}")
def kill_session(self, session_name):
"""Kill a tmux session"""
try:
# Kill session
subprocess.run(
["tmux", "kill-session", "-t", session_name],
check=True
)
# Refresh the session list
self.refresh_sessions()
# Close the launcher after killing session
self.close_manager()
except Exception as e:
print(f"Error killing tmux session: {e}")
# Add new method to handle key presses on session slots
def on_slot_key_press(self, button, event, session_name, label, entry):
"""Handle key presses on session buttons"""
# Print debugging info
print(f"Key pressed: {event.keyval}, State: {event.state}")
# Check if 'r' key was pressed for renaming
if event.keyval == Gdk.KEY_r:
self.start_rename(button, session_name, label, entry)
return True
# Check for 'K' (capital K) which indicates Shift is pressed
elif event.keyval == Gdk.KEY_K:
print("Shift+K detected - killing session without confirmation")
self.kill_session(session_name)
return True
# Check for lowercase 'k'
elif event.keyval == Gdk.KEY_k:
print("Regular k detected - showing confirmation")
self.show_kill_confirmation_menu(button, session_name)
return True
# Check if Delete key was pressed for killing session
elif event.keyval == Gdk.KEY_Delete:
self.show_kill_confirmation_menu(button, session_name)
return True
return False
def show_kill_confirmation_menu(self, button, session_name):
"""Show a confirmation menu for killing a session"""
menu = Gtk.Menu()
# Confirmation message as a disabled menu item
msg_item = Gtk.MenuItem(label=f"Kill session '{session_name}'?")
msg_item.set_sensitive(False)
menu.append(msg_item)
# Separator
menu.append(Gtk.SeparatorMenuItem())
# Confirm option
confirm_item = Gtk.MenuItem(label="Confirm")
confirm_item.connect("activate", lambda _: self.kill_session(session_name))
menu.append(confirm_item)
# Cancel option
cancel_item = Gtk.MenuItem(label="Cancel")
# Close launcher on cancel
cancel_item.connect("activate", lambda _: self.close_manager())
menu.append(cancel_item)
menu.show_all()
# Show the menu positioned at the button
menu.popup_at_widget(
button,
Gdk.Gravity.SOUTH_WEST,
Gdk.Gravity.NORTH_WEST,
None
)