Skip to main content

reovim_module_vim/
lib.rs

1#![cfg_attr(coverage_nightly, allow(unused_features))]
2#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
3//! Vim policy module.
4//!
5//! This module provides Vim-style behavior for reovim:
6//! - **Keybindings**: Standard Vim keys (hjkl, operators, modes)
7//! - **Operators**: Vim operators (d, y, c) - delete, yank, change
8//! - **Resolvers**: Mode-specific key interpretation (counts, registers)
9//! - **Visual mode**: Entry, exit, manipulation, operators
10//! - **Policy**: Vim lookup behavior (wait for longer sequences)
11//!
12//! # Architecture
13//!
14//! This is a **POLICY** module - it defines HOW the editor behaves.
15//! The kernel and drivers provide the mechanisms (WHAT can be done).
16//!
17//! ```text
18//! ┌─────────────────────────────────────────────────────────┐
19//! │  VIM POLICY MODULE (this module)           POLICY       │
20//! │  → Vim keybindings (hjkl, dd, etc.)                     │
21//! │  → Vim operators (d, y, c with motions)                 │
22//! │  → Vim resolvers (count/register handling)              │
23//! │  → Visual mode commands (selection operations)          │
24//! │  → Vim behavior (wait for longer sequences)             │
25//! ├─────────────────────────────────────────────────────────┤
26//! │  MECHANISM MODULES                         CAPABILITIES │
27//! │  editor/, keymap/, motions/                             │
28//! │  → "What operations are possible"                       │
29//! └─────────────────────────────────────────────────────────┘
30//! ```
31//!
32//! # Example
33//!
34//! ```ignore
35//! use reovim_module_vim::VimModule;
36//!
37//! let module = VimModule::new();
38//! let bindings = module.keybindings();
39//! // Returns ~180 Vim keybindings
40//! ```
41
42use std::sync::Arc;
43
44use {
45    reovim_driver_annotation::{AnnotationSourceKey, AnnotationSourceRegistry, LineNumberMode},
46    reovim_driver_command::{CommandHandler, CommandHandlerStore, CommandProvider},
47    reovim_driver_input::{
48        KeybindingStore, LookupPolicyStore, ModeInfo, ModeInfoStore, ModeProviderKey,
49        ModeProviderRegistry, ResolverRegistry,
50    },
51    reovim_driver_manifest::ModeBridgeStore,
52    reovim_driver_session::{InitialModeProvider, LeaderKeyProvider, bridges::BridgeProvider},
53    reovim_kernel::api::v1::{
54        KeybindingRegistration, ModeId, Module, ModuleContext, ModuleError, ModuleId, ProbeResult,
55        Version, pr_info,
56    },
57};
58
59pub mod annotation;
60pub mod bindings;
61pub mod commands;
62pub mod fallback;
63pub mod ids;
64pub mod macros;
65pub mod modes;
66pub mod operators;
67pub mod providers;
68pub mod resolvers;
69pub mod session_state;
70pub mod vim_lookup_policy;
71pub mod visual;
72
73#[cfg(test)]
74mod ids_tests;
75#[cfg(test)]
76mod lib_tests;
77#[cfg(test)]
78mod macros_tests;
79#[cfg(test)]
80mod modes_tests;
81#[cfg(test)]
82mod providers_tests;
83#[cfg(test)]
84mod registry_integration;
85#[cfg(test)]
86mod session_state_tests;
87#[cfg(test)]
88mod vim_lookup_policy_tests;
89
90// Re-export mode types (Epic #372 - Mode Ownership)
91pub use modes::{VIM_MODULE, VimMode};
92
93// Re-export provider types (Epic #415 - Module provider hooks)
94pub use providers::{VimDefaultModeProvider, VimModuleProviderExt};
95
96// Re-export session state (Epic #385 - Server Simplification)
97pub use session_state::{PendingCharOp, VimSessionState};
98
99// Re-export OperatorId (Epic #385 - operators are vim policy, not kernel mechanism)
100pub use ids::{CHANGE, DELETE, OperatorId, YANK};
101
102// Re-export operators (Epic #385 - operators are vim-specific, merged from operators module)
103pub use operators::{
104    ChangeCommand, ChangeOperator, DeleteCommand, DeleteOperator, Operator, OperatorContext,
105    OperatorError, Range, YankCommand, YankOperator, operator_commands,
106};
107
108// Re-export fallback handler (Epic #372 - Mode Ownership)
109pub use fallback::VimFallbackHandler;
110
111// Re-export Vim lookup policy (#542 - mechanism/policy separation)
112pub use vim_lookup_policy::VimLookupPolicy;
113
114// Re-export mode commands (Epic #372 - Mode Ownership)
115pub use commands::{
116    ChangeLine, ChangeToEndOfLine, EnterCommandLineMode, EnterInsertEndOfLine,
117    EnterInsertFirstNonBlank, EnterInsertMode, EnterInsertModeAppend, EnterReplaceMode,
118    EnterSearchBackward, EnterSearchForward, EnterWindowMode, ExecuteFindChar, ExitCommandLineMode,
119    ExitToNormal, OpenLineAbove, OpenLineBelow, ReplaceBackspace,
120};
121
122// Re-export resolvers
123pub use resolvers::{
124    VimCaseResolver, VimChangeResolver, VimCommandLineResolver, VimDeleteResolver,
125    VimInsertResolver, VimNormalResolver, VimReplaceResolver, VimVisualResolver, VimYankResolver,
126};
127
128// Re-export visual mode commands
129pub use visual::{
130    // Operators
131    ChangeSelection,
132    DedentSelection,
133    DeleteSelection,
134    // Entry
135    EnterVisualBlockMode,
136    EnterVisualLineMode,
137    EnterVisualMode,
138    // Exit
139    ExitVisualMode,
140    IndentSelection,
141    LowercaseSelection,
142    // Manipulation
143    ReselectLast,
144    SwapAnchor,
145    ToggleCaseSelection,
146    ToggleVisualBlock,
147    ToggleVisualChar,
148    ToggleVisualLine,
149    UppercaseSelection,
150    YankSelection,
151    // Helper functions
152    visual_commands,
153    visual_entry_commands,
154    visual_exit_commands,
155    visual_operator_commands,
156    visual_selection_commands,
157};
158
159/// Vim policy module.
160///
161/// Provides standard Vim keybindings and behavior.
162/// This module is stateless - all state is managed by the kernel.
163pub struct VimModule;
164
165impl VimModule {
166    /// Create a new Vim module.
167    #[must_use]
168    pub const fn new() -> Self {
169        Self
170    }
171}
172
173#[cfg_attr(coverage_nightly, coverage(off))]
174impl Default for VimModule {
175    fn default() -> Self {
176        Self::new()
177    }
178}
179
180#[cfg_attr(coverage_nightly, coverage(off))]
181impl Module for VimModule {
182    fn id(&self) -> ModuleId {
183        ModuleId::new("vim")
184    }
185
186    fn name(&self) -> &'static str {
187        "Vim"
188    }
189
190    fn version(&self) -> Version {
191        Version::new(0, 9, 0)
192    }
193
194    fn dependencies(&self) -> Vec<ModuleId> {
195        vec![ModuleId::new("editor"), ModuleId::new("motions")]
196    }
197
198    #[cfg_attr(coverage_nightly, coverage(off))]
199    fn init(&mut self, ctx: &ModuleContext) -> ProbeResult {
200        // Register YankFlashBridge via BridgeProvider (#657)
201        let provider = ctx.services.get_or_create::<BridgeProvider>();
202        provider.register(operators::yank_flash::YankFlashBridge);
203
204        // Register default mode provider with typed key (Epic #417)
205        let mode_registry = ctx.services.get_or_create::<ModeProviderRegistry>();
206        mode_registry.register(ModeProviderKey::Entry, Arc::new(VimDefaultModeProvider::new()));
207
208        // Epic #417 Part 3: Self-register modes
209        let mode_store = ctx.services.get_or_create::<ModeInfoStore>();
210        for mode in VimMode::ALL {
211            mode_store.add(ModeInfo::from_mode(*mode));
212        }
213
214        // Epic #417 Part 3: Self-register resolvers
215        let resolver_registry = ctx.services.get_or_create::<ResolverRegistry>();
216        resolver_registry.register(resolvers::VimNormalResolver::new());
217        resolver_registry.register(resolvers::VimInsertResolver::new());
218        resolver_registry.register(resolvers::VimDeleteResolver::new());
219        resolver_registry.register(resolvers::VimYankResolver::new());
220        resolver_registry.register(resolvers::VimChangeResolver::new());
221        resolver_registry.register(resolvers::VimCommandLineResolver::new());
222        // Epic #438: Window mode resolver
223        resolver_registry.register(resolvers::VimWindowResolver::new());
224        // #465 Bug fix: Visual mode resolvers (character, line, block)
225        resolver_registry.register(resolvers::VimVisualResolver::character_wise());
226        resolver_registry.register(resolvers::VimVisualResolver::line_wise());
227        resolver_registry.register(resolvers::VimVisualResolver::block_wise());
228
229        // Replace mode resolver (#666)
230        resolver_registry.register(resolvers::VimReplaceResolver::new());
231
232        // Case operator resolvers (#666)
233        resolver_registry.register(resolvers::VimCaseResolver::new(
234            resolvers::operator_common::OperatorType::Lowercase,
235        ));
236        resolver_registry.register(resolvers::VimCaseResolver::new(
237            resolvers::operator_common::OperatorType::Uppercase,
238        ));
239        resolver_registry.register(resolvers::VimCaseResolver::new(
240            resolvers::operator_common::OperatorType::ToggleCase,
241        ));
242
243        // #620: Self-register lookup policy (decouples bootstrap from vim import)
244        let policy_store = ctx.services.get_or_create::<LookupPolicyStore>();
245        policy_store.set(Arc::new(VimLookupPolicy));
246
247        // #623: Register initial mode for cross-personality support
248        let initial_mode_provider = ctx.services.get_or_create::<InitialModeProvider>();
249        if let Some(prev) = initial_mode_provider.set(ModeId::new(VIM_MODULE, "normal")) {
250            tracing::warn!(previous = %prev, "VimModule overrode existing initial mode");
251        }
252
253        // #700: Register leader key for personality-aware binding expansion
254        let leader_provider = ctx.services.get_or_create::<LeaderKeyProvider>();
255        if let Some(prev) = leader_provider.set("<Space>") {
256            tracing::warn!(previous = prev, "VimModule overrode existing leader key");
257        }
258
259        // Epic #417 Part 3: Self-register commands
260        let command_store = ctx.services.get_or_create::<CommandHandlerStore>();
261        for handler in self.command_handlers() {
262            command_store.add(handler);
263        }
264
265        // Epic #417 Part 3: Self-register keybindings
266        let keybinding_store = ctx.services.get_or_create::<KeybindingStore>();
267        keybinding_store.add_all(self.keybindings());
268
269        // #585/#610: Load personality manifest (keybindings, mode bridges, options)
270        if let Err(e) = load_personality_manifest(ctx, &keybinding_store) {
271            return ProbeResult::Failed(e);
272        }
273
274        // Register annotation source (display-side creates presenters)
275        let source_registry = ctx.services.get_or_create::<AnnotationSourceRegistry>();
276        source_registry.register(
277            AnnotationSourceKey::new("builtin.line_number"),
278            annotation::create_line_number_source(LineNumberMode::Absolute),
279        );
280
281        tracing::info!("VimModule: registered LineNumberSource in AnnotationSourceRegistry");
282
283        pr_info!("Vim module initialized");
284        ProbeResult::Success
285    }
286
287    fn exit(&mut self) -> Result<(), ModuleError> {
288        pr_info!("Vim module exiting");
289        Ok(())
290    }
291
292    fn provides(&self) -> &[&'static str] {
293        &[reovim_capabilities::MODE_MANAGEMENT]
294    }
295
296    fn extension_kinds(&self) -> &[&'static str] {
297        &[operators::yank_flash::KIND]
298    }
299
300    #[cfg_attr(coverage_nightly, coverage(off))]
301    fn keybindings(&self) -> Vec<KeybindingRegistration> {
302        bindings::all()
303    }
304}
305
306impl CommandProvider for VimModule {
307    fn command_handlers(&self) -> Vec<Box<dyn CommandHandler>> {
308        let mut handlers = Vec::new();
309        handlers.extend(commands::mode_commands());
310        handlers.extend(visual::visual_commands());
311        handlers.extend(operators::operator_commands()); // Epic #415
312        handlers
313    }
314}
315
316// ============================================================================
317// Personality manifest loading (#585)
318// ============================================================================
319
320/// Embedded vim personality manifest.
321const VIM_MANIFEST_TOML: &str = include_str!("../data/vim.toml");
322
323/// Load the vim personality manifest and register its keybindings and mode bridges.
324///
325/// Keybindings from the manifest are registered for all declared bindings.
326/// Command availability is validated at key-dispatch time (feature modules may
327/// not be loaded yet when `VimModule` initializes). Mode bridges are stored in
328/// `ModeBridgeStore` for feature modules to query during their `init()`.
329///
330/// Startup validation (#585 Phase 6):
331/// - Detects key conflicts within the manifest (same key + same mode)
332/// - Logs non-fatal warnings — does not fail initialization
333#[cfg_attr(coverage_nightly, coverage(off))]
334fn load_personality_manifest(
335    ctx: &ModuleContext,
336    keybinding_store: &KeybindingStore,
337) -> Result<(), ModuleError> {
338    let manifest = match reovim_driver_manifest::PersonalityManifest::parse(VIM_MANIFEST_TOML) {
339        Ok(m) => m,
340        Err(e) => {
341            return Err(ModuleError::InitFailed(format!(
342                "Failed to parse vim.toml personality manifest: {e}"
343            )));
344        }
345    };
346
347    // #585 Phase 6: Startup validation — detect key conflicts within the manifest
348    let conflicts = manifest.detect_conflicts();
349    for warning in &conflicts {
350        tracing::warn!("vim.toml: {warning}");
351    }
352
353    // Register manifest keybindings (all bindings — command availability is
354    // validated at key-dispatch time, not at registration time, because
355    // feature modules may not have loaded yet)
356    let registrations = manifest.to_keybinding_registrations();
357    let count = registrations.len();
358    keybinding_store.add_all(registrations);
359    tracing::info!(count, "VimModule: loaded personality manifest keybindings");
360
361    // #610: Register manifest-driven options (before mode_bridges move)
362    let option_specs = manifest.to_option_specs(&VIM_MODULE);
363
364    // Populate ModeBridgeStore for feature modules
365    let bridge_store = ModeBridgeStore::new(manifest.mode_bridges);
366    ctx.services.register(Arc::new(bridge_store));
367    tracing::info!("VimModule: registered ModeBridgeStore");
368
369    // #610: Register options (replaces hardcoded vim_option_specs)
370    for spec in option_specs {
371        ctx.kernel.options.register(spec).map_err(|e| {
372            ModuleError::InitFailed(format!("Failed to register manifest option: {e}"))
373        })?;
374    }
375
376    Ok(())
377}
378
379// Generate FFI entry points for dynamic loading (only when building standalone cdylib)
380#[cfg(feature = "dynamic")]
381reovim_module_macros::declare_module!(VimModule);