#![allow(clippy::significant_drop_tightening, clippy::uninlined_format_args)]
use {
super::super::*,
crate::modes::VimMode,
reovim_module_cmdline::{CmdlinePrompt, CmdlineState},
};
use {
crate::ids,
reovim_driver_command::{Command, CommandContext, CommandHandler, CommandResult},
reovim_driver_session::{
ClientId, ExtensionMap, Session, SessionRuntime, WindowLayout,
api::{CommandExecutor, CommandHandle},
testing::StubExecutor,
},
reovim_kernel::{
api::{
ModeStack,
v1::{
Buffer, BufferId, CommandId, HistoryRing, Jumplist, KernelContext, MarkBank,
Position, RegisterBank, RwLock,
},
},
testing::create_test_context,
},
std::{collections::HashMap, sync::Arc},
};
use reovim_driver_session::api::ModeApi;
struct TestExecutor {
handlers: HashMap<CommandId, Arc<dyn CommandHandler>>,
}
impl TestExecutor {
fn new() -> Self {
Self {
handlers: HashMap::new(),
}
}
fn register(&mut self, handler: Arc<dyn CommandHandler>) {
self.handlers.insert(handler.id(), handler);
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl CommandExecutor for TestExecutor {
fn get_handle(&self, id: &CommandId) -> Option<std::sync::Arc<dyn CommandHandle>> {
self.handlers.get(id).map(|h| {
let handler = Arc::clone(h);
Arc::new(TestHandleBridge(handler)) as Arc<dyn CommandHandle>
})
}
}
struct TestHandleBridge(Arc<dyn CommandHandler>);
#[cfg_attr(coverage_nightly, coverage(off))]
impl CommandHandle for TestHandleBridge {
fn execute(
&self,
runtime: &mut SessionRuntime<'_>,
ctx: &reovim_driver_command::CommandContext,
) -> CommandResult {
self.0.execute(runtime, ctx)
}
}
struct TestState {
session: Session,
mode_stack: ModeStack,
windows: WindowLayout,
extensions: ExtensionMap,
compositor: Option<Box<dyn reovim_driver_layout::RootCompositor>>,
tabs: reovim_driver_session::TabPageSet,
registers: RegisterBank,
clipboard_history: HistoryRing,
local_marks: MarkBank,
jumplist: Jumplist,
active_buffer: Option<BufferId>,
terminal_size: (u16, u16),
}
impl TestState {
fn with_buffer(buffer_id: Option<BufferId>) -> Self {
let home_mode = VimMode::NORMAL_ID;
let session = Session::new(ClientId::new(1), home_mode.clone());
let mode_stack = ModeStack::new(home_mode);
let mut windows = WindowLayout::empty();
let extensions = ExtensionMap::new();
let mut window = reovim_driver_session::Window::new();
if let Some(buffer_id) = buffer_id {
window.buffer_id = Some(buffer_id);
}
windows.add(window);
Self {
session,
mode_stack,
windows,
extensions,
compositor: None,
tabs: reovim_driver_session::TabPageSet::new(),
registers: RegisterBank::new(),
clipboard_history: HistoryRing::new(),
local_marks: MarkBank::new(),
jumplist: Jumplist::new(),
active_buffer: None,
terminal_size: (80, 24),
}
}
fn runtime<'a>(&'a mut self, kernel: &'a KernelContext) -> SessionRuntime<'a> {
self.runtime_with_executor(kernel, &StubExecutor)
}
fn runtime_with_executor<'a>(
&'a mut self,
kernel: &'a KernelContext,
executor: &'a dyn CommandExecutor,
) -> SessionRuntime<'a> {
SessionRuntime::new(
&mut self.session,
reovim_driver_session::ClientContext {
mode_stack: &mut self.mode_stack,
windows: &mut self.windows,
extensions: &mut self.extensions,
compositor: &mut self.compositor,
tabs: &mut self.tabs,
registers: &mut self.registers,
clipboard_history: &mut self.clipboard_history,
local_marks: &mut self.local_marks,
jumplist: &mut self.jumplist,
active_buffer: &mut self.active_buffer,
terminal_size: &mut self.terminal_size,
},
kernel,
executor,
)
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_mode_id() {
let cmd = EnterInsertMode;
assert_eq!(cmd.id(), ids::ENTER_INSERT);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_mode_description() {
let cmd = EnterInsertMode;
assert!(cmd.description().contains("insert"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_mode_append_id() {
let cmd = EnterInsertModeAppend;
assert_eq!(cmd.id(), ids::ENTER_INSERT_AFTER);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_mode_append_description() {
let cmd = EnterInsertModeAppend;
assert!(cmd.description().contains("append"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_to_normal_id() {
let cmd = ExitToNormal;
assert_eq!(cmd.id(), ids::EXIT_INSERT);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_to_normal_description() {
let cmd = ExitToNormal;
assert!(cmd.description().contains("normal"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_window_mode_id() {
let cmd = EnterWindowMode;
assert_eq!(cmd.id(), ids::ENTER_WINDOW_MODE);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_window_mode_description() {
let cmd = EnterWindowMode;
assert!(cmd.description().contains("window"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cancel_to_normal_id() {
let cmd = CancelToNormal;
assert_eq!(cmd.id(), ids::CANCEL_TO_NORMAL);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cancel_to_normal_description() {
let cmd = CancelToNormal;
assert!(cmd.description().contains("normal"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_commandline_mode_id() {
let cmd = EnterCommandLineMode;
assert_eq!(cmd.id(), ids::ENTER_COMMANDLINE);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_commandline_mode_description() {
let cmd = EnterCommandLineMode;
assert!(cmd.description().contains("command-line"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_mode_id() {
let cmd = ExitCommandLineMode;
assert_eq!(cmd.id(), ids::EXIT_COMMANDLINE);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_mode_description() {
let cmd = ExitCommandLineMode;
assert!(cmd.description().contains("command-line"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cancel_commandline_mode_id() {
let cmd = CancelCommandLineMode;
assert_eq!(cmd.id(), ids::CANCEL_COMMANDLINE);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cancel_commandline_mode_description() {
let cmd = CancelCommandLineMode;
assert!(cmd.description().contains("Cancel"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_search_forward_id() {
let cmd = EnterSearchForward;
assert_eq!(cmd.id(), ids::ENTER_SEARCH_FORWARD);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_search_forward_description() {
let cmd = EnterSearchForward;
assert!(cmd.description().contains("search"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_search_backward_id() {
let cmd = EnterSearchBackward;
assert_eq!(cmd.id(), ids::ENTER_SEARCH_BACKWARD);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_search_backward_description() {
let cmd = EnterSearchBackward;
assert!(cmd.description().contains("search"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_all_mode_commands_debug() {
let debug_insert = format!("{:?}", EnterInsertMode);
assert!(debug_insert.contains("EnterInsertMode"));
let debug_append = format!("{:?}", EnterInsertModeAppend);
assert!(debug_append.contains("EnterInsertModeAppend"));
let debug_exit = format!("{:?}", ExitToNormal);
assert!(debug_exit.contains("ExitToNormal"));
let debug_window = format!("{:?}", EnterWindowMode);
assert!(debug_window.contains("EnterWindowMode"));
let debug_cancel = format!("{:?}", CancelToNormal);
assert!(debug_cancel.contains("CancelToNormal"));
let debug_cmdline = format!("{:?}", EnterCommandLineMode);
assert!(debug_cmdline.contains("EnterCommandLineMode"));
let debug_exit_cmd = format!("{:?}", ExitCommandLineMode);
assert!(debug_exit_cmd.contains("ExitCommandLineMode"));
let debug_cancel_cmd = format!("{:?}", CancelCommandLineMode);
assert!(debug_cancel_cmd.contains("CancelCommandLineMode"));
let debug_search_forward = format!("{:?}", EnterSearchForward);
assert!(debug_search_forward.contains("EnterSearchForward"));
let debug_search_backward = format!("{:?}", EnterSearchBackward);
assert!(debug_search_backward.contains("EnterSearchBackward"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_all_mode_commands_default() {
let _ = EnterInsertMode;
let _ = EnterInsertModeAppend;
let _ = ExitToNormal;
let _ = EnterWindowMode;
let _ = CancelToNormal;
let _ = EnterCommandLineMode;
let _ = ExitCommandLineMode;
let _ = CancelCommandLineMode;
let _ = EnterSearchForward;
let _ = EnterSearchBackward;
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_mode_execute() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
let result = EnterInsertMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::INSERT_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_mode_without_buffer() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
let result = EnterInsertMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::INSERT_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_mode_append_execute() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
let result = EnterInsertModeAppend.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::INSERT_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_mode_append_moves_cursor() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
if let Some(w) = state.windows.active_mut() {
w.cursor = Position::new(0, 2).into();
}
let mut runtime = state.runtime(&ctx);
let result = EnterInsertModeAppend.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let window = runtime.windows().active().unwrap();
assert_eq!(window.cursor.column, 3);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_to_normal_execute() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
if let Some(w) = state.windows.active_mut() {
w.cursor = Position::new(0, 3).into();
}
let mut runtime = state.runtime(&ctx);
let result = ExitToNormal.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::NORMAL_ID);
let window = runtime.windows().active().unwrap();
assert_eq!(window.cursor.column, 2);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_to_normal_cursor_at_col_zero() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
let result = ExitToNormal.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let window = runtime.windows().active().unwrap();
assert_eq!(window.cursor.column, 0);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_window_mode_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
let result = EnterWindowMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::WINDOW_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cancel_to_normal_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
let result = CancelToNormal.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::NORMAL_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_commandline_mode_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
let result = EnterCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::COMMANDLINE_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_mode_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::NORMAL_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cancel_commandline_mode_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
let result = CancelCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::NORMAL_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_search_forward_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
let result = EnterSearchForward.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::COMMANDLINE_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_search_backward_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
let result = EnterSearchBackward.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::COMMANDLINE_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_to_normal_without_buffer() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
let result = ExitToNormal.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::NORMAL_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_append_at_end_of_line() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
if let Some(w) = state.windows.active_mut() {
w.cursor = Position::new(0, 4).into();
}
let mut runtime = state.runtime(&ctx);
let result = EnterInsertModeAppend.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let window = runtime.windows().active().unwrap();
assert_eq!(window.cursor.column, 5);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_append_without_buffer() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
let result = EnterInsertModeAppend.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::INSERT_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_mode_append_at_start_of_line() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
let result = EnterInsertModeAppend.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let window = runtime.windows().active().unwrap();
assert_eq!(window.cursor.column, 1); }
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_mode_append_empty_line() {
let ctx = create_test_context();
let buffer = Buffer::from_string("");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
let result = EnterInsertModeAppend.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let window = runtime.windows().active().unwrap();
assert_eq!(window.cursor.column, 0);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_to_normal_from_middle_of_line() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello world");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
if let Some(w) = state.windows.active_mut() {
w.cursor = Position::new(0, 5).into();
}
let mut runtime = state.runtime(&ctx);
let result = ExitToNormal.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::NORMAL_ID);
let window = runtime.windows().active().unwrap();
assert_eq!(window.cursor.column, 4); }
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_to_normal_from_column_one() {
let ctx = create_test_context();
let buffer = Buffer::from_string("ab");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
if let Some(w) = state.windows.active_mut() {
w.cursor = Position::new(0, 1).into();
}
let mut runtime = state.runtime(&ctx);
let result = ExitToNormal.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let window = runtime.windows().active().unwrap();
assert_eq!(window.cursor.column, 0);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_mode_returns_to_normal() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
EnterCommandLineMode.execute(&mut runtime, &args);
assert_eq!(runtime.current_mode(), &VimMode::COMMANDLINE_ID);
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::NORMAL_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cancel_commandline_mode_returns_to_normal() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
EnterCommandLineMode.execute(&mut runtime, &args);
let result = CancelCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::NORMAL_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_mode_on_second_line() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello\nworld");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
if let Some(w) = state.windows.active_mut() {
w.cursor = Position::new(1, 2).into();
}
let mut runtime = state.runtime(&ctx);
let result = EnterInsertMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::INSERT_ID);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_mode_append_on_second_line() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello\nworld");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
if let Some(w) = state.windows.active_mut() {
w.cursor = Position::new(1, 2).into();
}
let mut runtime = state.runtime(&ctx);
let result = EnterInsertModeAppend.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let window = runtime.windows().active().unwrap();
assert_eq!(window.cursor.line, 1);
assert_eq!(window.cursor.column, 3); }
use {
reovim_driver_undo::{UndoKey, UndoPersistError, UndoProvider, UndoProviderRegistry},
reovim_driver_vfs::VfsDriver,
reovim_kernel::api::v1::{Edit, UndoResult, UndoTree},
};
struct MockUndoProvider {
batch_begins: RwLock<Vec<(BufferId, Position)>>,
batch_ends: RwLock<Vec<(BufferId, Position)>>,
records: RwLock<Vec<BufferId>>,
}
impl MockUndoProvider {
fn new() -> Self {
Self {
batch_begins: RwLock::new(Vec::new()),
batch_ends: RwLock::new(Vec::new()),
records: RwLock::new(Vec::new()),
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl UndoProvider for MockUndoProvider {
fn undo(&self, _: BufferId) -> Option<UndoResult> {
None
}
fn redo(&self, _: BufferId) -> Option<UndoResult> {
None
}
fn redo_branch(&self, _: BufferId, _: usize) -> Option<UndoResult> {
None
}
fn record(&self, buffer_id: BufferId, _: Vec<Edit>, _: Position, _: Position) {
self.records.write().push(buffer_id);
}
fn has_history(&self, _: BufferId) -> bool {
false
}
fn remove(&self, _: BufferId) {}
fn buffer_count(&self) -> usize {
0
}
fn get_tree(&self, _: BufferId) -> Option<UndoTree> {
None
}
fn begin_batch(&self, buffer_id: BufferId, cursor_before: Position) {
self.batch_begins.write().push((buffer_id, cursor_before));
}
fn end_batch(&self, buffer_id: BufferId, cursor_after: Position) {
self.batch_ends.write().push((buffer_id, cursor_after));
}
fn is_batching(&self, _: BufferId) -> bool {
false
}
fn persist(&self, _: BufferId, _: &str, _: &dyn VfsDriver) -> Result<(), UndoPersistError> {
Ok(())
}
fn load(&self, _: BufferId, _: &str, _: &dyn VfsDriver) -> Result<bool, UndoPersistError> {
Ok(false)
}
}
fn create_test_context_with_undo() -> (KernelContext, Arc<MockUndoProvider>) {
let ctx = create_test_context();
let mock_undo = Arc::new(MockUndoProvider::new());
let undo_registry = Arc::new(UndoProviderRegistry::new());
undo_registry.register(UndoKey::Buffer, mock_undo.clone() as Arc<dyn UndoProvider>);
ctx.services.register(undo_registry);
(ctx, mock_undo)
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_mode_calls_begin_batch() {
let (ctx, mock_undo) = create_test_context_with_undo();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
let result = EnterInsertMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let begins = mock_undo.batch_begins.read();
assert_eq!(begins.len(), 1);
assert_eq!(begins[0].0, buffer_id);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_append_calls_begin_batch() {
let (ctx, mock_undo) = create_test_context_with_undo();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
let result = EnterInsertModeAppend.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let begins = mock_undo.batch_begins.read();
assert_eq!(begins.len(), 1);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_to_normal_calls_end_batch() {
let (ctx, mock_undo) = create_test_context_with_undo();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
if let Some(w) = state.windows.active_mut() {
w.cursor = Position::new(0, 3).into();
}
let mut runtime = state.runtime(&ctx);
let result = ExitToNormal.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let ends = mock_undo.batch_ends.read();
assert_eq!(ends.len(), 1);
assert_eq!(ends[0].0, buffer_id);
}
use reovim_driver_session::api::ExtensionApi;
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_enter_insert_mode_clears_insert_buffer() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
{
let vim = runtime.ext_mut::<crate::VimSessionState>();
vim.insert_buffer.push_str("previous text");
}
EnterInsertMode.execute(&mut runtime, &args);
let vim = runtime.ext_mut::<crate::VimSessionState>();
assert!(vim.insert_buffer.is_empty());
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_to_normal_records_dot_repeat_for_insert() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
{
let vim = runtime.ext_mut::<crate::VimSessionState>();
vim.insert_buffer.push_str("world");
}
ExitToNormal.execute(&mut runtime, &args);
let vim = runtime.ext_mut::<crate::VimSessionState>();
assert!(vim.last_change.is_some());
let last_change = vim.last_change.as_ref().unwrap();
match &last_change.change_type {
crate::session_state::ChangeType::Insert { text } => {
assert_eq!(text, "world");
}
_ => panic!("Expected Insert change type"),
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_to_normal_does_not_record_empty_insert() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
ExitToNormal.execute(&mut runtime, &args);
let vim = runtime.ext_mut::<crate::VimSessionState>();
assert!(vim.last_change.is_none());
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_mode_search_forward_with_pending() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello world hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
{
use reovim_driver_session::api::SearchState;
runtime
.ext_mut::<SearchState>()
.start_pending_search(reovim_driver_search::Direction::Forward);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchForward);
for ch in "world".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.current_mode(), &VimMode::NORMAL_ID);
let search_state = runtime.ext_mut::<reovim_driver_session::api::SearchState>();
assert!(search_state.pattern_for_repeat().is_some());
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_mode_search_backward_with_pending() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello world hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
{
use reovim_driver_session::api::SearchState;
runtime
.ext_mut::<SearchState>()
.start_pending_search(reovim_driver_search::Direction::Backward);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchBackward);
for ch in "hello".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_mode_search_empty_cmdline() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
{
use reovim_driver_session::api::SearchState;
runtime
.ext_mut::<SearchState>()
.start_pending_search(reovim_driver_search::Direction::Forward);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchForward);
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_mode_command_with_input() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
for ch in "w".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_mode_command_empty_input() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_search_no_buffer_id() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
{
use reovim_driver_session::api::SearchState;
runtime
.ext_mut::<SearchState>()
.start_pending_search(reovim_driver_search::Direction::Forward);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchForward);
for ch in "test".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_search_no_pending() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchForward);
for ch in "test".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
use reovim_driver_search::{
SearchError, SearchKey, SearchMatch, SearchProvider, SearchProviderRegistry,
};
struct MockSearchProvider {
result: RwLock<Option<Result<Option<SearchMatch>, SearchError>>>,
}
impl MockSearchProvider {
fn with_match(m: SearchMatch) -> Self {
Self {
result: RwLock::new(Some(Ok(Some(m)))),
}
}
fn with_no_match() -> Self {
Self {
result: RwLock::new(Some(Ok(None))),
}
}
fn with_error() -> Self {
Self {
result: RwLock::new(Some(Err(SearchError::InvalidPattern("test".to_string())))),
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl SearchProvider for MockSearchProvider {
fn find_next(
&self,
_buffer: &Buffer,
_cursor: Position,
_pattern: &str,
_direction: reovim_driver_search::Direction,
_wrap: bool,
) -> Result<Option<SearchMatch>, SearchError> {
self.result.read().clone().unwrap_or(Ok(None))
}
fn find_all(&self, _buffer: &Buffer, _pattern: &str) -> Result<Vec<SearchMatch>, SearchError> {
Ok(vec![])
}
fn word_at_cursor(&self, _buffer: &Buffer, _cursor: Position) -> Option<String> {
None
}
}
fn create_test_context_with_search(provider: Arc<dyn SearchProvider>) -> KernelContext {
let ctx = create_test_context();
let search_registry = Arc::new(SearchProviderRegistry::new());
search_registry.register(SearchKey::Regex, provider);
ctx.services.register(search_registry);
ctx
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_search_with_match_found() {
let mock = Arc::new(MockSearchProvider::with_match(SearchMatch {
start: Position::new(0, 6),
end: Position::new(0, 11),
}));
let ctx = create_test_context_with_search(mock);
let buffer = Buffer::from_string("hello world hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
{
runtime
.ext_mut::<reovim_driver_session::api::SearchState>()
.start_pending_search(reovim_driver_search::Direction::Forward);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchForward);
for ch in "world".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let window = runtime.windows().active().unwrap();
assert_eq!(window.cursor.line, 0);
assert_eq!(window.cursor.column, 6);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_search_no_match() {
let mock = Arc::new(MockSearchProvider::with_no_match());
let ctx = create_test_context_with_search(mock);
let buffer = Buffer::from_string("hello world");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
{
runtime
.ext_mut::<reovim_driver_session::api::SearchState>()
.start_pending_search(reovim_driver_search::Direction::Forward);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchForward);
for ch in "zzz".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let window = runtime.windows().active().unwrap();
assert_eq!(window.cursor.line, 0);
assert_eq!(window.cursor.column, 0);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_search_invalid_pattern() {
let mock = Arc::new(MockSearchProvider::with_error());
let ctx = create_test_context_with_search(mock);
let buffer = Buffer::from_string("hello world");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
{
runtime
.ext_mut::<reovim_driver_session::api::SearchState>()
.start_pending_search(reovim_driver_search::Direction::Forward);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchForward);
for ch in "[invalid".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let window = runtime.windows().active().unwrap();
assert_eq!(window.cursor.line, 0);
assert_eq!(window.cursor.column, 0);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_search_backward_with_match() {
let mock = Arc::new(MockSearchProvider::with_match(SearchMatch {
start: Position::new(0, 0),
end: Position::new(0, 5),
}));
let ctx = create_test_context_with_search(mock);
let buffer = Buffer::from_string("hello world");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
if let Some(w) = state.windows.active_mut() {
w.cursor = Position::new(0, 8).into();
}
let mut runtime = state.runtime(&ctx);
{
runtime
.ext_mut::<reovim_driver_session::api::SearchState>()
.start_pending_search(reovim_driver_search::Direction::Backward);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchBackward);
for ch in "hello".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let window = runtime.windows().active().unwrap();
assert_eq!(window.cursor.line, 0);
assert_eq!(window.cursor.column, 0);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_search_no_cursor_position() {
let mock = Arc::new(MockSearchProvider::with_no_match());
let ctx = create_test_context_with_search(mock);
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let home_mode = VimMode::NORMAL_ID;
let mut session = Session::new(ClientId::new(1), home_mode.clone());
let mut mode_stack = ModeStack::new(home_mode);
let mut windows = WindowLayout::empty();
let mut extensions = ExtensionMap::new();
let mut compositor = None;
let mut tabs = reovim_driver_session::TabPageSet::new();
let mut registers = RegisterBank::new();
let mut clipboard_history = HistoryRing::new();
let mut local_marks = MarkBank::new();
let mut jumplist = Jumplist::new();
let mut active_buffer = None;
let mut terminal_size = (80u16, 24u16);
let mut runtime = SessionRuntime::new(
&mut session,
reovim_driver_session::ClientContext {
mode_stack: &mut mode_stack,
windows: &mut windows,
extensions: &mut extensions,
compositor: &mut compositor,
tabs: &mut tabs,
registers: &mut registers,
clipboard_history: &mut clipboard_history,
local_marks: &mut local_marks,
jumplist: &mut jumplist,
active_buffer: &mut active_buffer,
terminal_size: &mut terminal_size,
},
&ctx,
&StubExecutor,
);
{
runtime
.ext_mut::<reovim_driver_session::api::SearchState>()
.start_pending_search(reovim_driver_search::Direction::Forward);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchForward);
for ch in "test".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
use reovim_driver_command::CommandNameIndex;
struct ExTestCmd {
cmd_id: CommandId,
result: CommandResult,
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl ExTestCmd {
fn success(name: &'static str) -> Self {
Self {
cmd_id: CommandId::new(reovim_kernel::api::v1::ModuleId::new("test"), name),
result: CommandResult::Success,
}
}
fn failing(name: &'static str) -> Self {
Self {
cmd_id: CommandId::new(reovim_kernel::api::v1::ModuleId::new("test"), name),
result: CommandResult::error("test failure"),
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl Command for ExTestCmd {
fn id(&self) -> CommandId {
self.cmd_id.clone()
}
fn description(&self) -> &'static str {
"test"
}
fn names(&self) -> &[&'static str] {
&[]
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl CommandHandler for ExTestCmd {
fn execute(&self, _runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
self.result.clone()
}
}
fn create_test_context_with_name_index(name_index: CommandNameIndex) -> KernelContext {
let ctx = create_test_context();
ctx.services.register(Arc::new(name_index));
ctx
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_ex_command_success() {
let cmd = Arc::new(ExTestCmd::success("test-cmd"));
let cmd_handler: Arc<dyn CommandHandler> = Arc::clone(&cmd) as Arc<dyn CommandHandler>;
let mut name_index = CommandNameIndex::new();
let cmd_as_command: Arc<dyn Command> = Arc::clone(&cmd) as Arc<dyn Command>;
name_index.insert("test".to_string(), cmd.id(), Arc::clone(&cmd_as_command));
name_index.insert("t".to_string(), cmd.id(), cmd_as_command);
let mut executor = TestExecutor::new();
executor.register(cmd_handler);
let ctx = create_test_context_with_name_index(name_index);
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime_with_executor(&ctx, &executor);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
for ch in "test".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_ex_command_not_found() {
let name_index = CommandNameIndex::new();
let ctx = create_test_context_with_name_index(name_index);
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
for ch in "nonexistent".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_ex_command_error() {
let cmd = Arc::new(ExTestCmd::failing("fail-cmd"));
let cmd_handler: Arc<dyn CommandHandler> = Arc::clone(&cmd) as Arc<dyn CommandHandler>;
let mut name_index = CommandNameIndex::new();
let cmd_as_command: Arc<dyn Command> = Arc::clone(&cmd) as Arc<dyn Command>;
name_index.insert("fail".to_string(), cmd.id(), cmd_as_command);
let mut executor = TestExecutor::new();
executor.register(cmd_handler);
let ctx = create_test_context_with_name_index(name_index);
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime_with_executor(&ctx, &executor);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
for ch in "fail".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_ex_command_whitespace_only() {
let name_index = CommandNameIndex::new();
let ctx = create_test_context_with_name_index(name_index);
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
runtime.ext_mut::<CmdlineState>().insert_char(' ');
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_ex_command_with_bang() {
let cmd = Arc::new(ExTestCmd::success("test-bang"));
let cmd_handler: Arc<dyn CommandHandler> = Arc::clone(&cmd) as Arc<dyn CommandHandler>;
let mut name_index = CommandNameIndex::new();
let cmd_as_command: Arc<dyn Command> = Arc::clone(&cmd) as Arc<dyn Command>;
name_index.insert("test".to_string(), cmd.id(), cmd_as_command);
let mut executor = TestExecutor::new();
executor.register(cmd_handler);
let ctx = create_test_context_with_name_index(name_index);
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime_with_executor(&ctx, &executor);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
for ch in "test!".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_ex_command_with_args() {
let cmd = Arc::new(ExTestCmd::success("test-args"));
let cmd_handler: Arc<dyn CommandHandler> = Arc::clone(&cmd) as Arc<dyn CommandHandler>;
let mut name_index = CommandNameIndex::new();
let cmd_as_command: Arc<dyn Command> = Arc::clone(&cmd) as Arc<dyn Command>;
name_index.insert("test".to_string(), cmd.id(), cmd_as_command);
let mut executor = TestExecutor::new();
executor.register(cmd_handler);
let ctx = create_test_context_with_name_index(name_index);
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime_with_executor(&ctx, &executor);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
for ch in "test filename.txt".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_ex_command_propagates_buffer_id_and_vfs() {
let cmd = Arc::new(ExTestCmd::success("test-ctx"));
let cmd_handler: Arc<dyn CommandHandler> = Arc::clone(&cmd) as Arc<dyn CommandHandler>;
let mut name_index = CommandNameIndex::new();
let cmd_as_command: Arc<dyn Command> = Arc::clone(&cmd) as Arc<dyn Command>;
name_index.insert("test".to_string(), cmd.id(), cmd_as_command);
let mut executor = TestExecutor::new();
executor.register(cmd_handler);
let ctx = create_test_context_with_name_index(name_index);
let buffer = Buffer::from_string("hello");
let buffer_id = ctx.buffers.register(buffer);
let mock_vfs = Arc::new(reovim_driver_vfs::MockVfs::new());
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
args.set_vfs(Arc::clone(&mock_vfs) as Arc<dyn reovim_driver_vfs::VfsDriver>);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime_with_executor(&ctx, &executor);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
for ch in "test".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_search_no_search_registry() {
let ctx = create_test_context();
let buffer = Buffer::from_string("hello world");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
{
runtime
.ext_mut::<reovim_driver_session::api::SearchState>()
.start_pending_search(reovim_driver_search::Direction::Forward);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchForward);
for ch in "world".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
let window = runtime.windows().active().unwrap();
assert_eq!(window.cursor.column, 0);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_search_no_search_provider_for_key() {
let ctx = create_test_context();
let search_registry = Arc::new(SearchProviderRegistry::new());
ctx.services.register(search_registry);
let buffer = Buffer::from_string("hello world");
let buffer_id = ctx.buffers.register(buffer);
let mut args = CommandContext::new();
args.set_buffer_id(buffer_id);
let mut state = TestState::with_buffer(Some(buffer_id));
let mut runtime = state.runtime(&ctx);
{
runtime
.ext_mut::<reovim_driver_session::api::SearchState>()
.start_pending_search(reovim_driver_search::Direction::Forward);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchForward);
for ch in "test".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_exit_commandline_search_buffer_not_found() {
let mock = Arc::new(MockSearchProvider::with_no_match());
let ctx = create_test_context_with_search(mock);
let fake_buffer_id = BufferId::from_raw(999);
let mut args = CommandContext::new();
args.set_buffer_id(fake_buffer_id);
let mut state = TestState::with_buffer(Some(fake_buffer_id));
let mut runtime = state.runtime(&ctx);
{
runtime
.ext_mut::<reovim_driver_session::api::SearchState>()
.start_pending_search(reovim_driver_search::Direction::Forward);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchForward);
for ch in "test".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
}
let result = ExitCommandLineMode.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_cursor_left_id() {
assert_eq!(CmdlineCursorLeft.id(), ids::CMDLINE_CURSOR_LEFT);
assert!(CmdlineCursorLeft.description().contains("left"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_cursor_right_id() {
assert_eq!(CmdlineCursorRight.id(), ids::CMDLINE_CURSOR_RIGHT);
assert!(CmdlineCursorRight.description().contains("right"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_cursor_home_id() {
assert_eq!(CmdlineCursorHome.id(), ids::CMDLINE_CURSOR_HOME);
assert!(CmdlineCursorHome.description().contains("start"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_cursor_end_id() {
assert_eq!(CmdlineCursorEnd.id(), ids::CMDLINE_CURSOR_END);
assert!(CmdlineCursorEnd.description().contains("end"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_delete_char_id() {
assert_eq!(CmdlineDeleteChar.id(), ids::CMDLINE_DELETE_CHAR);
assert!(CmdlineDeleteChar.description().contains("cursor"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_backspace_id() {
assert_eq!(CmdlineBackspace.id(), ids::CMDLINE_BACKSPACE);
assert!(CmdlineBackspace.description().contains("before"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_delete_word_id() {
assert_eq!(CmdlineDeleteWord.id(), ids::CMDLINE_DELETE_WORD);
assert!(CmdlineDeleteWord.description().contains("word"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_delete_to_start_id() {
assert_eq!(CmdlineDeleteToStart.id(), ids::CMDLINE_DELETE_TO_START);
assert!(CmdlineDeleteToStart.description().contains("start"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_editing_debug_and_default() {
let _ = format!("{:?}", CmdlineCursorLeft);
let _ = format!("{:?}", CmdlineCursorRight);
let _ = format!("{:?}", CmdlineCursorHome);
let _ = format!("{:?}", CmdlineCursorEnd);
let _ = format!("{:?}", CmdlineDeleteChar);
let _ = format!("{:?}", CmdlineBackspace);
let _ = format!("{:?}", CmdlineDeleteWord);
let _ = format!("{:?}", CmdlineDeleteToStart);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_cursor_left_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
runtime.ext_mut::<CmdlineState>().insert_char('a');
runtime.ext_mut::<CmdlineState>().insert_char('b');
assert_eq!(runtime.ext_mut::<CmdlineState>().cursor(), 2);
let result = CmdlineCursorLeft.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.ext_mut::<CmdlineState>().cursor(), 1);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_cursor_right_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
runtime.ext_mut::<CmdlineState>().insert_char('a');
runtime.ext_mut::<CmdlineState>().move_cursor_left();
let result = CmdlineCursorRight.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.ext_mut::<CmdlineState>().cursor(), 1);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_cursor_home_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
runtime.ext_mut::<CmdlineState>().insert_char('a');
let result = CmdlineCursorHome.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.ext_mut::<CmdlineState>().cursor(), 0);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_cursor_end_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
runtime.ext_mut::<CmdlineState>().insert_char('a');
runtime.ext_mut::<CmdlineState>().move_to_start();
let result = CmdlineCursorEnd.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.ext_mut::<CmdlineState>().cursor(), 1);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_delete_char_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
runtime.ext_mut::<CmdlineState>().insert_char('a');
runtime.ext_mut::<CmdlineState>().insert_char('b');
runtime.ext_mut::<CmdlineState>().move_to_start();
let result = CmdlineDeleteChar.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.ext_mut::<CmdlineState>().input(), "b");
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_backspace_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
runtime.ext_mut::<CmdlineState>().insert_char('a');
runtime.ext_mut::<CmdlineState>().insert_char('b');
let result = CmdlineBackspace.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.ext_mut::<CmdlineState>().input(), "a");
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_delete_word_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
for ch in "hello world".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
let result = CmdlineDeleteWord.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.ext_mut::<CmdlineState>().input(), "hello ");
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_history_up_id() {
assert_eq!(CmdlineHistoryUp.id(), ids::CMDLINE_HISTORY_UP);
assert!(CmdlineHistoryUp.description().contains("older"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_history_down_id() {
assert_eq!(CmdlineHistoryDown.id(), ids::CMDLINE_HISTORY_DOWN);
assert!(CmdlineHistoryDown.description().contains("newer"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_history_debug_and_default() {
let _ = format!("{:?}", CmdlineHistoryUp);
let _ = format!("{:?}", CmdlineHistoryDown);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_history_up_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
runtime.ext_mut::<CmdlineState>().insert_char('w');
runtime.ext_mut::<CmdlineState>().push_to_history();
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
let result = CmdlineHistoryUp.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.ext_mut::<CmdlineState>().input(), "w");
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_history_down_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
runtime.ext_mut::<CmdlineState>().insert_char('w');
runtime.ext_mut::<CmdlineState>().push_to_history();
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
for ch in "new".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
runtime.ext_mut::<CmdlineState>().history_up();
assert_eq!(runtime.ext_mut::<CmdlineState>().input(), "w");
let result = CmdlineHistoryDown.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.ext_mut::<CmdlineState>().input(), "new");
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_delete_to_start_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
for ch in "hello".chars() {
runtime.ext_mut::<CmdlineState>().insert_char(ch);
}
let result = CmdlineDeleteToStart.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.ext_mut::<CmdlineState>().input(), "");
assert_eq!(runtime.ext_mut::<CmdlineState>().cursor(), 0);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_complete_next_id() {
assert_eq!(CmdlineCompleteNext.id(), ids::CMDLINE_COMPLETE_NEXT);
assert!(CmdlineCompleteNext.description().contains("next"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_complete_prev_id() {
assert_eq!(CmdlineCompletePrev.id(), ids::CMDLINE_COMPLETE_PREV);
assert!(CmdlineCompletePrev.description().contains("previous"));
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_completion_debug_and_default() {
let _ = format!("{:?}", CmdlineCompleteNext);
let _ = format!("{:?}", CmdlineCompletePrev);
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_complete_next_with_name_index() {
struct WriteCmd;
impl Command for WriteCmd {
fn id(&self) -> CommandId {
CommandId::new(reovim_kernel::api::v1::ModuleId::new("test"), "write")
}
fn description(&self) -> &'static str {
"Write"
}
fn names(&self) -> &[&'static str] {
&["w", "write"]
}
}
struct WqCmd;
impl Command for WqCmd {
fn id(&self) -> CommandId {
CommandId::new(reovim_kernel::api::v1::ModuleId::new("test"), "wq")
}
fn description(&self) -> &'static str {
"Write and quit"
}
fn names(&self) -> &[&'static str] {
&["wq"]
}
}
let mut name_index = CommandNameIndex::new();
let write_cmd: Arc<dyn Command> = Arc::new(WriteCmd);
let wq_cmd: Arc<dyn Command> = Arc::new(WqCmd);
name_index.insert("w".to_string(), write_cmd.id(), Arc::clone(&write_cmd));
name_index.insert("write".to_string(), write_cmd.id(), write_cmd);
name_index.insert("wq".to_string(), wq_cmd.id(), wq_cmd);
let ctx = create_test_context_with_name_index(name_index);
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
runtime.ext_mut::<CmdlineState>().insert_char('w');
let result = CmdlineCompleteNext.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert!(!runtime.ext_mut::<CmdlineState>().completions().is_empty());
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_complete_next_empty_input() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
let result = CmdlineCompleteNext.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert!(runtime.ext_mut::<CmdlineState>().completions().is_empty());
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_complete_prev_execute() {
let ctx = create_test_context();
let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
runtime
.ext_mut::<CmdlineState>()
.set_completions("w".to_string(), vec!["write".to_string(), "wq".to_string()]);
let result = CmdlineCompletePrev.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert_eq!(runtime.ext_mut::<CmdlineState>().input(), "wq");
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_cmdline_complete_no_registry() {
let ctx = create_test_context(); let args = CommandContext::new();
let mut state = TestState::with_buffer(None);
let mut runtime = state.runtime(&ctx);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
runtime.ext_mut::<CmdlineState>().insert_char('w');
let result = CmdlineCompleteNext.execute(&mut runtime, &args);
assert_eq!(result, CommandResult::Success);
assert!(runtime.ext_mut::<CmdlineState>().completions().is_empty());
}