Fixes the issues where using the zed builtin prompt when removing
wsl/ssh remote servers did not capture focus and caused input to be
passed to the existing picker/editor.
Closes#60840.
# Objective
The objective of this fix was to make the remote server removal prompts
work correctly, capture focus, when using keyboard navigation to remove
a server or wsl distro. The fix was created directly to address #60840.
## Solution
The solution here is quite simple, we remove the forced, focus lines,
that attempt for the context to capture the new prompt window. The
oringal additional call to `cx.focus` was entirely unnecessary and was
duplicated from the original ssh remote server addition, into the wsl
distro addition. This behaviour and the correct deferal is handled by
the receivers invocation upon the execution of the listitem.
## Testing
I tested these changes manually, however this is incredibly easy to
reproduce, and is entirely platform agnostic. Simply add a testing
distro to the list, and then attempt to remove that distro by going into
its settings and removing the sever/distro. Then use the keybinds to
attempt to navigate. Prior to this PR, this would cause you to interact
with the editor behind the prompt, but now correclty directs
actions/inputs to the prompt window.
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
---
Release Notes:
- Fixed remote server removal prompts not capturing keyboard focus.
Signed-off-by: Counterfit IQ <logicye@proton.me>
Co-authored-by: dino <dinojoaocosta@gmail.com>
## Summary
Pin all PET dependencies to [our fork at
5aad716](https://github.com/zed-industries/python-environment-tools/commit/5aad7164bfc2a58977b103c8ef8fdd7302ed2e9f),
fixing ZED-BGV: non-UTF-8 interpreter stdout could panic discovery.
Refs https://github.com/microsoft/python-environment-tools/issues/525.
The bump also includes upstream’s Windows Conda alias-deduplication fix
(#519 in PET) and CI-only changes. No unrelated lockfile changes.
## Validation
- `corgi check -p languages` passed on macOS.
- PET’s regression test failed with the original code and passed with
the fix; all 51 `pet-python-utils` tests passed in the PET checkout.
- Verified all seven direct dependencies and 27 lockfile entries point
to the fork.
- `git diff --check` passed.
Release Notes:
- Fixed a crash during Python interpreter discovery when an executable
emits non-UTF-8 output.
# Objective
Get rid of instances of the `block` crate, as it will not compile in a
future version of Rust.
## Solution
Migrate an independent code block to the `objc2` crate family.
## Testing
Tested dragging/dropping files & folders from Zed into Finder. There
seems to be some highlighting bug with folders, but that existed
previously as well.
Release Notes:
- N/A
---------
Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
Anthropic reports exhausted credits as `invalid_request_error` rather
than the documented `billing_error`. Recognize the specific
credit-balance message when converting Anthropic API errors to
`PaymentRequired`.
This preserves the original status, code, and message, handles errors
with or without an HTTP status, and leaves other invalid requests
unchanged. The comment links to Anthropic's error documentation.
Closes DELTA-77
Validation: the existing Anthropic billing-error test and package
formatting check pass. A temporary classification test also passed for
exhausted credits and malformed requests, with and without HTTP status;
it is not included in this PR.
Release Notes:
- Fixed Anthropic credit exhaustion being classified as a malformed
request instead of a payment issue.
# Objective
Get rid of instances of the `block` crate, as it will not compile in a
future version of Rust.
## Solution
Migrate an independent code block to the `objc2` crate family.
## Testing
I tested screen sharing inside of a Zed channel
Release Notes:
- N/A
Use the registry's default_model() accessor so inline assist can use the
same automatically selected fallback model as agent sessions when no
configured default model resolves. Keep explicit inline model selection
as the first choice.
Related to #59309.
Release Notes:
- Fixed Inline Assistant failing to select an available fallback model
when no default model is configured.
---------
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
Simplifies the mobile web virtual keyboard implementation in the
following ways:
- assumes that GPUI web apps will set
`interactive-widget=resizes-visual`. This means that GPUI's window will
not change viewport size when the keyboard appears
- Exposes `Window::fully_visible_bounds`, which returns a rectangle
which is fully visible (pixels outside these bounds *may* be visible but
may not be)
- These two changes together mean that keyboard visibility is more
reliably detectable (though not fully), and also makes it easier to have
a stable scroll position when the keyboard is animating in/out
- tweaks the focus model so that:
- transitions into/out of the state "GPUI focus is in a text input, and
that text input is in an editable range" will change keyboard visibility
- manually-dismissed keyboards are preserved by not synchronizing unless
a tap event (or similar, or a programmatic request) on that input
happens
- there is a new `Window::{request,dismiss}_virtual_keyboard()` method
that allows programmatically controlling keyboard visibility (without
interacting with GPUI focus). This allows for patterns like "pressing
button A focuses text field B, *and opens the keyboard*
This PR also fixes a bug in non-touch web that caused text fields to
lose IME mirror focus when they entered non-editable regions, which
could prevent keyboard input from being detected by GPUI properly.
---
Release Notes:
- N/A or Added/Fixed/Improved ...
# Objective
Fix an issue in SSH remote mode where reopening a newly created file at
a path that was previously renamed could incorrectly reuse the buffer
for the renamed file.
## Solution
When handling UpdateBufferFile, remove the buffer's previous path from
path_to_buffer_id when the file path changes.
## Testing
Tested manually on Linux using SSH remote mode.
Reproduction before the fix:
touch A
Open A from the project panel
mv A B
touch A
Open the new A from the project panel
Before the fix, opening A reused the buffer for B.
After the fix:
B continues to use the original buffer.
The recreated A opens as a separate buffer.
Renaming an opened remote file still updates the existing buffer to the
new path correctly.
## 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 adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- Fixed an issue in SSH remote mode where reopening a newly created file
at a path that was previously renamed could incorrectly reuse the buffer
for the renamed file.
---------
Signed-off-by: xiaoff <xiaoff0019@outlook.com>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
# Objective
Get rid of instances of the `block` crate, as it will not compile in a
future version of Rust.
## Solution
Migrate to the `objc2` crate family.
This gets rid of `block` inside of `platform.rs` 🎉
Release Notes:
- N/A
Add an opt-in watch channel that atomically publishes immutable, owned
snapshots rather than holding a reader/writer lock. Readers can retain a
value without delaying publication.
Use independent atomic wakers for receivers while preserving
latest-value coalescing and channel closure semantics. Keep the existing
watch implementation unchanged and document when to choose each
implementation.
Release Notes:
- N/A
# Objective
Bumps `wasi-sdk` to v34 that is based on LLVM 23.1 which includes [a
compile time and memory usage
fix](https://github.com/llvm/llvm-project/pull/181755) for tree-sitter
grammars. It speeds up every grammar's compile time and reduces compile
memory usage unlocking big grammars that were impossible to compile
before to be submitted to Zed extensions.
Drive-by added additional wasi-sdk targets for the rest of the supported
architectures (by wasi-sdk), if riscv is unwanted I can remove it but it
should not create any problems (I think).
Also noticed the `download-wasi-sdk` script had `OS=mingw` which wasn't
even in the artifact name on v25.
## Testing
Compiled, ran and compiled my [big
grammar](https://github.com/usagi-coffee/tree-sitter-abl) on it, that
wouldn't compile at all pre-llvm 23.
## Self-Review Checklist:
- [X] I've reviewed my own diff for quality, security, and reliability
---
Release Notes:
- Fixed compiling dev extensions with big Tree-sitter grammars.
- Fixed compiling dev extensions on Windows ARM64.
- Added wasi-sdk installation invalidation.
---------
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
SQLite fails fast with `SQLITE_BUSY` when another process holds the
database lock. This database, and others like it, are shared between Zed
instances. SQLite allows multiple readers at once, but only one writer
at a time. Writes are short-lived, but if multiple Zed instances try to
write to the file at the same time, the default fail-fast behavior
throws `SQLITE_BUSY` and the write is dropped instead of retried. The
fix is to keep trying the write for up to five seconds, so transient
lock contention resolves and the write succeeds instead of erroring.
A concrete case is the agent threads database (`threads.db`), which
lives in the shared per-user data directory and is not namespaced by
release channel. Running two Zed instances side by side means both can
attempt to write to the same file at the same time, resulting in the
error mentioned previously.
Release Notes:
- Fixed intermittent "database is locked" errors when sharing a database
across Zed instances
Zed did not advertise document highlight dynamic registration support or
handle `textDocument/documentHighlight` registrations. Dynamically
registered providers therefore fell through to the unhandled-capability
path, while servers with a fallback had to advertise a static provider
for every attached document.
Advertise dynamic registration support and track document highlight
registrations and unregistrations alongside the other selector-aware
dynamic text document capabilities. Refresh editors whenever the
effective document highlight registration state changes, including
selector-only changes that leave the merged provider unchanged, so
runtime registrations take effect immediately and unregistrations clear
stale or in-flight highlights.
Add coverage for the client capability, registration lifecycle with
multiple selectors sharing identical provider options, and editor
refresh behavior when an applicable selector is added or removed.
## 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 adheres to Zed’s UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- N/A
---------
Co-authored-by: GPT-5.6 Sol <noreply@openai.com>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
## Summary
Updates Zed trial copy for
[CLO-1373](https://linear.app/zed-industries/issue/CLO-1373/update-zed-to-show-the-dollar5-gpt-luna-trial).
- Show $5 of GPT Luna and unlimited edit predictions across pre-sign-in,
Free-plan, and active-trial onboarding.
- Clarify that the 14 days begin when the trial starts, rather than when
the welcome panel is viewed, and retain no-credit-card messaging.
- Update hosted-model trial descriptions and use "Start Free Trial" for
onboarding and AI configuration trial CTAs.
- Document GPT Luna-only trial hosted-model access and the shared
Zed/Delta $5 trial balance.
This is a copy-only change. Trial eligibility, subscription creation,
trial timing, billing, model-access enforcement, button actions, URLs,
and telemetry are unchanged. Paid, Student, Business, and VIP plan
descriptions are unchanged. This does not implement the offer on the
backend.
## Verification
- Rust formatting and `git diff --check` passed.
- Prettier check passed for the pricing documentation.
- Source checks confirmed the offer wording and unchanged callbacks,
URLs, telemetry, and other plan definitions.
- Katie built and launched Zed with `cargo run -p zed --bin zed`, then
reviewed Component Preview and requested the final CTA wording.
Formatting and diff checks were rerun after that wording adjustment; the
final adjustment has not been rebuilt.
- Confirmed the branch merges cleanly with the latest `main`.
Release Notes:
- Improved trial descriptions to show $5 of GPT Luna and unlimited edit
predictions for 14 days from trial start.
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` (`d6449a9e3f`):**
```
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)
# Objective
The `block` crate will no longer compile with some unspecified future
version of Rust. The long term goal is to get rid of the `block` crate
entirely, and this is one such step.
## Solution
Migrate a standalone function to the `objc2` crate family. Removes many
lines of unsafe code, and switches from `block` to `block2`.
## Testing
All of the calls to `objc2` are identical to the ones in the old code.
Release Notes:
- N/A
---------
Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
Release Notes:
- Added button tu prevent idle sleep during AI response streaming
**Context / Motivation:**
When an AI agent takes a long time to generate a response, the operating
system might enter an idle sleep (or display sleep) state if there is no
user interaction (mouse or keyboard). This can interrupt the workflow.
This PR aims to prevent the system from sleeping during an active AI
turn by invoking native OS APIs, and provides a UI toggle for users to
control this behavior.
**Key Changes:**
1. **Cross-Platform Idle Sleep Prevention in GPUI (`gpui`, `gpui_*`)**
- Added a new `prevent_idle_sleep` method to the `Platform` trait. It
returns a RAII-based `PreventIdleSleepToken`, which automatically
restores the original sleep policy when dropped.
- **macOS**: Utilizes `NSProcessInfo`'s `beginActivityWithOptions` with
`NSActivityIdleDisplaySleepDisabled`.
- **Windows**: Uses `SetThreadExecutionState` to prevent display and
system sleep, incorporating a reference counter to handle concurrent
requests safely.
- **Linux**: Uses `ashpd` to request `org.freedesktop.portal.Inhibit`
(inhibiting `Idle` and `Suspend` states) via D-Bus.
2. **AI Agent State Integration (`crates/acp_thread`)**
- Introduced idle sleep prevention logic in `AcpThread`: Acquires the
token at the beginning of `run_turn`, and automatically releases it when
the turn completes normally, errors out, or is explicitly canceled by
the user.
- Added comprehensive unit tests to ensure the token is correctly
acquired and released under various cancellation and concurrency
scenarios.
3. **Settings and UI (`crates/agent_settings`, `crates/agent_ui`)**
- Added `AgentOutputIdleSleepControl` to global state.
- Added a new toggle button (screen icon) to the toolbar at the bottom
of the `ThreadView` (AI conversation panel). Users can hover to view the
tooltip and click to toggle the feature on or off.
<img width="419" height="132" alt="截屏2026-04-04 16 09 08"
src="https://github.com/user-attachments/assets/619da9ff-10f7-46e0-b959-d7c74b16b918"
/>
---------
Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Addresses #9640 (the Spaces part).
On macOS, Zed already restores each window's size, position, and
display, but not its Space (virtual desktop). So after a restart, all
windows open on whatever Space is active at launch, not the Space they
were on before. This is the most reported problem in #9640.
A window's Space is not part of the window bounds we save. It is handled
by AppKit's own window state restoration. So instead of tracking the
Space myself, this PR uses that AppKit feature:
- I mark `GPUIWindow` as restorable and encode its native state (frame +
Space) by calling `encodeRestorableStateWithCoder:` with an
`NSKeyedArchiver`. A delegate skips the `NSView`/`NSWindow` objects that
do not support `NSSecureCoding`.
- I save the blob per workspace in a new `restorable_window_states`
table.
- When a saved workspace is opened again, I create the window and then
replay the blob with `restoreStateWithCoder:`. AppKit then moves the
window back to its Space.
On other platforms, and when there is no saved blob, this does nothing.
Verification:
- Added a round-trip test for the database part (save / overwrite /
read, and `ON DELETE CASCADE`).
- The macOS encode/restore part calls into AppKit, so I could not
unit-test it. I checked it by hand: I put several windows on different
Spaces, quit with Cmd+Q, started Zed again, and each window came back on
the Space it was closed on. I also checked that the saved blob is a real
AppKit archive (it starts with `bplist00`).
For now this only works for local workspaces. Remote workspaces could
use the same way later.
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
Release Notes:
- macOS: Restored windows now reopen on the Space (virtual desktop) they
were on, instead of all landing on the currently active Space.
---------
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
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
## Summary
Replace GPUI's shared asset futures with owning cache entries. The
mutex-backed waiter collection in futures::future::Shared can contend
between a worker and the browser main thread, where blocking waits are
forbidden.
- Keep loading on the background executor, with one foreground task
publishing the cached result and notifying interested views.
- Deduplicate loads and view registrations; cancel pending work on
eviction without allowing completion to update a replacement entry.
- Change App::fetch_asset to return Option<A::Output>, remove
ImageLoadingTask, and make ImageCacheItem an opaque owning entry.
- Migrate the retain-all image cache and image gallery LRU example.
Repository-wide search found no further callers requiring migration.
Results become visible after the foreground completion task publishes
them. Entries expose neither cloneable nor awaitable task handles.
Release Notes:
- N/A
#### Fixes#52579
When quickly pressing and releasing Ctrl+Tab, the tab switcher popover
would briefly appear, and any mouse movement over it would select a
random tab instead of the intended recently-focused file.
### Fix :
This adds a 300ms delay before showing the tab switcher UI when opened
via modifier keys. If the modifier is released before the delay elapses,
the selected item is confirmed directly without ever showing the
popover. This matches the behavior of system window switchers (e.g.
macOS Cmd+Tab), where quick presses never flash the UI.
- `visible` and `_show_task` fields track whether the popover should be
rendered
- When not yet visible, the picker is mounted in a zero-size
overflow-hidden container to preserve focus and keyboard state
- When modifiers are released before the popover is visible, `confirm()`
is called directly instead of dispatching `menu::Confirm`, avoiding any
mouse-hover-based selection
### Self-Review Checklist
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments — *N/A, no unsafe
blocks*
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
— *Matches system window switcher behavior (e.g. macOS Cmd+Tab); no
visual change for slow/held switches*
- [x] Tests cover the new/changed behavior — *Added
`test_quick_switch_before_popover_visible`*
- [x] Performance impact has been considered and is acceptable — *Only
adds a lightweight 300ms timer task; no extra allocations or rendering
cost*
### Video
[Screencast from 2026-03-29
11-52-07.webm](https://github.com/user-attachments/assets/8e148a74-75ae-4c36-8d44-594c3c9097e6)
### Note : Reopens previous work from closed PR #52648 (fork was deleted
by mistake)
Release Notes:
- Fixed Ctrl+Tab jumping to random documents when the mouse is moved
during a quick tab switch
---------
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Release Notes:
- Fixed injected language highlighting for strings containing
interpolations
current | updated
:-------------------------:|:-------------------------:

|

<details>
<summary> test snippet </summary>
```python
my_regex = r"[0-9]"
spark.sql( # sql
f"""
SELECT
col1,
CASE WHEN col2 RLIKE '{my_regex}' THEN '1' ELSE '2' END col4,
ROW_NUMBER() OVER(PARTITION BY col1, col2 ORDER BY col3) as rn
FROM tbl
"""
)
```
</details>
## Summary
Strings with interpolations like `f"SELECT col1 FROM tbl WHERE col2 =
'{my_var}'"` in Python produce multiple `string_content` nodes. Without
grouping, each fragment gets its own injection layer which breaks syntax
highlighting.
This PR adds an `@injection.host` capture to group `@injection.content`
ranges by their host node. This was implemented by extending the
existing `combined_injection_ranges` hash map with a node key per host.
Added tests for this behavior covering simple strings, f-strings with
single/multiple interpolations, and multi-string isolation.
---------
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resubmit of #59731, which got auto-closed when I accidentally deleted
its branch. It wasn't rejected, it had already been reviewed. This is
rebased onto latest `main`, and osiewicz's review feedback (using `_`
for the unused param in the trait default) is already in.
Follow-up to #58510. Profiling multi-cursor typing on current main
showed the per-keystroke `set_active_selections` broadcast does an
O(selections) multi-buffer anchor remap and runs even with zero
collaborators (about 0.9ms/call at 1k cursors, 8.4ms at 10k, fired ~3x
per edited iteration).
This skips the broadcast when the buffer has no audience, and
re-publishes on share so there's no regression:
- Add `CollaborationHub::should_broadcast_selections`, defaulting to
`true`. `Entity<Project>` overrides it to `Project::is_shared()`, which
is true for a host that shared the project and for a collab guest, and
stays correct before peer-join notifications have propagated locally
(unlike a live collaborator count). A purely local project has no
audience.
- Gate `set_active_selections` in `selections_did_change` on it.
Follow-mode is unaffected since it uses the separate
UpdateFollowers/UpdateView channel, and the focused user's own cursors
render from `editor.selections`, not `remote_selections`.
- When the project becomes (re)shared (`RemoteIdChanged(Some)`,
`Reshared`, `HostReshared`), the focused editor re-publishes its current
selections, so a peer joining after a share still sees the host's cursor
without waiting for the next selection change.
- Reuse the post-edit display snapshot in the `hard_wrap` branch instead
of taking a redundant fresh buffer snapshot.
Multi-cursor type+delete benchmark: about 5% faster at both 1k and 10k
cursors (615 to 583 ms at 10k, significant). All 773 editor tests pass
locally. Collab and channel-buffer integration tests need the test DB,
so they run on CI.
Discussion and profiling:
https://github.com/zed-industries/zed/issues/32051
Release Notes:
- Improved multi cursor editing performance
# Objective
Files matching the `private_files` setting are meant not to be shared
with collaborators. That is true when a guest asks to open one directly,
`respond_to_open_buffer_request` refuses with `ErrorCode::UnsharedItem`.
Project search reaches the same `create_buffer_for_peer` without passing
through that check. In `handle_search_candidate_buffers`, every matching
buffer was shared with the requesting peer unconditionally, so a guest
searching for a string that happens to appear in a private file received
the buffer containing it.
## Solution
Check `is_private` before sharing a matched buffer with a peer
## Testing
added new test `test_project_search_excludes_private_files` in the
collab integration suite:"
The host configures `private_files: ["**/.env"]` and has two files
`.env` containing `API_KEY=secret` and `a.txt` containing `the secret is
out`. The guest joins the shared project and searches for `secret`.
---
Release Notes:
- Fixed private files being shared with collaborators through project
search.
Crash repro:
```
A
1234567890
B
C
```
1. place cursors before `A`, `B`, and `C`
2. run `editor: rotate selections forward`
Caused by the current `Editor::rotate_selections` logic assuming that
the text between the end of one of the lines being rotated and the start
of the next one is just the newline:
```rs
let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
```
which fails when those lines aren't consecutive in the editor.
## 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 adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
---
Release Notes:
- Fixed crash in `editor: rotate selections forward` and `editor: rotate
selections backward` using cursors on nonconsecutive lines.
# Objective
- Fixes#33819
- The `preview_tabs` documentation says preview tabs "close
automatically when you switch to another file unless you explicitly pin
them". They do not close. Moving to another file leaves the preview tab
open — either still as the preview tab, or converted into a regular one.
Four people ran into this on the issue over the past year.
- The same section then lists the ways to turn a preview tab into a
regular tab, and two of them are missing, including the pinning that the
sentence above uses as its counter-example.
## Solution
Two corrections to the `Preview tabs` section of
`docs/src/reference/all-settings.md`. No behaviour was touched.
**The description.** A pane holds a single `preview_item_id`, so there
is at most one preview tab at a time. What happens to it when you move
to another file depends on the path:
- Opening a not-yet-open file in preview mode from the project panel or
file finder goes through `Pane::open_item`, which calls
`close_current_preview_item` — that really does close the old tab
(`remove_item`) and hands back its index, and the new file is inserted
there.
- Code navigation with the default
`enable_keep_preview_on_code_navigation: false` instead calls
`unpreview_item_if_preview` on the old item *before*
`replace_preview_item_id`, so the previous file stays open as a regular
tab.
A third path, the file finder with the default
`enable_preview_from_file_finder: false`, opens the new file as a
regular tab and leaves the existing preview tab alone entirely.
Rather than spell out every path in a settings description — the
`enable_preview_*` settings immediately below already cover them — the
new wording sticks to what holds in all of them: a pane keeps at most
one preview tab and opening another file in preview mode takes over that
slot, and switching to a file that is already open does not close the
preview tab. That second half is the case the issue actually reports.
**The list.** Two more ways to convert a preview tab into a regular tab:
- Pinning it — `change_tab_pin_state` calls `unpreview_item_if_preview`
on the `Pin` branch.
- `pane::TogglePreviewTab` — the action dedicated to exactly this,
registered whenever preview tabs are enabled.
## Testing
- Built a debug Zed and walked through the steps from the issue on a
scratch project of `a.txt` / `b.txt` / `c.txt`: with `a.txt` open as a
regular tab and `b.txt` opened as a preview tab, clicking back to
`a.txt` leaves the `b.txt` preview tab open, which is what the current
wording says should not happen. Single-clicking `c.txt` takes over the
same slot rather than adding a second preview tab, and pinning the
preview tab drops the italics.
- Ran the docs steps from `run_tests.yml` locally: `zed
--dump-all-actions > crates/docs_preprocessor/actions.json`, then
`mdbook build ./docs` on mdbook 0.4.37 to match CI. Exit 0 with no
preprocessor errors, and both new `{#action ...}` references resolve —
the rendered page reads "pane: toggle pin tab" and "pane: toggle preview
tab" in the same style as the existing "project panel: open permanent".
- `cargo test -p workspace --lib` — 272 passed, 0 failed, including
`test_close_other_items_unpreviews_active_item`,
`test_reopening_closed_item_after_unpreview`, and the proptest that
opens random paths with random `allow_preview` and asserts a pane never
holds more than one preview item.
## Notes for review
- Several people on the issue would rather the *behaviour* changed, so
that preview tabs close on any tab switch the way the docs currently
describe. I left that alone deliberately: the issue is typed `Docs`, so
this PR only makes the prose match the implementation. Happy to open a
discussion separately if it is worth pursuing.
- Saving a file also unpreviews it (`Pane::save_item`), but I left it
off the list as redundant — you have to edit a file before you can save
it, and editing is already there. Say the word if you would rather it
were explicit.
- The comment on `preview_tabs.enabled` in
`assets/settings/default.json` words this as "close automatically when
you open another preview tab". That is right for the project-panel path
and carries the same code-navigation caveat as above; I left it alone to
keep this diff to one file, and am happy to align it in a follow-up.
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments — N/A
- [x] The content adheres to Zed's UI standards — N/A, docs only
- [x] Tests cover the new/changed behavior — N/A, no behaviour change;
the docs build is verified locally
- [x] Performance impact has been considered and is acceptable — N/A
---
Release Notes:
- N/A
# Objective
Video overview of this PR, maybe it helps with the review:
https://youtu.be/-JhKcZttXp8Fixes: #63828
Building Zed from source and opening a project in a dev container fails
with `Remote server exited with status 1`.
Zed compiles `remote_server` and uploads it into the container. The
binary loads Zed's default settings from the Zed source directory, which
doesn't exist there, and panics in `settings::init`:
```
thread 'main' panicked at crates/util/src/util.rs:729:10:
dev asset loading requires running from within the checkout
```
The build passes `--features debug-embed` to embed those assets instead,
but `remote_server/debug-embed` only enabled its own (unused)
`rust-embed` dependency. It never reached `util/debug-embed`, which is
what `fs_embed!` gates on. Regression from #63396.
SSH and WSL remotes are affected the same way. Release builds are not:
assets are embedded whenever `debug_assertions` is off.
## Solution
Forward the feature.
```diff
-debug-embed = ["dep:rust-embed"]
+debug-embed = ["dep:rust-embed", "util/debug-embed"]
```
The same one-line change is already included as a secondary fix in
#63613, whose subject is unrelated; its author offered to split it out.
## Testing
- `cargo tree --package remote_server --features debug-embed` now
resolves `util` with `debug-embed`. Before:
`rand,test-support,util_macros`.
- `cargo check -p remote_server --features debug-embed --target
x86_64-unknown-linux-musl` passes clean.
- Manually: built Zed from source on Linux x86_64, opened a project in a
dev container (`mcr.microsoft.com/devcontainers/base:ubuntu`). Before:
panic at startup. After: connects.
Not tested on macOS or Windows hosts.
No test added: this is feature wiring, and #63577 notes that nothing in
Zed's graph enables `util/debug-embed`, so CI can't exercise it.
## 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 adheres to Zed's UI standards
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
---
Release Notes:
- N/A
# Objective
Fixes#63869 — "Copy Value" in the debugger panel copied the trimmed
display preview instead of the full value.
## Solution
Evaluate the variable's `evaluateName` via the DAP `evaluate` request
using the `clipboard` context (falling back to `variables`), and copy
the returned result. Falls back to the display value if evaluation
fails.
## Testing
- `cargo test -p debugger_ui --lib` passes.
---
Release Notes:
- Fixed debugger "Copy Value" copying the truncated preview instead of
the full value.
Before, `GlobalWatcher` held a native backend and a polling backend, and
its methods dispatched to one or the other.
After this PR, we have `OsWatcher` which wraps a single watch backend,
and `RealFs` and `FakeFs` hold a native `OsWatcher` and a polling
`OsWatcher`.
This moves the split closer to the place where we decide which backend
should be used, and lets us clean up some indirection. A nice bonus is
that the "is this backend recursive" data exists in exactly one place.
Release Notes:
- N/A
This is a pure refactoring PR to make our FS watching code more
testable. It does two things:
1. Make the `GlobalWatcher` be owned by `RealFs` or `FakeFs` instead of
being a process-wide global.
2. Delete `FakeWatcher` and its `Watcher` implementation, and instead
mock the notify crate itself in `FakeFs` tests via the existing
`WatchBackend` trait. Now all our FS watching code down to the notify
boundary runs the same in tests and production.
Release Notes:
- N/A
# Objective
This removes an extra filesystem metadata lookup from
`RealFs::metadata`.
On Unix, `is_executable` performs a full extra stat(2)
(std::fs::metadata) just to read `mode() & 0o111`, even though the mode
bits are already present in the `symlink_metadata` result. On Windows
its impl also starts with `self.exists()`, another stat. This costs one
extra syscall plus one extra executor spawn per entry during the
worktree background scan - I experienced millions of avoidable syscalls
on a large monorepo.
## 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 adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
---
Release Notes:
- Improved worktree scan performance by removing redundant executable
checks.
---------
Co-authored-by: Hamish <hamishc@users.noreply.github.com>
Closes#63620
Count Agent Skill descriptions by Unicode characters rather than UTF-8
bytes across validation, load-time warnings, the skill creator counter,
and imported-description truncation. This aligns Zed with the Agent
Skills specification and reference validator.
Testing:
- `cargo test -p agent_skills --lib`
- `cargo test -p settings_ui --lib`
- `cargo build -p zed`
- Manually verified that a 342-character CJK description containing
1,026 UTF-8 bytes can be saved successfully
- `./script/clippy -p agent_skills -p settings_ui --tests` is blocked by
an unrelated existing `clippy::drain_collect` diagnostic in
`crates/rpc/src/proto_client.rs`
<img width="667" height="210" alt="Successfully created Unicode test
skill"
src="https://github.com/user-attachments/assets/2427fce2-9f0f-462d-959f-e08ad160b3d1"
/>
Confirmed that the Codex agent panel discovers the skill and exposes it
in command completion without a length warning.
<img width="953" height="367" alt="image"
src="https://github.com/user-attachments/assets/8c100197-2e6e-4627-b3f8-862bdfaac918"
/>
Release Notes:
- Fixed valid multibyte Agent Skill descriptions being rejected for
exceeding a byte-based length limit.
---------
Co-authored-by: Chara <249489759+cmdr-chara@users.noreply.github.com>
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
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>
Previously, on macOS, we set the [WatchRoot
flag](https://developer.apple.com/documentation/coreservices/kfseventstreamcreateflagwatchroot)
to `FsEventStreamCreate` (via our notify fork). This causes the
resulting stream to produce an event when any segment of the watched
path changes due to a rename (e.g. `/Users/cole/zed/crates/gpui` ->
`/Users/cole/zed2/crates/gpui`).
The cost of this is that for each path we watch on macOS, we hold open
file descriptors for that path and all its parents (and these are not
deduplicated across watched paths).
This especially blows up our file descriptor usage with non-visible
worktrees from GTD jumps:
```
zed 25415 cole 721r REG 1,14 99826 168007500 /Users/cole/.rustup/toolchains/1.97.1-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/option.rs
zed 25415 cole 722r DIR 1,14 2432 168008864 /Users/cole/.rustup/toolchains/1.97.1-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src
zed 25415 cole 723r DIR 1,14 128 168008863 /Users/cole/.rustup/toolchains/1.97.1-aarch64-apple-darwin/lib/rustlib/src/rust/library/core
zed 25415 cole 724r DIR 1,14 928 168008723 /Users/cole/.rustup/toolchains/1.97.1-aarch64-apple-darwin/lib/rustlib/src/rust/library
zed 25415 cole 725r DIR 1,14 128 168008722 /Users/cole/.rustup/toolchains/1.97.1-aarch64-apple-darwin/lib/rustlib/src/rust
zed 25415 cole 726r DIR 1,14 96 168008721 /Users/cole/.rustup/toolchains/1.97.1-aarch64-apple-darwin/lib/rustlib/src
zed 25415 cole 727r DIR 1,14 704 168004176 /Users/cole/.rustup/toolchains/1.97.1-aarch64-apple-darwin/lib/rustlib
zed 25415 cole 728r DIR 1,14 256 168004175 /Users/cole/.rustup/toolchains/1.97.1-aarch64-apple-darwin/lib
zed 25415 cole 729r DIR 1,14 224 168004164 /Users/cole/.rustup/toolchains/1.97.1-aarch64-apple-darwin
zed 25415 cole 730r DIR 1,14 576 292591 /Users/cole/.rustup/toolchains
zed 25415 cole 731r DIR 1,14 224 292480 /Users/cole/.rustup
zed 25415 cole 732r DIR 1,14 8576 43880 /Users/cole
zed 25415 cole 733r DIR 1,14 160 18221 /Users
```
This PR bumps our notify dependency so that we no longer set that flag,
and instead just wake the background scanner loop every 5s to check
whether the worktree root path has changed. Note that both before and
after this change, the process_events arm of that loop checks the
worktree root path in the same way before doing anything else, so there
is no risk of descending further into the background scanner code with
an incorrect root path.
Release Notes:
- Made Zed use fewer file descriptors on macOS.
> **AI disclosure:** I used Claude (Anthropic) to write this change, its
test, and draft this PR description, after my own app shipped a
rendering bug that only reproduces on 1x displays — every test built on
gpui's test platform renders at a fixed 2.0 scale factor, so the bug was
invisible to an otherwise thorough offscreen test suite. I reviewed the
diff, ran the tests myself, and understand and take responsibility for
the change, per Zed's [AI
Policy](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#ai-policy).
# Objective
- `TestWindow::scale_factor()` has returned a hardcoded `2.0` since the
test platform's inception (`f5ba22659`, 2024-01-03), with no way for a
test to change it. Rendering behavior that only appears on 1x displays —
device-grid rounding, pixel snapping — can't be exercised: an error of
one device pixel is the full width of a hairline at 1x and half a
logical pixel at 2x, so this class of bug passes every test and ships.
- `Window::set_scale_factor` (`547a6cd59`, #61348, 2025-08) exists but
doesn't cover this: it overrides only the `Window`'s cached copy, so the
next `bounds_changed` re-reads the platform window and silently reverts
the override to 2.0.
## Solution
- Back the value with `TestWindowState`, still defaulting to `2.0`, so
existing tests are unchanged.
- Add `TestWindow::simulate_scale_factor_change`, which stores the new
factor and reports it through the resize callback — the same channel a
real DPI change reaches the window on — plus the matching wrappers on
`TestAppContext`, `VisualTestContext`, and `TestAppWindow`, mirroring
the `simulate_resize` family.
- Because the platform window owns the value, it survives later bounds
changes (unlike `set_scale_factor`); the test pins that.
## Testing
- Added `test_simulate_scale_factor_change` in
`crates/gpui/src/window.rs`: asserts the unchanged 2.0 default, that a
simulated change propagates to `Window::scale_factor()` through the
resize callback, and that it persists across a subsequent
`simulate_resize`.
- Reverting `scale_factor()` to the hardcoded `2.0` while keeping the
test reproduces the failure (see Showcase), so the test pins the
behaviour rather than passing vacuously.
- `cargo test -p gpui --lib` — 299 passed, 0 failed:
```
running 299 tests
...
test window::tests::test_simulate_scale_factor_change ... ok
...
test result: ok. 299 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.23s
```
- Tested on macOS. The change is confined to test-support code
(`platform/test/`, test contexts), which is platform independent, but I
have not built the Linux or Windows platform crates.
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [ ] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
## Showcase
Before — the new test against the hardcoded `2.0`:
```
thread 'window::tests::test_simulate_scale_factor_change' panicked at crates/gpui/src/window.rs:7230:9:
assertion `left == right` failed
left: 2.0
right: 1.0
test result: FAILED. 0 passed; 1 failed
```
After — see the full run above.
Release Notes:
- N/A
---------
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
Closes#63725
Honor `preview_tabs.enable_preview_from_project_panel` when opening
files with `project_panel::Open` (Space), matching the setting-aware
single-click behavior. Add a regression test covering previews disabled.
Testing:
- `cargo test -p project_panel --lib`
- `cargo build -p zed`
- Manually verified with the setting disabled that Space opens two
permanent tabs
- Manually verified with the setting enabled that Space replaces the
previous preview tab
- `./script/clippy -p project_panel --tests` is blocked by an unrelated
existing `clippy::drain_collect` diagnostic in
`crates/rpc/src/proto_client.rs`
Manual verification:
Preview disabled — Space opens both files as permanent tabs:
<img width="1920" height="1032" alt="Preview disabled: Space opens both
files as permanent tabs"
src="https://github.com/user-attachments/assets/4b772aad-031e-401d-ac85-5a7f82a74bc1"
/>
Preview enabled — Space replaces the previous preview tab:
<img width="1920" height="1032" alt="Preview enabled: Space replaces the
previous preview tab"
src="https://github.com/user-attachments/assets/cfef36af-0cd5-4522-99b5-99d3bb2c3f9d"
/>
Release Notes:
- Fixed the project panel preview-tab setting being ignored when opening
files with the keyboard.
---------
Co-authored-by: Chara <249489759+cmdr-chara@users.noreply.github.com>
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
# Objective
Fixes#63774
## Solution
On Wayland, the IME remained enabled when the focused control did not
register a text input handler. This allowed IME to intercept ABC...Z
character shortcuts in non-text UI, including the Project Panel outside
filename editing. However Windows 11 doesn't have this problem.
Fixed it by treating a missing input handler as not accepting text
input.
## Testing
I've tested on both Vim/Helix mode and normal mode with Vim/Helix
keybinds. All clear now.
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
## Showcase
[after.webm](https://github.com/user-attachments/assets/91743d71-05c5-49d2-b032-4aebc8e91496)
ABC...Z character shortcuts are not going to be intercepted by IME
unexpectedly in project panel now.
---
Release Notes:
- Fixed IME remains enabled in project panel without a focused text
input in Wayland #63774
Follow up on #63775: disabling WebKit touch callouts did not fully
prevent iOS Safari from selecting the entire GPUI canvas alongside the
app's text selection.
Remote Web Inspector captured a viewport-sized DOM range containing the
canvas while its computed `-webkit-touch-callout` was `none` but
`-webkit-user-select` was `text`. Set both standard and WebKit-prefixed
`user-select` to `none` directly when GPUI creates the canvas, without
relying on the embedding application's stylesheet. This leaves
touch-event handling and the hidden IME textarea unchanged.
Closes DEL-2016
## Validation
- On an iPhone, injecting ordinary inline `-webkit-user-select: none`
prevented the issue; switching it back to `text` reproduced it. No
`!important` was necessary. This tested the equivalent runtime style,
not a rebuilt app containing this commit.
- `cargo fmt -p gpui_web -- --check` passed.
- `RUSTC_BOOTSTRAP=1 cargo check -p gpui_web --target
wasm32-unknown-unknown` passed. The bootstrap flag is required by the
existing `wasm_thread` dependency.
Release Notes:
- Fixed unintended whole-canvas selection when long-pressing in GPUI web
applications on iOS.