refactor: Decouple clipboard selection from screen rendering using data model
This commit is contained in:
parent
217174c647
commit
cfa96a436f
145
gtm
145
gtm
|
|
@ -622,66 +622,119 @@ def copy_selection_to_clipboard(stdscr, state):
|
|||
start_x, start_y = state.selection_start_coord
|
||||
end_x, end_y = state.selection_end_coord
|
||||
|
||||
# Determine pane boundaries based on sidebar visibility
|
||||
# Determine pane from where selection started
|
||||
if state.show_sidebar:
|
||||
# Determine pane from where selection started
|
||||
pane = 'left' if start_x < state.divider_col else 'right'
|
||||
if pane == 'left':
|
||||
pane_x1, pane_x2 = 0, state.divider_col - 1
|
||||
else: # right
|
||||
pane_x1, pane_x2 = state.divider_col + 2, state.width - 1
|
||||
else:
|
||||
# When sidebar is hidden, there's only the right pane
|
||||
pane = 'right'
|
||||
pane_x1, pane_x2 = 0, state.width - 1
|
||||
pane = 'right' # When sidebar is hidden, there's only the right pane
|
||||
|
||||
# Determine drag direction to handle multi-line selection correctly
|
||||
if start_y < end_y or (start_y == end_y and start_x <= end_x):
|
||||
drag_start_x, drag_start_y = start_x, start_y
|
||||
drag_end_x, drag_end_y = end_x, end_y
|
||||
else: # upward drag or right-to-left on same line
|
||||
drag_start_x, drag_start_y = end_x, end_y
|
||||
drag_end_x, drag_end_y = start_x, start_y
|
||||
if start_y > end_y or (start_y == end_y and start_x > end_x):
|
||||
# Swap coordinates if selection is bottom-to-top or right-to-left
|
||||
start_x, start_y, end_x, end_y = end_x, end_y, start_x, start_y
|
||||
|
||||
height, width = stdscr.getmaxyx()
|
||||
# Get the selected text from the data model
|
||||
selected_text_parts = []
|
||||
|
||||
for y in range(drag_start_y, drag_end_y + 1):
|
||||
if not (0 <= y < height):
|
||||
continue
|
||||
|
||||
x1, x2 = -1, -1
|
||||
if drag_start_y == drag_end_y: # single line selection
|
||||
x1, x2 = drag_start_x, drag_end_x
|
||||
elif y == drag_start_y: # first line of multi-line selection
|
||||
x1, x2 = drag_start_x, pane_x2
|
||||
elif y == drag_end_y: # last line of multi-line selection
|
||||
x1, x2 = pane_x1, drag_end_x
|
||||
else: # middle line of multi-line selection
|
||||
x1, x2 = pane_x1, pane_x2
|
||||
|
||||
if pane == 'left' and state.show_sidebar:
|
||||
# Selection in the commits pane
|
||||
visible_commits = state.commits[state.left_scroll_offset:state.left_scroll_offset + state.height - 1]
|
||||
for i in range(start_y, min(end_y + 1, len(visible_commits))):
|
||||
line_idx = i + state.left_scroll_offset
|
||||
if 0 <= line_idx < len(state.commits):
|
||||
line = state.commits[line_idx]
|
||||
|
||||
# Calculate character positions
|
||||
start_char = 0 if i > start_y else start_x
|
||||
end_char = len(line) if i < end_y else end_x + 1
|
||||
|
||||
# Get the substring
|
||||
if start_char < len(line):
|
||||
selected_text_parts.append(line[start_char:min(end_char, len(line))])
|
||||
else:
|
||||
# Selection in the file content pane
|
||||
# First, build a list of all visible lines including deleted lines if shown
|
||||
visible_lines = []
|
||||
deleted_line_map = {}
|
||||
|
||||
# Clamp selection to pane boundaries and screen width
|
||||
x1 = max(x1, pane_x1)
|
||||
x2 = min(x2, pane_x2, width - 1)
|
||||
|
||||
line_str = ""
|
||||
if x1 <= x2:
|
||||
for x in range(x1, x2 + 1):
|
||||
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)
|
||||
|
||||
if state.show_whole_diff or state.show_deletions:
|
||||
for del_line_num, del_content in state.deleted_lines:
|
||||
if del_line_num not in deleted_line_map:
|
||||
deleted_line_map[del_line_num] = []
|
||||
deleted_line_map[del_line_num].append(del_content)
|
||||
|
||||
# Build the list of visible lines with their types
|
||||
file_lines = state.file_lines[state.right_scroll_offset:state.right_scroll_offset + state.height - 1]
|
||||
display_lines = []
|
||||
|
||||
for i, line in enumerate(file_lines):
|
||||
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:
|
||||
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:
|
||||
for added_line_num, _ in state.added_lines:
|
||||
if added_line_num == line_num:
|
||||
is_added = True
|
||||
break
|
||||
|
||||
display_lines.append({'type': 'added' if is_added else 'regular', 'content': line})
|
||||
|
||||
# Now extract the selected text from these lines
|
||||
right_start = 0 if not state.show_sidebar else state.divider_col + 2
|
||||
|
||||
for i in range(start_y, min(end_y + 1, len(display_lines))):
|
||||
if i < 0 or i >= len(display_lines):
|
||||
continue
|
||||
|
||||
line_info = display_lines[i]
|
||||
content = line_info['content']
|
||||
line_type = line_info['type']
|
||||
|
||||
# Add prefix based on line type if in diff mode
|
||||
prefix = ""
|
||||
if line_type == 'added' and (state.show_whole_diff or state.show_additions):
|
||||
prefix = "+ "
|
||||
elif line_type == 'deleted' and (state.show_whole_diff or state.show_deletions):
|
||||
prefix = "- "
|
||||
elif state.show_whole_diff or state.show_additions or state.show_deletions:
|
||||
prefix = " "
|
||||
|
||||
# Calculate character positions, accounting for the prefix
|
||||
content_with_prefix = prefix + content
|
||||
|
||||
# Adjust start_x and end_x to account for the right pane offset
|
||||
adjusted_start_x = max(0, start_x - right_start) if i == start_y else 0
|
||||
adjusted_end_x = end_x - right_start if i == end_y else len(content_with_prefix)
|
||||
|
||||
# Ensure we don't go out of bounds
|
||||
adjusted_start_x = min(adjusted_start_x, len(content_with_prefix))
|
||||
adjusted_end_x = min(adjusted_end_x, len(content_with_prefix))
|
||||
|
||||
if adjusted_start_x < adjusted_end_x:
|
||||
selected_text_parts.append(content_with_prefix[adjusted_start_x:adjusted_end_x])
|
||||
|
||||
# Join the selected text parts and copy to clipboard
|
||||
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
|
||||
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
|
||||
|
||||
def handle_mouse_input(stdscr, state: AppState) -> AppState:
|
||||
try:
|
||||
|
|
|
|||
Loading…
Reference in New Issue