use {
reovim_driver_command::{Command, CommandContext, CommandHandler, CommandResult},
reovim_driver_session::{BufferApi, SessionRuntime, TransitionContext, api::ModeApi},
reovim_driver_undo::{UndoKey, UndoProviderRegistry},
reovim_kernel::api::v1::{BufferId, CommandId, OptionScopeId, Position},
};
use {
crate::{ids, modes::VimMode},
reovim_module_editor::command::get_line_indent,
};
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();
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
fn begin_insert_batch(runtime: &SessionRuntime<'_>, buffer_id: BufferId) {
if let Some(pos) = get_cursor_position(runtime)
&& let Some(undo_registry) = runtime.kernel().services.get::<UndoProviderRegistry>()
&& let Some(undo_provider) = undo_registry.get(&UndoKey::Buffer)
{
undo_provider.begin_batch(buffer_id, pos);
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EnterInsertFirstNonBlank;
impl Command for EnterInsertFirstNonBlank {
fn id(&self) -> CommandId {
ids::ENTER_INSERT_BOL
}
fn description(&self) -> &'static str {
"Enter insert mode at first non-blank character"
}
}
impl CommandHandler for EnterInsertFirstNonBlank {
fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
if let Some(buffer_id) = args.buffer_id() {
if let Some(pos) = get_cursor_position(runtime) {
let first_non_blank = runtime
.buffer_line(buffer_id, pos.line)
.map_or(0, |line| line.chars().position(|c| !c.is_whitespace()).unwrap_or(0));
set_cursor_position(runtime, Position::new(pos.line, first_non_blank));
}
begin_insert_batch(runtime, buffer_id);
}
runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EnterInsertEndOfLine;
impl Command for EnterInsertEndOfLine {
fn id(&self) -> CommandId {
ids::ENTER_INSERT_EOL
}
fn description(&self) -> &'static str {
"Enter insert mode at end of line"
}
}
impl CommandHandler for EnterInsertEndOfLine {
fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
if let Some(buffer_id) = args.buffer_id() {
if let Some(pos) = get_cursor_position(runtime) {
let line_len = runtime.buffer_line_len(buffer_id, pos.line).unwrap_or(0);
set_cursor_position(runtime, Position::new(pos.line, line_len));
}
begin_insert_batch(runtime, buffer_id);
}
runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct OpenLineBelow;
impl Command for OpenLineBelow {
fn id(&self) -> CommandId {
ids::OPEN_LINE_BELOW
}
fn description(&self) -> &'static str {
"Open line below and enter insert mode"
}
}
impl CommandHandler for OpenLineBelow {
fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
let Some(buffer_id) = args.buffer_id() else {
return CommandResult::error("No active buffer");
};
let autoindent = runtime
.kernel()
.options
.get("autoindent", OptionScopeId::Buffer(buffer_id))
.and_then(|v| v.as_bool())
.unwrap_or(true);
let Some(pos) = get_cursor_position(runtime) else {
return CommandResult::error("Failed to get buffer position");
};
let indent = if autoindent {
runtime
.buffer_line(buffer_id, pos.line)
.map(|line| get_line_indent(&line).to_owned())
.unwrap_or_default()
} else {
String::new()
};
let line_len = runtime.buffer_line_len(buffer_id, pos.line).unwrap_or(0);
let insert_pos = Position::new(pos.line, line_len);
let insert_text = format!("\n{indent}");
runtime.insert_text(buffer_id, insert_pos, &insert_text);
let indent_len = indent.chars().count();
set_cursor_position(runtime, Position::new(pos.line + 1, indent_len));
begin_insert_batch(runtime, buffer_id);
runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct OpenLineAbove;
impl Command for OpenLineAbove {
fn id(&self) -> CommandId {
ids::OPEN_LINE_ABOVE
}
fn description(&self) -> &'static str {
"Open line above and enter insert mode"
}
}
impl CommandHandler for OpenLineAbove {
fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
let Some(buffer_id) = args.buffer_id() else {
return CommandResult::error("No active buffer");
};
let autoindent = runtime
.kernel()
.options
.get("autoindent", OptionScopeId::Buffer(buffer_id))
.and_then(|v| v.as_bool())
.unwrap_or(true);
let Some(pos) = get_cursor_position(runtime) else {
return CommandResult::error("Failed to get buffer position");
};
let indent = if autoindent {
runtime
.buffer_line(buffer_id, pos.line)
.map(|line| get_line_indent(&line).to_owned())
.unwrap_or_default()
} else {
String::new()
};
let insert_pos = Position::new(pos.line, 0);
let insert_text = format!("{indent}\n");
runtime.insert_text(buffer_id, insert_pos, &insert_text);
let indent_len = indent.chars().count();
set_cursor_position(runtime, Position::new(pos.line, indent_len));
begin_insert_batch(runtime, buffer_id);
runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
CommandResult::Success
}
}
#[cfg(test)]
#[allow(clippy::uninlined_format_args, clippy::significant_drop_tightening)]
#[path = "tests/mode_entry.rs"]
mod tests;