Skip to main content

reovim_module_vim/visual/
entry.rs

1//! Visual mode entry commands.
2//!
3//! Provides commands to enter the various visual selection modes:
4//! - `v` - Character-wise selection
5//! - `V` - Line-wise selection
6//! - `Ctrl-V` - Block (rectangular) selection
7
8use {
9    reovim_driver_command::{Command, CommandContext, CommandHandler, CommandResult},
10    reovim_driver_session::{
11        BufferApi, SessionRuntime, TransitionContext,
12        api::{ChangeTracker, ModeApi, Selection},
13    },
14    reovim_kernel::api::v1::{CommandId, Position},
15};
16
17use crate::{ids, modes::VimMode};
18
19/// Enter visual mode (character-wise selection).
20#[derive(Debug, Clone, Copy, Default)]
21pub struct EnterVisualMode;
22
23impl Command for EnterVisualMode {
24    fn id(&self) -> CommandId {
25        ids::ENTER_VISUAL
26    }
27
28    fn description(&self) -> &'static str {
29        "Enter visual mode (character-wise)"
30    }
31}
32
33impl CommandHandler for EnterVisualMode {
34    #[cfg_attr(coverage_nightly, coverage(off))]
35    fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
36        // Get current cursor position from per-client window
37        let Some(window) = runtime.windows().active() else {
38            tracing::warn!("EnterVisualMode: No active window");
39            return CommandResult::error("No active window");
40        };
41        let pos = Position::new(window.cursor.line, window.cursor.column);
42
43        // Start character-wise selection at current cursor position.
44        // Phase 8 (#465): Use exclusive end semantics - to include the character
45        // at the cursor, end must be cursor + 1.
46        let selection = Selection::character(pos, Position::new(pos.line, pos.column + 1));
47        tracing::debug!(?pos, ?selection, "EnterVisualMode: Setting selection");
48
49        if let Some(window) = runtime.windows_mut().active_mut() {
50            window.selection = Some(selection);
51        }
52
53        // #474: Notify other clients about new selection
54        if let Some(buffer_id) = runtime.active_buffer() {
55            runtime.record_selection_change(buffer_id);
56        }
57
58        // Verify selection was set
59        let sel_check = runtime.windows().active().and_then(|w| w.selection.clone());
60        tracing::debug!(?sel_check, "EnterVisualMode: Selection after set");
61
62        // Change to visual mode
63        runtime.set_mode(VimMode::VISUAL_ID, TransitionContext::new());
64
65        CommandResult::Success
66    }
67}
68
69/// Enter visual line mode (line-wise selection).
70#[derive(Debug, Clone, Copy, Default)]
71pub struct EnterVisualLineMode;
72
73impl Command for EnterVisualLineMode {
74    fn id(&self) -> CommandId {
75        ids::ENTER_VISUAL_LINE
76    }
77
78    fn description(&self) -> &'static str {
79        "Enter visual line mode"
80    }
81}
82
83impl CommandHandler for EnterVisualLineMode {
84    #[cfg_attr(coverage_nightly, coverage(off))]
85    fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
86        // Get current cursor position from per-client window
87        let Some(window) = runtime.windows().active() else {
88            return CommandResult::error("No active window");
89        };
90        let pos = Position::new(window.cursor.line, window.cursor.column);
91
92        // Start line-wise selection at current cursor position.
93        // Phase 8 (#465): Use exclusive end semantics - to select the current line,
94        // end.line must be pos.line + 1.
95        let selection = Selection::line(Position::new(pos.line, 0), Position::new(pos.line + 1, 0));
96
97        if let Some(window) = runtime.windows_mut().active_mut() {
98            window.selection = Some(selection);
99        }
100
101        // #474: Notify other clients about new selection
102        if let Some(buffer_id) = runtime.active_buffer() {
103            runtime.record_selection_change(buffer_id);
104        }
105
106        // Change to visual line mode
107        runtime.set_mode(VimMode::VISUAL_LINE_ID, TransitionContext::new());
108
109        CommandResult::Success
110    }
111}
112
113/// Enter visual block mode (rectangular selection).
114#[derive(Debug, Clone, Copy, Default)]
115pub struct EnterVisualBlockMode;
116
117impl Command for EnterVisualBlockMode {
118    fn id(&self) -> CommandId {
119        ids::ENTER_VISUAL_BLOCK
120    }
121
122    fn description(&self) -> &'static str {
123        "Enter visual block mode"
124    }
125}
126
127impl CommandHandler for EnterVisualBlockMode {
128    #[cfg_attr(coverage_nightly, coverage(off))]
129    fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
130        // Get current cursor position from per-client window
131        let Some(window) = runtime.windows().active() else {
132            return CommandResult::error("No active window");
133        };
134        let pos = Position::new(window.cursor.line, window.cursor.column);
135
136        // Start block selection at current cursor position.
137        // Phase 8 (#465): Use exclusive end semantics - to include the character
138        // at the cursor, end must be cursor + 1.
139        let selection = Selection::block(pos, Position::new(pos.line, pos.column + 1));
140
141        if let Some(window) = runtime.windows_mut().active_mut() {
142            window.selection = Some(selection);
143        }
144
145        // #474: Notify other clients about new selection
146        if let Some(buffer_id) = runtime.active_buffer() {
147            runtime.record_selection_change(buffer_id);
148        }
149
150        // Change to visual block mode
151        runtime.set_mode(VimMode::VISUAL_BLOCK_ID, TransitionContext::new());
152
153        CommandResult::Success
154    }
155}