Skip to main content

reovim_driver_input/
resolver.rs

1//! Mode key resolver trait and types.
2//!
3//! This module defines how modes resolve key events into commands or transitions.
4//! Resolvers provide the policy layer for key handling - the kernel/runner only
5//! provides the mechanism (dispatch, mode stack, command execution).
6//!
7//! # Architecture
8//!
9//! The resolver system separates mechanism from policy:
10//!
11//! | Layer | Responsibility |
12//! |-------|---------------|
13//! | Kernel | Mechanism: `ModeId`, `ModeStack`, `CommandId`, `Position` |
14//! | Input Driver (this) | Contract: `ModeKeyResolver` trait, result types |
15//! | Modules | Policy: `VimNormalResolver`, `VimInsertResolver`, etc. |
16//! | Runner | Dispatch: get resolver → `resolve()` → execute result |
17//!
18//! # Different Editing Styles
19//!
20//! The same kernel/runner supports different editing paradigms:
21//!
22//! | Style | Resolver Behavior |
23//! |-------|-------------------|
24//! | Vim | Counts, operators+motions, mode stacking |
25//! | Emacs | C-x prefix chains, M-x commands |
26//! | Kakoune | Object-verb (select then act) |
27//! | CUA | Ctrl+C/V/X, Shift+arrows |
28//!
29//! # Example Flow: `dw` (delete word)
30//!
31//! ```text
32//! 1. Normal mode receives 'd'
33//!    └─ VimNormalResolver.resolve('d', state)
34//!    └─ Returns: ModeTransition::Set { mode: "vim:delete" }
35//!
36//! 2. EventLoop sets delete mode
37//!
38//! 3. Delete mode receives 'w'
39//!    └─ VimDeleteResolver.resolve('w', state)
40//!    └─ Dispatches word motion, captures range
41//!    └─ Returns: Delete text + ModeTransition::Set { mode: "vim:normal" }
42//!
43//! 4. EventLoop returns to normal mode
44//! ```
45
46use std::{any::TypeId, collections::HashMap};
47
48use {
49    reovim_driver_session::{ExtensionMap, SessionExtension, TextInputSink},
50    reovim_kernel::api::v1::{BufferId, CommandId, ModeId, ModeStack, Position},
51};
52
53use crate::{KeyEvent, KeySequence, KeymapQuery};
54
55// Re-export transition types and session API from session driver (canonical source)
56pub use reovim_driver_session::{PopResult, SessionApi, SessionApiDyn, TransitionContext};
57
58// ============================================================================
59// OperatorArgs - Shared count/register fields (#391)
60// ============================================================================
61
62/// Common arguments for operator operations.
63///
64/// Extracted to avoid field duplication across context types (DRY principle).
65/// Used by `TransitionContext` and `ResolveContext`.
66///
67/// # Example
68///
69/// ```
70/// use reovim_driver_input::OperatorArgs;
71///
72/// // Create with defaults
73/// let args = OperatorArgs::new();
74/// assert_eq!(args.effective_count(), 1); // Default count is 1
75///
76/// // Builder pattern
77/// let args = OperatorArgs::new().count(3).register('a');
78/// assert_eq!(args.count, Some(3));
79/// assert_eq!(args.register, Some('a'));
80///
81/// // From count shorthand
82/// let args = OperatorArgs::with_count(5);
83/// assert_eq!(args.effective_count(), 5);
84/// ```
85#[derive(Debug, Clone, Default, PartialEq, Eq)]
86pub struct OperatorArgs {
87    /// Count prefix (e.g., 3 in `3j` or `2d3w`).
88    ///
89    /// Counts can be combined across mode transitions: `2d3w` = 6 words.
90    pub count: Option<usize>,
91
92    /// Register for the operation (e.g., `a` in `"ayw`).
93    ///
94    /// In vim, registers store yanked/deleted text.
95    pub register: Option<char>,
96}
97
98impl OperatorArgs {
99    /// Create a new empty operator args.
100    #[must_use]
101    pub const fn new() -> Self {
102        Self {
103            count: None,
104            register: None,
105        }
106    }
107
108    /// Create operator args with a count.
109    #[must_use]
110    pub const fn with_count(count: usize) -> Self {
111        Self {
112            count: Some(count),
113            register: None,
114        }
115    }
116
117    /// Set the count (builder pattern).
118    #[must_use]
119    pub const fn count(mut self, count: usize) -> Self {
120        self.count = Some(count);
121        self
122    }
123
124    /// Set the register (builder pattern).
125    #[must_use]
126    pub const fn register(mut self, reg: char) -> Self {
127        self.register = Some(reg);
128        self
129    }
130
131    /// Get the effective count (default to 1 if not set).
132    ///
133    /// In vim, an unspecified count means "1", not "none".
134    #[must_use]
135    pub const fn effective_count(&self) -> usize {
136        match self.count {
137            Some(c) => c,
138            None => 1,
139        }
140    }
141
142    /// Check if any arguments are set.
143    #[must_use]
144    pub const fn is_empty(&self) -> bool {
145        self.count.is_none() && self.register.is_none()
146    }
147}
148
149// ============================================================================
150// ResolveInput - Input context for resolvers
151// ============================================================================
152
153/// Input context passed to resolvers for key resolution.
154///
155/// This struct provides resolvers with access to:
156/// - The current key sequence being resolved
157/// - The current mode
158/// - Access to keymap queries (via `KeymapQuery` trait)
159///
160/// # Mechanism vs Policy
161///
162/// `ResolveInput` enables the mechanism/policy separation:
163/// - **Mechanism**: The keymap registry reports FACTS via `keymap.query()`
164/// - **Policy**: The resolver decides what to DO with those facts
165///
166/// For example, given `KeyLookupState::ExactWithLonger`:
167/// - **Vim policy**: Wait for more keys (dd might follow d)
168/// - **Eager policy**: Execute immediately
169///
170/// # Example
171///
172/// ```ignore
173/// use reovim_driver_input::{ResolveInput, KeyLookupState, ResolveResult};
174///
175/// fn resolve(input: &ResolveInput<'_>) -> ResolveResult {
176///     // Query the keymap for facts
177///     let state = input.keymap.query(input.mode, input.keys);
178///
179///     // Apply policy to the facts
180///     match state {
181///         KeyLookupState::ExactWithLonger { exact } => {
182///             // Vim: wait for more keys
183///             ResolveResult::Pending
184///         }
185///         KeyLookupState::ExactOnly(cmd) => {
186///             // Execute the command
187///             ResolveResult::Execute(cmd, ResolveContext::new())
188///         }
189///         // ...
190///     }
191/// }
192/// ```
193pub struct ResolveInput<'a> {
194    /// The accumulated key sequence being resolved.
195    pub keys: &'a KeySequence,
196
197    /// The current mode.
198    pub mode: &'a ModeId,
199
200    /// Access to keymap queries.
201    ///
202    /// Use `keymap.query(mode, keys)` to get facts about what bindings exist.
203    pub keymap: &'a dyn KeymapQuery,
204
205    /// Optional access to register bank (Epic #465 Phase 8D - Macro Playback).
206    ///
207    /// Provides read/write access to registers for macro recording/playback.
208    /// This is optional because not all resolve contexts need register access.
209    pub registers: Option<
210        &'a std::sync::Arc<reovim_kernel::api::v1::RwLock<reovim_kernel::api::v1::RegisterBank>>,
211    >,
212}
213
214impl<'a> ResolveInput<'a> {
215    /// Create a new resolve input context.
216    #[must_use]
217    pub const fn new(keys: &'a KeySequence, mode: &'a ModeId, keymap: &'a dyn KeymapQuery) -> Self {
218        Self {
219            keys,
220            mode,
221            keymap,
222            registers: None,
223        }
224    }
225
226    /// Create a resolve input context with register access.
227    #[must_use]
228    pub const fn with_registers(
229        keys: &'a KeySequence,
230        mode: &'a ModeId,
231        keymap: &'a dyn KeymapQuery,
232        registers: &'a std::sync::Arc<
233            reovim_kernel::api::v1::RwLock<reovim_kernel::api::v1::RegisterBank>,
234        >,
235    ) -> Self {
236        Self {
237            keys,
238            mode,
239            keymap,
240            registers: Some(registers),
241        }
242    }
243}
244
245// ============================================================================
246// ModeKeyResolver Trait
247// ============================================================================
248
249/// Trait for resolving key events in a specific mode.
250///
251/// Resolvers have full control over how keys are interpreted:
252/// - Count prefixes (1-9 in Vim, none in Emacs)
253/// - Register selection (" in Vim)
254/// - Multi-key sequences (gg, <C-w>h)
255/// - Operator-pending state (d awaiting motion)
256///
257/// Each editing style implements different resolvers:
258/// - Vim: `VimNormalResolver`, `VimInsertResolver`, `VimDeleteResolver`, `VimYankResolver`, `VimChangeResolver`
259/// - Emacs: `EmacsResolver` (single mode with prefix handling)
260/// - etc.
261///
262/// # Object Safety
263///
264/// This trait is object-safe and requires `Send + Sync` for use in
265/// multi-threaded server contexts.
266///
267/// # Example
268///
269/// ```ignore
270/// use reovim_driver_input::{ModeKeyResolver, ResolveResult, KeyEvent, ModeState};
271/// use reovim_kernel::api::v1::ModeId;
272///
273/// struct MyResolver {
274///     mode: ModeId,
275/// }
276///
277/// impl ModeKeyResolver for MyResolver {
278///     fn resolve(&self, key: &KeyEvent, state: &mut ModeState) -> ResolveResult {
279///         // Handle key based on mode-specific logic
280///         ResolveResult::NotHandled
281///     }
282///
283///     fn mode_id(&self) -> &ModeId {
284///         &self.mode
285///     }
286/// }
287/// ```
288pub trait ModeKeyResolver: Send + Sync {
289    /// Process a key event in this mode's context (legacy API).
290    ///
291    /// **Deprecated**: Use `resolve_with_keymap` instead for access to keymap queries.
292    ///
293    /// The resolver has full control over interpretation:
294    /// - Accumulate counts, registers, pending sequences
295    /// - Execute commands
296    /// - Request mode transitions
297    ///
298    /// # Arguments
299    ///
300    /// * `key` - The key event to process
301    /// * `state` - Mutable access to shared mode state
302    ///
303    /// # Returns
304    ///
305    /// A `ResolveResult` indicating what action to take.
306    #[deprecated(since = "0.9.5", note = "Override resolve_with_keymap() instead")]
307    #[cfg_attr(coverage_nightly, coverage(off))]
308    fn resolve(&self, _key: &KeyEvent, _state: &mut ModeState) -> ResolveResult {
309        panic!("resolve() is removed - implement resolve_with_keymap() instead")
310    }
311
312    /// Process a key event with access to keymap queries.
313    ///
314    /// This is the preferred method for resolvers that need to query keybindings.
315    /// It enables mechanism/policy separation:
316    /// - Query `input.keymap.query()` to get FACTS about what bindings exist
317    /// - Apply your own POLICY to decide what to do
318    ///
319    /// # Arguments
320    ///
321    /// * `key` - The key event to process
322    /// * `state` - Mutable access to shared mode state
323    /// * `input` - Input context with keymap access
324    ///
325    /// # Returns
326    ///
327    /// A `ResolveResult` indicating what action to take.
328    ///
329    /// # Default Implementation
330    ///
331    /// Falls back to `resolve()` for backward compatibility with existing resolvers.
332    /// Override this method to use keymap queries.
333    ///
334    /// # Example
335    ///
336    /// ```ignore
337    /// fn resolve_with_keymap(
338    ///     &self,
339    ///     key: &KeyEvent,
340    ///     state: &mut ModeState,
341    ///     input: &ResolveInput<'_>,
342    /// ) -> ResolveResult {
343    ///     // Get facts about what bindings exist
344    ///     let lookup_state = input.keymap.query(input.mode, input.keys);
345    ///
346    ///     // Apply Vim policy: wait for longer sequences
347    ///     match lookup_state {
348    ///         KeyLookupState::ExactWithLonger { .. } => ResolveResult::Pending,
349    ///         KeyLookupState::ExactOnly(cmd) => {
350    ///             ResolveResult::Execute(cmd, ResolveContext::new())
351    ///         }
352    ///         // ...
353    ///     }
354    /// }
355    /// ```
356    #[allow(deprecated)]
357    fn resolve_with_keymap(
358        &self,
359        key: &KeyEvent,
360        state: &mut ModeState,
361        _input: &ResolveInput<'_>,
362    ) -> ResolveResult {
363        // Default: delegate to legacy resolve() for backward compatibility
364        // Note: Resolvers should override this method instead
365        self.resolve(key, state)
366    }
367
368    /// Process a key event with access to keymap queries AND session extensions.
369    ///
370    /// This is the preferred method for resolvers that need to access per-session
371    /// module state (e.g., `VimSessionState` for pending operators, find-char state).
372    ///
373    /// # Architecture (Epic #385)
374    ///
375    /// This method enables the proper mechanism/policy separation:
376    /// - **Mechanism**: Runner routes keys to resolvers, provides extension access
377    /// - **Policy**: Vim resolver stores/reads `VimSessionState` in extensions
378    ///
379    /// # Extension Maps
380    ///
381    /// Two extension maps are provided so modules can choose the correct scope:
382    /// - `shared_extensions` - Shared across all clients in a session (e.g., syntax state)
383    /// - `client_extensions` - Per-client isolated state (e.g., vim operator state, counts)
384    ///
385    /// # Arguments
386    ///
387    /// * `key` - The key event to process
388    /// * `state` - Mutable access to shared mode state
389    /// * `input` - Input context with keymap access
390    /// * `shared_extensions` - Session-wide extension storage (shared across clients)
391    /// * `client_extensions` - Per-client extension storage (isolated per connection)
392    ///
393    /// # Returns
394    ///
395    /// A `ResolveResult` indicating what action to take.
396    ///
397    /// # Default Implementation
398    ///
399    /// Falls back to `resolve_with_keymap()` for backward compatibility.
400    /// Override this method to use session extensions.
401    ///
402    /// # Example
403    ///
404    /// ```ignore
405    /// use reovim_module_vim::VimSessionState;
406    ///
407    /// fn resolve_with_extensions(
408    ///     &self,
409    ///     key: &KeyEvent,
410    ///     state: &mut ModeState,
411    ///     input: &ResolveInput<'_>,
412    ///     shared_extensions: &mut ExtensionMap,
413    ///     client_extensions: &mut ExtensionMap,
414    /// ) -> ResolveResult {
415    ///     // Per-client vim state (pending operator, counts)
416    ///     let vim = client_extensions.get_or_insert::<VimSessionState>();
417    ///
418    ///     // Check for pending operator
419    ///     if let Some(pending) = &vim.pending_operator {
420    ///         // Handle operator-pending logic
421    ///     }
422    ///
423    ///     // ... rest of resolution logic
424    /// }
425    /// ```
426    fn resolve_with_extensions(
427        &self,
428        key: &KeyEvent,
429        state: &mut ModeState,
430        input: &ResolveInput<'_>,
431        _shared_extensions: &mut ExtensionMap,
432        _client_extensions: &mut ExtensionMap,
433    ) -> ResolveResult {
434        // Default: delegate to resolve_with_keymap for backward compatibility
435        self.resolve_with_keymap(key, state, input)
436    }
437
438    /// Process a key event with full session API access.
439    ///
440    /// This is the preferred method for resolvers that need direct state manipulation.
441    /// Instead of returning commands for the runner to execute, resolvers can perform
442    /// operations directly via the session API and return `ResolveResult::Completed`.
443    ///
444    /// # Architecture (Epic #393)
445    ///
446    /// This method completes the mechanism/policy separation:
447    /// - **Mechanism (runner)**: Creates `SessionRuntime`, routes keys to resolvers
448    /// - **Policy (resolvers)**: Perform actions directly via `session.*` methods
449    ///
450    /// # Extension Maps
451    ///
452    /// Two extension maps are provided so modules can choose the correct scope:
453    /// - `shared_extensions` - Shared across all clients in a session
454    /// - `client_extensions` - Per-client isolated state
455    ///
456    /// `ExtensionApi` has generic methods, making it incompatible with trait objects.
457    /// By passing extensions separately, we preserve dyn-compatibility for
458    /// `ModeKeyResolver` while still providing full access.
459    ///
460    /// # Arguments
461    ///
462    /// * `key` - The key event to process
463    /// * `state` - Mutable access to shared mode state
464    /// * `input` - Input context with keymap access
465    /// * `session` - Dyn-compatible session API (mode, buffer, window, command, changes)
466    /// * `shared_extensions` - Session-wide extension storage (shared across clients)
467    /// * `client_extensions` - Per-client extension storage (isolated per connection)
468    ///
469    /// # Returns
470    ///
471    /// A `ResolveResult` indicating what action to take:
472    /// - `Completed` - Resolver handled everything via `SessionApi`
473    /// - Other variants - Runner should handle as before
474    ///
475    /// # Default Implementation
476    ///
477    /// Falls back to `resolve_with_extensions()` for backward compatibility.
478    ///
479    /// # Example
480    ///
481    /// ```ignore
482    /// use reovim_driver_session::{SessionApiDyn, ModeApi, BufferApi, ExtensionMap};
483    ///
484    /// fn resolve_with_session(
485    ///     &self,
486    ///     key: &KeyEvent,
487    ///     state: &mut ModeState,
488    ///     input: &ResolveInput<'_>,
489    ///     session: &mut dyn SessionApiDyn,
490    ///     shared_extensions: &mut ExtensionMap,
491    ///     client_extensions: &mut ExtensionMap,
492    /// ) -> ResolveResult {
493    ///     // Directly manipulate state via session
494    ///     session.push_mode(insert_mode, TransitionContext::new());
495    ///     session.move_cursor(buffer, Position::new(0, 5));
496    ///
497    ///     // Per-client module state
498    ///     let vim = client_extensions.get_or_insert::<VimSessionState>();
499    ///     vim.pending_count = None;
500    ///
501    ///     // Tell runner: "I'm done, just broadcast the changes"
502    ///     ResolveResult::Completed
503    /// }
504    /// ```
505    fn resolve_with_session(
506        &self,
507        key: &KeyEvent,
508        state: &mut ModeState,
509        input: &ResolveInput<'_>,
510        _session: &mut dyn SessionApiDyn,
511        shared_extensions: &mut ExtensionMap,
512        client_extensions: &mut ExtensionMap,
513    ) -> ResolveResult {
514        // Default: delegate to resolve_with_extensions for backward compatibility.
515        // Resolvers that need session access should override this method.
516        self.resolve_with_extensions(key, state, input, shared_extensions, client_extensions)
517    }
518
519    /// Hook called after a command executes successfully.
520    ///
521    /// Called by the runner after executing a command that returns `Success`.
522    /// This allows resolvers to complete pending operations.
523    ///
524    /// # Architecture (Issue #388, Epic #415)
525    ///
526    /// This method keeps vim-specific operator completion logic in the vim module
527    /// instead of the runner, maintaining the mechanism/policy separation:
528    /// - **Runner (mechanism)**: Executes commands, calls this hook after success
529    /// - **Resolver (policy)**: Checks its stored state, completes pending operations
530    ///
531    /// Per #388, `CommandResult` has no `Motion` variant. Instead, the resolver
532    /// stores motion type info in `VimSessionState` BEFORE dispatching the motion,
533    /// then uses this hook to complete the operator.
534    ///
535    /// # Example Use Case: `dw` (delete word)
536    ///
537    /// 1. `d` key → Normal resolver stores pending operator, pushes operator-pending mode
538    /// 2. `w` key → Operator-pending resolver:
539    ///    - Stores start position in `VimSessionState`
540    ///    - Stores motion type (characterwise for 'w') in `VimSessionState`
541    ///    - Returns `Execute(word-forward)`
542    /// 3. Runner executes motion → `CommandResult::Success`
543    /// 4. Runner calls `on_command_complete(session, extensions)`
544    /// 5. Operator-pending resolver:
545    ///    - Checks `VimSessionState` for pending motion
546    ///    - Gets end position from session
547    ///    - Builds `PopResult::ExecuteCommand` with operator and args
548    ///    - Returns `Some(ModeTransition::Pop { result: ExecuteCommand })`
549    /// 6. Runner handles the mode transition (executes the operator command)
550    ///
551    /// # Arguments
552    ///
553    /// * `session` - Session API for cursor/buffer access
554    /// * `shared_extensions` - Session-wide extension storage (shared across clients)
555    /// * `client_extensions` - Per-client extension storage (isolated per connection)
556    ///
557    /// # Returns
558    ///
559    /// * `Some(ModeTransition)` - Resolver wants to complete a pending operation
560    /// * `None` - No pending operation to complete
561    ///
562    /// # Default Implementation
563    ///
564    /// Returns `None` - most modes don't have pending operations.
565    fn on_command_complete(
566        &self,
567        _session: &mut dyn SessionApiDyn,
568        _shared_extensions: &mut ExtensionMap,
569        _client_extensions: &mut ExtensionMap,
570    ) -> Option<ModeTransition> {
571        // Default: no pending operation to complete
572        None
573    }
574
575    /// Which mode this resolver handles.
576    fn mode_id(&self) -> &ModeId;
577
578    /// Optional parent mode to try if we return `NotHandled`.
579    ///
580    /// This enables mode inheritance. For example, operator-pending mode
581    /// might inherit from normal mode for motion keys.
582    fn inherits_from(&self) -> Option<&ModeId> {
583        None
584    }
585
586    /// Reset any internal state (counts, pending keys, etc.).
587    ///
588    /// Called when:
589    /// - Mode is exited
590    /// - Escape is pressed
591    /// - An error occurs
592    fn reset(&mut self) {}
593
594    /// Get the accumulated pending keys for this resolver.
595    ///
596    /// Used by the session layer to populate `PendingBindings` generically
597    /// after a `Pending` result, enabling bridges (e.g., which-key) to
598    /// display available continuations without knowing about specific resolvers.
599    ///
600    /// # Default
601    ///
602    /// Returns an empty `KeySequence`. Resolvers that track pending keys
603    /// (e.g., `VimNormalResolver`) should override this.
604    fn pending_keys(&self) -> KeySequence {
605        KeySequence::new()
606    }
607}
608
609// ============================================================================
610// InputTarget - Where to route character input (#482)
611// ============================================================================
612
613/// Target for character input routing.
614///
615/// When a resolver returns `InsertChar`, this enum specifies where the
616/// character should be inserted. This eliminates string-based mode detection
617/// (like `mode_name.contains("command")`) in favor of explicit, type-safe routing.
618///
619/// # Architecture (#482)
620///
621/// | Layer | Responsibility |
622/// |-------|----------------|
623/// | Resolver | Returns `ResolveResult::InsertChar { char, target }` |
624/// | Runner | Routes char to target (buffer or extension) |
625/// | Extension | Receives char via `TextInputSink::insert_char()` |
626///
627/// # Example
628///
629/// ```ignore
630/// use reovim_driver_input::{ResolveResult, InputTarget};
631///
632/// // Insert into buffer (default for insert mode)
633/// ResolveResult::insert_char('x');
634///
635/// // Insert into a session extension implementing TextInputSink
636/// ResolveResult::insert_char_to::<MyExtension>('x');
637/// ```
638#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
639pub enum InputTarget {
640    /// Insert into the active buffer at cursor position.
641    ///
642    /// This is the default target, used by insert mode, replace mode, etc.
643    #[default]
644    Buffer,
645
646    /// Insert into a session extension that implements `TextInputSink`.
647    ///
648    /// The `TypeId` identifies the extension type. The runner looks up
649    /// the extension in `ExtensionMap` and calls `insert_char()` on it.
650    ///
651    /// # Example
652    ///
653    /// ```ignore
654    /// // Route to a session extension implementing TextInputSink
655    /// InputTarget::extension::<MyExtension>()
656    /// ```
657    Extension(TypeId),
658}
659
660impl InputTarget {
661    /// Create an extension target for the given type.
662    ///
663    /// # Example
664    ///
665    /// ```ignore
666    /// let target = InputTarget::extension::<MyExtension>();
667    /// ```
668    #[must_use]
669    pub const fn extension<T: SessionExtension + TextInputSink>() -> Self {
670        Self::Extension(TypeId::of::<T>())
671    }
672}
673
674// ============================================================================
675// ResolveResult
676// ============================================================================
677
678/// Result of resolving a key event.
679///
680/// This enum represents all possible outcomes of key resolution:
681/// - Execute a command with context
682/// - Wait for more keys (pending sequence)
683/// - Pass to parent mode
684/// - Insert a character
685/// - Request a mode transition
686/// - Completed (resolver executed everything via `SessionApi`)
687#[derive(Debug, Clone)]
688pub enum ResolveResult {
689    /// Execute this command with the given context.
690    ///
691    /// The command will be looked up in the command registry and executed
692    /// with the provided context (count, register, etc.).
693    Execute(CommandId, ResolveContext),
694
695    /// Key is part of a pending sequence, wait for more.
696    ///
697    /// Used for multi-key commands like `gg`, `<C-w>h`, etc.
698    /// The key has been accumulated; more keys are needed.
699    Pending,
700
701    /// This mode doesn't handle the key, try parent mode.
702    ///
703    /// If `inherits_from()` returns a parent mode, the runner should
704    /// try that mode's resolver next.
705    NotHandled,
706
707    /// Insert this character into the specified target.
708    ///
709    /// For input-accepting modes (insert, command-line, search).
710    /// The `target` specifies where to insert:
711    /// - `InputTarget::Buffer` - Insert at cursor position in active buffer
712    /// - `InputTarget::Extension(type_id)` - Insert into session extension
713    ///
714    /// # Helpers
715    ///
716    /// Use the helper methods for ergonomic construction:
717    /// - `ResolveResult::insert_char(c)` - Insert into buffer (default)
718    /// - `ResolveResult::insert_char_to::<T>(c)` - Insert into extension
719    ///
720    /// # Example
721    ///
722    /// ```ignore
723    /// use reovim_driver_input::ResolveResult;
724    ///
725    /// // Insert mode → buffer
726    /// ResolveResult::insert_char('x');
727    ///
728    /// // Extension mode → TextInputSink extension
729    /// ResolveResult::insert_char_to::<MyExtension>(':');
730    /// ```
731    InsertChar {
732        /// The character to insert.
733        char: char,
734        /// Where to insert (buffer or extension).
735        target: InputTarget,
736    },
737
738    /// Request a mode transition.
739    ///
740    /// Push, pop, or replace the current mode on the mode stack.
741    ModeTransition(ModeTransition),
742
743    /// Resolver already executed everything via `SessionApi`.
744    ///
745    /// The resolver used `SessionRuntime` methods directly to perform
746    /// all necessary operations (mode changes, cursor moves, buffer edits, etc.).
747    /// Runner should just take accumulated changes via `take_changes()` and
748    /// broadcast notifications to clients.
749    ///
750    /// This variant is used with the new `resolve_with_session` pattern
751    /// where resolvers receive `&mut impl SessionApi` and can act directly.
752    ///
753    /// # Example
754    ///
755    /// ```ignore
756    /// fn resolve_with_session<S: ModeApi + BufferApi>(
757    ///     &self,
758    ///     key: &KeyEvent,
759    ///     state: &mut ModeState,
760    ///     session: &mut S,
761    /// ) -> ResolveResult {
762    ///     // Directly modify state via SessionApi
763    ///     session.push_mode(insert_mode, TransitionContext::new());
764    ///
765    ///     // Tell runner: "I'm done, just broadcast the changes"
766    ///     ResolveResult::Completed
767    /// }
768    /// ```
769    Completed,
770
771    /// Inject keys into the input queue (Epic #465 Phase 8D - Macro Playback).
772    ///
773    /// Used by macro playback to inject recorded key sequences. The runner
774    /// should process these keys as if they were typed by the user.
775    ///
776    /// # Fields
777    ///
778    /// - `keys`: The key sequence to inject
779    /// - `exit_macro_playback`: If true, call `VimSessionState::exit_macro_playback()`
780    ///   after all injected keys are processed
781    ///
782    /// # Example
783    ///
784    /// ```ignore
785    /// // Play macro from register 'a'
786    /// ResolveResult::InjectKeys {
787    ///     keys: vec![KeyEvent::new(KeyCode::Char('d')), KeyEvent::new(KeyCode::Char('w'))],
788    ///     exit_macro_playback: true,
789    /// }
790    /// ```
791    InjectKeys {
792        /// Keys to inject into the input queue.
793        keys: Vec<crate::KeyEvent>,
794        /// Whether to call `exit_macro_playback()` after processing.
795        exit_macro_playback: bool,
796    },
797}
798
799impl ResolveResult {
800    /// Create an `InsertChar` result that inserts into the buffer (default).
801    ///
802    /// Use this for insert mode, replace mode, and other modes that edit
803    /// the active buffer.
804    ///
805    /// # Example
806    ///
807    /// ```ignore
808    /// use reovim_driver_input::ResolveResult;
809    ///
810    /// // In insert mode resolver
811    /// ResolveResult::insert_char('x')
812    /// ```
813    #[must_use]
814    pub const fn insert_char(c: char) -> Self {
815        Self::InsertChar {
816            char: c,
817            target: InputTarget::Buffer,
818        }
819    }
820
821    /// Create an `InsertChar` result that inserts into a session extension.
822    ///
823    /// Use this for modes that input into extension state, such as command-line
824    /// mode or search mode.
825    ///
826    /// The extension must implement both `SessionExtension` and `TextInputSink`.
827    ///
828    /// # Example
829    ///
830    /// ```ignore
831    /// use reovim_driver_input::ResolveResult;
832    ///
833    /// // In a resolver that inputs to an extension
834    /// ResolveResult::insert_char_to::<MyExtension>(':')
835    /// ```
836    #[must_use]
837    pub const fn insert_char_to<T: SessionExtension + TextInputSink>(c: char) -> Self {
838        Self::InsertChar {
839            char: c,
840            target: InputTarget::extension::<T>(),
841        }
842    }
843}
844
845// ============================================================================
846// ResolveContext
847// ============================================================================
848
849/// Context passed when executing a command.
850///
851/// Contains all the information gathered during key resolution:
852/// - Count prefix (e.g., 3 in `3j`)
853/// - Register (e.g., `a` in `"ayw`)
854/// - Key sequence that triggered the command
855/// - Arbitrary metadata for complex commands
856#[derive(Debug, Clone, Default)]
857pub struct ResolveContext {
858    /// Count prefix (e.g., 3 in `3j`).
859    pub count: Option<usize>,
860
861    /// Register for the operation (e.g., `a` in `"ayw`).
862    pub register: Option<char>,
863
864    /// The key sequence that triggered this command.
865    pub keys: KeySequence,
866
867    /// Arbitrary metadata for command-specific data.
868    ///
869    /// Used for passing operator-specific information, motion flags, etc.
870    pub metadata: HashMap<String, ArgValue>,
871}
872
873impl ResolveContext {
874    /// Create a new empty context.
875    #[must_use]
876    pub fn new() -> Self {
877        Self::default()
878    }
879
880    /// Create a context with just a count.
881    #[must_use]
882    pub fn with_count(count: usize) -> Self {
883        Self {
884            count: Some(count),
885            ..Default::default()
886        }
887    }
888
889    /// Set the count.
890    #[must_use]
891    pub const fn count(mut self, count: usize) -> Self {
892        self.count = Some(count);
893        self
894    }
895
896    /// Set the register.
897    #[must_use]
898    pub const fn register(mut self, register: char) -> Self {
899        self.register = Some(register);
900        self
901    }
902
903    /// Set the keys.
904    #[must_use]
905    pub fn keys(mut self, keys: KeySequence) -> Self {
906        self.keys = keys;
907        self
908    }
909
910    /// Add metadata.
911    #[must_use]
912    pub fn with_metadata(mut self, key: impl Into<String>, value: ArgValue) -> Self {
913        self.metadata.insert(key.into(), value);
914        self
915    }
916
917    /// Get the effective count (default to 1 if not set).
918    #[must_use]
919    pub fn effective_count(&self) -> usize {
920        self.count.unwrap_or(1)
921    }
922}
923
924// ============================================================================
925// ArgValue
926// ============================================================================
927
928/// Dynamic argument value for command metadata.
929///
930/// Supports common types used in command arguments.
931#[derive(Debug, Clone, PartialEq)]
932pub enum ArgValue {
933    /// Boolean flag.
934    Bool(bool),
935    /// Integer value.
936    Int(i64),
937    /// Unsigned integer.
938    Uint(u64),
939    /// Floating point.
940    Float(f64),
941    /// String value.
942    String(String),
943    /// Character value.
944    Char(char),
945    /// Position in buffer.
946    Position(Position),
947    /// Range (start, end, linewise).
948    Range {
949        start: Position,
950        end: Position,
951        linewise: bool,
952    },
953}
954
955impl From<bool> for ArgValue {
956    fn from(v: bool) -> Self {
957        Self::Bool(v)
958    }
959}
960
961impl From<i64> for ArgValue {
962    fn from(v: i64) -> Self {
963        Self::Int(v)
964    }
965}
966
967impl From<u64> for ArgValue {
968    fn from(v: u64) -> Self {
969        Self::Uint(v)
970    }
971}
972
973impl From<f64> for ArgValue {
974    fn from(v: f64) -> Self {
975        Self::Float(v)
976    }
977}
978
979impl From<String> for ArgValue {
980    fn from(v: String) -> Self {
981        Self::String(v)
982    }
983}
984
985impl From<&str> for ArgValue {
986    fn from(v: &str) -> Self {
987        Self::String(v.to_owned())
988    }
989}
990
991impl From<char> for ArgValue {
992    fn from(v: char) -> Self {
993        Self::Char(v)
994    }
995}
996
997impl From<Position> for ArgValue {
998    fn from(v: Position) -> Self {
999        Self::Position(v)
1000    }
1001}
1002
1003// ============================================================================
1004// ModeTransition
1005// ============================================================================
1006
1007/// Mode transition request.
1008///
1009/// Resolvers return this to request changes to the mode stack.
1010#[derive(Debug, Clone)]
1011pub enum ModeTransition {
1012    /// Push a mode onto the stack with context.
1013    ///
1014    /// The new mode becomes current. The previous mode remains on the stack.
1015    /// Used for operator-pending, command-line, search, etc.
1016    Push {
1017        /// The mode to push.
1018        mode: ModeId,
1019        /// Context to pass to the new mode.
1020        context: TransitionContext,
1021    },
1022
1023    /// Pop the current mode, passing result to parent.
1024    ///
1025    /// The current mode is removed from the stack. The parent mode
1026    /// receives the result (e.g., operator range, cancelled).
1027    Pop {
1028        /// Result to pass to the parent mode.
1029        result: Option<PopResult>,
1030    },
1031
1032    /// Replace the current mode (equivalent to pop + push, but atomic).
1033    ///
1034    /// Used for mode changes that don't stack (normal → insert).
1035    Set {
1036        /// The mode to switch to.
1037        mode: ModeId,
1038        /// Context for the new mode.
1039        context: TransitionContext,
1040    },
1041}
1042
1043// TransitionContext and PopResult are re-exported from session driver above.
1044
1045// ============================================================================
1046// ModeState
1047// ============================================================================
1048
1049/// Shared state passed to all resolvers.
1050///
1051/// This is the mechanism layer - it provides access to kernel state
1052/// without encoding any policy about how modes should behave.
1053///
1054/// # Note on Resolver-Owned State
1055///
1056/// Mode-specific state (counts, pending keys, pending operators) is
1057/// owned by each resolver, NOT by `ModeState`. This allows:
1058/// - Different editing styles to have different state needs
1059/// - Unit testing resolvers without full runner
1060/// - Clean hot-reload (replace resolver, state resets)
1061#[derive(Debug, Clone)]
1062pub struct ModeState {
1063    /// Keys accumulated for multi-key sequence matching.
1064    ///
1065    /// Cleared when a sequence completes or times out.
1066    pub pending_keys: KeySequence,
1067
1068    /// Current mode stack.
1069    ///
1070    /// Read-only access; transitions happen via `ResolveResult::ModeTransition`.
1071    pub mode_stack: ModeStack,
1072
1073    /// Currently active buffer (if any).
1074    pub active_buffer: Option<BufferId>,
1075
1076    /// Context from the last mode transition.
1077    ///
1078    /// Contains operator, count, register from the parent mode.
1079    pub transition_context: Option<TransitionContext>,
1080}
1081
1082impl ModeState {
1083    /// Create a new mode state with the given initial mode.
1084    #[must_use]
1085    pub fn new(initial_mode: ModeId) -> Self {
1086        Self {
1087            pending_keys: KeySequence::new(),
1088            mode_stack: ModeStack::new(initial_mode),
1089            active_buffer: None,
1090            transition_context: None,
1091        }
1092    }
1093
1094    /// Get the current mode.
1095    #[must_use]
1096    pub fn current_mode(&self) -> &ModeId {
1097        self.mode_stack.current()
1098    }
1099
1100    /// Check if keys are pending.
1101    #[must_use]
1102    pub const fn has_pending_keys(&self) -> bool {
1103        !self.pending_keys.is_empty()
1104    }
1105
1106    /// Clear pending keys.
1107    pub fn clear_pending_keys(&mut self) {
1108        self.pending_keys.clear();
1109    }
1110
1111    /// Add a key to the pending sequence.
1112    pub fn push_pending_key(&mut self, key: KeyEvent) {
1113        self.pending_keys.push(key);
1114    }
1115
1116    /// Take the transition context (clears it).
1117    pub const fn take_transition_context(&mut self) -> Option<TransitionContext> {
1118        self.transition_context.take()
1119    }
1120}
1121
1122// ============================================================================
1123// Tests
1124// ============================================================================