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;