Based on
https://github.com/zed-industries/zed/pull/64464#issuecomment-5763255028
With `RUST_BACKTRACE=1` set, `anyhow` captures a backtrace for every
error it constructs; on macOS this costs ~30ms per error, on Linux ~µs.
`Project::test` alone hits several such errors during setup
(`canonicalize`/`load` of the non-existent global `tasks.json` and
`debug.json`, worktree creation, buffer open), adding ~0.8s of fixed
cost per test on macOS.
`RUST_LIB_BACKTRACE=0` is checked by std before `RUST_BACKTRACE`, so
panics keep their backtraces while library error captures stop.
Set only on the macOS test jobs (`run_tests.yml`, `release.yml`), since
Linux and Windows pay nothing for the captures and keep the error-origin
backtraces in failure output.
Measured with `cargo nextest run -p editor --lib`: macOS 119s → 72s,
Linux 78s → 76s (noise).
Trade-off on macOS: a failing `.unwrap()` on an `anyhow::Error` in CI
output loses the backtrace of where the error was constructed; the panic
backtrace at the unwrap site is unaffected.
Release Notes:
- N/A
Move GPUI window/entity updates, view rendering, listener adapters,
arena allocation, and scheduler task setup into shared implementations.
Keep the generic entry points as thin adapters.
Uses pretty much the same technique prior PRs did, but this time on
`gpui` which I am not too confident about — can close it if seems
dangerous, but the binary strip win looks good to try.
`cargo llvm-lines --locked -p gpui`:
Before (bec63dee23): 934803 lines, 33225 copies
After: 926925 lines (-0.8%), 33454 copies (+0.7%)
With compiler wrappers disabled, `cargo clean --target-dir
target/binary-size-split-build` and `cargo build --locked -p zed`:
Before: 135.6s wall, 1569.8s CPU, zed binary 1175563800 bytes
After: 142.5s wall (+5.1%), 1589.0s CPU (+1.2%), zed binary 1144068952
bytes (-2.7%, -31.5MB)
Release Notes:
- N/A
- [GPUI] Shared GPUI and scheduler implementations to reduce the final
binary size of the app
Closes https://github.com/zed-industries/zed/issues/64118https://github.com/user-attachments/assets/f2152411-6011-4d87-8c8e-d83bed0afbbc
`ty` returns completion text without a `textEdit` or a completion-list
default edit range:
```json
{"label":"value2=","insertText":"value2="}
```
The [LSP
specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItem)
explicitly states:
> The `insertText` is subject to interpretation by the client side.
For the reported example, `ˇ` marks the cursor:
```py
test(value1, ˇvalue2)
```
Previously, Zed inferred only a range covering the existing `value2`.
Without an insert range, completion acceptance bypassed
`completions.lsp_insert_mode` and replaced that argument with `value2=`,
producing:
```py
test(value1, value2=)
```
The patch also infers an insert range, ending at the cursor rather than
the end of the word.
This lets the existing setting choose between inserting before `value2`
and replacing it.
When a prefix has already been typed, insertion replaces that prefix
while preserving the text after the cursor.
With the unchanged default, `"replace_suffix"`, Zed replaces text after
the cursor only when it is a suffix of the completion’s label.
Here, `value2` is not a suffix of `value2=`, so Zed inserts and
produces:
```py
test(value1, value2=value2)
```
The suffix and subsequence checks now use the raw LSP `label`, not
decorated menu text or `filterText`.
These checks select the edit range; `insertText` still supplies the
inserted text in this example.
Release Notes:
- Fixed insertion modes for completions without edit ranges
* Preserve bundle compiler flags commit commit is a follow-up to
https://github.com/zed-industries/zed/pull/63432
Linux bundling script had custom `RUSTFLAGS` set which eradicated prior
config.
Moved all flags into `.cargo/bundle-config.toml` instead.
* Trim audio features and compress resources commit removes unused
`rodio` features and enables resource compression
The win from the latter seems not that large, so can revert if needed,
but seemed very easy to enable.
With compiler wrappers disabled, `cargo clean --target-dir
target/binary-size-split-build` and `cargo build --locked -p zed`:
Before: 135.6s wall, 1569.8s CPU, zed binary 1175563800 bytes
After: 130.5s wall (-3.8%), 1488.7s CPU (-5.2%), zed binary 1171527064
bytes (-0.3%, -4.0MB)
With `ZED_BUNDLE=true CXXFLAGS=-stdlib=libc++` and `cargo --config
.cargo/bundle-config.toml build --locked --release -p zed -p cli
--target aarch64-apple-darwin`:
Before: 369.4s wall, 3861.6s CPU, zed binary 381662328 bytes
After: 369.4s wall (+0.01%), 3866.1s CPU (+0.1%), zed binary 377836968
bytes (-1.0%, -3.8MB)
Release Notes:
- N/A
Prerequisite to https://github.com/zed-industries/zed/pull/64257
* gpui: build inspector IDs only when the window's inspector exists,
drop the inspector maps on close, skip inspector state lookups for
absent or non-selected IDs
* gpui: create inspector element renderers per inspector instance
instead of at registration
* inspector_ui: create the project and editors lazily per inspector
session, keep the init task and subscriptions cancellable, hold the
inspector weakly from the completion provider
* lsp: bound the shutdown output drain by the same timeout as the
shutdown request
* gpui_platform: add an `inspector_render` benchmark that builds with
the inspector in or out
Mean time per rendered frame with the inspector closed; overhead is
relative to the inspector compiled out.
| Benchmark | Frame, compiled out | Frame, `main` | Frame, this PR |
Overhead on `main` | Overhead on this PR |
| --- | ---: | ---: | ---: | ---: | ---: |
| 1,000 bare divs, never opened | 3.352 ms | 3.505 ms | 3.362 ms | +153
µs (+4.57%) | +10 µs (+0.29%) |
| 1,000 bare divs, opened and closed twice | 3.349 ms | 3.508 ms | 3.364
ms | +159 µs (+4.76%) | +15 µs (+0.46%) |
| 100 bare divs, opened and closed twice | 317.0 µs | 336.3 µs | 317.6
µs | +19 µs (+6.10%) | +1 µs (+0.18%) |
| 10,000 bare divs, never opened | 56.617 ms | 58.925 ms | 56.472 ms |
+2.31 ms (+4.08%) | -145 µs (-0.26%) |
| 500 panel rows with text, hover, click, never opened | 4.173 ms |
4.451 ms | 4.179 ms | +278 µs (+6.67%) | +7 µs (+0.16%) |
| 500 panel rows with text, hover, click, opened and closed twice |
4.197 ms | 4.448 ms | 4.159 ms | +252 µs (+6.00%) | -37 µs (-0.89%) |
| 50 panel rows with text, hover, click, never opened | 523.4 µs | 543.3
µs | 522.3 µs | +20 µs (+3.80%) | -1 µs (-0.21%) |
| 500 panel rows with text, hover, click, opened and closed twice,
another window inspected | 4.191 ms | 4.469 ms | 4.176 ms | +278 µs
(+6.64%) | -15 µs (-0.35%) |
| editor, cursor moves (`crates/benchmarks`, `release-fast`) | 940.9 µs
| 943.0 µs | 942.5 µs | +2 µs (+0.23%) | +2 µs (+0.17%) |
Release Notes:
- N/A
- [GPUI] Optimize GPUI inspector's runtime cost by an order of magnitude
This reverts commit bf2d51bcfd.
As discussed with @\ChristopherBiscardi , this has to be implemented
differently, making a first step towards that.
Release Notes:
- N/A
Closes https://github.com/zed-industries/zed/issues/63730
Related to #52340 and #43460
Based on https://github.com/zed-industries/zed/pull/63138 and needs it
to be merged first.
`BufferSnapshot::chunks` re-ran the tree-sitter highlight query over the
requested range on every call, so scrolling and cursor movement paid the
full query cost per frame.
Highlight runs are now computed once per 50-row chunk (the same
`RowChunks` that back bracket colorization), resolved against the
current `HighlightMap` at compute time, merged into `(Range<usize>,
HighlightId)` runs, and kept in a cost-budgeted per-buffer LRU (10MB).
Entries reuse the `ResolvedHighlights` representation from #63138: each
stores the grammars and maps it was resolved against and is served only
while every map is pointer-identical (`is_current`), so theme changes
and grammar reloads invalidate structurally, with no invalidation hooks
and no global state.
Edits and reparses drop the cache through the existing `TreeSitterData`
swap.
Chunks spanning more than 64KB bypass the cache and fall back to direct
querying, so files with giant lines behave as before.
## Benchmarks
`editor_render_highlighted` and `editor_render_highlighted_minimap`
benches (`cargo bench -p benchmarks --bench editor_render`) move the
cursor through a generated 10K-line Rust file with real tree-sitter
highlighting, with and without the minimap.
The benches are new in this PR, so the baseline is the same binary with
`ZED_DISABLE_HIGHLIGHT_CACHE=1`, which routes rendering through the
pre-existing direct-query path.
Run 1 — baseline, cache disabled:
```
editor_render_highlighted time: [1.7164 ms 1.7377 ms 1.7604 ms]
editor_render_highlighted_minimap time: [12.273 ms 12.407 ms 12.549 ms]
```
Run 2 — cache enabled:
```
editor_render_highlighted time: [1.4572 ms 1.4597 ms 1.4627 ms]
change: [-16.378% -14.944% -13.526%] (p = 0.00 < 0.05)
editor_render_highlighted_minimap time: [9.2750 ms 9.3213 ms 9.3744 ms]
change: [-25.800% -24.872% -23.930%] (p = 0.00 < 0.05)
```
Over the same runs, GPUI reports window draw mean 0.81 ms → 0.68 ms and
p99 1.06 ms → 0.76 ms without the minimap, and mean 5.14 ms → 4.12 ms,
p99 6.12 ms → 4.75 ms with it.
The relative deltas held across two independent sessions (-14.1…-14.9%
and -20.7…-24.9%), while absolute times drift a few percent with machine
state.
Release Notes:
- Improved editor rendering performance in syntax-highlighted files,
especially with the minimap enabled
Part of https://github.com/zed-industries/zed/issues/57349
`MarkdownElement` re-highlights every code block with tree-sitter on
every frame.
Each visible code block pays a full parse per frame, which makes agent
panel streaming and hover popovers janky once the content grows.
This PR computes code block highlights once, in the background parse
task, instead of at render time:
- Highlights are resolved into `(Range<usize>, HighlightId)` runs at
parse time; each entry stores the grammar and `HighlightMap` it was
resolved against and is served only while the grammar still holds the
pointer-identical map (`ResolvedHighlights::is_current`), so theme
changes need no invalidation hooks, and a block whose entry went stale
falls back to direct fragment highlighting at render until the next
parse.
- `Language::highlight_text_resolved` memoizes results in a
content-keyed LRU (4MB budget, 512KB per-entry cap), so the re-parses
triggered by streaming appends reuse the highlights of unchanged blocks
instead of re-running tree-sitter on them.
- Blocks larger than the per-entry cap bypass the cache and are
highlighted directly, as before.
`CostBudgetedLru` is generic rather than specialized because a follow-up
PR reuses it for per-buffer highlight caching in
`BufferSnapshot::chunks`; the capture flattening keyed by grammar index
(`HighlightCaptureRef`) is shared with that follow-up as well.
## Benchmarks
`cargo bench -p benchmarks --bench markdown_renderer` renders generated
documents that mix paragraphs, headings, tables, and Rust code blocks.
**Run 1 — baseline, `main` (`ee2366a395`):**
```
Markdown render/min_bytes/5000 time: [1.2956 ms 1.2974 ms 1.3016 ms]
Markdown render/min_bytes/10000 time: [1.7919 ms 1.7955 ms 1.8026 ms]
Markdown render/min_bytes/50000 time: [6.5510 ms 6.6241 ms 6.6671 ms]
Markdown render/min_bytes/250000 time: [37.644 µs 2.4819 ms 4.3791 ms] <- bimodal already on main
```
**Run 2 — this branch:**
```
Markdown render/min_bytes/5000 time: [940.11 µs 945.28 µs 948.79 µs]
change: [-29.317% -28.385% -27.607%] (p = 0.00 < 0.05)
Markdown render/min_bytes/10000 time: [1.1162 ms 1.1331 ms 1.1407 ms]
change: [-40.859% -39.151% -37.719%] (p = 0.00 < 0.05)
Markdown render/min_bytes/50000 time: [2.4943 ms 2.9225 ms 3.1051 ms]
change: [-80.088% -67.885% -58.058%] (p = 0.00 < 0.05)
Markdown render/min_bytes/250000 time: [43.442 µs 44.661 µs 45.514 µs]
change: [-98.210% -95.806% +5.5167%] (p = 0.17 > 0.05)
```
Over the same runs, GPUI reports window draw mean 2.22 ms → 1.26 ms, p99
31.2 ms → 16.0 ms, and the share of frames that blow the frame budget
drops from 3.9% to 2.0%.
Note: at the 250,000-byte case root blocks render lazily, which makes
the measurement bimodal and inconclusive in both configurations.
Release Notes:
- Improved rendering performance of markdown code blocks (agent panel,
hover popovers, markdown preview)
Closes https://github.com/zed-industries/zed/discussions/51582
During testing, spotted that if I toggle off the hints with the modifier
keys, they are resurrected on scrolling — fixed that in a small commit
on top.
Release Notes:
- Supported commands in inlay hints
I think this is the final attempt of mine to do anything about the
binary size, as those fixes become relatively intricate and I like them
less and less.
Each commit has its own improvement stats and mainly reduce generic-code
duplication through shared non-generic helpers and `#[inline(never)]`
boundaries.
Total reduction stats:
With sccache disabled, `cargo clean` and `cargo build --release -p zed`:
Before: zed binary 396716472 bytes
After: zed binary 359416504 bytes (-9.40%, -37.29MB)
Release Notes:
- N/A
---------
Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
Before this change, two different guests requesting the same kind of
request would cancel each other's requests and only the latest one would
have arrived to the latest to query that.
Release Notes:
- Fixed collab LSP requests cancelling each other sometimes
When working with multiple windows, one agent worked in the external
hard drive's directory.
After finishing working on that external dir, I've closed the entire
window (the other one, unrelated, remained) only to discover I cannot
unmount the external hard drive.
Investigated, and seems that that manual `ref_count` is buggy — replaced
that with `WeakEntry` for both agent and ACP parts.
Also added leaked project logging into reliability.
Release Notes:
- Fixed projects leaking via agents panel
Not sure if this PR is 100% good, since it removes symbols from local
traces, leaving addresses only — but this what Windows seem to do
already, and we have those symbols on Sentry to restore from, which were
needed to normally read Linux perf traces already (see `perf.zip` in
https://github.com/zed-industries/zed/issues/59689#issuecomment-5452720248)
On the upside, uncompressed binaries' are now ~100MB less, which is a
lot — hence decided to open the PR for the review
-------------------
Strips debug info and local symbols from shipped macOS and Linux
binaries, matching what Windows already does with PDBs: `bundle-mac` now
always runs `dsymutil` before a new `strip -x` step, and `bundle-linux`
adds `--discard-all` plus `zed.dbg`/`remote_server.dbg` extraction via
`--only-keep-debug`.
Sentry uploads are unchanged and precede stripping.
The macOS binary UUID and the GNU build id survive stripping, so
user-provided traces stay recoverable without rebuilding at the
reporter's commit: `sample` output resolves via `atos` against the
archived `zed.dwarf`, and `perf.data` via `perf buildid-cache` against
the Sentry-archived binary.
zed binary:
macOS (2026-08-29 nightly): 420478976 -> 316503984 bytes (-24.7%,
-104.0MB)
Linux (v1.17.2 aarch64): 439417448 -> 336967256 bytes (-23.3%, -102.5MB)
Downloads shrink far less, symbols compress ~7:1: the macOS DMG loses
only ~15MB.
Release Notes:
- Improved installed size on macOS and Linux by stripping debug symbols
from release binaries (roughly -25%)
Closes https://github.com/zed-industries/zed/issues/62391
Workspace diagnostics were polled incorrectly:
* Workspace pulls were cancelled and re-sent every 120s.
Pyright-family servers hold the request open forever and re-analyze the
whole workspace on every new one, so the retry loop leaked memory; the
request is now kept open (long-poll), as the spec recommends.
* No re-trigger after a server closed a workspace pull.
ty closes the request when diagnostics change and expects the client to
re-pull
* `ServerCancelled` / `retriggerRequest` was ignored.
The spec defaults cancellation data to `{ retriggerRequest: true }`, so
these errors now retrigger workspace pulls and retry document pulls
instead of failing them.
* LSP error codes were erased with `anyhow!(message)`
Errors now carry a typed `lsp::ResponseError` through the chain,
replacing four copies of `ends_with(...)` matching against
rust-analyzer's literal message texts.
* Workspace partial results were not tied to their request.
Stale or numeric-token `$/progress` reports were applied as current data
and could poison `previousResultIds` until a server restart; reports are
now matched against the exact in-flight token.
* `interFileDependencies` was never read.
Every edit re-pulled all other open buffers even for servers declaring
no inter-file dependencies (e.g. ruff); cross-buffer pulls are now gated
on that capability.
* Cross-buffer pulls were unpaced.
All open buffers were pulled back-to-back after each edit debounce; they
are now paced at one document per 500ms.
* The failure valve killed workspace pulls permanently.
51 hard errors over a server's lifetime silently stopped its workspace
pulls until restart
* Concurrent refresh waiters could be dropped instantly.
The capacity-1 refresh channel resolved a second concurrent waiter to
`false` immediately, and request errors dropped queued refreshes;
waiters now queue and carry over to the retried pull.
Release Notes:
- Fixed basedpyright memory leak caused by incorrect workspace
diagnostics polling
Fix flacks like
https://github.com/zed-industries/zed/actions/runs/33546906778/job/99987145940?pr=63573
`test_extension_store_with_test_extension` gives the extension builder
`target` as its cache at
`crates/extension_host/src/extension_store_test.rs:801`.
`install_dev_extension` performs a real extension and grammar build at
`crates/extension_host/src/extension_host.rs:1139`.
When `target/wasi-sdk` is absent (e.g. after `target/` cleanup), the
builder downloads and extracts it at
`crates/extension/src/extension_builder.rs:511`.
In the failed run, downloading and extracting the SDK consumed 59
seconds, grammar compilation began at 60 seconds, and the timeout fired
immediately.
Release Notes:
- N/A
Closes https://github.com/zed-industries/zed/issues/59689
Symbolicating the reporter's profile (21s of typing, Windows) showed
`Window::draw` taking 63% of the main thread at ~62 redraws/s (p95 draw
= 40ms), and 30.9% of the *entire* main thread inside
`EditorconfigStore::properties()`:
every `LanguageSettings` lookup — several per frame via
`DisplayMap::tab_size`, `MultiBuffer::language_settings`, indent guides,
and `LspStore::supports_range_formatting` — re-ran the full editorconfig
resolution (ancestor walk plus `ec4rs::Section::apply_to` over every key
of every section) and then cloned the whole `LanguageSettings` struct.
Real-world `.editorconfig` files carry hundreds of irrelevant keys
(`dotnet_*`, `ij_*`, ...), so the per-call map assembly dominated:
`RtlAllocateHeap` 7.3%, `memcmp` 7.0%, `memcpy` 6.6%,
`Properties::find_idx` 5.9% self time.
## Commit 1: resolve only the editorconfig properties Zed reads
- `EditorconfigStore::properties()` now applies only the 7 keys Zed
consumes (`indent_style`, `indent_size`, `tab_width`, `end_of_line`,
`max_line_length`, `insert_final_newline`, `trim_trailing_whitespace`)
instead of materializing every key of every matching section.
- Worktrees with no editorconfig at all now return `None` early instead
of allocating an empty `Properties` and forcing a `LanguageSettings`
clone on every lookup.
- Adds an `editor_render_with_editorconfig` benchmark: maximized window,
file-backed buffer at `src/app/components/editor_pane.ts`, a root
`.editorconfig` shaped after [the second
report](https://github.com/zed-industries/zed/issues/59689#issuecomment-5475124891)
(no `root = true`, a `[*]` section with ~3200 keys, 25 sections with
large `{...}` alternation lists) plus a 4-level nested chain, and a
typing workload (insert/delete + cursor moves).
```
main (f034409b6a): time: [37.905 ms 38.005 ms 38.107 ms]
time: [38.069 ms 38.157 ms 38.248 ms]
this commit: time: [2.8547 ms 2.8618 ms 2.8702 ms]
time: [2.8372 ms 2.8435 ms 2.8510 ms]
```
The candidate is ~13x faster.
GPUI frame report over the same workload: window draw p50 12.71 ms →
0.76 ms, p99 23.38 ms → 1.22 ms; frame budget overruns 556 → 0.
## Commit 2: cache resolved `LanguageSettings` per buffer
- `Buffer` and `BufferSnapshot` now hold `resolved_settings:
Arc<LanguageSettings>`, recomputed by a `SettingsStore` global observer.
- Global methods for retrieving the settings are still there, but buffer
only uses them for multi buffer's `language_settings_at` with an
injected language different from the buffer's primary; cold scopes such
as extension API, LSP workspace configuration, server startup still use
the global resolution
Same benchmark, same method, on top of commit 1 (alternated runs, quiet
machine):
```
commit 1: time: [2.8547 ms 2.8618 ms 2.8702 ms]
time: [2.8372 ms 2.8435 ms 2.8510 ms]
this commit: time: [1.6256 ms 1.6325 ms 1.6403 ms]
change: [-42.685% -42.270% -41.854%] (p = 0.00 < 0.05)
time: [1.6221 ms 1.6284 ms 1.6352 ms]
```
Window draw p50 0.76 ms → 0.47 ms, p99 1.22 ms → 0.69 ms, max 1.02 ms;
zero frame-budget overruns on both sides.
Combined, the benchmark's per-keystroke cost drops ~23x vs `main`
(37.9–38.2 ms → 1.62–1.64 ms).
Release Notes:
- Improved typing and rendering performance in projects that use
`.editorconfig` files.
* ensures The Rule cannot be accidentall removed
* fixes the perf command example to capture call graphs, to avoid
round-trips like
https://github.com/zed-industries/zed/issues/59689#issuecomment-5453623044
(samples without call graphs are not quite usable for investigating bugs
like these)
Release Notes:
- N/A
Cargo joins `build.rustflags` across config sources, so the base
`symbol-mangling-version=v0` and `tokio_unstable` flags still apply
after the newly added config.
With sccache disabled, `cargo build --release -p zed` on macOS:
Before: zed binary 420677880 bytes
After: zed binary 375109048 bytes (-10.8%, -45.6MB)
After `strip -x` of both
(https://github.com/zed-industries/zed/pull/63429):
313879024 -> 307727432 bytes (-2.0%, -6.2MB)
The rest of the delta is the smaller symbol table. `drop_glue`
instantiations: 101778 -> 65041.
Release Notes:
- N/A
Follow-up to https://github.com/zed-industries/zed/pull/63232
Cuts closure monomorphization in settings and editor: settings file
updates now share one boxed write-pipeline instantiation instead of
inlining the `SettingsContent` clone, both `serde_json::to_value` calls,
and the JSON diff at every call site; `Editor::register_action` and the
settings enum dropdown get the same treatment. Behavior and public
signatures are unchanged; thin generic shims remain.
Symbol table of the release binary:
Before: `update_settings_file_inner` 229 instantiations, inlined
`serde_json` `SerializeMap` machinery 6547 copies
After: 3 instantiations, 673 copies (-89.7%); `render_dropdown` is no
longer instantiated at all
With sccache disabled, `cargo clean` and `cargo build --release -p zed`:
Before: zed binary 420677880 bytes
After: zed binary 396999464 bytes (-5.6%, -23.7MB)
Release Notes:
- N/A
Follow-up to https://github.com/zed-industries/zed/pull/60477Closes#25022
We have more places that need a similar synchronization before quitting
the app: restarts on update, reloading the workspace.
On top of it, two more findings:
* `on_app_will_quit` was redundant: the global `on_app_quit` hook
flushes a strict superset of the same tasks and runs before windows are
cleared, while the entity hook held only a weak handle and could never
flush the `Window`-dependent workspace state anyway.
* previously, shutdown was run as `cx.spawn`, yet main threads got
parked on app shutdown: now using `cx.background_spawn` instead
Release Notes:
- Improved data persistence during various app shutdown cases
---------
Co-authored-by: Kostia R <k-bx@k-bx.com>
Closes https://github.com/zed-industries/zed/issues/9318
Most of the diff is tests.
Notable behavior changes during this mode, compared to current mode:
* unsaved changes in multi buffer won't spawn any "save or not" pop-ups
during typing — submitting the query still does it, as before.
Unsaved files will stay so after being sorted out on typing: we cannot
save them by default as file-less buffers need a save modal; Zed should
already be able to persist and restore such changes on restart.
* gutter may jump during search, if new excerpts with large line numbers
appear during query input — yet it would not shrink back to avoid
excessive flickering and rewraps
Release Notes:
- Enabled project search on type by default (use `{ "search": {
"search_on_type": false } }` to restore the previous behavior)
Closes https://github.com/zed-industries/zed/issues/62435
Part of https://github.com/zed-industries/zed/issues/60548
Before:
https://github.com/user-attachments/assets/6b5147da-44cb-40c7-9da5-c50145e2ab4e
After:
https://github.com/user-attachments/assets/c58423cc-ea99-40d9-ad18-23b31dfb8e5d
Previously, `TerminalPanel::load` awaited full deserialization of
serialized
terminals (each awaiting directory environment capture via a login
shell)
before the panel was registered in the dock.
Until then, `terminal_panel::Toggle{Focus}` was a silent no-op: the
workspace finds no
panel in any dock, the keystroke is eaten, and nothing happens.
When it got eventually restored, it would close whatever panel in the
dock was open manually instead, wrecking the workflow.
Repro:
1. Add `case "$ZSH_EXECUTION_STRING" in *--printenv*) sleep 10;; esac`
to `~/.zshrc`.
2. Open a project, open at least one terminal in the panel, quit Zed
(Cmd+Q).
3. Relaunch via Dock/Spotlight (not the `zed` CLI, which skips env
capture via
`cli_environment`).
4. Press the terminal shortcut within 10s: nothing happens; after ~10s
the
panel appears and the shortcut works.
* `Register terminal panel before restoring serialized terminals` — the
panel
is now created and registered immediately, and restoration runs in
panel-owned tasks (cancelled on panel drop, nothing detached).
Terminal-adding paths were initially queued behind restoration (later
replaced by grafting, see below); the default shell spawns only if the
panel ends up active and empty after restore.
* `Stop replaying serialized dock state over manual panel changes` —
fixes a
related race: `Dock::add_panel` replayed the serialized dock state
(active
panel, visibility, zoom) on every panel insertion, so a late-loading
panel
would re-activate the serialized panel over one the user had switched to
in
the meantime (e.g. the terminal panel yanking the dock away from the
agent
panel), or re-open a dock the user had closed. Serialized state is now
applied at most once and is discarded as soon as the user changes the
dock
state manually. The new tests fail without this change and pass with it.
* `Show restoration progress in the terminal panel and allow spawning
terminals during it` — while terminals restore, the panel shows a
spinner
with "Restoring terminals…" instead of a blank pane, and logs the
restored
count and elapsed time. Supersedes the queuing from the first commit:
new
terminals spawn immediately, and any that land mid-restore are grafted
into
the restored layout instead of being lost. Serialization is paused
during
restoration, so quitting mid-restore can no longer persist a partial tab
list and silently drop terminals.
Release Notes:
- Fixed the terminal panel not opening at all under certain
circumstances, made panels no longer switching back to the serialized
one when opened during startup.
Fixes https://github.com/zed-industries/zed/issues/62780
When formatting changed-only regions, we have received formatting
changes that are larger than the regions submitted.
Previous code merged only adjacent hunks in a single response, without
merging anything else, e.g. multiple responses' ranges that overlapped.
Release Notes:
- Fixed overlapping range format results duplicating the text
`ThreadView::regenerate` does `thread.rewind()` which de-focused the
message editor mid-flight causing the zoomed panel to disappear.
* workspace: Keep zoomed panels open when window focus is lost
Commit adds a test + tweaks a default, generic, fallback to be more
lenient: instead of focusing the workspace (and hiding all panels right
away), try to fall back to whatever panel open
* gpui: Restore focus to nearest surviving ancestor when focus is lost
Commit tries to push it down to a library level and add a way to fall
back to closest ancestor that's still visible with the focus instead.
Release Notes:
- Fixed the zoomed agent panel closing unexpectedly on rewind
Follow-up to https://github.com/zed-industries/zed/pull/62658
The problematic field is not needed at all in the snapshot, as can be
constructed before starting the scanner — moreover, the field had
accumulated more and more paths between rescans, leaking memory.
Now, we spend more time traversing the entire tree between rescans, but
that happens for rescans only which should be relatively rare?
Release Notes:
- N/A
Fixes https://github.com/zed-industries/zed/issues/35780
Collab schema migration PR:
https://github.com/zed-industries/cloud/pull/3422
The corresponding database schema migration has been created in the
Cloud repo and applied to the production database.
Before, Zed scanned each and every entry in the tree down from the
directory it was opened in, except gitignored files and scan exclusions.
The approach is unchanged, if Zed detects it was open inside a git
repository: e.g. the directory open in Zed contains `.git` directory.
For the rest of the projects, 2 optimizations are made:
* Limit the depth of file scan traversal.
Now, `file_scan_depth` (default `5`) restricts Zed from traversing any
directory that has same number or more segments in its file path.
Such directories behave similar to gitignored directories: their
contents is not available in file finder, project search and project
panel, but can be lazily traversed when the directory is expanded (e.g.
project panel expands it or a nested file is open by path via terminal,
etc.)
To indicate that to the users, a status entry is shown firs time the
limitation is hit in the project:
<img width="858" height="133" alt="image"
src="https://github.com/user-attachments/assets/7da6cfbb-98b4-4cc3-bf2a-8902a9597a15"
/>
* During the scan, any git repositories that are not direct children of
the directory open in Zed (depth >= 2), are traversed and indexed
normally, but their git metadata is never fetched eagerly.
Only when Zed opens a buffer from that repo the git metadata is fetched
and applied.
All that combined now uses a way more moderate amount of CPU and RAM
when opening `~`:
<img width="1717" height="368" alt="Screenshot 2026-08-13 at 17 43 53"
src="https://github.com/user-attachments/assets/ec83e2a9-f7cc-452b-8eb7-af158284ca4e"
/>
File scan inclusions and exclusions are considered still for such
projects.
Set `file_scan_depth` to `0` to enable old behavior.
The setting is supported in the project settings, so custom values can
be set based on the project's structure.
---
Release Notes:
- Fixed Zed using a lot of memory and CPU in large, non-git-tracked,
directory trees
Spotted
```
The package `block v0.1.6` currently triggers the following future incompatibility lints:
> warning: static of uninhabited type
> --> .../block-0.1.6/src/lib.rs:64:5
> |
> 64 | static _NSConcreteStackBlock: Class;
> | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> |
> = note: uninhabited statics cannot be initialized, and any access would be an immediate error
> = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
> = note: for more information, see issue #74840 <https://github.com/rust-lang/rust/issues/74840>
The package `proc-macro-error2 v2.0.1` currently triggers the following future incompatibility lints:
> warning[E0365]: extern crate `proc_macro` is private and cannot be re-exported
> --> .../proc-macro-error2-2.0.1/src/lib.rs:494:13
> |
> 494 | pub use proc_macro;
> | ^^^^^^^^^^
> |
> = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
> = note: for more information, see issue #127909 <https://github.com/rust-lang/rust/issues/127909>
> help: consider making the `extern crate` item publicly accessible
> |
> 277 | pub extern crate proc_macro;
> | +++
```
warnings recently.
The former is impossible to fix quickly as needs a migration to `objc2`,
but the latter is easily fixed by a version bump, ergo this PR.
Release Notes:
- N/A
Closes https://github.com/zed-industries/zed/issues/61208
Before, Zed showed no toasts on startup when tasks.json contained
malformed entries, also if there were two top-level arrays, the last one
was silently discarded without any toasts too.
The PR fixes both.
Release Notes:
- Fixed error toast not showing for malformed tasks.json
Closes ZED-AKV
Closes ZED-ANB
Follow-up to https://github.com/zed-industries/zed/pull/61153
The original PR mixed up the coordinate spaces, and the newly added
methods used buffer rows (as the existing code did before that PR), but
the new code uses `FoldMap` which expects a tab row instead of the
buffer one.
Release Notes:
- Fixed a panic when selections are added by tab-expanded column
Splits the crate graph.
Before, the compilation graph: `editor → picker_preview → search →
project_panel → open_path_prompt → recent_projects → title_bar →
collab_ui → zed`
After, the compilation graph: `editor → picker_preview → search →
agent_ui → sidebar → zed`
With sccache disabled and project fully built, `touch
crates/editor/src/editor.rs` and `cargo build -p zed` took time
Before (0500fd9ee9): 13.19s
After: 11.85s (-10.2% speed up)
Each commit contains a separate compilation instructions, and a test in
the final commit.
The main approach is to split coupled crates and replace them with
`zed_actions` and move some shared functionality into `git_ui_core` new,
shared module.
I've also tried to add a test to prevent common pitfalls, but not sure
I'm happy with the end result — can remove it if it looks too synthetic.
Release Notes:
- N/A
Follow-up to hints part of
https://github.com/zed-industries/zed/pull/61523
Instead of appending to the chunk data, we have to replace as we always
get all hints for the entire chunk.
Given that we order and invalidate the requests by `Version`, this can
only happen when a racy requests from e.g. same file split or /refresh
and edit etc. happens.
Release Notes:
- N/A
Closes https://github.com/zed-industries/zed/issues/61524https://github.com/zed-industries/zed/issues/60763 was a bogus issue:
the semantic of root `language_settings` is the **default settings for
all languages**, not a "magic string that gets applied when an `!x`
exclusion is mentioned there".
Thus, reverts https://github.com/zed-industries/zed/pull/60984 and
redoes https://github.com/zed-industries/zed/pull/60000 in a correct
way: more custom settings replace less custom ones, same applies to the
language server settings.
Instead of doing some odd `!x` manipulations, the right fix is to remove
the old state as
```diff
impl merge_from::MergeFrom for AllLanguageSettingsContent {
fn merge_from(&mut self, other: &Self) {
...
self.defaults.merge_from(&other.defaults);
for language_settings in self.languages.0.values_mut() {
+ let language_servers = language_settings.language_servers.take();
language_settings.merge_from(&other.defaults);
+ language_settings.language_servers = language_servers;
}
for (language_name, user_language_settings) in &other.languages.0 {
if let Some(existing) = self.languages.0.get_mut(language_name) {
existing.merge_from(&user_language_settings);
} else {
let mut new_settings = self.defaults.clone();
+ new_settings.language_servers = None;
new_settings.merge_from(&user_language_settings);
```
After the final override is determined, `...` is expanded and `!x`
entries are removed from the final list.
Project settings override user settings, so it's possible that a certain
language server is disabled by the user and re-enabled in the project:
worktree trust mechanism is supposed to mitigate the security issues.
More tests were added to cover all the cases.
Release Notes:
- Fixed project settings not re-enabling language servers
Currently, Zed's "VSCode" keymap has certain discrepancies that are not
changed for compatibility reasons, e.g. opening inline assistant is
different in each editor.
To simplify the transition, extract VSCode bindings its own keymap so in
the future it's simpler to accept changes to each keymap separately.
Caveat: people who had ever changed the keymap, will have VSCode keymap
in their settings which will change their bindings slightly after this
commit is released.
I had to introduce more logic to `null` handling as had to somehow
disable `ctrl-enter` on mac from spawning the inline assistant block for
VSCode keymap (it's cmd-i there).
Release Notes:
- Split VSCode and Zed keymap files
<img width="1728" height="1084" alt="image"
src="https://github.com/user-attachments/assets/a560b10a-6e26-43ff-b830-c528e2dc6798"
/>
Before, each edit in a 50 MB plaintext file would trigger a lot of
anchor calculations (as chunk is 50 lines only) done on main thread +
did extra work when no language grammar or brackets were available.
The PR now moves all anchor calculations into `computed_chunks:
Mutex<HashMap<usize, RowChunk>>,` cache miss.
`TreeSitterData` is wrapped in `Arc` and shared as a part of the
snapshot, hence the need for `Mutex` and internal mutability here.
Trace after the changes:
<img width="1728" height="1084" alt="image"
src="https://github.com/user-attachments/assets/cb258980-8461-4559-b725-aef63c835e60"
/>
Release Notes:
- Improved input performance in large files
---------
Co-authored-by: Finn Evers <finn@zed.dev>
Closes https://github.com/zed-industries/zed/issues/60905
Also makes the breadcrumbs to update on settings change — before, one
needed to focus them.
Release Notes:
- Improved breadcrumbs for `"document_symbols": "on"` case when no LSP
data is sent
Follow-up to https://github.com/zed-industries/zed/pull/60721
Before:
<img width="564" height="470" alt="before"
src="https://github.com/user-attachments/assets/d825464d-f8d9-4fa1-bee3-42b5f615d4ba"
/>
<img width="745" height="221" alt="image"
src="https://github.com/user-attachments/assets/18139bec-6778-491a-9aa5-0112bd121723"
/>
After:
<img width="576" height="472" alt="after"
src="https://github.com/user-attachments/assets/4c3c37cf-12c4-421e-98e9-cc6e40aa099d"
/>
<img width="812" height="275" alt="image"
src="https://github.com/user-attachments/assets/407e06b4-101f-4984-8d5b-4e33895b5ae9"
/>
### Bug 1 — constraint-blind text layout cache (the collapse)
`TextLayout`'s measure closure is stateful (each call overwrites the
shared element state used for painting), and its cache check served
cached sizes to unconstrained probes regardless of the constraints they
were computed under:
1. `uniform_list::measure_item` lays an item out at `(MinContent,
MinContent)`; the row's `min_w_0` lets it shrink to ~0, so labels get
truncated at a tiny width — and that tiny size lands in the element
state.
2. The real layout probes the label's flex base size with
`available_space.width = MaxContent` (taffy maps Definite → MaxContent).
Truncated labels are `whitespace_nowrap`, so both `wrap_width` and
`truncate_width` were `None` → the cache check passed → the poisoned
tiny size became the flex basis → labels rendered at ~5 chars.
**Fix**: record `truncate_width` in `TextLayoutInner` and never serve a
cached layout that was computed *with* truncation to an unconstrained
probe; the honest intrinsic size is recomputed instead, and the final
`PerformLayout` call re-truncates against the real resolved width.
### Bug 2 — truncation width math disagrees with shaping (the ~2-char
loss)
After fix 1, rows got their full width, but exact-fit strings like
`Cargo.toml` still lost ~2 chars:
- Sizing measures via `shape_text` (real shaping with kerning); the
element gets exactly `ceil(shaped_width)`.
- The truncation decision (`LineWrapper::should_truncate_line_middle`
etc.) sums **per-character advances** — no kerning — with zero
tolerance, overestimating width for some glyph sequences. Text sitting
in a box of exactly its own measured width got truncated, losing ~2
chars to fit the `…`. String-dependent (kerning pairs), hence
`Cargo.toml` broke while `Cargo.lock` didn't.
- Previously masked because taffy 0.10 usually skipped the final measure
invocation, so the truncation path rarely ran with an exact-fit width.
**Fix**: before truncating (nowrap case, `wrap_width.is_none()`), shape
the untruncated text and skip truncation if the *shaped* width fits —
matching CSS `text-overflow`, which only activates on actual overflow.
Cheap: the shaping result is a `line_layout_cache` hit from the earlier
untruncated sizing probe.
### Bug 3 — `MinContent` used instead of `MaxContent` that caused over
truncation of certain labels
Release Notes:
- N/A