Skip to main content

par_term/copy_mode/
visual.rs

1//! Visual mode and selection methods for the copy mode state machine.
2
3use super::types::{CopyModeState, VisualMode};
4use crate::selection::{Selection, SelectionMode};
5
6impl CopyModeState {
7    // ========================================================================
8    // Visual mode
9    // ========================================================================
10
11    /// Toggle character-wise visual mode
12    pub fn toggle_visual_char(&mut self) {
13        if self.visual_mode == VisualMode::Char {
14            self.visual_mode = VisualMode::None;
15            self.selection_anchor = None;
16        } else {
17            self.visual_mode = VisualMode::Char;
18            self.selection_anchor = Some((self.cursor_absolute_line, self.cursor_col));
19        }
20    }
21
22    /// Toggle line-wise visual mode
23    pub fn toggle_visual_line(&mut self) {
24        if self.visual_mode == VisualMode::Line {
25            self.visual_mode = VisualMode::None;
26            self.selection_anchor = None;
27        } else {
28            self.visual_mode = VisualMode::Line;
29            self.selection_anchor = Some((self.cursor_absolute_line, self.cursor_col));
30        }
31    }
32
33    /// Toggle block/rectangular visual mode
34    pub fn toggle_visual_block(&mut self) {
35        if self.visual_mode == VisualMode::Block {
36            self.visual_mode = VisualMode::None;
37            self.selection_anchor = None;
38        } else {
39            self.visual_mode = VisualMode::Block;
40            self.selection_anchor = Some((self.cursor_absolute_line, self.cursor_col));
41        }
42    }
43
44    /// Compute a `Selection` from the current visual mode state.
45    ///
46    /// The selection coordinates are in screen-relative terms
47    /// (row = line - viewport_top) for rendering.
48    /// `scroll_offset` is the current viewport scroll position.
49    pub fn compute_selection(&self, scroll_offset: usize) -> Option<Selection> {
50        if self.visual_mode == VisualMode::None {
51            return None;
52        }
53
54        let (anchor_line, anchor_col) = self.selection_anchor?;
55
56        // Convert absolute lines to viewport-relative rows
57        // viewport_top = scrollback_len - scroll_offset (absolute line at top of screen)
58        let viewport_top = self.scrollback_len.saturating_sub(scroll_offset);
59
60        // Both anchor and cursor must produce valid viewport rows for rendering
61        // We allow negative (above screen) and beyond-screen positions
62        // by clamping to 0..rows-1 for rendering purposes
63        let anchor_row = anchor_line.saturating_sub(viewport_top);
64        let cursor_row = self.cursor_absolute_line.saturating_sub(viewport_top);
65
66        let mode = match self.visual_mode {
67            VisualMode::None => return None,
68            VisualMode::Char => SelectionMode::Normal,
69            VisualMode::Line => SelectionMode::Line,
70            VisualMode::Block => SelectionMode::Rectangular,
71        };
72
73        let start = (anchor_col, anchor_row);
74        let end = (self.cursor_col, cursor_row);
75
76        Some(Selection::new(start, end, mode, scroll_offset))
77    }
78}