reovim_module_vim/ids.rs
1//! Command and operator ID constants for the vim module.
2//!
3//! The vim module defines mode-switching commands, operators, and vim-specific
4//! behavior. These constants enable compile-time verification of IDs
5//! referenced in keybindings.
6//!
7//! # Operators
8//!
9//! Vim operators (d, y, c) are vim-specific policy, not kernel mechanism.
10//! The kernel has ZERO vim knowledge - operators live here in the vim module.
11//!
12//! ```
13//! use reovim_module_vim::ids::{OperatorId, DELETE, YANK, CHANGE};
14//!
15//! assert_eq!(DELETE.name(), "delete");
16//! assert_eq!(YANK.name(), "yank");
17//! assert_eq!(CHANGE.name(), "change");
18//! ```
19
20use std::{borrow::Cow, fmt};
21
22use reovim_kernel::api::v1::{CommandId, ModuleId};
23
24/// Vim module ID.
25pub const MODULE: ModuleId = ModuleId::new("vim");
26
27// =============================================================================
28// Operator ID Type
29// =============================================================================
30
31/// Namespaced operator identifier.
32///
33/// Operators are identified by their owning module and a local name.
34/// This prevents naming conflicts between modules and provides type-safe
35/// operator references instead of string-based identification.
36///
37/// # Note
38///
39/// Operators (d, y, c) are vim-specific policy. The kernel has ZERO vim
40/// knowledge - this type lives in the vim module, not the kernel.
41///
42/// # Example
43///
44/// ```
45/// use reovim_module_vim::ids::{OperatorId, MODULE, DELETE};
46///
47/// let delete = OperatorId::new(MODULE, "delete");
48/// assert_eq!(delete.name(), "delete");
49/// assert_eq!(delete.module().as_str(), "vim");
50///
51/// // Or use the pre-defined constant
52/// assert_eq!(DELETE.name(), "delete");
53/// ```
54#[derive(Debug, Clone, PartialEq, Eq, Hash)]
55pub struct OperatorId {
56 /// The module that owns this operator.
57 module: ModuleId,
58 /// The local name within the module.
59 name: Cow<'static, str>,
60}
61
62impl OperatorId {
63 /// Create a new operator identifier from static strings.
64 #[must_use]
65 pub const fn new(module: ModuleId, name: &'static str) -> Self {
66 Self {
67 module,
68 name: Cow::Borrowed(name),
69 }
70 }
71
72 /// Create an operator identifier from a qualified string like "module:operator".
73 ///
74 /// This method is intended for dynamic use cases like FFI where operator IDs
75 /// are specified as strings at runtime.
76 ///
77 /// If the string doesn't contain ':', the entire string is treated as the
78 /// operator name with "unknown" as the module.
79 #[must_use]
80 pub fn from_qualified(qualified: String) -> Self {
81 let (module_str, name_str) = if let Some(idx) = qualified.find(':') {
82 (qualified[..idx].to_string(), qualified[idx + 1..].to_string())
83 } else {
84 ("unknown".to_string(), qualified)
85 };
86
87 Self {
88 module: ModuleId::from_string(module_str),
89 name: Cow::Owned(name_str),
90 }
91 }
92
93 /// Get the owning module.
94 #[must_use]
95 pub const fn module(&self) -> &ModuleId {
96 &self.module
97 }
98
99 /// Get the local name.
100 #[must_use]
101 pub fn name(&self) -> &str {
102 &self.name
103 }
104
105 /// Get the local name as an owned `Cow`.
106 #[must_use]
107 pub fn name_owned(&self) -> Cow<'static, str> {
108 self.name.clone()
109 }
110}
111
112impl fmt::Display for OperatorId {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 write!(f, "{}:{}", self.module, self.name)
115 }
116}
117
118// =============================================================================
119// Operator Constants
120// =============================================================================
121
122/// Delete operator - removes text and saves to register.
123///
124/// Vim equivalent: `d`
125pub const DELETE: OperatorId = OperatorId::new(MODULE, "delete");
126
127/// Yank operator - copies text to register without modification.
128///
129/// Vim equivalent: `y`
130pub const YANK: OperatorId = OperatorId::new(MODULE, "yank");
131
132/// Change operator - deletes text and enters insert mode.
133///
134/// Vim equivalent: `c`
135pub const CHANGE: OperatorId = OperatorId::new(MODULE, "change");
136
137/// Lowercase operator - converts text to lowercase.
138///
139/// Vim equivalent: `gu`
140pub const LOWERCASE: OperatorId = OperatorId::new(MODULE, "lowercase");
141
142/// Uppercase operator - converts text to uppercase.
143///
144/// Vim equivalent: `gU`
145pub const UPPERCASE: OperatorId = OperatorId::new(MODULE, "uppercase");
146
147/// Toggle case operator - swaps uppercase/lowercase.
148///
149/// Vim equivalent: `g~`
150pub const TOGGLE_CASE_OP: OperatorId = OperatorId::new(MODULE, "toggle-case");
151
152// =============================================================================
153// Mode Switching - Insert
154// =============================================================================
155
156/// Enter insert mode (i).
157pub const ENTER_INSERT: CommandId = CommandId::new(MODULE, "enter-insert");
158
159/// Enter insert mode after cursor (a).
160pub const ENTER_INSERT_AFTER: CommandId = CommandId::new(MODULE, "enter-insert-after");
161
162/// Enter insert mode at end of line (A).
163pub const ENTER_INSERT_EOL: CommandId = CommandId::new(MODULE, "enter-insert-eol");
164
165/// Enter insert mode at first non-blank (I).
166pub const ENTER_INSERT_BOL: CommandId = CommandId::new(MODULE, "enter-insert-bol");
167
168/// Open line below and enter insert (o).
169pub const OPEN_LINE_BELOW: CommandId = CommandId::new(MODULE, "open-line-below");
170
171/// Open line above and enter insert (O).
172pub const OPEN_LINE_ABOVE: CommandId = CommandId::new(MODULE, "open-line-above");
173
174/// Exit insert mode to normal (Esc).
175pub const EXIT_INSERT: CommandId = CommandId::new(MODULE, "exit-insert");
176
177// =============================================================================
178// Mode Switching - Visual
179// =============================================================================
180
181/// Enter visual mode (v).
182pub const ENTER_VISUAL: CommandId = CommandId::new(MODULE, "enter-visual");
183
184/// Enter visual line mode (V).
185pub const ENTER_VISUAL_LINE: CommandId = CommandId::new(MODULE, "enter-visual-line");
186
187/// Enter visual block mode (Ctrl-v).
188pub const ENTER_VISUAL_BLOCK: CommandId = CommandId::new(MODULE, "enter-visual-block");
189
190/// Exit visual mode (Esc).
191pub const EXIT_VISUAL: CommandId = CommandId::new(MODULE, "exit-visual");
192
193// =============================================================================
194// Mode Switching - Other
195// =============================================================================
196
197/// Enter command-line mode (:).
198pub const ENTER_COMMANDLINE: CommandId = CommandId::new(MODULE, "enter-commandline");
199
200/// Cancel command-line mode without executing (Esc).
201pub const CANCEL_COMMANDLINE: CommandId = CommandId::new(MODULE, "cancel-commandline");
202
203/// Execute command-line and exit (Enter).
204pub const EXIT_COMMANDLINE: CommandId = CommandId::new(MODULE, "exit-commandline");
205
206// Command-line editing commands (#451)
207
208/// Move cursor left in command-line.
209pub const CMDLINE_CURSOR_LEFT: CommandId = CommandId::new(MODULE, "cmdline-cursor-left");
210/// Move cursor right in command-line.
211pub const CMDLINE_CURSOR_RIGHT: CommandId = CommandId::new(MODULE, "cmdline-cursor-right");
212/// Move cursor to start of command-line.
213pub const CMDLINE_CURSOR_HOME: CommandId = CommandId::new(MODULE, "cmdline-cursor-home");
214/// Move cursor to end of command-line.
215pub const CMDLINE_CURSOR_END: CommandId = CommandId::new(MODULE, "cmdline-cursor-end");
216/// Delete character at cursor in command-line.
217pub const CMDLINE_DELETE_CHAR: CommandId = CommandId::new(MODULE, "cmdline-delete-char");
218/// Delete character before cursor in command-line.
219pub const CMDLINE_BACKSPACE: CommandId = CommandId::new(MODULE, "cmdline-backspace");
220/// Delete word before cursor in command-line.
221pub const CMDLINE_DELETE_WORD: CommandId = CommandId::new(MODULE, "cmdline-delete-word");
222/// Delete to start of command-line.
223pub const CMDLINE_DELETE_TO_START: CommandId = CommandId::new(MODULE, "cmdline-delete-to-start");
224/// Navigate to older history entry.
225pub const CMDLINE_HISTORY_UP: CommandId = CommandId::new(MODULE, "cmdline-history-up");
226/// Navigate to newer history entry.
227pub const CMDLINE_HISTORY_DOWN: CommandId = CommandId::new(MODULE, "cmdline-history-down");
228/// Cycle to next completion.
229pub const CMDLINE_COMPLETE_NEXT: CommandId = CommandId::new(MODULE, "cmdline-complete-next");
230/// Cycle to previous completion.
231pub const CMDLINE_COMPLETE_PREV: CommandId = CommandId::new(MODULE, "cmdline-complete-prev");
232
233/// Enter window mode (Ctrl-w).
234pub const ENTER_WINDOW_MODE: CommandId = CommandId::new(MODULE, "enter-window-mode");
235
236/// Enter replace mode (R).
237///
238/// Switches to replace mode where typed characters overwrite existing text.
239/// Backspace restores original characters.
240pub const ENTER_REPLACE_MODE: CommandId = CommandId::new(MODULE, "enter-replace-mode");
241
242/// Backspace in replace mode.
243///
244/// Restores the original character that was overwritten by replace mode.
245/// Pops from the replace restore stack in `VimSessionState`.
246pub const REPLACE_BACKSPACE: CommandId = CommandId::new(MODULE, "replace-backspace");
247
248/// Cancel and return to normal mode (no cursor adjustment).
249///
250/// Used by operator modes (delete, yank, change) when escape is pressed.
251/// Unlike `EXIT_INSERT`, this does not adjust cursor position.
252pub const CANCEL_TO_NORMAL: CommandId = CommandId::new(MODULE, "cancel-to-normal");
253
254// =============================================================================
255// Search Mode Entry (#435)
256// =============================================================================
257
258/// Enter search forward mode (/).
259///
260/// Sets pending search direction to Forward and enters command-line mode.
261pub const ENTER_SEARCH_FORWARD: CommandId = CommandId::new(MODULE, "enter-search-forward");
262
263/// Enter search backward mode (?).
264///
265/// Sets pending search direction to Backward and enters command-line mode.
266pub const ENTER_SEARCH_BACKWARD: CommandId = CommandId::new(MODULE, "enter-search-backward");
267
268// =============================================================================
269// Find-Char Motion Execution (Epic #385 - Resolver-based)
270// =============================================================================
271
272/// Execute find-char motion with character from context.
273///
274/// This command is called by the resolver when `pending_char` is set and a
275/// character key is pressed. The character and direction are passed via
276/// command context metadata:
277/// - `find_char`: The target character (`ArgValue::Char`)
278/// - `find_direction`: "forward" or "backward" (`ArgValue::String`)
279/// - `find_inclusive`: true for f/F, false for t/T (`ArgValue::Bang`)
280pub const EXECUTE_FIND_CHAR: CommandId = CommandId::new(MODULE, "execute-find-char");
281
282// =============================================================================
283// Visual Mode Manipulation
284// =============================================================================
285
286/// Swap cursor and anchor in visual mode (o).
287pub const VISUAL_SWAP_ANCHOR: CommandId = CommandId::new(MODULE, "visual-swap-anchor");
288
289/// Toggle to visual char mode.
290pub const TOGGLE_VISUAL_CHAR: CommandId = CommandId::new(MODULE, "toggle-visual-char");
291
292/// Toggle to visual line mode.
293pub const TOGGLE_VISUAL_LINE: CommandId = CommandId::new(MODULE, "toggle-visual-line");
294
295/// Toggle to visual block mode.
296pub const TOGGLE_VISUAL_BLOCK: CommandId = CommandId::new(MODULE, "toggle-visual-block");
297
298/// Reselect last visual selection (gv).
299pub const RESELECT_LAST: CommandId = CommandId::new(MODULE, "reselect-last");
300
301// =============================================================================
302// Visual Mode Operators
303// =============================================================================
304
305/// Delete selection (d, x in visual).
306pub const DELETE_SELECTION: CommandId = CommandId::new(MODULE, "delete-selection");
307
308/// Yank selection (y in visual).
309pub const YANK_SELECTION: CommandId = CommandId::new(MODULE, "yank-selection");
310
311/// Change selection (c, s in visual).
312pub const CHANGE_SELECTION: CommandId = CommandId::new(MODULE, "change-selection");
313
314/// Indent selection (> in visual).
315pub const INDENT_SELECTION: CommandId = CommandId::new(MODULE, "indent-selection");
316
317/// Dedent selection (< in visual).
318pub const DEDENT_SELECTION: CommandId = CommandId::new(MODULE, "dedent-selection");
319
320// =============================================================================
321// Change Operations (vim-specific, enters insert after)
322// =============================================================================
323
324/// Change line (cc, S).
325pub const CHANGE_LINE: CommandId = CommandId::new(MODULE, "change-line");
326
327/// Change to end of line (C).
328pub const CHANGE_TO_EOL: CommandId = CommandId::new(MODULE, "change-to-eol");
329
330// =============================================================================
331// Dot Repeat (Epic #465)
332// =============================================================================
333
334/// Repeat last change (.).
335///
336/// Replays the last change operation (operator + motion/text object, or
337/// insert mode edits). Count overrides the original count.
338pub const DOT_REPEAT: CommandId = CommandId::new(MODULE, "dot-repeat");
339
340// =============================================================================
341// Macro Recording/Playback (Epic #465 Phase 8D)
342// =============================================================================
343
344/// Execute macro from register (@{a-z}).
345///
346/// Plays back a recorded macro from the specified register.
347/// The register is provided in the resolve context metadata.
348pub const PLAY_MACRO: CommandId = CommandId::new(MODULE, "play-macro");
349
350/// Repeat last macro (@@).
351///
352/// Plays the same macro that was last played with @{a-z}.
353pub const REPEAT_MACRO: CommandId = CommandId::new(MODULE, "repeat-macro");
354
355// =============================================================================
356// Session Management (not yet implemented)
357// =============================================================================
358
359/// Detach from server (server continues).
360pub const SESSION_DETACH: CommandId = CommandId::new(MODULE, "session-detach");
361
362/// List running server instances.
363pub const SESSION_SERVERS: CommandId = CommandId::new(MODULE, "session-servers");
364
365/// Kill the current server.
366pub const SESSION_KILL_SERVER: CommandId = CommandId::new(MODULE, "session-kill-server");
367
368// =============================================================================
369// Visual Mode Additional Operations (not yet implemented)
370// =============================================================================
371
372/// Swap cursor to opposite corner in block mode.
373pub const VISUAL_SWAP_CORNER: CommandId = CommandId::new(MODULE, "visual-swap-corner");
374
375/// Toggle case of selection.
376pub const TOGGLE_CASE_SELECTION: CommandId = CommandId::new(MODULE, "toggle-case-selection");
377
378/// Lowercase selection.
379pub const LOWERCASE_SELECTION: CommandId = CommandId::new(MODULE, "lowercase-selection");
380
381/// Uppercase selection.
382pub const UPPERCASE_SELECTION: CommandId = CommandId::new(MODULE, "uppercase-selection");
383
384/// Join selected lines.
385pub const JOIN_SELECTION: CommandId = CommandId::new(MODULE, "join-selection");
386
387/// Enter command mode with selection range.
388pub const COMMAND_WITH_SELECTION: CommandId = CommandId::new(MODULE, "command-with-selection");
389
390/// No-op for blocked keys in visual mode.
391pub const VISUAL_NOOP: CommandId = CommandId::new(MODULE, "visual-noop");
392
393/// Exit visual, move to line start, enter insert.
394pub const VISUAL_INSERT_START: CommandId = CommandId::new(MODULE, "visual-insert-start");
395
396/// Exit visual, move to line end, enter insert.
397pub const VISUAL_INSERT_END: CommandId = CommandId::new(MODULE, "visual-insert-end");
398
399/// Insert at block left column on all lines.
400pub const BLOCK_INSERT_START: CommandId = CommandId::new(MODULE, "block-insert-start");
401
402/// Append at block right column on all lines.
403pub const BLOCK_INSERT_END: CommandId = CommandId::new(MODULE, "block-insert-end");
404
405// =============================================================================
406// Text Objects (not yet implemented - will move to textobjects module)
407// =============================================================================
408
409/// Inner word text object.
410pub const INNER_WORD: CommandId = CommandId::new(MODULE, "inner-word");
411/// Inner WORD text object.
412pub const INNER_WORD_BIG: CommandId = CommandId::new(MODULE, "inner-word-big");
413/// Inner double quote text object.
414pub const INNER_DOUBLE_QUOTE: CommandId = CommandId::new(MODULE, "inner-double-quote");
415/// Inner single quote text object.
416pub const INNER_SINGLE_QUOTE: CommandId = CommandId::new(MODULE, "inner-single-quote");
417/// Inner backtick text object.
418pub const INNER_BACKTICK: CommandId = CommandId::new(MODULE, "inner-backtick");
419/// Inner parentheses text object.
420pub const INNER_PAREN: CommandId = CommandId::new(MODULE, "inner-paren");
421/// Inner brackets text object.
422pub const INNER_BRACKET: CommandId = CommandId::new(MODULE, "inner-bracket");
423/// Inner braces text object.
424pub const INNER_BRACE: CommandId = CommandId::new(MODULE, "inner-brace");
425/// Inner angle brackets text object.
426pub const INNER_ANGLE: CommandId = CommandId::new(MODULE, "inner-angle");
427/// Inner tag text object.
428pub const INNER_TAG: CommandId = CommandId::new(MODULE, "inner-tag");
429/// Inner sentence text object.
430pub const INNER_SENTENCE: CommandId = CommandId::new(MODULE, "inner-sentence");
431/// Inner paragraph text object.
432pub const INNER_PARAGRAPH: CommandId = CommandId::new(MODULE, "inner-paragraph");
433
434/// Around word text object.
435pub const AROUND_WORD: CommandId = CommandId::new(MODULE, "around-word");
436/// Around WORD text object.
437pub const AROUND_WORD_BIG: CommandId = CommandId::new(MODULE, "around-word-big");
438/// Around double quote text object.
439pub const AROUND_DOUBLE_QUOTE: CommandId = CommandId::new(MODULE, "around-double-quote");
440/// Around single quote text object.
441pub const AROUND_SINGLE_QUOTE: CommandId = CommandId::new(MODULE, "around-single-quote");
442/// Around backtick text object.
443pub const AROUND_BACKTICK: CommandId = CommandId::new(MODULE, "around-backtick");
444/// Around parentheses text object.
445pub const AROUND_PAREN: CommandId = CommandId::new(MODULE, "around-paren");
446/// Around brackets text object.
447pub const AROUND_BRACKET: CommandId = CommandId::new(MODULE, "around-bracket");
448/// Around braces text object.
449pub const AROUND_BRACE: CommandId = CommandId::new(MODULE, "around-brace");
450/// Around angle brackets text object.
451pub const AROUND_ANGLE: CommandId = CommandId::new(MODULE, "around-angle");
452/// Around tag text object.
453pub const AROUND_TAG: CommandId = CommandId::new(MODULE, "around-tag");
454/// Around sentence text object.
455pub const AROUND_SENTENCE: CommandId = CommandId::new(MODULE, "around-sentence");
456/// Around paragraph text object.
457pub const AROUND_PARAGRAPH: CommandId = CommandId::new(MODULE, "around-paragraph");
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462
463 // === Coverage: lines 107-109 — OperatorId::name_owned() ===
464
465 #[test]
466 fn operator_id_name_owned_static() {
467 // Constant uses Cow::Borrowed — name_owned() clones the Cow.
468 let owned = DELETE.name_owned();
469 assert_eq!(owned, "delete");
470 }
471
472 #[test]
473 fn operator_id_name_owned_dynamic() {
474 // from_qualified uses Cow::Owned — name_owned() clones the Cow.
475 let op = OperatorId::from_qualified("mymod:custom-op".to_string());
476 let owned = op.name_owned();
477 assert_eq!(owned, "custom-op");
478 }
479}