When truncating text at the end with an ellipsis, the truncation point
can land right after a space or punctuation character, producing results
like `"some text …"` or `"some text-…"`.
This trims trailing whitespace and ASCII punctuation from the truncated
prefix before appending the ellipsis affix, so you get clean results
like `"some text…"` instead.
Release Notes:
- Improved text truncation to avoid trailing spaces or punctuation
before the ellipsis.
The `draw()` method was calling `.pop()` on `next_frame.input_handlers`
to extract the active input handler for the platform window. This
reduced the Vec length, making cached `paint_range` indices stale. On
the next frame, when a cached view called `reuse_paint()`, it would
index into `rendered_frame.input_handlers` with out-of-bounds indices,
causing a panic: `range start index 1 out of range for slice of length
0`.
**Fix:** Use `.last_mut().and_then(|h| h.take())` instead of `.pop()` to
extract the handler without changing the Vec length. The slot becomes
`None`, which is already handled by `reuse_paint`'s `.take()` logic.
Closes#50456
### Testing
- All 83 existing GPUI unit tests pass
- Manually verified with
[longbridge/gpui-component](https://github.com/longbridge/gpui-component)
`cargo run --example dock`:
- Switch to Input panel → type text → no crash ✅
- Before fix: crash immediately on first keystroke
---
Before you mark this PR as ready for review, make sure that you have:
- [x] Added a solid test coverage and/or screenshots from doing manual
testing
- [x] Done a self-review taking into account security and performance
aspects
- [ ] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
*(N/A — no UI changes)*
Release Notes:
- Fixed a crash in GPUI when typing into an Input widget inside a cached
view ([#50456](https://github.com/zed-industries/zed/issues/50456))
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
## Summary
Fixes scroll position inversion when content height changes during a
scrollbar drag (e.g. in the agent panel while streaming responses).
### Root cause
`set_offset_from_scrollbar` used the **live** `items.summary().height`
for `content_height` and computed a `drag_offset` correction (`live −
frozen`). However, `max_offset_for_scrollbar()` → `max_scroll_offset()`
already uses the **frozen** `scrollbar_drag_start_height` during drag.
This mismatch means:
1. The scrollbar computes thumb position using the frozen height range
2. `set_offset_from_scrollbar` converts that position using the live
height range
3. As content grows during drag, `drag_offset` increases
4. `(point.y - drag_offset).abs()` produces an incorrectly large value,
overshooting `scroll_max`
The `.abs()` call also masked the sign convention:
`compute_click_offset` (in `scrollbar.rs`) returns `-max_offset *
percentage` — a negative value — but `.abs()` converted it regardless of
sign, hiding the mismatch.
### Fix
- Use `scrollbar_drag_start_height` (frozen during drag) for
`content_height` in `set_offset_from_scrollbar`, matching
`max_scroll_offset()`. Both sides of the scrollbar mapping now use the
same height reference.
- Replace `(point.y - drag_offset).abs()` with `(-point.y)` to correctly
convert the negative scroll offset.
- Remove the symmetric `drag_offset` from
`scroll_px_offset_for_scrollbar`, which reported position back to the
scrollbar and suffered from the same inconsistency.
- Update existing tests to use negative offsets, matching the actual
values produced by the scrollbar component.
- Add a regression test that freezes height, grows content, drags the
scrollbar, and asserts the position is correct.
## Test plan
- [x] Existing tests updated to use correct sign convention (negative
offsets)
- [x] New regression test `test_scrollbar_drag_with_growing_content`:
verifies scroll position remains correct when content grows during a
scrollbar drag
- [ ] Manual: open agent panel, start a long generation, drag scrollbar
while content is streaming — position should track the thumb correctly
Release Notes:
- Fixed scrollbar position jumping or inverting when content height
changes during a scrollbar drag (e.g. in the agent panel while
streaming).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
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)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes#56314
When running `cargo run -p gpui --example hello_world` on a wayland
linux machine, the code panics and give the following output:
```
thread 'main' (15645) panicked at crates/gpui_linux/src/linux.rs:55:14:
internal error: entered unreachable code
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
and the related code is:
c8f09caee4/crates/gpui_linux/src/linux.rs (L29-L57)
`gpui::guess_compositor()` correctly returns `"Wayland"`, but the
corresponding match arm is gated behind `#[cfg(feature = "wayland")]` in
`gpui_linux`, and that feature is never enabled. During the extract
gpui_platform refactor (#49277), the `wayland` and `x11` features were
added to the `zed` binary's dependency on `gpui_platform`, but were
missed in the `[dev-dependencies]` of `gpui/Cargo.toml`, which is what
the examples use.
This PR adds the missing features to the dev-dependency so that
`gpui_platform` propagates them to `gpui_linux` when building examples.
Release Notes:
- N/A or Added/Fixed/Improved ...
Co-authored-by: Kunall Banerjee <hey@kimchiii.space>
## Summary
Expose whether the scrollbar is currently being dragged via a new
`is_scrollbar_dragging()` method on `ListState`.
This returns `true` between `scrollbar_drag_started()` and
`scrollbar_drag_ended()` calls. It lets consumers distinguish scrollbar
drags from wheel/trackpad scrolls, which is useful for suppressing
auto-scroll or follow-tail behavior during manual scrollbar positioning.
## Test plan
- [x] Code review — one-liner accessor, delegates to existing
`scrollbar_drag_start_height` field
- [ ] Verify compilation with `cargo check -p gpui`
Release Notes:
- N/A
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The function is unsound due to the classic fact that one can leak tasks,
sidestepping the blocking drop behavior resulting in a use after free.
Release Notes:
- N/A or Added/Fixed/Improved ...
## Problem
A Sentry-reported crash on Windows (Intel Iris Xe Graphics, v1.0.1):
```
index out of bounds: the len is 1 but the index is 1
```
panicking at `DirectXAtlasState::texture` in
[`crates/gpui_windows/src/directx_atlas.rs`](https://github.com/zed-industries/zed/blob/main/crates/gpui_windows/src/directx_atlas.rs):
```rust
AtlasTextureKind::Subpixel => {
&self.subpixel_textures[id.index as usize].as_ref().unwrap()
}
```
## Root cause
After a GPU device-lost recovery, GPUI's view cache replays stale
`AtlasTile`
references from the previous frame's `paint_operations` via
`Scene::replay`.
1. **Atlas grows past one texture.** A long enough session pushes
`subpixel_textures.textures.len() ≥ 2` (easy on Iris Xe at the default
1024×1024 atlas size). Top-level views in Zed use `cached(...)`, so
their
`AnyViewState.paint_range` records into
`rendered_frame.scene.paint_operations`,
referencing both index `0` and index `1`.
2. **Device lost.** `handle_device_lost` clears every `AtlasTextureList`
(`textures.len() == 0`)
and `tiles_by_key`, then sets `skip_draws = true`.
3. **`WM_GPUI_FORCE_UPDATE_WINDOW` arrives.** `mark_drawable()` flips
`skip_draws` back to `false` and `request_frame` runs with
`force_render: true`.
4. **The cache hit.** Inside `Window::draw`, `AnyView::prepaint`'s cache
check (`!dirty_views.contains(...) && !window.refreshing`) succeeds for
every cached view because the recovery doesn't touch invalidator state
and
`force_render` doesn't propagate into `Window`. `AnyView::paint` calls
`window.reuse_paint` → `Scene::replay` → `primitive.clone()`, which
(since
`SubpixelSprite`/`AtlasTile` are `Copy`) verbatim copies a
`Primitive::SubpixelSprite { tile: { texture_id: { index: 1, ... }, ...
} }`
into `next_frame.scene`.
5. **Atlas regrows to one.** Dirty/uncached parts of the same frame
(caret, animations, anything that called `cx.notify`) fall through to
`paint_glyph` → `get_or_insert_with` → `push_texture`, growing
`subpixel_textures.textures` from `0` to **`1`** with index `0` valid.
6. **Panic.** After `mem::swap`, `rendered_frame.scene` contains a mix
of
fresh `index = 0` and replayed `index = 1` sprites. `Scene::batches`
emits separate batches per `texture_id`; the `index = 1` batch reaches
`atlas.get_texture_view` → `subpixel_textures[1]` → panic with
`len = 1, index = 1`.
The two earlier related fixes do not catch this:
- **#52389 / dbd95ea7** (`if force_render { mark_drawable }`) protects
the
200 ms recovery sleep — pending `WM_PAINT`s carry `force_render = false`
and so do not clear `skip_draws`. But `WM_GPUI_FORCE_UPDATE_WINDOW`
carries `force_render = true`, so `mark_drawable` runs, then
`Window::draw`'s `reuse_paint` still reproduces stale tiles.
- The unmerged Windows draft `2e5d890e37`
(`force_render_after_recovery`)
similarly only forces the forced-render branch — it doesn't bypass the
view cache.
## Fix
Two parts:
**1. Bypass the view cache on a forced draw (cross-platform).**
In the platform-agnostic `request_frame` closure in `Window::new`, call
`window.refresh()` whenever `RequestFrameOptions::force_render` is
`true`.
`Window::refresh` is the documented escape hatch for cached views (per
the
`AnyView::cached` docs: *"The one exception is when [Window::refresh] is
called, in which case caching is ignored."*). With `refreshing = true`
every `AnyView::prepaint` cache check fails, every cached view fully
repaints, and `paint_glyph` allocates fresh tiles for every glyph, so
`rendered_frame.scene` ends up free of stale `AtlasTile`s.
**2. Add the `force_render_after_recovery` flag on Windows.**
Mirror the Linux fix from #52389: a per-window `Cell<bool>` set after
`WindowsWindowInner::handle_device_lost` succeeds and consumed at the
top
of `draw_window`. Together with the GPUI change above, the first frame
after recovery (whether a stray `WM_PAINT` during the 200 ms recovery
sleep or the explicit `WM_GPUI_FORCE_UPDATE_WINDOW`) is treated as a
forced render that both clears `skip_draws` and bypasses the view cache.
## Testing
- `script/clippy -p gpui` is clean.
- I do not have a Windows toolchain available locally, so I have not
cross-compiled `gpui_windows`. Reviewers with Windows access — please
smoke-test on a machine where the device-lost path can be exercised
(Intel iGPU, suspend/resume, or running a TDR-inducing test on a GPU
driver).
## Related
- Sentry issue ID 7457971403 (DirectX subpixel atlas crash, Intel Iris
Xe).
- Builds on / fixes the residual gap in #52389 (`gpui_linux: Force scene
rebuild after GPU device recovery"). The GPUI change here also hardens
the corresponding Linux path against the same `reuse_paint` mechanism.
Release Notes:
- Fixed a crash on Windows when the GPU device is lost and recovered
during use (typically driver crash, suspend/resume, or display
reconfiguration, most commonly on Intel iGPUs)
Before this PR, `ListState` always used a proportional pending scroll
fraction to preserve the relative scroll position when list items were
resized. This caused text to creep in the agent panel when the scrollbar
was in the middle of the list while the agent was generating text,
because streaming updates changed the logical scroll top slightly.
This PR fixes that behavior by using an absolute offset when only a
subset of items is remeasured, added, or deleted, keeping the logical
top position stable.
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes #ISSUE
Release Notes:
- N/A
---------
Co-authored-by: Remco Smits <djsmits12@gmail.com>
Closes#52292
This PR preserves the scroll offset of the List used in Recent Projects
while deleting items. It does that by:
- Adding the `is_scrolled_to_end` method to the GPUI list, which helps
us determine where the new selection should be, since it depends on
whether items are taking the deleted slot from below or above.
- Adding `ScrollBehavior` to `update_matches`, which lets you preserve
the scroll offset even for `List` (not `UniformList`) after a reset.
Before:
https://github.com/user-attachments/assets/e3eb7092-59ec-4b54-b57a-503555addd27
After:
https://github.com/user-attachments/assets/6929f6a0-04d7-44f9-a9b2-f9e5c077b368
Release Notes:
- Fixed the recent projects list jumping to the top after deleting a
project, so you can now bulk-delete entries by repeatedly clicking the
delete icon or pressing the keybind.
Fixes ZED-5K1
Release Notes:
- Fixed a panic on windows when a monitor disappears from windows
monitor enumeration
---------
Co-authored-by: John Tur <john-tur@outlook.com>
Adds 4 (technically 5) new tools to the zed agent, corresponding to LSP
actions:
- `find_references`
- `goto_definition`
- `rename_symbol`
- `get_code_actions` and `apply_code_actions`
Notes:
- `rename_symbol` skips doing a `prepare_rename`. If there is nothing to
rename at the position, it will forward the error to the agent
- The code action tools are stateful. The state is stored in the
`get_code_actions` tool itself as a `PendingCodeActions`. It is not
passed into/out of subagents. Calling `apply_code_actions` without
calling `get_code_actions` first is an error, but I've never seen an
agent do this
Symbols are identified by:
- file name
- line number
- symbol
If there is no substring match on that line for the symbol text, it is
an error. If there are multiple, it chooses the first. This may not be
great if you have a line like: `fn convert(x: foo::Something) ->
bar::Something` - the second `Something` is a different symbol, but is
inacessible to these tools. Probably fine for now, but we can look into
improving
Release Notes:
- Added: New tools for the Zed Agent for interacting with language
servers
---------
Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
Instead of manually handing hiding the cursor on keyboard input at the
editor level, GPUI will now take care of it.
This makes it significantly easier to handle the edge cases, and allows
delegating the cursor restoration to the platform itself in the macOS
case. On Linux and Windows, we still have to restore the cursor on
movement ourselves, but this now happens at the platform-specific level.
Bugs fixed by this change:
- No cursor when "Unsaved edits" prompt appears
- Cursor disappears when clicking a panel button if it contains a search
bar (e.g. collab panel)
### Setting rename
The `hide_mouse` setting value `"on_typing_and_movement"` has been
renamed to `"on_typing_and_action"` to better reflect what it actually
does — it hides the cursor when a keystroke resolves to an action (e.g.
cursor movement, deletion). Existing settings are migrated
automatically.
### Tested platforms
- [x] macOS
- [x] Wayland
- [x] X11
- [x] Windows
- [x] Web
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)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- Renamed the `hide_mouse` setting value `on_typing_and_movement` to
`on_typing_and_action` to better describe its behavior (existing
settings are auto-migrated)
- Fixed a few situations where the mouse cursor would be incorrectly
hidden
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes #ISSUE
Release Notes:
- N/A
---------
Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
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)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- N/A
Closes#53202
`Anchored::prepaint` computes the bounding box of its children to
determine the size used for fitting the anchored element in the window.
Previously, this calculation manually tracked the minimum origin and
maximum bottom-right point, initializing the maximum point to `(0, 0)`.
If child bounds were in negative coordinates, the maximum point could be
clamped to `(0, 0)`, inflating the computed size.
This replaces the manual min/max accumulation with `Bounds::union`,
starting from the actual child bounds instead of sentinel values. This
computes the child bounding box correctly regardless of coordinate sign.
---
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:
- Fixed a bug where the context menu in the agent panel (and other
scrollable surfaces) would appear at the wrong location
Apple's text rendering stack dilates glyph outlines for text rendered
with a light foreground color. Zed doesn't consider this nuance today;
we populate our atlas using glyphs rendered with a dark foreground
color. This means that, particularly in dark themes, text in Zed looks
thin and blurry, and doesn't match the look of native macOS
applications.
This pull request replicates the native behavior of Core Graphics. Some
reverse-engineering revealed that CG computes the foreground color
luminance using the Rec. 709 formula ($Y=0.2126R + 0.7152B + 0.0722G$)
and quantizes it into five levels (0, 0.25, 0.5, 0.75, and 1). Each
level uses a different dilation factor.
With this patch, we calculate this same luminance bucket and supply it
as the foreground color during rasterization. The correct dilation will
be applied, and we'll store this glyph in the atlas keyed by this
luminance bucket. So, we'll generate and use up to 5 different bitmaps
for each glyph based on its foreground color.
I've confirmed that text rendered by Zed now exactly matches native
applications like Safari, TextEdit, etc.
Release Notes:
- Improved text rendering clarity on macOS, particularly in dark themes.
---------
Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
#48029 introduced `set_document_path` which is frequently (we are also
working on a PR to make it less frequent) called as tabs and panes
update. Apparently, the AppKit function it uses
(`NSWindow::setRepresentedFilename`) can cause the current cursor style
to be reset, producing flicker as the cursor would change to `Arrow`
temporarily until the right cursor style is set again in the next frame.
This PR reworks how we set the cursor to use AppKit's
`resetCursorRects`, giving us a chance to re-set the cursor when the OS
decides to invalidate it.
Finally, it fixes a separate bug introduced in #50827 where moving the
mouse while typing in an editor would cause to the cursor to flicker
between `None` (hidden) -> `Arrow` -> `IBeam`. This was caused by
incorrectly resetting the cursor to `Arrow` when the last input came
from the keyboard.
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)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- N/A
---------
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
This allows us to move entities between windows without breaking all the
callbacks.
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:
- N/A
This fixes an issue where due to the scrollbar appearing, the reported
content size would shift, causing issues in the process. We now actually
always reserve space for the scrollbar appropriately as described in
https://github.com/zed-industries/zed/pull/33636 initially.
Release Notes:
- Fixed an issue where the scrollbar could cause a layout shift in the
terminal.
---------
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Clamp the cached `frame_index` of an `img` element when the underlying
image changes so a stale index from a previous, longer animation cannot
index out of bounds on the new image.
Release Notes:
- Fixed a panic when a GIF was replaced with one that had fewer frames
in Markdown Preview.
This reduces the size of Sharedstring from 32 bytes to 24 while also
allowing for small-string optimization, meaning strings with length < 23
bytes will not actually allocate.
Release Notes:
- N/A or Added/Fixed/Improved ...
Painting primitives at non-integer pixel coordinates produces blurry
output. Pixel snapping converts layout coordinates into integer
device-pixel coordinates so painted edges land exactly on physical pixel
boundaries.
Non-integer coordinates can arise for several reasons, including:
- flex distribution, percentages, centering, and text measurement can
produce fractional element sizes and positions;
- at fractional scale factors (for example 125% or 150%), integer
logical-pixel values can map to non-integer device-pixel values.
We pixel-snap by rounding in device-pixel space, after multiplying by
`scale_factor`, so that snapping targets physical pixels. Bounds are
divided by `scale_factor` before being returned to GPUI.
Midpoints are rounded toward zero. This is a stylistic choice: a
1-logical-pixel line at 150% scale should render as 1 dp rather than 2
dp.
Pixel snapping is done in two phases:
1. Pre-layout metric snapping. Before Taffy computes layout, all
authored absolute lengths are rounded in `to_taffy`. This includes
borders, padding, gaps, and explicit sizes. Custom-measured leaf nodes
have their measured sizes rounded up to integer device-pixel lengths.
2. Post-layout edge snapping. After Taffy resolves the tree, layout
relationships such as flex shares, grid tracks, percentages, and
centering can produce new fractional edge positions. Boxes now have
edges in absolute coordinates, and snapping must decide where those
edges land on the device-pixel grid.
Ideally, post-layout snapping would satisfy:
- Edge closure. Two raw layout edges at the same absolute position
should snap to the same pixel column.
- Translation stability. A component's internal geometry should not
change when it moves to a new absolute position.
These goals are in tension because rounding is not associative. The
simple local schemes make different tradeoffs:
- Absolute edge rounding gives each window coordinate one answer, so
coincident edges always close globally. But a span's snapped length is
`round(far) - round(near)`, which may change by 1 dp as its absolute
origin moves.
- Parent-relative edge rounding rounds each child inside its parent's
coordinate space. This guarantees translation stability, but a shared
edge reached through different parents can accumulate different
rounding, causing non-closure between cousins.
- Length rounding rounds each width, height, and thickness independently
and then places boxes from those rounded lengths. Sizes stay stable
under translation, but neighboring boxes derive their shared boundary
from different sources, so closure is not guaranteed.
We apply absolute edge rounding for each element's outer box in
post-layout rounding to preserve closure. Border and padding widths are
not touched by post-layout rounding; they keep their pre-layout rounded
value so that they remain stable under translation.
This gives both closure and translation stability in the case that all
local metrics are integer device-pixel lengths. Pre-layout rounding
covers that in most cases. The exception is metrics resolved by layout
relationships, such as percentages. Outer box edges will still close
globally, and painted border widths are still snapped independently, but
the raw content-box origin can carry a 1 dp residual into descendants.
---
Fixes https://github.com/zed-industries/zed/issues/46360
Fixes https://github.com/zed-industries/zed/issues/44528
Fixes https://github.com/zed-industries/zed/issues/40282
Fixes https://github.com/zed-industries/zed/issues/42257
---
Release Notes:
- Fixed potentially blurry appearance of UI elements when using
fractional display scaling.
This sets the accessibility document property (AXDocument) of the
window, which other apps can use to understand what file the current
window represents. Document-based apps on macOS are generally expected
to set this property.
The document path is set via `Window::set_document_path()` which calls
`setRepresentedFilename:` on the underlying NSWindow. The workspace
updates this path in `update_window_title()` whenever the active item or
project structure changes. For tests, instead of trying to somehow
assemble a proper NSWindow, we store the document path on the window
mock used for testing and check its value.
Motivation: I am the developer of Timing, an automatic time tracking app
for Mac. Timing uses the `AXDocument` property (a standard property of
most document-based app windows on macOS) to understand what document
the user is working on. With this change, Timing is able to understand
which directory the user is working in. Without this, Timing would only
record the window title, i.e. the filename without information about the
containing directory. I've had several users ask for better support for
Zed.
Here's a screenshot of the macOS Accessibility Inspector showing the
`AXDocument` property with this change. The UI of Zed itself does not
change. However, in my dev build of Zed, the traffic lights are a bit
too large and misaligned. However, this happens even when building
`main`, so I assume it’s unrelated to my changes.
<img width="1370" height="1162" alt="Screenshot 2026-01-30 at 16 18 25"
src="https://github.com/user-attachments/assets/dc260252-91fb-41e1-97a9-e6fb843c6a70"
/>
Release Notes:
- Set the represented filename property of windows on macOS
---------
Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
No, sadly, the title is not a typo. See
https://www.githubstatus.com/incidents/zsg1lk7w13cf for the context.
I'll read with joy and popcorn through that root cause analysis.
It makes literally zero sense what happened here, but for some completly
bonkers reason GitHub completely messed up the merge queue with
https://github.com/zed-industries/zed/pull/54632.
I have no idea how it happened. It makes literally zero sense. A PR
going into the merge queue should have the same LoC when getting out of
it. GitHub obviously does not check this. GitHub causes extra work with
a feature that is supposed to save time.
Thanks, I guess.
Release Notes:
- N/A
---------
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Needs https://github.com/zed-industries/zed/pull/54635 for the profile
overrides added into default settings json to work.
Part of https://github.com/zed-industries/zed/issues/48968
Another part of the fix related seems to be
https://github.com/zed-industries/zed/pull/45669 ?
Using the steps from the issue and profiling on macOs had shown that Zed
has 2 memory "leaks" in play when a certain file is being rewritten a
lot of times.
* First, the thread profiler registers a lot of tasks' data and fills
its buffer to the limit:
<img width="3456" height="2158" alt="image"
src="https://github.com/user-attachments/assets/f183312d-4389-4072-8915-d54e60419b08"
/>
* Second, if the buffer gets open, the undo history fragments start to
creep up infinitely:
<img width="3456" height="2158" alt="image"
src="https://github.com/user-attachments/assets/61a2b66b-81fd-4973-9c3c-c339f886d9b2"
/>
The PR aims to solve the first issue by disabling the profiling by
default, yet leaving the way to turn in on quickly with settings.
The memory usage profiling shows that the memory usage is now
dynamically affected by the new setting:
<img width="2032" height="1136" alt="image"
src="https://github.com/user-attachments/assets/8a6c76b9-6fb7-44bc-ac1d-3c34afe7c575"
/>
While the test directory being thrashed with the script from the issue,
* first, Zed starts with the profiling disabled
* then gets the profiling enabled which results in the memory growth
close to 1 minute mark of the screenshot
* last, the profiling gets disabled again, releasing all the memory
accumulated
Release Notes:
- Improved Zed's default memory usage
Fixes an inconsistency with the way scrolling interacts with the
`follow_tail` GPUI list feature:
- Scrolling normally sets `follow_state` to `FollowState::Tail {
is_following: false }`, so later scrolling to the bottom re-engages
`follow_tail`
- Scrolling by dragging the scrollbar previously set `follow_state` to
`FollowState::Normal`, preventing `follow_tail` from re-engaging after
scrolling to the bottom
As far as I can tell this behavior was unintentional, despite there
being a test that exercised this exact behavior that `follow_tail`
doesn't re-engage after dragging the scrollbar — my best guess is that
that test was derived from the bugged scrollbar handler logic, and not
based on any actual intent for scrollbar drags to prevent `follow_tail`
from re-engaging.
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:
- Fixed agent panel auto-scroll not re-engaging after interacting with
the scrollbar
---------
Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This PR brings back the button to filter remote branches when accessing
the title bar's branch picker with the mouse. It was unintentionally
removed when we introduced the new worktree picker.
Release Notes:
- N/A
Build split text directly from Arc<str> slices instead of allocating
intermediate Strings. Reuse the original SharedString for full-line
splits, return a static empty string for empty sides, and add a
regression test covering boundary splits.
Release Notes:
- N/A
---------
Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>
## Summary
- Enable VSCode-like side-by-side file comparison in FileDiffView using
SplittableEditor
- Leverages existing SplittableEditor infrastructure that ProjectDiff
already uses
- Controlled by the user's `diff_view_style` setting (split/unified)
## Changes
- `crates/git_ui/src/file_diff_view.rs`: Refactored to use
SplittableEditor instead of plain Editor (+66/-30 lines)
## Test plan
- [ ] Open a file with git changes and verify the diff view renders
correctly
- [ ] Toggle between split and unified diff view styles via settings
- [ ] Verify side-by-side comparison shows old/new content correctly
Release Notes:
- Enabled side by side diffs in the file diff view
Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes#46942
## Problem
The terminal's monospace grid forcing logic in `LineLayoutCache` breaks
rendering of combining marks in complex scripts. Thai, Lao, Arabic,
Hebrew, Myanmar, Khmer, and other scripts use combining characters
(vowels, tone marks, diacritics) that should render on top of or below
their base character — not in a separate cell.
The root cause is in `apply_force_width` within `line_layout.rs`. After
HarfBuzz correctly shapes the text (positioning combining marks at the
same x coordinate as their base character), the force-width loop blindly
increments the cell position counter for **every** glyph — including
zero-advance combining marks. This displaces them into the next cell,
breaking the visual output.
For example, Thai `สวัสดี` renders as `ส ว ั ส ด ี` with vowels and tone
marks scattered across separate cells.
#39526 previously fixed Latin combining characters (like NFD-normalized
é) by handling them at the terminal cell level via
`append_zero_width_chars`. That fix works when alacritty's cell model
already identifies the characters as zero-width. However, many complex
script combining marks still get mispositioned at the shaping layer —
which is what this PR addresses.
## Solution
Extracted `apply_force_width_to_layout()` in `line_layout.rs` that
detects combining marks during the force-width pass by checking whether
a glyph's shaped x position has advanced by at least half a cell width
beyond the previous base character. Combining marks (which HarfBuzz
places at the same x as their base) are kept at their shaped position
relative to the forced base, rather than being pushed to the next cell.
This approach is **script-agnostic** — it works for all complex scripts
(Thai, Arabic, Devanagari, Myanmar, Khmer, etc.) without hardcoding
Unicode ranges or disabling the monospace grid. The fix lives at the
text shaping layer in GPUI, so it benefits any consumer of
`LineLayoutCache` that uses `force_width`.
The two duplicate force-width loops (in `layout_line` and
`layout_line_by_hash`) are consolidated into the single shared helper.
## Testing
**Unit tests** cover the following scenarios:
- Latin text positions remain unchanged
- A single combining mark stays at its base character's cell
- A base character following combining marks gets the correct next cell
- Multiple stacked combining marks all stay anchored to cell 0
- Drifted base positions respect the existing 1px tolerance
**Manual testing** with the following scripts in the integrated
terminal:
| Script | Test text | What to look for |
|--------|-----------|-----------------|
| Thai | `สวัสดีครับ ภาษาไทย ก้อนหิน น้ำตก` | Vowel signs (ั ี) and tone
marks (้) render above/below consonants, not in separate cells |
| Lao | `ສະບາຍດີ ພາສາລາວ` | Vowel signs stay attached to consonants |
| Arabic | `بِسْمِ اللَّهِ الرَّحْمَٰنِ` | Diacritics (harakat) render
on their base letters |
| Hebrew | `בְּרֵאשִׁית בָּרָא אֱלֹהִים` | Vowel points (nikud) stay
under/over their letters |
| Hindi | `नमस्ते हिन्दी भाषा` | Vowel matras render correctly on
consonants |
| Bengali | `বাংলা ভাষা নমস্কার` | Combining marks stay attached |
| Tamil | `தமிழ் மொழி வணக்கம்` | Vowel signs render correctly |
| Myanmar | `မြန်မာဘာသာ မင်္ဂလာပါ` | Combining marks and medial
consonants stay in place |
| Khmer | `ភាសាខ្មែរ សួស្តី` | Subscript consonants and vowel signs
render correctly |
<details>
<summary>Script to reproduce</summary>
```sh
echo "=== Thai ==="
echo "สวัสดีครับ ภาษาไทย ก้อนหิน น้ำตก"
echo ""
echo "=== Lao ==="
echo "ສະບາຍດີ ພາສາລາວ"
echo ""
echo "=== Arabic ==="
echo "بِسْمِ اللَّهِ الرَّحْمَٰنِ"
echo ""
echo "=== Hebrew ==="
echo "בְּרֵאשִׁית בָּרָא אֱלֹהִים"
echo ""
echo "=== Hindi (Devanagari) ==="
echo "नमस्ते हिन्दी भाषा"
echo ""
echo "=== Bengali ==="
echo "বাংলা ভাষা নমস্কার"
echo ""
echo "=== Tamil ==="
echo "தமிழ் மொழி வணக்கம்"
echo ""
echo "=== Myanmar ==="
echo "မြန်မာဘာသာ မင်္ဂလာပါ"
echo ""
echo "=== Khmer ==="
echo "ភាសាខ្មែរ សួស្តី"
```
</details>
### Before
<img width="1144" height="740" alt="Screenshot 2569-04-05 at 10 43 00"
src="https://github.com/user-attachments/assets/bce2075a-9e19-40dd-81ea-0e29a451b781"
/>
### After
<img width="1144" height="747" alt="Screenshot 2569-04-05 at 11 22 55"
src="https://github.com/user-attachments/assets/e0613bf6-8452-4c65-b893-f9931a471b2e"
/>
### Tests passing
<img width="1144" height="740" alt="Screenshot 2569-04-05 at 11 20 22"
src="https://github.com/user-attachments/assets/9100bd77-4ea6-4f77-ae31-5129484552fd"
/>
Release Notes:
- Fixed terminal rendering of combining marks in complex scripts (Thai,
Arabic, Hebrew, Devanagari, Myanmar, Khmer, and others) where vowel
signs and tone marks were incorrectly displaced to adjacent cells
instead of rendering on their base characters.
---------
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
When soft-wrapping text, `LineWrapper::is_word_char` determines which
characters stick to the preceding word at wrap boundaries. The list
already includes trailing punctuation like `,`, `.`, and `:`, but was
missing `;`. As a result, a message like "we won't pass it on; no big
deal" could wrap with `;` isolated at the start of the next line (`on` /
`; no`).
This was most visible in the agent panel message editor but affects any
surface that uses soft-wrapping.
Also adds `assert_word("on;")` to `test_is_word_char` to guard against
regressions.
Release Notes:
- Fixed trailing `;` being wrapped to a new line instead of staying
attached to the preceding word.
Reduces memory usage on Windows by using a newer heap implementation.
Memory usage after opening into the Welcome screen:
- Before: 124 MB
- After: 91 MB
Release Notes:
- N/A
This PR adds support for rendering **Netpbm** image formats (`.pbm`,
`.ppm`, `.pgm`) within Zed's built-in image viewer.
These formats are particularly useful for projects that want minimal
external dependencies, a common scenario in academic environments and
low-level graphics programming.
Since the underlying `image` crate and `GPUI` already provide support
for these codecs, this change explicitly exposes the `Pnm` variant
within `gpui::ImageFormat` by mapping it to `image::ImageFormat::Pnm`.
## Screenshots/Examples
Below is an example of `.pbm`, `.ppm`, and `.pgm` files being rendered
correctly in the image preview (images taken from
https://filesamples.com):
<img width="1917" height="1012" alt="pnm_example"
src="https://github.com/user-attachments/assets/0056133f-908c-4c91-ba9d-53aef0657b05"
/>
Release Notes:
- Added support for PNM image previews (`.pbm`, `.ppm`, `.pgm`).
Adds basic bookmark functionality to the editor, allowing users to mark
lines and later navigate between them. This is an MVP and will later be
expanded with a picker, vim marks integration and syntax tree based
bookmark positions. In this MVP bookmarks shift under external edits.
# UI
## Adding/Removing bookmarks
To add a bookmark:
- run the toggle bookmark action
- hold secondary and click in the gutter
- open the context menu by right clicking in the gutter and select add
bookmark To remove a bookmark:
- run the toggle bookmark action
- click on the bookmarks icon in the gutter
- open the context menu by right clicking in the gutter and select
remove bookmark
remove all bookmarks with `workspace: clear bookmarks`
# Implementation
This mirrors the implementation of breakpoints. The rendering of the
gutter was refactored to make place for bookmark icons and buttons:
- Code was extracted to a `Gutter` struct
- Runnables, breakpoints and bookmarks are now collected ahead of
layouting. Just before layouting we remove the items that collide and do
not have priority.
- The `phantom_breakpoint` is replaced by a `gutter_hover_button`
## In depth phantom breakpoint discussion:
This was phantom_breakpoint. It worked as follows:
- A fake breakpoint was added to the list of breakpoints.
- While rendering the breakpoints it a breakpoint turned out to be fake
it would get a different description and look.
- The breakpoint list was edited run_indicators ("play buttons")
rendering to removes the fake breakpoint if it collided.
This would not scale to more functionality. Now we only render
breakpoints, bookmarks and run indicators. Then we render a button if
there is not breakpoint, bookmark or run indicator already present. We
can do so since the rendering of such "gutter indicators" has been
refactored into two phases:
- collect the items.
- render them if no higher priority item collides.
This is far easier and more readable which enabled me to easily take the
phantom_breakpoint system and use it for placing bookmarks as well :)
Note: this was previously merged but it needed a better squashed commit
message. For the actual PR see: 51404. This reverts commit
7e523a2d2b.
Release Notes:
- Added Bookmarks
Co-authored-by: Austin Cummings <me@austincummings.com>
Closes#4526
Adds basic bookmark functionality to the editor, allowing users to mark
lines and later navigate between them.
### What's new
**Toggling bookmarks**
Users can toggle a bookmark on the current line(s) via the `editor:
toggle bookmark` action. A bookmark icon appears in the gutter for each
bookmarked line.
**Navigation**
Two new actions, `editor: go to next bookmark` and `editor: go to
previous bookmark`, navigate between bookmarks in the current buffer,
wrapping around at the ends of the buffer.
**Viewing all bookmarks**
`editor: view bookmarks` opens all bookmarks across the project in a
multibuffer, similar to how references and diagnostics are surfaced.
**Clearing bookmarks**
`workspace: clear bookmarks` removes all bookmarks in the current
project.
**Persistence**
Bookmarks are persisted to the workspace database and restored when the
workspace is reopened. They are stored as `(path, row)` pairs and
resolved back to text anchors. Out of range or unresolvable bookmarks
are skipped with a logged warning.
**Gutter rendering**
Bookmark icons are rendered in the gutter using the existing gutter
button layout system, consistent with breakpoints. They are suppressed
on lines that already show a breakpoint or phantom breakpoint indicator.
A new `gutter.bookmarks` setting (defaulting to `true`) controls their
visibility.
### What's left
- [x] Lazily load buffers that have bookmarks
- [x] Clean up test boilerplate
- [ ] Assign default keybindings
- [ ] Compare line of saved bookmarks with current buffer (gray out the
"stale" bookmarks)
### What's next (and nice to haves)
- [ ] Resilience against external edits
- [ ] Save column position with the bookmark
- [ ] Bookmarks attached to syntactic structures?
- [ ] Labeled bookmarks?
---
Release Notes:
- Added bookmarks: toggle bookmarks on lines with `editor: toggle
bookmark`, navigate with `editor: go to next bookmark` / `editor: go to
previous bookmark`, view all bookmarks with `editor: view bookmarks`,
and clear with `workspace: clear bookmarks`. Bookmarks are shown in the
gutter and persisted across sessions.
---------
Co-authored-by: Yara <git@yara.blue>
Adds instrumentation to track input-to-frame latency in GPUI windows,
helping diagnose input responsiveness issues.
## What this does
- Records the time between when an input event is dispatched and when
the resulting frame is presented, capturing worst-case latency when
multiple events are coalesced into a single frame.
- Tracks how many input events get coalesced per rendered frame.
- Both metrics are stored in
[HdrHistogram](https://docs.rs/hdrhistogram) instances with 3
significant digits of precision.
- Latency is only recorded when the input event actually causes a redraw
(i.e. marks the window dirty), so idle mouse moves and other no-op
events don't skew the data.
- Adds a `Dump Input Latency Histogram` command that opens a buffer with
a formatted report including percentile breakdowns and visual
distribution bars.
## Example output
The report shows percentile latencies, a bucketed distribution with bar
charts, and a per-frame event coalescing breakdown.
Release Notes:
- N/A
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cole Miller <cole@zed.dev>
### Summary
`#[gpui::property_test]` was non-deterministic because it always used a
random seed with proptest. So it generated different test cases and
scheduler seeds on every run, causing flaky failures. `#[gpui::test]`
doesn't have this problem because it defaults to fixed seeds.
This changes `#[gpui::property_test]` to match: proptest's RNG seed now
defaults to `0` instead of random, so the same cases run every time.
The`$SEED` env var overrides both the scheduler seed and case generation
seed.
Self-Review Checklist:
- [ ] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [ ] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [ ] Performance impact has been considered and is acceptable
Closes #ISSUE
Release Notes:
- N/A or Added/Fixed/Improved ...
---------
Co-authored-by: cameron <cameron.studdstreet@gmail.com>
Closes#53426
Fixes a crash when previewing markdown with GIFs, and enables GIF
animation in the preview panel.
Two issues: a partially-decoded GIF could crash the preview, and GIFs in
markdown previews never animated.
The fix for GIFs with empty comment extensions (`21 fe 00`) was
implemented upstream in the `image-gif` crate (image-rs/image-gif#228)
and released as `gif 0.14.2`. This PR bumps the dependency so those GIFs
now render correctly in the markdown preview without any further changes
to Zed itself.
## Screenshot
Left=VS Code
Right=Zed
https://github.com/user-attachments/assets/7950abbc-1a79-4f01-a425-9595aa688325
Release Notes:
- Fixed a crash in certain scenarios when opening Markdown Preview with
GIFs.
- Added GIF animation support for Markdown Preview.
---------
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>