Skip to main content

par_term/copy_mode/
mod.rs

1//! Vi-style Copy Mode state machine.
2//!
3//! Copy Mode provides keyboard-driven text selection and navigation,
4//! matching iTerm2's Copy Mode. When active, all keyboard input navigates
5//! an independent cursor through the terminal buffer (including scrollback).
6//!
7//! ## Module layout
8//!
9//! - `types`: All type and struct definitions (`CopyModeState`, `VisualMode`, etc.)
10//! - `cursor`: Cursor movement methods (basic motions, page motions, viewport helpers)
11//! - `motion`: Word and line navigation helpers (`move_word_forward`, etc.)
12//! - `visual`: Visual mode and selection methods (`toggle_visual_*`, `compute_selection`)
13//! - `search`: Search state methods (`start_search`, `search_input`, etc.)
14
15mod cursor;
16mod motion;
17mod search;
18mod types;
19mod visual;
20
21// Re-export the public API so external callers are unaffected.
22pub use crate::selection::SelectionMode;
23pub use types::{CopyModeState, Mark, PendingOperator, SearchDirection, VisualMode};
24
25impl CopyModeState {
26    // ========================================================================
27    // Marks
28    // ========================================================================
29
30    /// Set a named mark at the current cursor position
31    pub fn set_mark(&mut self, name: char) {
32        self.marks.insert(
33            name,
34            Mark {
35                col: self.cursor_col,
36                absolute_line: self.cursor_absolute_line,
37            },
38        );
39    }
40
41    /// Jump to a named mark, returning true if the mark exists
42    pub fn goto_mark(&mut self, name: char) -> bool {
43        if let Some(mark) = self.marks.get(&name) {
44            self.cursor_col = mark.col;
45            self.cursor_absolute_line = mark.absolute_line;
46            true
47        } else {
48            false
49        }
50    }
51
52    // ========================================================================
53    // Status
54    // ========================================================================
55
56    /// Get a status line description of the current mode
57    pub fn status_text(&self) -> String {
58        if self.is_searching {
59            let dir = match self.search_direction {
60                SearchDirection::Forward => '/',
61                SearchDirection::Backward => '?',
62            };
63            format!("{}{}", dir, self.search_query)
64        } else {
65            let mode = match self.visual_mode {
66                VisualMode::None => "COPY",
67                VisualMode::Char => "VISUAL",
68                VisualMode::Line => "VISUAL LINE",
69                VisualMode::Block => "VISUAL BLOCK",
70            };
71            let pos = format!(
72                "{}:{} (abs {})",
73                self.cursor_absolute_line
74                    .saturating_sub(self.scrollback_len),
75                self.cursor_col,
76                self.cursor_absolute_line,
77            );
78            format!("-- {} -- {}", mode, pos)
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests;