Skip to main content

MultibufferDocumentHandle

Struct MultibufferDocumentHandle 

Source
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

Source

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.

Source

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.

Source

pub fn buffer_id(&self) -> BufferId

Source

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.

Source

pub fn row_translation(&self) -> Arc<RowTranslation> ⓘ

Source

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.

Source

pub fn excerpt_count(&self) -> usize

Count of currently-registered excerpts. Cheap probe that avoids the Vec clone of Self::excerpts.

Source

pub fn source_buffer_ids(&self) -> Vec<BufferId>

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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 > 0 expands; delta_rows < 0 contracts; delta_rows == 0 is a no-op.
  • Symmetric split: delta_rows / 2 above, the remainder below. With delta_rows = 5: 2 rows added above, 3 below.
  • Clip: start_line never goes below 0; end_line never exceeds the source’s last row (read from source.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.

Source

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.

Source

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.
Source

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.

Source

pub fn add_source(&self, id: BufferId, source: Arc<dyn Document>)

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

pub fn headerline(&self) -> Arc<HeaderlineStatus> ⓘ

M.4 (2026-06-01): the view’s current headerline status. Lock-free read.

Source

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).

Source

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

Source§

fn clone(&self) -> MultibufferDocumentHandle

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for MultibufferDocumentHandle

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Document for MultibufferDocumentHandle

Source§

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>> ⓘ

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>> ⓘ

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]>>

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> ⓘ

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> ⓘ

Load the current snapshot. The returned Arc lives as long as the caller needs it; subsequent publishes don’t invalidate it.
Source§

fn snapshot_cache(&self) -> SnapshotCache

Per-thread cache for hot loops that load the snapshot many times between edits. The cache reduces per-load cost from ~17 ns to ~2 ns when the writer hasn’t published since the last load.
Source§

fn redo(&self) -> Pending<Vec<AppliedEdit>> ⓘ

Source§

fn save(&self) -> Pending<PathBuf> ⓘ

Source§

fn save_as(&self, _path: PathBuf) -> Pending<()> ⓘ

Source§

fn set_selections(&self, selections: SelectionSet) -> Pending<()> ⓘ

Source§

fn excerpt_highlights(&self) -> Vec<ExcerptHighlight>

K.4.7: per-excerpt highlight entries for multibuffer panes. Default returns empty — regular single-file documents carry no excerpt structure. MultibufferDocumentHandle overrides to return one entry per excerpt that has a SyntaxHandle. Read more
Source§

fn excerpt_syntax_version(&self) -> u64

Monotonic counter that bumps whenever the excerpt highlight set changes (new sources added, lang registry wired). Default: 0.
Source§

fn dispatch_with_cancel( &self, invocation: CommandInvocation, cursor: Position, cancel: CancellationToken, ) -> Pending<Effect> ⓘ

Dispatch a [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

Rendered text. Allocates — prefer 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)

Open an undo-coalescing group so a run of edits (a vim insert session: 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)

Close the group opened by [Self::begin_undo_group]. Default no-op; see that method.
§

fn dispatch( &self, invocation: CommandInvocation, cursor: Position, ) -> Pending<Effect> ⓘ

Convenience: dispatch with a never-cancelled token.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts 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
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more