reovim_module_vim/operators/
change.rs1use {
6 reovim_driver_undo::{UndoKey, UndoProviderRegistry},
7 reovim_kernel::api::v1::{Edit, RegisterContent},
8};
9
10use super::{Operator, OperatorContext, OperatorError, Range, char_col_to_byte, registers};
11
12#[derive(Debug, Clone, Copy)]
30pub struct ChangeOperator;
31
32impl Operator for ChangeOperator {
33 fn id(&self) -> &'static str {
34 "change"
35 }
36
37 #[allow(clippy::option_if_let_else)]
38 #[cfg_attr(coverage_nightly, coverage(off))]
39 fn execute(&self, ctx: &mut OperatorContext<'_>, range: Range) -> Result<(), OperatorError> {
40 let buffer_arc = ctx
42 .kernel
43 .buffers
44 .get(ctx.buffer_id)
45 .ok_or(OperatorError::BufferNotFound(ctx.buffer_id))?;
46
47 let mut buffer = buffer_arc.write();
48
49 let cursor_before = ctx.cursor_position;
51
52 let start = range.start;
54 let end = range.end;
55
56 let mut deleted_text = String::new();
58 let line_count = buffer.line_count();
59
60 let delete_pos;
62
63 if range.is_linewise {
64 let clamped_end = end.line.min(line_count.saturating_sub(1));
68
69 for line_idx in start.line..=clamped_end {
70 if let Some(line) = buffer.line(line_idx) {
71 deleted_text.push_str(line);
72 deleted_text.push('\n');
73 }
74 }
75
76 let delete_start = reovim_kernel::api::v1::Position::new(start.line, 0);
82 let delete_end = if clamped_end + 1 < line_count {
83 let end_line_char_len = buffer.line(clamped_end).map_or(0, |l| l.chars().count());
86 reovim_kernel::api::v1::Position::new(clamped_end, end_line_char_len)
87 } else {
88 let last_line_char_len = buffer.line(clamped_end).map_or(0, |l| l.chars().count());
90 reovim_kernel::api::v1::Position::new(clamped_end, last_line_char_len)
91 };
92
93 delete_pos = delete_start;
94 buffer.delete_range(delete_start, delete_end);
95 } else if start.line == end.line {
96 if let Some(line) = buffer.line(start.line) {
98 let char_len = line.chars().count();
99 let start_col = start.column.min(char_len);
100 let end_col = end.column.min(char_len);
101 if start_col < end_col {
102 let start_byte = char_col_to_byte(line, start_col);
103 let end_byte = char_col_to_byte(line, end_col);
104 deleted_text.push_str(&line[start_byte..end_byte]);
105 }
106 }
107 delete_pos = start;
108 buffer.delete_range(start, end);
109 } else {
110 for line_idx in start.line..=end.line {
112 if let Some(line) = buffer.line(line_idx) {
113 if line_idx == start.line {
114 let char_len = line.chars().count();
115 let start_col = start.column.min(char_len);
116 let start_byte = char_col_to_byte(line, start_col);
117 deleted_text.push_str(&line[start_byte..]);
118 deleted_text.push('\n');
119 } else if line_idx == end.line {
120 let char_len = line.chars().count();
121 let end_col = end.column.min(char_len);
122 let end_byte = char_col_to_byte(line, end_col);
123 deleted_text.push_str(&line[..end_byte]);
124 } else {
125 deleted_text.push_str(line);
126 deleted_text.push('\n');
127 }
128 }
129 }
130 delete_pos = start;
131 buffer.delete_range(start, end);
132 }
133
134 let cursor_after = delete_pos;
136 ctx.cursor_after = Some(cursor_after);
137
138 drop(buffer);
139
140 if !deleted_text.is_empty()
142 && let Some(undo_registry) = ctx.kernel.services.get::<UndoProviderRegistry>()
143 && let Some(undo_provider) = undo_registry.get(&UndoKey::Buffer)
144 {
145 let edit = Edit::Delete {
146 position: delete_pos,
147 text: deleted_text.clone(),
148 };
149 undo_provider.record(ctx.buffer_id, vec![edit], cursor_before, cursor_after);
150 }
151
152 let content = if range.is_linewise {
154 RegisterContent::linewise(deleted_text)
155 } else {
156 RegisterContent::characterwise(deleted_text)
157 };
158
159 registers::store_and_sync(ctx.kernel, ctx.registers, ctx.register, &content);
160 registers::push_to_history(ctx.clipboard_history, &content);
161
162 Ok(())
166 }
167
168 fn is_linewise(&self) -> bool {
169 false }
171
172 fn is_text_modifying(&self) -> bool {
173 true
174 }
175}