diff options
Diffstat (limited to 'src/app/input/selection.rs')
| -rw-r--r-- | src/app/input/selection.rs | 306 |
1 files changed, 0 insertions, 306 deletions
diff --git a/src/app/input/selection.rs b/src/app/input/selection.rs deleted file mode 100644 index a66509f0..00000000 --- a/src/app/input/selection.rs +++ /dev/null @@ -1,306 +0,0 @@ -use crossterm::event::{MouseEvent, MouseEventKind}; - -use crate::{ - app::state::{AppState, SelectionAutoscroll, SelectionAutoscrollDirection}, - terminal::TerminalRuntimeRegistry, -}; - -impl AppState { - pub(crate) fn update_selection_cursor( - &mut self, - terminal_runtimes: &TerminalRuntimeRegistry, - pane_id: crate::layout::PaneId, - screen_col: u16, - screen_row: u16, - ) { - let Some(info) = self.pane_info_by_id(pane_id).cloned() else { - return; - }; - let metrics = self.pane_scroll_metrics(terminal_runtimes, pane_id); - if let Some(selection) = self.selection.as_mut() { - selection.drag(screen_col, screen_row, info.inner_rect, metrics); - } - } - - fn selection_edge_scroll_lines(distance: u16) -> usize { - usize::from(distance).saturating_mul(3).clamp(3, 15) - } - - pub(super) fn update_selection_drag( - &mut self, - terminal_runtimes: &TerminalRuntimeRegistry, - screen_col: u16, - screen_row: u16, - ) { - let Some(pane_id) = self.selection.as_ref().map(|selection| selection.pane_id) else { - return; - }; - let Some(info) = self.pane_info_by_id(pane_id).cloned() else { - return; - }; - - let top = info.inner_rect.y; - let bottom = info.inner_rect.y + info.inner_rect.height.saturating_sub(1); - - // Only activate autoscroll when the user is actively dragging. - // An anchored click in the hot zone should not start the timer. - // Check before advancing the cursor: if already Dragging from a prior - // event, it stays true. If Anchored, the mouse must have moved away - // from the anchor cell for this to count as a real drag. - let was_dragging = self.selection.as_ref().is_some_and(|s| s.is_dragging()); - let anchor_differs_from_mouse = self.selection.as_ref().is_some_and(|s| { - // Convert anchor to screen coords for comparison. - // Anchor is stored in absolute row; for a simple screen - // comparison, check whether the mouse is on a different - // cell than the anchor's screen position. - let (ar, ac) = s.anchor_screen_pos( - info.inner_rect, - self.pane_scroll_metrics(terminal_runtimes, s.pane_id), - ); - ar != screen_row || ac != screen_col - }); - let is_dragging = was_dragging || anchor_differs_from_mouse; - - // Advance the selection cursor. - self.update_selection_cursor(terminal_runtimes, pane_id, screen_col, screen_row); - - // If the mouse is on a different cell than the anchor but drag() - // didn't transition (cursor clamped to edge == anchor), force - // Dragging so the selection becomes visible and autoscroll can run. - if is_dragging { - if let Some(sel) = self.selection.as_mut() { - if sel.is_just_click() { - sel.force_dragging(); - } - } - } - - if screen_row < top { - // Cursor above pane — immediate scroll + set autoscroll state - if is_dragging { - self.scroll_pane_up( - terminal_runtimes, - pane_id, - Self::selection_edge_scroll_lines(top - screen_row), - ); - // Re-advance cursor after scroll so it reflects the new viewport position - self.update_selection_cursor(terminal_runtimes, pane_id, screen_col, screen_row); - self.selection_autoscroll = Some(SelectionAutoscroll { - direction: SelectionAutoscrollDirection::Up, - last_mouse_screen_col: screen_col, - last_mouse_screen_row: screen_row, - inner_rect: info.inner_rect, - }); - } - } else if screen_row > bottom { - // Cursor below pane — immediate scroll + set autoscroll state - if is_dragging { - self.scroll_pane_down( - terminal_runtimes, - pane_id, - Self::selection_edge_scroll_lines(screen_row - bottom), - ); - // Re-advance cursor after scroll so it reflects the new viewport position - self.update_selection_cursor(terminal_runtimes, pane_id, screen_col, screen_row); - self.selection_autoscroll = Some(SelectionAutoscroll { - direction: SelectionAutoscrollDirection::Down, - last_mouse_screen_col: screen_col, - last_mouse_screen_row: screen_row, - inner_rect: info.inner_rect, - }); - } - } else if screen_row == top { - // Hot zone: top edge row — no immediate scroll, set autoscroll state - if is_dragging { - self.selection_autoscroll = Some(SelectionAutoscroll { - direction: SelectionAutoscrollDirection::Up, - last_mouse_screen_col: screen_col, - last_mouse_screen_row: screen_row, - inner_rect: info.inner_rect, - }); - } else { - self.selection_autoscroll = None; - } - } else if screen_row == bottom { - // Hot zone: bottom edge row — no immediate scroll, set autoscroll state - if is_dragging { - self.selection_autoscroll = Some(SelectionAutoscroll { - direction: SelectionAutoscrollDirection::Down, - last_mouse_screen_col: screen_col, - last_mouse_screen_row: screen_row, - inner_rect: info.inner_rect, - }); - } else { - self.selection_autoscroll = None; - } - } else { - // Safe zone: inside pane, not on edge rows — clear autoscroll - self.selection_autoscroll = None; - } - } - - pub(super) fn scroll_selection_with_wheel( - &mut self, - terminal_runtimes: &TerminalRuntimeRegistry, - mouse: MouseEvent, - ) -> bool { - let lines_per_notch = self.mouse_scroll_lines; - - let Some(selection) = self.selection.as_ref() else { - return false; - }; - if !selection.is_in_progress() { - return false; - } - let pane_id = selection.pane_id; - self.focus_pane(pane_id); - match mouse.kind { - MouseEventKind::ScrollUp => { - self.scroll_pane_up(terminal_runtimes, pane_id, lines_per_notch) - } - MouseEventKind::ScrollDown => { - self.scroll_pane_down(terminal_runtimes, pane_id, lines_per_notch) - } - _ => return false, - } - self.update_selection_cursor(terminal_runtimes, pane_id, mouse.column, mouse.row); - true - } -} - -#[cfg(test)] -mod autoscroll_tests { - use super::*; - use crate::layout::PaneInfo; - use crate::terminal::TerminalRuntimeRegistry; - use crate::workspace::Workspace; - use ratatui::layout::Rect; - - /// Build an AppState with one workspace/pane and pane_infos populated - /// so pane_info_by_id works. Returns (state, pane_id). - fn make_state_with_pane() -> (AppState, crate::layout::PaneId) { - let mut state = AppState::test_new(); - let ws = Workspace::test_new("test"); - let pane_id = ws.tabs[0].root_pane; - state.workspaces.push(ws); - state.active = Some(0); - state.view.pane_infos.push(PaneInfo { - id: pane_id, - rect: Rect::new(0, 0, 80, 24), - inner_rect: Rect::new(0, 0, 80, 24), - scrollbar_rect: None, - borders: ratatui::widgets::Borders::NONE, - is_focused: true, - }); - (state, pane_id) - } - - #[test] - fn above_pane_sets_autoscroll_up() { - // Build state with pane starting at row 5 so we can drag above it - let mut state = AppState::test_new(); - let ws = Workspace::test_new("test"); - let pane_id = ws.tabs[0].root_pane; - state.workspaces.push(ws); - state.active = Some(0); - state.view.pane_infos.push(PaneInfo { - id: pane_id, - rect: Rect::new(0, 5, 80, 24), - inner_rect: Rect::new(0, 5, 80, 24), - scrollbar_rect: None, - borders: ratatui::widgets::Borders::NONE, - is_focused: true, - }); - // Anchor at (5, 10), drag to different cell above pane - let mut sel = crate::selection::Selection::anchor(pane_id, 5, 10, None); - sel.drag(4, 5, Rect::new(0, 5, 80, 24), None); - state.selection = Some(sel); - let terminal_runtimes = TerminalRuntimeRegistry::new(); - state.update_selection_drag(&terminal_runtimes, 5, 4); - let autoscroll = state.selection_autoscroll.as_ref().unwrap(); - assert_eq!(autoscroll.direction, SelectionAutoscrollDirection::Up); - } - - #[test] - fn top_hot_zone_sets_autoscroll_up_on_drag() { - let (mut state, pane_id) = make_state_with_pane(); - // Anchor at (5, 10), drag to top edge row (row 0) — different cell - let mut sel = crate::selection::Selection::anchor(pane_id, 5, 10, None); - sel.drag(0, 0, Rect::new(0, 0, 80, 24), None); - state.selection = Some(sel); - let terminal_runtimes = TerminalRuntimeRegistry::new(); - state.update_selection_drag(&terminal_runtimes, 0, 0); - let autoscroll = state.selection_autoscroll.as_ref().unwrap(); - assert_eq!(autoscroll.direction, SelectionAutoscrollDirection::Up); - } - - #[test] - fn top_hot_zone_clears_autoscroll_on_click() { - // An anchored click on the top edge row should NOT start autoscroll. - let (mut state, pane_id) = make_state_with_pane(); - state.selection = Some(crate::selection::Selection::anchor(pane_id, 0, 0, None)); - // Same-cell drag on top edge row — still anchored - let terminal_runtimes = TerminalRuntimeRegistry::new(); - state.update_selection_drag(&terminal_runtimes, 0, 0); - assert!(state.selection_autoscroll.is_none()); - } - - #[test] - fn bottom_hot_zone_sets_autoscroll_down_on_drag() { - let (mut state, pane_id) = make_state_with_pane(); - // Anchor at (0, 0), drag to bottom edge row (row 23) — different cell - let mut sel = crate::selection::Selection::anchor(pane_id, 0, 0, None); - sel.drag(23, 0, Rect::new(0, 0, 80, 24), None); - state.selection = Some(sel); - let terminal_runtimes = TerminalRuntimeRegistry::new(); - state.update_selection_drag(&terminal_runtimes, 0, 23); - let autoscroll = state.selection_autoscroll.as_ref().unwrap(); - assert_eq!(autoscroll.direction, SelectionAutoscrollDirection::Down); - } - - #[test] - fn bottom_hot_zone_clears_autoscroll_on_click() { - // An anchored click on the bottom edge row should NOT start autoscroll. - let (mut state, pane_id) = make_state_with_pane(); - // Anchor at bottom edge row - state.selection = Some(crate::selection::Selection::anchor(pane_id, 23, 0, None)); - // Same-cell drag — still anchored - let terminal_runtimes = TerminalRuntimeRegistry::new(); - state.update_selection_drag(&terminal_runtimes, 0, 23); - assert!(state.selection_autoscroll.is_none()); - } - - #[test] - fn below_pane_sets_autoscroll_down_on_drag() { - let (mut state, pane_id) = make_state_with_pane(); - // Anchor at (0, 0), drag to different cell below pane - let mut sel = crate::selection::Selection::anchor(pane_id, 0, 0, None); - sel.drag(5, 5, Rect::new(0, 0, 80, 24), None); - state.selection = Some(sel); - // Drag cursor one row below the pane bottom - let terminal_runtimes = TerminalRuntimeRegistry::new(); - state.update_selection_drag(&terminal_runtimes, 0, 24); - let autoscroll = state.selection_autoscroll.as_ref().unwrap(); - assert_eq!(autoscroll.direction, SelectionAutoscrollDirection::Down); - } - - #[test] - fn safe_zone_clears_autoscroll() { - let (mut state, pane_id) = make_state_with_pane(); - // Anchor at (0, 0), drag to (5, 5) so it's truly dragging - let mut sel = crate::selection::Selection::anchor(pane_id, 0, 0, None); - sel.drag(5, 5, Rect::new(0, 0, 80, 24), None); - state.selection = Some(sel); - // Set autoscroll first - state.selection_autoscroll = Some(SelectionAutoscroll { - direction: SelectionAutoscrollDirection::Down, - last_mouse_screen_col: 5, - last_mouse_screen_row: 23, - inner_rect: Rect::new(0, 0, 80, 24), - }); - // Move cursor into safe zone (middle of pane, not on edge rows) - let terminal_runtimes = TerminalRuntimeRegistry::new(); - state.update_selection_drag(&terminal_runtimes, 5, 12); - assert!(state.selection_autoscroll.is_none()); - } -} |
