use {
reovim_driver_command::{
ArgKind, ArgSpec, Command, CommandContext, CommandHandler, CommandResult,
},
reovim_driver_session::{BufferApi, SessionRuntime, TransitionContext, api::ModeApi},
reovim_kernel::api::v1::{CommandId, Position, RegisterContent},
};
fn get_cursor_position(runtime: &SessionRuntime<'_>) -> Option<Position> {
let window = runtime.windows().active()?;
Some(Position::new(window.cursor.line, window.cursor.column))
}
#[cfg_attr(coverage_nightly, coverage(off))]
fn set_cursor_position(runtime: &mut SessionRuntime<'_>, pos: Position) {
if let Some(window) = runtime.windows_mut().active_mut() {
window.cursor = pos.into();
}
}
use crate::{ids, modes::VimMode};
#[derive(Debug, Clone, Copy, Default)]
pub struct ChangeLine;
impl Command for ChangeLine {
fn id(&self) -> CommandId {
ids::CHANGE_LINE
}
fn description(&self) -> &'static str {
"Change current line"
}
fn args(&self) -> Vec<ArgSpec> {
vec![
ArgSpec::optional("count", ArgKind::Count, "Number of lines to change"),
ArgSpec::optional("register", ArgKind::Register, "Target register"),
]
}
}
impl CommandHandler for ChangeLine {
#[cfg_attr(coverage_nightly, coverage(off))]
fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
let Some(buffer_id) = args.buffer_id() else {
return CommandResult::error("No active buffer");
};
let count = args.count().unwrap_or(1);
let start_line = get_cursor_position(runtime).map_or(0, |p| p.line);
let line_count = runtime.buffer_line_count(buffer_id).unwrap_or(0);
if line_count == 0 {
runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
return CommandResult::Success;
}
let lines_to_change = count.min(line_count.saturating_sub(start_line));
if lines_to_change == 0 {
runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
return CommandResult::Success;
}
let mut deleted_text = String::new();
for i in 0..lines_to_change {
let line_idx = start_line + i;
if let Some(line) = runtime.buffer_line(buffer_id, line_idx) {
deleted_text.push_str(&line);
}
if i < lines_to_change - 1 {
deleted_text.push('\n');
}
}
deleted_text.push('\n');
let content = RegisterContent::linewise(deleted_text);
let register = args.register();
runtime.store_register_with_sync(register, content);
if lines_to_change == 1 {
let line_len = runtime.buffer_line_len(buffer_id, start_line).unwrap_or(0);
if line_len > 0 {
let delete_start = Position::new(start_line, 0);
let delete_end = Position::new(start_line, line_len);
runtime.delete_range(buffer_id, delete_start, delete_end);
set_cursor_position(runtime, Position::new(start_line, 0));
}
runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
return CommandResult::Success;
}
let last_changed_line = start_line + lines_to_change - 1;
let last_line_len = runtime
.buffer_line_len(buffer_id, last_changed_line)
.unwrap_or(0);
let end_line = start_line + lines_to_change;
let (delete_start, delete_end) = if end_line >= line_count {
(Position::new(start_line, 0), Position::new(last_changed_line, last_line_len))
} else {
(Position::new(start_line, 0), Position::new(end_line, 0))
};
runtime.delete_range(buffer_id, delete_start, delete_end);
set_cursor_position(runtime, Position::new(start_line, 0));
runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ChangeToEndOfLine;
impl Command for ChangeToEndOfLine {
fn id(&self) -> CommandId {
ids::CHANGE_TO_EOL
}
fn description(&self) -> &'static str {
"Change to end of line"
}
fn args(&self) -> Vec<ArgSpec> {
vec![ArgSpec::optional(
"register",
ArgKind::Register,
"Target register",
)]
}
}
impl CommandHandler for ChangeToEndOfLine {
#[cfg_attr(coverage_nightly, coverage(off))]
fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
let Some(buffer_id) = args.buffer_id() else {
return CommandResult::error("No active buffer");
};
let pos = get_cursor_position(runtime).unwrap_or_else(|| Position::new(0, 0));
let line_len = runtime.buffer_line_len(buffer_id, pos.line).unwrap_or(0);
if pos.column >= line_len {
runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
return CommandResult::Success;
}
let deleted_text = runtime
.buffer_line(buffer_id, pos.line)
.map(|line| line[pos.column..].to_string())
.unwrap_or_default();
let content = RegisterContent::characterwise(deleted_text);
let register = args.register();
runtime.store_register_with_sync(register, content);
let delete_end = Position::new(pos.line, line_len);
runtime.delete_range(buffer_id, pos, delete_end);
runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
CommandResult::Success
}
}