Skip to main content

hjkl_engine/
discipline.rs

1//! Pluggable input-discipline state (#265 G3 / #267).
2//!
3//! The engine owns the editing *core* (buffer, undo, registers, marks,
4//! search, viewport) but is agnostic about the *keybinding discipline* (vim,
5//! vscode, future helix/emacs). Each discipline's FSM state lives in its own
6//! crate (e.g. `hjkl-vim`'s `VimState`) and is stored on the [`Editor`] through
7//! this type-erased slot — the engine never names the concrete state type.
8//!
9//! The trait is intentionally **minimal**: the engine only asks a discipline
10//! for its [`CoarseMode`] (status badge / cursor shape) plus an `Any` upcast.
11//! All discipline-specific behavior lives behind that downcast in the
12//! discipline crate (e.g. `hjkl_vim::vim_state`), never on this trait.
13//!
14//! [`Editor`]: crate::Editor
15
16use crate::CoarseMode;
17
18/// Discipline-private FSM state, stored type-erased on the [`Editor`].
19///
20/// [`Editor`]: crate::Editor
21pub trait DisciplineState: std::any::Any + std::fmt::Debug {
22    /// Discipline-agnostic coarse mode for app chrome (status badge, cursor
23    /// shape). Every discipline projects its internal mode onto this.
24    fn coarse_mode(&self) -> CoarseMode;
25
26    /// Upcast to `&dyn Any` so the owning crate can `downcast_ref` to its
27    /// concrete state type.
28    fn as_any(&self) -> &dyn std::any::Any;
29
30    /// Mutable upcast — see [`DisciplineState::as_any`].
31    fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
32
33    /// Return the discipline to its idle / command-ready state, discarding any
34    /// in-flight input (pending chords, counts, insert sessions).
35    ///
36    /// The engine calls this when an operation must leave the editor in a known
37    /// resting state regardless of which discipline is installed — after undo /
38    /// redo, and after a `:!` filter rewrites the buffer. Without this hook the
39    /// engine core would have to name vim to reset vim (#267).
40    ///
41    /// For vim this is Normal mode; a non-modal discipline may treat it as a
42    /// no-op.
43    fn reset_to_idle(&mut self);
44
45    /// Put the discipline's *mode* back to idle after undo / redo rewound the
46    /// buffer, WITHOUT discarding in-flight session state.
47    ///
48    /// Deliberately weaker than [`DisciplineState::reset_to_idle`]: undo must
49    /// not clear an open insert session, because non-modal disciplines (vscode)
50    /// live inside one permanently and rely on it for undo granularity. Getting
51    /// this wrong silently breaks vscode-mode undo while leaving vim green.
52    fn reset_mode_after_history(&mut self);
53}
54
55/// Default discipline for an [`Editor`] that has not had a real discipline
56/// installed: no FSM, always reports [`CoarseMode::Normal`]. Editors that
57/// receive vim/vscode/etc. input install their discipline at construction
58/// (e.g. `hjkl_vim::install_vim_discipline`).
59///
60/// [`Editor`]: crate::Editor
61#[derive(Debug, Default)]
62pub struct NoDiscipline;
63
64impl DisciplineState for NoDiscipline {
65    fn coarse_mode(&self) -> CoarseMode {
66        CoarseMode::Normal
67    }
68    /// No FSM state to discard.
69    fn reset_to_idle(&mut self) {}
70    /// No FSM mode to reset.
71    fn reset_mode_after_history(&mut self) {}
72    fn as_any(&self) -> &dyn std::any::Any {
73        self
74    }
75    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
76        self
77    }
78}