agent: Batch streaming edit operations#58037
Merged
Merged
Conversation
bennetbo
approved these changes
May 29, 2026
kylekz
pushed a commit
to kylekz/zed
that referenced
this pull request
May 29, 2026
Sync fork up to the latest zed-industries/zed upstream (6bca213, zed-industries#58037), on top of the existing partial sync already on main (which reached zed-industries#57456). Resolved conflicts: - .gitignore: kept both the fork (/claude-code-ext/) and upstream (.nixos-test-history) entries. - crates/proto/proto/zed.proto: integrated upstream Envelope fields 453-456 (document links) alongside the fork's reserved claude-code-ide range (10000-10004). Cargo.lock reconciled with cargo so it matches upstream's pins plus the fork's claude_code_ide / claude_code_ide_server dependencies; verified with `cargo metadata --locked`.
TomPlanche
pushed a commit
to TomPlanche/zed
that referenced
this pull request
Jun 2, 2026
## Summary While profiling agent sessions that make a lot of `edit_file` operations, I noticed the LSP `textDocument/didChange` handler firing excessively. Looking into this, I found out that the streaming edit pipeline was applying each `CharOperation` from `StreamingDiff` as its own `buffer.edit` transaction, and every transaction emits a `BufferEvent::Edited` event. Each event can trigger several other expensive events depending on whether the buffer is being rendered in an editor or is registered with a language. For example, there are `didChange` LSP events, the editor's on edit work (matching brackets, bracket colorization, code actions, outline), and more. A single `edit_file` could trigger hundreds of these at the higher end in a single synchronous app update, which would block the foreground thread for a bit and cause Zed to drop frames. I fixed this by collecting all of a chunk's `CharOperation`s and applying them in one `buffer.edit` call, so only a single `BufferEvent::Edited` event gets emitted. This is safe because operations are non overlapping by design of streaming diff (the edit cursor only advances). ## Why this wasn't caught earlier The cost only fully appears when a buffer is both registered with a language server and rendered in an editor. Without that, most of the per transaction observers never run, so the existing `edit_file_tool` benchmark (which ran the tool against a bare buffer) didn't surface it. I reworked the benchmark to open the edited buffer in an editor view, register a fake language server with per edit diagnostics, and lay out a frame, so it exercises the same cascade as the real editor. I also added a larger fixture. ## Results Measured with the `release-fast` profile on the reworked benchmark: | Fixture | Initial file | Before | After | Improvement | | --- | --- | --- | --- | --- | | `tiny_function_rewrite` | 1.4 KB | 31.1 ms | 12.1 ms | −61% | | `small_function_rewrite` | 3.0 KB | 42.4 ms | 19.3 ms | −55% | | `medium_many_small_changes` | 4.6 KB | 309.2 ms | 151.5 ms | −51% | | `medium_insertions` | 4.6 KB | 171.8 ms | 126.1 ms | −27% | | `large_multi_edit` | 44 KB | 9,549 ms | 919 ms | −90% | 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 - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Improved agent's edit file tool performance
kylekz
pushed a commit
to kylekz/zed
that referenced
this pull request
Jun 5, 2026
Sync fork up to the latest zed-industries/zed upstream (8ce658f, zed-industries#58579), on top of the previous sync that reached 6bca213 (zed-industries#58037). Resolved conflicts: - crates/zed/src/main.rs: dropped the standalone `git_graph::init(cx)` call because upstream folded the `git_graph` crate into `git_ui` (the `git_ui::init` on the preceding line now invokes `git_graph::init` internally). Kept the fork-only `claude_code_ide::init(cx)` call. Other fork-specific touch points (workspace `Cargo.toml`, `.gitignore` `/claude-code-ext/` entry, `crates/proto/proto/zed.proto` reserved range 10000-10004, `crates/proto/src/proto.rs`, `crates/project/src/project.rs`, `crates/project/src/terminals.rs`, `crates/remote_server/src/headless_project.rs`, `crates/zed/Cargo.toml`, etc.) auto-merged cleanly against upstream's changes; verified the fork additions (claude_code_ide crates, claude_code_ide_dispatcher modules, script/build-fork.ps1, README header, gpui_tokio shim) are intact. Cargo.lock auto-merged; verified with `cargo metadata --locked`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
While profiling agent sessions that make a lot of
edit_fileoperations, I noticed the LSPtextDocument/didChangehandler firing excessively. Looking into this, I found out that the streaming edit pipeline was applying eachCharOperationfromStreamingDiffas its ownbuffer.edittransaction, and every transaction emits aBufferEvent::Editedevent. Each event can trigger several other expensive events depending on whether the buffer is being rendered in an editor or is registered with a language.For example, there are
didChangeLSP events, the editor's on edit work (matching brackets, bracket colorization, code actions, outline), and more. A singleedit_filecould trigger hundreds of these at the higher end in a single synchronous app update, which would block the foreground thread for a bit and cause Zed to drop frames.I fixed this by collecting all of a chunk's
CharOperations and applying them in onebuffer.editcall, so only a singleBufferEvent::Editedevent gets emitted. This is safe because operations are non overlapping by design of streaming diff (the edit cursor only advances).Why this wasn't caught earlier
The cost only fully appears when a buffer is both registered with a language server and rendered in an editor. Without that, most of the per transaction observers never run, so the existing
edit_file_toolbenchmark (which ran the tool against a bare buffer) didn't surface it. I reworked the benchmark to open the edited buffer in an editor view, register a fake language server with per edit diagnostics, and lay out a frame, so it exercises the same cascade as the real editor. I also added a larger fixture.Results
Measured with the
release-fastprofile on the reworked benchmark:tiny_function_rewritesmall_function_rewritemedium_many_small_changesmedium_insertionslarge_multi_editSelf-Review Checklist:
Release Notes: