use {
reovim_driver_command::{Command, CommandContext, CommandHandler, CommandResult},
reovim_driver_search::Direction,
reovim_driver_session::{
BufferApi, SessionRuntime, TransitionContext,
api::{ChangeTracker, ExtensionApi, ModeApi, SearchState},
},
reovim_driver_undo::{UndoKey, UndoProviderRegistry},
reovim_kernel::api::v1::{CommandId, Position},
reovim_module_cmdline::{CmdlineMessage, CmdlinePrompt, CmdlineState},
std::sync::Arc,
};
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};
#[cfg_attr(coverage_nightly, coverage(off))]
fn begin_insert_batch(runtime: &SessionRuntime<'_>, buffer_id: reovim_kernel::api::v1::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);
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
fn end_insert_batch(runtime: &SessionRuntime<'_>, buffer_id: reovim_kernel::api::v1::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.end_batch(buffer_id, pos);
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EnterInsertMode;
impl Command for EnterInsertMode {
fn id(&self) -> CommandId {
ids::ENTER_INSERT
}
fn description(&self) -> &'static str {
"Enter insert mode"
}
}
impl CommandHandler for EnterInsertMode {
#[cfg_attr(coverage_nightly, coverage(off))]
fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
if let Some(buffer_id) = args.buffer_id() {
begin_insert_batch(runtime, buffer_id);
}
if let Some(vim) = runtime.ext_mut::<crate::VimSessionState>().into() {
vim.insert_buffer.clear();
}
runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EnterInsertModeAppend;
impl Command for EnterInsertModeAppend {
fn id(&self) -> CommandId {
ids::ENTER_INSERT_AFTER
}
fn description(&self) -> &'static str {
"Enter insert mode after cursor (append)"
}
}
impl CommandHandler for EnterInsertModeAppend {
#[cfg_attr(coverage_nightly, coverage(off))]
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 Some(line_len) = runtime.buffer_line_len(buffer_id, pos.line)
{
if pos.column < line_len {
set_cursor_position(runtime, Position::new(pos.line, pos.column + 1));
}
}
begin_insert_batch(runtime, buffer_id);
}
runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ExitToNormal;
impl Command for ExitToNormal {
fn id(&self) -> CommandId {
ids::EXIT_INSERT
}
fn description(&self) -> &'static str {
"Exit insert mode and return to normal mode"
}
}
impl CommandHandler for ExitToNormal {
#[cfg_attr(coverage_nightly, coverage(off))]
fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
if let Some(buffer_id) = args.buffer_id() {
end_insert_batch(runtime, buffer_id);
if let Some(pos) = get_cursor_position(runtime)
&& pos.column > 0
{
set_cursor_position(runtime, Position::new(pos.line, pos.column - 1));
}
}
if let Some(vim) = runtime.ext_mut::<crate::VimSessionState>().into() {
let insert_text = std::mem::take(&mut vim.insert_buffer);
if vim.recording_repeat {
vim.finish_repeat_recording();
} else if !insert_text.is_empty() {
vim.last_change = Some(crate::session_state::LastChange {
change_type: crate::session_state::ChangeType::Insert { text: insert_text },
count: None,
register: None,
keys: Vec::new(),
});
}
}
runtime.set_mode(VimMode::NORMAL_ID, TransitionContext::new());
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EnterReplaceMode;
impl Command for EnterReplaceMode {
fn id(&self) -> CommandId {
ids::ENTER_REPLACE_MODE
}
fn description(&self) -> &'static str {
"Enter replace mode"
}
}
impl CommandHandler for EnterReplaceMode {
#[cfg_attr(coverage_nightly, coverage(off))]
fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
if let Some(buffer_id) = args.buffer_id() {
begin_insert_batch(runtime, buffer_id);
}
if let Some(vim) = runtime.ext_mut::<crate::VimSessionState>().into() {
vim.insert_buffer.clear();
vim.replace_restore_stack.clear();
}
runtime.set_mode(VimMode::REPLACE_ID, TransitionContext::new());
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ReplaceBackspace;
impl Command for ReplaceBackspace {
fn id(&self) -> CommandId {
ids::REPLACE_BACKSPACE
}
fn description(&self) -> &'static str {
"Restore original character in replace mode"
}
}
impl CommandHandler for ReplaceBackspace {
#[cfg_attr(coverage_nightly, coverage(off))]
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
let entry = runtime
.ext_mut::<crate::VimSessionState>()
.replace_restore_stack
.pop();
let Some(entry) = entry else {
return CommandResult::Success;
};
let Some(window) = runtime.windows().active() else {
return CommandResult::Success;
};
let Some(buffer_id) = window.buffer_id else {
return CommandResult::Success;
};
let cursor = Position::new(window.cursor.line, window.cursor.column);
if cursor.column > 0 {
let delete_col = cursor.column - 1;
runtime.delete_range(buffer_id, Position::new(cursor.line, delete_col), cursor);
if let Some(original) = entry.original {
let restore_str = String::from(original);
runtime.insert_text(
buffer_id,
Position::new(cursor.line, delete_col),
&restore_str,
);
}
if let Some(w) = runtime.windows_mut().active_mut() {
w.cursor.column = delete_col;
}
} else if cursor.line > 0 {
let prev_line_len = runtime
.buffer_line_len(buffer_id, cursor.line - 1)
.unwrap_or(0);
runtime.delete_range(
buffer_id,
Position::new(cursor.line - 1, prev_line_len),
Position::new(cursor.line, 0),
);
if let Some(original) = entry.original {
let restore_str = String::from(original);
runtime.insert_text(
buffer_id,
Position::new(cursor.line - 1, prev_line_len),
&restore_str,
);
}
if let Some(w) = runtime.windows_mut().active_mut() {
w.cursor.line -= 1;
w.cursor.column = prev_line_len;
}
}
runtime
.ext_mut::<crate::VimSessionState>()
.insert_buffer
.pop();
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EnterWindowMode;
impl Command for EnterWindowMode {
fn id(&self) -> CommandId {
ids::ENTER_WINDOW_MODE
}
fn description(&self) -> &'static str {
"Enter window management mode"
}
}
impl CommandHandler for EnterWindowMode {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime.push_mode(VimMode::WINDOW_ID, TransitionContext::new());
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CancelToNormal;
impl Command for CancelToNormal {
fn id(&self) -> CommandId {
ids::CANCEL_TO_NORMAL
}
fn description(&self) -> &'static str {
"Cancel and return to normal mode"
}
}
impl CommandHandler for CancelToNormal {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime.set_mode(VimMode::NORMAL_ID, TransitionContext::new());
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EnterCommandLineMode;
impl Command for EnterCommandLineMode {
fn id(&self) -> CommandId {
ids::ENTER_COMMANDLINE
}
fn description(&self) -> &'static str {
"Enter command-line mode"
}
}
impl CommandHandler for EnterCommandLineMode {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::Command);
runtime.set_mode(VimMode::COMMANDLINE_ID, TransitionContext::new());
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ExitCommandLineMode;
impl Command for ExitCommandLineMode {
fn id(&self) -> CommandId {
ids::EXIT_COMMANDLINE
}
fn description(&self) -> &'static str {
"Exit command-line mode and return to normal mode"
}
}
impl CommandHandler for ExitCommandLineMode {
fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
let prompt = runtime.ext_mut::<CmdlineState>().prompt();
runtime.ext_mut::<CmdlineState>().push_to_history();
let cmdline = runtime.ext_mut::<CmdlineState>().take_cmdline_input();
runtime.ext_mut::<CmdlineState>().exit();
runtime.set_mode(VimMode::NORMAL_ID, TransitionContext::new());
match prompt {
CmdlinePrompt::SearchForward | CmdlinePrompt::SearchBackward => {
let pending = {
let search_state = runtime.ext_mut::<SearchState>();
search_state.take_pending_search()
};
if let Some(direction) = pending
&& !cmdline.is_empty()
{
runtime
.ext_mut::<SearchState>()
.set(cmdline.clone(), direction);
execute_search(runtime, args, &cmdline, direction);
}
}
CmdlinePrompt::Command => {
if !cmdline.is_empty() {
execute_ex_command(runtime, args, &cmdline);
}
}
}
CommandResult::Success
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
fn strip_range_prefix<'a>(
runtime: &SessionRuntime<'_>,
args: &CommandContext,
cmdline: &'a str,
) -> (Option<(usize, usize)>, &'a str) {
let trimmed = cmdline.trim_start();
if let Some(rest) = trimmed.strip_prefix('%') {
let line_count = args
.buffer_id()
.and_then(|bid| runtime.buffer_line_count(bid))
.unwrap_or(1);
let last_line = line_count.saturating_sub(1);
return (Some((0, last_line)), rest);
}
(None, cmdline)
}
#[cfg_attr(coverage_nightly, coverage(off))]
fn execute_ex_command(runtime: &mut SessionRuntime<'_>, args: &CommandContext, cmdline: &str) {
use {
reovim_driver_command::{CommandNameIndex, bind_args, parse_cmdline},
reovim_driver_session::CommandApi,
};
let (range, effective_cmdline) = strip_range_prefix(runtime, args, cmdline);
let Some(parsed) = parse_cmdline(effective_cmdline) else {
return;
};
let Some(name_index) = runtime.kernel().services.get::<CommandNameIndex>() else {
tracing::warn!("CommandNameIndex not registered - ex-commands not available");
return;
};
let (cmd_id, specs) = match name_index.resolve_prefix(&parsed.name) {
Ok(Some((id, cmd))) => (id.clone(), cmd.args()),
Ok(None) => {
let msg = format!("E492: Not an editor command: {}", parsed.name);
runtime
.ext_mut::<CmdlineState>()
.set_message(CmdlineMessage::Error(msg));
return;
}
Err(ambiguous) => {
runtime
.ext_mut::<CmdlineState>()
.set_message(CmdlineMessage::Error(ambiguous.to_string()));
return;
}
};
drop(name_index);
let bound = match bind_args(&specs, &parsed.raw_args, parsed.bang) {
Ok(map) => map,
Err(e) => {
runtime
.ext_mut::<CmdlineState>()
.set_message(CmdlineMessage::Error(e.to_string()));
return;
}
};
let mut ctx = CommandContext::new();
for (name, value) in bound {
ctx.set(&name, value);
}
if let Some(bid) = args.buffer_id() {
ctx.set_buffer_id(bid);
}
if let Some(vfs) = args.vfs() {
ctx.set_vfs(Arc::clone(vfs));
}
if let Some((start, end)) = range {
ctx.set("range", reovim_driver_command_types::ArgValue::Range(start, end));
}
let result = runtime.execute_command(cmd_id, ctx);
if let CommandResult::Error(msg) = result {
runtime
.ext_mut::<CmdlineState>()
.set_message(CmdlineMessage::Error(msg));
}
}
fn execute_search(
runtime: &mut SessionRuntime<'_>,
args: &CommandContext,
pattern: &str,
direction: Direction,
) {
use reovim_driver_search::{SearchKey, SearchProviderRegistry};
let Some(buffer_id) = args.buffer_id() else {
return;
};
let Some(cursor) = get_cursor_position(runtime) else {
return;
};
let Some(search_registry) = runtime.kernel().services.get::<SearchProviderRegistry>() else {
tracing::warn!("Search provider not available");
return;
};
let Some(search_provider) = search_registry.get(&SearchKey::Regex) else {
tracing::warn!("Regex search engine not registered");
return;
};
let search_result = runtime.with_buffer_read(buffer_id, |buffer| {
search_provider.find_next(buffer, cursor, pattern, direction, true)
});
match search_result {
Some(Ok(Some(m))) => {
set_cursor_position(runtime, m.start);
runtime.record_cursor_move(buffer_id);
tracing::debug!(
pattern,
?direction,
match_start = ?m.start,
"Search found match"
);
}
Some(Ok(None)) => {
tracing::debug!(pattern, "Search: no match found");
}
Some(Err(e)) => {
tracing::debug!(pattern, ?e, "Search: invalid pattern");
}
None => {
tracing::warn!("Buffer not found for search");
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CancelCommandLineMode;
impl Command for CancelCommandLineMode {
fn id(&self) -> CommandId {
ids::CANCEL_COMMANDLINE
}
fn description(&self) -> &'static str {
"Cancel command-line mode without executing"
}
}
impl CommandHandler for CancelCommandLineMode {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime.ext_mut::<SearchState>().clear_pending_search();
runtime.ext_mut::<CmdlineState>().cancel();
runtime.set_mode(VimMode::NORMAL_ID, TransitionContext::new());
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CmdlineCursorLeft;
impl Command for CmdlineCursorLeft {
fn id(&self) -> CommandId {
ids::CMDLINE_CURSOR_LEFT
}
fn description(&self) -> &'static str {
"Move cursor left in command-line"
}
}
impl CommandHandler for CmdlineCursorLeft {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime.ext_mut::<CmdlineState>().move_cursor_left();
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CmdlineCursorRight;
impl Command for CmdlineCursorRight {
fn id(&self) -> CommandId {
ids::CMDLINE_CURSOR_RIGHT
}
fn description(&self) -> &'static str {
"Move cursor right in command-line"
}
}
impl CommandHandler for CmdlineCursorRight {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime.ext_mut::<CmdlineState>().move_cursor_right();
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CmdlineCursorHome;
impl Command for CmdlineCursorHome {
fn id(&self) -> CommandId {
ids::CMDLINE_CURSOR_HOME
}
fn description(&self) -> &'static str {
"Move cursor to start of command-line"
}
}
impl CommandHandler for CmdlineCursorHome {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime.ext_mut::<CmdlineState>().move_to_start();
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CmdlineCursorEnd;
impl Command for CmdlineCursorEnd {
fn id(&self) -> CommandId {
ids::CMDLINE_CURSOR_END
}
fn description(&self) -> &'static str {
"Move cursor to end of command-line"
}
}
impl CommandHandler for CmdlineCursorEnd {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime.ext_mut::<CmdlineState>().move_to_end();
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CmdlineDeleteChar;
impl Command for CmdlineDeleteChar {
fn id(&self) -> CommandId {
ids::CMDLINE_DELETE_CHAR
}
fn description(&self) -> &'static str {
"Delete character at cursor in command-line"
}
}
impl CommandHandler for CmdlineDeleteChar {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime.ext_mut::<CmdlineState>().delete_at_cursor();
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CmdlineBackspace;
impl Command for CmdlineBackspace {
fn id(&self) -> CommandId {
ids::CMDLINE_BACKSPACE
}
fn description(&self) -> &'static str {
"Delete character before cursor in command-line"
}
}
impl CommandHandler for CmdlineBackspace {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime.ext_mut::<CmdlineState>().backspace();
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CmdlineDeleteWord;
impl Command for CmdlineDeleteWord {
fn id(&self) -> CommandId {
ids::CMDLINE_DELETE_WORD
}
fn description(&self) -> &'static str {
"Delete word before cursor in command-line"
}
}
impl CommandHandler for CmdlineDeleteWord {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime.ext_mut::<CmdlineState>().delete_word_back();
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CmdlineDeleteToStart;
impl Command for CmdlineDeleteToStart {
fn id(&self) -> CommandId {
ids::CMDLINE_DELETE_TO_START
}
fn description(&self) -> &'static str {
"Delete to start of command-line"
}
}
impl CommandHandler for CmdlineDeleteToStart {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime.ext_mut::<CmdlineState>().delete_to_start();
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CmdlineHistoryUp;
impl Command for CmdlineHistoryUp {
fn id(&self) -> CommandId {
ids::CMDLINE_HISTORY_UP
}
fn description(&self) -> &'static str {
"Navigate to older history entry"
}
}
impl CommandHandler for CmdlineHistoryUp {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime.ext_mut::<CmdlineState>().history_up();
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CmdlineHistoryDown;
impl Command for CmdlineHistoryDown {
fn id(&self) -> CommandId {
ids::CMDLINE_HISTORY_DOWN
}
fn description(&self) -> &'static str {
"Navigate to newer history entry"
}
}
impl CommandHandler for CmdlineHistoryDown {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime.ext_mut::<CmdlineState>().history_down();
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CmdlineCompleteNext;
impl Command for CmdlineCompleteNext {
fn id(&self) -> CommandId {
ids::CMDLINE_COMPLETE_NEXT
}
fn description(&self) -> &'static str {
"Cycle to next command-line completion"
}
}
impl CommandHandler for CmdlineCompleteNext {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
populate_completions_if_needed(runtime);
runtime.ext_mut::<CmdlineState>().complete_next();
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CmdlineCompletePrev;
impl Command for CmdlineCompletePrev {
fn id(&self) -> CommandId {
ids::CMDLINE_COMPLETE_PREV
}
fn description(&self) -> &'static str {
"Cycle to previous command-line completion"
}
}
impl CommandHandler for CmdlineCompletePrev {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
populate_completions_if_needed(runtime);
runtime.ext_mut::<CmdlineState>().complete_prev();
CommandResult::Success
}
}
fn populate_completions_if_needed(runtime: &mut SessionRuntime<'_>) {
if !runtime.ext_mut::<CmdlineState>().completions().is_empty() {
return;
}
let prefix = runtime.ext_mut::<CmdlineState>().input().to_string();
if prefix.is_empty() {
return;
}
let candidates = {
use reovim_driver_command::CommandNameIndex;
runtime
.kernel()
.services
.get::<CommandNameIndex>()
.map_or_else(Vec::new, |index| {
index
.search_by_prefix(&prefix)
.into_iter()
.flat_map(|(_, cmd)| cmd.names().iter().copied().map(String::from))
.filter(|name| name.starts_with(&prefix))
.collect()
})
};
if !candidates.is_empty() {
runtime
.ext_mut::<CmdlineState>()
.set_completions(prefix, candidates);
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EnterSearchForward;
impl Command for EnterSearchForward {
fn id(&self) -> CommandId {
ids::ENTER_SEARCH_FORWARD
}
fn description(&self) -> &'static str {
"Enter search forward mode (/)"
}
}
impl CommandHandler for EnterSearchForward {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime
.ext_mut::<SearchState>()
.start_pending_search(Direction::Forward);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchForward);
runtime.set_mode(VimMode::COMMANDLINE_ID, TransitionContext::new());
CommandResult::Success
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EnterSearchBackward;
impl Command for EnterSearchBackward {
fn id(&self) -> CommandId {
ids::ENTER_SEARCH_BACKWARD
}
fn description(&self) -> &'static str {
"Enter search backward mode (?)"
}
}
impl CommandHandler for EnterSearchBackward {
fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
runtime
.ext_mut::<SearchState>()
.start_pending_search(Direction::Backward);
runtime
.ext_mut::<CmdlineState>()
.enter(CmdlinePrompt::SearchBackward);
runtime.set_mode(VimMode::COMMANDLINE_ID, TransitionContext::new());
CommandResult::Success
}
}