lattice_protocol/event.rs
1//! Events published by the core to subscribed clients.
2//!
3//! Per DESIGN.md §5.10: every meaningful editor state transition
4//! publishes a typed event. Vim's `autocmd` and emacs's hooks both
5//! desugar to the same `EventBus::subscribe` call (filter +
6//! sink). The `Event` enum is the catalog; `EventKind` is the
7//! discriminator used by filter dispatch.
8//!
9//! This is the *closed* catalogue: editor-core transitions the host owns.
10//! Feature crates declare their own events as types through
11//! [`crate::event_registry`] instead of growing this enum, and plugins
12//! publish theirs through the one open arm, [`Event::Plugin`].
13//!
14//! The bus itself (`lattice_runtime::EventBus`) is not here — this crate has
15//! no runtime. Publishing is fire-and-forget unless a variant says otherwise.
16//! Most variants are also delivered to WASM plugins (mirrored in WIT by
17//! `lattice-plugin-host`); the ones marked *host-internal* below are refused
18//! at that boundary.
19//!
20//! Versions: `version` fields carry `lattice_core::Document`'s counters.
21//! [`Event::DocumentOpened`] carries the *text* version (bumps on text
22//! changes only); [`Event::DocumentChanged`] and [`Event::SelectionsChanged`]
23//! carry the whole-document version, which also bumps on selection changes.
24//!
25//! # Examples
26//!
27//! ```
28//! use lattice_protocol::{DocumentId, Event, EventKind};
29//! use std::path::PathBuf;
30//!
31//! let saved = Event::DocumentSaved {
32//! id: DocumentId::new(3),
33//! path: PathBuf::from("src/lib.rs"),
34//! };
35//! // Filters bucket on the payload-free discriminator.
36//! assert_eq!(saved.kind(), EventKind::DocumentSaved);
37//! assert_eq!(Event::BeforeQuit.kind(), EventKind::BeforeQuit);
38//! ```
39
40use std::path::PathBuf;
41
42use serde::{Deserialize, Serialize};
43
44use crate::ids::{BufferId, DocumentId};
45use crate::position::Range;
46use crate::selection::SelectionSet;
47
48/// One editor-core state transition, as published on the event bus.
49///
50/// See the [module docs](crate::event) for how this relates to typed events and the
51/// plugin boundary; each variant says when it fires, who publishes it, and
52/// what its fields carry.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub enum Event {
55 /// Fired when a document buffer opens. Subscribers (the
56 /// LSP attach driver, future plugin hooks, project-watcher,
57 /// completion warmer) react asynchronously; the publisher
58 /// (`Editor::publish_document_opened_for_active`, run at boot for
59 /// the initial document and after each `:e <path>` open) returns
60 /// immediately. The
61 /// event-driven design keeps the UI thread off the LSP
62 /// `initialize` round-trip -- aligned with paramount goal
63 /// #4 (asynchronicity).
64 ///
65 /// `path` is `None` for unsaved scratch buffers (no LSP
66 /// attach work to drive). `text` carries the buffer's
67 /// initial content so subscribers don't have to reach back
68 /// through a document handle on the publish path -- LSP
69 /// hands it straight to `didOpen`.
70 ///
71 /// Caveat: the current publisher builds `id` from the raw value of the
72 /// buffer-registry id (`DocumentId::new(buffer_id.0)`), whereas every
73 /// other document event carries the document's own [`DocumentId`]. The
74 /// two number spaces are not guaranteed to agree.
75 DocumentOpened {
76 /// The opened document (see the caveat above).
77 id: DocumentId,
78 /// Its file path; `None` for a scratch buffer.
79 path: Option<PathBuf>,
80 /// The document's *text* version at open — the version LSP's
81 /// `didOpen` starts from.
82 version: u64,
83 /// The full initial content.
84 text: String,
85 },
86 /// A document was closed. Subscribers drop per-document state keyed by
87 /// `id` (the LSP references provider, multibuffer excerpts, diff and VCS
88 /// caches all do).
89 ///
90 /// Note: the variant is subscribed to and mirrored in WIT, but no
91 /// production code path publishes it yet.
92 DocumentClosed {
93 /// The closed document.
94 id: DocumentId,
95 },
96 /// Fired before [`Self::DocumentSaved`]. Observation-only in
97 /// v1; future revisions may carry a payload that handlers can
98 /// mutate (formatters rewriting buffer content) or veto
99 /// (return Err to abort the save).
100 ///
101 /// Published by the host's save paths (`:w` and background saves)
102 /// immediately before the write.
103 BeforeSave {
104 /// The document about to be written.
105 id: DocumentId,
106 /// Where it is about to be written.
107 path: PathBuf,
108 },
109 /// A document was written to disk successfully (after
110 /// [`Self::BeforeSave`]; a failed write publishes nothing further).
111 /// Published by the host's save paths, including background saves of
112 /// buffers the user is not looking at.
113 DocumentSaved {
114 /// The saved document.
115 id: DocumentId,
116 /// The path actually written.
117 path: PathBuf,
118 },
119 /// A document's text changed. Published by the host after each applied
120 /// edit (and by `lattice-multibuffer` when an edit through a multibuffer
121 /// lands in its source document). LSP's `didChange`, the multibuffer's
122 /// excerpt refresh and the diff subsystem feed on it.
123 DocumentChanged {
124 /// The changed document.
125 id: DocumentId,
126 /// The buffer's filesystem path, if it has one. Carried so
127 /// subscribers can resolve URIs without holding their own
128 /// DocumentId -> path map. `None` for scratch / unsaved
129 /// buffers.
130 path: Option<PathBuf>,
131 /// The document's whole version after the change (see the module
132 /// docs on versions).
133 version: u64,
134 /// The edits, in the order they were applied; each one's ranges are
135 /// in the coordinates of the buffer as the previous one left it.
136 /// Today's publishers send one edit per event.
137 edits: Vec<AppliedEdit>,
138 },
139 /// The selection set of a document changed — visual extension, a
140 /// selection-changing effect, `gv`. Published by
141 /// `Editor::publish_selections_changed`. Carries the complete new set,
142 /// not a delta.
143 SelectionsChanged {
144 /// The document whose selections changed.
145 id: DocumentId,
146 /// The document's whole version after the change.
147 version: u64,
148 /// The full new selection set.
149 selections: SelectionSet,
150 },
151 /// Fired when the modal state transitions
152 /// (Normal -> Insert, Insert -> Normal, ...). Carries the
153 /// previous and next state as opaque labels; the App owns the
154 /// `ModalState` type so the protocol layer keeps it as String.
155 ///
156 /// Published by the host's modal-state setter, only on a real
157 /// transition. The labels are the `Debug` rendering of the host's
158 /// `ModalState` (`"Normal"`, `"Insert"`, ...).
159 ModalModeChanged {
160 /// The state being left.
161 from: String,
162 /// The state being entered.
163 to: String,
164 },
165 /// Fired before the editor exits. Observation-only in v1; the
166 /// veto path (a handler returning Err to abort quit) layers on
167 /// once the bus grows the Before-event mutation semantics.
168 BeforeQuit,
169 /// Fired after a typed-options registry value changes
170 /// (DESIGN.md §5.12). Carries the option's canonical name plus
171 /// the formatted old / new value strings -- string-formatted
172 /// (rather than `Box<dyn Any>`) because most subscribers just
173 /// react to the change signal and don't need the typed value.
174 /// Subscribers that need the typed value re-read through the
175 /// registry (`config.with(handle, |v| ...)`).
176 ///
177 /// `old` is `None` for the very first publish after registration
178 /// (when the option is initialised to its default and no prior
179 /// value exists); subsequent edits always carry both sides.
180 ///
181 /// Published by `lattice_config::ConfigRegistry` through its injected
182 /// publisher, after the write and outside the registry lock (so a
183 /// handler may read other options).
184 OptionChanged {
185 /// The option's canonical name (`tabstop`, not `ts`).
186 name: String,
187 /// The previous value, formatted; `None` on the first publish.
188 old: Option<String>,
189 /// The new value, formatted as `:set` would print it.
190 new: String,
191 },
192 /// A major mode became the active major on `buffer` (published
193 /// *after* the mode's `on_activate` resolved, so subscribers see
194 /// a consistent state). `major` is the major mode's canonical
195 /// name (e.g. `rust-mode`) -- carried as a `String` so the
196 /// protocol layer stays free of the `ModeId` type, mirroring
197 /// [`Self::ModalModeChanged`].
198 ///
199 /// This is the event minor-mode activation triggers filter on:
200 /// `EventFilter.major_modes` matches against `major`
201 /// (mode-architecture.md §7.4). Published by the mode
202 /// dispatcher's cascade task (MA.1); supersedes the prior typed
203 /// `ModeEvent::MajorEntered` so the EF.1 filter machinery applies.
204 MajorEntered {
205 /// The buffer the major mode is now active on.
206 buffer: BufferId,
207 /// The major mode's canonical name.
208 major: String,
209 },
210 /// The active major mode on `buffer` is about to be deactivated
211 /// (published *before* the mode's Guard drops, so subscribers can
212 /// inspect what's being torn down). Pairs with
213 /// [`Self::MajorEntered`] for minor-mode teardown. `major` is the
214 /// canonical name of the major being torn down.
215 MajorExiting {
216 /// The buffer the major mode is leaving.
217 buffer: BufferId,
218 /// The major mode's canonical name.
219 major: String,
220 },
221 /// A minor mode was activated on `buffer` (published *after* its
222 /// `on_activate` resolved). `minor` is the minor mode's canonical
223 /// name. The full observable mode-lifecycle quartet
224 /// (`MajorEntered`/`MajorExiting`/`MinorActivated`/`MinorDeactivated`)
225 /// lives on this `Event` enum (design.md §5.10.1) so hooks /
226 /// `EventFilter` apply uniformly; only the internal
227 /// `ModeActivationFailed` / `OptionConflict` cascade signals stay
228 /// on the typed `lattice_mode::ModeEvent` bus.
229 MinorActivated {
230 /// The buffer the minor mode is now active on.
231 buffer: BufferId,
232 /// The minor mode's canonical name.
233 minor: String,
234 },
235 /// A minor mode was deactivated on `buffer` (published *before*
236 /// its Guard drops). `minor` is the minor mode's canonical name.
237 MinorDeactivated {
238 /// The buffer the minor mode is leaving.
239 buffer: BufferId,
240 /// The minor mode's canonical name.
241 minor: String,
242 },
243 /// A plugin-DEFINED event (PH7.8b). Unlike every arm above -- each a
244 /// closed, host-owned editor-core transition -- this arm is the OPEN
245 /// escape hatch a runtime-loaded plugin publishes through
246 /// (`host-services emit-event`). The host is a thin router: `name` is
247 /// the plugin's event identifier (declared via `register-event`, surfaced
248 /// in the runtime event registry, `event_registry`); `payload` is opaque
249 /// MessagePack the *plugin* owns and the host NEVER interprets -- the
250 /// boundary discipline the plugin host rests on. Every plugin event shares
251 /// this one variant + [`EventKind::Plugin`]; subscribers filter by `name`
252 /// inside their handler (the bus discriminates only to `Plugin`, not
253 /// per-name), so a new plugin event needs no enum/WIT change.
254 Plugin {
255 /// The plugin-declared event name (`my-plugin.file-indexed`).
256 name: String,
257 /// Opaque MessagePack bytes, owned and interpreted only by plugins.
258 payload: Vec<u8>,
259 },
260 /// A plugin instance crashed (a lifecycle / callback export trapped: fuel
261 /// exhaustion, epoch deadline, a guest panic, or any wasm trap) and was
262 /// quarantined by the host (PH7.12). Unlike [`Self::Plugin`] -- the OPEN
263 /// escape hatch a *live* plugin publishes through -- this is a CLOSED,
264 /// host-owned lifecycle transition the host itself originates, mirroring the
265 /// mode-lifecycle quartet ([`Self::MinorDeactivated`] et al.): the host is
266 /// the sole publisher, and subscribers (a future crash-notification surface,
267 /// the Phase-8 plugin manager's reload/health UI) filter it by *kind*, not by
268 /// string-matching a name.
269 ///
270 /// Fired exactly once per instance -- on the first trap that trips
271 /// quarantine. A component trap taints its instance irrecoverably (wasmtime
272 /// offers no rollback), so the instance is dead-until-reinstantiation: every
273 /// later call short-circuits without re-entering the dead `Store`, and no
274 /// further `PluginCrashed` fires until a reload (PH7.12b) mints a fresh
275 /// instance. The guarantee is **isolation**: the editor, actor, other
276 /// plugins, LSP, and UI are untouched.
277 ///
278 /// `plugin` is the host-issued numeric plugin id (the same id inside
279 /// `SourceLayer::Plugin(id)`); `func` is the export that trapped
280 /// (`"on-event"` / `"spec"` / `"generate"` / `"apply-motion"` / ...); `kind`
281 /// is a stable machine label (`"fuel"` / `"epoch"` / `"trap"`) -- a `String`
282 /// (not the host's `TrapKind`) so the protocol layer stays free of the
283 /// plugin-host type, mirroring [`Self::ModalModeChanged`].
284 ///
285 /// Host-internal: refused at the plugin boundary, never delivered to a
286 /// guest.
287 PluginCrashed {
288 /// The host-issued numeric id of the quarantined plugin instance.
289 plugin: u32,
290 /// The export that trapped (`"on-event"`, `"spec"`, ...).
291 func: String,
292 /// Why: `"fuel"`, `"epoch"` or `"trap"`.
293 kind: String,
294 },
295 /// A named plugin is ABOUT to run the load-time exports that read its own
296 /// options (OA.14d). Published by the loader mid-load: after the plugin's
297 /// `config` seam drained — so every option it declares EXISTS and can be
298 /// set — and before every seam that consumes one.
299 ///
300 /// It exists because [`Self::PluginLoaded`] is too late for a value the
301 /// plugin reads at load. org derives its per-keyword theme elements and its
302 /// generated highlight-query rules from `org.todo-keywords` inside
303 /// `register-theme-elements`; a handler that runs after the load sets an
304 /// option nothing will read again until a restart.
305 ///
306 /// `name` is the manifest id, and it is carried rather than left implicit
307 /// so a handler discriminates: config for a plugin you know is legible,
308 /// config invited to run for every plugin on the disk is not.
309 ///
310 /// **Delivery is awaited.** The loader publishes this via
311 /// `EventBus::publish_awaited` and does not continue the load until every
312 /// guest handler has returned — an unawaited publish would leave the
313 /// handler racing the very export it exists to precede. It is the one event
314 /// with that property; everything else on the bus is fire-and-forget.
315 PrePluginLoaded {
316 /// The loading plugin's manifest id. (No numeric id: guests receive
317 /// the name only.)
318 name: String,
319 },
320 /// A plugin finished loading (CI.1): every seam drained, its modes /
321 /// options / commands all registered. Published by the loader at
322 /// `load_discovered` completion. UNLIKE [`Self::PluginCrashed`], this IS
323 /// delivered to guests — an `init.rs` subscribes (filtered by `name`) to run
324 /// deferred config against a now-present plugin (`with-eval-after-load`;
325 /// config-and-init.md). `name` is the manifest id; `id` the host-issued
326 /// numeric plugin id.
327 PluginLoaded {
328 /// The plugin's manifest id.
329 name: String,
330 /// The host-issued numeric plugin id.
331 id: u32,
332 },
333 /// A plugin was unloaded (CI.1): teardown reversed its contributions
334 /// (`:plugin-unload` / crash-teardown). Delivered to guests so a handler can
335 /// tear down its own dependent setup. Fields mirror [`Self::PluginLoaded`].
336 PluginUnloaded {
337 /// The plugin's manifest id.
338 name: String,
339 /// The host-issued numeric plugin id it had while loaded.
340 id: u32,
341 },
342 /// A request to enable/disable a minor mode globally (CI.4) — the
343 /// guest-to-Editor bridge for `enable-mode` / `disable-mode`. A plugin
344 /// (init.rs) calls the modes-seam `enable-mode` from an `on-plugin-loaded`
345 /// handler; the host publishes THIS, and the Editor (which owns the mode
346 /// registry + the open-buffer set + the activator) flips the enablement and
347 /// re-activates open buffers. Host-internal — NOT delivered back to guests
348 /// (like [`Self::PluginCrashed`]). `mode` is the mode id.
349 ModeEnablementRequested {
350 /// The minor mode's id.
351 mode: String,
352 /// `true` to enable globally, `false` to disable.
353 enabled: bool,
354 },
355 /// A request to set an option for ONE buffer — the guest-to-Editor bridge
356 /// for `set-option-in-buffer`, and the peer of
357 /// [`Self::ModeEnablementRequested`] in both shape and reason.
358 ///
359 /// The config seam's `set-option` writes the GLOBAL layer (it is the
360 /// `:set` path). A handler that wants "wrap in org buffers" cannot use it:
361 /// it would wrap everything, and nothing would unwrap on leaving org. The
362 /// buffer-local layer is what expresses that, and it lives on the Editor
363 /// (`buffer_local_overrides`) rather than in the `ConfigRegistry` the
364 /// plugin host holds — hence the bridge.
365 ///
366 /// Host-internal, NOT delivered back to guests: a plugin observing every
367 /// other plugin's option writes is a surveillance seam nobody asked for,
368 /// and `option-changed` already reports the outcome.
369 BufferOptionOverrideRequested {
370 /// The buffer the override applies to.
371 buffer: BufferId,
372 /// `name=value` in `:set` syntax, parsed by the same
373 /// `parse_for_buffer_local` the `:setlocal` path uses — so a guest
374 /// cannot express anything `:setlocal` could not, and a bad value is
375 /// rejected with the same message.
376 option: String,
377 },
378 /// MG.41g: a long-running background operation finished.
379 ///
380 /// The decoupling seam between *producers* of async work (magit's
381 /// git invocations, LSP requests, a plugin's task) and whatever
382 /// *reports* completion. Producers publish this and never mention
383 /// notifications; the notification layer is one subscriber, so the
384 /// policy — which levels surface, whether to notify at all, rate
385 /// limiting — lives in one place and is configurable later without
386 /// touching a single producer.
387 ///
388 /// Replaces threading a `NotificationStoreHandle` into every
389 /// spawner, which was opt-in and therefore already forgotten in
390 /// five of magit's ten (`spawn_git`, the generic one, among them).
391 ///
392 /// Published today by magit's git spawners; `lattice-notify` is the
393 /// subscriber that turns it into a notification. Not yet mirrored in WIT,
394 /// so it is not delivered to guests.
395 BackgroundTaskFinished {
396 /// Subsystem that ran it — `"magit"`, `"lsp"`, a plugin id.
397 /// Lets a subscriber filter without parsing `label`.
398 source: String,
399 /// NC.2: what the work was done *in* — a repository name, a
400 /// project, a server. `None` when the work has no such place.
401 ///
402 /// A field, not part of `label`, so every producer's scope
403 /// lands in the same place on screen: several notifications at
404 /// once are told apart by reading one column, not by parsing
405 /// each producer's own phrasing.
406 scope: Option<String>,
407 /// What was done, as an imperative phrase naming its object:
408 /// `"push main → origin/main"`, `"drop stash@{2}"`. The outcome
409 /// is appended by whoever reports it, so the label must read
410 /// correctly before "failed" as well as before a summary.
411 label: String,
412 /// How it ended.
413 outcome: TaskOutcome,
414 },
415 /// OR.2: files under a directory a plugin asked the host to watch
416 /// (`host-services.watch`) changed on disk.
417 ///
418 /// **Addressed, not broadcast.** `plugin` is the host-issued numeric id of
419 /// the plugin whose watch produced this, and the event-delivery actor drops
420 /// any delivery whose `plugin` is not its own. The bus is a broadcast and a
421 /// watch is a capability: a plugin granted `fs:read` over one directory
422 /// must not learn which files under *another* plugin's watched directory
423 /// changed, and it would if this rode [`Self::Plugin`] — every
424 /// `EventKind::Plugin` subscriber sees every plugin event. The id never
425 /// crosses to a guest, because by the time it does it is always the guest's
426 /// own.
427 ///
428 /// **Many paths, one event.** A `git pull` that rewrites two hundred files
429 /// produces one delivery carrying two hundred paths, not two hundred
430 /// deliveries: the host coalesces a burst behind a quiet window before
431 /// publishing. A consumer re-reads what changed, so ordering within the
432 /// batch carries no meaning and the paths are deduplicated and sorted.
433 ///
434 /// Published by `lattice-plugin-host`'s watch host.
435 FilesChanged {
436 /// The host-issued numeric plugin id that armed the watch.
437 plugin: u32,
438 /// Absolute paths that changed, created or were removed. A removal is
439 /// reported as a change — the consumer stats the path — because an
440 /// index that cannot see deletions offers destinations that no longer
441 /// exist.
442 paths: Vec<std::path::PathBuf>,
443 },
444}
445
446/// MG.41g: how a [`Event::BackgroundTaskFinished`] ended.
447///
448/// Deliberately two variants rather than a `Result<String, String>`:
449/// the payload crosses the plugin boundary, where a typed variant
450/// mirrors cleanly and a `Result` does not.
451#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
452pub enum TaskOutcome {
453 /// Finished cleanly. `summary` is a short human line — the full
454 /// output belongs in a log, not a notification.
455 Succeeded {
456 /// A short human-readable result line.
457 summary: String,
458 },
459 /// NC.2: ended cleanly but **not done** — a rebase paused on an
460 /// `edit`, a merge left uncommitted, a conflict waiting for the
461 /// user. Reporting these as success says "finished" about work the
462 /// user still has to finish. `message` says what is waiting.
463 Stopped {
464 /// What is waiting on the user.
465 message: String,
466 },
467 /// Failed. `message` is the reason, already truncated for display.
468 Failed {
469 /// Why it failed, display-ready.
470 message: String,
471 },
472}
473
474// M.5.3.b: `LspLogPushed`, `LspBufferAttached`, and
475// `LspBufferDetached` moved out of this enum and into
476// `lattice-lsp::events` as concrete types implementing
477// [`crate::event_registry::Event`]. They publish via the
478// typed-bus path (`EventBus::publish_typed`); subscribers
479// use `EventBus::subscribe_typed::<T>`. Future cleanup will
480// migrate the rest of the enum the same way.
481
482impl Event {
483 /// Project the event to its [`EventKind`] discriminator. Used
484 /// by the runtime event bus's filter dispatch to bucket
485 /// subscriptions without string-matching variant names.
486 pub fn kind(&self) -> EventKind {
487 match self {
488 Event::DocumentOpened { .. } => EventKind::DocumentOpened,
489 Event::DocumentClosed { .. } => EventKind::DocumentClosed,
490 Event::BeforeSave { .. } => EventKind::BeforeSave,
491 Event::DocumentSaved { .. } => EventKind::DocumentSaved,
492 Event::DocumentChanged { .. } => EventKind::DocumentChanged,
493 Event::SelectionsChanged { .. } => EventKind::SelectionsChanged,
494 Event::ModalModeChanged { .. } => EventKind::ModalModeChanged,
495 Event::BeforeQuit => EventKind::BeforeQuit,
496 Event::OptionChanged { .. } => EventKind::OptionChanged,
497 Event::MajorEntered { .. } => EventKind::MajorEntered,
498 Event::MajorExiting { .. } => EventKind::MajorExiting,
499 Event::MinorActivated { .. } => EventKind::MinorActivated,
500 Event::MinorDeactivated { .. } => EventKind::MinorDeactivated,
501 Event::Plugin { .. } => EventKind::Plugin,
502 Event::PluginCrashed { .. } => EventKind::PluginCrashed,
503 Event::PrePluginLoaded { .. } => EventKind::PrePluginLoaded,
504 Event::PluginLoaded { .. } => EventKind::PluginLoaded,
505 Event::PluginUnloaded { .. } => EventKind::PluginUnloaded,
506 Event::ModeEnablementRequested { .. } => EventKind::ModeEnablementRequested,
507 Event::BufferOptionOverrideRequested { .. } => EventKind::BufferOptionOverrideRequested,
508 Event::BackgroundTaskFinished { .. } => EventKind::BackgroundTaskFinished,
509 Event::FilesChanged { .. } => EventKind::FilesChanged,
510 }
511 }
512}
513
514/// Discriminator for [`Event`] variants. Stored in
515/// `EventFilter::kinds` and used by the bus to bucket
516/// subscriptions per kind so publish does no global iteration.
517#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
518pub enum EventKind {
519 /// Discriminator for [`Event::DocumentOpened`].
520 DocumentOpened,
521 /// Discriminator for [`Event::DocumentClosed`].
522 DocumentClosed,
523 /// Discriminator for [`Event::BeforeSave`].
524 BeforeSave,
525 /// Discriminator for [`Event::DocumentSaved`].
526 DocumentSaved,
527 /// Discriminator for [`Event::DocumentChanged`].
528 DocumentChanged,
529 /// Discriminator for [`Event::SelectionsChanged`].
530 SelectionsChanged,
531 /// Discriminator for [`Event::ModalModeChanged`].
532 ModalModeChanged,
533 /// Discriminator for [`Event::BeforeQuit`].
534 BeforeQuit,
535 /// Discriminator for [`Event::OptionChanged`].
536 OptionChanged,
537 /// Discriminator for [`Event::MajorEntered`].
538 MajorEntered,
539 /// Discriminator for [`Event::MajorExiting`].
540 MajorExiting,
541 /// Discriminator for [`Event::MinorActivated`].
542 MinorActivated,
543 /// Discriminator for [`Event::MinorDeactivated`].
544 MinorDeactivated,
545 /// Discriminator for every plugin-defined event ([`Event::Plugin`]). All
546 /// plugin events share this one kind; the per-event `name` is NOT a bus
547 /// discriminator (subscribers filter by name in their handler, PH7.8b).
548 Plugin,
549 /// Discriminator for [`Event::PluginCrashed`] -- the host-originated
550 /// crash/quarantine lifecycle transition (PH7.12). A single kind so a
551 /// crash-notification surface or the Phase-8 plugin manager subscribes to
552 /// every plugin crash with one filter.
553 PluginCrashed,
554 /// Discriminator for [`Event::PrePluginLoaded`] (OA.14d) — the awaited
555 /// signal an `init.rs` subscribes to for config a plugin reads at LOAD.
556 PrePluginLoaded,
557 /// Discriminator for [`Event::PluginLoaded`] (CI.1) — the plugin-load
558 /// lifecycle signal an `init.rs` subscribes to for deferred config.
559 PluginLoaded,
560 /// Discriminator for [`Event::PluginUnloaded`] (CI.1).
561 PluginUnloaded,
562 /// Discriminator for [`Event::ModeEnablementRequested`] (CI.4) — the
563 /// host-internal enable/disable-minor-mode bridge the Editor handles.
564 ModeEnablementRequested,
565 /// Discriminator for [`Event::BufferOptionOverrideRequested`] — like its
566 /// neighbour above, host-internal and never deliverable to a guest.
567 BufferOptionOverrideRequested,
568 /// Discriminator for [`Event::BackgroundTaskFinished`] (MG.41g).
569 BackgroundTaskFinished,
570 /// Discriminator for [`Event::FilesChanged`] (OR.2) — a plugin's
571 /// `host-services.watch` fired. One kind for every watch; the delivery
572 /// actor, not the filter, is what scopes a batch to the plugin that armed
573 /// it (see the variant's doc).
574 FilesChanged,
575}
576
577/// An edit as actually applied to the buffer (the original `Edit` plus the
578/// resulting range, useful for clients that want to know what changed).
579///
580/// The bus-level copy of `lattice_core::AppliedEdit`, without its
581/// tree-sitter [`EditDelta`](crate::EditDelta). Positions are
582/// (line, UTF-8 byte), as everywhere in this crate.
583///
584/// `inserted_text` carries the text that was placed into `inserted_range`.
585/// Together with `original_range` this is exactly what an LSP
586/// `textDocument/didChange` payload needs, which lets the
587/// `lattice-lsp` fan-in synthesise a `lattice_protocol::edit::Edit`
588/// from this event without re-reading the buffer.
589#[derive(Debug, Clone, Serialize, Deserialize)]
590pub struct AppliedEdit {
591 /// The range the edit targeted, in pre-edit coordinates.
592 pub original_range: Range,
593 /// Where the inserted text now sits, in post-edit coordinates: starts at
594 /// `original_range.start`; empty for a pure delete.
595 pub inserted_range: Range,
596 /// The text removed from `original_range` (empty for a pure insert).
597 pub replaced_text: String,
598 /// The text placed at `inserted_range` (empty for a pure delete).
599 pub inserted_text: String,
600}