pub enum Event {
Show 22 variants
DocumentOpened {
id: DocumentId,
path: Option<PathBuf>,
version: u64,
text: String,
},
DocumentClosed {
id: DocumentId,
},
BeforeSave {
id: DocumentId,
path: PathBuf,
},
DocumentSaved {
id: DocumentId,
path: PathBuf,
},
DocumentChanged {
id: DocumentId,
path: Option<PathBuf>,
version: u64,
edits: Vec<AppliedEdit>,
},
SelectionsChanged {
id: DocumentId,
version: u64,
selections: SelectionSet,
},
ModalModeChanged {
from: String,
to: String,
},
BeforeQuit,
OptionChanged {
name: String,
old: Option<String>,
new: String,
},
MajorEntered {
buffer: BufferId,
major: String,
},
MajorExiting {
buffer: BufferId,
major: String,
},
MinorActivated {
buffer: BufferId,
minor: String,
},
MinorDeactivated {
buffer: BufferId,
minor: String,
},
Plugin {
name: String,
payload: Vec<u8>,
},
PluginCrashed {
plugin: u32,
func: String,
kind: String,
},
PrePluginLoaded {
name: String,
},
PluginLoaded {
name: String,
id: u32,
},
PluginUnloaded {
name: String,
id: u32,
},
ModeEnablementRequested {
mode: String,
enabled: bool,
},
BufferOptionOverrideRequested {
buffer: BufferId,
option: String,
},
BackgroundTaskFinished {
source: String,
scope: Option<String>,
label: String,
outcome: TaskOutcome,
},
FilesChanged {
plugin: u32,
paths: Vec<PathBuf>,
},
}Expand description
One editor-core state transition, as published on the event bus.
See the module docs for how this relates to typed events and the plugin boundary; each variant says when it fires, who publishes it, and what its fields carry.
Variants§
DocumentOpened
Fired when a document buffer opens. Subscribers (the
LSP attach driver, future plugin hooks, project-watcher,
completion warmer) react asynchronously; the publisher
(Editor::publish_document_opened_for_active, run at boot for
the initial document and after each :e <path> open) returns
immediately. The
event-driven design keeps the UI thread off the LSP
initialize round-trip – aligned with paramount goal
#4 (asynchronicity).
path is None for unsaved scratch buffers (no LSP
attach work to drive). text carries the buffer’s
initial content so subscribers don’t have to reach back
through a document handle on the publish path – LSP
hands it straight to didOpen.
Caveat: the current publisher builds id from the raw value of the
buffer-registry id (DocumentId::new(buffer_id.0)), whereas every
other document event carries the document’s own DocumentId. The
two number spaces are not guaranteed to agree.
Fields
id: DocumentIdThe opened document (see the caveat above).
DocumentClosed
A document was closed. Subscribers drop per-document state keyed by
id (the LSP references provider, multibuffer excerpts, diff and VCS
caches all do).
Note: the variant is subscribed to and mirrored in WIT, but no production code path publishes it yet.
Fields
id: DocumentIdThe closed document.
BeforeSave
Fired before Self::DocumentSaved. Observation-only in
v1; future revisions may carry a payload that handlers can
mutate (formatters rewriting buffer content) or veto
(return Err to abort the save).
Published by the host’s save paths (:w and background saves)
immediately before the write.
Fields
id: DocumentIdThe document about to be written.
DocumentSaved
A document was written to disk successfully (after
Self::BeforeSave; a failed write publishes nothing further).
Published by the host’s save paths, including background saves of
buffers the user is not looking at.
DocumentChanged
A document’s text changed. Published by the host after each applied
edit (and by lattice-multibuffer when an edit through a multibuffer
lands in its source document). LSP’s didChange, the multibuffer’s
excerpt refresh and the diff subsystem feed on it.
Fields
id: DocumentIdThe changed document.
path: Option<PathBuf>The buffer’s filesystem path, if it has one. Carried so
subscribers can resolve URIs without holding their own
DocumentId -> path map. None for scratch / unsaved
buffers.
edits: Vec<AppliedEdit>The edits, in the order they were applied; each one’s ranges are in the coordinates of the buffer as the previous one left it. Today’s publishers send one edit per event.
SelectionsChanged
The selection set of a document changed — visual extension, a
selection-changing effect, gv. Published by
Editor::publish_selections_changed. Carries the complete new set,
not a delta.
Fields
id: DocumentIdThe document whose selections changed.
selections: SelectionSetThe full new selection set.
ModalModeChanged
Fired when the modal state transitions
(Normal -> Insert, Insert -> Normal, …). Carries the
previous and next state as opaque labels; the App owns the
ModalState type so the protocol layer keeps it as String.
Published by the host’s modal-state setter, only on a real
transition. The labels are the Debug rendering of the host’s
ModalState ("Normal", "Insert", …).
BeforeQuit
Fired before the editor exits. Observation-only in v1; the veto path (a handler returning Err to abort quit) layers on once the bus grows the Before-event mutation semantics.
OptionChanged
Fired after a typed-options registry value changes
(DESIGN.md §5.12). Carries the option’s canonical name plus
the formatted old / new value strings – string-formatted
(rather than Box<dyn Any>) because most subscribers just
react to the change signal and don’t need the typed value.
Subscribers that need the typed value re-read through the
registry (config.with(handle, |v| ...)).
old is None for the very first publish after registration
(when the option is initialised to its default and no prior
value exists); subsequent edits always carry both sides.
Published by lattice_config::ConfigRegistry through its injected
publisher, after the write and outside the registry lock (so a
handler may read other options).
Fields
MajorEntered
A major mode became the active major on buffer (published
after the mode’s on_activate resolved, so subscribers see
a consistent state). major is the major mode’s canonical
name (e.g. rust-mode) – carried as a String so the
protocol layer stays free of the ModeId type, mirroring
Self::ModalModeChanged.
This is the event minor-mode activation triggers filter on:
EventFilter.major_modes matches against major
(mode-architecture.md §7.4). Published by the mode
dispatcher’s cascade task (MA.1); supersedes the prior typed
ModeEvent::MajorEntered so the EF.1 filter machinery applies.
Fields
MajorExiting
The active major mode on buffer is about to be deactivated
(published before the mode’s Guard drops, so subscribers can
inspect what’s being torn down). Pairs with
Self::MajorEntered for minor-mode teardown. major is the
canonical name of the major being torn down.
Fields
MinorActivated
A minor mode was activated on buffer (published after its
on_activate resolved). minor is the minor mode’s canonical
name. The full observable mode-lifecycle quartet
(MajorEntered/MajorExiting/MinorActivated/MinorDeactivated)
lives on this Event enum (design.md §5.10.1) so hooks /
EventFilter apply uniformly; only the internal
ModeActivationFailed / OptionConflict cascade signals stay
on the typed lattice_mode::ModeEvent bus.
Fields
MinorDeactivated
A minor mode was deactivated on buffer (published before
its Guard drops). minor is the minor mode’s canonical name.
Fields
Plugin
A plugin-DEFINED event (PH7.8b). Unlike every arm above – each a
closed, host-owned editor-core transition – this arm is the OPEN
escape hatch a runtime-loaded plugin publishes through
(host-services emit-event). The host is a thin router: name is
the plugin’s event identifier (declared via register-event, surfaced
in the runtime event registry, event_registry); payload is opaque
MessagePack the plugin owns and the host NEVER interprets – the
boundary discipline the plugin host rests on. Every plugin event shares
this one variant + EventKind::Plugin; subscribers filter by name
inside their handler (the bus discriminates only to Plugin, not
per-name), so a new plugin event needs no enum/WIT change.
Fields
PluginCrashed
A plugin instance crashed (a lifecycle / callback export trapped: fuel
exhaustion, epoch deadline, a guest panic, or any wasm trap) and was
quarantined by the host (PH7.12). Unlike Self::Plugin – the OPEN
escape hatch a live plugin publishes through – this is a CLOSED,
host-owned lifecycle transition the host itself originates, mirroring the
mode-lifecycle quartet (Self::MinorDeactivated et al.): the host is
the sole publisher, and subscribers (a future crash-notification surface,
the Phase-8 plugin manager’s reload/health UI) filter it by kind, not by
string-matching a name.
Fired exactly once per instance – on the first trap that trips
quarantine. A component trap taints its instance irrecoverably (wasmtime
offers no rollback), so the instance is dead-until-reinstantiation: every
later call short-circuits without re-entering the dead Store, and no
further PluginCrashed fires until a reload (PH7.12b) mints a fresh
instance. The guarantee is isolation: the editor, actor, other
plugins, LSP, and UI are untouched.
plugin is the host-issued numeric plugin id (the same id inside
SourceLayer::Plugin(id)); func is the export that trapped
("on-event" / "spec" / "generate" / "apply-motion" / …); kind
is a stable machine label ("fuel" / "epoch" / "trap") – a String
(not the host’s TrapKind) so the protocol layer stays free of the
plugin-host type, mirroring Self::ModalModeChanged.
Host-internal: refused at the plugin boundary, never delivered to a guest.
Fields
PrePluginLoaded
A named plugin is ABOUT to run the load-time exports that read its own
options (OA.14d). Published by the loader mid-load: after the plugin’s
config seam drained — so every option it declares EXISTS and can be
set — and before every seam that consumes one.
It exists because Self::PluginLoaded is too late for a value the
plugin reads at load. org derives its per-keyword theme elements and its
generated highlight-query rules from org.todo-keywords inside
register-theme-elements; a handler that runs after the load sets an
option nothing will read again until a restart.
name is the manifest id, and it is carried rather than left implicit
so a handler discriminates: config for a plugin you know is legible,
config invited to run for every plugin on the disk is not.
Delivery is awaited. The loader publishes this via
EventBus::publish_awaited and does not continue the load until every
guest handler has returned — an unawaited publish would leave the
handler racing the very export it exists to precede. It is the one event
with that property; everything else on the bus is fire-and-forget.
PluginLoaded
A plugin finished loading (CI.1): every seam drained, its modes /
options / commands all registered. Published by the loader at
load_discovered completion. UNLIKE Self::PluginCrashed, this IS
delivered to guests — an init.rs subscribes (filtered by name) to run
deferred config against a now-present plugin (with-eval-after-load;
config-and-init.md). name is the manifest id; id the host-issued
numeric plugin id.
PluginUnloaded
A plugin was unloaded (CI.1): teardown reversed its contributions
(:plugin-unload / crash-teardown). Delivered to guests so a handler can
tear down its own dependent setup. Fields mirror Self::PluginLoaded.
Fields
ModeEnablementRequested
A request to enable/disable a minor mode globally (CI.4) — the
guest-to-Editor bridge for enable-mode / disable-mode. A plugin
(init.rs) calls the modes-seam enable-mode from an on-plugin-loaded
handler; the host publishes THIS, and the Editor (which owns the mode
registry + the open-buffer set + the activator) flips the enablement and
re-activates open buffers. Host-internal — NOT delivered back to guests
(like Self::PluginCrashed). mode is the mode id.
BufferOptionOverrideRequested
A request to set an option for ONE buffer — the guest-to-Editor bridge
for set-option-in-buffer, and the peer of
Self::ModeEnablementRequested in both shape and reason.
The config seam’s set-option writes the GLOBAL layer (it is the
:set path). A handler that wants “wrap in org buffers” cannot use it:
it would wrap everything, and nothing would unwrap on leaving org. The
buffer-local layer is what expresses that, and it lives on the Editor
(buffer_local_overrides) rather than in the ConfigRegistry the
plugin host holds — hence the bridge.
Host-internal, NOT delivered back to guests: a plugin observing every
other plugin’s option writes is a surveillance seam nobody asked for,
and option-changed already reports the outcome.
Fields
BackgroundTaskFinished
MG.41g: a long-running background operation finished.
The decoupling seam between producers of async work (magit’s git invocations, LSP requests, a plugin’s task) and whatever reports completion. Producers publish this and never mention notifications; the notification layer is one subscriber, so the policy — which levels surface, whether to notify at all, rate limiting — lives in one place and is configurable later without touching a single producer.
Replaces threading a NotificationStoreHandle into every
spawner, which was opt-in and therefore already forgotten in
five of magit’s ten (spawn_git, the generic one, among them).
Published today by magit’s git spawners; lattice-notify is the
subscriber that turns it into a notification. Not yet mirrored in WIT,
so it is not delivered to guests.
Fields
source: StringSubsystem that ran it — "magit", "lsp", a plugin id.
Lets a subscriber filter without parsing label.
scope: Option<String>NC.2: what the work was done in — a repository name, a
project, a server. None when the work has no such place.
A field, not part of label, so every producer’s scope
lands in the same place on screen: several notifications at
once are told apart by reading one column, not by parsing
each producer’s own phrasing.
label: StringWhat was done, as an imperative phrase naming its object:
"push main → origin/main", "drop stash@{2}". The outcome
is appended by whoever reports it, so the label must read
correctly before “failed” as well as before a summary.
outcome: TaskOutcomeHow it ended.
FilesChanged
OR.2: files under a directory a plugin asked the host to watch
(host-services.watch) changed on disk.
Addressed, not broadcast. plugin is the host-issued numeric id of
the plugin whose watch produced this, and the event-delivery actor drops
any delivery whose plugin is not its own. The bus is a broadcast and a
watch is a capability: a plugin granted fs:read over one directory
must not learn which files under another plugin’s watched directory
changed, and it would if this rode Self::Plugin — every
EventKind::Plugin subscriber sees every plugin event. The id never
crosses to a guest, because by the time it does it is always the guest’s
own.
Many paths, one event. A git pull that rewrites two hundred files
produces one delivery carrying two hundred paths, not two hundred
deliveries: the host coalesces a burst behind a quiet window before
publishing. A consumer re-reads what changed, so ordering within the
batch carries no meaning and the paths are deduplicated and sorted.
Published by lattice-plugin-host’s watch host.