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>;