fix: Improve text selection and clipboard handling

This commit is contained in:
n loewen (aider) 2025-06-08 10:31:11 +01:00
parent bc10b41059
commit b8f49880f7
1 changed files with 54 additions and 21 deletions

75
gtm
View File

@ -351,7 +351,9 @@ def toggle_sidebar(state: AppState) -> AppState:
def move_commit_selection(state: AppState, delta: int) -> AppState: def move_commit_selection(state: AppState, delta: int) -> AppState:
new_idx = state.selected_commit_idx + delta new_idx = state.selected_commit_idx + delta
if 0 <= new_idx < len(state.commits): if 0 <= new_idx < len(state.commits):
new_state = replace(state, selected_commit_idx=new_idx) # Clear selection when changing commits
new_state = replace(state, selected_commit_idx=new_idx, is_selecting=False,
selection_start_coord=None, selection_end_coord=None, click_position=None)
return load_commit_content(new_state) return load_commit_content(new_state)
return state return state
@ -371,6 +373,11 @@ def scroll_right_pane(state: AppState, delta: int) -> AppState:
new_offset = state.right_scroll_offset + delta new_offset = state.right_scroll_offset + delta
new_offset = max(0, min(new_offset, max_scroll)) new_offset = max(0, min(new_offset, max_scroll))
# Clear selection when scrolling
if state.is_selecting:
return replace(state, right_scroll_offset=new_offset, is_selecting=False,
selection_start_coord=None, selection_end_coord=None, click_position=None)
return replace(state, right_scroll_offset=new_offset) return replace(state, right_scroll_offset=new_offset)
def page_right_pane(state: AppState, direction: int) -> AppState: def page_right_pane(state: AppState, direction: int) -> AppState:
@ -1152,13 +1159,13 @@ def copy_selection_to_clipboard(stdscr, state):
line_num = i + state.right_scroll_offset + 1 line_num = i + state.right_scroll_offset + 1
# Add deleted lines if they should be shown # Add deleted lines if they should be shown
if (state.show_whole_diff or state.show_deletions) and line_num in deleted_line_map: if state.show_deletions and line_num in deleted_line_map:
for del_content in deleted_line_map[line_num]: for del_content in deleted_line_map[line_num]:
display_lines.append({'type': 'deleted', 'content': del_content}) display_lines.append({'type': 'deleted', 'content': del_content})
# Determine if this is an added line # Determine if this is an added line
is_added = False is_added = False
if state.show_whole_diff or state.show_additions: if state.show_additions:
for added_line_num, _ in state.added_lines: for added_line_num, _ in state.added_lines:
if added_line_num == line_num: if added_line_num == line_num:
is_added = True is_added = True
@ -1204,18 +1211,37 @@ def copy_selection_to_clipboard(stdscr, state):
if selected_text_parts: if selected_text_parts:
text_to_copy = "\n".join(selected_text_parts) text_to_copy = "\n".join(selected_text_parts)
if text_to_copy.strip(): if text_to_copy.strip():
# Log the text being copied for debugging
with open("/tmp/gtm_clipboard.log", "w") as f:
f.write(f"Copying to clipboard: {text_to_copy}\n")
# Try macOS pbcopy first (most likely on this system)
try: try:
subprocess.run(['pbcopy'], input=text_to_copy, text=True, check=True) proc = subprocess.Popen(['pbcopy'], stdin=subprocess.PIPE)
except (FileNotFoundError, subprocess.CalledProcessError): proc.communicate(text_to_copy.encode('utf-8'))
try: proc.wait()
# Try xclip for Linux systems return # Return early if successful
subprocess.run(['xclip', '-selection', 'clipboard'], input=text_to_copy, text=True, check=True) except (FileNotFoundError, subprocess.SubprocessError):
except (FileNotFoundError, subprocess.CalledProcessError): pass
try:
# Try clip.exe for Windows # Try Linux xclip
subprocess.run(['clip.exe'], input=text_to_copy, text=True, check=True) try:
except (FileNotFoundError, subprocess.CalledProcessError): proc = subprocess.Popen(['xclip', '-selection', 'clipboard'], stdin=subprocess.PIPE)
pass # Silently fail if no clipboard command is available proc.communicate(text_to_copy.encode('utf-8'))
proc.wait()
return # Return early if successful
except (FileNotFoundError, subprocess.SubprocessError):
pass
# Try Windows clip.exe
try:
proc = subprocess.Popen(['clip.exe'], stdin=subprocess.PIPE)
proc.communicate(text_to_copy.encode('utf-8'))
proc.wait()
except (FileNotFoundError, subprocess.SubprocessError):
# Log failure
with open("/tmp/gtm_clipboard.log", "a") as f:
f.write("Failed to copy to clipboard - no clipboard command available\n")
def update_status_bar_height(state: AppState, my: int) -> AppState: def update_status_bar_height(state: AppState, my: int) -> AppState:
"""Update the status bar height based on mouse position.""" """Update the status bar height based on mouse position."""
@ -1275,17 +1301,24 @@ def handle_mouse_input(stdscr, state: AppState) -> AppState:
if state.commit_hash_clicked: if state.commit_hash_clicked:
# Copy commit hash to clipboard # Copy commit hash to clipboard
if state.commit_hash: if state.commit_hash:
# Try macOS pbcopy first (most likely on this system)
try: try:
subprocess.run(['pbcopy'], input=state.commit_hash, text=True, check=True) proc = subprocess.Popen(['pbcopy'], stdin=subprocess.PIPE)
except (FileNotFoundError, subprocess.CalledProcessError): proc.communicate(state.commit_hash.encode('utf-8'))
proc.wait()
except (FileNotFoundError, subprocess.SubprocessError):
try: try:
# Try xclip for Linux systems # Try xclip for Linux systems
subprocess.run(['xclip', '-selection', 'clipboard'], input=state.commit_hash, text=True, check=True) proc = subprocess.Popen(['xclip', '-selection', 'clipboard'], stdin=subprocess.PIPE)
except (FileNotFoundError, subprocess.CalledProcessError): proc.communicate(state.commit_hash.encode('utf-8'))
proc.wait()
except (FileNotFoundError, subprocess.SubprocessError):
try: try:
# Try clip.exe for Windows # Try clip.exe for Windows
subprocess.run(['clip.exe'], input=state.commit_hash, text=True, check=True) proc = subprocess.Popen(['clip.exe'], stdin=subprocess.PIPE)
except (FileNotFoundError, subprocess.CalledProcessError): proc.communicate(state.commit_hash.encode('utf-8'))
proc.wait()
except (FileNotFoundError, subprocess.SubprocessError):
pass # Silently fail if no clipboard command is available pass # Silently fail if no clipboard command is available
# Reset the clicked state # Reset the clicked state
@ -1356,7 +1389,7 @@ def handle_keyboard_input(key, state: AppState) -> AppState:
if state.search_mode: if state.search_mode:
new_state = replace(new_state, search_mode=False) new_state = replace(new_state, search_mode=False)
elif state.is_selecting: elif state.is_selecting:
new_state = replace(new_state, is_selecting=False, selection_start_coord=None, selection_end_coord=None) new_state = replace(new_state, is_selecting=False, selection_start_coord=None, selection_end_coord=None, click_position=None)
# Return the updated state with all changes # Return the updated state with all changes
return new_state return new_state