2e2fb0a21811ce2dbaca5bb738b0be8f94de051e
586
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9552acc2bc |
gpui: Apply EXIF orientation when decoding static images (#61017)
## Context Static images decoded by GPUI ignored their EXIF `Orientation` tag, so JPEGs that rely on it (photos from phones and cameras) rendered rotated or flipped in image preview, Markdown preview, and other `ImageSource` consumers. The `image` crate only exposes orientation through `ImageDecoder::orientation()`, which must be read before the decoder is consumed; `DynamicImage::from_decoder` does not apply it. The old path went straight through `image::load_from_memory_with_format`, so the metadata was never read. Closes #60844. Video of manual test below : [Screencast from 2026-07-15 02-14-41.webm](https://github.com/user-attachments/assets/002b0324-44f8-4ddb-acbd-8c3e070d5248) ## How to Review **Cargo.toml** Raises the workspace `image` dependency minimum from `0.25.1` to `0.25.4`, the first release exposing `orientation()` and `apply_orientation()`. The lockfile already resolves to `0.25.10`, so no lock change is required. **crates/gpui/src/platform.rs** Adds two crate-private helpers. `decode_static_image_from_decoder` reads `orientation()`, decodes to a `DynamicImage`, applies the orientation, then converts RGBA to GPUI's BGRA. `decode_static_image` builds a format decoder from bytes and delegates to it. `Image::to_image_data` now routes its static formats through these helpers instead of `image::load_from_memory_with_format`. Adds `test_image_to_image_data_applies_exif_orientation`, which decodes a 16x32 fixture stored upside down with EXIF Orientation 3 and asserts the corner pixels were flipped upright; the test fails if orientation is dropped. **crates/gpui/src/elements/img.rs** Routes `ImageAssetLoader` (Markdown paths, URLs, embedded resources) through the same helpers, including the static WebP branch. GIF and animated-WebP frame handling are left unchanged, since animated orientation is out of scope. ## 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 is consistent with the UI/UX checklist - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed static images ignoring their EXIF orientation, which caused some JPEGs to render rotated or flipped in image and Markdown preview. |
||
|
|
058f01fa93 |
gpui: Fix clicks on window prompts dismissing popovers behind them (#61136)
I noticed that when Zed renders prompts in-window (Linux, or `use_system_prompts: false`), clicking a prompt button also dismisses the popover behind it. Native macOS dialogs don't have this problem since the click goes to the OS dialog and the window never sees it. For example, in the worktree picker: 1. Delete a dirty worktree. 2. Confirm "Force Delete". 3. Mouse down dismissed the picker, which aborted the confirmed deletion. This PR makes GPUI-rendered prompts behave the same way as native. If someone actually wants to observe mouse downs during a prompt, the raw `window.on_mouse_event` API still sees every event. macOS: https://github.com/user-attachments/assets/9f85622f-c0d1-44f2-83f9-8378d9d7d13b Before GPUI prompt: https://github.com/user-attachments/assets/d1845ba1-9886-4733-8cf6-ab32424416e1 After GPUI prompt: https://github.com/user-attachments/assets/27936aca-8de5-4da8-88be-d88dbc1259d8 Release Notes: - Fixed confirmation dialog buttons dismissing the popover underneath them on click when using Zed-rendered prompts. |
||
|
|
ae3bbdeb2d |
gpui: Avoid redundant group hover redraws (#61118)
Anonymous elements with group hover styles have no persistent hover state. The prepaint mouse handler treated missing state as unhovered and notified the current view for every pointer movement within the group. This made controls such as switches redraw continuously while the pointer moved over them. Let anonymous elements use the existing paint-time hover transition handler. Only elements with persistent hover state now update that state and notify from prepaint. Add regression coverage for entering, moving within, and leaving an anonymous hover group. Signed-off-by: Daan De Meyer <daan@amutable.com> Release Notes: - N/A Signed-off-by: Daan De Meyer <daan@amutable.com> |
||
|
|
b05f40c554 |
Fix post-taffy-bump layouting issues (#61107)
Follow-up to https://github.com/zed-industries/zed/pull/60721 Before: <img width="564" height="470" alt="before" src="https://github.com/user-attachments/assets/d825464d-f8d9-4fa1-bee3-42b5f615d4ba" /> <img width="745" height="221" alt="image" src="https://github.com/user-attachments/assets/18139bec-6778-491a-9aa5-0112bd121723" /> After: <img width="576" height="472" alt="after" src="https://github.com/user-attachments/assets/4c3c37cf-12c4-421e-98e9-cc6e40aa099d" /> <img width="812" height="275" alt="image" src="https://github.com/user-attachments/assets/407e06b4-101f-4984-8d5b-4e33895b5ae9" /> ### Bug 1 — constraint-blind text layout cache (the collapse) `TextLayout`'s measure closure is stateful (each call overwrites the shared element state used for painting), and its cache check served cached sizes to unconstrained probes regardless of the constraints they were computed under: 1. `uniform_list::measure_item` lays an item out at `(MinContent, MinContent)`; the row's `min_w_0` lets it shrink to ~0, so labels get truncated at a tiny width — and that tiny size lands in the element state. 2. The real layout probes the label's flex base size with `available_space.width = MaxContent` (taffy maps Definite → MaxContent). Truncated labels are `whitespace_nowrap`, so both `wrap_width` and `truncate_width` were `None` → the cache check passed → the poisoned tiny size became the flex basis → labels rendered at ~5 chars. **Fix**: record `truncate_width` in `TextLayoutInner` and never serve a cached layout that was computed *with* truncation to an unconstrained probe; the honest intrinsic size is recomputed instead, and the final `PerformLayout` call re-truncates against the real resolved width. ### Bug 2 — truncation width math disagrees with shaping (the ~2-char loss) After fix 1, rows got their full width, but exact-fit strings like `Cargo.toml` still lost ~2 chars: - Sizing measures via `shape_text` (real shaping with kerning); the element gets exactly `ceil(shaped_width)`. - The truncation decision (`LineWrapper::should_truncate_line_middle` etc.) sums **per-character advances** — no kerning — with zero tolerance, overestimating width for some glyph sequences. Text sitting in a box of exactly its own measured width got truncated, losing ~2 chars to fit the `…`. String-dependent (kerning pairs), hence `Cargo.toml` broke while `Cargo.lock` didn't. - Previously masked because taffy 0.10 usually skipped the final measure invocation, so the truncation path rarely ran with an exact-fit width. **Fix**: before truncating (nowrap case, `wrap_width.is_none()`), shape the untruncated text and skip truncation if the *shaped* width fits — matching CSS `text-overflow`, which only activates on actual overflow. Cheap: the shaping result is a `line_layout_cache` hit from the earlier untruncated sizing probe. ### Bug 3 — `MinContent` used instead of `MaxContent` that caused over truncation of certain labels Release Notes: - N/A |
||
|
|
d06ebb7c03 |
Disable all spinners in Zed (#60614)
Closes https://github.com/zed-industries/zed/pull/48577 Part of https://github.com/zed-industries/zed/issues/8043 See https://zed-industries.slack.com/archives/C07NUKHLVUZ/p1783335475957139 for context. Release Notes: - Allow to reduce animations with `"reduce_motion": "on"` settings |
||
|
|
5e982c6bdc |
gpui: Fix crash when opening a popover from inside another popover (#60926)
Opening a popover from inside another popover (e.g. the branch picker's new filter menu, when the picker is hosted in the git panel or title bar) corrupted the frame-caching bookkeeping for whichever cached view contained them. The corruption was invisible until a later, unrelated redraw reused that view's cached subtree, at which point the window crashed (debug) or could misroute input events (release). Fixed by processing deferred draws in place so their recorded positions remain valid across frames; added a regression test reproducing the crash via a cached panel hosting nested popovers. Release Notes: - Fixed the branch picker menu in the Git Panel, which was previously broken due to popovers-within-popovers not being possible in GPUI. |
||
|
|
a496bf0ff6 |
Add a bunch of stray design refinement throughout (#60937)
This PR is a collection of grab bag UI design refinements across the entire app. Most of them are minor icon fixes/standardizations, spacing, and sizing tweaks. The one change that ended up getting a bit bigger than I initially anticipated is the debugger panel tabs improvements; added a bunch of tweaks there to make it look slicker. A relevant change is a small one-line change to the GPUI div element where it wouldn't add drag-over styles to elements that aren't initially a hitbox target. Given I wanted to pull off the drop indicator you see on the video below, this turned out to be needed: https://github.com/user-attachments/assets/964b456a-eef0-42bb-a433-7dac54ab8ec8 --- Release Notes: - N/A |
||
|
|
2268045a11 |
a11y: Landmarks and menu improvements (#60397)
Fixes menu a11y, and adds landmarks with `F6`-navigation. Also fixes a GPUI bug, and adds debug actions for dumping a11y tree info. Since `F6` was already in use by the pause debugger keybind, also tightens up the debugger keybind context so they require an active debugger session. When there is one active, `F6` stays as pause debugger. `ctrl-F6` always works to go to the next landmark. Also adds an "accessible mode" setting. Currently, this only controls whether we show all menus all the time, but I suspect it will expand significantly in the future. Also adds `.aria_keyshortcuts()` API, but it's not wired up within accesskit adapters, so is not yet reported to screen readers. --- Release Notes: - N/A or Added/Fixed/Improved ... |
||
|
|
49ad06c1b4 |
gpui: Add container_query element (#60774)
Adds a `container_query` element to GPUI, in the spirit of CSS container
queries: the element's own size is determined solely by its style and
the space offered by its parent, and once that size is known the
provided closure is called with the measured size to build the contents.
```rust
container_query(|size, _window, _cx| {
if size.width < px(240.) {
div().child("Narrow layout")
} else {
div().child("Wide layout")
}
});
```
Implementation notes:
- Defaults to filling its parent (`size_full()` semantics), overridable
via `Styled` since contents can't influence the container's size (the
same constraint CSS container queries impose).
- Reworks the `grid_layout` example (the Holy Grail layout) to
demonstrate it: the three-column grid collapses to a stacked column when
the window is narrower than 400px, and the header shows the live
measured width.
Release Notes:
- N/A
---------
Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
|
||
|
|
664b7ecb40 |
gpui: Fix hover state not clearing when mouse leaves window (#60275)
# Objective Fix two gaps in element hover tracking at window boundaries. Hover was only re-evaluated on `MouseMove`, so when the pointer left the window no event fired `on_hover(false)` and the element stayed hovered. Symmetrically on Wayland, no `Motion` follows `Enter` until the pointer moves again, so hover was not established at the entry pixel. Both cases are easy to miss since most hover-styled elements don't sit flush against the window edge, but they surfaced while implementing layer_shell popups with input_regions, which should close when stop hovering. ## Solution The hover compare-and-fire logic in `div` is refactored into a shared `update_hover` closure, and a second listener on `MouseExitEvent` clears hover when the pointer leaves the window. It clears unconditionally because `MouseExited` doesn't update the tracked mouse position, so a hit test during that dispatch would still report the element as hovered. On Wayland, a `MouseMove` is synthesized at the entry position on `wl_pointer.enter`, mirroring the `MouseExited` already dispatched on `Leave`. ## Testing Tested manually on Wayland/Linux: hover on a window-edge element clears when the pointer leaves the window, and hover is established immediately when the pointer enters a surface with an element under the entry pixel. Not tested on other platforms. The `div` change relies on each platform's existing `MouseExited` dispatch: macOS and X11 emit it, so they get the exit fix too. Windows never dispatches `MouseExited` (`WM_MOUSELEAVE` only flips the window-level hover flag), so the stuck-hover case might remain there, unchanged from before. Before: https://github.com/user-attachments/assets/6af83bb3-de9d-40e2-a64c-bdefc98fc96d After: https://github.com/user-attachments/assets/721a9b5c-108a-499f-867e-da111836a34a Here is an example application to test this: ```rs #![cfg_attr(target_family = "wasm", no_main)] use gpui::{ App, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size, }; use gpui_platform::application; struct HoverExit { hovered: bool, } impl Render for HoverExit { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { // Fills the whole window so its edge is the window edge: moving the mouse // out of the window is what exercises the MouseExited path. div() .id("hover-exit") .size_full() .flex() .justify_center() .items_center() .text_xl() .text_color(rgb(0xffffff)) .bg(if self.hovered { rgb(0x585f58) } else { rgb(0x505050) }) .child(if self.hovered { "HOVERED" } else { "not hovered" }) .on_hover(cx.listener(|this, hovered, _, cx| { this.hovered = *hovered; cx.notify(); })) } } fn run_example() { application().run(|cx: &mut App| { let bounds = Bounds::centered(None, size(px(240.), px(160.0)), cx); cx.open_window( WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), app_id: Some("gpui-hover-exit".to_string()), ..Default::default() }, |_, cx| cx.new(|_| HoverExit { hovered: false }), ) .unwrap(); cx.activate(true); }); } #[cfg(not(target_family = "wasm"))] fn main() { run_example(); } #[cfg(target_family = "wasm")] #[wasm_bindgen::prelude::wasm_bindgen(start)] pub fn start() { gpui_platform::web_init(); run_example(); } ``` ## 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) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Fixed element hover state not clearing when the mouse leaves the window |
||
|
|
d9e35975c2 |
csv_preview: Add row filtering feature (#60339)
# Objective CSV feature needs row filtering feature by column values. This PR provides base implementation of it with barebones ui. > NOTE: Sleek UI with search & proper scrolling hanling is implemented in next PR. It's stacked on top to reduce review scope ## Solution - New `FilterEntry` / `FilterEntryState` model in `table_data_engine/filtering_by_column.rs` tracking per-column applied/candidate filter values - Filtering runs in the background (`feat: Implement background filtering`) so large CSVs don't block the UI thread while a filter is applied - Filter menu entries reflect live counts and support a configurable sort order (`FilterSortOrder`, added in `renderer/settings.rs` / `settings.rs`) - Filter/sort trigger buttons on column headers are hidden until hover, using `GradientFade` (new in `ui/src/components/gradient_fade.rs`) to fade content behind them ## Testing Filter chain tested on csv fixtures with multiple filters applied sequentially columns. ## Self-Review Checklist: (todo) - [ ] I've reviewed my own diff for quality, security, and reliability - [x] 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) (out of scope of this pr) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase <img width="664" height="249" alt="image" src="https://github.com/user-attachments/assets/0e9b0a91-1a27-4e0f-a8d4-fdce36735131" /> <img width="663" height="205" alt="image" src="https://github.com/user-attachments/assets/0428f5c6-6aaa-4891-b010-ca79803f6613" /> --- Release Notes: - Added initial row filtering UI & logic |
||
|
|
693962917b |
gpui: Fix clear drag overlay when external drag ends outside window (#45759)
Release Notes: - Fixed clear drag overlay when external drag ends outside window When dragging files from macOS Finder over the project panel and then dragging back to Finder, the drag overlay remained visible because the drag state was not properly cleaned up. The root cause was that only `draggingExited:` was handled, but not `draggingEnded:`. On macOS: - `draggingExited:` is called when the drag leaves the window area - `draggingEnded:` is called when the drag operation ends entirely When a user drags a file back to Finder and drops it there, `draggingEnded:` is called but was not being handled. --------- Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com> |
||
|
|
e7803a88f5 |
gpui: Add ParentElement impl for AnimationElement (#54145)
Hello, I've been loving using GPUI! Recently I noticed that calling `.with_animiation()` would give an `AnimationElement<E>` which did not allow `.child()` to be called on it. It was a quick fix, I hope it's a quick easy merge but let me know if you'd like anything changed :) 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 Co-authored-by: Lukas Wirth <lukas@zed.dev> |
||
|
|
10628c3d2c |
agent_ui: Add in-thread search bar (#57231)
This is a narrower alternative to #54816, scoped to search only the currently-loaded thread, excluding tool output or thinking blocks (happy to follow up on those, see next). It uses a custom bar confined to `agent_ui` rather than `BufferSearchBar` + `SearchableItem`, avoiding the cross-crate plumbing that #54816 reached. Open as draft pending direction from @benbrandt on what scope/approach would be acceptable for in-thread search. <img width="959" height="609" alt="image" src="https://github.com/user-attachments/assets/8971e432-61d3-46db-a6a4-2bbd355089e7" /> ## What this PR does Adds a search bar to the agent panel, triggered by Ctrl+F (Cmd+F on macOS), that lets users grep the currently-loaded thread without leaving the agent panel; not cross-thread or cross-agent. - Searches visible content only: user messages, assistant message chunks, and tool-call labels. Thought blocks and rendered tool-call content (collapsed by default) are intentionally skipped so that: 1. The visible match count matches what the user sees. 2. The search experience is consistent between tool output blocks in the current Zed session, ans tool output blocks from past sessions, which are not rendered - see issue #57230 - Highlights matches inline via `Markdown::set_search_highlights` for markdown-rendered content and `Editor::highlight_background(HighlightKey::BufferSearchHighlights, …)` for past user messages (rendered through `MessageEditor`'s inner `Editor`, not through markdown). - Next/prev navigation, case/whole-word/regex toggles (same UI as `BufferSearchBar`). - Returns focus to the message editor on dismiss so the user can keep typing immediately. <details><summary>Commits (authored by Claude 4.7 Opus, max)</summary> ### Seven logical commits 1. **`agent_ui: Add in-thread search bar`** — initial implementation: bar UI, keymap bindings under `AcpThreadSearchBar` context, markdown highlight plumbing, focus-restore on dismiss. 2. **`agent_ui: Add unit tests for in-thread search`** — coverage of the matcher across entry kinds and the dismiss-clears-highlights path. 3. **`agent_ui: Limit search to visible tool-call text`** — UX fixes from manual testing: `track_focus` so `AcpThreadSearchBar` context lands in the editor's dispatch chain; red border on zero-match query; skip tool-call content (only search labels). 4. **`agent_ui: Fix Esc dispatch, smart toggle, error message, skip Thought blocks`** — round 2 of UX fixes: contribute `AcpThreadSearchBar` context from `ThreadView` when bar is visible, smart Ctrl/Cmd+F outside-the-bar focuses instead of closing, regex error message row, skip `AssistantMessageChunk::Thought`. 5. **`agent_ui: Polish thread search bar — Esc routing, user-message highlights, action forwarding`** — `search::*` action forwarders on `ThreadView`; `cx.defer` around the activate callback (fixes a double-borrow panic); `editor::actions::Cancel` interception so Esc dismisses our bar instead of escaping to the workspace's `BufferSearchBar`; user-message highlights via the inner `Editor`; muted zero-match counter; three new gpui regression tests. 6. **agent_ui: Fix Shift+Enter shadowing in thread search bar`** — capture-phase intercept of `editor::Newline*` on the bar's `bar_row` element so a base keymap binding `shift-enter` at the `Editor` context (e.g. JetBrains → `editor::NewlineBelow`) can't shadow the bar's `agent::SelectPreviousThreadMatch`. Adds a regression test that loads `default-linux.json` + `linux/jetbrains.json` and asserts `shift-enter` navigates instead of inserting a newline. 7. `agent_ui: Debounce thread search, refresh on thread changes, highlight user messages`** — last round before maintainer review: 150 ms debounce on the match rescan; subscribe to `AcpThread` updates so results/highlights/counter follow a streaming conversation live; navigate the list to the entry owning the active match; `.ok()` instead of `let _ =`; assorted cleanups; two new regression tests (`test_thread_search_refreshes_on_new_thread_entry`, `test_thread_search_scrolls_to_later_user_message_match`). </details> 8 gpui tests cover the load-bearing logic (`cargo test -p agent_ui --lib -- thread_search`). All pass. ## Manual testing Verified on Linux against `upstream/main` `13e7c11768` (full `release` profile, with LTO and `codegen-units=1` built and tested at that commit). Branch since merged with `upstream/main` `a6780a5`. Of the 100 intervening upstream commits, several touch files in this diff; one produced a real conflict in `crates/agent_ui/src/conversation_view.rs` (`gpui::{...}` import block — both sides added new, non-overlapping symbols; resolved by union). - Bar opens / dismisses via Ctrl+F and Esc - Next/prev navigation via Enter / Shift+Enter / F3 / Shift+F3 / chevron buttons - Case / whole-word / regex toggles via buttons and via Alt+C / Alt+W / Alt+R (Linux) - Highlights inside past user messages render correctly on the `MessageEditor`'s inner `Editor` - query text turns red on no-match; bad regex shows error message; zero-match counter stays muted (not red) - `search::*` actions fire from outside the bar (focus in message editor) via the `ThreadView`-level forwarders - Workspace pane `BufferSearchBar` is unaffected; the two bars hold independent state - Feature looks great in light mode and dark mode, and with various themes - Navigating to a match in a past user message scrolls that message into view (its top to the viewport top) - Results update live as the agent streams new messages while the bar is open (debounced ~150 ms, same for typing) [Zed agent thread search subscribe to thread updates.webm](https://github.com/user-attachments/assets/75585fee-ba25-42ac-9dde-16c8e152fb06) <details><summary>Full manual test plan</summary> ### Setup (preconditions for every test below unless otherwise noted) 1. Launch the dev binary: `./target/release-fast/zed --user-data-dir ~/.local/share/zed-dev-feat` 2. Open the agent panel, start a thread, send 2–3 prompts so the thread contains: - At least one user prompt with a distinctive substring (e.g. `watermelon`) - At least one assistant message with a distinctive substring - At least one tool call with a known label substring - At least one collapsed Thinking block 3. In parallel: have at least one code editor open in a workspace pane (some tests cross-check that path). ### Core matching #### T-001 — Ctrl+F opens the bar with focus in query **Status:** ✅ PASS 2026-05-15 15:30 **Repro:** With focus in the agent panel's message editor, press Ctrl+F (Linux) / Cmd+F (macOS). The search bar appears at the top of the thread view and the query input is focused. #### T-002a — Active match visually distinct from inactive matches **Status:** ✅ PASS 2026-05-15 15:30 **Repro:** Type a query that produces ≥3 matches. The current ("active") match must be visually different from the others — different highlight color, not just position. If they look identical, this may be a theme issue (`search_active_match_background` vs `search_match_background` too close on the entry's background); note the theme. #### T-002b — User-prompt text gets inline highlight, not just match count **Status:** ✅ PASS 2026-05-15 17:15 (fix shipped 2026-05-15 16:15; regression-guarded by `test_thread_search_highlights_user_message_editor`) **Repro:** Send a user prompt containing a distinctive substring. Then search that substring. The user prompt text **must** show yellow highlight, not just be counted. The bar's matcher reads from `message.content.markdown()` but the on-screen render goes through a `MessageEditor`; the current code only highlights via `Markdown::set_search_highlights`, which misses the editor. **Notes:** Fix routes user-message matches through `Editor::highlight_background(HighlightKey::BufferSearchHighlights, …)` — the same path `Editor`'s own `SearchableItem` impl uses. `collect_markdowns` no longer pulls the user message's markdown (avoiding double-counting). The bar now maintains separate `highlighted_markdowns` and `highlighted_editors` lists for clean-up. Match-target enum (`MatchTarget::Markdown` vs `MatchTarget::Editor`) tags each match so active-vs-inactive re-paint can dispatch correctly. Pending runtime verification on next `release-fast` build. #### T-003 — Enter / Shift+Enter in bar navigates matches **Status:** ✅ PASS 2026-05-25 14:00 (verified on `release` build) **Repro:** With focus in the query, type a query with ≥3 matches. Press Enter → counter `1/N → 2/N → 3/N`. Press Shift+Enter → reverses (and no `\n`s are output in the search box). F3 / Shift+F3 same. ### Option toggles #### T-004 — Alt+C toggles case sensitivity from inside bar **Status:** ✅ PASS 2026-05-15 07:50 **Repro:** With focus in query, press Alt+C. `Aa` button toggles state; results re-filter. #### T-005 — Alt+W toggles whole-word from inside bar **Status:** ✅ PASS 2026-05-15 15:30 **Repro:** Same as T-004, with Alt+W toggling `wd` button. #### T-006 — Alt+R toggles regex from inside bar (Linux) **Status:** ✅ PASS 2026-05-15 15:30 (fix shipped 2026-05-15 06:35) **Repro:** Press Alt+R. `.*` button toggles. **Notes:** macOS keymap (`alt-cmd-x`) left unchanged in this session — it matches the wider macOS Zed convention. #### T-006a — Tooltips on toggle buttons show their hotkey **Status:** ✅ PASS 2026-05-15 15:30 **Repro:** Hover each toggle button (`Aa`, `wd`, `.*`). Tooltip shows action name AND keybinding (e.g. "Toggle Case Sensitive · Alt+C"). ### Error / empty states #### T-007 — Empty matches: no red border on bar input **Status:** ✅ PASS 2026-05-15 15:30 **Repro:** Type `zzzzzzz`. Query text turns red. Counter shows `0/0`. Bar input border stays **neutral**, no red box. #### T-008 — Bad-regex error message **Status:** ✅ PASS 2026-05-15 15:30 (error renders aligned with the input) **Repro:** Switch to regex mode, type `[`. Error message appears below bar, aligned with the input. #### T-019 — Zero-match counter not colored red **Status:** ✅ PASS 2026-05-15 15:30 (fix shipped 2026-05-15 06:30) **Repro:** Type a query with no matches. The counter (`0/0`) stays muted, **not** red. MPS-parity. ### Dismissal & re-entry #### T-009 — Esc doesn't interrupt agent generation **Status:** ✅ PASS 2026-05-15 15:30 **Repro:** Start a long prompt; while the agent is generating, open the search bar and press Esc. Bar closes, generation continues. #### T-010 — Close-X button position **Status:** ✅ PASS 2026-05-15 15:30 **Repro:** Look at the bar. Close (X) button is on the right side, after the nav arrows (`<` `>` `1/N` `X`). NOT free-floating left of the input. #### T-011 — Ctrl+F in bar selects all query text **Status:** ✅ PASS 2026-05-15 15:30 **Repro:** With bar open and query containing text, press Ctrl+F. All query text becomes selected (next keystroke replaces). #### T-012 — Ctrl+F outside the bar (bar already open) does not crash **Status:** ✅ PASS 2026-05-15 15:30 (fix shipped 2026-05-15 06:30; regression-guarded by `test_thread_search_select_next_from_thread_view_update_does_not_panic`) **Repro:** Bar visible, focus in the message editor (not the bar). Press Ctrl+F. Bar gets re-focused, query text is selected. **Must not crash.** **Notes:** Original crash also reproducible from Enter / Shift+Enter inside the bar after a search had matches. Fix wraps the `on_activate_match` callback's `view.update` in `cx.defer`. #### T-021 — Esc dismisses agent bar (not the unrelated workspace pane's BufferSearchBar) **Status:** ✅ PASS 2026-05-15 15:30 (fix shipped 2026-05-15 14:40; regression-guarded by `test_thread_search_editor_cancel_dismisses_bar`) **Repro:** Open a file in a workspace pane editor (e.g. `settings.json`). Open BufferSearchBar there. Switch focus to the agent panel, open agent search bar, type a query. Press Esc. **The agent bar must dismiss; the workspace pane's BufferSearchBar must remain.** **Notes:** Root cause: `Editor::cancel` in single-line mode calls `cx.propagate()`. The propagated `editor::actions::Cancel` walked past our bar all the way to `Workspace`, where `BufferSearchBar::register` had registered a workspace-wide handler that dismissed the active pane's bar. Fix: `ThreadView` now catches `editor::actions::Cancel` when the bar is visible. ### Action routing while bar is open, focus is in the message editor #### T-013 — F3 / Shift+F3 from outside the bar **Status:** ✅ PASS 2026-05-15 15:30 (Enter from outside is *expected* not to work — message editor's `enter` binding outranks. F3 is the canonical out-of-bar nav key.) **Repro:** Bar open with matches, focus in message editor. Press F3 → next match. Shift+F3 → previous. #### T-014 — Alt+C / Alt+W / Alt+R from outside the bar **Status:** ✅ PASS 2026-05-15 15:30 (fix shipped 2026-05-15 02:30; `ThreadView` forwarders for `search::ToggleCaseSensitive`, `ToggleWholeWord`, `ToggleRegex`, `FocusSearch`) **Repro:** Bar visible, focus in message editor. Alt+C/W/R toggles the bar's options and re-runs search. ### Nav buttons #### T-022 — `<` / `>` chevron buttons in the bar advance match **Status:** ✅ PASS 2026-05-15 15:30 **Repro:** With ≥2 matches, click `<` → previous. Click `>` → next. Wraps at ends. ### Coexistence with workspace-pane buffer search #### T-015 — Ctrl+F in a workspace-pane editor still opens `BufferSearchBar` (no agent-panel takeover) **Status:** ✅ PASS 2026-05-15 15:30 **Repro:** Focus a code editor in a workspace pane, press Ctrl+F. The pane's `BufferSearchBar` opens as before; agent panel's bar does NOT open. #### T-016 — Independent bars: agent vs workspace pane **Status:** ✅ PASS 2026-05-15 15:30 **Repro:** Have both bars open simultaneously. They hold independent state (different queries, different toggles, independent dismissal). ### Tool-call content (auto-expand on match) #### T-017 — Auto-expand collapsed tool calls on match **Status:** ⏸️ BLOCKED (not implemented; out of scope for current session) **Repro:** Have a tool call whose collapsed content contains a unique substring. Search that substring. The tool call auto-expands so the match is visible. After dismissal, the tool call collapses again unless the user manually expanded it before. #### T-018 — Auto-expand collapsed Thinking blocks on match **Status:** ⏸️ BLOCKED (not implemented; out of scope for current session) **Repro:** Same shape as T-017 for a collapsed Thinking block. #### T-019 — Streaming results automatically highlighted / live thread search **Status:** ✅ PASS 2026-06-08 **Repro:** Send prompt "Write a 5-paragraph story about a researcher who hated noise. The last paragraph should be a one-sentence: "So she moved to <name of the quietest continent on Earth>" (replace that <name> with the actual name)", then search for `Antarctica`. </details> ## Known limitations 1. (pre-existing, not from this PR): search highlights inside markdown table cells can be visually offset by a few characters in columns starting with the second. The bug lives in `crates/markdown`'s render layer - see issue #57229 2. (excluded vs. #54816 to keep the scope tight): raw tool-call input/output JSON and terminal scrollback are not indexed. 3. Navigating to a search hit located **below the fold of a very long user message** highlights the match but only scrolls the message's top into view, so the hit can stay off-screen until you scroll manually. Past user messages render through an `AutoHeight` `Editor` inside a virtualized `List`, which can't reliably drive intra-entry autoscroll the way markdown content does. User messages are usually shorter than a viewport, so this is left unaddressed. 4. Plain-text search across triple-backtick fences does not match (the fence characters live in the markdown source; regex search with explicit fence handling does). 5. The literal text "Thinking" rendered as the thought-disclosure header is not searchable (rendered chrome, not markdown source). Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - No new `unsafe` introduced; no network, file-system, or process APIs touched; only added dep is `search` (promoted from dev-dep to runtime), already used by this crate's tests; `MessageEditor::editor()` accessor widening is `pub(crate)` (in-crate only). Assessment: no new attack surface. - [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) — partially; see "What I did NOT verify" above - [ ] I did not verify **macOS and Windows**. Keymap entries exist for all three platforms (`AcpThreadSearchBar` context bindings) but I only verified Linux at runtime. - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable: regexp-searching for "t.e" in a thread almost at the context limit (919k tokens) felt instant, and navigation among the 2100+ results occurred at the keyboard autorepeat rate. - [ ] Haven't measured against the 8ms / 120fps frame budget. Partially closes #39338 Release Notes: - Added in-thread search to the agent panel (Ctrl/Cmd+F) --------- Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de> |
||
|
|
83d4847462 |
a11y: Settings UI (#59429)
Adds: - aria attributes to most UI elements in settings UI - a new a11y API to GPUI - a fix for a GPUI keyboard focus bug ## Accesible settings UI Settings UI should now be fully accessible to users of assistive technology. **However**, there are some caveats: - accessibility features require zed to be launched with the `ZED_EXPERIMENTAL_A11Y=1` env var to be set - I have not exhaustively checked every control - There are some quite surprising keyboard focus behaviours which predate this code - The main Zed UI is still largely inaccessible, though a handful of shared components will now report themselves, but the experience is suboptimal. For anyone wishing to try out the settings UI: - make sure `ZED_EXPERIMENTAL_A11Y=1` is set - open zed - press `ctrl-,` (or `cmd+,` on a mac) to open settings UI in a separate window ## `aria_active_descendant` This is equivalent to the [`aria-activedescendant`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-activedescendant) API on the web. It allows a container to maintain focus, while indicating that one of its children should be active. Zed uses this for menus and combo boxes, for example. GPUI will report a div with `.aria_active_descendant()` as long as: - an ancestor is focused - only one descendant of the focused ancestor has `.aria_active_descendant()` Two descendants with this property where the focused node is a common ancestor is an error. ## GPUI keyboard fix GPUI will map a space or enter keypress to a div's `on_click` handler, if it exists. However, the keyboard-driven path was missing some checks that the mouseclick path has. With this PR, for an enter/space keypress to be considered a click, between the "key down" and "key up" events: - there must be no other key events - focus must not move to a different node This fixes an existing bug with combo boxes in zed where: - enter maps to `menu::Confirm` **on key down* - the menu item is selected - focus moves to the combo box - enter then triggers the combo box's `on_click` **on key up**, re-opening the menu --- Release Notes: - N/A or Added/Fixed/Improved ... --------- Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com> |
||
|
|
c7ad65e468 |
markdown: Optimize paint_search_highlights (#59473)
Follow up to #52502 This fixes a severe hang in the markdown preview when searching for text caused by `paint_search_highlights`. We were iterating over all lines for each highlight, and for each highlight we called `layout.line_layout_for_index` which internally would iterate over all the lines of the text layout. With this optimisation we paint all highlights in one pass. We sort the highlight ranges, iterate through the lines and only access `WrappedLineSegments` when we actually have a highlight in that line. E.g. if you copy this text (https://gist.github.com/bennetbo/e313f3023da9fec42e090177a5373152) into the editor, open the markdown preview and search for `codex` Zed Nightly will completely freeze. After this optimisation it is totally smooth even in debug mode. Release Notes: - Improved performance when searching in markdown preview |
||
|
|
7fd5ea4bf3 |
gpui: Fix list scroll events being reverted by pending scroll (#59002)
This PR fixes some cases where scrolling is choppy due to a user inputting a scroll event while a list state is going through a remeasure. Currently, the list state would ignore the user's scroll and fall back to the pending scroll position, this PR fixes this by rebasing the pending scroll onto the user's new scroll position. 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: - agent_panel: Improve scroll smoothness while a response is streaming |
||
|
|
03a8544040 |
tab_switcher: Middle-truncate long picker filenames (#59072)
## Context This follow-up was inspired by Christopher’s suggestion in the review comment on #58483: long filenames should truncate in the middle so both the beginning and the suffix/extension remain visible. Previously, very long filenames in picker rows could still be clipped or end-truncated, making files with shared prefixes hard to distinguish. This adds middle-truncation support to GPUI text overflow and applies it to filename-focused picker surfaces: the tab switcher and the Ctrl-P file finder. Normal editor tab bar behavior remains unchanged and continues to use the existing title length cap. ## How to Review - **`crates/gpui/src/style.rs`, `crates/gpui/src/styled.rs`, `crates/gpui/src/elements/text.rs`, `crates/gpui/src/text_system/line_wrapper.rs`**: Adds `TextOverflow::TruncateMiddle`, wires it through text layout, and implements middle truncation while preserving valid text runs. Very narrow widths now show the truncation affix instead of falling back to the abruptly clipped original text. - **`crates/ui/src/components/label/label_like.rs`, `crates/ui/src/components/label/label.rs`, `crates/ui/src/components/label/highlighted_label.rs`**: Exposes middle truncation through the shared label components. - **`crates/workspace/src/item.rs`, `crates/editor/src/items.rs`, `crates/workspace/src/pane.rs`, `crates/tab_switcher/src/tab_switcher.rs`**: Adds an explicit tab-content opt-in for middle truncation. The tab switcher enables it, while the normal tab bar and dragged tabs keep their existing behavior. - **`crates/file_finder/src/file_finder.rs`**: Applies middle truncation to Ctrl-P file finder filenames and lets the path shrink from the start so long paths do not hide important filename suffixes. Manual test before change : [Screencast from 2026-06-10 22-59-28.webm](https://github.com/user-attachments/assets/5e032646-a48c-45f2-8fe2-0424507fd576) Manual test after change : [Screencast from 2026-06-10 22-54-48.webm](https://github.com/user-attachments/assets/4b9253da-f169-4ed1-bdd5-ee71dcf49a83) ## 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 is consistent with the UI/UX checklist - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Improved long filename truncation in the tab switcher and file finder so extensions remain visible. |
||
|
|
1cecd7d66a |
Fix agent permission row flicker when scrolled away from a tall plan (#58689)
When a pending tool call awaiting permission contains tall content (e.g. a full plan in Claude Code plan mode) and the inline prompt is scrolled out of view, the floating awaiting-permission row embeds that content and can grow to consume the entire panel, squeezing the conversation list to zero height. `ListState::item_is_above_viewport` / `item_is_below_viewport` returned `None` for a zero-height viewport, so the row's visibility decision oscillated from frame to frame, flickering between the conversation and the permission prompt and making the thread unusable. This PR fixes the root cause and hardens the UI: - `gpui`: `item_is_above_viewport` / `item_is_below_viewport` now answer definitively from the last layout bounds even when the viewport is zero-height, so callers that size sibling UI based on these queries can't oscillate. Adds a regression test. - `agent_ui`: adds a `Floating` tool call layout used by the awaiting-permission row that caps the embedded tool call content with a scrollable max height, so the row can never crowd the conversation list out of view while keeping the permission buttons visible. Adds an integration test that drives real window draws and asserts the row's visibility is stable across frames. Closes AI-372 Closes https://github.com/zed-industries/zed/issues/58564 Release Notes: - Fixed the agent panel flickering and becoming unusable when a permission prompt with tall content (such as a plan awaiting approval) was scrolled out of view. |
||
|
|
b3bc83b57e |
gpui: Make tooltip show delay configurable (#58291)
Self-Review Checklist: - [x] 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) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A This adds a `tooltip_show_delay(Duration)` API so callers can override the default 500ms delay. |
||
|
|
39f7849a0f |
Log worst hanging tasks and actions (#57835)
We have a lot of long blocking tasks on both the foreground and background, this is a start of getting some insight into those. We will now log tasks running longer then 100ms on the foreground or background. Hanging actions will also be logged including their name. We simultaneously collect statistics on task and action performance and send those to telemetry. This includes quantiles and averages for each hanging task. Finally this adds tree dev actions: - hang action - hang foreground - hang background These cause a hang to check if hang reporting is working and in the future telemetry. 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: - Added logging and telemetry of tasks and actions with performance issues |
||
|
|
507d043d96 |
gpui: Fix stuck tooltips after mouse leaves origin (#58134)
Self-Review Checklist: - [x] 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) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A I encountered this bug while building my app with GPUI where tooltips were getting stuck when i move my mouse away from the origin element. Zed kinda gets away with it because tab close buttons use `visible_on_hover`, so when the mouse leaves the tab, close button disappears. This triggers a redraw which makes GPUI recheck and hide the tooltip. In the "Before" version i had to trigger the recheck by moving my mouse over an element with hover styles to make the tooltip disappear. ### Before https://github.com/user-attachments/assets/f405620f-6d6d-4a24-8992-214180bc7b76 ### After https://github.com/user-attachments/assets/2e41fbca-af61-4f34-bd5d-ab288f34e73a |
||
|
|
a1d2ef6514 |
gpui: Add item_is_above_viewport and item_is_below_viewport APIs to ListState (#58061)
In prep for handling the above-viewport case in https://github.com/zed-industries/zed/pull/57632, which currently only handles below case. This PR adds `ListState::item_is_above_viewport` and `ListState::item_is_below_viewport` methods, which report whether a given list item is entirely outside the current viewport. Both return `None` when the list has not measured enough layout to answer. Release Notes: - N/A |
||
|
|
777e16dd1f |
gpui: Fix panic with invalid focus IDs (#57885)
We were missing a check when setting the focused node ID, causing a panic in accesskit's internal validation logic. Fixes the bug, and adds some defensive logic to help mitigate potential future issues. You can reproduce the panic by: - turning on a screenreader - launching zed - opening a menu and pressing tab This change fixes it. Release Notes: - N/A or Added/Fixed/Improved ... |
||
|
|
1d029c5ff5 |
gpui: Accesskit support (#56065)
GPUI AccessKit integration This PR is replacing #51097 , and is much more limited in scope. This PR *ONLY* adds AccessKit support to GPUI, and doesn't touch Zed. Once this lands, we can start adding aria attributes to Zed's components. This PR is the first step to addressing #41138 . Release Notes: - N/A or Added/Fixed/Improved ... --------- Co-authored-by: John Tur <john-tur@outlook.com> Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com> Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de> Co-authored-by: Agus Zubiaga <agus@zed.dev> |
||
|
|
ba350974af |
Fix multiline text truncation (#57450)
Improves .text_truncate() in combination with .line_clamp() <img width="384" height="124" alt="Screenshot 2026-05-21 at 4 28 59 PM" src="https://github.com/user-attachments/assets/3decc78c-8d5d-4d34-84c5-4274a2d12bea" /> <img width="385" height="143" alt="Screenshot 2026-05-21 at 4 26 23 PM" src="https://github.com/user-attachments/assets/f807b19a-6834-4504-9749-e16b8d68a7aa" /> This was previously broken because we assumed lines would break evenly. Release Notes: - Improved truncation of multi-line text in the UI |
||
|
|
1c61cc3fc2 |
gpui: Fix scrollbar drag position calculation in list (#53378)
## 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> |
||
|
|
51b43c90f9 |
gpui: Add is_scrollbar_dragging() accessor to ListState (#53380)
## 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) |
||
|
|
dfd8328f7b |
gpui: Fix material list unstable scrollbar position (#55808)
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> |
||
|
|
4e28b0a3fc |
recent_projects: Allow bulk deleting entries without scroll jumping (#54777)
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. |
||
|
|
b38194198b |
gpui: Fix anchored element size calculation with negative coordinates (#55124)
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 |
||
|
|
80c4c3ec93 |
terminal_view: Ensure reported size does not change once content becomes scrollable (#46648)
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> |
||
|
|
749fcfdfd8 |
gpui: Fix out-of-bounds panic rendering GIFs (#54701)
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. |
||
|
|
58d3a9eef4 |
gpui_shared_string: Implement SharedString via smol_str (#54649)
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 ... |
||
|
|
7d42f276f2 |
Pixel snapping (#54728)
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. |
||
|
|
9b40411c6a |
Fix bad GitHub merge queue merge (#54721)
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> |
||
|
|
2f445d4059 |
gpui: Fix follow_tail not re-engaging after scrollbar drag (#54570)
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>
|
||
|
|
0ab64d6414 |
branch_picker: Add button to filter remote branches (#54632)
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 |
||
|
|
5f68479937 |
Stop showing backtraces in default logging (#54660)
When using `.log_err()` or `.detach_and_log_err(cx)` or similar. See also https://github.com/zed-industries/zed/pull/36404, https://github.com/zed-industries/zed/pull/46383, https://github.com/zed-industries/zed/pull/44896, https://github.com/zed-industries/zed/pull/43917 and many more. Before, a https://github.com/zed-industries/zed/blob/62bd61a6793c5f983fbc1700825208f19d780d52/crates/languages/src/go.rs#L568 line would show a backtrace even for `.context("no cached binary")?;` case, same as many other usages around the code: <img width="2032" height="1162" alt="before" src="https://github.com/user-attachments/assets/ef2188b3-74c9-4c86-82b8-9fdaed3c26ae" /> After: <img width="1896" height="157" alt="after" src="https://github.com/user-attachments/assets/a1067d9f-61f4-4833-aeab-9f1042d2514a" /> To show a backtrace as before, use `log_err_with_backtrace`. Release Notes: - Improved Zed's log output on errors |
||
|
|
84dcf38dbe |
gpui: Improve Anchored to support center position (#47154)
Release Notes: - N/A Ref https://github.com/longbridge/gpui-component/pull/1956 extract my fork version of `anchored.rs` to let GPUI to support position Anchored at center. https://github.com/user-attachments/assets/8d0230ed-4b75-440b-b8c3-9bde3decd141 --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
0ddc7d0666 |
Fix crash and enable GIF animation in markdown preview (#53459)
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> |
||
|
|
45c0ced8b2 |
cli: Add first-run prompt for default open behavior and abstract IPC transport (#53663)
This PR adds the `cli_default_open_behavior` setting and a first-run TUI prompt that appears when `zed <path>` is invoked without flags while existing windows are open and the setting hasn't been configured yet. ## What it does ### Setting and prompt - Adds a new `cli_default_open_behavior` workspace setting with two values: `existing_window` (default) and `new_window`. - When the user runs `zed <path>` for the first time with existing Zed windows open, a `dialoguer::Select` prompt in the CLI asks them to choose their preferred behavior. The choice is persisted to `settings.json`. - The prompt is skipped when: - An explicit flag (`-n`, `-e`, `-a`) is given - No existing Zed windows are open - The setting is already configured in `settings.json` - The paths being opened are already contained in an existing workspace ### IPC transport abstraction - Introduces a `CliResponseSink` trait in the `cli` crate that abstracts `IpcSender<CliResponse>`, with an implementation for the real IPC sender. - Replaces `IpcSender<CliResponse>` with `Box<dyn CliResponseSink>` / `&dyn CliResponseSink` across all signatures in `open_listener.rs`: `OpenRequestKind::CliConnection`, `handle_cli_connection`, `maybe_prompt_open_behavior`, `open_workspaces`, `open_local_workspace`. - Extracts the inline CLI response loop from `main.rs` into a testable `cli::run_cli_response_loop` function. - Switches the request channel from bounded `mpsc::channel(16)` to `mpsc::unbounded()`, eliminating `smol::block_on` in the bridge thread. ### End-to-end tests Seven new tests exercise both the CLI-side response loop and the Zed-side handler connected through in-memory channels, using `allow_parking()` so the real `cli::run_cli_response_loop` runs on an OS thread while the GPUI executor drives the Zed handler: - No flags, no windows → no prompt, opens new window - No flags, existing windows, user picks "existing window" → prompt, setting persisted - No flags, existing windows, user picks "new window" → prompt, setting persisted - Setting already configured → no prompt - Paths already in existing workspace → no prompt - Explicit `-e` flag → no prompt - Explicit `-n` flag → no prompt Existing tests that previously used `ipc::channel()` now use a `DiscardResponseSink`, removing OS-level IPC from all tests. Release Notes: - Added a first-run prompt when using `zed <path>` to choose between opening in an existing window or a new window. The choice is saved to settings and can be changed later via the `cli_default_open_behavior` setting. --------- Co-authored-by: Nathan Sobo <nathan@zed.dev> |
||
|
|
1823be5678 |
agent_ui: Fix "scroll to" buttons (#53232)
Follow-up to https://github.com/zed-industries/zed/pull/53101 In the process of fixing the thread view's scroll experience, we for got to turn off the follow state tail for functions that power the scroll buttons in the agent panel. Release Notes: - N/A |
||
|
|
2fbf83049f |
gpui: Refactor follow_tail implementation to fix scroll snapping bugs (#53101)
Follow up to https://github.com/zed-industries/zed/pull/53017 This PR does some significant refactoring of the `follow_tail` feature in the GPUI list. That's only used by the agent panel's thread view and given to the height-changing nature of streaming agent responses, we were seeing some scroll snapping bugs upon scrolling while the thread is generating. In the process of fixing it, we introduced a `remeasure_items` method as an alternative to `splice` so that we could get the remeasurement fix without scroll position changes. We already had a `remeasure` method that did that for all of the indexes, but we needed something more scoped out for the agent panel case, so as to not remeasure the entire list's content on every new streamed token. Effectively, this ends up reverting what the PR linked above introduced, but it improved the API in the process. Release Notes: - N/A Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com> |
||
|
|
cb99ab4ac7 |
Add PageUp/PageDown scrolling in agent view (#52657)
Fixes #52656 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 #52656 Release Notes: - Added keybindings for scrolling in agent view --------- Co-authored-by: Oleksiy Syvokon <oleksiy.syvokon@gmail.com> |
||
|
|
0638897cf0 |
Fix tooltip reference cycles (#52853)
## Summary - break tooltip back-references from stored callbacks and tasks with weak handles - keep the tooltip controller as the sole strong owner of tooltip lifecycle state - add a regression test that exercises the visible-tooltip ownership graph directly ## Testing - cargo test -p gpui tooltip_is_released_when_its_owner_disappears --lib Closes AI-120 Release Notes: - Fixed a tooltip memory leak. |
||
|
|
086060558f |
gpui: Update animated image timing to pause when inactive (#52685)
- only advance frames and request animation frames while the window is active. - Reset last_frame_time to None when inactive to avoid fast catch-up after resuming focus - This fixes an issue where if you would have a gif open and lose focus, the app tries to speedup to catch up the elapsed time 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 Might Close #21563, I shall measure the performance, I originally only intended the fix to fix the speed up issue as I faced it while working on gifs. Release Notes: - N/A or Added/Fixed/Improved ... |
||
|
|
dbb8afe676 |
gpui: Fix BGRA conversion for SVG rendering (#52641)
### Description Fixes swapped red/blue channels when rendering SVG images. #### Describe the bug When rendering a full-color SVG into an Image object using Image::from_bytes(ImageFormat::Svg, ...) on macOS, the resulting bitmap has its Red and Blue channels swapped. For example, a color specified as #38BDF8 (Light Blue) in the SVG source appears as yellowish in the rendered GPUI view. #### Steps to reproduce 1. Create a GPUI application. 1. Generate or load an SVG string containing a specific color, for example: ```xml <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"> <rect width="100" height="100" fill="#38BDF8"/> </svg> ``` 3. Load this SVG into an Image object: ```rust let image = Arc::new(Image::from_bytes( ImageFormat::Svg, svg_string.into_bytes(), )); ``` 4. Display this image in a view using an img() element. #### Expected behavior The rectangle should be rendered in **Light Blue (#38BDF8)**. #### Actual behavior The rectangle is rendered in **Yellowish Color (#F8BD38)**. ### 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: - Fixed swapped color channels when pasting SVG images from the clipboard. --------- Co-authored-by: MrSubidubi <finn@zed.dev> |
||
|
|
6694a3bd14 |
gpui: Implement pinch event support for X11 and Windows (#51354)
Closes #51312 - Remove platform-specific #[cfg] gates from PinchEvent, event listeners, and dispatch logic in GPUI - Windows: Intercept Ctrl+ScrollWheel (emitted by precision trackpads for pinch gestures) and convert them to GPUI PinchEvents - Image Viewer: remove redundant platform-specific blocks - X11: Bump XInput version to 2.4 and implement handlers for XinputGesturePinch events - [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 - [x] Aligned any UI changes with the [UI checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) Release Notes: - Pinching gestures now available on all devices. --------- Co-authored-by: John Tur <john-tur@outlook.com> |
||
|
|
a3d72e5427 |
agent_ui: Make thread generation top-down (#52440)
Ever since we introduced the agent panel the way it is right now back in May 2025, we wanted to make the thread generation be top-down. Now, with the changes we're planning to launch very soon revolving around parallel agents, doing this become even more important for a better experience. Particularly because in the agent panel's new empty state, the message editor is full screen. We want to minimize as much as possible layout shift between writing your first prompt and actually submitting it. So this means that content will stream down from your first prompt and auto-scroll you if it goes beyond the viewport. To pull this off, we added a `follow_tail` feature directly to the GPUI list so that we could only call it in the thread view layer as opposed to doing it all there. https://github.com/user-attachments/assets/99961819-6a79-40e0-b482-dca68c829161 Release Notes: - Agent: Made the thread generation be top-down instead of bottom-up. Agent content now streams from the top and auto-scroll as they go beyond the viewport. --------- Co-authored-by: Richard Feldman <oss@rtfeldman.com> |