feat: Implement mouse-based text selection and clipboard copying

This commit is contained in:
n loewen (aider) 2025-06-07 21:27:46 +01:00
parent b58840de58
commit 66940d44f9
1 changed files with 80 additions and 4 deletions

84
gtm2.py
View File

@ -118,6 +118,10 @@ class AppState:
self.dragging_divider = False
self.should_exit = False
self.is_selecting = False
self.selection_start_coord = None
self.selection_end_coord = None
def update_dimensions(self, height, width):
self.height = height
self.width = width
@ -268,6 +272,28 @@ def draw_divider(stdscr, state):
except curses.error:
pass
def draw_selection(stdscr, state):
if not state.is_selecting or not state.selection_start_coord:
return
start_x, start_y = state.selection_start_coord
end_x, end_y = state.selection_end_coord
x1, x2 = min(start_x, end_x), max(start_x, end_x)
y1, y2 = min(start_y, end_y), max(start_y, end_y)
for y in range(y1, y2 + 1):
try:
# chgat can fail at the bottom-right corner of the screen
if x1 < state.width:
# `width-1` is the last valid column
end_col = min(x2, state.width - 1)
length = end_col - x1 + 1
if length > 0:
stdscr.chgat(y, x1, length, curses.A_REVERSE)
except curses.error:
pass
def draw_status_bars(stdscr, state):
visible_height = state.height - 1
@ -308,26 +334,76 @@ def draw_ui(stdscr, state):
draw_right_pane(stdscr, state)
draw_divider(stdscr, state)
draw_status_bars(stdscr, state)
draw_selection(stdscr, state)
stdscr.refresh()
# --- Input Handling Functions (Controller) ---
def handle_mouse_input(state):
def copy_selection_to_clipboard(stdscr, state):
if not state.selection_start_coord or not state.selection_end_coord:
return
start_x, start_y = state.selection_start_coord
end_x, end_y = state.selection_end_coord
x1, x2 = min(start_x, end_x), max(start_x, end_x)
y1, y2 = min(start_y, end_y), max(start_y, end_y)
height, width = stdscr.getmaxyx()
selected_text_parts = []
for y in range(y1, y2 + 1):
if 0 <= y < height:
line_str = ""
for x in range(x1, x2 + 1):
if 0 <= x < width:
try:
char_and_attr = stdscr.inch(y, x)
char = char_and_attr & 0xFF
line_str += chr(char)
except curses.error:
line_str += " "
selected_text_parts.append(line_str.rstrip())
if selected_text_parts:
text_to_copy = "\n".join(selected_text_parts)
if text_to_copy.strip():
try:
subprocess.run(['pbcopy'], input=text_to_copy, text=True, check=True)
except (FileNotFoundError, subprocess.CalledProcessError):
pass
def handle_mouse_input(stdscr, state):
try:
_, mx, my, _, bstate = curses.getmouse()
if bstate & curses.BUTTON1_PRESSED:
if abs(mx - state.divider_col) <= 1:
state.dragging_divider = True
else:
state.is_selecting = True
state.selection_start_coord = (mx, my)
state.selection_end_coord = (mx, my)
if state.dragging_divider:
state.update_divider(mx)
elif state.is_selecting:
state.selection_end_coord = (mx, my)
if bstate & curses.BUTTON1_RELEASED:
if state.dragging_divider:
state.dragging_divider = False
else:
state.focus = "left" if mx < state.divider_col else "right"
elif state.is_selecting:
state.is_selecting = False
start_x, start_y = state.selection_start_coord
if start_x == mx and start_y == my:
state.focus = "left" if mx < state.divider_col else "right"
else:
copy_selection_to_clipboard(stdscr, state)
state.selection_start_coord = None
state.selection_end_coord = None
except curses.error:
pass
@ -397,7 +473,7 @@ def main(stdscr, filename, show_diff, show_add, show_del, mouse):
continue
if state.enable_mouse and key == curses.KEY_MOUSE:
handle_mouse_input(state)
handle_mouse_input(stdscr, state)
else:
handle_keyboard_input(key, state)