Skip to main content

lattice_grammar/
error.rs

1//! [`CommandError`]: why a grammar invocation produced no effect.
2//!
3//! Every evaluator (motion, operator, text object, ex-command, action) and
4//! the dispatcher itself return [`GrammarResult`]. The contract shared by
5//! all variants: **an `Err` commits nothing.** The dispatcher returns before
6//! any [`crate::Effect`] reaches the host, so the document, cursor, undo
7//! stack and registers are exactly as they were when the keystroke arrived.
8//! Variants differ only in what the host does *besides* dropping the
9//! invocation -- most are logged, [`CommandError::User`] is echoed to the
10//! user, [`CommandError::MotionFailed`] is vim's silent beep.
11
12use thiserror::Error;
13
14use lattice_core::CoreError;
15use lattice_protocol::ProtocolError;
16
17/// Why a grammar invocation failed. See the [module docs](self) for the
18/// no-effect-on-error contract every variant shares.
19#[derive(Debug, Error)]
20pub enum CommandError {
21    /// The invocation named a [`crate::CommandId`] (or a motion / operator /
22    /// text-object id) that is not in the [`crate::CommandRegistry`] -- e.g.
23    /// a plugin contribution that has since been unloaded, or a stale id
24    /// carried in a recorded macro.
25    #[error("unknown command id")]
26    UnknownCommand,
27
28    /// The id resolved, but to a registration of a different kind than the
29    /// call site needs (an operator id passed where a motion was expected).
30    /// A programming error in the caller, not a user error.
31    #[error("command kind mismatch: expected {expected}, got {actual}")]
32    KindMismatch {
33        /// The kind the call site required, as its registry label
34        /// (`"motion"`, `"operator"`, `"text-object"`, `"ex-command"`,
35        /// `"action"`).
36        expected: &'static str,
37        /// The kind the id actually names, same label vocabulary.
38        actual: &'static str,
39    },
40
41    /// An operator invocation carried neither a [`crate::Target`] nor a
42    /// [`crate::Range`], so there is nothing to operate on. The keystroke
43    /// parser never builds such an invocation; this guards programmatic
44    /// callers (plugins, replayed macros).
45    #[error("missing target for operator")]
46    MissingTarget,
47
48    /// VM.3L: a motion couldn't move (vim beeps): `j` on the last line, `k`
49    /// on the first. The dispatcher commits no effect, so an operator it was
50    /// feeding is cancelled — vim deletes nothing for `dj` on the last line,
51    /// where returning the cursor would have deleted that line.
52    #[error("motion failed")]
53    MotionFailed,
54
55    /// VM.3d-2: a command failed with a message the user should see — vim's
56    /// `E486: Pattern not found`, `E35: no previous regular expression`. Like
57    /// every error, no effect is committed (an operator fed by a failing `n`
58    /// deletes nothing, as in vim); unlike the others, the host echoes it.
59    #[error("{0}")]
60    User(String),
61
62    /// The evaluator received [`crate::Args`] of the wrong shape, or args
63    /// that do not fit the document (the common case is a position computed
64    /// past the end of the buffer). The static string names what was wrong;
65    /// it is for logs, not the user.
66    #[error("invalid args for command: {0}")]
67    InvalidArgs(&'static str),
68
69    /// An ex-command's `parse_args` callback rejected the input. Carries
70    /// the human-readable reason; the parser front-end surfaces it through
71    /// `ExCommandError::BadArgs`.
72    #[error("invalid ex-command args: {0}")]
73    BadArgs(String),
74
75    /// A WASM-plugin grammar contribution failed at `apply` / `parse_args`
76    /// (PH7.7c): a guest-returned `err`, a fuel/epoch trap (the Reflex-budget
77    /// runaway guard), a boundary-conversion failure, or a dead plugin. The
78    /// dispatcher treats it like any evaluator error — **no `Effect` is
79    /// committed**, the contribution is a no-op (graceful degradation,
80    /// plugin-host.md §8), and the reason is logged. Built-in grammar never
81    /// produces this.
82    #[error("plugin grammar failed: {0}")]
83    Plugin(String),
84
85    /// The evaluator observed a cancelled [`crate::CancellationToken`]
86    /// and returned early. By DESIGN.md §5.2.5, no `Effect` is
87    /// committed; the document is left at the version the keystroke
88    /// arrived at, exactly as if the user had not pressed the key.
89    #[error("operation cancelled")]
90    Cancelled,
91
92    /// A buffer-model failure from [`lattice_core`] (I/O, nothing to
93    /// undo/redo, a core-level cancellation) surfaced through `?`.
94    #[error(transparent)]
95    Core(#[from] CoreError),
96
97    /// A protocol-layer failure from [`lattice_protocol`] (unknown
98    /// document, out-of-bounds position, stale version) surfaced through
99    /// `?`.
100    #[error(transparent)]
101    Protocol(#[from] ProtocolError),
102}
103
104/// `Result` alias used by every evaluator and by the dispatcher.
105pub type GrammarResult<T> = Result<T, CommandError>;