From b8f49880f7e335473a68861d3dac0e1c7348534d Mon Sep 17 00:00:00 2001 From: "n loewen (aider)" Date: Sun, 8 Jun 2025 10:31:11 +0100 Subject: [PATCH] fix: Improve text selection and clipboard handling --- gtm | 75 ++++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/gtm b/gtm index b1c8314..51e4c39 100755 --- a/gtm +++ b/gtm @@ -351,7 +351,9 @@ def toggle_sidebar(state: AppState) -> AppState: def move_commit_selection(state: AppState, delta: int) -> AppState: new_idx = state.selected_commit_idx + delta 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 state @@ -371,6 +373,11 @@ def scroll_right_pane(state: AppState, delta: int) -> AppState: new_offset = state.right_scroll_offset + delta 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) 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 # 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]: display_lines.append({'type': 'deleted', 'content': del_content}) # Determine if this is an added line is_added = False - if state.show_whole_diff or state.show_additions: + if state.show_additions: for added_line_num, _ in state.added_lines: if added_line_num == line_num: is_added = True @@ -1204,18 +1211,37 @@ def copy_selection_to_clipboard(stdscr, state): if selected_text_parts: text_to_copy = "\n".join(selected_text_parts) 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: - subprocess.run(['pbcopy'], input=text_to_copy, text=True, check=True) - except (FileNotFoundError, subprocess.CalledProcessError): - try: - # Try xclip for Linux systems - subprocess.run(['xclip', '-selection', 'clipboard'], input=text_to_copy, text=True, check=True) - except (FileNotFoundError, subprocess.CalledProcessError): - try: - # Try clip.exe for Windows - subprocess.run(['clip.exe'], input=text_to_copy, text=True, check=True) - except (FileNotFoundError, subprocess.CalledProcessError): - pass # Silently fail if no clipboard command is available + proc = subprocess.Popen(['pbcopy'], stdin=subprocess.PIPE) + proc.communicate(text_to_copy.encode('utf-8')) + proc.wait() + return # Return early if successful + except (FileNotFoundError, subprocess.SubprocessError): + pass + + # Try Linux xclip + try: + proc = subprocess.Popen(['xclip', '-selection', 'clipboard'], stdin=subprocess.PIPE) + 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: """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: # Copy commit hash to clipboard if state.commit_hash: + # Try macOS pbcopy first (most likely on this system) try: - subprocess.run(['pbcopy'], input=state.commit_hash, text=True, check=True) - except (FileNotFoundError, subprocess.CalledProcessError): + proc = subprocess.Popen(['pbcopy'], stdin=subprocess.PIPE) + proc.communicate(state.commit_hash.encode('utf-8')) + proc.wait() + except (FileNotFoundError, subprocess.SubprocessError): try: # Try xclip for Linux systems - subprocess.run(['xclip', '-selection', 'clipboard'], input=state.commit_hash, text=True, check=True) - except (FileNotFoundError, subprocess.CalledProcessError): + proc = subprocess.Popen(['xclip', '-selection', 'clipboard'], stdin=subprocess.PIPE) + proc.communicate(state.commit_hash.encode('utf-8')) + proc.wait() + except (FileNotFoundError, subprocess.SubprocessError): try: # Try clip.exe for Windows - subprocess.run(['clip.exe'], input=state.commit_hash, text=True, check=True) - except (FileNotFoundError, subprocess.CalledProcessError): + proc = subprocess.Popen(['clip.exe'], stdin=subprocess.PIPE) + proc.communicate(state.commit_hash.encode('utf-8')) + proc.wait() + except (FileNotFoundError, subprocess.SubprocessError): pass # Silently fail if no clipboard command is available # Reset the clicked state @@ -1356,7 +1389,7 @@ def handle_keyboard_input(key, state: AppState) -> AppState: if state.search_mode: new_state = replace(new_state, search_mode=False) 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 new_state