Skip to content

Refactor BufferDiff to allow multiple diffs to share the same base text buffer - #58266

Merged
cole-miller merged 29 commits into
mainfrom
staged-diff-refactor
Jun 9, 2026
Merged

Refactor BufferDiff to allow multiple diffs to share the same base text buffer#58266
cole-miller merged 29 commits into
mainfrom
staged-diff-refactor

Conversation

@cole-miller

@cole-miller cole-miller commented Jun 1, 2026

Copy link
Copy Markdown
Member

This PR changes how base texts are managed by the buffer_diff crate, to enable keeping two diff entities alive that share the same base text buffer entity. Previously, each diff owned its own base text buffer and edited it when calling BufferDiff::set_snapshot, so the only way to reuse the same base text between two diffs was to have two independent buffers for it, which is pretty inefficient.

After this PR, each diff still has a base text buffer, but set_snapshot doesn't edit it. Instead, that responsibility moves into the caller. For updating the base text buffer, this PR also introduces a new pair of APIs, Buffer::snapshot_with_edits and Buffer::fast_forward, which allow us to move the parsing of the new base text into the background and then install the new syntax tree synchronously on the foreground.

The git store uses the low-level APIs set_snapshot and fast_forward directly, and manages the head text and index text buffers itself (garbage-collecting them when they're no longer needed); this enables adding an open_staged_diff API which returns a diff between the managed index buffer and the managed head buffer (the latter is also used for the uncommitted diff's base text). Other downstreams don't need to reuse a base text buffer, and those have been migrated to use the high-level set_base_text API, which now calls set_snapshot and fast_forward internally, with a guard to prevent concurrent updates.

Another change worthy of note is that we now always diff the old base text with the new base text to create snapshot_with_edits.

There are also some incidental bug fixes:

  • Fixed an issue where a dangling weak unstaged diff could stick around in the git store forever
  • Restored the IndexMatchesHead optimization that had become inoperative in the remote case
  • Fixed a crash in the multibuffer due to the handling of BufferDiffEvent::LanguageChanged, which could cause the multibuffer to have transforms that were inconsistent with the diff base text.

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content is consistent with the UI/UX checklist
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Closes ZED-81P

Release Notes:

  • Fixed a rare crash that could occur while using the uncommitted diff.

cole-miller and others added 9 commits May 19, 2026 12:15
Co-authored-by: Ben Kunkle <ben@zed.dev>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Jun 1, 2026
@zed-community-bot zed-community-bot Bot added the staff Pull requests authored by a current member of Zed staff label Jun 1, 2026
@cole-miller
cole-miller force-pushed the staged-diff-refactor branch from be31fc8 to 17add78 Compare June 9, 2026 16:34
@cole-miller
cole-miller enabled auto-merge June 9, 2026 19:36
@cole-miller
cole-miller disabled auto-merge June 9, 2026 19:37
@cole-miller
cole-miller enabled auto-merge June 9, 2026 19:44
@cole-miller
cole-miller added this pull request to the merge queue Jun 9, 2026
Merged via the queue into main with commit 3df0812 Jun 9, 2026
31 checks passed
@cole-miller
cole-miller deleted the staged-diff-refactor branch June 9, 2026 20:16
@maxbrunsfeld

Copy link
Copy Markdown
Collaborator

🚀

This was referenced Jun 18, 2026
@cole-miller cole-miller mentioned this pull request Jul 7, 2026
2 tasks
jonx pushed a commit to jonx/zed-aros that referenced this pull request Jul 17, 2026
…text buffer (zed-industries#58266)

This PR changes how base texts are managed by the `buffer_diff` crate,
to enable keeping two diff entities alive that share the same base text
buffer entity. Previously, each diff owned its own base text buffer and
edited it when calling `BufferDiff::set_snapshot`, so the only way to
reuse the same base text between two diffs was to have two independent
buffers for it, which is pretty inefficient.

After this PR, each diff still has a base text buffer, but
`set_snapshot` doesn't edit it. Instead, that responsibility moves into
the caller. For updating the base text buffer, this PR also introduces a
new pair of APIs, `Buffer::snapshot_with_edits` and
`Buffer::fast_forward`, which allow us to move the parsing of the new
base text into the background and then install the new syntax tree
synchronously on the foreground.

The git store uses the low-level APIs `set_snapshot` and `fast_forward`
directly, and manages the head text and index text buffers itself
(garbage-collecting them when they're no longer needed); this enables
adding an `open_staged_diff` API which returns a diff between the
managed index buffer and the managed head buffer (the latter is also
used for the uncommitted diff's base text). Other downstreams don't need
to reuse a base text buffer, and those have been migrated to use the
high-level `set_base_text` API, which now calls `set_snapshot` and
`fast_forward` internally, with a guard to prevent concurrent updates.

Another change worthy of note is that we now always diff the old base
text with the new base text to create `snapshot_with_edits`.

There are also some incidental bug fixes:
- Fixed an issue where a dangling weak unstaged diff could stick around
in the git store forever
- Restored the `IndexMatchesHead` optimization that had become
inoperative in the remote case
- Fixed a crash in the multibuffer due to the handling of
`BufferDiffEvent::LanguageChanged`, which could cause the multibuffer to
have transforms that were inconsistent with the diff base text.

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Closes ZED-81P

Release Notes:

- Fixed a rare crash that could occur while using the uncommitted diff.

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
jolutz pushed a commit to jolutz/zed that referenced this pull request Aug 8, 2026
…text buffer (zed-industries#58266)

This PR changes how base texts are managed by the `buffer_diff` crate,
to enable keeping two diff entities alive that share the same base text
buffer entity. Previously, each diff owned its own base text buffer and
edited it when calling `BufferDiff::set_snapshot`, so the only way to
reuse the same base text between two diffs was to have two independent
buffers for it, which is pretty inefficient.

After this PR, each diff still has a base text buffer, but
`set_snapshot` doesn't edit it. Instead, that responsibility moves into
the caller. For updating the base text buffer, this PR also introduces a
new pair of APIs, `Buffer::snapshot_with_edits` and
`Buffer::fast_forward`, which allow us to move the parsing of the new
base text into the background and then install the new syntax tree
synchronously on the foreground.

The git store uses the low-level APIs `set_snapshot` and `fast_forward`
directly, and manages the head text and index text buffers itself
(garbage-collecting them when they're no longer needed); this enables
adding an `open_staged_diff` API which returns a diff between the
managed index buffer and the managed head buffer (the latter is also
used for the uncommitted diff's base text). Other downstreams don't need
to reuse a base text buffer, and those have been migrated to use the
high-level `set_base_text` API, which now calls `set_snapshot` and
`fast_forward` internally, with a guard to prevent concurrent updates.

Another change worthy of note is that we now always diff the old base
text with the new base text to create `snapshot_with_edits`.

There are also some incidental bug fixes:
- Fixed an issue where a dangling weak unstaged diff could stick around
in the git store forever
- Restored the `IndexMatchesHead` optimization that had become
inoperative in the remote case
- Fixed a crash in the multibuffer due to the handling of
`BufferDiffEvent::LanguageChanged`, which could cause the multibuffer to
have transforms that were inconsistent with the diff base text.

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Closes ZED-81P

Release Notes:

- Fixed a rare crash that could occur while using the uncommitted diff.

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement staff Pull requests authored by a current member of Zed staff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants