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:

  1. Locate excerpts: Walks the snapshot cursor to find start and end excerpts for the edit range
  2. Split operations: Divides edits that cross excerpt boundaries into separate per-buffer operations
  3. Emit notifications: Fires Event::ExcerptsEdited so 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 Buffer entities into excerpts managed by MultiBuffer in crates/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, while sync methods handle lazy updates
  • Cross-file editing uses convert_edits_to_buffer_edits to route virtual document changes back to specific source buffers
  • Diff integration merges per-buffer diffs into a unified view using DiffState and DiffTransform logic

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →