pub struct MultibufferDocumentHandle { /* private fields */ }Expand description
A multibuffer document handle. Composes N source
Arc<dyn Document>s into one read-only composed view; impls
[Document] so dispatch / motion / render code paths serve
it the same as a regular RopeDocumentHandle.
Implementations§
Source§impl MultibufferDocumentHandle
impl MultibufferDocumentHandle
Sourcepub fn new(
sources: HashMap<BufferId, Arc<dyn Document>>,
excerpts: Vec<Excerpt>,
registry: CommandRegistryHandle,
) -> Result<Self, MultibufferError>
pub fn new( sources: HashMap<BufferId, Arc<dyn Document>>, excerpts: Vec<Excerpt>, registry: CommandRegistryHandle, ) -> Result<Self, MultibufferError>
Construct a multibuffer composing sources + excerpts.
M.2.b.2 (2026-06-01): empty sources + empty excerpts
are valid — async providers (project-search, lsp-references,
etc.) open an empty view immediately and stream content in
via Self::append_excerpts / Self::add_source as
their scan progresses. The previous EmptyExcerpts error
was relaxed when the async-provider pattern landed; see
multibuffer-views.md §3.7.
Returns UnknownSource if any excerpt references a
source BufferId not present in sources.
Sourcepub fn empty(registry: CommandRegistryHandle) -> Self
pub fn empty(registry: CommandRegistryHandle) -> Self
Convenience constructor for the async-provider pattern:
build an empty view with no sources and no excerpts. The
provider streams content in via Self::append_excerpts.
Infallible.
K.4.11 (2026-06-02): takes the same CommandRegistryHandle
as the full Self::new constructor. The multibuffer is
grammar-capable from creation — empty-view or not.
pub fn buffer_id(&self) -> BufferId
Sourcepub fn document_id(&self) -> DocumentId
pub fn document_id(&self) -> DocumentId
M.2.b.2 (2026-06-01): the multibuffer’s DocumentId, used
by the cleanup subscriber to match an Event::DocumentClosed
payload (which carries DocumentId, not BufferId) back
to a registry entry keyed by BufferId.
pub fn row_translation(&self) -> Arc<RowTranslation> ⓘ
Sourcepub fn excerpts(&self) -> Vec<Excerpt>
pub fn excerpts(&self) -> Vec<Excerpt>
Snapshot the current excerpt list. M.2.b.2 (2026-06-01):
returns an owned Vec clone because excerpts now live
behind a Mutex (async providers mutate); callers that
need a borrow held across await points or across
concurrent mutations get a deterministic copy instead.
Sourcepub fn excerpt_count(&self) -> usize
pub fn excerpt_count(&self) -> usize
Count of currently-registered excerpts. Cheap probe that
avoids the Vec clone of Self::excerpts.
pub fn source_buffer_ids(&self) -> Vec<BufferId>
Sourcepub fn has_source(&self, id: BufferId) -> bool
pub fn has_source(&self, id: BufferId) -> bool
OA.23b: whether id is one of this view’s sources.
Membership, not path-ness: a synthetic source has no path, and asking
source_path(..).is_some() would say a view does not own one it does.
Sourcepub fn source_path(&self, source_buffer_id: BufferId) -> Option<PathBuf>
pub fn source_path(&self, source_buffer_id: BufferId) -> Option<PathBuf>
Generic multibuffer jump-to-source: resolve a source buffer
id to its on-disk path by reading the source document’s path
directly. Unlike the per-provider source_path mapping
(e.g. ProjectSearchService::source_path), this works for
ANY multibuffer view without provider-specific state — every
source document carries its path through the Document trait.
Consumed by the generic action:multibuffer-jump-to-source
handler registered by MultibufferMode::on_activate. Returns
None when the source buffer id is unknown or has no path.
Sourcepub fn source_text(&self, source_buffer_id: BufferId) -> Option<String>
pub fn source_text(&self, source_buffer_id: BufferId) -> Option<String>
The current text of a source document, by the same key
Self::source_path takes.
PD.4: the peer of source_path, and it exists for the same
reason — a provider that spawns its own sources (project-diff,
search) hands them to add_source and keeps no handle, so
without this there is no way to ask whether an edit made in the
view actually arrived in the file’s document. Propagating into
some document proves nothing; the claim is that it reaches the
one anchored to the path.
Reads through the source’s own snapshot, so it observes the forwarder task’s progress rather than the composed rope’s.
Sourcepub fn source_line(
&self,
source_buffer_id: BufferId,
line: u32,
) -> Option<String>
pub fn source_line( &self, source_buffer_id: BufferId, line: u32, ) -> Option<String>
OA.23b: one line of a source, by the key Self::source_path takes,
without its trailing newline. None for an unknown source or a line
past its last.
The read half of acting on a row’s source. A caller that has located a
headline through translate_composed_to_source needs to see the line
BELOW it to know whether there is already a planning line there, and
that line is outside every excerpt — the composed text cannot show it.
Reads the source’s own snapshot, so it sees edits the forwarder has
already applied. Reading the FILE instead would not: a second s in a
row would see the first one’s absence and stack a duplicate line.
Sourcepub fn source_snapshot(
&self,
source_buffer_id: BufferId,
) -> Option<Arc<DocumentSnapshot>>
pub fn source_snapshot( &self, source_buffer_id: BufferId, ) -> Option<Arc<DocumentSnapshot>>
OA.23b: a source’s current snapshot, by the key Self::source_path
takes. What a caller needs to publish a DocumentChanged for an edit
it just made through Self::apply_to_source.
Sourcepub fn apply_to_source(
&self,
source_buffer_id: BufferId,
edit: Edit,
) -> Option<Pending<AppliedEdit>>
pub fn apply_to_source( &self, source_buffer_id: BufferId, edit: Edit, ) -> Option<Pending<AppliedEdit>>
MU.1: symmetric with Self::undo in every respect, including
carrying the result to the sources. An asymmetry here would be the
nastier half of the same bug — u then <C-r> would leave the file
holding the undone text.
whatever the source actor made of the edit.
Sourcepub fn translate_composed_to_source(
&self,
cursor: Position,
) -> Option<(BufferId, Position)>
pub fn translate_composed_to_source( &self, cursor: Position, ) -> Option<(BufferId, Position)>
M.10.2 (2026-06-03): translate a composed-coordinate
cursor to its source-coordinate equivalent. Walks excerpts
in display order to find the one containing
cursor.line; returns (source_buffer_id, source_position) where source_position.line is the
row in the originating source rope and
source_position.byte is preserved verbatim (each
composed row is a verbatim copy of its source line, so
byte columns map 1:1).
Returns None when the cursor is past the last
excerpt’s last composed row. Pure read; doesn’t mutate
any state.
Consumed by mode handlers (search <CR>, project-diff
<CR>, lsp-references <CR> once those land) — see
mode-architecture.md §5.3.4 (substrate-vs-helper
rule). Returning data, not behavior — the chord-binding +
open-and-position logic lives in the mode’s handler
closure registered via the M.10.1.b
ActionHandlerRegistry.
Sourcepub fn expand_excerpt_at(&self, cursor_row: u32, delta_rows: i32)
pub fn expand_excerpt_at(&self, cursor_row: u32, delta_rows: i32)
M.5 (2026-06-01): grow / shrink the excerpt containing
cursor_row by delta_rows total rows, split
symmetrically above and below.
Behaviour:
delta_rows > 0expands;delta_rows < 0contracts;delta_rows == 0is a no-op.- Symmetric split:
delta_rows / 2above, the remainder below. Withdelta_rows = 5: 2 rows added above, 3 below. - Clip:
start_linenever goes below 0;end_linenever exceeds the source’s last row (read fromsource.snapshot().buffer.line_count() - 1). - Min size: if the contract would make
start > end, no-op (excerpt keeps its existing range). - No-op when the cursor sits outside every excerpt OR the excerpt’s source isn’t in the source map (closed source).
Recomposes + publishes after the mutation, matching
append_excerpts / replace_excerpts shape.
Sourcepub fn append_excerpts(&self, excerpts: Vec<Excerpt>)
pub fn append_excerpts(&self, excerpts: Vec<Excerpt>)
M.2.b.2 (2026-06-01): append excerpts to the end of the view. Used by async providers streaming batches of results (project-search, lsp-references, etc.). Any excerpts whose source isn’t present are silently skipped (log + drop). Recomposes + publishes after the mutation.
Sourcepub fn replace_excerpts(
&self,
sources: HashMap<BufferId, Arc<dyn Document>>,
excerpts: Vec<Excerpt>,
)
pub fn replace_excerpts( &self, sources: HashMap<BufferId, Arc<dyn Document>>, excerpts: Vec<Excerpt>, )
M.2.b.2 (2026-06-01): replace the entire excerpt list + source map atomically. Used by providers reacting to a query / filter change (e.g. user refines a search). The previous excerpts are dropped; the new set is composed
- published in one mutation.
Sourcepub fn source_fingerprint(&self, source: BufferId) -> Option<OnDiskFingerprint>
pub fn source_fingerprint(&self, source: BufferId) -> Option<OnDiskFingerprint>
M.2.b.2 (2026-06-01): add a source buffer to the view’s
source map. Subsequent append_excerpts calls can
reference it. Idempotent: re-adding an existing source
updates the handle reference (which may have been
replaced via slot-replacement upstream).
K.4.7 (2026-06-07): if set_lang_registry has been called,
detect the source’s language from its path and create a
long-lived SyntaxHandle for it. The handle worker runs on
the tokio runtime of the caller (the scan task); subsequent
reparsing is async and wait-free at read time.
SS.2: the recorded on-disk baseline for source, if any.
None for a pathless (synthetic) source, which is never stale.
pub fn add_source(&self, id: BufferId, source: Arc<dyn Document>)
Sourcepub fn set_fold_grouping(&self, grouping: FoldGrouping)
pub fn set_fold_grouping(&self, grouping: FoldGrouping)
K.4.7 (2026-06-07): enable per-source syntax highlighting.
Called by the host immediately after create_multibuffer_view.
Subsequent add_source calls use the registry to detect the
source language and create a SyntaxHandle per source.
AF.1: how this view’s rows group for folding. Declared by the provider
that builds the view, because the provider is the only thing that knows
what its rows MEAN — see FoldGrouping.
Sourcepub fn fold_grouping(&self) -> FoldGrouping
pub fn fold_grouping(&self) -> FoldGrouping
This view’s fold grouping. FoldGrouping::SourceFile unless a
provider said otherwise, which is the pre-AF.1 behaviour.
Sourcepub fn set_lang_registry(&self, lr: Arc<LangRegistry>)
pub fn set_lang_registry(&self, lr: Arc<LangRegistry>)
Also retroactively creates handles for sources that were already
added before this call (the common case: new(sources, ...) is
called first, then set_lang_registry wires highlighting).
Sourcepub fn excerpt_syntax_version(&self) -> u64
pub fn excerpt_syntax_version(&self) -> u64
K.4.7 (2026-06-08): monotonic version that increments whenever
per-source SyntaxHandles are created. Used by publish_render_state
to invalidate the cells-worker cache when handles are first populated.
Sourcepub fn excerpt_syntax_entries(&self) -> Vec<(u32, u32, u32, Arc<SyntaxHandle>)>
pub fn excerpt_syntax_entries(&self) -> Vec<(u32, u32, u32, Arc<SyntaxHandle>)>
K.4.7 (2026-06-07): per-excerpt syntax entries for the cells
worker. Each entry is (composed_start, composed_end, source_start, handle) where composed_start/composed_end
are inclusive row bounds in the composed snapshot (0-indexed)
and source_start is the first source row mapped to
composed_start. Only excerpts with a SyntaxHandle are
included.
Sourcepub fn recompose(&self)
pub fn recompose(&self)
Recompose the snapshot from current source state.
Rebuilds the composed buffer + row translation, then
publishes via ArcSwap::store.
M.1 shipped this as a manual API; M.4 wires automatic invocation via source-edit event subscriptions. M.2.b.2 kept the public surface stable but rerouted reads through the Mutex.
Sourcepub fn headerline(&self) -> Arc<HeaderlineStatus> ⓘ
pub fn headerline(&self) -> Arc<HeaderlineStatus> ⓘ
M.4 (2026-06-01): the view’s current headerline status. Lock-free read.
Sourcepub fn set_headerline(&self, status: HeaderlineStatus)
pub fn set_headerline(&self, status: HeaderlineStatus)
M.4 (2026-06-01): set the view’s headerline status.
Publishes MultibufferHeaderlineChanged on the event bus
the handle was attached to (no-op if
Self::attach_event_subscriptions hasn’t been called —
the status still updates locally).
Sourcepub fn attach_event_subscriptions(&self, events: &Arc<EventBus>)
pub fn attach_event_subscriptions(&self, events: &Arc<EventBus>)
M.4 (2026-06-01): subscribe the view to its sources’
DocumentChanged / DocumentClosed events. On a source
change, the view auto-recomposes; on a source close, the
view publishes MultibufferSourceClosed and removes the
source from its internal map.
Subscriptions live until the handle drops — MultibufferInner::drop
unsubscribes via the bookkeeping. The spawned forwarder
task holds a Weak<MultibufferInner> so it exits cleanly
once the handle is dropped.
Idempotent: re-calling on an already-attached handle is a
no-op. Requires a current tokio runtime context (the
forwarder task is spawned via tokio::spawn).
Trait Implementations§
Source§impl Clone for MultibufferDocumentHandle
impl Clone for MultibufferDocumentHandle
Source§fn clone(&self) -> MultibufferDocumentHandle
fn clone(&self) -> MultibufferDocumentHandle
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for MultibufferDocumentHandle
impl Debug for MultibufferDocumentHandle
Source§impl Document for MultibufferDocumentHandle
impl Document for MultibufferDocumentHandle
Source§fn apply_edit(&self, edit: Edit) -> Pending<AppliedEdit> ⓘ
fn apply_edit(&self, edit: Edit) -> Pending<AppliedEdit> ⓘ
M.3 (2026-06-01): translate the composed-coordinate edit
to its source-coordinate equivalent and forward to the
source document’s apply_edit.
The returned Pending<AppliedEdit> carries the source’s
AppliedEdit — ranges + delta in source coordinates. Caller
recompose()s (M.3) or auto-subscribes (M.4) to reflect.
Boundary clipping (architecture §4): if edit.range.end
extends past the start excerpt’s last composed row, the
end is clipped to the end-of-line of the excerpt’s last
source row. The edit’s contribution to subsequent
excerpts (and their sources) is dropped — matching Zed’s
“edits stay in the excerpt” rule. Out-of-range edits
(cursor past view end, no excerpts) return
RuntimeError::ReadOnly.
Source§fn apply_edit_batch(&self, edits: Vec<Edit>) -> Pending<Vec<AppliedEdit>> ⓘ
fn apply_edit_batch(&self, edits: Vec<Edit>) -> Pending<Vec<AppliedEdit>> ⓘ
M.3 (2026-06-01): translate + forward each edit to its
source. The batch is serialised through apply_edit
per-edit and combined via Pending::spawn so the
returned Pending resolves asynchronously without
blocking the runtime. Multi-source batches dispatch
each sub-edit sequentially; per-edit parallelism is a
later refinement once a consumer needs it.
Source§fn undo(&self) -> Pending<Vec<AppliedEdit>> ⓘ
fn undo(&self) -> Pending<Vec<AppliedEdit>> ⓘ
MU.1: undo the composed view and carry it to the sources.
§Why this is not source.undo()
The obvious shape — fan out and pop each source’s undo stack — is wrong twice over, and the code carried both bugs in turn.
It is wrong on correctness: a source’s undo stack is its own. If a
third pane edited that file since, its most-recent entry is the third
pane’s edit, so u in the multibuffer would silently roll back
somebody else’s work. The old comment here called that “v1 atomicity”
and queued transaction tracking for later; that is the later.
It was also wrong on liveness: the pre-M.11 version awaited each
source and deadlocked the editor actor. M.11 fixed it by dropping the
fan-out entirely, which left u looking like it worked while the file
on disk kept the change — a visual undo over a real edit, which is the
worse failure of the two because nothing on screen says so.
§What it does instead
The undo’s OWN result is the transaction record. composed_doc.undo()
returns AppliedEdits describing exactly what changed in composed
coordinates — original_range and inserted_text — so each one is
replayed at its source through the same resolve_edit_target +
build_source_edit translation the forward path uses.
Nothing is guessed and nothing is stored: the record is derived from the operation that just happened, so it cannot drift from it, and a concurrent edit in another pane is untouched because we address a range rather than pop a stack.
Source§fn display_line_numbers(&self) -> Option<Arc<[u32]>>
fn display_line_numbers(&self) -> Option<Arc<[u32]>>
K.4.6 follow-up (2026-06-02): publish the composed→source
row map so the gutter can show original file line numbers
(429, 430, 432, …) instead of composed-row indices
(0, 1, 2, …). Walks the published RowTranslation once
per call; cheap (typical N = hundreds-to-thousands; called
once per render-state publish, NOT per keystroke).
Source§fn dispatch_with_env(
&self,
invocation: CommandInvocation,
cursor: Position,
cancel: CancellationToken,
env: DispatchEnv,
) -> Pending<Effect> ⓘ
fn dispatch_with_env( &self, invocation: CommandInvocation, cursor: Position, cancel: CancellationToken, env: DispatchEnv, ) -> Pending<Effect> ⓘ
IN.0: the multibuffer overrides this too, purely to forward
env.indent to > / <.
It still ignores env.scope_resolver – it builds its own
composed↔source resolver in dispatch_composed (N.1.5), which
is why the env was ignored wholesale before. But indent is a
plain resolved value with nothing multibuffer-specific about it,
and dropping it would mean > obeys :setlocal shiftwidth=2 in
a document buffer and silently ignores it in a multibuffer –
kind-specific behaviour in a buffer, which is the thing
multibuffer_is_a_regular_buffer.rs exists to prevent.
Source§fn snapshot(&self) -> Arc<DocumentSnapshot> ⓘ
fn snapshot(&self) -> Arc<DocumentSnapshot> ⓘ
Arc lives as
long as the caller needs it; subsequent publishes don’t
invalidate it.Source§fn snapshot_cache(&self) -> SnapshotCache
fn snapshot_cache(&self) -> SnapshotCache
fn redo(&self) -> Pending<Vec<AppliedEdit>> ⓘ
fn save(&self) -> Pending<PathBuf> ⓘ
fn save_as(&self, _path: PathBuf) -> Pending<()> ⓘ
fn set_selections(&self, selections: SelectionSet) -> Pending<()> ⓘ
Source§fn excerpt_highlights(&self) -> Vec<ExcerptHighlight>
fn excerpt_highlights(&self) -> Vec<ExcerptHighlight>
MultibufferDocumentHandle overrides
to return one entry per excerpt that has a SyntaxHandle. Read moreSource§fn excerpt_syntax_version(&self) -> u64
fn excerpt_syntax_version(&self) -> u64
Source§fn dispatch_with_cancel(
&self,
invocation: CommandInvocation,
cursor: Position,
cancel: CancellationToken,
) -> Pending<Effect> ⓘ
fn dispatch_with_cancel( &self, invocation: CommandInvocation, cursor: Position, cancel: CancellationToken, ) -> Pending<Effect> ⓘ
CommandInvocation] through the impl’s
internal grammar execution path. MultibufferDocument Handle (M.1) routes the invocation through its
row-translation table to the underlying source(s).fn id(&self) -> DocumentId
§fn text(&self) -> String
fn text(&self) -> String
snapshot().buffer .as_string() on a held snapshot when looping.fn path(&self) -> Option<PathBuf>
fn dirty(&self) -> bool
fn version(&self) -> u64
fn text_version(&self) -> u64
fn selections(&self) -> Arc<SelectionSet> ⓘ
§fn begin_undo_group(&self)
fn begin_undo_group(&self)
i/a/o/cw .. <Esc>) collapses into a single
undo unit until [Self::end_undo_group]. Fire-and-forget:
ordering against the following edits is the impl’s responsibility
(the actor mailbox is FIFO), so there is nothing to await. Read more§fn end_undo_group(&self)
fn end_undo_group(&self)
Self::begin_undo_group]. Default
no-op; see that method.Auto Trait Implementations§
impl !RefUnwindSafe for MultibufferDocumentHandle
impl !UnwindSafe for MultibufferDocumentHandle
impl Freeze for MultibufferDocumentHandle
impl Send for MultibufferDocumentHandle
impl Sync for MultibufferDocumentHandle
impl Unpin for MultibufferDocumentHandle
impl UnsafeUnpin for MultibufferDocumentHandle
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
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§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