Expand description
Vim modal editing engine and the unified command/grammar dispatch.
Per DESIGN.md §5.2:
- The vim grammar is the public command API.
- There is one
CommandRegistryand oneexecute(...)dispatcher. - Operators, motions, text objects, ex-commands, and plugin contributions are all peers of the dispatcher.
- Built-in motions / operators / text objects live here in native Rust (off the WASM hot path).
§What this crate owns
- The command registry.
CommandRegistryholds every motion, operator, text object, ex-command and action under a namespaced name (motion:word-forward,operator:delete,ex:write), each with a typed spec (MotionSpec,OperatorSpec,TextObjectSpec,ExCommandSpec,ActionSpec) and introspectable metadata (CommandSpec). Plugins register into the same registry throughregister_plugin_*;CommandRegistryHandleis how it is shared. - The call type and the dispatcher.
CommandInvocationis the one shape every front-end produces (chord,:line, palette, macro, plugin);execute/execute_with_envresolve it against alattice_core::Documentand return anEffect. Dispatch is synchronous and runs on the document’s actor. - The grammar’s typed values.
Count,Register,Range,Target,Args,ModalState, and theEffect/AppEffectvocabulary commands return to the host. - The native vim catalog.
builtins::populateandex_commands::populateregister the built-in motions, operators, text objects and ex-commands;reflowis thegqengine. - Introspection.
Introspectableandrender_introspectiongive every:describe-*view one shape.
§What it must not depend on
Only lattice-protocol and lattice-core from the workspace. No
tree-sitter, no lattice-mode / lattice-runtime / host / UI crate, no
plugin runtime. The grammar runs on every keystroke and is linked by all
of those layers, so it must sit beneath them; anything it needs from
above arrives as data or as a small trait the host implements
(ScopeResolver, IndentResolver, FoldResolver,
MarkResolver, ViewportResolver, DisplayResolver) bundled in
a per-dispatch GrammarEnv. That boundary is why this is a crate: it
is what keeps built-in and plugin commands, and every buffer kind, on
one dispatch path without dragging the syntax stack or the host into it.
§Example
Register a motion, then dispatch 2 of it through the one dispatcher:
use std::sync::Arc;
use lattice_core::{BufferId, Document};
use lattice_grammar::registry::MotionResult;
use lattice_grammar::{
CancellationToken, CommandInvocation, CommandRegistry, Count, CurswantEffect, Effect,
MotionSpec, execute,
};
use lattice_protocol::position::Position;
let mut registry = CommandRegistry::new();
let down = registry.register_motion(
"motion:my-line-down",
"Move `count` lines down, to column 0.",
MotionSpec {
jump: false,
exclusive: false,
curswant: CurswantEffect::default(),
args_schema: vec![],
apply: Arc::new(|ctx| {
let target = Position::new(ctx.from.line + ctx.count.get(), 0);
Ok(MotionResult { target, ..Default::default() })
}),
},
);
let mut doc = Document::from_text("a\nb\nc\n");
let effect = execute(
®istry,
&mut doc,
BufferId(0),
Position::ZERO,
CommandInvocation::of(down.0).with_count(Count(2)),
&CancellationToken::never(),
)
.unwrap();
assert!(matches!(effect, Effect::CursorMove(p) if p == Position::new(2, 0)));§Design documents
docs/dev/architecture/design.md§5.2 (modal engine; §5.2.1 unified dispatch, §5.2.5 latency classes), §5.11 (introspection)docs/dev/architecture/typed-motion-dispatch.mddocs/dev/architecture/treesitter-motions.mddocs/dev/architecture/select-mode.mddocs/dev/architecture/text-reflow.mddocs/dev/architecture/auto-indent.md
Re-exports§
pub use crate::app_effect::AppEffect;pub use crate::app_effect::ErrorTarget;pub use crate::app_effect::HScroll;pub use crate::app_effect::InsertLineEdit;pub use crate::app_effect::ScrollPos;pub use crate::app_effect::ViewportPos;pub use crate::args::ArgDefault;pub use crate::args::ArgKind;pub use crate::args::ArgSpec;pub use crate::args::ArgValue;pub use crate::args::Args;pub use crate::cancel::CheckCancelled;pub use crate::command::CommandInvocation;pub use crate::command::CommandKind;pub use crate::command::CommandSpec;pub use crate::command::Count;pub use crate::command::LatencyClass;pub use crate::command::kind_icon;pub use crate::dispatcher::execute;pub use crate::dispatcher::execute_motion_only;pub use crate::dispatcher::execute_motion_only_reporting;pub use crate::dispatcher::execute_with_env;pub use crate::dispatcher::notice_text;pub use crate::effect::EchoLevel;pub use crate::effect::Effect;pub use crate::effect::FileAnchor;pub use crate::effect::LspRequest;pub use crate::effect::QuitScope;pub use crate::effect::SubstituteScope;pub use crate::effect::Utf16Pos;pub use crate::effect::YankKind;pub use crate::error::CommandError;pub use crate::error::GrammarResult;pub use crate::introspect::HelpSection;pub use crate::introspect::Introspectable;pub use crate::introspect::RenderedAnchor;pub use crate::introspect::RenderedIntrospection;pub use crate::introspect::SourceEntry;pub use crate::introspect::SourceLabel;pub use crate::introspect::render_introspection;pub use crate::introspect::render_introspection_lines;pub use crate::introspect::render_introspection_with;pub use crate::modal::ModalState;pub use crate::modal::SearchDirection;pub use crate::modal::VisualKind;pub use crate::range::Range;pub use crate::range::RangeBound;pub use crate::register::Register;pub use crate::registry::ActionContext;pub use crate::registry::ActionSpec;pub use crate::registry::CommandRegistration;pub use crate::registry::CommandRegistry;pub use crate::registry::CommentSyntax;pub use crate::registry::Curswant;pub use crate::registry::CurswantEffect;pub use crate::registry::DisplayResolver;pub use crate::registry::ExCommandContext;pub use crate::registry::ExCommandSpec;pub use crate::registry::FindKind;pub use crate::registry::FoldResolver;pub use crate::registry::GrammarEnv;pub use crate::registry::IndentResolver;pub use crate::registry::LastFind;pub use crate::registry::LastSearch;pub use crate::registry::MarkResolver;pub use crate::registry::MotionNotice;pub use crate::registry::MotionSpec;pub use crate::registry::OperatorContext;pub use crate::registry::OperatorSpec;pub use crate::registry::ScopeResolver;pub use crate::registry::ShownLines;pub use crate::registry::SurfaceForm;pub use crate::registry::TextObjectSpec;pub use crate::registry::ViewportResolver;pub use crate::registry::ExCommandId;pub use crate::registry::MotionId;pub use crate::registry::OperatorId;pub use crate::registry::TextObjectId;pub use crate::source::SourceKind;pub use crate::source::SourceLayer;pub use crate::source::SourceLocation;pub use crate::target::Target;
Modules§
- app_
effect AppEffect– the typed App-side effects produced by free-formCommandKind::Actionregistrations.- args
- Per-command argument values.
- builtins
- Built-in motions, text objects, and operators that ship native (off the WASM boundary; per DESIGN.md §5.5.2 “built-ins stay native”).
- cancel
- Cancellation tokens for evaluator interruption (DESIGN.md §5.2.5).
- command
CommandInvocation– the unified call type that flows through the dispatcher (DESIGN.md §5.2.1).- dispatcher
- The unified dispatcher (DESIGN.md §5.2.1).
- effect
- What a
CommandInvocationproduced once executed. - error
CommandError: why a grammar invocation produced no effect.- ex_
commands - Built-in ex-commands registered as peers of motions / operators / text
objects in the unified
CommandRegistry(DESIGN.md §5.2.1). - introspect
- Generic introspection (DESIGN.md §5.11).
- modal
- Modal state – a buffer-level state machine in front of the buffer (DESIGN.md §5.2). Orthogonal to major / minor modes.
- range
- The vim grammar
Range– the dispatcher’s range arg. - reflow
- The text-reflow engine — re-break a range of lines to
textwidth. - register
- Vim registers (DESIGN.md §5.2.2).
- registry
- The
CommandRegistryholds every registered operator, motion, text object, and ex-command. The dispatcher (super::dispatcher::execute) looks up commands here. - source
- Provenance metadata (DESIGN.md §5.11).
- target
Target: the value an operator operates on.
Structs§
- Cancellation
Token - Re-export of the protocol-layer cancellation primitive. Grammar
callers access it under this name; lower crates (search loops in
lattice-core) uselattice_protocol::CancellationTokendirectly. The two are the same type. Cooperative cancellation handle. Cheap to clone (one Arc bump); safe to share across threads / tasks. - Command
Id - Re-export the protocol’s CommandId so callers don’t need a second import.
Identifies a registered command — the
commandof alattice_grammar::CommandInvocation, and what a keymap binding resolves to. Minted at registration bylattice-grammar’s command registry from a process-wide counter starting at 1. (The completion registry keeps a separate counter of its own, so an id is only meaningful together with the registry that issued it.)
Enums§
- Pane
Direction <C-w>h/j/k/lcardinal navigation. Geometry-aware: walks the tree to find the spatial neighbour of the active pane.
Type Aliases§
- Command
Registry Handle - The shared, hot-swappable command registry: the typed handle for
ServiceRegistryregistration + lookup (M.10.3, 2026-06-03). Mode crates pull it viactx.service::<CommandRegistryHandle>()to look up CommandIds by action name (id_by_name("action:...")) aton_activatetime. Same shape aslattice_mode::ActionHandlerRegistryHandleperfeedback_servicesregistry_arc_typeid.