How Zed's Multi-Buffer System Handles Multiple Files: Architecture and Implementation
Zed's multi-buffer system aggregates independent file buffers into a single logical view using excerpts, enabling unified editing, coordinate mapping, and diff rendering across multiple files as one continuous document.
Zed's multi-buffer system powers features like project-wide search results and refactoring previews in the zed-industries/zed repository. By representing multiple open files as a continuous sequence of excerpts rather than separate tabs, the system allows developers to edit, review diffs, and navigate across file boundaries within a unified interface.
Core Architecture: Buffers, Excerpts, and Snapshots
At the foundation of Zed's multi-buffer system are three core concepts implemented primarily in crates/multi_buffer/src/multi_buffer.rs: the raw Buffer entities, the Excerpts that represent slices of those buffers, and the immutable Snapshots that provide thread-safe views.
The Buffer Entity
Individual files are backed by the Buffer type defined in crates/language/src/buffer.rs. Each Buffer stores raw text, supports editing operations, language services, and diff tracking. The multi-buffer does not duplicate text content; instead, it holds references to these independent buffer instances.
Excerpts as Virtual File Slices
An excerpt is a contiguous slice of a Buffer presented in the multi-buffer view. The Excerpt struct (and its public counterpart MultiBufferExcerpt) tracks:
- A path key identifying the source file
- A range within the underlying buffer to display
- Context lines before and after the range
The system stores excerpts in a sum-tree structure, allowing efficient lookup and cursor navigation across the virtual document.
Immutable Snapshots for Concurrent Access
All read-only operations—such as row/column mapping, rendering, and diff hunk discovery—operate on a MultiBufferSnapshot. This immutable, point-in-time view prevents race conditions during concurrent editing. When underlying buffers change, the multi-buffer updates its snapshot lazily via sync or sync_mut methods.
Creating and Populating a MultiBuffer
Developers construct a multi-buffer using MultiBuffer::new, specifying capabilities like Capability::ReadWrite. For simple cases, MultiBuffer::singleton creates a view containing exactly one buffer and one excerpt.
Singleton Mode for Single Files
The singleton convenience method wraps a single buffer for standard file editing:
use gpui::Context;
use multi_buffer::{MultiBuffer, Capability};
use language::Buffer;
let mut cx = Context::default();
let buffer = Buffer::new_local(Arc::new("main.rs".into()), Capability::ReadWrite, &mut cx);
let mb = MultiBuffer::singleton(buffer, &mut cx);
Aggregating Multiple Files with set_excerpts_for_path
To display multiple files, use set_excerpts_for_path, which creates Excerpt instances and inserts them into the internal sum-tree:
use multi_buffer::{MultiBuffer, PathKey, Capability};
use language::{Buffer, Point};
use gpui::Context;
fn open_two_files(cx: &mut Context<MultiBuffer>) -> MultiBuffer {
let buf1 = Buffer::new_local(Arc::new("path/a.rs".into()), Capability::ReadWrite, cx);
let buf2 = Buffer::new_local(Arc::new("path/b.rs".into()), Capability::ReadWrite, cx);
let mut mb = MultiBuffer::new(Capability::ReadWrite);
// Show entire files with zero context lines
mb.set_excerpts_for_path(
PathKey::sorted(0),
buf1,
[Point::zero()..buf1.read(cx).max_point()],
0,
cx,
);
mb.set_excerpts_for_path(
PathKey::sorted(1),
buf2,
[Point::zero()..buf2.read(cx).max_point()],
0,
cx,
);
mb
}
Coordinate Mapping Between MultiBuffer and Source Files
The multi-buffer provides bidirectional coordinate translation between its virtual document space and the underlying buffer coordinates. The MultiBufferSnapshot exposes methods like row_to_buffer to map positions:
let snapshot = mb.snapshot(&cx);
let mb_row = MultiBufferRow(42);
let (buffer_id, buffer_row) = snapshot.row_to_buffer(mb_row);
Internally, this uses MultiBufferCursor traversing the excerpt sum-tree to locate the correct excerpt, then delegates to buffer.row_to_point on the source Buffer.
Cross-File Editing and Diff Integration
When users edit text in a multi-buffer view, the system must route changes back to the appropriate source files, handling cases where an edit spans multiple files.
Routing Edits to Source Buffers
The MultiBuffer::edit method calls convert_edits_to_buffer_edits (approximately lines 1670-1790 in crates/multi_buffer/src/multi_buffer.rs) to translate multi-buffer ranges into per-buffer edits:
- Locate excerpts: Walks the snapshot cursor to find start and end excerpts for the edit range
- Split operations: Divides edits that cross excerpt boundaries into separate per-buffer operations
- Emit notifications: Fires
Event::ExcerptsEditedso UI views can redraw affected regions
// Delete text spanning file boundaries
let edit_range = MultiBufferOffset(1_000)..MultiBufferOffset(1_020);
mb.edit(vec![(edit_range, "")], None, &mut cx);
Unified Diff Rendering
When a BufferDiff is attached via MultiBuffer::set_diff_for_buffer, the DiffState subscription listens for BufferDiffEvents. The system recomputes diff_transforms to merge diffs across excerpts, exposing unified diff hunks via MultiBuffer::diff_hunks for renderers in crates/buffer_diff/src/buffer_diff.rs.
Change Synchronization and Subscriptions
The multi-buffer maintains consistency with underlying buffers through lazy synchronization. When buffers change, the system updates snapshots and notifies listeners via a Topic<MultiBufferOffset>:
let subscription = mb.subscribe(); // Returns Topic<MultiBufferOffset>
cx.notify_on(subscription, move |_, cx| {
// Re-render UI or recalculate line numbers
update_sidebar(cx);
});
The sync and sync_mut methods reconcile changes, while subscriptions.publish(offset) emits notifications during edits or diff updates.
Summary
- Zed's multi-buffer system treats multiple files as one continuous document by aggregating
Bufferentities into excerpts managed byMultiBufferincrates/multi_buffer/src/multi_buffer.rs - Excerpts are virtual slices defined by path keys and ranges, stored in a sum-tree for efficient navigation
- Immutable snapshots (
MultiBufferSnapshot) provide thread-safe read access, whilesyncmethods handle lazy updates - Cross-file editing uses
convert_edits_to_buffer_editsto route virtual document changes back to specific source buffers - Diff integration merges per-buffer diffs into a unified view using
DiffStateandDiffTransformlogic
Frequently Asked Questions
What is a MultiBuffer excerpt in Zed?
An excerpt is a contiguous slice of an underlying Buffer displayed within a multi-buffer view. Defined by the Excerpt struct in crates/multi_buffer/src/multi_buffer.rs, each excerpt tracks a path key identifying the source file and a specific byte or point range. Excerpts enable the system to show arbitrary portions of multiple files as one continuous scrollable document.
How does Zed handle edits that span multiple files in a multi-buffer?
When MultiBuffer::edit receives a range crossing excerpt boundaries, it invokes convert_edits_to_buffer_edits (approximately lines 1670-1790 in multi_buffer.rs). This method walks the snapshot cursor to locate affected excerpts, splits the edit into per-buffer operations, and applies them atomically to the underlying Buffer instances. The system then emits Event::ExcerptsEdited to notify UI components of the changes.
What is the purpose of MultiBufferSnapshot?
MultiBufferSnapshot provides an immutable, point-in-time view of all excerpts, diff states, and layout information. All read-only operations—including coordinate mapping via row_to_buffer, rendering, and diff hunk discovery—operate on this snapshot to prevent race conditions during concurrent modifications. The snapshot updates lazily when sync or sync_mut detects changes in underlying buffers.
How does Zed integrate diffs into multi-buffer views?
The system attaches BufferDiff instances to individual buffers via MultiBuffer::set_diff_for_buffer. A DiffState subscription listens for BufferDiffEvents and recomputes diff_transforms to merge diff information across all excerpts. This allows MultiBuffer::diff_hunks to present a unified diff view spanning multiple files, implemented in coordination with crates/buffer_diff/src/buffer_diff.rs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →