reovim_module_vim/commands/
change.rs1use {
13 reovim_driver_command::{
14 ArgKind, ArgSpec, Command, CommandContext, CommandHandler, CommandResult,
15 },
16 reovim_driver_session::{BufferApi, SessionRuntime, TransitionContext, api::ModeApi},
17 reovim_kernel::api::v1::{CommandId, Position, RegisterContent},
18};
19
20fn get_cursor_position(runtime: &SessionRuntime<'_>) -> Option<Position> {
22 let window = runtime.windows().active()?;
23 Some(Position::new(window.cursor.line, window.cursor.column))
24}
25
26#[cfg_attr(coverage_nightly, coverage(off))]
28fn set_cursor_position(runtime: &mut SessionRuntime<'_>, pos: Position) {
29 if let Some(window) = runtime.windows_mut().active_mut() {
30 window.cursor = pos.into();
31 }
32}
33
34use crate::{ids, modes::VimMode};
35
36#[derive(Debug, Clone, Copy, Default)]
42pub struct ChangeLine;
43
44impl Command for ChangeLine {
45 fn id(&self) -> CommandId {
46 ids::CHANGE_LINE
47 }
48
49 fn description(&self) -> &'static str {
50 "Change current line"
51 }
52
53 fn args(&self) -> Vec<ArgSpec> {
54 vec![
55 ArgSpec::optional("count", ArgKind::Count, "Number of lines to change"),
56 ArgSpec::optional("register", ArgKind::Register, "Target register"),
57 ]
58 }
59}
60
61impl CommandHandler for ChangeLine {
62 #[cfg_attr(coverage_nightly, coverage(off))]
63 fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
64 let Some(buffer_id) = args.buffer_id() else {
65 return CommandResult::error("No active buffer");
66 };
67
68 let count = args.count().unwrap_or(1);
69 let start_line = get_cursor_position(runtime).map_or(0, |p| p.line);
70 let line_count = runtime.buffer_line_count(buffer_id).unwrap_or(0);
71
72 if line_count == 0 {
73 runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
75 return CommandResult::Success;
76 }
77
78 let lines_to_change = count.min(line_count.saturating_sub(start_line));
80 if lines_to_change == 0 {
81 runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
82 return CommandResult::Success;
83 }
84
85 let mut deleted_text = String::new();
87 for i in 0..lines_to_change {
88 let line_idx = start_line + i;
89 if let Some(line) = runtime.buffer_line(buffer_id, line_idx) {
90 deleted_text.push_str(&line);
91 }
92 if i < lines_to_change - 1 {
93 deleted_text.push('\n');
94 }
95 }
96 deleted_text.push('\n'); let content = RegisterContent::linewise(deleted_text);
100 let register = args.register();
101 runtime.store_register_with_sync(register, content);
102
103 if lines_to_change == 1 {
107 let line_len = runtime.buffer_line_len(buffer_id, start_line).unwrap_or(0);
109 if line_len > 0 {
110 let delete_start = Position::new(start_line, 0);
111 let delete_end = Position::new(start_line, line_len);
112 runtime.delete_range(buffer_id, delete_start, delete_end);
113 set_cursor_position(runtime, Position::new(start_line, 0));
114 }
115 runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
116 return CommandResult::Success;
117 }
118
119 let last_changed_line = start_line + lines_to_change - 1;
122 let last_line_len = runtime
123 .buffer_line_len(buffer_id, last_changed_line)
124 .unwrap_or(0);
125 let end_line = start_line + lines_to_change;
126
127 let (delete_start, delete_end) = if end_line >= line_count {
128 (Position::new(start_line, 0), Position::new(last_changed_line, last_line_len))
130 } else {
131 (Position::new(start_line, 0), Position::new(end_line, 0))
133 };
134
135 runtime.delete_range(buffer_id, delete_start, delete_end);
136 set_cursor_position(runtime, Position::new(start_line, 0));
137 runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
138
139 CommandResult::Success
140 }
141}
142
143#[derive(Debug, Clone, Copy, Default)]
148pub struct ChangeToEndOfLine;
149
150impl Command for ChangeToEndOfLine {
151 fn id(&self) -> CommandId {
152 ids::CHANGE_TO_EOL
153 }
154
155 fn description(&self) -> &'static str {
156 "Change to end of line"
157 }
158
159 fn args(&self) -> Vec<ArgSpec> {
160 vec![ArgSpec::optional(
161 "register",
162 ArgKind::Register,
163 "Target register",
164 )]
165 }
166}
167
168impl CommandHandler for ChangeToEndOfLine {
169 #[cfg_attr(coverage_nightly, coverage(off))]
170 fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
171 let Some(buffer_id) = args.buffer_id() else {
172 return CommandResult::error("No active buffer");
173 };
174
175 let pos = get_cursor_position(runtime).unwrap_or_else(|| Position::new(0, 0));
176 let line_len = runtime.buffer_line_len(buffer_id, pos.line).unwrap_or(0);
177
178 if pos.column >= line_len {
180 runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
181 return CommandResult::Success;
182 }
183
184 let deleted_text = runtime
186 .buffer_line(buffer_id, pos.line)
187 .map(|line| line[pos.column..].to_string())
188 .unwrap_or_default();
189
190 let content = RegisterContent::characterwise(deleted_text);
192 let register = args.register();
193 runtime.store_register_with_sync(register, content);
194
195 let delete_end = Position::new(pos.line, line_len);
197 runtime.delete_range(buffer_id, pos, delete_end);
198
199 runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
200
201 CommandResult::Success
202 }
203}