pub struct DiffSubsystem { /* private fields */ }Expand description
Process-wide registry + routing layer for diff sessions.
Lifecycle (D.2.a):
register(buffer_id, algorithm)— idempotent; pure-compute path with no sources, no debouncer, no routing entries. For tests and the future:describe-diffstandalone path.register_with_sources(buffer_id, algorithm, descriptor)(D.2.c) — production registration. Installs the descriptor + watch entries + a per-sessionDebouncer. Edits to any buffer indescriptor.watchwill route to this session.lookup(buffer_id)— returnsSome(Arc<DiffSession>)if registered.lookup_descriptor(buffer_id)(D.2.c) — returnsSome(DiffDescriptor)if registered with sources.drop_session(buffer_id)— removes session, descriptor, debouncer, and the session’s entries from everywatchersbucket. In-flightArcholders are unaffected.iter_sessions()— snapshot of all currently-registered sessions. Powers:describe-diff(D.2.d).
Routing (D.2.c):
bind(bus, resolver)— installs the bus subscription + drainer task. Returns aDiffSubscriptionGuardwhoseDropunsubscribes and aborts.note_buffer_edited(buffer_id)— pokes the debouncer for every session inwatchers[buffer_id]. Public so tests and (future) non-bus drivers can fire it directly.note_buffer_closed(buffer_id)— callsdrop_session.
The registry is Default-able and zero-cost to construct; the
host owns one instance, threaded through Editor. Debounce
window defaults to DEFAULT_DEBOUNCE_WINDOW and can be
overridden via Self::with_debounce_window.
Implementations§
Source§impl DiffSubsystem
impl DiffSubsystem
pub fn new() -> DiffSubsystem
Sourcepub fn with_debounce_window(window: Duration) -> DiffSubsystem
pub fn with_debounce_window(window: Duration) -> DiffSubsystem
Construct a subsystem with a non-default debounce window.
Primarily a test hook (so unit tests can run with
Duration::from_millis(1) and avoid wall-clock waits) but
hosts can also tune the window via the typed options
registry once that wiring lands (D.2.e).
pub fn debounce_window(&self) -> Duration
Sourcepub fn mode_bridge(&self) -> Arc<DiffModeBridge> ⓘ
pub fn mode_bridge(&self) -> Arc<DiffModeBridge> ⓘ
D.5.a (2026-05-30): access the diff-mode lifecycle
bridge so the editor’s dispatch tail can drain queued
activations. The subsystem owns the bridge identity; the
returned Arc is a cheap reference clone, not a take.
Sourcepub fn register(
&self,
buffer_id: BufferId,
algorithm: DiffAlgorithm,
) -> Arc<DiffSession> ⓘ
pub fn register( &self, buffer_id: BufferId, algorithm: DiffAlgorithm, ) -> Arc<DiffSession> ⓘ
Register a session for buffer_id with no sources. The
session has no debouncer, no descriptor, no watchers
entries — used by tests and by the pure-compute API path.
Production callers want Self::register_with_sources.
Idempotent: returns the existing Arc<DiffSession> if
one is already registered (the algorithm argument is
ignored in that case).
Sourcepub fn register_with_sources(
&self,
buffer_id: BufferId,
algorithm: DiffAlgorithm,
descriptor: DiffDescriptor,
) -> Arc<DiffSession> ⓘ
pub fn register_with_sources( &self, buffer_id: BufferId, algorithm: DiffAlgorithm, descriptor: DiffDescriptor, ) -> Arc<DiffSession> ⓘ
D.2.c: register a session with a full DiffDescriptor.
Inserts (or reuses) the session, stores the descriptor,
rebuilds the inverse watchers entries to include this
session for every buffer in descriptor.watch, and
installs a per-session Debouncer.
Idempotent on session identity (same Arc<DiffSession>
returned on re-registration) but descriptor is
replaced on re-registration — the caller may be
updating sources (e.g. switching baseline from
StaticSource to GitBaseline). The old watch
entries are scrubbed before the new ones are installed
so a re-register with a shrunken watch list doesn’t
leave stale routes.
Sourcepub fn lookup_session_for(
&self,
buffer_id: BufferId,
) -> Option<Arc<DiffSession>>
pub fn lookup_session_for( &self, buffer_id: BufferId, ) -> Option<Arc<DiffSession>>
D.4.d.3.a (2026-05-30): resolve a session from any
buffer that participates in it (primary or secondary).
Returns the same Arc<DiffSession> whether the caller
passes the session’s primary key or one of its
descriptor’s watched buffers. The teardown path
(do_diff_off) uses this so :diffoff from either
pane of a two-way diff finds the same session.
Sourcepub fn all_sessions_for(&self, buffer_id: BufferId) -> Vec<Arc<DiffSession>>
pub fn all_sessions_for(&self, buffer_id: BufferId) -> Vec<Arc<DiffSession>>
D.6.g (2026-05-31): every session buffer_id
participates in — as primary key or as a member
of any descriptor’s watch list. Used by
:diffoff! (the force bang) to cascade tear-down
across all sessions the active buffer belongs to,
not just the one Self::lookup_session_for
happens to resolve.
Today’s secondary_index is single-valued
(HashMap<BufferId, BufferId>), so a buffer
participating in two simultaneous sessions only
resolves to the most-recently-registered one via
lookup_session_for. This method iterates the
descriptors map directly and returns every
session whose descriptor’s watch list contains
buffer_id. Order is unspecified (HashMap
iteration). Empty when the buffer is not a
participant anywhere.
Sourcepub fn sessions_awaiting_outcome(&self) -> Vec<Arc<DiffSession>>
pub fn sessions_awaiting_outcome(&self) -> Vec<Arc<DiffSession>>
Snapshot of every registered session still awaiting a verdict — a
bound completion sender (a programmatic / agent review opened via
DiffSession::bind_completion). Ordered by primary BufferId
ascending; since ids are monotonic, the LAST entry is the
most-recently-opened review. :diff-accept / :diff-reject fall back
to this when the active buffer isn’t itself a diff pane, so a verdict
typed from the :claude terminal (or anywhere) resolves the pending
review instead of doing nothing and stranding the agent. Empty when no
review is pending (e.g. only inline :diff / :diffsplit views exist).
Sourcepub fn lookup(&self, buffer_id: BufferId) -> Option<Arc<DiffSession>>
pub fn lookup(&self, buffer_id: BufferId) -> Option<Arc<DiffSession>>
Look up the session for buffer_id. Returns None if no
session is registered.
Sourcepub fn lookup_descriptor(&self, buffer_id: BufferId) -> Option<DiffDescriptor>
pub fn lookup_descriptor(&self, buffer_id: BufferId) -> Option<DiffDescriptor>
D.2.c: look up the descriptor for buffer_id. Returns
None if the session was registered via Self::register
(sources-less) or not registered at all.
Sourcepub fn participant_slot(&self, buffer_id: BufferId) -> Option<usize>
pub fn participant_slot(&self, buffer_id: BufferId) -> Option<usize>
D-fix.5: the Hunk::ranges slot buffer_id occupies in its
session (0 = baseline / two-way left, 1 = current / right, 2 =
remote in three-way). The diff modes need this so a per-side
fold source (HunkFoldSource, UnchangedFoldSource) folds the
buffer’s OWN side rather than always ranges[1]. None when the
buffer participates in no session, or the session was registered
sources-less (no descriptor). Resolves from either side via
Self::descriptor_for_participant.
Sourcepub fn first_change_line(&self, buffer_id: BufferId) -> Option<u32>
pub fn first_change_line(&self, buffer_id: BufferId) -> Option<u32>
D-fix.5: the line (on buffer_id’s own side) of the first hunk —
the auto-scroll target on diff open (vim positions the cursor at
the first diff). None when buffer_id participates in no
session, has no descriptor (no resolvable slot), or the published
HunkIndex is empty (a clean diff — nothing to scroll to). The
host moves the pane’s cursor here + centres it (zz) through the
generic cursor primitive; the decision (which line, which side)
is the diff subsystem’s.
Sourcepub fn compute_get_edit(
&self,
active_buffer_id: BufferId,
cursor_row: u32,
target: Option<BufferId>,
) -> DiffGetOutcome
pub fn compute_get_edit( &self, active_buffer_id: BufferId, cursor_row: u32, target: Option<BufferId>, ) -> DiffGetOutcome
D.5.b (2026-05-30): compute the edit the diff-mode do
chord would apply for buffer_id at cursor_row.
Returns None (silent no-op) when:
- no session is registered for
buffer_id, - no descriptor is registered (sources-less test registration — there’s no baseline to read from),
- no hunk covers
cursor_rowon the current side, - the matched hunk is a three-way
HunkKind::Conflict(D.6 lands the conflict-resolution path).
Behaviour by hunk kind on the two-way current side
(ranges[1]):
- Change: replace
ranges[1]with the baseline slice forranges[0]. - Add: current side has the extra lines; baseline
range is empty → delete
ranges[1](revert the add). - Remove: current side is empty at the deletion
point; baseline has the removed lines → insert the
baseline text at
ranges[1].start(revert the remove). For aRemovehunk the current range is empty;cursor_rowmust equalranges[1].startexactly for the lookup to match (vim parity —doonly fires while the cursor sits on the deletion- marker row).
Reads the baseline through
[DiffDescriptor::baseline].snapshot(). The
snapshot is potentially expensive (file re-read for
OnDiskSource); callers invoke once per do
keystroke, never inside a tight loop.
D.6.d (2026-05-31): compute the edit the diff-mode
do chord or :diffget [<bufnr>] ex-command would
apply at cursor_row on active_buffer_id.
target semantics:
Nonein two-way: pull from the peer (the only other side) — preserves D.5.b’sdochord behaviour.Some(buffer)in two-way: pull from that side (must be the peer, elseDiffGetOutcome::Nothing).Nonein three-way: ambiguous →DiffGetOutcome::TargetRequiredwith the two available targets.Some(buffer)in three-way: pull from that participant’s side; allows resolving Conflict hunks by picking which side wins.
Reads the target side’s rope via
[snapshot_for_pane]. Cheap for buffer-backed
sources (rope-Arc clone); the snapshot is called
once per dispatch, never inside a tight loop.
Sourcepub fn compute_keep_both_edit(
&self,
active_buffer_id: BufferId,
cursor_row: u32,
theirs: BufferId,
) -> DiffGetOutcome
pub fn compute_keep_both_edit( &self, active_buffer_id: BufferId, cursor_row: u32, theirs: BufferId, ) -> DiffGetOutcome
CR.2 (2026-06-24): compute the “keep both” resolution edit for the
conflict hunk under cursor_row on the active (local / “ours”)
side of a three-way session — the dB chord. Splices the active
side’s lines followed by theirs’ lines (ours-then-theirs, the v1
convention; base is omitted, matching git’s non-diff3 style) into
the active range, so the conflict region ends up holding both
sides’ content in order. The edit applies to the active buffer
(like keep-ours / keep-theirs, which reuse
Self::compute_get_edit with the resolved slot→bufnr target);
target_buffer_id in the returned DiffGetOutcome::Edit is the
active buffer — the apply destination.
Conflict-only: a non-Conflict covering hunk (or none) →
Nothing, as does an unknown / self theirs, or a
session/descriptor miss. The whole-line ranges are
newline-terminated, so the splice is a clean line concatenation.
Sourcepub fn compute_put_plan(
&self,
active_buffer_id: BufferId,
cursor_row: u32,
target: Option<BufferId>,
) -> DiffPutOutcome
pub fn compute_put_plan( &self, active_buffer_id: BufferId, cursor_row: u32, target: Option<BufferId>, ) -> DiffPutOutcome
D.5.c (2026-05-30): compute the outcome the diff-mode
dp chord would produce for buffer_id at
cursor_row. Mirror of Self::compute_get_edit but
pushes the current side’s text into the peer instead
of pulling from the baseline.
Returns:
DiffPutOutcome::Editfor two-pane sessions — carriespeer_buffer_id, theEditto apply to the peer, and the current-side cursor row.DiffPutOutcome::NoPeerBufferwhen the session’s participants don’t include a peer buffer (inline file-on-disk; D.7 git baseline). Dispatch surfaces the clear error rather than silently doing nothing.DiffPutOutcome::Nothingfor the same silent no-op cases asSelf::compute_get_edit: no session, no descriptor, no covering hunk, three-wayConflict.
Reads the current side via
[DiffDescriptor::current].snapshot(). Cheap for
buffer-backed current sources (rope-Arc clone); the
snapshot reads happen once per dp keystroke at the
production rate.
Three-way scope. Participants length other than
2 is treated as “no peer” rather than synthesising a
best-guess target. D.6 lands :diffput <bufnr> /
:diffget <bufnr> with the disambiguating argument
and replaces this conservative bail-out with a
participant-indexed peer lookup. v1’s two-pane shape
(the only one D.5.c claims) cleanly hits the 2
arm.
Sourcepub fn diff_get_effect(
&self,
active_buffer: BufferId,
cursor_row: u32,
target: Option<BufferId>,
) -> Option<Effect>
pub fn diff_get_effect( &self, active_buffer: BufferId, cursor_row: u32, target: Option<BufferId>, ) -> Option<Effect>
CR.1 (2026-06-24): resolve the diff-get (do chord / :diffget)
at cursor_row on active_buffer into an [Effect] the host
applies — the mode-owned replacement for the host’s former
Editor::do_diff_get. Diff-get rewrites the active side’s hunk
to match the resolved baseline, so the edit targets active_buffer
(the cursor’s buffer); the target_buffer_id from
Self::compute_get_edit names only the source side and is NOT
the apply target.
- covering hunk →
Effect::ApplyEdit { target: active_buffer, .. }carrying the post-edit cursor row; - three-way without a disambiguating
target→ an errorEcholisting the available bufnrs; - nothing under the cursor / no session →
None(silent no-op).
Sourcepub fn diff_put_effect(
&self,
active_buffer: BufferId,
cursor_row: u32,
target: Option<BufferId>,
) -> Option<Effect>
pub fn diff_put_effect( &self, active_buffer: BufferId, cursor_row: u32, target: Option<BufferId>, ) -> Option<Effect>
CR.1 (2026-06-24): resolve the diff-put (dp chord / :diffput)
at cursor_row on active_buffer into an [Effect] — the
mode-owned replacement for Editor::do_diff_put. Diff-put pushes
the active side’s hunk INTO the peer, so the edit targets the
resolved target_buffer_id (the peer); the cursor parks on the
active side.
- peer + covering hunk →
Effect::ApplyEdit { target: peer, .. }; - inline baseline (no live peer buffer) → an error
Echo(“dp: baseline is not a buffer; use :write”); - three-way without a
target→ an errorEcholisting bufnrs; - nothing under the cursor / no session →
None.
Sourcepub fn session_participants(
&self,
session_key: BufferId,
) -> Option<Vec<BufferId>>
pub fn session_participants( &self, session_key: BufferId, ) -> Option<Vec<BufferId>>
CR.3 (2026-06-24): the participant buffers of the session keyed by
session_key, in slot order ([base, local, remote] for a
three-way). Used by the host to drive diff-conflict-mode
activation off the published sign map.
Sourcepub fn diff_keep_ours_effect(
&self,
active_buffer: BufferId,
cursor_row: u32,
) -> Option<Effect>
pub fn diff_keep_ours_effect( &self, active_buffer: BufferId, cursor_row: u32, ) -> Option<Effect>
CR.3 d2o keep-ours: the local side already holds ours, so there
is nothing to apply — but the chord is a recognised resolution
command (Dhruva 2026-06-24: full fugitive set, degenerate →
informative echo, not a silent no-op). Echoes over a conflict
region; None off-hunk (silent, like the other chords).
Sourcepub fn diff_keep_theirs_effect(
&self,
active_buffer: BufferId,
cursor_row: u32,
) -> Option<Effect>
pub fn diff_keep_theirs_effect( &self, active_buffer: BufferId, cursor_row: u32, ) -> Option<Effect>
CR.3 d3o keep-theirs: pull theirs (remote) into the local range
— compute_get_edit with target = theirs (already
conflict-capable). None when there’s no three-way session, no
covering conflict, etc.
Sourcepub fn diff_put_ours_effect(
&self,
active_buffer: BufferId,
cursor_row: u32,
) -> Option<Effect>
pub fn diff_put_ours_effect( &self, active_buffer: BufferId, cursor_row: u32, ) -> Option<Effect>
CR.3 d2p put-ours: the local side IS ours, so there is nothing
to push — informative echo over a conflict region (degenerate
self-target), else None.
Sourcepub fn diff_put_theirs_effect(
&self,
active_buffer: BufferId,
cursor_row: u32,
) -> Option<Effect>
pub fn diff_put_theirs_effect( &self, active_buffer: BufferId, cursor_row: u32, ) -> Option<Effect>
CR.3 d3p put-theirs: push the local side’s hunk into theirs
(remote) — compute_put_plan with target = theirs.
Sourcepub fn diff_keep_both_effect(
&self,
active_buffer: BufferId,
cursor_row: u32,
) -> Option<Effect>
pub fn diff_keep_both_effect( &self, active_buffer: BufferId, cursor_row: u32, ) -> Option<Effect>
CR.3 dB keep-both: splice ours⌢theirs into the local range via
Self::compute_keep_both_edit, returning an Effect::ApplyEdit
targeting the active (local) buffer. None when there’s no
three-way session or no covering conflict.
Sourcepub fn diff_next_hunk_effect(
&self,
active_buffer: BufferId,
cursor_row: u32,
) -> Option<Effect>
pub fn diff_next_hunk_effect( &self, active_buffer: BufferId, cursor_row: u32, ) -> Option<Effect>
CR.6 ]c / :hunk-next: move the cursor to the next hunk start
(slot 1, wraps to the first) — the mode-owned replacement for the
host’s do_next_hunk. Returns a generic Effect::SelectionChange
(the host owns the cursor write), or an info Echo (“no diff
session” / “no hunks”) preserving the former host messages for the
:hunk-next ex-command path (the ]c chord is K.1.c-gated and
never hits those).
Sourcepub fn diff_prev_hunk_effect(
&self,
active_buffer: BufferId,
cursor_row: u32,
) -> Option<Effect>
pub fn diff_prev_hunk_effect( &self, active_buffer: BufferId, cursor_row: u32, ) -> Option<Effect>
CR.6 [c / :hunk-prev: mirror of Self::diff_next_hunk_effect
— largest slot-1 start strictly before cursor_row, wrapping to
the last hunk.
Sourcepub fn watchers_of(&self, watched_buffer: BufferId) -> Vec<BufferId>
pub fn watchers_of(&self, watched_buffer: BufferId) -> Vec<BufferId>
D.2.c: snapshot of the inverse routing index for
watched_buffer. Returns the session keys whose
descriptors include watched_buffer in their watch
list. Empty if no sessions watch it.
Test-friendly; production code uses
Self::note_buffer_edited.
Sourcepub fn drop_session(&self, buffer_id: BufferId) -> bool
pub fn drop_session(&self, buffer_id: BufferId) -> bool
Drop the registry entry for buffer_id. Removes the
session, descriptor, every watchers-bucket entry, and
the debouncer. Returns true if a session entry was
removed. Safe to call on a non-registered id.
In-flight Arc<DiffSession> holders stay coherent; the
registry’s job is naming, not lifetime enforcement.
Sourcepub fn add_participant(
self: &Arc<DiffSubsystem>,
session_key: BufferId,
source: Arc<dyn DiffParticipantSource>,
participant_buffer: Option<BufferId>,
) -> Result<usize, MembershipError>
pub fn add_participant( self: &Arc<DiffSubsystem>, session_key: BufferId, source: Arc<dyn DiffParticipantSource>, participant_buffer: Option<BufferId>, ) -> Result<usize, MembershipError>
D.8.d (2026-05-31): add a participant to an existing
session. Appends source to descriptor.sources and
(if participant_buffer is Some) to descriptor. watch + descriptor.participants + the inverse
watcher index. Triggers a recompute through the
existing debouncer so the new arity shows up
immediately.
Returns the new arity after the add (on success).
Errors:
MembershipError::NoSession— no descriptor registered forsession_key.MembershipError::EngineRejected— the new arity would exceed what the engine supports (v1: N≥4). The session’s descriptor is not mutated when this fires — the caller’s add fails atomically.
Sourcepub fn remove_participant(
self: &Arc<DiffSubsystem>,
session_key: BufferId,
slot: usize,
) -> Result<usize, MembershipError>
pub fn remove_participant( self: &Arc<DiffSubsystem>, session_key: BufferId, slot: usize, ) -> Result<usize, MembershipError>
D.8.d (2026-05-31): remove the participant at slot
slot from session_key. Drops the corresponding
sources[slot]; if the slot corresponded to a buffer
in participants, scrubs it from the watcher index +
notifies the bridge.
Auto-collapse semantics:
- New arity ≥ 2: session stays active, recompute fires with the smaller participant set.
- New arity == 1: session is dormant (registered,
refcount stays on the remaining buffer, but
compute_diffpublishes an emptyHunkIndexsince there’s no peer to diff against). - New arity == 0: session auto-drops (calls
drop_sessioninternally).
Returns the new arity (0 on auto-drop).
Sourcepub fn remove_participant_buffer(
self: &Arc<DiffSubsystem>,
session_key: BufferId,
buffer_id: BufferId,
) -> Result<usize, MembershipError>
pub fn remove_participant_buffer( self: &Arc<DiffSubsystem>, session_key: BufferId, buffer_id: BufferId, ) -> Result<usize, MembershipError>
D.8.d (2026-05-31): convenience — remove the slot
whose participants entry equals buffer_id. Looks
up the slot via [pane_index_of] (D.6.d helper),
then delegates to Self::remove_participant.
Updates watch + participants + bridge in this
path (unlike slot-only removal, since we know which
buffer leaves).
This is the typical entry point for :diffthis /
per-buffer :diffoff (D.8.e / D.8.f).
Sourcepub fn replace_descriptor(
self: &Arc<DiffSubsystem>,
session_key: BufferId,
descriptor: DiffDescriptor,
) -> Result<(), MembershipError>
pub fn replace_descriptor( self: &Arc<DiffSubsystem>, session_key: BufferId, descriptor: DiffDescriptor, ) -> Result<(), MembershipError>
D.8.d (2026-05-31): atomically swap a session’s
descriptor while preserving session identity. The
Arc<DiffSession> stays the same — any holder
(compute_get_edit / compute_put_plan callers,
renderer-side current_hunks readers) sees a smooth
transition. Useful for transitioning a session from
N=1 dormant to N=2 active (the natural
:diffthis flow) when we want to swap the entire
source list rather than add_participant-ing one
at a time.
Internally: drop_session’s scrub semantic for the
old descriptor, then register_with_sources’s install
semantic for the new one — but without dropping the
session entry from the registry. The mode-bridge
re-scrubs + re-installs participants the same way
note_session_opened already does on re-open.
Sourcepub fn iter_sessions(&self) -> Vec<Arc<DiffSession>>
pub fn iter_sessions(&self) -> Vec<Arc<DiffSession>>
Snapshot of all currently-registered sessions. Returns
fresh Arc clones — callers may hold them past a
concurrent drop_session without affecting registry
state. Order is unspecified (HashMap iteration); D.2.d
sorts for display.
Sourcepub fn describe_sessions(&self) -> Vec<DiffSessionDescription>
pub fn describe_sessions(&self) -> Vec<DiffSessionDescription>
Snapshot of all currently-registered sessions for
:describe-diff introspection. Sorted by BufferId so
the rendered output is stable across calls.
Each row carries everything the renderer needs to format
one line of the help buffer: the session key, the
algorithm, the currently-published revision + hunk
count, and (when a descriptor is registered) the
declared watch list.
Sourcepub fn build_describe_diff_content(&self) -> String
pub fn build_describe_diff_content(&self) -> String
Build the :describe-diff help-buffer body — the
human-readable text rendered into the synthetic
Document buffer that do_describe_diff opens.
Output shape:
Active diff sessions: 2
BufferId Algorithm Rev Hunks Watches
-------- ------------ --- ----- -------
1 Histogram 5 3 [1, 2]
7 MyersMinimal 0 0 [7]Sourcepub fn schedule_recompute(
&self,
buffer_id: BufferId,
sources: Vec<Arc<dyn DiffParticipantSource>>,
) -> Option<JoinHandle<Option<Arc<HunkIndex>>>>
pub fn schedule_recompute( &self, buffer_id: BufferId, sources: Vec<Arc<dyn DiffParticipantSource>>, ) -> Option<JoinHandle<Option<Arc<HunkIndex>>>>
D.2.b: schedule a recompute of buffer_id’s session on
the tokio blocking pool. Returns None if no session is
registered.
The returned JoinHandle resolves to the publish result
(Some(Arc<HunkIndex>) on take, None if dropped as
stale). Production callers can fire-and-forget — the
session’s ArcSwap<HunkIndex> is the source of truth and
reads stay coherent regardless of whether anyone awaits
the handle. Tests and :describe-diff await for
observability.
Supersede semantics: when two recomputes are scheduled in
rapid succession, both run to completion on the blocking
pool — there is no abort. Whichever finishes second
allocates the higher revision and wins the
DiffSession::try_publish_if_newer gate; whichever
finishes first either gets there first (and is then
superseded by the second’s publish) or loses the gate.
Either way the final state is the latest scheduled
recompute’s hunks. D.2.c’s debounce will eliminate most
of the redundant spawn cost before it hits the pool.
Sourcepub fn note_buffer_edited(self: &Arc<DiffSubsystem>, buffer_id: BufferId)
pub fn note_buffer_edited(self: &Arc<DiffSubsystem>, buffer_id: BufferId)
Notify the subsystem that buffer_id was edited. Walks
the inverse watchers index and pokes the debouncer for
every session whose descriptor’s watch list includes
buffer_id. Each poke schedules a recompute after the
debounce window; multiple pokes during the window
collapse to one recompute (see Debouncer).
Production driver is Self::bind’s drainer task; tests
(and future non-bus drivers) can call this directly.
Sourcepub fn note_buffer_closed(&self, buffer_id: BufferId)
pub fn note_buffer_closed(&self, buffer_id: BufferId)
Notify the subsystem that buffer_id was closed. Drops
the session for that buffer. If the closed buffer was a
watched-only dependency of some other session (e.g.
BufferSource(closed_id) for session X), session X’s
watcher entry for closed_id is left in place — the
next snapshot returns an empty rope per the
BufferTextProvider contract, and the session will
recompute the all-Add diff. The session itself is not
dropped on a watched-side close; only on a current-side
close.
Sourcepub fn bind(
self: &Arc<DiffSubsystem>,
bus: Arc<EventBus>,
resolver: Arc<dyn DocumentBufferResolver>,
) -> DiffSubscriptionGuard
pub fn bind( self: &Arc<DiffSubsystem>, bus: Arc<EventBus>, resolver: Arc<dyn DocumentBufferResolver>, ) -> DiffSubscriptionGuard
D.2.c: bind the subsystem to an event bus. Subscribes to
EventKind::DocumentChanged + EventKind::DocumentClosed,
spawns one drainer task that translates each event’s
DocumentId to BufferId via resolver and fans the
signal into the routing path.
Returns a DiffSubscriptionGuard whose Drop
unsubscribes the bus subscription and aborts the
drainer task. Hosts hold the guard for the editor’s
lifetime; tests drop it to verify cleanup.
Trait Implementations§
Source§impl Debug for DiffSubsystem
impl Debug for DiffSubsystem
Source§impl Default for DiffSubsystem
impl Default for DiffSubsystem
Source§fn default() -> DiffSubsystem
fn default() -> DiffSubsystem
Auto Trait Implementations§
impl !Freeze for DiffSubsystem
impl RefUnwindSafe for DiffSubsystem
impl Send for DiffSubsystem
impl Sync for DiffSubsystem
impl Unpin for DiffSubsystem
impl UnsafeUnpin for DiffSubsystem
impl UnwindSafe for DiffSubsystem
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.§impl<T> DowncastSync for T
impl<T> DowncastSync for T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more