Skip to main content

reovim_module_vim/
modes.rs

1//! Vim mode definitions.
2//!
3//! This module defines the `VimMode` enum implementing the kernel's `Mode` trait.
4//! All Vim modes are owned by this policy module - the kernel only provides the
5//! trait contract.
6//!
7//! # Mode Ownership
8//!
9//! This is the canonical definition of Vim modes. Previously, mode identities
10//! were scattered across kernel, runner, and modules. Now they live here.
11//!
12//! # Inheritance
13//!
14//! Modes form an inheritance hierarchy for keybinding fallback:
15//!
16//! ```text
17//! Normal ─┬─ Delete
18//!         ├─ Yank
19//!         ├─ Change
20//!         ├─ Window
21//!         └─ Visual ─┬─ VisualLine
22//!                    └─ VisualBlock
23//!
24//! Insert ─── Replace
25//!
26//! CommandLine (no parent)
27//! ```
28
29use reovim_kernel::api::v1::{CursorStyle, Mode, ModeId, ModuleId};
30
31/// The Vim module ID.
32pub const VIM_MODULE: ModuleId = ModuleId::new("vim");
33
34/// Vim editing modes.
35///
36/// Each variant represents a distinct Vim mode with specific behavior:
37/// - Cursor style (block, bar, underline)
38/// - Whether it accepts character input
39/// - Whether it has an active selection
40/// - Parent mode for keybinding inheritance
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42#[repr(u16)]
43pub enum VimMode {
44    /// Normal mode - default command mode.
45    Normal = 0,
46    /// Insert mode - text input mode.
47    Insert = 1,
48    /// Visual character mode - character-wise selection.
49    Visual = 2,
50    /// Visual line mode - line-wise selection.
51    VisualLine = 3,
52    /// Visual block mode - block/column selection.
53    VisualBlock = 4,
54    /// Replace mode - overwrite text.
55    Replace = 5,
56    /// Command line mode - ex commands.
57    CommandLine = 6,
58    /// Window mode - window management (`<C-w>` prefix).
59    Window = 8,
60    /// Delete operator mode - waiting for motion to delete.
61    Delete = 9,
62    /// Yank operator mode - waiting for motion to yank.
63    Yank = 10,
64    /// Change operator mode - waiting for motion to change.
65    Change = 11,
66    /// Lowercase operator mode - waiting for motion to lowercase.
67    Lowercase = 12,
68    /// Uppercase operator mode - waiting for motion to uppercase.
69    Uppercase = 13,
70    /// Toggle case operator mode - waiting for motion to toggle case.
71    ToggleCase = 14,
72}
73
74impl VimMode {
75    /// All Vim modes for registration.
76    pub const ALL: &'static [Self] = &[
77        Self::Normal,
78        Self::Insert,
79        Self::Visual,
80        Self::VisualLine,
81        Self::VisualBlock,
82        Self::Replace,
83        Self::CommandLine,
84        Self::Window,
85        Self::Delete,
86        Self::Yank,
87        Self::Change,
88        Self::Lowercase,
89        Self::Uppercase,
90        Self::ToggleCase,
91    ];
92
93    // ========================================================================
94    // Mode ID Constants
95    // ========================================================================
96
97    /// Mode ID for Normal mode.
98    pub const NORMAL_ID: ModeId = ModeId::with_discriminant(VIM_MODULE, "normal", 0);
99
100    /// Mode ID for Insert mode.
101    pub const INSERT_ID: ModeId = ModeId::with_discriminant(VIM_MODULE, "insert", 1);
102
103    /// Mode ID for Visual mode (character-wise).
104    pub const VISUAL_ID: ModeId = ModeId::with_discriminant(VIM_MODULE, "visual", 2);
105
106    /// Mode ID for Visual Line mode.
107    pub const VISUAL_LINE_ID: ModeId = ModeId::with_discriminant(VIM_MODULE, "visual-line", 3);
108
109    /// Mode ID for Visual Block mode.
110    pub const VISUAL_BLOCK_ID: ModeId = ModeId::with_discriminant(VIM_MODULE, "visual-block", 4);
111
112    /// Mode ID for Replace mode.
113    pub const REPLACE_ID: ModeId = ModeId::with_discriminant(VIM_MODULE, "replace", 5);
114
115    /// Mode ID for Command-line mode.
116    pub const COMMANDLINE_ID: ModeId = ModeId::with_discriminant(VIM_MODULE, "command", 6);
117
118    /// Mode ID for Window mode.
119    pub const WINDOW_ID: ModeId = ModeId::with_discriminant(VIM_MODULE, "window", 8);
120
121    /// Mode ID for Delete mode.
122    pub const DELETE_ID: ModeId = ModeId::with_discriminant(VIM_MODULE, "delete", 9);
123
124    /// Mode ID for Yank mode.
125    pub const YANK_ID: ModeId = ModeId::with_discriminant(VIM_MODULE, "yank", 10);
126
127    /// Mode ID for Change mode.
128    pub const CHANGE_ID: ModeId = ModeId::with_discriminant(VIM_MODULE, "change", 11);
129
130    /// Mode ID for Lowercase operator mode.
131    pub const LOWERCASE_ID: ModeId = ModeId::with_discriminant(VIM_MODULE, "lowercase", 12);
132
133    /// Mode ID for Uppercase operator mode.
134    pub const UPPERCASE_ID: ModeId = ModeId::with_discriminant(VIM_MODULE, "uppercase", 13);
135
136    /// Mode ID for Toggle Case operator mode.
137    pub const TOGGLE_CASE_ID: ModeId = ModeId::with_discriminant(VIM_MODULE, "toggle-case", 14);
138}
139
140impl Mode for VimMode {
141    fn module() -> ModuleId {
142        VIM_MODULE
143    }
144
145    fn discriminant(&self) -> u16 {
146        *self as u16
147    }
148
149    fn id(&self) -> ModeId {
150        // Use lowercase names for ModeId (for programmatic matching)
151        // display_name() returns uppercase for statusline display
152        let name = match self {
153            Self::Normal => "normal",
154            Self::Insert => "insert",
155            Self::Visual => "visual",
156            Self::VisualLine => "visual-line",
157            Self::VisualBlock => "visual-block",
158            Self::Replace => "replace",
159            Self::CommandLine => "command",
160            Self::Window => "window",
161            Self::Delete => "delete",
162            Self::Yank => "yank",
163            Self::Change => "change",
164            Self::Lowercase => "lowercase",
165            Self::Uppercase => "uppercase",
166            Self::ToggleCase => "toggle-case",
167        };
168        ModeId::with_discriminant(VIM_MODULE, name, self.discriminant())
169    }
170
171    fn display_name(&self) -> &'static str {
172        // Uppercase names for statusline display
173        match self {
174            Self::Normal => "NORMAL",
175            Self::Insert => "INSERT",
176            Self::Visual => "VISUAL",
177            Self::VisualLine => "V-LINE",
178            Self::VisualBlock => "V-BLOCK",
179            Self::Replace => "REPLACE",
180            Self::CommandLine => "COMMAND",
181            Self::Window => "WINDOW",
182            Self::Delete => "DELETE",
183            Self::Yank => "YANK",
184            Self::Change => "CHANGE",
185            Self::Lowercase => "LOWERCASE",
186            Self::Uppercase => "UPPERCASE",
187            Self::ToggleCase => "TOGGLE-CASE",
188        }
189    }
190
191    fn cursor_style(&self) -> CursorStyle {
192        match self {
193            Self::Normal | Self::Visual | Self::VisualLine | Self::VisualBlock => {
194                CursorStyle::Block
195            }
196            Self::Insert | Self::CommandLine => CursorStyle::Bar,
197            Self::Replace => CursorStyle::Underline,
198            Self::Window
199            | Self::Delete
200            | Self::Yank
201            | Self::Change
202            | Self::Lowercase
203            | Self::Uppercase
204            | Self::ToggleCase => CursorStyle::Block,
205        }
206    }
207
208    fn accepts_char_input(&self) -> bool {
209        matches!(self, Self::Insert | Self::Replace | Self::CommandLine)
210    }
211
212    fn has_selection(&self) -> bool {
213        matches!(self, Self::Visual | Self::VisualLine | Self::VisualBlock)
214    }
215
216    fn inherits_from(&self) -> Option<Self> {
217        match self {
218            // Operator modes, window mode, and visual inherit from normal
219            Self::Window
220            | Self::Delete
221            | Self::Yank
222            | Self::Change
223            | Self::Lowercase
224            | Self::Uppercase
225            | Self::ToggleCase
226            | Self::Visual => Some(Self::Normal),
227            Self::VisualLine | Self::VisualBlock => Some(Self::Visual),
228            Self::Replace => Some(Self::Insert),
229            Self::Normal | Self::Insert | Self::CommandLine => None,
230        }
231    }
232
233    fn is_entry(&self) -> bool {
234        matches!(self, Self::Normal)
235    }
236}