Expand description
diff-mode minor mode + the host-side bridge that toggles
it on participating buffers as DiffSessions open and close.
§The invariant
A buffer participates in any [DiffSession] ⟺ diff-mode
is active on that buffer. The toggle lives inside the
[DiffSubsystem] (register_with_sources /
drop_session) so every present and future :diff*
ex-command (:diff <buf>, :diffthis, :diffsplit,
future :Gdiff D.7, future :diff-accept / :diff-reject
D.6) and the doc-close auto-drop
(note_buffer_closed → drop_session) inherit the toggle
without each call site repeating the activation logic.
See docs/dev/architecture/diff-system.md §3.4.7.
§Why a bridge instead of a direct call
The subsystem holds an Arc<DiffSubsystem> and may be
invoked from a tokio worker (the bus-subscription drainer
that translates DocumentClosed events into
drop_session). The mode-activation API
(ModeRegistry::activate_minor / deactivate_minor)
needs &mut ActiveModes per buffer plus references to
mode-guards, config, event bus, and services — all of which
live on Editor and aren’t shareable across threads.
The bridge resolves this by queueing mode changes from any
thread into a Mutex<Vec<DiffModeChange>> and exposing a
drain_pending call the editor invokes from the dispatch
tail (publish_render_state). The dispatch tail applies
each change using the existing mode_registry pattern.
§Why ref-counted per buffer
v1’s :diff* flows don’t intentionally put the same
buffer in two sessions, but lookup_session_for indexes
only the primary side. A baseline-side buffer can therefore
be staged into a second session without the existing
rejection check catching it; D.6 three-way will plausibly
see a MergeBase participant also diffed against working
tree. Naive “session opens → mode on / session closes →
mode off” would flip diff-mode off prematurely when the
first of two sessions sharing a buffer closes.
The bridge therefore keeps a per-buffer refcount keyed by
the participating session keys. Activation appends the
session key to the buffer’s bucket; deactivation removes
it; the bridge enqueues a Deactivate change only when
the bucket empties.
Structs§
- Diff
Conflict Mode diff-conflict-mode(smerge-style) minor — DX.8 shell (2026-06-24).- Diff
Decoration Data - Render-time snapshot for
DiffMode::gutter_decorations. Carries the active buffer’s diff sign map; injected by the renderer only for the active-document buffer (sign_map is active-doc only). - Diff
Mode diff-modeminor. Empty marker in v1 (D.5.a): the bit other layers consult. D.5.b/c land thedo/dpchord bindings against theMinorMode(ModeId::new("diff-mode"))keymap layer (K.1.b shape).- Diff
Mode Bridge - Host-side bridge between [
DiffSubsystem] session lifecycle and the per-bufferActiveModesset. See module docs for the why. - Diff
Mode Change - One queued toggle. Buffered until the dispatch tail drains the bridge and applies them through the mode registry.
- Diff
Mode Guard - DX.3-C7 (2026-06-24): deregisters the buffer’s
HunkFoldSourcewhendiff-modedeactivates (the diff session closed, or the buffer’s mode set changed).Dropfires from the mode guard store whendeactivate_minorruns — the same Drop-based lifecycle multibuffer’sMultibufferModeGuarduses for its excerpt/file-boundary fold sources. Empty when the fold service / diff subsystem weren’t registered (some test harnesses) — Drop is then a no-op.
Enums§
- Diff
Mode Action - Direction of a pending
DiffModeChange.Activateaddsdiff-modeto the buffer’sActiveModes.minors();Deactivateremoves it. - Diff
Mode Kind - CR.3 (2026-06-24): which diff minor mode a
DiffModeChangetoggles.Base=diff-mode(every session participant);Conflict=diff-conflict-mode(only sessions whose sign map carries aConflictregion). The host’sapply_pending_diff_mode_changesmaps the kind to the concreteModeId.
Constants§
- DIFF_
ELEMENT - Modeline element id for the diff add/change summary. Owned by the
diff subsystem (
feedback_mode_owns_its_surface).
Functions§
- diff_
content - Format a diff sign-map into the
+N ~Mmodeline content (ML.3). The formatter the diff subsystem owns — moved here from the retiredDiffMode::status_line_items. Empty content (no adds/changes) ⇒ hidden this frame (the caller’sapplyclears the slot). - register_
diff_ modeline_ element - Register the
diffmodeline descriptor (ML.3).Scope::Global: the active document’s session sign-map is a single shared value, so the summary renders on the active pane’s modeline (the renderer gates Global elements to the active pane, modeline.md §7) — there is no distinct per-side count to show. Content is computed host-side on the actor (where the sign map already lives,sync_diff_modeline_element) rather than counted on the render thread (paramount #1). Left zone, aftercore.path. - register_
diff_ modes - Register the diff modes against
registry. Called fromcrate::install::install(the Phase-B install list) alongside the other feature-crateregister_*_modeshelpers. Registers bothdiff-mode(the 2-way base surface) and thediff-conflict-modeshell (DX.8). - sign_
map_ has_ conflicts - DX.8 (2026-06-24): the
diff-conflict-modeactivation predicate.