feat: Implement mouse-based text selection and clipboard copying
This commit is contained in:
parent
b58840de58
commit
66940d44f9
82
gtm2.py
82
gtm2.py
|
|
@ -118,6 +118,10 @@ class AppState:
|
||||||
self.dragging_divider = False
|
self.dragging_divider = False
|
||||||
self.should_exit = 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):
|
def update_dimensions(self, height, width):
|
||||||
self.height = height
|
self.height = height
|
||||||
self.width = width
|
self.width = width
|
||||||
|
|
@ -268,6 +272,28 @@ def draw_divider(stdscr, state):
|
||||||
except curses.error:
|
except curses.error:
|
||||||
pass
|
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):
|
def draw_status_bars(stdscr, state):
|
||||||
visible_height = state.height - 1
|
visible_height = state.height - 1
|
||||||
|
|
||||||
|
|
@ -308,26 +334,76 @@ def draw_ui(stdscr, state):
|
||||||
draw_right_pane(stdscr, state)
|
draw_right_pane(stdscr, state)
|
||||||
draw_divider(stdscr, state)
|
draw_divider(stdscr, state)
|
||||||
draw_status_bars(stdscr, state)
|
draw_status_bars(stdscr, state)
|
||||||
|
draw_selection(stdscr, state)
|
||||||
stdscr.refresh()
|
stdscr.refresh()
|
||||||
|
|
||||||
# --- Input Handling Functions (Controller) ---
|
# --- 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:
|
try:
|
||||||
_, mx, my, _, bstate = curses.getmouse()
|
_, mx, my, _, bstate = curses.getmouse()
|
||||||
|
|
||||||
if bstate & curses.BUTTON1_PRESSED:
|
if bstate & curses.BUTTON1_PRESSED:
|
||||||
if abs(mx - state.divider_col) <= 1:
|
if abs(mx - state.divider_col) <= 1:
|
||||||
state.dragging_divider = True
|
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:
|
if state.dragging_divider:
|
||||||
state.update_divider(mx)
|
state.update_divider(mx)
|
||||||
|
elif state.is_selecting:
|
||||||
|
state.selection_end_coord = (mx, my)
|
||||||
|
|
||||||
if bstate & curses.BUTTON1_RELEASED:
|
if bstate & curses.BUTTON1_RELEASED:
|
||||||
if state.dragging_divider:
|
if state.dragging_divider:
|
||||||
state.dragging_divider = False
|
state.dragging_divider = False
|
||||||
else:
|
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"
|
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:
|
except curses.error:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -397,7 +473,7 @@ def main(stdscr, filename, show_diff, show_add, show_del, mouse):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if state.enable_mouse and key == curses.KEY_MOUSE:
|
if state.enable_mouse and key == curses.KEY_MOUSE:
|
||||||
handle_mouse_input(state)
|
handle_mouse_input(stdscr, state)
|
||||||
else:
|
else:
|
||||||
handle_keyboard_input(key, state)
|
handle_keyboard_input(key, state)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue