1use {
21 reovim_driver_command::{Command, CommandContext, CommandHandler, CommandResult},
22 reovim_driver_search::Direction,
23 reovim_driver_session::{
24 BufferApi, SessionRuntime, TransitionContext,
25 api::{ChangeTracker, ExtensionApi, ModeApi, SearchState},
26 },
27 reovim_driver_undo::{UndoKey, UndoProviderRegistry},
28 reovim_kernel::api::v1::{CommandId, Position},
29 reovim_module_cmdline::{CmdlineMessage, CmdlinePrompt, CmdlineState},
30 std::sync::Arc,
31};
32
33fn get_cursor_position(runtime: &SessionRuntime<'_>) -> Option<Position> {
35 let window = runtime.windows().active()?;
36 Some(Position::new(window.cursor.line, window.cursor.column))
37}
38
39#[cfg_attr(coverage_nightly, coverage(off))]
41fn set_cursor_position(runtime: &mut SessionRuntime<'_>, pos: Position) {
42 if let Some(window) = runtime.windows_mut().active_mut() {
43 window.cursor = pos.into();
44 }
45}
46
47use crate::{ids, modes::VimMode};
48
49#[cfg_attr(coverage_nightly, coverage(off))]
53fn begin_insert_batch(runtime: &SessionRuntime<'_>, buffer_id: reovim_kernel::api::v1::BufferId) {
54 if let Some(pos) = get_cursor_position(runtime)
55 && let Some(undo_registry) = runtime.kernel().services.get::<UndoProviderRegistry>()
56 && let Some(undo_provider) = undo_registry.get(&UndoKey::Buffer)
57 {
58 undo_provider.begin_batch(buffer_id, pos);
59 }
60}
61
62#[cfg_attr(coverage_nightly, coverage(off))]
66fn end_insert_batch(runtime: &SessionRuntime<'_>, buffer_id: reovim_kernel::api::v1::BufferId) {
67 if let Some(pos) = get_cursor_position(runtime)
68 && let Some(undo_registry) = runtime.kernel().services.get::<UndoProviderRegistry>()
69 && let Some(undo_provider) = undo_registry.get(&UndoKey::Buffer)
70 {
71 undo_provider.end_batch(buffer_id, pos);
72 }
73}
74
75#[derive(Debug, Clone, Copy, Default)]
77pub struct EnterInsertMode;
78
79impl Command for EnterInsertMode {
80 fn id(&self) -> CommandId {
81 ids::ENTER_INSERT
82 }
83
84 fn description(&self) -> &'static str {
85 "Enter insert mode"
86 }
87}
88
89impl CommandHandler for EnterInsertMode {
90 #[cfg_attr(coverage_nightly, coverage(off))]
91 fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
92 if let Some(buffer_id) = args.buffer_id() {
94 begin_insert_batch(runtime, buffer_id);
95 }
96 if let Some(vim) = runtime.ext_mut::<crate::VimSessionState>().into() {
98 vim.insert_buffer.clear();
99 }
100 runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
101 CommandResult::Success
102 }
103}
104
105#[derive(Debug, Clone, Copy, Default)]
107pub struct EnterInsertModeAppend;
108
109impl Command for EnterInsertModeAppend {
110 fn id(&self) -> CommandId {
111 ids::ENTER_INSERT_AFTER
112 }
113
114 fn description(&self) -> &'static str {
115 "Enter insert mode after cursor (append)"
116 }
117}
118
119impl CommandHandler for EnterInsertModeAppend {
120 #[cfg_attr(coverage_nightly, coverage(off))]
121 fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
122 if let Some(buffer_id) = args.buffer_id() {
124 if let Some(pos) = get_cursor_position(runtime)
125 && let Some(line_len) = runtime.buffer_line_len(buffer_id, pos.line)
126 {
127 if pos.column < line_len {
129 set_cursor_position(runtime, Position::new(pos.line, pos.column + 1));
130 }
131 }
132 begin_insert_batch(runtime, buffer_id);
134 }
135
136 runtime.set_mode(VimMode::INSERT_ID, TransitionContext::new());
137 CommandResult::Success
138 }
139}
140
141#[derive(Debug, Clone, Copy, Default)]
143pub struct ExitToNormal;
144
145impl Command for ExitToNormal {
146 fn id(&self) -> CommandId {
147 ids::EXIT_INSERT
148 }
149
150 fn description(&self) -> &'static str {
151 "Exit insert mode and return to normal mode"
152 }
153}
154
155impl CommandHandler for ExitToNormal {
156 #[cfg_attr(coverage_nightly, coverage(off))]
157 fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
158 if let Some(buffer_id) = args.buffer_id() {
159 end_insert_batch(runtime, buffer_id);
161
162 if let Some(pos) = get_cursor_position(runtime)
164 && pos.column > 0
165 {
166 set_cursor_position(runtime, Position::new(pos.line, pos.column - 1));
167 }
168 }
169
170 if let Some(vim) = runtime.ext_mut::<crate::VimSessionState>().into() {
172 let insert_text = std::mem::take(&mut vim.insert_buffer);
173 if vim.recording_repeat {
174 vim.finish_repeat_recording();
177 } else if !insert_text.is_empty() {
178 vim.last_change = Some(crate::session_state::LastChange {
180 change_type: crate::session_state::ChangeType::Insert { text: insert_text },
181 count: None,
182 register: None,
183 keys: Vec::new(),
184 });
185 }
186 }
187
188 runtime.set_mode(VimMode::NORMAL_ID, TransitionContext::new());
189 CommandResult::Success
190 }
191}
192
193#[derive(Debug, Clone, Copy, Default)]
198pub struct EnterReplaceMode;
199
200impl Command for EnterReplaceMode {
201 fn id(&self) -> CommandId {
202 ids::ENTER_REPLACE_MODE
203 }
204
205 fn description(&self) -> &'static str {
206 "Enter replace mode"
207 }
208}
209
210impl CommandHandler for EnterReplaceMode {
211 #[cfg_attr(coverage_nightly, coverage(off))]
212 fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
213 if let Some(buffer_id) = args.buffer_id() {
215 begin_insert_batch(runtime, buffer_id);
216 }
217 if let Some(vim) = runtime.ext_mut::<crate::VimSessionState>().into() {
219 vim.insert_buffer.clear();
220 vim.replace_restore_stack.clear();
221 }
222 runtime.set_mode(VimMode::REPLACE_ID, TransitionContext::new());
223 CommandResult::Success
224 }
225}
226
227#[derive(Debug, Clone, Copy, Default)]
232pub struct ReplaceBackspace;
233
234impl Command for ReplaceBackspace {
235 fn id(&self) -> CommandId {
236 ids::REPLACE_BACKSPACE
237 }
238
239 fn description(&self) -> &'static str {
240 "Restore original character in replace mode"
241 }
242}
243
244impl CommandHandler for ReplaceBackspace {
245 #[cfg_attr(coverage_nightly, coverage(off))]
246 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
247 let entry = runtime
248 .ext_mut::<crate::VimSessionState>()
249 .replace_restore_stack
250 .pop();
251
252 let Some(entry) = entry else {
253 return CommandResult::Success;
255 };
256
257 let Some(window) = runtime.windows().active() else {
258 return CommandResult::Success;
259 };
260 let Some(buffer_id) = window.buffer_id else {
261 return CommandResult::Success;
262 };
263 let cursor = Position::new(window.cursor.line, window.cursor.column);
264
265 if cursor.column > 0 {
266 let delete_col = cursor.column - 1;
267 runtime.delete_range(buffer_id, Position::new(cursor.line, delete_col), cursor);
269
270 if let Some(original) = entry.original {
272 let restore_str = String::from(original);
273 runtime.insert_text(
274 buffer_id,
275 Position::new(cursor.line, delete_col),
276 &restore_str,
277 );
278 }
279
280 if let Some(w) = runtime.windows_mut().active_mut() {
282 w.cursor.column = delete_col;
283 }
284 } else if cursor.line > 0 {
285 let prev_line_len = runtime
287 .buffer_line_len(buffer_id, cursor.line - 1)
288 .unwrap_or(0);
289
290 runtime.delete_range(
292 buffer_id,
293 Position::new(cursor.line - 1, prev_line_len),
294 Position::new(cursor.line, 0),
295 );
296
297 if let Some(original) = entry.original {
299 let restore_str = String::from(original);
300 runtime.insert_text(
301 buffer_id,
302 Position::new(cursor.line - 1, prev_line_len),
303 &restore_str,
304 );
305 }
306
307 if let Some(w) = runtime.windows_mut().active_mut() {
309 w.cursor.line -= 1;
310 w.cursor.column = prev_line_len;
311 }
312 }
313
314 runtime
316 .ext_mut::<crate::VimSessionState>()
317 .insert_buffer
318 .pop();
319
320 CommandResult::Success
321 }
322}
323
324#[derive(Debug, Clone, Copy, Default)]
330pub struct EnterWindowMode;
331
332impl Command for EnterWindowMode {
333 fn id(&self) -> CommandId {
334 ids::ENTER_WINDOW_MODE
335 }
336
337 fn description(&self) -> &'static str {
338 "Enter window management mode"
339 }
340}
341
342impl CommandHandler for EnterWindowMode {
343 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
344 runtime.push_mode(VimMode::WINDOW_ID, TransitionContext::new());
346 CommandResult::Success
347 }
348}
349
350#[derive(Debug, Clone, Copy, Default)]
355pub struct CancelToNormal;
356
357impl Command for CancelToNormal {
358 fn id(&self) -> CommandId {
359 ids::CANCEL_TO_NORMAL
360 }
361
362 fn description(&self) -> &'static str {
363 "Cancel and return to normal mode"
364 }
365}
366
367impl CommandHandler for CancelToNormal {
368 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
369 runtime.set_mode(VimMode::NORMAL_ID, TransitionContext::new());
370 CommandResult::Success
371 }
372}
373
374#[derive(Debug, Clone, Copy, Default)]
379pub struct EnterCommandLineMode;
380
381impl Command for EnterCommandLineMode {
382 fn id(&self) -> CommandId {
383 ids::ENTER_COMMANDLINE
384 }
385
386 fn description(&self) -> &'static str {
387 "Enter command-line mode"
388 }
389}
390
391impl CommandHandler for EnterCommandLineMode {
392 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
393 runtime
395 .ext_mut::<CmdlineState>()
396 .enter(CmdlinePrompt::Command);
397 runtime.set_mode(VimMode::COMMANDLINE_ID, TransitionContext::new());
398 CommandResult::Success
399 }
400}
401
402#[derive(Debug, Clone, Copy, Default)]
407pub struct ExitCommandLineMode;
408
409impl Command for ExitCommandLineMode {
410 fn id(&self) -> CommandId {
411 ids::EXIT_COMMANDLINE
412 }
413
414 fn description(&self) -> &'static str {
415 "Exit command-line mode and return to normal mode"
416 }
417}
418
419impl CommandHandler for ExitCommandLineMode {
420 fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
421 let prompt = runtime.ext_mut::<CmdlineState>().prompt();
423
424 runtime.ext_mut::<CmdlineState>().push_to_history();
426
427 let cmdline = runtime.ext_mut::<CmdlineState>().take_cmdline_input();
429
430 runtime.ext_mut::<CmdlineState>().exit();
434 runtime.set_mode(VimMode::NORMAL_ID, TransitionContext::new());
435
436 match prompt {
437 CmdlinePrompt::SearchForward | CmdlinePrompt::SearchBackward => {
438 let pending = {
440 let search_state = runtime.ext_mut::<SearchState>();
441 search_state.take_pending_search()
442 };
443
444 if let Some(direction) = pending
445 && !cmdline.is_empty()
446 {
447 runtime
449 .ext_mut::<SearchState>()
450 .set(cmdline.clone(), direction);
451
452 execute_search(runtime, args, &cmdline, direction);
454 }
455 }
456 CmdlinePrompt::Command => {
457 if !cmdline.is_empty() {
459 execute_ex_command(runtime, args, &cmdline);
460 }
461 }
462 }
463
464 CommandResult::Success
465 }
466}
467
468#[cfg_attr(coverage_nightly, coverage(off))]
477fn strip_range_prefix<'a>(
478 runtime: &SessionRuntime<'_>,
479 args: &CommandContext,
480 cmdline: &'a str,
481) -> (Option<(usize, usize)>, &'a str) {
482 let trimmed = cmdline.trim_start();
483
484 if let Some(rest) = trimmed.strip_prefix('%') {
486 let line_count = args
487 .buffer_id()
488 .and_then(|bid| runtime.buffer_line_count(bid))
489 .unwrap_or(1);
490 let last_line = line_count.saturating_sub(1);
491 return (Some((0, last_line)), rest);
492 }
493
494 (None, cmdline)
495}
496
497#[cfg_attr(coverage_nightly, coverage(off))]
503fn execute_ex_command(runtime: &mut SessionRuntime<'_>, args: &CommandContext, cmdline: &str) {
504 use {
505 reovim_driver_command::{CommandNameIndex, bind_args, parse_cmdline},
506 reovim_driver_session::CommandApi,
507 };
508
509 let (range, effective_cmdline) = strip_range_prefix(runtime, args, cmdline);
511
512 let Some(parsed) = parse_cmdline(effective_cmdline) else {
513 return;
514 };
515
516 let Some(name_index) = runtime.kernel().services.get::<CommandNameIndex>() else {
517 tracing::warn!("CommandNameIndex not registered - ex-commands not available");
518 return;
519 };
520
521 let (cmd_id, specs) = match name_index.resolve_prefix(&parsed.name) {
522 Ok(Some((id, cmd))) => (id.clone(), cmd.args()),
523 Ok(None) => {
524 let msg = format!("E492: Not an editor command: {}", parsed.name);
525 runtime
526 .ext_mut::<CmdlineState>()
527 .set_message(CmdlineMessage::Error(msg));
528 return;
529 }
530 Err(ambiguous) => {
531 runtime
532 .ext_mut::<CmdlineState>()
533 .set_message(CmdlineMessage::Error(ambiguous.to_string()));
534 return;
535 }
536 };
537 drop(name_index);
539
540 let bound = match bind_args(&specs, &parsed.raw_args, parsed.bang) {
541 Ok(map) => map,
542 Err(e) => {
543 runtime
544 .ext_mut::<CmdlineState>()
545 .set_message(CmdlineMessage::Error(e.to_string()));
546 return;
547 }
548 };
549
550 let mut ctx = CommandContext::new();
552 for (name, value) in bound {
553 ctx.set(&name, value);
554 }
555 if let Some(bid) = args.buffer_id() {
557 ctx.set_buffer_id(bid);
558 }
559 if let Some(vfs) = args.vfs() {
560 ctx.set_vfs(Arc::clone(vfs));
561 }
562 if let Some((start, end)) = range {
564 ctx.set("range", reovim_driver_command_types::ArgValue::Range(start, end));
565 }
566
567 let result = runtime.execute_command(cmd_id, ctx);
568 if let CommandResult::Error(msg) = result {
569 runtime
570 .ext_mut::<CmdlineState>()
571 .set_message(CmdlineMessage::Error(msg));
572 }
573}
574
575fn execute_search(
577 runtime: &mut SessionRuntime<'_>,
578 args: &CommandContext,
579 pattern: &str,
580 direction: Direction,
581) {
582 use reovim_driver_search::{SearchKey, SearchProviderRegistry};
583
584 let Some(buffer_id) = args.buffer_id() else {
585 return;
586 };
587
588 let Some(cursor) = get_cursor_position(runtime) else {
589 return;
590 };
591
592 let Some(search_registry) = runtime.kernel().services.get::<SearchProviderRegistry>() else {
593 tracing::warn!("Search provider not available");
594 return;
595 };
596
597 let Some(search_provider) = search_registry.get(&SearchKey::Regex) else {
598 tracing::warn!("Regex search engine not registered");
599 return;
600 };
601
602 let search_result = runtime.with_buffer_read(buffer_id, |buffer| {
604 search_provider.find_next(buffer, cursor, pattern, direction, true)
605 });
606
607 match search_result {
608 Some(Ok(Some(m))) => {
609 set_cursor_position(runtime, m.start);
611 runtime.record_cursor_move(buffer_id);
612 tracing::debug!(
613 pattern,
614 ?direction,
615 match_start = ?m.start,
616 "Search found match"
617 );
618 }
619 Some(Ok(None)) => {
620 tracing::debug!(pattern, "Search: no match found");
621 }
622 Some(Err(e)) => {
623 tracing::debug!(pattern, ?e, "Search: invalid pattern");
624 }
625 None => {
626 tracing::warn!("Buffer not found for search");
627 }
628 }
629}
630
631#[derive(Debug, Clone, Copy, Default)]
636pub struct CancelCommandLineMode;
637
638impl Command for CancelCommandLineMode {
639 fn id(&self) -> CommandId {
640 ids::CANCEL_COMMANDLINE
641 }
642
643 fn description(&self) -> &'static str {
644 "Cancel command-line mode without executing"
645 }
646}
647
648impl CommandHandler for CancelCommandLineMode {
649 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
650 runtime.ext_mut::<SearchState>().clear_pending_search();
652
653 runtime.ext_mut::<CmdlineState>().cancel();
655 runtime.set_mode(VimMode::NORMAL_ID, TransitionContext::new());
656 CommandResult::Success
657 }
658}
659
660#[derive(Debug, Clone, Copy, Default)]
666pub struct CmdlineCursorLeft;
667
668impl Command for CmdlineCursorLeft {
669 fn id(&self) -> CommandId {
670 ids::CMDLINE_CURSOR_LEFT
671 }
672 fn description(&self) -> &'static str {
673 "Move cursor left in command-line"
674 }
675}
676
677impl CommandHandler for CmdlineCursorLeft {
678 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
679 runtime.ext_mut::<CmdlineState>().move_cursor_left();
680 CommandResult::Success
681 }
682}
683
684#[derive(Debug, Clone, Copy, Default)]
686pub struct CmdlineCursorRight;
687
688impl Command for CmdlineCursorRight {
689 fn id(&self) -> CommandId {
690 ids::CMDLINE_CURSOR_RIGHT
691 }
692 fn description(&self) -> &'static str {
693 "Move cursor right in command-line"
694 }
695}
696
697impl CommandHandler for CmdlineCursorRight {
698 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
699 runtime.ext_mut::<CmdlineState>().move_cursor_right();
700 CommandResult::Success
701 }
702}
703
704#[derive(Debug, Clone, Copy, Default)]
706pub struct CmdlineCursorHome;
707
708impl Command for CmdlineCursorHome {
709 fn id(&self) -> CommandId {
710 ids::CMDLINE_CURSOR_HOME
711 }
712 fn description(&self) -> &'static str {
713 "Move cursor to start of command-line"
714 }
715}
716
717impl CommandHandler for CmdlineCursorHome {
718 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
719 runtime.ext_mut::<CmdlineState>().move_to_start();
720 CommandResult::Success
721 }
722}
723
724#[derive(Debug, Clone, Copy, Default)]
726pub struct CmdlineCursorEnd;
727
728impl Command for CmdlineCursorEnd {
729 fn id(&self) -> CommandId {
730 ids::CMDLINE_CURSOR_END
731 }
732 fn description(&self) -> &'static str {
733 "Move cursor to end of command-line"
734 }
735}
736
737impl CommandHandler for CmdlineCursorEnd {
738 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
739 runtime.ext_mut::<CmdlineState>().move_to_end();
740 CommandResult::Success
741 }
742}
743
744#[derive(Debug, Clone, Copy, Default)]
746pub struct CmdlineDeleteChar;
747
748impl Command for CmdlineDeleteChar {
749 fn id(&self) -> CommandId {
750 ids::CMDLINE_DELETE_CHAR
751 }
752 fn description(&self) -> &'static str {
753 "Delete character at cursor in command-line"
754 }
755}
756
757impl CommandHandler for CmdlineDeleteChar {
758 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
759 runtime.ext_mut::<CmdlineState>().delete_at_cursor();
760 CommandResult::Success
761 }
762}
763
764#[derive(Debug, Clone, Copy, Default)]
766pub struct CmdlineBackspace;
767
768impl Command for CmdlineBackspace {
769 fn id(&self) -> CommandId {
770 ids::CMDLINE_BACKSPACE
771 }
772 fn description(&self) -> &'static str {
773 "Delete character before cursor in command-line"
774 }
775}
776
777impl CommandHandler for CmdlineBackspace {
778 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
779 runtime.ext_mut::<CmdlineState>().backspace();
780 CommandResult::Success
781 }
782}
783
784#[derive(Debug, Clone, Copy, Default)]
786pub struct CmdlineDeleteWord;
787
788impl Command for CmdlineDeleteWord {
789 fn id(&self) -> CommandId {
790 ids::CMDLINE_DELETE_WORD
791 }
792 fn description(&self) -> &'static str {
793 "Delete word before cursor in command-line"
794 }
795}
796
797impl CommandHandler for CmdlineDeleteWord {
798 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
799 runtime.ext_mut::<CmdlineState>().delete_word_back();
800 CommandResult::Success
801 }
802}
803
804#[derive(Debug, Clone, Copy, Default)]
806pub struct CmdlineDeleteToStart;
807
808impl Command for CmdlineDeleteToStart {
809 fn id(&self) -> CommandId {
810 ids::CMDLINE_DELETE_TO_START
811 }
812 fn description(&self) -> &'static str {
813 "Delete to start of command-line"
814 }
815}
816
817impl CommandHandler for CmdlineDeleteToStart {
818 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
819 runtime.ext_mut::<CmdlineState>().delete_to_start();
820 CommandResult::Success
821 }
822}
823
824#[derive(Debug, Clone, Copy, Default)]
830pub struct CmdlineHistoryUp;
831
832impl Command for CmdlineHistoryUp {
833 fn id(&self) -> CommandId {
834 ids::CMDLINE_HISTORY_UP
835 }
836 fn description(&self) -> &'static str {
837 "Navigate to older history entry"
838 }
839}
840
841impl CommandHandler for CmdlineHistoryUp {
842 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
843 runtime.ext_mut::<CmdlineState>().history_up();
844 CommandResult::Success
845 }
846}
847
848#[derive(Debug, Clone, Copy, Default)]
850pub struct CmdlineHistoryDown;
851
852impl Command for CmdlineHistoryDown {
853 fn id(&self) -> CommandId {
854 ids::CMDLINE_HISTORY_DOWN
855 }
856 fn description(&self) -> &'static str {
857 "Navigate to newer history entry"
858 }
859}
860
861impl CommandHandler for CmdlineHistoryDown {
862 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
863 runtime.ext_mut::<CmdlineState>().history_down();
864 CommandResult::Success
865 }
866}
867
868#[derive(Debug, Clone, Copy, Default)]
877pub struct CmdlineCompleteNext;
878
879impl Command for CmdlineCompleteNext {
880 fn id(&self) -> CommandId {
881 ids::CMDLINE_COMPLETE_NEXT
882 }
883 fn description(&self) -> &'static str {
884 "Cycle to next command-line completion"
885 }
886}
887
888impl CommandHandler for CmdlineCompleteNext {
889 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
890 populate_completions_if_needed(runtime);
891 runtime.ext_mut::<CmdlineState>().complete_next();
892 CommandResult::Success
893 }
894}
895
896#[derive(Debug, Clone, Copy, Default)]
898pub struct CmdlineCompletePrev;
899
900impl Command for CmdlineCompletePrev {
901 fn id(&self) -> CommandId {
902 ids::CMDLINE_COMPLETE_PREV
903 }
904 fn description(&self) -> &'static str {
905 "Cycle to previous command-line completion"
906 }
907}
908
909impl CommandHandler for CmdlineCompletePrev {
910 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
911 populate_completions_if_needed(runtime);
912 runtime.ext_mut::<CmdlineState>().complete_prev();
913 CommandResult::Success
914 }
915}
916
917fn populate_completions_if_needed(runtime: &mut SessionRuntime<'_>) {
919 if !runtime.ext_mut::<CmdlineState>().completions().is_empty() {
921 return;
922 }
923
924 let prefix = runtime.ext_mut::<CmdlineState>().input().to_string();
925 if prefix.is_empty() {
926 return;
927 }
928
929 let candidates = {
931 use reovim_driver_command::CommandNameIndex;
932 runtime
933 .kernel()
934 .services
935 .get::<CommandNameIndex>()
936 .map_or_else(Vec::new, |index| {
937 index
938 .search_by_prefix(&prefix)
939 .into_iter()
940 .flat_map(|(_, cmd)| cmd.names().iter().copied().map(String::from))
941 .filter(|name| name.starts_with(&prefix))
942 .collect()
943 })
944 };
945
946 if !candidates.is_empty() {
947 runtime
948 .ext_mut::<CmdlineState>()
949 .set_completions(prefix, candidates);
950 }
951}
952
953#[derive(Debug, Clone, Copy, Default)]
961pub struct EnterSearchForward;
962
963impl Command for EnterSearchForward {
964 fn id(&self) -> CommandId {
965 ids::ENTER_SEARCH_FORWARD
966 }
967
968 fn description(&self) -> &'static str {
969 "Enter search forward mode (/)"
970 }
971}
972
973impl CommandHandler for EnterSearchForward {
974 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
975 runtime
977 .ext_mut::<SearchState>()
978 .start_pending_search(Direction::Forward);
979 runtime
981 .ext_mut::<CmdlineState>()
982 .enter(CmdlinePrompt::SearchForward);
983 runtime.set_mode(VimMode::COMMANDLINE_ID, TransitionContext::new());
985 CommandResult::Success
986 }
987}
988
989#[derive(Debug, Clone, Copy, Default)]
993pub struct EnterSearchBackward;
994
995impl Command for EnterSearchBackward {
996 fn id(&self) -> CommandId {
997 ids::ENTER_SEARCH_BACKWARD
998 }
999
1000 fn description(&self) -> &'static str {
1001 "Enter search backward mode (?)"
1002 }
1003}
1004
1005impl CommandHandler for EnterSearchBackward {
1006 fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
1007 runtime
1009 .ext_mut::<SearchState>()
1010 .start_pending_search(Direction::Backward);
1011 runtime
1013 .ext_mut::<CmdlineState>()
1014 .enter(CmdlinePrompt::SearchBackward);
1015 runtime.set_mode(VimMode::COMMANDLINE_ID, TransitionContext::new());
1017 CommandResult::Success
1018 }
1019}