Skip to main content

par_term/
selection.rs

1/// Selection mode
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3pub enum SelectionMode {
4    /// Normal character-based selection
5    Normal,
6    /// Rectangular/block selection
7    Rectangular,
8    /// Full line selection (triple-click)
9    Line,
10    /// Entire terminal buffer (scrollback + visible screen).
11    ///
12    /// The stored `start`/`end` cover the visible screen so the on-screen
13    /// highlight works through the normal cell-marking path, but copy pulls the
14    /// whole buffer via `TerminalManager::export_text` rather than the viewport.
15    All,
16}
17
18/// Selection state for text selection
19#[derive(Debug, Clone, Copy, PartialEq)]
20pub struct Selection {
21    /// Start position (col, row) in viewport-relative coordinates at `scroll_offset`
22    pub start: (usize, usize),
23    /// End position (col, row) in viewport-relative coordinates at `scroll_offset`
24    pub end: (usize, usize),
25    /// Selection mode
26    pub mode: SelectionMode,
27    /// Scroll offset at the time the selection was captured.
28    ///
29    /// Row coordinates are viewport-relative (0 = top of the visible screen) when
30    /// `scroll_offset` was the active viewport offset.  The renderer adjusts the
31    /// rows by `(self.scroll_offset as isize - current_scroll_offset as isize)` so
32    /// the highlight tracks the content as the user scrolls.
33    pub scroll_offset: usize,
34}
35
36impl Selection {
37    /// Create a new selection.
38    ///
39    /// `scroll_offset` must be the current viewport scroll offset so the
40    /// renderer can compensate when the user scrolls after the selection.
41    pub fn new(
42        start: (usize, usize),
43        end: (usize, usize),
44        mode: SelectionMode,
45        scroll_offset: usize,
46    ) -> Self {
47        Self {
48            start,
49            end,
50            mode,
51            scroll_offset,
52        }
53    }
54
55    /// Return a copy of this selection with rows adjusted to `current_scroll_offset`.
56    ///
57    /// Rows that shift above the top of the viewport become `usize::MAX` so that
58    /// `is_cell_selected` never matches them.  Rows shifted below the viewport are
59    /// left as-is (they exceed the row count and are also never matched).
60    pub fn viewport_adjusted(&self, current_scroll_offset: usize) -> Self {
61        let delta = current_scroll_offset as isize - self.scroll_offset as isize;
62        let adjust = |row: usize| -> usize {
63            let adjusted = row as isize + delta;
64            if adjusted < 0 {
65                usize::MAX
66            } else {
67                adjusted as usize
68            }
69        };
70        Self {
71            start: (self.start.0, adjust(self.start.1)),
72            end: (self.end.0, adjust(self.end.1)),
73            mode: self.mode,
74            scroll_offset: current_scroll_offset,
75        }
76    }
77
78    /// Get normalized selection (ensures start is before end)
79    pub fn normalized(&self) -> ((usize, usize), (usize, usize)) {
80        let (start_col, start_row) = self.start;
81        let (end_col, end_row) = self.end;
82
83        if start_row < end_row || (start_row == end_row && start_col <= end_col) {
84            (self.start, self.end)
85        } else {
86            (self.end, self.start)
87        }
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_selection_normalization() {
97        // Forward selection
98        let sel = Selection::new((0, 0), (10, 0), SelectionMode::Normal, 0);
99        assert_eq!(sel.normalized(), ((0, 0), (10, 0)));
100
101        // Backward selection (same line)
102        let sel = Selection::new((10, 0), (0, 0), SelectionMode::Normal, 0);
103        assert_eq!(sel.normalized(), ((0, 0), (10, 0)));
104
105        // Forward selection (multi-line)
106        let sel = Selection::new((10, 0), (5, 1), SelectionMode::Normal, 0);
107        assert_eq!(sel.normalized(), ((10, 0), (5, 1)));
108
109        // Backward selection (multi-line)
110        let sel = Selection::new((5, 1), (10, 0), SelectionMode::Normal, 0);
111        assert_eq!(sel.normalized(), ((10, 0), (5, 1)));
112    }
113}