feat: Implement mouse-based text selection and clipboard copying
This commit is contained in:
parent
b58840de58
commit
66940d44f9
84
gtm2.py
84
gtm2.py
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue