Skip to main content

reovim_module_vim/operators/
yank.rs

1//! Yank operator.
2//!
3//! Reference: lib/core/src/command/builtin/operator.rs (concept-extraction, not migration)
4
5use reovim_kernel::api::v1::RegisterContent;
6
7use super::{Operator, OperatorContext, OperatorError, Range, char_col_to_byte, registers};
8
9/// Yank operator - copies text to register.
10///
11/// Behavior:
12/// - Copies text in the given range to register
13/// - Does NOT modify the buffer
14/// - Linewise if the motion was linewise
15///
16/// # Example
17///
18/// ```ignore
19/// let yank = YankOperator;
20/// yank.execute(&mut ctx, range)?;
21/// ```
22#[derive(Debug, Clone, Copy)]
23pub struct YankOperator;
24
25impl Operator for YankOperator {
26    fn id(&self) -> &'static str {
27        "yank"
28    }
29
30    #[cfg_attr(coverage_nightly, coverage(off))]
31    fn execute(&self, ctx: &mut OperatorContext<'_>, range: Range) -> Result<(), OperatorError> {
32        // Get the buffer via kernel's buffer manager
33        let buffer_arc = ctx
34            .kernel
35            .buffers
36            .get(ctx.buffer_id)
37            .ok_or(OperatorError::BufferNotFound(ctx.buffer_id))?;
38
39        let buffer = buffer_arc.read();
40
41        // Build yanked text from lines
42        let start = range.start;
43        let end = range.end;
44        let mut yanked_text = String::new();
45
46        if range.is_linewise {
47            // Linewise yank: copy entire lines from start.line to end.line (inclusive)
48            // Ignore column values - always yank full lines
49            // Clamp end.line to last valid line to handle counts exceeding buffer
50            let line_count = buffer.line_count();
51            let clamped_end = end.line.min(line_count.saturating_sub(1));
52
53            for line_idx in start.line..=clamped_end {
54                if let Some(line) = buffer.line(line_idx) {
55                    yanked_text.push_str(line);
56                    yanked_text.push('\n');
57                }
58            }
59        } else if start.line == end.line {
60            // Single line characterwise yank
61            if let Some(line) = buffer.line(start.line) {
62                let char_len = line.chars().count();
63                let start_col = start.column.min(char_len);
64                let end_col = end.column.min(char_len);
65                if start_col < end_col {
66                    let start_byte = char_col_to_byte(line, start_col);
67                    let end_byte = char_col_to_byte(line, end_col);
68                    yanked_text.push_str(&line[start_byte..end_byte]);
69                }
70            }
71        } else {
72            // Multi-line characterwise yank
73            for line_idx in start.line..=end.line {
74                if let Some(line) = buffer.line(line_idx) {
75                    if line_idx == start.line {
76                        let char_len = line.chars().count();
77                        let start_col = start.column.min(char_len);
78                        let start_byte = char_col_to_byte(line, start_col);
79                        yanked_text.push_str(&line[start_byte..]);
80                        yanked_text.push('\n');
81                    } else if line_idx == end.line {
82                        let char_len = line.chars().count();
83                        let end_col = end.column.min(char_len);
84                        let end_byte = char_col_to_byte(line, end_col);
85                        yanked_text.push_str(&line[..end_byte]);
86                    } else {
87                        yanked_text.push_str(line);
88                        yanked_text.push('\n');
89                    }
90                }
91            }
92        }
93
94        // Release the buffer lock before register operations
95        drop(buffer);
96
97        // Store in register - linewise if the range was linewise
98        let content = if range.is_linewise {
99            RegisterContent::linewise(yanked_text)
100        } else {
101            RegisterContent::characterwise(yanked_text)
102        };
103
104        // Store to the specified register (handles +, *, a-z, etc.)
105        // Uses ClipboardProvider for +/* registers, per-client RegisterBank for others (#515)
106        registers::store_and_sync(ctx.kernel, ctx.registers, ctx.register, &content);
107
108        // Push to per-client history for numbered registers (0-9) (#515)
109        // This happens on every yank regardless of target register
110        registers::push_to_history(ctx.clipboard_history, &content);
111
112        Ok(())
113    }
114
115    fn is_linewise(&self) -> bool {
116        false // Default; actual linewise-ness is determined by motion
117    }
118
119    fn is_text_modifying(&self) -> bool {
120        false // Yank does NOT modify text
121    }
122}