Compare commits

...

374 Commits

Author SHA1 Message Date
Peter Tripp
88a37b04aa Up or Down 2025-08-01 16:23:32 -04:00
Peter Tripp
23dddba1e6 Upper controls. 2025-08-01 15:43:30 -04:00
Danilo Leal
faa45c53d7 onboarding: Add design adjustments (#35480)
Release Notes:

- N/A

---------

Co-authored-by: Anthony <anthony@zed.dev>
2025-08-01 15:08:15 -03:00
Max Brunsfeld
b31f893408 Rasterize glyphs without D2D (#35376)
This allows debugging Zed with Renderdoc, and also fixes an issue where
glyphs' bounds were miscalculated for certain sizes and scale factors.

Release Notes:

- N/A

---------

Co-authored-by: Kate <kate@zed.dev>
Co-authored-by: Julia <julia@zed.dev>
Co-authored-by: Junkui Zhang <364772080@qq.com>
2025-08-01 19:46:09 +02:00
Antonio Scandurra
f888f3fc0b Start separating authentication from connection to collab (#35471)
This pull request should be idempotent, but lays the groundwork for
avoiding to connect to collab in order to interact with AI features
provided by Zed.

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
Co-authored-by: Richard Feldman <oss@rtfeldman.com>
2025-08-01 17:37:38 +00:00
Finn Evers
b01d1872cc onboarding: Add the AI page (#35351)
This PR starts the work on the AI onboarding page as well as the
configuration modal

Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Anthony <anthony@zed.dev>
2025-08-01 10:43:59 -04:00
Ben Brandt
e5c6a596a9 agent_ui: More agent notifications (#35441)
Release Notes:

- N/A
2025-08-01 14:29:02 +00:00
Joseph T. Lyons
106aa0d9cc Add default binding to open settings profile selector (#35459)
Release Notes:

- N/A
2025-08-01 05:53:40 +00:00
Marshall Bowers
f7f90593ac inline_completion_button: Replace UserStore with CloudUserStore (#35456)
This PR replaces usages of the `UserStore` in the inline completion
button with the `CloudUserStore`.

Release Notes:

- N/A
2025-08-01 03:25:23 +00:00
Marshall Bowers
8be3f48f37 client: Remove unused subscription_period from UserStore (#35454)
This PR removes the `subscription_period` field from the `UserStore`, as
its usage has been replaced by the `CloudUserStore`.

Release Notes:

- N/A
2025-08-01 03:10:16 +00:00
Peter Tripp
76a8293cc6 editor_tests: Fix for potential race loading editor languages (#35453)
Fix for potential race when loading HTML and JS languages (JS is
slower). Wait for both to load before continue tests.
Observed failure on linux:
[job](https://github.com/zed-industries/zed/actions/runs/16662438526/job/47162345259)
as part of https://github.com/zed-industries/zed/pull/35436

```
    thread 'editor_tests::test_autoclose_with_embedded_language' panicked at crates/editor/src/editor_tests.rs:8724:8:
    assertion failed: `(left == right)`: unexpected buffer text

    Diff < left / right > :
     <body><>
         <script>
    <        var x = 1;<>
    >        var x = 1;<
         </script>
     </body><>
```

Inserted `<` incorrect gets paired bracket inserted `>`.
I believe because the JS language injection hasn't fully loaded.

Release Notes:

- N/A
2025-08-01 03:05:03 +00:00
Marshall Bowers
2315962e18 cloud_api_client: Add accept_terms_of_service method (#35452)
This PR adds an `accept_terms_of_service` method to the
`CloudApiClient`.

Release Notes:

- N/A
2025-08-01 02:50:38 +00:00
Marshall Bowers
f8673dacf5 ai_onboarding: Read the plan from the CloudUserStore (#35451)
This PR updates the AI onboarding to read the plan from the
`CloudUserStore` so that we don't need to connect to Collab.

Release Notes:

- N/A
2025-08-01 02:08:21 +00:00
Marshall Bowers
72d354de6c Update Agent panel to work with CloudUserStore (#35436)
This PR updates the Agent panel to work with the `CloudUserStore`
instead of the `UserStore`, reducing its reliance on being connected to
Collab to function.

Release Notes:

- N/A

---------

Co-authored-by: Richard Feldman <oss@rtfeldman.com>
2025-08-01 01:44:43 +00:00
Marshall Bowers
09b93caa9b Rework authentication for local Cloud/Collab development (#35450)
This PR reworks authentication for developing Zed against a local
version of Cloud and/or Collab.

You will still connect the same way—using the `zed-local` script—but
will need to be running an instance of Cloud locally.

Release Notes:

- N/A
2025-08-01 00:55:17 +00:00
Cole Miller
7c169fc9b5 debugger: Send initialized event from fake server at a more realistic time (#35446)
The spec says:

> ⬅️ Initialized Event
> This event indicates that the debug adapter is ready to accept
configuration requests (e.g. setBreakpoints, setExceptionBreakpoints).
>
> A debug adapter is expected to send this event when it is ready to
accept configuration requests (but not before the initialize request has
finished).

Previously in tests, `intercept_debug_sessions` was just spawning off a
background task to send the event after setting up the client, so the
event wasn't actually synchronized with the flow of messages in the way
the spec says it should be. This PR makes it so that the `FakeTransport`
injects the event right after a successful response to the initialize
request, and doesn't send it otherwise.

Release Notes:

- N/A
2025-07-31 19:45:02 -04:00
Mikayla Maki
2b36d4ec94 Add a field to MultiLSPQuery span showing the current request (#35372)
Release Notes:

- N/A
2025-07-31 22:40:19 +00:00
Peter Tripp
4a82b6c5ee jetbrains: Unmap cmd-k in Jetbrains keymap (#35443)
This only works after a delay in most situations because of the all
chorded `cmd-k` mappings in the so disable them for now.

Reported by @jer-k:
https://x.com/J_Kreutzbender/status/1951033355434336606

Release Notes:

- Undo mapping of `cmd-k` for Git Panel in default Jetbrains keymap
(thanks [@jer-k](https://github.com/jer-k))
2025-07-31 18:29:51 -04:00
Joseph T. Lyons
5feb759c20 Additions for settings profile selector (#35439)
- Added profile selector to `zed > settings` submenu.
- Added examples to the `default.json` docs.
- Reduced length of the setting description that shows on autocomplete,
since it was cutoff in the autocomplete popover.


Release Notes:

- N/A
2025-07-31 22:20:35 +00:00
Marshall Bowers
410348deb0 Acquire LLM token from Cloud instead of Collab for Edit Predictions (#35431)
This PR updates the Zed Edit Prediction provider to acquire the LLM
token from Cloud instead of Collab to allow using Edit Predictions even
when disconnected from or unable to connect to the Collab server.

Release Notes:

- N/A

---------

Co-authored-by: Richard Feldman <oss@rtfeldman.com>
2025-07-31 22:12:04 +00:00
Mikayla Maki
8e7f1899e1 Revert "Increase the number of parallel request handlers per connection" (#35435)
Reverts zed-industries/zed#35046

This made the problem worse ;-;

Release Notes:

- N/A
2025-07-31 17:31:29 -04:00
Marshall Bowers
aea1d48184 cloud_api_client: Add create_llm_token method (#35428)
This PR adds a `create_llm_token` method to the `CloudApiClient`.

Release Notes:

- N/A
2025-07-31 21:01:21 +00:00
Ben Kunkle
c946b98ea1 onboarding: Expand power of theme selector (#35421)
Closes #ISSUE

The behavior of the theme selector is documented above the function,
copied here for reference:
```rust
/// separates theme "mode" ("dark" | "light" | "system") into two separate states
/// - appearance = "dark" | "light"
/// - "system" true/false
/// when system selected:
///  - toggling between light and dark does not change theme.mode, just which variant will be changed
/// when system not selected:
///  - toggling between light and dark does change theme.mode
/// selecting a theme preview will always change theme.["light" | "dark"] to the selected theme,
///
/// this allows for selecting a dark and light theme option regardless of whether the mode is set to system or not
/// it does not support setting theme to a static value
```

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-07-31 16:21:58 -04:00
Anthony Eid
c6947ee4f0 onboarding ui: Add theme preview tiles and button functionality to basic page (#35413)
This PR polishes and adds functionality to the onboarding UI with a
focus on the basic page. It added theme preview tiles, got the Vim,
telemetry, crash reporting, and sign-in button working.

The theme preview component was moved to the UI crate and it now can
have a click handler on it.

Finally, this commit also changed `client::User.github_login` and
`client::UserStore.by_github_login` to use `SharedStrings` instead of
`Strings`. This change was made because user.github_login was cloned in
several areas including the UI, and was cast to a shared string in some
cases too.

Release Notes:

- N/A

---------

Co-authored-by: Remco Smits <djsmits12@gmail.com>
2025-07-31 18:40:41 +00:00
Marshall Bowers
b59f992928 cloud_api_types: Add types for POST /client/llm_tokens endpoint (#35420)
This PR adds some types for the new `POST /client/llm_tokens` endpoint.

Release Notes:

- N/A

Co-authored-by: Richard <richard@zed.dev>
2025-07-31 18:00:29 +00:00
Joseph T. Lyons
0a21b845fa Tighten up settings profile selector modal width (#35419)
Release Notes:

- N/A
2025-07-31 17:31:12 +00:00
Kirill Bulatov
6a8be1714e Fix panic with completion ranges and autoclose regions interop (#35408)
As reported [in
Discord](https://discord.com/channels/869392257814519848/1106226198494859355/1398470747227426948)
C projects with `"` as "brackets" that autoclose, may invoke panics when
edited at the end of the file.

With a single selection-caret (`ˇ`), at the end of the file,
```c
ifndef BAR_H
#define BAR_H

#include <stdbool.h>

int fn_branch(bool do_branch1, bool do_branch2);

#endif // BAR_H
#include"ˇ"
```
gets an LSP response from clangd
```jsonc
{
  "filterText": "AGL/",
  "insertText": "AGL/",
  "insertTextFormat": 1,
  "kind": 17,
  "label": " AGL/",
  "labelDetails": {},
  "score": 0.78725427389144897,
  "sortText": "40b67681AGL/",
  "textEdit": {
    "newText": "AGL/",
    "range": { "end": { "character": 11, "line": 8 }, "start": { "character": 10, "line": 8 } }
  }
}
```

which replaces `"` after the caret (character/column 11, 0-indexed).
This is reasonable, as regular follow-up (proposed in further
completions), is a suffix + a closing `"`:

<img width="842" height="259" alt="image"
src="https://github.com/user-attachments/assets/ea56f621-7008-4ce2-99ba-87344ddf33d2"
/>

Yet when Zed handles user input of `"`, it panics due to multiple
reasons:

* after applying any snippet text edit, Zed did a selection change:
5537987630/crates/editor/src/editor.rs (L9539-L9545)
which caused eventual autoclose region invalidation:
5537987630/crates/editor/src/editor.rs (L2970)

This covers all cases that insert the `include""` text.

* after applying any user input and "plain" text edit, Zed did not
invalidate any autoclose regions at all, relying on the "bracket" (which
includes `"`) autoclose logic to rule edge cases out

* bracket autoclose logic detects previous `"` and considers the new
user input as a valid closure, hence no autoclose region needed.
But there is an autoclose bracket data after the plaintext completion
insertion (`AGL/`) really, and it's not invalidated after `"` handling

* in addition to that, `Anchor::is_valid` method in `text` panicked, and
required `fn try_fragment_id_for_anchor` to handle "pointing at odd,
after the end of the file, offset" cases as `false`

A test reproducing the feedback and 2 fixes added: proper, autoclose
region invalidation call which required the invalidation logic tweaked a
bit, and "superficial", "do not apply bad selections that cause panics"
fix in the editor to be more robust

Release Notes:

- Fixed panic with completion ranges and autoclose regions interop

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2025-07-31 16:18:26 +00:00
Cole Miller
a2aea00253 Bump livekit-rust-sdks with another attempt to fix build failures (#35344)
Includes https://github.com/zed-industries/livekit-rust-sdks/pull/7

Release Notes:

- N/A
2025-07-31 11:39:46 -04:00
张小白
98c66eddb8 windows: Don't create directx device with debug flag when debug layer is missing (#35405)
Release Notes:

- N/A
2025-07-31 14:42:44 +00:00
Marshall Bowers
558bbfffae title_bar: Show the plan from the CloudUserStore (#35401)
This PR updates the user menu in the title bar to show the plan from the
`CloudUserStore` instead of the `UserStore`.

We're still leveraging the RPC connection to listen for `UpdateUserPlan`
messages so that we can get live-updates from the server, but we are
merely using this as a signal to re-fetch the information from Cloud.

Release Notes:

- N/A
2025-07-31 13:56:53 +00:00
Smit Barmase
89ed0b9601 workspace: Fix multiple remote projects not restoring on reconnect or restart and not visible in recent projects (#35398)
Closes #33787

We were not updating SSH paths after initial project was created. Now we
update paths when worktrees are added/removed and serialize these
updated paths. This is separate from workspace because unlike local
paths, SSH paths are not part of the workspace table, but the SSH table
instead. We don't need to update SSH paths every time we serialize the
workspace.

<img width="400"
src="https://github.com/user-attachments/assets/9e1a9893-e08e-4ecf-8dab-1e9befced58b"
/>

Release Notes:

- Fixed issue where multiple remote folders in a project were lost on
reconnect, not restored on restart, and not visible in recent projects.
2025-07-31 16:32:31 +05:30
Julia Ryan
4b9334b910 Fix vim cw at end of words (#35300)
Fixes #35269

Release Notes:

- N/A
2025-07-31 03:48:36 -07:00
Joseph T. Lyons
47af878ebb Do not sort settings profiles (#35389)
After playing with this for a bit, I realize it does not feel good to
not have control over the order of profiles. I find myself wanting to
group similar profiles together and not being able to.

Release Notes:

- N/A
2025-07-31 07:34:35 +00:00
Danilo Leal
5488398986 onboarding: Refine page and component designs (#35387)
Includes adding new variants to the Dropdown and Numeric Stepper
components.

Release Notes:

- N/A
2025-07-31 05:32:18 +00:00
Marshall Bowers
b1a7993544 cloud_api_types: Add more data to the GetAuthenticatedUserResponse (#35384)
This PR adds more data to the `GetAuthenticatedUserResponse`.

We now return more information about the authenticated user, as well as
their plan information.

Release Notes:

- N/A
2025-07-30 23:38:51 -04:00
Marshall Bowers
b90fd4287f client: Don't fetch the authenticated user once we have them (#35385)
This PR makes it so we don't keep fetching the authenticated user once
we have them.

Release Notes:

- N/A
2025-07-31 03:37:02 +00:00
Ben Kunkle
e1e2775b80 docs: Run lychee link check on generated docs output (#35381)
Closes #ISSUE

Following #35310, . This PR makes it so the lychee link check is ran
before building the docs on the md files to catch basic errors, and then
after building on the html output to catch generation errors, including
regressions like the one #35380 fixes.

Release Notes:

- N/A
2025-07-31 02:01:40 +00:00
Joseph T. Lyons
ed104ec5e0 Ensure settings are being adjusted via settings profile selector (#35382)
This PR just pins down the behavior of the settings profile selector by
checking a single setting, `buffer_font_size`, as options in the
selector are changed / selected.

Release Notes:

- N/A
2025-07-31 01:52:02 +00:00
Kainoa Kanter
67a491df50 Use outlined bolt icon for the LSP tool (#35373)
| Before | After |
|--------|--------|
| <img width="266" height="67" alt="image"
src="https://github.com/user-attachments/assets/bbfc75b6-6747-4eb1-ab94-ab098eba5335"
/> | <img width="266" height="67" alt="image"
src="https://github.com/user-attachments/assets/4631be9d-3d5e-4eb6-bf2f-596403fdf014"
/> |

Release Notes:

- Changed the icon of the language servers entry in the status bar.
2025-07-30 21:37:10 -04:00
Marshall Bowers
f003036aec docs: Pin mdbook to v0.4.40 (#35380)
This PR pins `mdbook` to v0.4.40 to fix an issue with sidebar links
having some of their path segments duplicated (e.g.,
`http://localhost:3000/extensions/extensions/developing-extensions.html`.

For reference:

-
https://zed-industries.slack.com/archives/C04S5TU0RSN/p1745439470378339?thread_ts=1745428671.190059&cid=C04S5TU0RSN
-
https://zed-industries.slack.com/archives/C04S5TU0RSN/p1753922478290399

Release Notes:

- N/A
2025-07-31 01:34:26 +00:00
Marshall Bowers
fbc784d323 Use the user from the CloudUserStore to drive the user menu (#35375)
This PR updates the user menu in the title bar to base the "signed in"
state on the user in the `CloudUserStore` rather than the `UserStore`.

This makes it possible to be signed-in—at least, as far as the user menu
is concerned—even when disconnected from Collab.

Release Notes:

- N/A
2025-07-30 20:31:22 -04:00
Piotr Osiewicz
296bb66b65 chore: Move a few more tasks into background_spawn (#35374)
Release Notes:

- N/A
2025-07-30 23:56:47 +00:00
Marshall Bowers
bb1a7ccbba client: Add CloudUserStore (#35370)
This PR adds a new `CloudUserStore` for storing information about the
user retrieved from Cloud instead of Collab.

Release Notes:

- N/A
2025-07-30 18:43:10 -04:00
Marshall Bowers
289f420504 Sort crate members in Cargo.toml (#35371)
This PR sorts the crate members in the `Cargo.toml` file, as they had
gotten unsorted.

Release Notes:

- N/A
2025-07-30 22:35:17 +00:00
张小白
15ad986329 windows: Port to DirectX 11 (#34374)
Closes #16713
Closes #19739
Closes #33191
Closes #26692
Closes #17374
Closes #35077
Closes https://github.com/zed-industries/zed/issues/35205
Closes https://github.com/zed-industries/zed/issues/35262


Compared to the current Vulkan implementation, this PR brings several
improvements:

- Fewer weird bugs
- Better hardware compatibility
- VSync support
- More accurate colors
- Lower memory usage
- Graceful handling of device loss

---

**TODO:**

- [x] Don’t use AGS binaries directly
- [ ] The message loop is using too much CPU when ths app is idle
- [x] There’s a
[bug](https://github.com/zed-industries/zed/issues/33191#issuecomment-3109306630)
in how `Path` is being rendered.

---

Release Notes:

- N/A

---------

Co-authored-by: Kate <kate@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2025-07-30 15:27:58 -07:00
Finn Evers
0d9715325c docs: Add section about terminal contrast adjustments (#35369)
Closes #35146

This change adds documentation for the `terminal.minimum_contrast`
setting to the docs as we've had a lot of reports regarding the contrast
adjustments, yet are missing proper documentation (aside from that in
the `defaults.json`) for it.

Release Notes:

- N/A
2025-07-31 00:19:56 +02:00
Joseph T. Lyons
5ef5f3c5ca Introduce settings profiles (#35339)
Settings Profiles

- [X] Allow profiles to be defined, where each profile can be any of
Zed's settings
    - [X] Autocompletion of all settings
    - [X] Errors on invalid keys
- [X] Action brings up modal that shows user-defined profiles
- [X] Alphabetize profiles
- [X] Ability to filter down via keyboard, and navigate via arrow up and
down
- [X] Auto select Disabled option by default (first in list, after
alphabetizing user-defined profiles)
- [X] Automatically select active profile on next picker summoning
- [X] Persist settings until toggled off
- [X] Show live preview as you select from the profile picker
- [X] Tweaking a setting, while in a profile, updates the profile live
- [X] Make sure actions that live update Zed, such as `cmd-0`, `cmd-+`,
and `cmd--`, work while in a profile
- [X] Add a test to track state

Release Notes:

- Added the ability to configure settings profiles, via the "profiles"
key. Example:

```json
{
  "profiles": {
    "Streaming": {
      "agent_font_size": 20,
      "buffer_font_size": 20,
      "theme": "One Light",
      "ui_font_size": 20
    }
  }
}
```

To set a profile, use `settings profile selector: toggle`
2025-07-30 21:48:24 +00:00
Anthony Eid
2d4afd2119 Polish onboarding page (#35367)
- Added borders to the numeric stepper.
- Changed the hover mouse style for SwitchField.
- Made the edit page toggle buttons more responsive.

Release Notes:

- N/A
2025-07-30 20:35:21 +00:00
Ben Kunkle
afcb8f2a3f onboarding: Wire up settings import (#35366)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-07-30 20:09:11 +00:00
Smit Barmase
cdce3b3620 linux: Fix caps lock not working consistently for certain X11 systems (#35361)
Closes #35316

Bug in https://github.com/zed-industries/zed/pull/34514

Turns out you are not supposed to call `update_key` for modifiers on
`KeyPress`/`KeyRelease`, as modifiers are already updated in
`XkbStateNotify` events. Not sure why this only causes issues on a few
systems and works on others.

Tested on Ubuntu 24.04.2 LTS (initial bug) and Kubuntu 25.04 (worked
fine before too).

Release Notes:

- Fixed an issue where caps lock stopped working consistently on some
Linux X11 systems.
2025-07-31 00:56:36 +05:30
Marshall Bowers
bc6bb42745 Add cloud_api_client and cloud_api_types crates (#35357)
This PR adds two new crates for interacting with Cloud:

- `cloud_api_client` - The client that will be used to talk to Cloud.
- `cloud_api_types` - The types for the Cloud API that are shared
between Zed and Cloud.

Release Notes:

- N/A
2025-07-30 18:57:51 +00:00
Marshall Bowers
7695c4b82e collab: Temporarily add back GET /user endpoint for local development (#35358)
This PR temporarily adds back the `GET /user` endpoint to Collab since
we're still using it for local development.

Will remove it again once we update the local development process to
leverage Cloud.

Release Notes:

- N/A
2025-07-30 18:54:44 +00:00
Smit Barmase
794ade8b6d ui_prompt: Fix prompt dialog is hard to see on large screen (#35348)
Closes #18516

Release Notes:

- Improved visibility of prompt dialog on Linux by dimming the
background.
2025-07-30 23:03:53 +05:30
Smit Barmase
f4bd524d7f ui_prompt: Fix copy version number from About Zed (#35346)
Closes #29361

Release Notes:

- Fixed not selectable version number in About Zed prompt on Linux.
2025-07-30 22:51:59 +05:30
Joseph T. Lyons
9d82e148de Bump Zed to v0.199 (#35343)
Release Notes:

-N/A
2025-07-30 16:53:53 +00:00
Finn Evers
f8d1062484 onboarding: Fix keybindings showing up after a delay (#35342)
This fixes an issue where keybinds would only show up after a delay on
the welcome page upon re-opening it. It also binds one of the buttons to
the corresponding action.

Release Notes:

- N/A
2025-07-30 16:18:14 +00:00
Antonio Scandurra
45af1fcc2f Always double reconnection delay and add jitter (#35337)
Previously, we would pick an exponent between 0.5 and 2.5, which would
cause a lot of clients to try reconnecting in rapid succession,
overwhelming the server as a result.

This pull request always doubles the previous delay and introduces a
jitter that can, at most, double it.

As part of this, we're also increasing the maximum reconnection delay
from 10s to 30s: this gives us more space to spread out the reconnection
requests.

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-07-30 15:34:09 +00:00
Peter Tripp
0aea5acc68 Fix Windows CI logic (#35335)
Fixes unintentional change in
https://github.com/zed-industries/zed/pull/35204

Release Notes:

- N/A
2025-07-30 11:01:20 -04:00
Kirill Bulatov
4d66d967f2 Revert "gpui: Implement support for wlr layer shell (#32651)" (#35331)
This reverts commit c110f78015.

On Linux Wayland, that causes a panic:

```
already mutably borrowed: BorrowError
zed::reliability::init_panic_hook::{{closure}}::h276cc55bf0717738+165677654
std::panicking::rust_panic_with_hook::h409da73ddef13937+139331443
std::panicking::begin_panic_handler::{{closure}}::h159b61b27f96a9c2+139330666
std::sys::backtrace::__rust_end_short_backtrace::h5b56844d75e766fc+139314825
__rustc[4794b31dd7191200]::rust_begin_unwind+139329805
core::panicking::panic_fmt::hc8737e8cca20a7c8+9934576
core::cell::panic_already_mutably_borrowed::h95c7d326eb19a92a+9934403
<gpui::platform::linux::wayland::window::WaylandWindow as gpui::platform::PlatformWindow>::set_app_id::hfa7deae0be264f60+10621600
gpui::window::Window::new::h6505f6042d99702f+80424235
gpui::app::async_context::AsyncApp::open_window::h62ef8f80789a0af2+159117345
workspace::Workspace::new_local::{{closure}}::{{closure}}::h4d786ba393f391b5+160720110
gpui::app::App::spawn::{{closure}}::haf6a6ef0f9bab21c+159294806
async_task::raw::RawTask<F,T,S,M>::run::h9e5f668e091fddff+158375501
<gpui::platform::linux::wayland::client::WaylandClient as gpui::platform::linux::platform::LinuxClient>::run::h69e40feabd97f1bb+79906738
gpui::platform::linux::platform::<impl gpui::platform::Platform for P>::run::hd80e5b2da41c7d0a+79758141
gpui::app::Application::run::h9136595e7346a2c9+163935333
zed::main::h83f7ef86a32dbbfd+165755480
std::sys::backtrace::__rust_begin_short_backtrace::hb6da6fe5454d7688+168421891
std::rt::lang_start::{{closure}}::h51a50d6423746d5f+168421865
std::rt::lang_start_internal::ha8ef919ae4984948+139244369
main+168421964
__libc_start_call_main+29344125649354
__libc_start_main_impl+29344125649547
_start+12961358
```


Release Notes:

- N/A
2025-07-30 13:36:22 +00:00
Kirill Bulatov
93e6b01486 Actually disable ai for now (#35327)
Closes https://github.com/zed-industries/zed/issues/35325

* removes Supermaven actions
* removes copilot-related action
* stops re-enabling edit predictions when disabled

Release Notes:

- N/A
2025-07-30 13:10:05 +00:00
Danilo Leal
00725273e4 agent: Rename "open configuration" action to "open settings" (#35329)
"Settings" is the terminology we use in the agent panel, thus having the
action use "configuration" makes it harder for folks to find this either
via the command palette or the keybinding editor UI in case they'd like
to change it.

Release Notes:

- agent: Renamed the "open configuration" action to "open settings" for
better discoverability and consistency
2025-07-30 09:55:13 -03:00
Piotr Osiewicz
c22fa9adee chore: Move a bunch of foreground tasks into background (#35322)
Closes #ISSUE

Release Notes:

- N/A
2025-07-30 10:29:03 +00:00
Kirill Bulatov
49b75e9e93 Kb/wasm panics (#35319)
Follow-up of https://github.com/zed-industries/zed/pull/34208
Closes https://github.com/zed-industries/zed/issues/35185

Previous code assumed that extensions' language server wrappers may leak
only in static data (e.g. fields that were not cleared on deinit), but
we seem to have a race that breaks this assumption.

1. We do clean `all_lsp_adapters` field after
https://github.com/zed-industries/zed/pull/34334 and it's called for
every extension that is unregistered.
2. `LspStore::maintain_workspace_config` ->
`LspStore::refresh_workspace_configurations` chain is triggered
independently, apparently on `ToolchainStoreEvent::ToolchainActivated`
event which means somewhere behind there's potentially a Python code
that gets executed to activate the toolchian, making
`refresh_workspace_configurations` start timings unpredictable.
3. Seems that toolchain activation overlaps with plugin reload, as 
`2025-07-28T12:16:19+03:00 INFO [extension_host] extensions updated.
loading 0, reloading 1, unloading 0` suggests in the issue logs.

The plugin reload seem to happen faster than workspace configuration
refresh in


c65da547c9/crates/project/src/lsp_store.rs (L7426-L7456)

as the language servers are just starting and take extra time to respond
to the notification.

At least one of the `.clone()`d `adapter`s there is the adapter that got
removed during plugin reload and has its channel closed, which causes a
panic later.

----------------------------

A good fix would be to re-architect the workspace refresh approach, same
as other accesses to the language server collections.
One way could be to use `Weak`-based structures instead, as definitely
the extension server data belongs to extension, not the `LspStore`.
This is quite a large undertaking near the extension core though, so is
not done yet.

Currently, to stop the excessive panics, no more `.expect` is done on
the channel result, as indeed, it now can be closed very dynamically.
This will result in more errors (and backtraces, presumably) printed in
the logs and no panics.

More logging and comments are added, and workspace querying is replaced
to the concurrent one: no need to wait until a previous server had
processed the notification to send the same to the next one.

Release Notes:

- Fixed warm-related panic happening during startup
2025-07-30 09:18:26 +00:00
Marshall Bowers
7be1f2418d Replace zed_llm_client with cloud_llm_client (#35309)
This PR replaces the usage of the `zed_llm_client` with the
`cloud_llm_client`.

It was ported into this repo in #35307.

Release Notes:

- N/A
2025-07-30 00:09:14 +00:00
Mikayla Maki
17a0179f0a Stop caching needlessly (#35308)
Release Notes:

- N/A
2025-07-29 23:38:06 +00:00
Marshall Bowers
b8f3a9101c Add cloud_llm_client crate (#35307)
This PR adds a `cloud_llm_client` crate to take the place of the
`zed_llm_client`.

Release Notes:

- N/A
2025-07-29 23:30:45 +00:00
Ben Kunkle
3824751e61 Add meta description tag to docs pages (#35112)
Closes #ISSUE

Adds basic frontmatter support to `.md` files in docs. The only
supported keys currently are `description` which becomes a `<meta
name="description" contents="...">` tag, and `title` which becomes a
normal `title` tag, with the title contents prefixed with the subject of
the file.

An example of the syntax can be found in `git.md`, as well as below

```md
---
title: Some more detailed title for this page
description: A page-specific description
---

# Editor
```

The above will be transformed into (with non-relevant tags removed)

```html
<head>
    <title>Editor | Some more detailed title for this page</title>
    <meta name="description" contents="A page-specific description">
</head>
<body>
<h1>Editor</h1>
</body>
```

If no front-matter is provided, or If one or both keys aren't provided,
the title and description will be set based on the `default-title` and
`default-description` keys in `book.toml` respectively.

## Implementation details

Unfortunately, `mdbook` does not support post-processing like it does
pre-processing, and only supports defining one description to put in the
meta tag per book rather than per file. So in order to apply
post-processing (necessary to modify the html head tags) the global book
description is set to a marker value `#description#` and the html
renderer is replaced with a sub-command of `docs_preprocessor` that
wraps the builtin `html` renderer and applies post-processing to the
`html` files, replacing the marker value and the `<title>(.*)</title>`
with the contents of the front-matter if there is one.

## Known limitations

The front-matter parsing is extremely simple, which avoids needing to
take on an additional dependency, or implement full yaml parsing.

* Double quotes and multi-line values are not supported, i.e. Keys and
values must be entirely on the same line, with no double quotes around
the value.

The following will not work:

```md
---
title: Some
 Multi-line
 Title
---
```

* The front-matter must be at the top of the file, with only white-space
preceding it

* The contents of the title and description will not be html-escaped.
They should be simple ascii text with no unicode or emoji characters

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Katie Greer <katie@zed.dev>
2025-07-29 23:01:03 +00:00
Finn Evers
57766199cf ui: Clean up toggle button group component (#35303)
This change cleans up the toggle button component a bit by utilizing
const parameters instead and also removes some clones by consuming the
values where possible instead.

Release Notes:

- N/A
2025-07-30 00:47:04 +02:00
Finn Evers
0be83f1c67 emmet: Bump to 0.0.4 (#35305)
This PR bumps the emmet extension to version 0.0.4. 

Includes:
- https://github.com/zed-industries/zed/pull/33865
- https://github.com/zed-industries/zed/pull/32208
- https://github.com/zed-industries/zed/pull/15177

Note that this intentionally does NOT include a change in the
`extension.toml`: The version was bumped incorrectly once in
https://github.com/zed-industries/zed/pull/32208 in both the
`extension.toml` as well as the `Cargo.lock` but not in the
`Cargo.toml`. After that,
https://github.com/zed-industries/zed/pull/33667 only removed the
changes in the `Cargo.lock` but didn't revert the change in the
`extension.toml` file. Hence, the version in the `extension.toml` is
already at `0.0.4`

Release Notes:

- N/A
2025-07-29 22:46:17 +00:00
Marshall Bowers
f0927faf61 collab: Add kill switches for syncing data to and from Stripe (#35304)
This PR adds two kill switches for syncing data to and from Stripe using
Collab.

The `cloud-stripe-events-polling` and `cloud-stripe-usage-meters-sync`
feature flags control whether we use Cloud for polling Stripe events and
updating Stripe meters, respectively.

When we're ready to hand off the syncing to Cloud we can enable the
feature flag to do so.

Release Notes:

- N/A
2025-07-29 22:45:00 +00:00
Marshall Bowers
d2d116cb02 collab: Remove GET /user endpoint (#35301)
This PR removes the `GET /user` endpoint, as it has been moved to
`cloud.zed.dev`.

Release Notes:

- N/A
2025-07-29 22:34:04 +00:00
Ben Kunkle
9f69b53869 keymap_ui: Additional cleanup (#35299)
Closes #ISSUE

Additional cleanup and testing for the keystroke input including
- Focused testing of the "previous modifiers" logic in search mode
- Not merging unmodified keystrokes into previous modifier only bindings
(extension of #35208)
- Fixing a bug where input would overflow in search mode when entering
only modifiers
- Additional testing logic to ensure keystrokes updated events are
always emitted correctly

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-07-29 18:04:00 -04:00
Anthony Eid
48e085a523 onboarding ui: Add editing page to onboarding page (#35298)
I added buttons for inlay values, showing the mini map, git blame, and
controlling the UI/Editor Font/Font size. The only thing left for this
page is some UI clean up and adding buttons for setting import from
VSCode/cursor.

I also added Numeric Stepper as a component preview.

Current state:
<img width="1085" height="585" alt="image"
src="https://github.com/user-attachments/assets/230df474-da81-4810-ba64-05673896d119"
/>


Release Notes:

- N/A
2025-07-29 21:54:58 +00:00
marius851000
3378f02b7e Fix link to panic location on GitHub (#35162)
I had a panic, and it reported


``24c2a465bb/src/crates/assistant_tools/src/edit_agent.rs (L686)
(may not be uploaded, line may be incorrect if files modified)``

The `/src` part seems superfluous, and result in a link that don’t work
(unlike
`24c2a465bb/src/crates/assistant_tools/src/edit_agent.rs (L686)`).
I don’t know why it originally worked (of if it even actually originally
worked properly), but there seems to be no reason to keep that `/src`.

Release Notes:

- N/A
2025-07-29 17:45:46 -04:00
Ridan Vandenbergh
c110f78015 gpui: Implement support for wlr layer shell (#32651)
I was interested in potentially using gpui for a hobby project, but
needed [layer
shell](https://wayland.app/protocols/wlr-layer-shell-unstable-v1)
support for it. Turns out gpui's (excellent!) architecture made that
super easy to implement, so I went ahead and did it.

Layer shell is a window role used for notification windows, lock
screens, docks, backgrounds, etc. Supporting it in gpui opens the door
to implementing applications like that using the framework.

If this turns out interesting enough to merge - I'm also happy to
provide a follow-up PR (when I have the time to) to implement some of
the desirable window options for layer shell surfaces, such as:
- namespace (currently always `""`)
- keyboard interactivity (currently always `OnDemand`, which mimics
normal keyboard interactivity)
- anchor, exclusive zone, margins
- popups

Release Notes:

- Added support for wayland layer shell surfaces in gpui

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-07-29 21:26:30 +00:00
Ben Kunkle
85b712c04e keymap_ui: Clear close keystroke capture on timeout (#35289)
Closes #ISSUE

Introduces a mechanism whereby keystrokes that have a post-fix which
matches the prefix of the stop recording binding can still be entered.
The solution is to introduce a (as of right now) 300ms timeout before
the close keystroke state is wiped.

Previously, with the default stop recording binding `esc esc esc`,
searching or entering a binding ending in esc was not possible without
using the mouse. `e.g.` entering keystroke `ctrl-g esc` and then
attempting to hit `esc` three times would stop recording on the
penultimate `esc` press and the final `esc` would not be intercepted.
Now with the timeout, it is possible to enter `ctrl-g esc`, pause for a
moment, then hit `esc esc esc` and end the recording with the keystroke
input state being `ctrl-g esc`.

I arrived at 300ms for this delay as it was long enough that I didn't
run into it very often when trying to escape, but short enough that a
natural pause will almost always work as expected.

Release Notes:

- Keymap Editor: Added a short timeout to the stop recording keybind
handling in the keystroke input, so that it is now possible using the
default bindings as an example (custom bindings should work as well) to
search for/enter a binding ending with `escape` (with no modifier),
pause for a moment, then hit `escape escape escape` to stop recording
and search for/enter a keystroke ending with `escape`.
2025-07-29 17:24:57 -04:00
Daniel Sauble
5fa212183a Fix animations in the component preview (#33673)
Fixes #33869

The Animation page in the Component Preview had a few issues.

* The animations only ran once, so you couldn't watch animations below
the fold.
* The offset math was wrong, so some animated elements were rendered
outside of their parent container.
* The "animate in from right" elements were defined with an initial
`.left()` offset, which overrode the animation behavior.

I made fixes to address these issues. In particular, every time you
click the active list item, it renders the preview again (which causes
the animations to run again).

Before:


https://github.com/user-attachments/assets/a1fa2e3f-653c-4b83-a6ed-c55ca9c78ad4

After:


https://github.com/user-attachments/assets/3623bbbc-9047-4443-b7f3-96bd92f582bf

Release Notes:

- N/A
2025-07-29 14:22:53 -07:00
David Kleingeld
1501ae0013 Upgrade rodio to 0.21 (#34368)
Hi all,

We just released [Rodio
0.21](https://github.com/RustAudio/rodio/blob/master/CHANGELOG.md)
🥳 with quite some breaking changes. This should take care
of those for zed. I tested it by hopping in and out some of the zed
channels, sound seems to still work.

Given zed uses tracing I also took the liberty of enabling the tracing
feature for rodio.

edit:
We changed the default wav decoder from hound to symphonia. The latter
has a slightly more restrictive license however that should be no issue
here (as the audio crate uses the GPL)

Release Notes:

- N/A
2025-07-29 13:24:34 -07:00
Joseph T. Lyons
3973142324 Adjust Zed badge (#35294)
- Make right side background white
- Fix Zed casing

Release Notes:

- N/A
2025-07-29 15:09:31 -04:00
Cole Miller
7878eacc73 python: Use a single workspace folder for basedpyright (#35292)
Treat the new basedpyright adapter the same as pyright was treated in
#35243.

Release Notes:

- N/A
2025-07-29 19:00:41 +00:00
Joseph T. Lyons
72f8fa6d1e Adjust Zed badge (#35290)
- Inline badges
- Set label background fill color to black
- Uppercase Zed text
- Remove gray padding

Release Notes:

- N/A
2025-07-29 14:24:10 -04:00
Joseph T. Lyons
902c17ac1a Add Zed badge to README.md (#35287)
Release Notes:

- N/A
2025-07-29 14:15:17 -04:00
Ben Kunkle
efa3cc13ef keymap_ui: Test keystroke input (#35286)
Closes #ISSUE

Separate out the keystroke input into it's own component and add a bunch
of tests for it's core keystroke+modifier event handling logic

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-07-29 14:10:51 -04:00
Michael Sloan
65250fe08d cloud provider: Use CompletionEvent type from zed_llm_client (#35285)
Release Notes:

- N/A
2025-07-29 17:28:18 +00:00
Marshall Bowers
77dc65d826 collab: Attach User-Agent to handle connection span (#35282)
This PR makes it so we attach the value from the `User-Agent` header to
the `handle connection` span.

We'll start sending this header in
https://github.com/zed-industries/zed/pull/35280.

Release Notes:

- N/A
2025-07-29 17:06:27 +00:00
Marshall Bowers
f9224b1d74 client: Send User-Agent header on WebSocket connection requests (#35280)
This PR makes it so we send the `User-Agent` header on the WebSocket
connection requests when connecting to Collab.

We use the user agent set on the parent HTTP client.

Release Notes:

- N/A
2025-07-29 16:53:56 +00:00
localcc
aa3437e98f Allow installing from an administrator user (#35202)
Release Notes:

- N/A
2025-07-29 18:03:57 +02:00
Finn Evers
397b5f9301 Ensure context servers are spawned in the workspace directory (#35271)
This fixes an issue where we were not setting the context server working
directory at all.

Release Notes:

- Context servers will now be spawned in the currently active project
root.

---------

Co-authored-by: Danilo Leal <danilo@zed.dev>
2025-07-29 18:03:43 +02:00
localcc
d43f464174 Fix nightly icon (#35204)
Release Notes:

- N/A
2025-07-29 18:01:07 +02:00
localcc
511fdaed43 Allow searching Windows paths with forward slash (#35198)
Release Notes:

- Searching windows paths is now possible with a forward slash
2025-07-29 17:58:28 +02:00
Marshall Bowers
a8bdf30259 client: Fix typo in the error message (#35275)
This PR fixes a typo in the error message for when we fail to parse the
Collab URL.

Release Notes:

- N/A
2025-07-29 15:45:49 +00:00
devjasperwang
2fced602b8 paths: Fix using relative path as custom_data_dir (#35256)
This PR fixes issue of incorrect LSP path args caused by using a
relative path when customizing data directory.

command:
```bash
.\target\debug\zed.exe --user-data-dir=.\target\data
```

before:
```log
2025-07-29T14:17:18+08:00 INFO  [lsp] starting language server process. binary path: "F:\\nvm\\nodejs\\node.exe", working directory: "F:\\zed\\target\\data\\config", args: [".\\target\\data\\languages\\json-language-server\\node_modules/vscode-langservers-extracted/bin/vscode-json-language-server", "--stdio"]
2025-07-29T14:17:18+08:00 INFO  [project::prettier_store] Installing default prettier and plugins: [("prettier", "3.6.2")]
2025-07-29T14:17:18+08:00 ERROR [lsp] cannot read LSP message headers
2025-07-29T14:17:18+08:00 ERROR [lsp] Shutdown request failure, server json-language-server (id 1): server shut down

2025-07-29T14:17:43+08:00 ERROR [project] Invalid file path provided to LSP request: ".\\target\\data\\config\\settings.json"
Thread "main" panicked with "called `Result::unwrap()` on an `Err` value: ()" at crates\project\src\lsp_store.rs:7203:54
cfd5b8ff10/src/crates\project\src\lsp_store.rs#L7203 (may not be uploaded, line may be incorrect if files modified)
```

after:
```log
2025-07-29T14:24:20+08:00 INFO  [lsp] starting language server process. binary path: "F:\\nvm\\nodejs\\node.exe", working directory: "F:\\zed\\target\\data\\config", args: ["F:\\zed\\target\\data\\languages\\json-language-server\\node_modules/vscode-langservers-extracted/bin/vscode-json-language-server", "--stdio"]
```

Release Notes:

- N/A
2025-07-29 15:31:54 +00:00
Peter Tripp
3fc84f8a62 Comment on source of ctrl-m in keymaps (#35273)
Closes https://github.com/zed-industries/zed/issues/23896

Release Notes:

- N/A
2025-07-29 15:29:12 +00:00
Kirill Bulatov
5a218d8323 Add more data to see which extension got leaked (#35272)
Part of https://github.com/zed-industries/zed/issues/35185

Release Notes:

- N/A
2025-07-29 15:24:52 +00:00
Agus Zubiaga
9353ba7887 Fix remaining agent server integration tests (#35222)
Release Notes:

- N/A
2025-07-29 12:40:59 +00:00
Finn Evers
8f952f1b58 gpui: Ensure first tab index is selected on first focus (#35247)
This fixes an issue with tab indices where we would actually focus the
second focus handle on first focus instead of the first one. The test
was updated accordingly.

Release Notes:

- N/A

---------

Co-authored-by: Jason Lee <huacnlee@gmail.com>
2025-07-29 10:30:38 +00:00
Piotr Osiewicz
6c5791532e lsp: Remove Attach enum, default to Shared behaviour (#35248)
This should be a no-op PR, behavior-wise.

Release Notes:

- N/A
2025-07-29 10:07:36 +00:00
Kirill Bulatov
691b3ca238 Cache LSP code lens requests (#35207) 2025-07-29 09:51:58 +03:00
Cole Miller
cfd5b8ff10 python: Uplift basedpyright support into core (#35250)
This PR adds a built-in adapter for the basedpyright language server.

For now, it's behind the `basedpyright` feature flag, and needs to be
requested explicitly like this for staff:

```
  "languages": {
    "Python": {
      "language_servers": ["basedpyright", "!pylsp", "!pyright"]
    }
  }
```

(After uninstalling the basedpyright extension.)

Release Notes:

- N/A
2025-07-29 03:19:31 +00:00
Piotr Osiewicz
e5269212ad lsp/python: Temporarily report just a singular workspace folder instead of all of the roots (#35243)
Temporarily fixes #29133

Co-authored-by: Cole <cole@zed.dev>

Release Notes:

- python: Zed now reports a slightly different set of workspace folders
for Python projects to work around quirks in handling of multi-lsp
projects with virtual environment. This behavior will be revisited in a
near future.

Co-authored-by: Cole <cole@zed.dev>
2025-07-29 00:10:32 +00:00
Bedis Nbiba
d2ef287791 Add runnable support for Deno.test (#34593)
example of detected code:
```ts
Deno.test("t", () => {
  console.log("Hello, World!");
});

Deno.test(function azaz() {
  console.log("Hello, World!");
});
```

I can't build zed locally so I didn't test this, but I think the code is
straightforward enough, hopefully someone else can verify it

Closes #ISSUE

Release Notes:

- N/A
2025-07-29 01:45:41 +02:00
Tom Monaghan
109eddafd0 docs: Fix link in configuration documentation (#35249)
# Summary

The link "under the configuration page" [on this
page](https://zed.dev/docs/configuring-zed#agent) is broken. It should
be linking to [this page](https://zed.dev/docs/ai/configuration).

## Approach

I noted that all other links in this document begin with "./" where the
ai configuration link does not, I also noticed [this
PR](https://github.com/zed-industries/zed/pull/31119) fixing a link with
the same approach. I don't fully understand why this is the fix.

## Previous Approaches

I have tried writing the following redirect in `docs/book.toml`:
`"/ai/configuration.html" = "/docs/ai/configuration.html"`. However this
broke the `mdbook` build with the below error.

```
2025-07-29 08:49:36 [ERROR] (mdbook::utils): 	Caused By: Not redirecting "/Users/tmonaghan/dev/zed/docs/book/ai/configuration.html" to "/docs/ai/configuration.html" because it already exists. Are you sure it needs to be redirected?
```

Release Notes:

- N/A
2025-07-28 22:59:46 +00:00
Kirill Bulatov
798aa50df8 Fix tasks leaked despite workspace window close (#35246)
Closes https://github.com/zed-industries/zed/issues/34932

Release Notes:

- Fixed tasks leaked despite workspace window close
2025-07-28 22:37:48 +00:00
Julia Ryan
11c7b498b3 Fix panic feature flag detection (#35245)
The flag was being checked before feature flags were resolved.

Release Notes:

- N/A
2025-07-28 22:18:20 +00:00
Finn Evers
ca34ead6d9 onboarding: Add proper icon for action (#35241)
This change updates one icon within the onboarding flow to the indended
icon for that entry.

Release Notes:

- N/A
2025-07-28 22:02:12 +00:00
Finn Evers
158f65fd1e gpui: Ensure tab index handles are properly reused across frames (#35235)
This fixes an issue where focus handles with a tab index would get lost
between rendered frames because the focus handles were not reused for
the following paint cycle.

Release Notes:

- N/A
2025-07-28 23:33:20 +02:00
Ben Kunkle
fa6b1a0114 keymap_ui: Fix bug introduced in #35208 (#35237)
Closes #ISSUE

Fixes a bug that was cherry picked onto stable and preview branches
introduced in #35208 whereby modifier keys would show up and not be
removable when editing a keybind

Release Notes:

- (preview only) Keymap Editor: Fixed an issue introduced in v0.197.2
whereby modifier keys would show up and not be removable while recording
keystrokes in the keybind edit modal
2025-07-28 17:27:10 -04:00
Danilo Leal
f3dc842ce6 keymap editor: Make table denser (#35236)
Hopefully, this will make it a bit easier to parse as a whole.

Release Notes:

- Made the keymap editor denser, improving how easy you can parse it at
a glance.
2025-07-28 21:25:48 +00:00
Finn Evers
7ccf8c2f8c onboarding: Continue work on new flow (#35233)
This PR continues the work on the new and revamped onboarding flow.


Release Notes:

- N/A
2025-07-28 23:10:28 +02:00
Peter Tripp
8207621a4a Improve JetBrains keymap for dock toggling (#35234)
Follow-up to: 
- https://github.com/zed-industries/zed/pull/34641
- https://github.com/zed-industries/zed/pull/35230

This improves Zed's behavior with the Jetbrains keymap for toggling
specific docks/sidebars with cmd-0 thru cmd-9 (macos) and alt-0 thru
alt-9 (linux). Added in
https://github.com/zed-industries/zed/pull/34641. Additionally, this
also maps `ctrl-b` / `ctrl-alt-`b to their JetBrains equivalents
(`editor::GoToDefinition` and `editor::GoToDefinitionSplit`) instead of
the default vscode-compatable behavior (toggle left / right dock). This
is because we those specific toggles and a default Hide keyboard
shortcut (`shift-escape`) added in
https://github.com/zed-industries/zed/pull/35230.

Thanks to @thomaseizinger for raising this in:
-
https://github.com/zed-industries/zed/discussions/34643#discussioncomment-13856746

Release Notes:

- Improve support keyboard-based dock show/hide in Jetbrains keymap.
2025-07-28 21:00:01 +00:00
Marshall Bowers
ab90ed41da collab: Remove POST /billing/subscriptions/sync endpoint (#35232)
This PR removes the `POST /billing/subscriptions/sync` endpoint, as it
has been moved to `cloud.zed.dev`.

Release Notes:

- N/A
2025-07-28 20:22:54 +00:00
Peter Tripp
994d400ab8 Map shift-escape in the Jetbrains keymaps (#35230)
Release Notes:

- Added support for closing docks (sidebars) with `shift-escape` in the
Jetbrains keymaps.
2025-07-28 19:59:01 +00:00
Smit Barmase
cf13a76618 editor: Prioritize fuzzy score over sort positions in code completion sort (#35229)
We already prioritize matches that come after separators like `_`: 


cef7d53607/crates/fuzzy/src/matcher.rs (L274)

and deprioritize non-consecutive matches using distance penalty:


cef7d53607/crates/fuzzy/src/matcher.rs (L281)

In completion sort, letting fuzzy score be the primary sort factor and
sort positions be secondary yields better results upon testing. We still
need sort positions because of this kind of test case:


cef7d53607/crates/editor/src/code_completion_tests.rs (L195-L217)

Before/After:

<img height="250" alt="image"
src="https://github.com/user-attachments/assets/38495576-add6-4435-93f0-891f48ec9263"
/>
<img height="250" alt="image"
src="https://github.com/user-attachments/assets/0c73b835-0e23-4e30-a3ff-28bb56294239"
/>


Release Notes:

- N/A
2025-07-29 01:15:29 +05:30
Richard Feldman
b64977f6f4 Use zed settings to detect .zed folders (#35224)
Behind-the-scenes enhancement of
https://github.com/zed-industries/zed/pull/35221

Release Notes:

- N/A
2025-07-28 19:38:20 +00:00
Finn Evers
c3920b806b editor: Ensure code actions menu doesn't grow beyond its max size (#35211)
This resolves the same issue as fixed by
d295409f0f
for the code actions menu.

No release notes since this only occurs on Nightly.

Release Notes:

- N/A
2025-07-28 21:07:58 +02:00
Peter Tripp
055a9f21a0 Fix keybinds and 'No default binding' shown in docs (#35227)
Previously:
<img width="724" height="234" alt="Screenshot 2025-07-28 at 14 49 50"
src="https://github.com/user-attachments/assets/b5203316-221d-4af5-8190-124f9b2b4cdf"
/>


Release Notes:

- N/A
2025-07-28 14:58:43 -04:00
Richard Feldman
a55bd49c8d Allow edit tool to access files outside project (with confirmation) (#35221)
Now the edit tool can access files outside the current project (just
like the terminal tool can), but it's behind a prompt (unlike other edit
tool actions).

Release Notes:

- The edit tool can now access files outside the current project, but
only if the user grants it permission to.
2025-07-28 14:01:34 -04:00
Julia Ryan
a57e4dc8a8 Upload debug info to sentry.io in nightly builds (#35089)
This is a preparatory change which will allow us to use sentry for crash
reporting once we start uploading minidumps.

Release Notes:

- N/A
2025-07-28 17:42:36 +00:00
Ben Kunkle
eef15abbe4 keymap_ui: Additional keystroke input polish (#35208)
Closes #ISSUE

Fixed various issues and improved UX around the keystroke input
primarily when used for keystroke search.

Release Notes:

- Keymap Editor: FIxed an issue where the modifiers used to activate
keystroke search would appear in the keystroke search
- Keymap Editor: Made it possible to search for repeat modifiers, such
as a binding with `cmd-shift cmd`
- Keymap Editor: Made keystroke search matches match based on ordered
(not necessarily contiguous) runs. For example, searching for `cmd
shift-j` will match `cmd-k cmd-shift-j alt-q` and `cmd-i g shift-j` but
not `alt-k shift-j` or `cmd-k alt-j`
- Keymap Editor: Fixed the clear keystrokes binding (`delete` by
default) not working in the keystroke input
2025-07-28 13:15:21 -04:00
Danilo Leal
5aa8425449 Update Nightly app icon (#35215)
Follow-up to https://github.com/zed-industries/zed/pull/35119.

Release Notes:

- N/A
2025-07-28 17:01:07 +00:00
Todd L Smith
05d3473df8 Fix Nushell environment variables (#35166)
- Fixes environment variable ingestion for Nushell.

Closes #35056

Release Notes:

- N/A
2025-07-28 13:00:41 -04:00
Cole Miller
3dc36dc7c3 git: Touch up amend UX (#35114)
Follow-up to #26114

- Ensure that the previous commit message is filled in when toggling on
amend mode from the context menu
- Fix keybinding flicker in context menu

Release Notes:

- N/A
2025-07-28 12:21:07 -04:00
Agus Zubiaga
fd68265efd Fix integration tests for claude (#35212)
Release Notes:

- N/A
2025-07-28 16:18:01 +00:00
devjasperwang
5e2da042ef debugger: Fix the terminal popping up when the Rust debugger starts on Windows (#35125)
This PR fixs a terminal popping up when debugging Rust on Windows.


Release Notes:

- N/A

---------

Co-authored-by: 张小白 <364772080@qq.com>
2025-07-28 22:44:22 +08:00
Agus Zubiaga
c2fc70eef7 ACP over MCP server impl (#35196)
Release Notes:

- N/A

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-07-28 14:14:10 +00:00
Danilo Leal
b02ae771cd ai onboarding: Add first-open upsell card (#35199)
Release Notes:

- N/A
2025-07-28 11:00:59 -03:00
张小白
3a1b1847c1 Fix debugger on Windows (#35180)
Closes #33429

Release Notes:

- N/A
2025-07-28 22:00:26 +08:00
Piotr Osiewicz
4aae7aed93 debugger: Fix not being able to stop a Go debug session when no breakpoints were ever hit (#35190)
Fixes #35030

Release Notes:

- debugger: Fixed a bug where a Go debug session could not be stopped if
no breakpoint was ever hit.
2025-07-28 13:14:36 +00:00
Danilo Leal
cef7d53607 collab: Refine call buttons design (#35007)
Making icons consistent, adjusting spacing, and moving the "Leave Call"
button to be the very last, which makes more sense to me than the
"Share" button being the last. Sharing your project is still part of the
call, so in the left edge of the button strip is where, conceptually,
the option to end the call should be, I think!

Release Notes:

- N/A
2025-07-28 10:01:31 -03:00
Maksim Bondarenkov
3ad0546b0d Re-add TestScreenCaptureStream implementation for mock_client to fix FreeBSD and MinGW builds (#35191)
The implementation was removed in #31506. re-added it to match the
current implementation from `gpui::test`

Closes #35189

Release Notes:

- N/A
2025-07-28 14:02:48 +02:00
Piotr Osiewicz
e38f5759c4 ci: Use cargo update --locked instead of --frozen (#35192)
This fixes false positives when e.g. bumping git deps

Release Notes:

- N/A
2025-07-28 12:01:00 +00:00
Robert Fratto
2566acc2e7 go: Support benchmarks named "Benchmark" (#35167)
The regular expression for benchmarks was enforcing using a suffix
(e.g., `BenchmarkFoo`), but `Benchmark` is a valid benchmark name, just
as `Test` is a valid test name, and `Fuzz` is a valid fuzz test name.

Release Notes:

- Add support for running Go benchmarks named "Benchmark"
2025-07-28 10:40:46 +00:00
Smit Barmase
45b3af713e doc: Add docs for Forcing X11 scale factor (#35181)
Associated PR: https://github.com/zed-industries/zed/pull/34265
Recent Discussion:
https://github.com/zed-industries/zed/issues/33987#issuecomment-3125936302

Release Notes:

- N/A
2025-07-28 14:31:27 +05:30
marius851000
ee9b60e60c gpui: Fix inset being used in SSD on Wayland (#35151)
Closes #31330

Second parts of https://github.com/zed-industries/zed/pull/31335

While the initial fix set the inset during drawing, that was after the
window was resized, resulting in needing to manually resize the window
for the change to properly take effect.

I updated the code to not make the Wayland renderer rely on
`client_inset` being updated by the API user to match with the
decoration mode (given it is supposed to only be used when using CSD).

I might later try to generalize that, and eventually make the
client_inset only defined on window creation (instead of inside
`client_side_decorations`, that would need testing on X) (and maybe also
allow configuration for shadow, but it’s not something I need).

Release Notes:

- Fixed switching from client side decoration to server side decoration
on Wayland
2025-07-28 11:36:00 +03:00
lowlandghost
2a0aad0aaa Docs: Fix invalid JSON syntax in Visual Customizations - Editor Scrollbar and Minimap (#35159)
I was on the [Visual Customiztions - Editor
Scrollbar](https://zed.dev/docs/visual-customization#editor-scrollbar)
section of the docs, and copy and pasted the code block into my personal
Zed settings and saw there was a syntax error.

This is a PR to add a missing comma and fix the syntax error in the
docs.

First time contributing, please let me know if I missed any
steps/important info.

Release Notes:

- N/A
2025-07-27 20:56:55 +03:00
Danilo Leal
e7b5d93b7c docs: Add more improvements to the AI docs (#35160)
Follow-up to https://github.com/zed-industries/zed/pull/35133 with some
more findings upon re-reviewing it again.

Release Notes:

- N/A
2025-07-27 14:06:18 -03:00
Ben Brandt
c995d45bd9 agent_servers: Include result text in Claude error messages (#35156)
This will better surfaces issues that are classified as "success" but
actually have a more meaningful error message attached.

Release Notes:

- N/A
2025-07-27 13:58:02 +00:00
Ben Brandt
a5b7cfd128 agent_servers: Use built-in interrupt handling for Claude sessions (#35154)
We no longer have to stop and restart the entire process. 
I left in the Start/Resume mode handling since we will likely need to
handle restarting Claude in other situations.

Release Notes:

- N/A
2025-07-27 13:50:04 +00:00
Marshall Bowers
89e88c245e extension_host: Add npm:install capability (#35144)
This PR adds a new `npm:install` capability for installing npm packges
in extensions.

Currently all npm packages are allowed.

Release Notes:

- N/A
2025-07-26 22:40:02 +00:00
Marshall Bowers
2a0170dc3c extension: Reorganize capabilities (#35143)
This PR reorganizes the capabilities within the `extension` crate to
make it easier to add more.

Release Notes:

- N/A
2025-07-26 22:05:22 +00:00
Marshall Bowers
6a9a539b10 extension_host: Add capability for downloading files (#35141)
This PR adds a new capability for downloading files in extensions.

Currently all file downloads are allowed.

Release Notes:

- N/A
2025-07-26 21:33:16 +00:00
Marshall Bowers
d7b403e981 extension_host: Refactor capability checks (#35139)
This PR refactors the extension capability checks to be centralized in
the `CapabilityGranter`.

Release Notes:

- N/A
2025-07-26 20:53:19 +00:00
Danilo Leal
290f84a9e1 docs: Restructure and improve AI configuration docs (#35133)
Adding a number of settings that weren't documented, restructuring
things a bit to separate what is model-related settings from agent panel
usage-related settings, adding the recently introduced `disable_ai` key,
and more.

Release Notes:

- Improved docs around configuring and using AI in Zed
2025-07-26 16:06:12 +00:00
Finn Evers
08402e25e0 ui: Fix scrollbar mouse down handler (#35131)
Follow-up to https://github.com/zed-industries/zed/pull/35121 - in the
process of adding the check for the left mouse button to the guard
(which was practically already there before, just not in the mouse-down
listener), I accidentally removed the negation for the bounds check.
This PR fixes this.

Release Notes:

- N/A
2025-07-26 15:45:13 +00:00
Finn Evers
e911364664 ui: Clean up scrollbar component (#35121)
This PR does some minor cleanup to the scrollbar component. Namely, it
removes some clones, reduces the amount of unnecessary notifies and
ensures the scrollbar hover state is more accurately updated.

Release Notes:

- N/A
2025-07-26 00:08:20 +00:00
Danilo Leal
4854f83e8c agent: Fix settings view switch field about permissions for running commands (#35120)
Follow-up to https://github.com/zed-industries/zed/pull/34866, where I
mistakenly didn't update the copy and id for this item.

Closes https://github.com/zed-industries/zed/issues/34850 — that's
because the edit tool runs by default, but the terminal tool does not. I
updated the original copy to reflect this, which should be enough to
close the issue, given that in terms of behavior, it is working
correctly.

Release Notes:

- agent: Fixed duplicated settings item in the agent panel as well as
improve copy for the setting to allow running commands without
permission.
2025-07-26 00:00:44 +00:00
Danilo Leal
6a79c4e50a ui: Add ToggleButtonGroup component (#35118)
<img width="600" height="704" alt="CleanShot 2025-07-25 at 8  03 04@2x"
src="https://github.com/user-attachments/assets/3d2b29ba-e0fd-4231-bb80-746903d61481"
/>

Release Notes:

- N/A

---------

Co-authored-by: MrSubidubi <dev@bahn.sh>
2025-07-25 20:32:53 -03:00
Danilo Leal
aea309160b Update the new Nightly app icon (#35119)
Follow-up to https://github.com/zed-industries/zed/pull/35104, with some
tiny design updates.

Release Notes:

- N/A
2025-07-25 23:29:51 +00:00
Smit Barmase
43d0aae617 languages: Fix Bash indentation issues with multi-cursors, newlines, and keyword outdenting (#35116)
Closes #34390

This PR fixes several Bash indentation issues:

- Adding indentation or comment using multi cursors no longer breaks
relative indentation
- Adding newline now places the cursor at the correct indent
- Typing a valid keyword triggers context-aware auto outdent

It also adds tests for all of them.

Release Notes:

- Fixed various issues with handling indentation in Bash.
2025-07-26 04:58:10 +05:30
Alvaro Parker
07252c3309 git: Enable git stash in git panel (#32821)
Related discussion #31484

Release Notes:

- Added a menu entry on the git panel to git stash and git pop stash. 

Preview: 


![Screenshot-2025-06-17_08:26:36](https://github.com/user-attachments/assets/d3699ba4-511f-4c7b-a7cc-00a295d01f64)

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-07-25 23:15:54 +00:00
Max Brunsfeld
4d00d07df1 Render paths to a single fixed-size MSAA texture (#34992)
This is another attempt to solve the same problem as
https://github.com/zed-industries/zed/pull/29718, while avoiding the
regression on Intel GPUs.

###  Background

Currently, on main, all paths are first rendered to an intermediate
"atlas" texture, similar to what we use for rendering glyphs, but with
multi-sample antialiasing enabled. They are then drawn into our actual
frame buffer in a separate pass, via the "path sprite" shaders.

Notably, the intermediate texture acts as an "atlas" - the paths are
laid out in a non-overlapping way, so that each path could be copied to
an arbitrary position in the final scene. This non-overlapping approach
makes a lot sense for Glyphs (which are frequently re-used in multiple
places within a frame, and even across frames), but paths do not have
these properties.
* we clear the atlas every frame
* we rasterize each path separately. there is no deduping.

The problem with our current approach is that the path atlas textures
can end up using lots of VRAM if the scene contains many paths. This is
more of a problem in other apps that use GPUI than it is in Zed, but I
do think it's an issue for Zed as well. On Windows, I have hit some
crashes related to GPU memory.

In https://github.com/zed-industries/zed/pull/29718, @sunli829
simplified path rendering to just draw directly to the frame buffer, and
enabled msaa for the whole frame buffer. But apparently this doesn't
work well on Intel GPUs because MSAA is slow on those GPUs. So we
reverted that PR.

### Solution

With this PR, we rasterize paths to an intermediate texture with MSAA.
But rather than treating this intermediate texture like an *atlas*
(growing it in order to allocate non-overlapping rectangles for every
path), we simply use a single fixed-size, color texture that is the same
size as thew viewport. In this texture, we rasterize the paths in their
final screen position, allowing them to overlap. Then we simply blit
them from the resolved texture to the frame buffer.

### To do

* [x] Implement for Metal
* [x] Implement for Blade
* [x] Fix content masking for paths
* [x] Fix rendering of partially transparent paths
* [x] Verify that this performs well on Intel GPUs (help @notpeter 🙏 )
* [ ] Profile and optimize

Release Notes:

- N/A

---------

Co-authored-by: Junkui Zhang <364772080@qq.com>
2025-07-25 14:39:24 -07:00
Cole Miller
bf8e4272bc Add a CI check that Cargo.lock is up to date (#35111)
Should catch cases like #33667 where a dependency was bumped in
Cargo.toml without a corresponding lockfile update. (This can happen
when committing from outside of Zed or if rust-analyzer isn't running.)
Passing `--frozen --workspace` should ensure this doesn't fail just
because there's a newer patch version of some third-party dependency,
and prevent it from taking long.

We only need to run this on macOS because Cargo.lock is
platform-independent.

Release Notes:

- N/A
2025-07-25 15:37:58 -04:00
Marshall Bowers
f787f7d291 Update Cargo.lock (#35106)
This PR updates the `Cargo.lock` file, as it seems like it wasn't fully
updated in https://github.com/zed-industries/zed/pull/35103.

Release Notes:

- N/A
2025-07-25 18:20:18 +00:00
Danilo Leal
0e7eea0d10 Add new Nightly app icons (#35104)
Release Notes:

- N/A
2025-07-25 17:39:33 +00:00
Cole Miller
ff67f18e0d Bump livekit dep to try to fix flaky builds (#35103)
Let's see if https://github.com/zed-industries/livekit-rust-sdks/pull/6
helps.

Release Notes:

- N/A

---------

Signed-off-by: Cole Miller <cole@zed.dev>
2025-07-25 17:36:58 +00:00
Anthony Eid
4abe14f94a keymap ui: Resize columns on double click improvement (#35095)
This PR improves the behavior of resetting a column's size by
double-clicking on its column handle. We now shrink/grow to the side
that has more leftover/additional space.

We also improved the below

1. dragging was a couple of pixels off to the left because we didn't
take the column handle’s width into account.
2. Column dragging now has memory and will shift things to their exact
position when reversing a drag before letting the drag handle go.
3. Improved our test infrastructure.
4. Double clicking on a column's header resizes the column

Release Notes:

- N/A

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-07-25 13:19:05 -04:00
Kainoa Kanter
cd50958727 Add icon for SurrealQL files (#34855)
Release Notes:

- Added icon for SurrealQL (`.surql`) files

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-07-25 17:17:16 +00:00
Umesh Yadav
2f812c339c agent_ui: Fix delay when loading keybindings in the Agent panel settings (#34954)
Fixes a annoying lag in agent settings panel where the keybindings would
show up after a lag. Close to 1-2 secs. It was a simple fix previously
we were not passing the focus handler to context menu which made the
keybindings lookup slower compared to other parts like git panel and
title bar profile dropdown.

| Before | After |
|--------|--------|
| <video
src="https://github.com/user-attachments/assets/1f9c1861-d089-41d3-8a89-4334d7b2f43a"
controls preload></video> | <video
src="https://github.com/user-attachments/assets/fa33d58d-a1ae-48cf-bff7-8e0ee07ed4e1"
controls preload></video> |





Release Notes:

- Fix delay when loading keybindings in the Agent panel settings
2025-07-25 13:55:38 -03:00
Haojian Wu
993d5753d5 docs: Add a missing "," in the C/C++ debugger configuration (#35098)
Release Notes:

- N/A
2025-07-25 16:14:44 +00:00
Piotr Osiewicz
abb3ed1ed1 git: Fix commit modal contents being searchable (#35099)
Fixes #35093

Release Notes:

- Fixed Git commit editor being searchable.
2025-07-25 15:48:09 +00:00
Piotr Osiewicz
985350f9e8 terminal: Support ~ in cwd field of task definitions (#35097)
Closes #35022

Release Notes:

- Fixed `~` not being expanded correctly in `cwd` field of task
definitions.
2025-07-25 15:39:50 +00:00
Justin Su
0e9d955e9b Hide Copilot commands when AI functionality is disabled (#35055)
Follow-up of https://github.com/zed-industries/zed/pull/34896

Related to https://github.com/zed-industries/zed/issues/31346

cc @rtfeldman

Release Notes:

- Hide copilot commands when AI functionality is disabled
2025-07-25 10:53:57 -04:00
etimvr
5de544eb4b Fix unnecessary Ollama model loading (#35032)
Closes https://github.com/zed-industries/zed/issues/35031

Similar solution as in https://github.com/zed-industries/zed/pull/30589

Release Notes:

- Fix unnecessary ollama model loading
2025-07-25 16:58:05 +03:00
Umesh Yadav
f21ba9e2c6 lmstudio: Propagate actual error message from server (#34538)
Discovered in this issue: #34513

Previously, we were propagating deserialization errors to users when
using LMStudio, instead of the actual error message sent from the
LMStudio server. This change will help users understand why their
request failed while streaming responses.

Release Notes:

- lmsudio: Display specific backend error messaging on failure rather
than generic ones

---------

Signed-off-by: Umesh Yadav <git@umesh.dev>
Co-authored-by: Peter Tripp <peter@zed.dev>
2025-07-25 09:36:43 -04:00
Julia Ryan
a2408f353b Don't separately rebuild crates for the build platform (#35084)
macos:
- `cargo build`: 1838 -> 1400
- `cargo build -r`1891 -> 1400

linux:
- `cargo build`: 1893 -> 1455
- `cargo build -r`: 1893 -> 1455

windows: 
- `cargo build`: 1830 -> 1392
- `cargo build -r`: 1880 -> 1392

We definitely want this change for debug builds, for release builds it's
_possible_ that it pessimizes the critical path, but we'll see if it
impacts CI times before merging.

Release Notes:

- N/A

---------

Co-authored-by: Rahul Butani <rrbutani@users.noreply.github.com>
2025-07-25 13:12:55 +00:00
Julia Ryan
9071341a1d Add TestCrash action (#35088)
This triggers a crash that avoids our panic-handler, which is useful for
testing that our crash reporting works even when you don't get a panic.

Release Notes:

- N/A
2025-07-25 05:40:26 -07:00
Piotr Osiewicz
57b463fd0d typescript: Fix handling of jest/vitest tests with regex characters in name (#35090)
Closes #35065

Release Notes:

- JavaScript/TypeScript tasks: Fixed handling of Vitest/Jest tests with
regex-specific characters in their name.
2025-07-25 12:23:52 +00:00
Piotr Osiewicz
631f9a1b31 worktree: Improve performance with large # of repositories (#35052)
In this PR we've reworked how git status updates are processed. Most
notable change is moving the processing into a background thread (and
splitting it across multiple background workers). We believe it is safe
to do so, as worktree events are not deterministic (fs updates are not
guaranteed to come in any order etc), so I've figured that git store
should not be overly order-reliant anyways.

Note that this PR does not solve perf issues wholesale - other parts of
the system are still slow to process stuff (which I plan to nuke soon).

Related to #34302

Release Notes:

- Improved Zed's performance in projects with large # of repositories

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
2025-07-25 13:18:19 +02:00
Agus Zubiaga
af0c909924 McpServerTool output schema (#35069)
Add an `Output` associated type to `McpServerTool`, so that we can
include its schema in `tools/list`.

Release Notes:

- N/A
2025-07-25 02:57:18 +00:00
Agus Zubiaga
15c9da4ea4 Add ability to register tools in McpServer (#35068)
Makes it easier to add tools to a server by implementing a trait

Release Notes:

- N/A
2025-07-25 02:19:20 +00:00
morgankrey
b446d66be7 Telemetry docs cleanup (#35060)
Release Notes:

- N/A
2025-07-24 19:25:39 -06:00
morgankrey
a0f2019b6f Add sales tax to docs (#35059)
Release Notes:

- N/A
2025-07-24 19:18:40 -06:00
Smit Barmase
6e4f747041 project_panel: Fix autoscroll to treat entries behind sticky items as out of viewport (#35067)
Closes #34831

Autoscroll centers items only if they’re out of viewport. Before this
PR, entry behind sticky items was not considered out of viewport, and
hence actions like `reveal in project panel` or focusing buffer would
not autoscroll that entry into the view in that case.

This PR fixes that by using recently added `scroll_to_item_with_offset`
in https://github.com/zed-industries/zed/pull/35064.

Release Notes:

- Fixed issue where `pane: reveal in project panel` action was not
working if the entry was behind sticky items.
2025-07-25 06:21:38 +05:30
Daniel Sauble
4ee52433ae Do not subtract gutter margin twice from the editor width (#34564)
Closes #33176

In auto-height layouts, horizontal autoscroll can occur right before
soft wrapping is triggered. This seems to be caused by gutter margin
being subtracted twice from the editor width.

If we subtract gutter width only once, the horizontal autoscroll
behavior goes away.

Before:


https://github.com/user-attachments/assets/03b6687e-3c0e-4b34-8e07-a228bcc6f798

After:


https://github.com/user-attachments/assets/84e54088-b5bd-4259-a193-d9fcf32cd3a7

Release Notes:

- Fixes issue with auto-height layouts where horizontal autoscroll is
triggered right before text wraps

---------

Co-authored-by: MrSubidubi <finn@zed.dev>
2025-07-25 00:00:59 +00:00
Smit Barmase
f78a112387 gpui: Add scroll_to_item_with_offset to UniformListScrollState (#35064)
Previously we had `ScrollStrategy::ToPosition(usize)` which lets you
define the offset where you want to scroll that item to. This is the
same as `ScrollStrategy::Top` but imagine some space reserved at the
top.

This PR removes `ScrollStrategy::ToPosition` in favor of
`scroll_to_item_with_offset` which is the method to do the same. The
reason to add this method is that now not just `ScrollStrategy::Top` but
`ScrollStrategy::Center` can also uses this offset to center the item in
the remaining unreserved space.

```rs
// Before
scroll_handle.scroll_to_item(index, ScrollStrategy::ToPosition(offset));

// After
scroll_handle.scroll_to_item_with_offset(index, ScrollStrategy::Top, offset);

// New! Centers item skipping first x items
scroll_handle.scroll_to_item_with_offset(index, ScrollStrategy::Center, offset);
```

This will be useful for follow up PR.

Release Notes:

- N/A
2025-07-25 05:13:09 +05:30
Zachary Hamm
66acc2698a git_hosting_providers: Support GitHub remote URLs that start with a slash (#34134)
Remote URLs like `git@github.com:/zed-industries/zed` are valid git
remotes, but currently Zed does not parse them correctly. The result is
invalid "permalink" generation, and presumably other bugs anywhere that
git remote urls are required for the current repository. This ensures
Zed will parse those URLs correctly.

Release Notes:

- Improved support for GitHub remote urls where the
username/organization is preceded by an extra `/` to match the behavior
of `git`, `gh` and `github.com`.

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-07-24 18:25:21 -04:00
Peter Tripp
707df51664 Fix environment loading with tcsh (#35054)
Closes https://github.com/zed-industries/zed/issues/34973

Fixes an issue where environment variables were not loaded when the
user's shell was tcsh and instead a file named `0` was dumped in the
current working directory with a copy of your environment variables as
json.

Follow-up to: 
- https://github.com/zed-industries/zed/pull/35002
- https://github.com/zed-industries/zed/pull/33599

Release Notes:

- Fixed a regression with loading environment variables in nushell
2025-07-24 15:39:13 -04:00
Mikayla Maki
13df1dd5ff Increase the number of parallel request handlers per connection (#35046)
Discussed this with @ConradIrwin. The problem we're having with collab
is that a bunch of LSP requests take a really long time to resolve,
particularly `RefreshCodeLens`. But Those requests are sharing a limited
amount of concurrency that we've allocated for all message traffic on
one connection. That said, there's not _that_ many concurrent requests
made at any one time. The burst traffic seems to top out in the low
hundreds for these requests. So let's just expand the amount of space in
the queue to accommodate these long-running requests while we work on
upgrading our cloud infrastructure.

Release Notes:

- N/A

Co-authored-by: finn <finn@zed.dev>
2025-07-24 11:44:26 -07:00
Peter Tripp
1f7ff956bc Fix environment loading with nushell (#35002)
Closes https://github.com/zed-industries/zed/issues/34739

I believe this is a regression introduced here:
- https://github.com/zed-industries/zed/pull/33599

Release Notes:

- Fixed a regression with loading environment variables in nushell
2025-07-24 14:29:28 -04:00
Agus Zubiaga
2d0f10c48a Refactor to use new ACP crate (#35043)
This will prepare us for running the protocol over MCP

Release Notes:

- N/A

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Richard Feldman <oss@rtfeldman.com>
2025-07-24 14:39:29 -03:00
Joseph T. Lyons
45ddf32a1d Fix variable name in project type reporting (#35045)
Release Notes:

- N/A
2025-07-24 17:08:02 +00:00
Kirill Bulatov
2658b2801e Shutdown language servers better (#35038)
Follow-up of https://github.com/zed-industries/zed/pull/33417

* adjust prettier mock LSP to handle `shutdown` and `exit` messages
* removed another `?.log_err()` backtrace from logs and improved the
logging info
* always handle the last parts of the shutdown logic even if the
shutdown response had failed

Release Notes:

- N/A
2025-07-24 15:24:53 +00:00
Richard Feldman
2a9355a3d2 Don't auto-retry in certain circumstances (#35037)
Someone encountered this in production, which should not happen:

<img width="1266" height="623" alt="Screenshot 2025-07-24 at 10 38
40 AM"
src="https://github.com/user-attachments/assets/40f3f977-5110-4808-a456-7e708d953b3b"
/>

This moves certain errors into the category of "never retry" and reduces
the number of retries for some others. Also it adds some diagnostic
logging for retry policy.

It's not a complete fix for the above, because the underlying issues is
that the server is sending a HTTP 403 response and although we were
already treating 403s as "do not retry" it was deciding to retry with 2
attempts anyway. So further debugging is needed to figure out why it
wasn't going down the 403 branch by the time the request got here.

Release Notes:

- N/A
2025-07-24 11:11:26 -04:00
Smit Barmase
fa788a39a4 project_panel: Reuse index_for_entry in index_for_selection (#35034)
Just refactor I came across while working on another issue.
`index_for_entry` and `index_for_selection` have the exact same logic,
here we can simply reuse `index_for_entry` for `index_for_selection`.

Release Notes:

- N/A
2025-07-24 20:07:38 +05:30
Pablo Ramón Guevara
7cdd808db2 helix: Fix replace in helix mode (#34789)
Closes https://github.com/zed-industries/zed/issues/33076

Release Notes:

- Fixed replace command on helix mode: now it actually replaces what was
selected and keeps the replaced text selected to better match helix
2025-07-24 08:29:58 -06:00
Danilo Leal
29332c1962 ai onboarding: Add overall fixes to the whole flow (#34996)
Closes https://github.com/zed-industries/zed/issues/34979

Release Notes:

- N/A

---------

Co-authored-by: Agus Zubiaga <hi@aguz.me>
Co-authored-by: Ben Kunkle <Ben.kunkle@gmail.com>
2025-07-24 11:26:15 -03:00
Peter Tripp
fab450e39d Fix invalid regular expressions highlighting all search fields (#35001)
Closes https://github.com/zed-industries/zed/issues/34969
Closes https://github.com/zed-industries/zed/issues/34970

Only highlight the search field on regex error (buffer search and
project search).
Clear errors when the buffer search hidden so stale errors aren't shown
on next search.

Before (all fields highlighted red): 
<img width="631" height="180" alt="Screenshot 2025-07-23 at 22 59 45"
src="https://github.com/user-attachments/assets/a91d3090-1bae-4718-a1f5-0a1237559ff2"
/>

After (only query field highlighted red): 
<img width="632" height="187" alt="Screenshot 2025-07-23 at 23 10 49"
src="https://github.com/user-attachments/assets/6ae72c84-9333-4cdb-907b-afb7a0a6e808"
/>

Release Notes:

- Improved highlighting of regex errors in search dialogs
2025-07-24 09:20:25 -04:00
Danilo Leal
4fb540d6d2 dock: Add divider between panels on the right side, too (#35003)
A while ago, we added a divider in the left side of the status bar
between the icon buttons that open panels vs. those that open something
else (tabs, popover menus, etc.). There isn't a super good reason why we
wouldn't do the same in the right side, even more so given that (at
least by default) we usually have more buttons on that side; buttons
that _don't_ open panels (regardless of being docked to the bottom or
right). So, this PR does this!

<img width="700" height="314" alt="CleanShot 2025-07-24 at 1  59 10@2x"
src="https://github.com/user-attachments/assets/5f8bd4bc-a983-4000-a8f9-05a36b9e4b81"
/>

Release Notes:

- N/A
2025-07-24 09:39:10 -03:00
Piotr Osiewicz
1e2b0fcab6 sum_tree: Remove Unit type (#35027)
This solves one ~TODO, as Unit type was a workaround for a lack of
ability to implement Summary for ().


Release Notes:

- N/A
2025-07-24 14:30:49 +02:00
Oleksiy Syvokon
0af690080b linux: Fix ctrl-0..9, ctrl-[, ctrl-^ (#35028)
There were two different underlying reasons for the issues with
ctrl-number and ctrl-punctuation:

1. Some keys in the ctrl-0..9 range send codes in the `\1b`..`\1f`
range. For example, `ctrl-2` sends keycode for `ctrl-[` (0x1b), but we
want to map it to `2`, not to `[`.

2. `ctrl-[` and four other ctrl-punctuation were incorrectly mapped,
since the expected conversion is by adding 0x40

Closes #35012

Release Notes:

- N/A
2025-07-24 12:22:57 +00:00
Finn Evers
dd52fb58fe terminal_view: Ensure breadcrumbs are updated on settings change (#35016)
Currently, terminal breadcrumbs are only updated after a settings change
once the terminal view is focused again. This change ensures that the
breadcrumbs are updated instantaneously once the breadcrumb settings
changes.

Release Notes:

- Fixed an issue where terminal breadcrumbs would not react instantly to
settings changes.
2025-07-24 08:51:40 +00:00
Joseph T. Lyons
913b9296d7 Add editor: convert to sentence case (#35015)
This PR adds an `editor: convert to sentence case` action.

I frequently find myself copying branch names and then removing the
hyphens and ensuring the first letter is capitalized, and then using the
result text for the commit message.

For example:

<img width="927" height="482" alt="image"
src="https://github.com/user-attachments/assets/adf14a37-a92e-44df-8c0e-267b5c7677fb"
/>

You can achieve this with a combination of other text manipulation
commands, but this action makes it even easier.

Also, moved `toggle_case` down into the area where all other commands
internally using `manipulate_text` are located.

Release Notes:

- Added `editor: convert to sentence case`
2025-07-24 08:49:04 +00:00
Joseph T. Lyons
5c9363b1c4 Differentiate between file and selection diff events (#35014)
Release Notes:

- N/A
2025-07-24 08:43:28 +00:00
Finn Evers
cd9bcc7f09 agent_ui: Improve wrapping behavior in provider configuration header (#34989)
This ensures that the "Add provider" button does not move offscreen too
fast and ensures the text wraps for smaller panel sizes.

| Before | After |
| --- | --- |
| <img width="413" height="84" alt="image"
src="https://github.com/user-attachments/assets/565f7503-bddb-4b05-83c1-8f8745ac3ce3"
/> | <img width="392" height="84" alt="image"
src="https://github.com/user-attachments/assets/18469e4d-d94c-4641-9081-1af8981bfffd"
/> |

Release Notes:

- N/A
2025-07-24 10:40:36 +02:00
Jason Lee
65759d4316 gpui: Fix Interactivity prepaint to update scroll_handle bounds (#35013)
It took a long time to check this problem.

Finally, I found that due to a detail missing when changing #34832, the
bounds of `ScrollHandle` was not updated in the Interactivity `prepaint`
phase.

```diff
- scroll_handle_state.padded_content_size = padded_content_size;
+ scroll_handle_state.max_offset = scroll_max;
```

It was correct before the change, because the `padded_content_size`
(including `bounds.size`) was saved before, and the bounds was missing
after changing to `max_offset`, but the bounds were not updated
anywhere.

So when `scroll_handle.bounds()` is obtained outside, it is always 0px
here.

@MrSubidubi

Release Notes:

- N/A
2025-07-24 08:27:29 +00:00
Joseph T. Lyons
ddd50aabba Fix some bugs with editor: diff clipboard with selection (#34999)
Improves testing around `editor: diff clipboard with selection` as well.

Release Notes:

- Fixed some bugs with `editor: diff clipboard with selection`
2025-07-24 02:52:02 -04:00
Conrad Irwin
34bf6ebba6 Disable auto-close in search (#35005)
Currently if you type `\(`, it auto-closes to `\()` which is broken.

It's arguably nice that if you type `(` it auto-closes to `()`, but I am
much more likely to be looking for a function call `name\(` than to be
starting a group in search.

Release Notes:

- search: Regex search will no longer try to close parenthesis
automatically.
2025-07-24 05:45:01 +00:00
Pablo Ramón Guevara
a6956eebcb Improve Helix insert (#34765)
Closes #34763 

Release Notes:

- Improved insert in `helix_mode` when a selection exists to better
match helix's behavior: collapse selection to avoid replacing it
- Improved append (`insert_after`) to better match helix's behavior:
move cursor to end of selection if it exists
2025-07-23 23:27:07 -06:00
AidanV
8b0ec287a5 vim: Add :norm support (#33232)
Closes #21198

Release Notes:

- Adds support for `:norm`
- Allows for vim and zed style modified keys specified in issue
  - Vim style <C-w> and zed style <ctrl-w>
- Differs from vim in how multi-line is handled 
  - vim is sequential
  - zed is combinational (with multi-cursor)
2025-07-23 23:06:05 -06:00
versecafe
c08851a85e ollama: Add Magistral to Ollama (#35000)
See also: #34983

Release Notes:

- Added magistral support to ollama
2025-07-24 00:17:54 -04:00
Peter Tripp
b93e1c736b mistral: Add support for magistral-small and magistral-medium (#34983)
Release Notes:

- mistral: Added support for magistral-small and magistral-medium
2025-07-23 23:13:49 -04:00
Piotr Osiewicz
67027bb241 agent: Fix Zed header in settings view (#34993)
Follow-up to taffy bump (#34939), fixes an issue reported by @MrSubidubi


Release Notes:

- N/A
2025-07-24 00:13:47 +00:00
Smit Barmase
31afda3c0c project_panel: Automatically open project panel when Rename or Duplicate is triggered from workspace (#34988)
In project panel, `rename` and `duplicate` action further needs user
input for editing, so if panel is closed we should open it.

Release Notes:

- Fixed project panel not opening when `project panel: rename` and
`project panel: duplicate` actions are triggered from workspace.
2025-07-24 05:26:12 +05:30
Marshall Bowers
3d4266bb8f collab: Remove POST /billing/subscriptions/manage endpoint (#34986)
This PR removes the `POST /billing/subscriptions/manage` endpoint, as it
has been moved to `cloud.zed.dev`.

Release Notes:

- N/A
2025-07-23 23:30:00 +00:00
Maksim Bondarenkov
4a87397d37 livekit_client: Revert a change that broke MinGW builds (#34977)
the change was made in https://github.com/zed-industries/zed/pull/34223
for unknown reason. it wasn't required actually, and the code can be
safely left as before

update: after this revert Zed compiles with MinGW as before

Release Notes:

- N/A
2025-07-24 01:53:13 +03:00
Piotr Osiewicz
3da23cc65b Re-land taffy 0.8.3 (#34939)
Re #34938
- **chore: Bump taffy to 0.8.3**
- **editor: Fix sticky multi-buffer header not extending to the full
width**


Release Notes:

- N/A
2025-07-24 00:33:43 +02:00
Smit Barmase
b63d820be2 editor: Fix move line up panic when selection is at end of line next to fold marker (#34982)
Closes #34826

In move line up method, make use of `prev_line_boundary` which accounts
for fold map, etc., for selection start row so that we don't incorrectly
calculate row range to move up.

Release Notes:

- Fixed an issue where `editor: move line up` action sometimes crashed
if the cursor was at the end of a line beside a fold marker.
2025-07-24 03:46:29 +05:30
Renato Lochetti
7e9d6cc25c mistral: Add support for Mistral Devstral Medium (#34888)
Mistral released their new DevstralMedium model to be used via API:
https://mistral.ai/news/devstral-2507

Release Notes:

- Add support for Mistral Devstral Medium
2025-07-23 17:27:25 -04:00
Danilo Leal
8bf7dcb613 agent: Fix follow button disabled state (#34978)
Release Notes:

- N/A
2025-07-23 17:09:05 -04:00
Peter Tripp
edceb7284f Redact secrets from environment in LSP Server Info (#34971)
In "Server Info" view of LSP logs:
- Redacts sensitive values from environment
- Sorts environment by name

| Before | After | 
| - | - | 
| <img width="797" height="327" alt="Screenshot 2025-07-23 at 14 10 14"
src="https://github.com/user-attachments/assets/75781f30-9099-4994-9824-94d9c46f63e1"
/> | <img width="972" height="571" alt="image"
src="https://github.com/user-attachments/assets/c5bef744-a1b7-415f-9eb7-8314275c59b9"
/> |


Release Notes:

- Improved display of environment variables in LSP Logs: Server Info
view
2025-07-23 16:55:13 -04:00
Joseph T. Lyons
50985b7d23 Fix telemetry event type names (#34974)
Release Notes:

- N/A
2025-07-23 20:30:21 +00:00
Nicolas Rodriguez
be0d9eecb7 Add collapse functionality to outline entries (#33490)
partly Closes #23075 

Release Notes:

- Now provides collapse and enables functionality to outline entries
- Add a new expand_outlines_with_depth setting to customize how deep the
tree is expanded by when a file is opened

part 2 is in #34164 

**Visual examples**

![image](https://github.com/user-attachments/assets/5dcdb83b-6e3e-4bfd-8ef4-76ae2ce4d3e6)

![image](https://github.com/user-attachments/assets/7b786a5a-1a8c-4f34-aaa5-4a8d0afa9668)

![image](https://github.com/user-attachments/assets/1817be06-ac71-4480-8f17-0bd862e913c8)
2025-07-23 18:52:44 +00:00
Umesh Yadav
9863c8a44e agent_ui: Show keybindings for NewThread and NewTextThread in new thread button (#34967)
I believe in this PR: #34829 we moved to context menu entry from action
but the side effect of that was we also removed the Keybindings from
showing it in the new thread button dropdown. This PR fixes that. cc
@danilo-leal

| Before | After |
|--------|--------|
| <img width="900" height="1962" alt="CleanShot 2025-07-23 at 23 36
28@2x"
src="https://github.com/user-attachments/assets/760cbe75-09b9-404b-9d33-1db73785234f"
/> | <img width="850" height="1964" alt="CleanShot 2025-07-23 at 23 37
17@2x"
src="https://github.com/user-attachments/assets/24a7e871-aebc-475c-845f-b76f02527b8f"
/> |

Release Notes:

- N/A
2025-07-23 18:28:05 +00:00
Joseph T. Lyons
a48247a313 Bump Zed to v0.198 (#34964)
Release Notes:

-N/A
2025-07-23 18:14:39 +00:00
Julia Ryan
5f0edd38f8 Add TestPanic feature flag (#34963)
Now the `dev: panic` action can be run on all release channels if the
user has the feature flag enabled.

Release Notes:

- N/A
2025-07-23 18:01:16 +00:00
Anthony Eid
56b64b1d3f keymap ui: Improve resize columns on double click (#34961)
This PR splits the resize logic into separate left/right propagation
methods and improve code organization around column width adjustments.
It also allows resize to work for both the left and right sides as well,
instead of only checking the right side for room

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-07-23 13:45:49 -04:00
Danilo Leal
fdcd86617a ai onboarding: Add telemetry event capturing (#34960)
Release Notes:

- N/A

Co-authored-by: Katie Geer <katie@zed.dev>
Co-authored-by: Joseph T. Lyons <JosephTLyons@gmail.com>
2025-07-23 17:33:53 +00:00
Michael Sloan
3b428e2ecc Remove !menu from j k binding in initial keymap examples (#34959)
See
https://github.com/zed-industries/zed/pull/34912#issuecomment-3108802582

Release Notes:

- N/A
2025-07-23 17:33:40 +00:00
Anthony Eid
986b446749 keymap ui: Resizable column follow up (#34955)
I cherry picked a small fix that didn't get into the original column
resizable branch PR because I turned on auto merge.

Release Notes:

- N/A
2025-07-23 12:18:55 -04:00
Finn Evers
8713c556d6 keymap_ui: Dim keybinds that are overridden by other keybinds (#34952)
This change dims rows in the keymap editor for which the corresponding
keybind is overridden by other keybinds coming from higher priority
sources.

Release Notes:

- N/A
2025-07-23 16:03:04 +00:00
Mikayla Maki
326fe05b33 Resizable columns (#34794)
This PR adds resizable columns to the keymap editor and the ability to
double-click on a resizable column to set a column back to its default
size.

The table uses a column's width to calculate what position it should be
laid out at. So `column[i]` x position is calculated by the summation of
`column[..i]`. When resizing `column[i]`, `column[i+1]`’s size is
adjusted to keep all columns’ relative positions the same. If
`column[i+1]` is at its minimum size, we keep seeking to the right to
find a column with space left to take.

An improvement to resizing behavior and double-clicking could be made by
checking both column ranges `0..i-1` and `i+1..COLS`, since only one
range of columns is checked for resize capacity.

Release Notes:

- N/A

---------

Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Remco Smits <djsmits12@gmail.com>
2025-07-23 15:44:45 +00:00
claytonrcarter
1f4c9b9427 language: Update block_comment and documentation comment (#34861)
As suggested in https://github.com/zed-industries/zed/pull/34418, this
proposes various changes to language configs to make block comments and
doc-block-style comments more similar. In doing so, it introduces some
breaking changes into the extension schema.

This change is needed to support the changes I'm working on in #34418,
to be able to support `rewrap` in block comments like `/* really long
comment ... */`. As is, we can do this in C-style doc-block comments (eg
`/** ... */`) because of the config in `documentation`, but we can't do
this in regular block comments because we lack the info about what the
line prefix and indentation should be.

And while I was here, I did various other clean-ups, many of which feel
nice but are optional.

I would love special attention on the changes to the schema, version and
related changes; I'm totally unfamiliar with that part of Zed.

**Summary of changes**
- break: changes type of `block_comment` to same type as
`documentation_comment` (**this is the important change**)
- break: rename `documentation` to `documentation_comment` (optional,
but improves consistency w/ `line_comments` and `block_comment`)
- break/refactor?: removes some whitespace in the declaration of
`block_comment` delimiters (optional, may break things, need input; some
langs had no spaces, others did)
- refactor: change `tab_size` from `NonZeroU32` to just a `u32` (some
block comments don't seem to need/want indent past the initial
delimiter, so we need this be 0 sometimes)
- refactor: moves the `documentation_comment` declarations to appear
next to `block_comment`, rearranges the order of the fields in the TOML
for `documentation_comment`, rename backing `struct` (all optional)

**Future scope**
I believe that this will also allow us to extend regular block comments
on newline – as we do doc-block comments – but I haven't looked into
this yet. (eg, in JS try pressing enter in both of these: `/* */` and
`/** */`; the latter should extend w/ a `*` prefixed line, while the
former does not.)

Release Notes:

- BREAKING CHANGE: update extension schema version from 1 to 2, change
format of `block_comment` and rename `documentation_comment`

/cc @smitbarmase
2025-07-23 20:38:52 +05:30
Marshall Bowers
14171e0721 collab: Add POST /users/:id/update_plan endpoint (#34953)
This PR adds a new `POST /users/:id/update_plan` endpoint to Collab to
allow Cloud to push down plan updates to users.

Release Notes:

- N/A
2025-07-23 14:30:08 +00:00
Peter Tripp
326ab5fa3f Improve collab channel organization keybinds (#34821)
Change channel reorganization (move up/down) from `cmd-up/down` (mac) /
`ctrl-up/down` (linux) to `alt-up/down` (both) to match moving lines in
the editor.

Also fix an issue where if you selected channels using down/up in the
filter field, the movement shortcuts would not work (`editing` vs
`not_editing`).

Release Notes:

- N/A
2025-07-23 10:04:53 -04:00
Finn Evers
2bc6e18ac9 Ensure disable_ai is properly respected (#34941)
Quick follow up to #34896 which ensures that the Agent Panel cannot be
caught by actions like `workspace: toggle left dock` when `disable_ai`
is set to true.

Also removes a method that was introduced but unused in the workspace
because `first_enabled_panel_idx` already covers all cases this method
could be useful for.

Release Notes:

- N/A
2025-07-23 12:20:09 +02:00
Piotr Osiewicz
c2c2264a60 gpui: Add tree example (#34942)
This commit adds an example with deep children hierarchy.
The depth of a tree can be tweaked with GPUI_TREE_DEPTH env variable.
With depth=100
<img width="301" height="330" alt="image"
src="https://github.com/user-attachments/assets/844cd285-c5f3-4410-a74e-981bf093ba2e"
/>
With this example, I can trigger a stack overflow at depth=633 (and
higher).


Release Notes:

- N/A
2025-07-23 12:17:23 +02:00
Piotr Osiewicz
6cd3726a5a Revert "chore: Bump taffy to 0.8.3" (#34938)
Reverts zed-industries/zed#34876

From our Slack:
<img width="1694" height="1610" alt="image"
src="https://github.com/user-attachments/assets/c7b8f02a-8609-4ed3-9cd5-7d05d152e40e"
/>


https://github.com/user-attachments/assets/828964be-9b6e-4496-9361-9e3a2e9aa208

Release Notes:
- N/A
2025-07-23 10:28:06 +02:00
tidely
7db110f48d sum_tree: Utilize size_hint in TreeSet::extend (#34936)
Collect the iterator instead of manually looping over it to utilize
possible size hints. Zed usually passes in owned `Vec`'s, meaning we get
to reuse memory as well.

Release Notes:

- N/A
2025-07-23 09:57:57 +02:00
maan2003
6122f46095 project: Fix search filter patterns on remote projects (#34748)
we were join(",") and split(",") to serialize the patterns.

This doesn't work when pattern includes a ","
example: *.{ts,tsx} (very common pattern used by agent)


help needed:

how will this work on version mismatch?

Release Notes:

- Fixed search filter patterns on remote projects.
2025-07-23 00:18:45 -06:00
Joseph T. Lyons
500ceaabcd Add an editor: diff clipboard with selection action (#33283)
https://github.com/user-attachments/assets/d472fbdd-7736-4bd7-8a90-8cca356b2815

This PR adds `editor: diff clipboard with selection` - good for spotting
the differences in eerily-similar code, which is when refactoring code,
as you need to see what needs to be passed in in order to maintain
previous behavior of both snippets.

1. Copy some text from anywhere
2. Highlight some text in Zed
3. Run `editor: diff clipboard with selection`

Like JetBrains' IDEs and VS Code with the `PartialDiff` package, if the
selection is empty, we take the entire buffer as the selection.

Caveats:

- We do not know the language of the text in the clipboard. I went ahead
and just assumed that in most cases, it will be the same language as the
selected text, which does mean we will highlight the old text
incorrectly if they are copying from a different language, but I think
in most cases, it will be the same, and the alternative of always having
no syntax highlighting is worse. PyCharm seems to do the same thing.

Release Notes:

- Added an `editor: diff clipboard with selection` action

---------

Co-authored-by: Junkui Zhang <364772080@qq.com>
Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-07-23 02:39:32 +00:00
Jason Lee
3e27fa1d92 gpui: Allow Animation to be cloned (#34933)
Release Notes:

- N/A

---

Let `Animation` able to clone, then we can define one to share for
multiple places.

For example:

<img width="914" height="637" alt="image"
src="https://github.com/user-attachments/assets/8eafb318-afba-4399-a975-d83cb7afe74c"
/>
2025-07-23 02:10:40 +00:00
Smit Barmase
11ac83f3d4 workspace: Fix closing remote workspace restores last local workspace on startup (#34931)
Closes #7759

While opening a new SSH project if we are reusing an existing window,
i.e. drop the existing workspace and create a new one, then before
dropping it, we should remove `session_id` from that workspace's
serialized entry. That way:

1. Upon closing (cmd-w) this remote workspace (which also clears
`session_id` for this), and then quitting. No workspace with that
`session_id` is found, and it starts fresh.
2. Upon directly quitting (cmd-q) this remote workspace, only this
workspace exists in db (among two of them) with that `session_id`, and
it restores correctly.
 
Release Notes:

- Fixed an issue while closing remote workspace restores last local
workspace on startup.
2025-07-23 06:48:31 +05:30
Smit Barmase
e90cf0b941 workspace: Fix last removed folder from workspace used to reopen on Zed startup (#34925)
Closes #34924

Now, when `local_paths` are empty, we detach `session_id` from that
workspace serialization item. This way, when we restore it using the
default "last_session", we don't restore this workspace back. This is
same as when we use `cmd-w` to close window, which also sets
`session_id` to `None` before serialization.

Release Notes:

- Fixed an issue where last removed folder from workspace used to reopen
on Zed startup.
2025-07-23 06:46:24 +05:30
Marshall Bowers
056003860a collab: Remove POST /billing/subscriptions endpoint (#34928)
This PR removes the `POST /billing/subscriptions` endpoint, as it has
been moved to `cloud.zed.dev`.

Release Notes:

- N/A
2025-07-22 18:57:07 -04:00
Agus Zubiaga
5d985fa1d8 Improve MCP server responses (#34927)
Release Notes:

- N/A
2025-07-22 22:14:34 +00:00
Marshall Bowers
7f70325a93 language_models: Rename handler to handle in Bedrock provider (#34923)
This PR renames the `handler` field to `handle` on the
`BedrockLanguageModelProvider` and `BedrockModel` structs.

Release Notes:

- N/A
2025-07-22 20:04:08 +00:00
Umesh Yadav
f3c332d839 Fix new crate license symlink (#34922)
The license file is not properly linked to actual license. This was
casued due to new-crate script linking the license to wrong file. Fixed
both of them.

Reference logs:

```
2025-07-22T17:16:19+05:30 ERROR [worktree] error reading target of symlink "/Users/umesh/code/zed/crates/onboarding/LICENSE-GPL": canonicalizing
```

Release Notes:

- N/A
2025-07-22 15:46:25 -04:00
Remco Smits
446d333515 debugger: Fix debug console persist to history when reusing a previous item (#34893)
Closes #34887

Release Notes:

- Debugger: Fix debug console persist to history when reusing a previous
item
2025-07-22 15:40:11 -04:00
Remco Smits
c0f75e1a17 debugger: Fix built-in JavaScript debug tasks were not working due missing type field value (#34894)
Release Notes:

- Debugger: Fix built-in JavaScript debug tasks were not working due
missing `type` field value
2025-07-22 15:39:52 -04:00
Piotr Osiewicz
708c2645d1 collab: Tweak screen selector appearance (#34919)
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>


Release Notes:

- N/A

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-07-22 18:53:57 +00:00
Danilo Leal
4272c1508e ai onboarding: Copyedit the whole flow (#34916)
Release Notes:

- N/A

Co-authored-by: Katie Geer <katie@zed.dev>
2025-07-22 15:41:12 -03:00
Peter Tripp
99466f4aeb Make zooming from menus not persist (#34910)
Closes: https://github.com/zed-industries/zed/issues/34479
Follow-up to: https://github.com/zed-industries/zed/issues/23505

View->Zoom In / Zoom Out / Reset Zoom were not reverted to match when
the default keybindings were reverted.

Release Notes:

- N/A
2025-07-22 13:57:36 -04:00
Marshall Bowers
9e280d0905 collab: Remove unneeded caching of Stripe price IDs by meter ID (#34915)
This PR removes the caching of Stripe price IDs by meter ID on the
`StripeBilling` object, as we weren't actually reading them anywhere.

Release Notes:

- N/A
2025-07-22 17:42:07 +00:00
Michael Sloan
d81a8178e9 Bind "j k" to NormalBefore in initial keymap examples (#34912)
It looks like typically vim configurations bind "j k" to be the same as
escape, which has the "NormalBefore" behavior positioning the block
cursor on the character before the insertion cursor. The [vim mode
docs](https://zed.dev/docs/vim#useful-contexts-for-vim-mode-key-bindings)
also use NormalBefore here.

Thanks to @omniwrench for mentioning this in
https://github.com/zed-industries/zed/discussions/6661#discussioncomment-13848043
. This was a mistake in #31163.

Release Notes:

- N/A
2025-07-22 11:35:58 -06:00
Ben Kunkle
14cea06f0f keymap_ui: Fix panic in clear keystrokes (#34909)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-07-22 13:18:59 -04:00
Piotr Osiewicz
caa520c499 workspace: Clean up empty panes left over from file opening failures (#34908)
Closes #34583

Release Notes:

- Fixed empty pane being left after a binary file is dropped into a new
pane.s
2025-07-22 16:52:17 +00:00
Piotr Osiewicz
64d0fec699 sum_tree: Store context on cursor (#34904)
This gets rid of the need to pass context to all cursor functions. In
practice context is always immutable when interacting with cursors.

A nicety of this is in the follow-up PR we will be able to implement
Iterator for all Cursors/filter cursors (hell, we may be able to get rid
of filter cursor altogether, as it is just a custom `filter` impl on
iterator trait).
Release Notes:

- N/A
2025-07-22 18:20:48 +02:00
Piotr Osiewicz
fa3e1ccc37 chore: Bump taffy to 0.8.3 (#34876)
That's the latest release. Note that we have an opportunity to simplify
our size types per
https://github.com/DioxusLabs/taffy/blob/main/CHANGELOG.md#highlights-1
(though that's left out of this PR)

<img width="1156" height="603" alt="image"
src="https://github.com/user-attachments/assets/cb9501b9-541a-4080-998a-b6347a0c6887"
/>


Release Notes:

- N/A
2025-07-22 18:19:51 +02:00
tiagoq
56b99f49fd bedrock: Fix remaining streaming delays (#33931)
Closes #26030

*Note: This is my first contribution to Zed*

This addresses a second streaming bottleneck in Bedrock that remained
after the initial fix in #28281 (released in preview 194).

The issue is in the mechanism used to convert Zed's internal `AsyncBody`
into the `SdkBody` expected by the Bedrock language provider. We are
using a non-streaming converter that buffers responses.

**How the fix works:**
The AWS SDK provides streaming-compatible converters to create `SdkBody`
instances, but these require the input body to implement the `Body`
trait from the `http-body` crate.

This PR enables streaming by implementing the required trait and
switching to the streaming-compatible converter.

**Changes (2 commits):**

 * 1st Commit - **Implement http-body Body trait for AsyncBody:**
   - Add `http-body = 1.0` dependency (already an indirect dependency)
   - Implement the `Body` trait for our existing `AsyncBody` type
- Uses `poll_frame` to read data chunks asynchronously, preserving
streaming behavior

 * 2nd Commit - **Use streaming-compatible AWS SDK converter:**
- Create `SdkBody` using `SdkBody::from_body_1_x()` with the new `Body`
trait implementation

**Details/FAQ:**

**Q: Why add another dependency?**
A: We tried to avoid adding a dependency, but the AWS SDK requires the
`Body` trait and `http-body` is where it's defined. The crate is already
an indirect dependency, making this a reasonable solution.

**Q: Why modify the shared `http_client` crate instead of just
`aws_bedrock_client`?**
A: We considered implementing the `Body` trait on a wrapper in
`aws_bedrock_client`, but since `AsyncBody` already uses `http` crate
types, extending support to the companion `http-body` crate seems
reasonable and may benefit other integrations.

**Q: How was this bottleneck discovered?**
A: After @5herlocked's initial streaming fix in #28281, I tested preview
194 and noticed streaming still had issues. I found a way to reproduce
the problem and chatted with @5herlocked about it. He immediately
pinpointed the exact location where the issue was occurring, his
diagnosis made this fix possible.

**Q: How does this relate to the previous fix?**
A: #28281 fixed buffering issues higher in the stack, but unfortunately
there was another bottleneck lower-down in the aws-http-client. This PR
addresses that separate buffering issue.

**Q: Does this use zero-copy or one-copy?**
A: The `Body` implementation includes one copy. Someone more
knowledgeable might be able to achieve a zero-copy approach, but we
opted for a conservative approach. The performance impact should not be
perceptible in typical usage.

**Testing:**
Confirmed that Bedrock streaming now works without buffering delays in a
local build.


Release Notes:

- Improved Bedrock streaming by eliminating response buffering delays

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-07-22 11:55:24 -04:00
Ben Kunkle
2b888e1d30 Fix redo after noop format (#34898)
Closes #31917

Previously, as of #28457 we used a hack, creating an empty transaction
in the history that we then merged formatting changes into in order to
correctly identify concurrent edits to the buffer while formatting was
happening. This caused issues with noop formatting however, as using the
normal API of the buffer history (in an albeit weird way) resulted in
the redo stack being cleared, regardless of whether the formatting
transaction included edits or not, which is the correct behavior in all
other contexts.

This PR fixes the redo issue by codifying the behavior formatting wants,
that being the ability to push an empty transaction to the history with
no other side-effects (i.e. clearing the redo stack) to detect
concurrent edits, with the tradeoff being that it must then manually
remove the transaction later if no changes occurred from the formatting.
The redo stack is still cleared when there are formatting edits, as the
individual format steps use the normal `{start,end}_transaction` methods
which clear the redo stack if the finished transaction isn't empty.

Release Notes:

- Fixed an issue where redo would not work after buffer formatting
(including formatting on save) when the formatting did not result in any
changes
2025-07-22 11:45:42 -04:00
Richard Feldman
96f9942791 Add setting to disable all AI features (#34896)
https://github.com/user-attachments/assets/674bba41-40ac-4a98-99e4-0b47f9097b6a


Release Notes:

- Added setting to disable all AI features
2025-07-22 11:32:39 -04:00
Marshall Bowers
939f9fffa3 collab: Remove unneeded caching of Stripe meters (#34900)
This PR removes the caching of Stripe meters on the `StripeBilling`
object, as we weren't actually reading them anywhere.

Release Notes:

- N/A
2025-07-22 15:27:58 +00:00
Bennet Bo Fenner
230061a6cb Support multiple OpenAI compatible providers (#34212)
TODO
- [x] OpenAI Compatible API Icon
- [x] Docs
- [x] Link to docs in OpenAI provider section about configuring OpenAI
API compatible providers

Closes #33992

Related to #30010

Release Notes:

- agent: Add support for adding multiple OpenAI API compatible providers

---------

Co-authored-by: MrSubidubi <dev@bahn.sh>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-07-22 12:20:07 -03:00
Ben Kunkle
1a76a6b0bf gpui: Simplify bindings_for_action API (#34857)
Closes #ISSUE

Simplifies the API to no longer have a variant that returns indices. The
downside is that a few places that used to call
`bindings_for_action_with_indices` now compare `Box<dyn Action>` instead
of indices, however the result is the removal of wrapper code and index
handling that is largely unnecessary

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Co-authored-by: Conrad <conrad@zed.dev>
2025-07-22 10:59:51 -04:00
Peter Tripp
2eeab5b0bf textmate: Correct context for 'Editor && mode == full' keybinds (#34895)
Closes https://github.com/zed-industries/zed/issues/34891

Release Notes:

- Fixed textmate keymap misbehaving in certain contexts
2025-07-22 14:52:04 +00:00
Ben Kunkle
30177b87d6 Fix detection of pending bindings when binding in parent context matches (#34856)
Broke in #34664

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Co-authored-by: Conrad <conrad@zed.dev>
2025-07-22 13:51:30 +00:00
Umesh Yadav
31aab89ab0 ai_onboarding: Fix API key onboarding callout not showing properly (#34880)
The current onboarding callout for ApiKeysWithProviders is broken.

| Before | After |
|--------|--------|
| <img width="822" height="1914" alt="CleanShot 2025-07-22 at 16 21
53@2x"
src="https://github.com/user-attachments/assets/5a611a8c-1ca2-4a13-965e-6fbd7cfe757a"
/> | <img width="814" height="1956" alt="CleanShot 2025-07-22 at 16 22
38@2x"
src="https://github.com/user-attachments/assets/3263b804-671a-4637-b5dc-ee7c87befa48"
/> |

cc @danilo-leal 

Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-07-22 12:54:06 +00:00
Danilo Leal
a3850b3d38 agent: Add use_modifier_to_send section in the settings view (#34866)
This PR also converts all of these switch-based settings to use the new
`SwitchField` component, introduced in
https://github.com/zed-industries/zed/pull/34713.

Release Notes:

- agent: Added the ability to change the `use_modifier_to_send` setting
from the agent panel settings UI.
2025-07-22 09:42:34 -03:00
Oleksiy Syvokon
c7158f0bd7 Improve distinguishing user from agent edits (#34716)
We no longer rely on the `author` field to tell if a change was made by
the user or the agent. The `author` can be set to `User` in many
situations that are not really user-made edits, such as saving a file,
accepting a change, auto-formatting, and more. I started tracking and
fixing some of these cases, but found that inspecting changes in
`diff_base` is a more reliable method.

Also, we no longer show empty diffs. For example, if the user adds a
line and then removes the same line, the final diff is empty, even
though the buffer is marked as user-changed. Now we won't show such
edit.

There are still some issues to address:

- When a user edits within an unaccepted agent-written block, this
change becomes a part of the agent's edit. Rejecting this block will
lose user edits. It won't be displayed in project notifications, either.

- Accepting an agent block counts as a user-made edit.

- Agent start to call `project_notifications` tool after seeing enough
auto-calls.

Release Notes:

- N/A
2025-07-22 14:23:50 +03:00
Ben Brandt
3a651c546b context_server: Change command string field to PathBuf (#34873)
Release Notes:

- N/A
2025-07-22 12:12:07 +02:00
Bret Comnes
87014cec71 theme: Add panel.overlay_background and panel.overlay_hover (#34655)
In https://github.com/zed-industries/zed/pull/33994 sticky scroll was
added to project_panel.

I love this feature! 

This introduces a new element layering not seen before. On themes that
use transparency, the overlapping elements can make it difficult to read
project panel entries. This PR introduces a new selector:
~~panel.sticky_entry.background~~ `panel.overlay_background` This
selector lets you set the background of entries when they become sticky.

Closes https://github.com/zed-industries/zed/issues/34654

Before:

<img width="373" height="104" alt="Screenshot 2025-07-17 at 10 19 11 AM"
src="https://github.com/user-attachments/assets/d5bab065-53ca-4b27-b5d8-3b3f8d1f7a81"
/>

After:

<img width="292" height="445" alt="Screenshot 2025-07-17 at 11 46 57 AM"
src="https://github.com/user-attachments/assets/4cd2b87b-2989-4489-972f-872d2dc13a33"
/>

<img width="348" height="390" alt="Screenshot 2025-07-17 at 11 39 57 AM"
src="https://github.com/user-attachments/assets/49c0757f-2c50-4e01-92c6-2ae7e4132a53"
/>

<img width="668" height="187" alt="Screenshot 2025-07-17 at 11 39 29 AM"
src="https://github.com/user-attachments/assets/167536c2-5872-4306-90c6-c6b68276b618"
/>

Release Notes:

- Add `panel.sticky_entry.background` theme selector for modifying
project panel entries when they become sticky when scrolling and overlap
with entries below them.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-07-22 15:20:26 +05:30
Danilo Leal
2b671a46f2 ai onboarding: Don't show API keys section if user is already in Pro (#34867)
Release Notes:

- N/A
2025-07-22 05:39:22 +00:00
Danilo Leal
eaccd542fd Add fast-follows to the AI onboarding flow (#34737)
Follow-up to https://github.com/zed-industries/zed/pull/33738.

Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-07-22 02:09:05 -03:00
Marshall Bowers
5a530ecd39 zed: Add support for zed://agent links (#34862)
This PR adds support for `zed://agent` links for opening the Agent
Panel.

Release Notes:

- N/A
2025-07-22 01:40:33 +00:00
Daste
233e66d35f Add editor::BlameHover action for triggering the blame popover via keyboard (#32096)
Make the git blame popover available via the keymap by making it an
action. The blame popover stays open after being shown via the action,
similar to the `editor::Hover` action.

I added a default vim-mode key binding for `g b`, which goes in hand
with `g h` for hover. I'm not sure what the keybind would be for regular
layouts, if any would be set by default.

I'm opening this as a draft because I coludn't figure out a way to
position the popover correctly above/under the cursor head. I saw some
uses of `content_origin` in other places for calculating absolute pixel
positions, but I'm not sure how to make use of it here without doing a
big refactor of the blame popover code 🤔. I would appreciate some
help/tips with positioning, because it seems like the last thing to
implement here.

Opening as a draft for now because I think without the correct
positioning this feature is not complete.

Closes https://github.com/zed-industries/zed/discussions/26447

Release Notes:

- Added `editor::BlameHover` action for showing the git blame popover
under the cursor. By default bound to `ctrl-k ctrl-b` and to `g h` in
vim mode.
2025-07-21 19:30:23 -06:00
Marshall Bowers
15353630e4 zed: Add OpenRequestKind (#34860)
This PR refactors the `OpenRequest` to introduce an `OpenRequestKind`
enum.

It seems most of the fields on `OpenRequest` are mutually-exclusive, so
it is better to model it as an enum rather than using a bunch of
`Option`s.

There are likely more of the existing fields that can be converted into
`OpenRequestKind` variants, but I'm being conservative for this first
pass.

Release Notes:

- N/A
2025-07-22 01:11:11 +00:00
Marshall Bowers
5289b815fe ai_onboarding: Send users directly into the trial checkout flow when starting the trial (#34859)
This PR makes it so users will be sent immediately into the trial
checkout flow (by hitting zed.dev/account/start-trial) when they click
the "Start Pro Trial" button.

Release Notes:

- N/A
2025-07-21 23:58:16 +00:00
Danilo Leal
8515487bbc agent: Add new thread start buttons to the empty state (#34829)
Release Notes:

- N/A
2025-07-21 20:39:29 -03:00
Sergei Surovtsev
19ab1eb792 Fix an issue where xkb defined hotkeys for arrows would not work (#34823)
Addresses
https://github.com/zed-industries/zed/pull/34053#issuecomment-3096447601
where custom-defined arrows would stop working in Zed.

How to reproduce:

1. Define custom keyboard layout

```bash
cd /usr/share/X11/xkb/symbols/
sudo nano mykbd
```

```
default partial alphanumeric_keys
xkb_symbols "custom" {

    name[Group1]= "Custom Layout";

    key <AD01> { [ q,  Q,  Escape,     Escape      ] };
    key <AD02> { [ w,  W,  Home,       Home        ] };
    key <AD03> { [ e,  E,  Up,         Up          ] };
    key <AD04> { [ r,  R,  End,        End         ] };
    key <AD05> { [ t,  T,  Tab,        Tab         ] };

    key <AC01> { [ a,  A,  Return,     Return      ] };
    key <AC02> { [ s,  S,  Left,       Left        ] };
    key <AC03> { [ d,  D,  Down,       Down        ] };
    key <AC04> { [ f,  F,  Right,      Right       ] };
    key <AC05> { [ g,  G,  BackSpace,  BackSpace   ] };

    // include a base layout to inherit the rest
    include "us(basic)"
};
```

2. Activate custom layout with win-key as AltGr

```bash
setxkbmap mykbd -variant custom -option lv3:win_switch
```

3. Now Win-S should produce left arrow, Win-F right arrow
4. Test whether it works in Zed

Release Notes:

 - linux: xkb-defined hotkeys for arrow keys should behave as expected.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-07-21 17:16:44 -06:00
Agus Zubiaga
722a05bc21 Wire up stop button in claude threads (#34839)
Release Notes:

- N/A

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-07-21 20:33:59 +00:00
Finn Evers
5b3e371812 gpui: Round scroll_max to two decimal places (#34832)
Follow up to #31836 

After enabling rounding in the Taffy layout engine, we frequently run
into cases where the bounds produced by Taffy and ours slightly differ
after 5 or more decimal places. This leads to cases where containers
become scrollable for less than 0.0000x Pixels. In case this happens for
e.g. hover popovers, we render a scrollbar due to the container being
technically scrollable, even though the scroll amount here will in
practice never be visible.

This change fixes this by rounding the `scroll_max` by which we clamp
the current scroll position to two decimal places. We don't benefit from
the additional floating point precision here at all and it stops such
containers from becoming scrollable altogether. Furthermore, we now
store the `scroll_max` instead of the `padded_content_size` as the
former gives a much better idea on whether the corresponding container
is scrollable or not.

| `main` | After these changes |
| -- | -- |
| <img width="610" height="316" alt="main"
src="https://github.com/user-attachments/assets/ffcc0322-6d6e-4f79-a916-bd3c57fe4211"
/> | <img width="610" height="316" alt="scroll_max_rounded"
src="https://github.com/user-attachments/assets/5fe530f5-2e21-4aaa-81f4-e5c53ab73e4f"
/> |

Release Notes:

- Fixed an issue where scrollbars would appear in containers where no
scrolling was possible.
2025-07-21 22:24:33 +02:00
Peter Tripp
8eca7f32e2 Fix for vim bindings in Pickers on Linux (#34840)
Closes: https://github.com/zed-industries/zed/issues/34780

Also relocated undo/redo selection in the keymap (no-op) as they are
from Sublime, not VSCode.

Release Notes:

- vim: Fixed an issue so `ctrl-w` / `ctrl-h` and `ctrl-u` work in
pickers on Linux when Vim mode is enabled.
2025-07-21 20:09:02 +00:00
Ben Kunkle
1a1715766f Fix enter to select model in agent panel (#34846)
Broken by #34664

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-07-21 19:16:47 +00:00
Anthony Eid
241acbe4be Stop onboarding page from showing up instead of welcome page (#34845)
This is from PR #34723 where I was working on developing the onboarding
page, but I forgot to switch the first page back to our current version.

Release Notes:

- N/A
2025-07-21 19:02:02 +00:00
Piotr Osiewicz
6ea09beea8 terminal: Handle spaces in cwds of remote terminals (#34844)
Closes #34807

Release Notes:

- Fixed "Open in terminal" action not working with paths that contain
spaces in SSH projects.
2025-07-21 18:51:27 +00:00
Piotr Osiewicz
3e50d997dd agent: Fix double-lease panic when clicking on thread to jump (#34843)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-07-21 18:44:45 +00:00
Richard Feldman
da8bf9ad79 Auto-retry agent errors by default (#34842)
Now we explicitly carve out exceptions for which HTTP responses we do
*not* retry for, and retry at least once on all others.

Release Notes:

- The Agent panel now automatically retries failed requests under more
circumstances.
2025-07-21 14:32:22 -04:00
Bennet Bo Fenner
589af59dfe collab: Refresh the LLM token once the terms of service have been accepted (#34833)
Release Notes:

- N/A
2025-07-21 20:02:28 +02:00
Kirill Bulatov
254c7a330a Regroup LSP context menu items by the worktree name (#34838)
Also 

* remove the feature gate
* open buffers with an error when no logs are present
* adjust the hover text to indicate that difference

<img width="480" height="380" alt="image"
src="https://github.com/user-attachments/assets/6b2350fc-5121-4b1e-bc22-503d964531a2"
/>

Release Notes:

- N/A
2025-07-21 17:48:07 +00:00
Balboa Codes
b6cf398eab docs: Fix PHP docs typo (#34836)
This fixes a minor typo in the PHP docs.

Release Notes:

- N/A
2025-07-21 17:25:59 +00:00
Anthony Eid
bc5c5cf5d6 onboarding: Create basic onboarding page (#34723)
Release Notes:

- N/A

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-07-21 16:20:38 +00:00
Marshall Bowers
bf8aba566c collab: Remove unused billing preferences queries (#34830)
This PR removes some billing preferences queries that are no longer in
use.

Release Notes:

- N/A
2025-07-21 15:47:40 +00:00
Piotr Osiewicz
e14c9479e4 chore: Pin taffy version (#34827)
Follow-up to 34817

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-07-21 15:36:52 +00:00
Marshall Bowers
97af7e1bd9 collab: Remove PUT /billing/preferences endpoint (#34825)
This PR removes the `PUT /billing/preferences` endpoint, as it has been
moved to `cloud.zed.dev`.

Release Notes:

- N/A
2025-07-21 15:29:16 +00:00
Kamal Ahmad
c251f2a2d4 gpui: Throttle interactive resize events on Wayland (#34760)
Wayland compositors can potentially generate thousands of resize
requests when drag-resizing a window, which Zed is unable to keep up
with. This commit changes the behavior to only resize once per vblank
cycle, which helps significantly when resizing the window with a high
poll-rate mouse (1000Hz is common these days)

Here is an example of the behavior pre and post this commit, with some
print-debugging added for tracking resize calls. I have a wireless mouse
with a 2000Hz polling rate and a 165Hz display:

Before: 


https://github.com/user-attachments/assets/4c657f90-5fd2-4809-97ef-363fd48e81b8

After: 


https://github.com/user-attachments/assets/4a0f5fbd-c3c4-40a1-9f71-3b4358c827cf






Closes #20660

Release Notes:

- Improved: Make resizing smoother on Wayland/Linux
2025-07-21 17:48:01 +03:00
Agus Zubiaga
cc56196152 Fix loading agent server settings (#34662)
Release Notes:

- N/A
2025-07-21 14:26:00 +00:00
Agus Zubiaga
405244d422 Display ACP plans (#34816)
Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-07-21 11:11:37 -03:00
Piotr Osiewicz
35b4a918c9 chore: Bump taffy to 0.5.1 (#34817)
We've had problems in the past with bumping past 0.5.2 due to perf
regressions reported by @huacnlee; 0.5.1 was fine though.

Hence, let's bump taffy to 0.5.1 as a safe bet and then try to push past
0.5.2 (after we identify the root cause of regression
Related to #19189
Release Notes:

- N/A
2025-07-21 15:43:51 +02:00
Danilo Leal
56fd950d94 thread view: Add ability to expand message editor and fix scroll (#34766)
Release Notes:

- N/A
2025-07-21 09:26:31 -03:00
Piotr Osiewicz
88af35fe47 collab: Add screen selector (#31506)
Instead of selecting a screen to share arbitrarily, we'll now allow user
to select the screen to share. Note that sharing multiple screens at the
time is still not supported (though prolly not too far-fetched).

Related to #4666

![image](https://github.com/user-attachments/assets/1afb664f-3cdb-4e0a-bb29-9d7093d87fa5)

Release Notes:

- Added screen selector dropdown to screen share button

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Cole Miller <cole@zed.dev>
2025-07-21 13:44:51 +02:00
Ben Brandt
57ab09c2da claude: Don't quote executable path in mcp configuration (#34805)
This was generating an invalid string for the configuration, removing
the extra quotes makes it work. This affected the versions of Zed that
have a space in their name.

Release Notes:

- N/A
2025-07-21 09:53:05 +00:00
Jason Lee
caa4b529e4 gpui: Add tab focus support (#33008)
Release Notes:

- N/A

With a `tab_index` and `tab_stop` option to `FocusHandle` to us can
switch focus by `Tab`, `Shift-Tab`.

The `tab_index` is from
[WinUI](https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.control.tabindex?view=winrt-26100)
and [HTML
tabindex](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/tabindex),
only the `tab_stop` is enabled that can be added into the `tab_handles`
list.

- Added `window.focus_next()` and `window.focus_previous()` method to
switch focus.
- Added `tab_index` to `InteractiveElement`.

```bash
cargo run -p gpui --example tab_stop
```


https://github.com/user-attachments/assets/ac4e3e49-8359-436c-9a6e-badba2225211
2025-07-20 16:38:54 -07:00
Michael Sloan
137081f050 Misc code cleanups accumulated while working on other changes (#34787)
Release Notes:

- N/A
2025-07-20 23:22:13 +00:00
Ben Kunkle
7c1040bc93 keymap_ui: Auto complete action arguments (#34785)
Supersedes: #34242

Creates an `ActionArgumentsEditor` that implements the required logic to
have a JSON language server run when editing keybinds so that there is
auto-complete for action arguments.

This is the first time action argument schemas are required by
themselves rather than inlined in the keymap schema. Rather than add all
action schemas to the configuration options we send to the JSON LSP on
startup, this PR implements support for the
`vscode-json-language-server` extension to the LSP whereby the server
will request the client (Zed) to resolve URLs with URI schemes it does
not recognize, in our case `zed://`. This limits the impact on the size
of the configuration options to ~1KB as we send URLs for the language
server to resolve on demand rather than the schema itself. My
understanding is that this is how VSCode handles JSON schemas as well. I
plan to investigate converting the rest of our schema generation logic
to this method in a follow up PR.

Co-Authored-By: Cole <cole@zed.dev>

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-07-20 16:24:17 -04:00
Michael Sloan
ff79b29f38 Set stripe-mock version to 0.178.0 to match stripe API version used (#34786)
Release Notes:

- N/A
2025-07-20 19:39:04 +00:00
Bennet Bo Fenner
2e41e312ad component preview: Fix Zed AI onboarding young account preview (#34783)
Release Notes:

- N/A
2025-07-20 16:27:18 +00:00
Peter Tripp
5f92ac25a7 docs: Consolidate backend setup docs into local-collaboration.md (#34653)
Simplify docs for mac/linux/windows by consolidating the backend
dependencies (collaboration) docs into local-collaboration.md. Most
users building zed will not need to do this -- streamline them into
getting setup to build the zed client app first.

Release Notes:

- N/A
2025-07-19 12:01:33 -04:00
Peter Tripp
fb88de9223 Fix error in OpenRouter svg logo (#34764)
Fix a spurious error in Zed logs from the OpenRouter svg Logo introduced
in https://github.com/zed-industries/zed/pull/29496:

```log
WARN  [usvg::parser::svgtree] Failed to parse clip-path value: 'url(#clip0_205_3)'.
```

Release Notes:

- N/A
2025-07-19 16:01:18 +00:00
Oleksandr Mykhailenko
29111304dd agent: Fix Mistral tool use error message (#34692)
Closes #32675

Exactly the same changes as in #33640 by @sviande

The PR has been in WIP state for 3 weeks with no activity, and the issue
basically makes Mistral models unusable. I have tested the changes
locally, and it does indeed work. Full credit goes to @sviande, I just
want this feature to be finished.

Release Notes:

- agent: Fixed an issue with tool calling with the Mistral provider
(thanks [@sviande](https://github.com/sviande) and
[@armyhaylenko](https://github.com/armyhaylenko))

Co-authored-by: sviande <sviande@gmail.com>
2025-07-19 11:59:57 -04:00
Vitaly Slobodin
0ffd93774c Fix Tailwind support for HTML/ERB files (#34743)
Closes #27118
Closes #34165

Fix a small issue after we landed
https://github.com/zed-extensions/ruby/pull/113+ where we introduced
`HTML/ERB` and `YAML/ERB` language IDs to improve user experience. Sorry
about that. Thanks!

Release Notes:

- N/A
2025-07-19 11:06:35 -04:00
Mikayla Maki
2da2ae65a0 gpui: Add use state APIs (#34741)
This PR adds a component level state API to GPUI, as well as a few
utilities for simplified interactions with entities

Release Notes:

- N/A
2025-07-19 01:27:54 +00:00
morgankrey
4bdac8026c collab: Enable automatic tax calculation for all new subscriptions (#34720)
Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-07-18 21:42:48 +00:00
Peter Tripp
70bde54a2c ci: Lint GitHub Actions workflows with actionlint (#34729)
Added [rhysd/actionlint](https://github.com/rhysd/actionlint/) a static
checker for GitHub Actions workflow files.
Install locally with `brew install actionlint` the run with
`actionlint`.

Inspired by: https://github.com/zed-industries/zed/pull/34704 which
yielded this observation:

> In github actions: 
> 1. strings are truthy
> 2. `${{ }}` will become a string if it doesn't wrap the whole value.
>
> So `if: false && true` becomes `false`
> and `if: ${{ false && true }}` becomes `false`
> but `if: false && ${{ true }}` becomes `"false && true"` which
evaluates true
> The reason you sometimes need `${{ }}` is because YAML doesn't like
`!`
> so `if: !false` is invalid yaml
> and `if: ${{ !false }}` works just fine.

Changes:
- Add `actionlint` job
- Refactor `job_spec` job to be more readable
- Fix all `actionlint` and `shellcheck` errors in Actions workflows (62
in all)
- Add `self-mini-macos` and `self-32vcpu-windows-2022` labels to
self-hosted runners. Not strictly related, but useful if you need to
take a runner out of the rotation (since `macOS`, `self-hosted`, and
`ARM64` are auto-set and cannot be added/removed).
- Change ci.yml macos_relase to target `self-mini-macos` instead of
`bundle` which was previously deprecated.

This would've caught the error fixed in
https://github.com/zed-industries/zed/pull/34704. Here's what that [job
failure](https://github.com/zed-industries/zed/actions/runs/16376993944/job/46279281842?pr=34729)
would've looked like.

Release Notes:

- N/A
2025-07-18 15:15:07 -04:00
Conrad Irwin
43486c416c Fix enter in branch view (#34731)
Broken by #34664

Release Notes:

- N/A
2025-07-18 19:04:05 +00:00
Michael Sloan
d197c96cdc Add stripe-mock to docker compose configuration (#34732)
Release Notes:

- N/A
2025-07-18 18:58:55 +00:00
Peter Tripp
7b6b75b63f ci: Skip generating Windows release artifacts (#34704)
Release Notes:

- N/A
2025-07-18 14:31:08 -04:00
Anthony Eid
fd64ee1bb6 keymap ui: Fix remove key mapping bug (#34683)
Release Notes:

- N/A

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-07-18 14:09:28 -04:00
Danilo Leal
e1d28ff957 agent: Add use_modifier_to_send setting (#34709)
When `use_modifier_to_send` is turned on, holding `cmd`/`ctrl` is
necessary to send a message in the agent panel. Text threads already use
`cmd-enter` by default to submit a message, and it was done this way to
have the usual text editing bindings not taken over when writing a
prompt, sort of stimulating more thoughtful writing. While `enter` to
send is still somewhat a huge pattern in chat-like LLM UIs, it still
makes sense to allow this for the new agent panel... hence the existence
of this setting now!

Release Notes:

- agent: Added the `use_modifier_to_send` setting, which makes holding a
modifier (`cmd`/`ctrl`), together with `enter`, required to send a new
message.
2025-07-18 15:03:31 -03:00
Danilo Leal
64ce696aae ui: Add the SwitchField component (#34713)
This will be useful for both the current agent panel and some other
onboarding stuff we're working on. Also ended up removing the
`SwitchWithLabel` as it was unused.

Release Notes:

- N/A
2025-07-18 15:03:14 -03:00
tidely
87555d3f0b project: Remove clones from git blame serialization (#34727)
Release Notes:

- N/A
2025-07-18 20:41:12 +03:00
Finn Evers
5b18ce79ab editor: Ensure topmost buffer header can be properly folded (#34721)
This PR fixes an issue where the topmost header in a multibuffer would
jump when the corresponding buffer was folded.
The issue arose because for the topmost header, the offset within the
scroll anchor is negative, as the corresponding buffer only starts below
the header itself and thus the offset for the scroll position has to be
negative.
However, upon collapsing that buffer, we end up with a negative vertical
scroll position, which causes all kinds of different problems. The issue
has been present for a long time, but became more visible after
https://github.com/zed-industries/zed/pull/34295 landed, as that change
removed the case distinction for buffers scrolled all the way to the
top.

This PR fixes this by clamping just the vertical scroll position upon
return, which ensures the negative offset works as expected when the
buffer is expanded, but the vertical scroll position does not turn
negative once the buffer is folded.

Release Notes:

- Fixed an issue where folding the topmost buffer in a multibuffer would
cause the header to jump slightly.
2025-07-18 19:16:31 +02:00
Smit Barmase
1dd470ca48 editor: Fix double $ sign on completion accept in PHP (#34726)
Closes #33510 https://github.com/zed-extensions/php/issues/29

If certain language servers do not provide an insert/replace range, we
use `surrounding_word` as a fallback for that range, which internally
uses `word_characters`. It makes sense to use
`completion_query_characters` instead of `word_characters` to get that
range, because we use `completion_query_characters` to query completions
in the first place.

That means, for some hypothetical reason (e.g., if the Tailwind server
stops providing insert/replace ranges), we would correctly fall back to
the range "bg-blue-200^" instead of "200^", because
`completion_query_characters` includes "-" in this case.

For this particular fix, right now the default PHP language server
`phpactor` does not provide an insert/replace range, and hence
completion query character is used, which is `$` in this case.

Note that `$` isn't in word characters for reasons mentioned here:
https://github.com/zed-extensions/php/issues/14

Release Notes:

- Fixed an issue where accepting variable completion in PHP would result
in a double $ sign in the prefix.
2025-07-18 22:39:00 +05:30
Finn Evers
8bc8d61fa6 theme_importer: Add missing color imports for the minimap thumb (#34724)
These should have been part of
https://github.com/zed-industries/zed/pull/30785 but I forgot to add
them there.

Release Notes:

- N/A
2025-07-18 16:55:03 +00:00
Marshall Bowers
750ceeb760 collab: Don't use screen-capture feature from gpui (#34725)
This PR removes the `screen-capture` feature from `gpui` when depending
on it in `collab`.

Release Notes:

- N/A
2025-07-18 16:28:58 +00:00
Danilo Leal
4476860664 Add refinements to the AI onboarding flow (#33738)
This includes making sure that both the agent panel and Zed's edit
prediction have a consistent narrative when it comes to onboarding users
into the AI features, considering the possible different plans and
conditions (such as being signed in/out, account age, etc.)

Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com>
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-07-18 18:25:36 +02:00
Joseph T. Lyons
9a20843ba2 Revert "gpui: Improve path rendering & global multisample anti-aliasing" (#34722)
Reverts zed-industries/zed#29718

We've noticed some issues with Zed on Intel-based Macs where typing has
become sluggish, and git bisect has seemed to point towards this PR.
Reverting for now, until we can understand why it is causing this issue.
2025-07-18 16:03:08 +00:00
Conrad Irwin
e421fc7a2d Update keymap context binding behavior of > and ! (#34664)
Now ! means "no ancestors matches this", and > means "any descendent"
not "any child".

Updates #34570

Co-authored-by: Ben Kunkle <ben@zed.dev>

Release Notes:

- *Breaking change*. The context predicates in the keymap file now
handle ! and > differently. Before this change ! meant "this node does
not match", now it means "none of these nodes match". Before this change
> meant "child of", now it means "descendent of". We do not expect these
changes to break many keymaps, but they may cause subtle changes for
complex context queries.

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-07-18 09:25:18 -06:00
Marshall Bowers
f461290ac3 collab: Add POST /users/:id/refresh_llm_tokens endpoint (#34714)
This PR adds a new `POST /users/:id/refresh_llm_tokens` endpoint to
Collab so that we can refresh LLM tokens from Cloud.

Release Notes:

- N/A
2025-07-18 14:52:42 +00:00
Peter Tripp
6a24b2479c Redact license keys in environment variables from log output (#34711)
Release Notes:

- N/A
2025-07-18 14:28:20 +00:00
Piotr Osiewicz
2ac99e7a11 debugger: Fix attaching with DebugPy (#34706)
@cole-miller found a root cause of our struggles with attach scenarios;
we did not fetch .so files necessary for attaching to work,
as we were downloading DebugPy source tarballs from GitHub.

This PR does away with it by setting up a virtualenv instead that has
debugpy installed.

Closes #34660
Closes #34575

Release Notes:

- debugger: Fixed attaching with DebugPy. DebugPy is now installed
automatically from pip (instead of GitHub), unless it is present in
active virtual environment. Additionally this should resolve any startup
issues with missing .so on Linux.
2025-07-18 14:28:03 +00:00
Peter Tripp
d604b3b291 Fix incorrect minimum_contrast comment (#34710)
Actual default is 45


fd05f17fa7/assets/settings/default.json (L1402)

Release Notes:

- N/A
2025-07-18 10:01:09 -04:00
Bennet Bo Fenner
4002801034 agent: Fix new thread model selection when starting new thread (#34708)
Release Notes:

- agent: Fixed an issue where clicking on "Start New Thread" in the
agent configuration would not always switch to the correct
provider/model
2025-07-18 13:58:44 +00:00
Peter Tripp
fd8480a9dc Document terminal.cursor_shape (#34707)
Release Notes:

- N/A
2025-07-18 13:35:36 +00:00
Danilo Leal
1070de47ec component preview: Add separators between sections in sidebar (#34701)
Small improvement for navigating inside the component preview.

Release Notes:

- N/A
2025-07-18 10:24:57 -03:00
Ben Brandt
cfe1adc792 E2E Claude tests (#34702)
- **Fix cancellation of tool calls**
- **Make tool_call test more resilient**
- **Fix tool call confirmation test**

Release Notes:

- N/A
2025-07-18 13:17:41 +00:00
Lukas Spiss
fd05f17fa7 go: Support raw string subtest names (#34636)
Currently, we're not able to run Go sub-tests that have a raw string
(e.g. we're using multi-line names a lot) via the UI. I added the
changes that are needed, plus a handful of tests to cover the basics.

Quick comparison:

Before:
<img width="901" height="370" alt="before"
src="https://github.com/user-attachments/assets/4e5cadeb-9a0c-49e2-b976-2223e1010f85"
/>



After:
<img width="901" height="505" alt="after"
src="https://github.com/user-attachments/assets/994fc69b-f720-488c-a14b-853a3ca2f53c"
/>


Release Notes:

- Added support for Go subtest runner with raw string names
2025-07-18 13:38:18 +02:00
tidely
7e3fd7bb02 gpui: Use static keyword with LazyLock when loading system fonts (#34555)
Use the `static` keyword to actually make the `LazyLock` static, which
previously would reinitialize on every call to `SvgRenderer::new`.

Related: #26335 

Release Notes:

- N/A
2025-07-18 12:35:38 +02:00
Arseny Kapoulkine
00097df0d5 Improve C/C++ indentation flow for single statement blocks (#34549)
Before this, indentation did not automatically increase after
if/for/while/do/else statements in C++, and only increased after if/for
in C. This led to Zed using last line logic when inserting lines *after*
the indented statement, as well as not indenting the statement itself,
resulting in irregular indentation during typing.

Just adding indentation (similar to C) creates a new problem: now if a
scope is started with a brace on a new line, that brace is indented.
Thus we need to deindent it.

Using else_clause in the indent guide results in the else statement
being indented forward as well, so we need to deindent that too.

Note: the most significant issue for me is the one where indentation
jumps forward when inserting lines after indented lines. Unfortunately,
it appears that fixing that issue requires all of these other changes. I
would have preferred a simpler fix, but I'm not sure if disabling last
line behavior for C/C++ is appropriate as it probably breaks something
else, like cases where the file is incomplete and the statements can't
be parsed properly.

Editing flow before this change:

[Screencast From 2025-07-16
08-31-36.webm](https://github.com/user-attachments/assets/3dea86c5-47bd-47c2-aee8-b0aa613948e6)

Editing flow after this change:

[Screencast From 2025-07-16
08-35-36.webm](https://github.com/user-attachments/assets/7ef23e60-1ee3-49fd-90f9-d53f909ca674)

(note: the "else" snippet is completely breaking the flow here, but I
think that comes from clangd by default? Unfortunately I haven't found a
way to disable it cleanly but that is a separate problem that happens
right now too.)

Release Notes:

- Improve indentation during typing for C/C++ around if/for/while/do
blocks
2025-07-18 12:24:20 +02:00
Andy Waite
c13322397e docs: Document pull diagnostics support for Ruby (#34028)
This is now supported.

Release Notes:

- N/A
2025-07-18 12:46:36 +03:00
Daniel Sauble
c1307cead4 Add ; key binding for Helix mode (#34315)
Closes #34111

In Helix mode, the `;` key should collapse the current selection without
moving the cursor. I've added a new action `vim::HelixCollapseSelection`
to support this behavior.


https://github.com/user-attachments/assets/1a40821a-f56f-456e-9d37-532500bef17b

Release Notes:

- Added `;` key binding to collapse the current text selection in Helix
mode
2025-07-17 22:30:01 -06:00
Danilo Leal
8a7bd5f47b agent: Adjust retry on Burn Mode layout (#34680)
Quick follow-up to https://github.com/zed-industries/zed/pull/34669 so
that the buttons don't look so big in comparison to the callout.

Release Notes:

- N/A
2025-07-18 01:19:27 +00:00
Danilo Leal
c287397a18 Rename "CloseInactiveItems" action to "CloseOtherItems" (#34676)
This is following feedback from folks that were searching the "close
others" action, available in the tab's context menu, and not finding it
because it was actually named "close inactive", which was confusing. So,
this PR makes sure the tab's menu item and the action have consistent
naming.

Release Notes:

- Rename "CloseInactiveItems" action to "CloseOtherItems" for naming
consistency.
2025-07-17 21:40:02 -03:00
Smit Barmase
a7284adafa editor: Fix cursor doesn’t move up and down on arrow keys when no completions are shown (#34678)
Closes #34338

After https://github.com/zed-industries/zed/pull/31872, to avoid
re-querying language servers, we keep the context menu around, which
stores initial query, completions items, etc., even though it may not
contain any items and hence not be rendered on screen. In this state,
up/down arrows try to switch focus in the context menu instead of
propagating it to the editor. Hence blocking buffer movement.

This PR fixes it by changing the context for `menu`,
`showing_completions`, and `showing_code_actions` to only be added when
the menu is actually being rendered (i.e., not empty).

Release Notes:

- Fix an issue where the cursor doesn’t move up and down on arrow keys
when no completions are shown.
2025-07-18 06:07:52 +05:30
Ben Kunkle
4314b35288 keymap_ui: Don't panic on KeybindSource::from_meta (#34652)
Closes #ISSUE

Log error instead of panicking when `from_meta` is passed an invalid
value

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-07-17 20:03:10 -04:00
Danilo Leal
ed4deaa738 agent: Remove layout shift due to the "waiting for confirmation" label (#34674)
Take 2 on https://github.com/zed-industries/zed/pull/33046.

Release Notes:

- N/A
2025-07-17 20:46:16 -03:00
Danilo Leal
f0a91502a9 keymap_ui: Add some design refinements (#34673)
Mostly small stuff over here.

Release Notes:

- N/A
2025-07-17 20:22:21 -03:00
Richard Feldman
1ab659c71f Retry on burn mode (#34669)
Now we only auto-retry if burn mode is enabled. We also show a "Retry"
button (so you don't have to type "continue") if you think that's the
right remedy, and additionally we show a "Retry and Enable Burn Mode"
button if you don't have it enabled.

<img width="484" height="260" alt="Screenshot 2025-07-17 at 6 25 27 PM"
src="https://github.com/user-attachments/assets/dc5bf1f6-8b11-4041-87aa-4f37c95ea9f0"
/>

<img width="478" height="307" alt="Screenshot 2025-07-17 at 6 22 36 PM"
src="https://github.com/user-attachments/assets/1ed6578a-1696-449d-96d1-e447d11959fa"
/>


Release Notes:

- Only auto-retry Agent requests when Burn Mode is enabled
2025-07-17 23:04:03 +00:00
Richard Feldman
d470411725 Improve upstream error reporting (#34668)
Now we handle more upstream error cases using the same auto-retry logic.

Release Notes:

- N/A
2025-07-17 18:12:48 -04:00
Finn Evers
6c741292df keymap_ui: Fix various keymap editor issues (#34647)
This PR tackles miscellaneous nits for the new keymap editor UI.

Release Notes:

- N/A

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-07-17 18:12:10 -04:00
Danilo Leal
29030a243c docs: Add instructions about how to use MCP servers (#34656)
Release Notes:

- N/A
2025-07-17 18:49:44 -03:00
Cole Miller
237ce5c8e8 Report build SHA to Slack for Zed Nightly panics (#34665)
There can be a bunch of nightlies with the same version number and it's
helpful to know exactly which one reported a panic.

Release Notes:

- N/A
2025-07-17 21:38:45 +00:00
Peter Tripp
0c88189aab Refine JetBrains keymaps (#34658)
Follow-up to: https://github.com/zed-industries/zed/pull/34641

Release Notes:

- N/A
2025-07-17 19:23:00 +00:00
Marshall Bowers
1e60ebb2c6 collab: Remove GET /billing/usage endpoint (#34651)
This PR removes the `GET /billing/usage` endpoint, as it has been moved
to `cloud.zed.dev`.

Release Notes:

- N/A
2025-07-17 18:34:04 +00:00
Peter Tripp
9efe9df800 Harmonize buffer_font_size between default.json and initial_settings.json (#34650)
Release Notes:

- N/A
2025-07-17 18:32:04 +00:00
Agus Zubiaga
dab0b3509d Unify agent server settings and extract e2e tests out (#34642)
Release Notes:

- N/A
2025-07-17 17:34:38 +00:00
Smit Barmase
0f72d7ed52 editor: Fix sometimes green (+) cursor style appearing when cmd-clicking in same buffer (#34638)
Follow-up for https://github.com/zed-industries/zed/pull/34557

This PR clears the selection drag state on click, because mouse up
doesn't trigger on click event because of `cx.stop_propagation`. The
issue occurs with similar repro steps as mentioned in the attached PR.

Release Notes:

- Fixed the issue where the green (+) cursor style sometimes appears
when navigating to the definition in buffer.
2025-07-17 22:46:44 +05:30
Peter Tripp
1ceda2babd JetBrains keymap improvements (July 2025) (#34641)
Closes: https://github.com/zed-industries/zed/issues/14639
Closes: https://github.com/zed-industries/zed/issues/33020

If would have ideas for future enhancements, please see:
- https://github.com/zed-industries/zed/discussions/34643

Various Jetbrains keymaps improvements for macOS and Linux/Windows:

| Area | Action | macOS | Linux |
| ------------- | -------------------------- |
--------------------------------- | --------------------------------- |
| Workspace | Toggle Git Panel | `cmd-0` | `ctrl-0` |
| Workspace | Toggle Project Panel | `cmd-1` | `alt-0` |
| Workspace | Toggle Debug Panel | `cmd-5` | `alt-1` |
| Workspace | Toggle Diagnostics | `cmd-6` | `alt-6` |
| Workspace | Toggle Outline Panel | `cmd-7` | `alt-7` |
| Workspace | Toggle Terminal Panel | `alt-f12` | `alt-f12` |
| Workspace | File Finder | `cmd-e` | `ctrl-e` |
| Workspace | Task Spawn | `ctrl-alt-r` | `alt-shift-f10` |
| Workspace | Close All Docks | `ctrl-shift-f12` | `ctrl-shift-f12` |
| Project Panel | Search in Directory | `cmd-shift-f` | `ctrl-shift-f` |
| Search | Replace in Files | `cmd-shift-r` | `ctrl-shift-r` |
| Search | Replace in Buffer | `cmd-r` | `ctrl-r` |
| Search | Toggle Case Sensitive | `ctrl-alt-c` / `alt-c` | `ctrl-alt-c`
|
| Search | Toggle Search in Selection | `ctrl-alt-s` / `alt-s` |
`ctrl-alt-s` |
| Search | Toggle Regex | `ctrl-alt-x` / `alt-x` | `ctrl-alt-x` |
| Search | Toggle Whole Word | `ctrl-alt-w` / `alt-w` | `ctrl-alt-w` |
| Terminal | New Terminal Tab | `cmd-t` | `ctrl-shift-t` |
| Terminal | Scroll Line | `cmd-up` / `cmd-down` | `ctrl-up` /
`ctrl-down` |
| Terminal | Scroll Page | `shift-pageup` / `shift-pagedown` |
`shift-pageup` / `shift-pagedown` |
| Git | Git Panel | `cmd-k` | `ctrl-k` |
| Git | Git Push | `cmd-shift-k` | `ctrl-shift-k` |

In addition, with the help of the recently merged
https://github.com/zed-industries/zed/pull/34495, no matter where you
are mashing `escape` will refocus you back to your most recent editor
buffer similar to the behavior of JetBrains.

Release Notes:

- jetbrains: Added 25+ keybinds to the macOS and Linux/Windows JetBrains
compatibility keymaps
2025-07-17 17:14:24 +00:00
Anthony Eid
ae0d4f6a0d debugger: Add data breakpoint access type support (#34639)
Release Notes:

- Support specifying a data breakpoint's access type - Read, Write, Read
& Write
2025-07-17 17:05:58 +00:00
Piotr Osiewicz
8980526a85 chore: Bump lsp-types rev (#34345)
Closes #ISSUE

Release Notes:

- N/A
2025-07-17 15:58:54 +00:00
Umesh Yadav
b4dc7f8a8a debugger: Add support for running test methods with function receiver in Go (#34613)
![CleanShot 2025-07-17 at 16 35
10](https://github.com/user-attachments/assets/bad794fb-198e-40a1-958c-6ff30a0a4e53)


Closes #33759

Release Notes:

- debugger: Add support for running test methods with function receiver
in Go

Signed-off-by: Umesh Yadav <git@umesh.dev>
2025-07-17 17:44:40 +02:00
Ben Kunkle
b94649ce1e keymap_ui: Show edit icon on hovered and selected row (#34630)
Closes #ISSUE

Improves the behavior of the edit icon in the far left column of the
keymap UI table. It is now shown in both the selected and the hovered
row as an indicator that the row is editable in this configuration. When
hovered a row can be double clicked or the edit icon can be clicked, and
when selected it can be edited via keyboard shortcuts. Additionally, the
edit icon and all other hover tooltips will now disappear when the table
is navigated via keyboard shortcuts.

<details><summary>Video</summary>



https://github.com/user-attachments/assets/6584810f-4c6d-4e6f-bdca-25b16c920cfc

</details>

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-07-17 11:34:34 -04:00
Ben Kunkle
948c1f22bb keymap_ui: Improve keybind display in menus (#34587)
Closes #ISSUE

Defines keybindings for `keymap_editor::EditBinding` and
`keymap_editor::CreateBinding`, making sure those actions are used in
tooltips.

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Finn <dev@bahn.sh>
2025-07-17 11:10:07 -04:00
Ben Brandt
b0eac4267d Mark glob/grep as code blocks (#34628)
Release Notes:

- N/A
2025-07-17 15:01:02 +00:00
Agus Zubiaga
8e4555455c Claude experiment (#34577)
Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: Nathan Sobo <nathan@zed.dev>
Co-authored-by: Oleksiy Syvokon <oleksiy.syvokon@gmail.com>
2025-07-17 14:25:55 +00:00
Kirill Bulatov
5b97cd1900 Better serialize the git panel (#34622)
Follow-up of https://github.com/zed-industries/zed/pull/29874
Closes https://github.com/zed-industries/zed/issues/34618
Closes https://github.com/zed-industries/zed/issues/34611

Release Notes:

- N/A
2025-07-17 12:39:41 +00:00
Kirill Bulatov
ceab139f54 Rework extension-related errors (#34620)
Before:
<img width="1728" height="1079" alt="before"
src="https://github.com/user-attachments/assets/4ab19211-8de4-458d-a835-52de859b7b20"
/>

After:
<img width="1728" height="1079" alt="after"
src="https://github.com/user-attachments/assets/231c9362-a0b0-47ae-b92e-de6742781d36"
/>

Makes clear which path is causing the FS error and removes backtraces
from logging.

Release Notes:

- N/A
2025-07-17 12:20:47 +00:00
Oleksiy Syvokon
4df7f52bf3 agent: Disable project_notifications by default (#34615)
This tool needs more polishing before being generally available.

Release Notes:

- agent: Disabled `project_notifications` tool by default for the time
being
2025-07-17 11:55:38 +00:00
Oleksiy Syvokon
1e67e30034 Fix shortcuts with Shift (#34614)
Closes #34605, #34606, #34609

Release Notes:

- Fixed shortcuts involving Shift
2025-07-17 11:21:20 +00:00
Arseny Kapoulkine
758c5fb955 Allow disabling snippet completion by setting snippet_sort_order to none (#34565)
This mirrors VSCode setting that inspired `snippet_sort_order` to begin
with; VSCode supports inline/top/bottom/none, with none completely
disabling snippet completion. See
https://code.visualstudio.com/docs/editing/intellisense#_snippets-in-suggestions

This is helpful for LSPs that do not allow configuring snippets via
configuration such as clangd.

Release Notes:

- Added `none` as one of the values for `snippet_sort_order` to
completely disable snippet completion.
2025-07-17 16:22:26 +05:30
Oleksiy Syvokon
acb3ecef0c Do not send project notifications when agent creates a file (#34610)
Release Notes:

- N/A
2025-07-17 13:08:20 +03:00
Finn Evers
ad2bfa3edd Disable minimap in the inspector (#34607)
This disables the minimap in the inspector UI as it doesn't bring any
value to it and just takes up unnecessary space.

Release Notes:

- N/A
2025-07-17 09:22:04 +00:00
Eric Cornelissen
1d72fa8e9e git: Add ability to pass --signoff (#29874)
This adds an option for `--signoff` to the git panel and commit modal.
It allows users to enable the [`--signoff`
flag](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt-code--signoffcode)
when committing through Zed. The option is added to the context menu of
the commit button (following the style of the "Editor Controls").

To support this, the commit+amend experience was revamped (following the
ideas of [this
comment](https://github.com/zed-industries/zed/pull/29874#issuecomment-2950848000)).
Amending is now also a toggle in the commit button's dropdown menu. I've
kept some of the original experience such as the changed button text and
ability to cancel outside the context menu.

The tooltip of the commit buttons now also includes the flags that will
be used based on the amending and signoff status (which I couldn't
capture in screenshots unfortunately). So, by default the tooltip will
say `git commit` and if you toggle, e.g., amending on it will say `git
commit --amend`.

| What | Panel | Modal |
| --- | --- | --- |
| Not amending, dropdown | ![git modal preview, not amending,
dropdown](https://github.com/user-attachments/assets/82c2b338-b3b5-418c-97bf-98c33202d7dd)
| ![commit modal preview, not amending,
dropdown](https://github.com/user-attachments/assets/f7a6f2fb-902d-447d-a473-2efb4ba0f444)
|
| Amending, dropdown | ![git modal preview, amending,
dropdown](https://github.com/user-attachments/assets/9e755975-4a27-43f0-aa62-be002ecd3a92)
| ![commit modal preview, amending,
dropdown](https://github.com/user-attachments/assets/cad03817-14e1-46f6-ba39-8ccc7dd12161)
|
| Amending | ![git modal preview,
amending](https://github.com/user-attachments/assets/e1ec4eba-174e-4e5f-9659-5867d6b0fdc2)
| - |

The initial implementation was based on the changeset of
https://github.com/zed-industries/zed/pull/28187.

Closes https://github.com/zed-industries/zed/discussions/26114

Release Notes:

- Added git `--signoff` support.
- Update the git `--amend` experience.
- Improved git panel to persist width as well as amend and signoff on a
per-workspace basis.
2025-07-17 03:39:54 +00:00
Conrad Irwin
1ce384bbda Fix ctrl-q on AZERTY on Linux (#34597)
Closes #ISSUE

Release Notes:

- N/A
2025-07-16 21:27:46 -06:00
Conrad Irwin
9f302df6d6 Don't override ascii graphical shortcuts (#34592)
Closes #34536

Release Notes:

- (preview only) Fix shortcuts on Extended Latin keyboards on Linux
2025-07-16 20:09:38 -06:00
Smit Barmase
ebad5ca50e linux: Fix buttons clicks wouldn’t work on startup until clicked on center pane (#34590)
Closes #31805

This is an issue with Linux currently that `window.focus` is `None` upon
startup in both X11 and Wayland. Specifically, the order in which
[this](8d05a3d389/crates/gpui/src/window.rs (L3116))
and
[this](8d05a3d389/crates/gpui/src/app.rs (L956))
are executed varies between Linux and macOS. That is, one tries to
remove (blur) focus from a window, while other checks window focus to
put that focus id to a frame. In macOS, blur happens afterwards setting
focus on a frame, but in Linux, the inverse of it happens, leading to
`window.focus` to `None`.

For the time being, we handle all visible buttons to take care of this
**focus can be `None`** case, and make it work anyway. But, we should
look at the deeper issue mentioned above with GPUI. Created new issue to
track that https://github.com/zed-industries/zed/issues/34591.

Release Notes:

- Fixed an issue where button clicks wouldn’t work on startup until
clicked on the center pane on Linux.
2025-07-17 06:36:02 +05:30
Cole Miller
b9ff538747 docs: Discuss inlay_hints.show_value_hints in debugger docs (#34581)
This isn't under the `debugger` settings key, but it seems good to
document on the debugger page anyway.

Release Notes:

- N/A
2025-07-16 19:35:30 -04:00
Anthony Eid
c0261a1ea9 keymap ui: Fix keymap editor search bugs (#34579)
Keystroke input now gets cleared when toggling to normal search mode
Main search bar is focused when toggling to normal search mode

This also gets rid of highlight on focus from keystroke_editor because
it also matched the search bool field and was redundant

Release Notes:

- N/A
2025-07-16 18:05:26 -04:00
Marshall Bowers
f43bcc1492 collab: Remove GET /billing/subscriptions endpoint (#34580)
This PR removes the `GET /billing/subscriptions` endpoint, as it has
been moved to `cloud.zed.dev`.

Release Notes:

- N/A
2025-07-16 22:04:53 +00:00
Umesh Yadav
e23a4564cc keymap_ui: Open Keymap editor from settings dropdown (#34576)
@probably-neb I guess we should be opening the keymap editor from title
bar and menu as well. I believe this got missed in this: #34568.

Release Notes:

- Open Keymap editor from settings from menu and title bar.
2025-07-16 17:30:08 -04:00
Peter Tripp
f82ef1f76f agent: Support GEMINI_API_KEY environment variable (#34574)
Google Gemini Docs now recommend usage of `GEMINI_API_KEY` and the
legacy `GOOGLE_AI_API_KEY` variable is no longer supported in the modern
SDKs.

Zed will now accept either.

Release Notes:

- N/A
2025-07-16 20:55:54 +00:00
Richard Feldman
b4c2ae5196 Handle upstream_http_error completion responses (#34573)
Addresses upstream errors such as:
<img width="831" height="100" alt="Screenshot 2025-07-16 at 3 37 03 PM"
src="https://github.com/user-attachments/assets/2aeb0257-6761-4148-b687-25fae93c68d8"
/>

These should now automatically retry like other upstream HTTP error
codes.

Release Notes:

- N/A
2025-07-16 16:31:31 -04:00
Peter Tripp
0023773c68 docs: Add Zed as Git Editor example (#34572)
Release Notes:

- N/A
2025-07-16 19:57:02 +00:00
Anthony Eid
0bde929d54 Add keymap editor UI telemetry events (#34571)
- Search queries
- Keybinding update or removed
- Copy action name
- Copy context name

cc @katie-z-geer 

Release Notes:

- N/A

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-07-16 19:50:53 +00:00
Joseph T. Lyons
6f60939d30 Bump Zed to v0.197 (#34569)
Release Notes:

-N/A
2025-07-16 18:48:50 +00:00
568 changed files with 41753 additions and 14673 deletions

View File

@@ -24,7 +24,7 @@ workspace-members = [
third-party = [
{ name = "reqwest", version = "0.11.27" },
# build of remote_server should not include scap / its x11 dependency
{ name = "scap", git = "https://github.com/zed-industries/scap", rev = "270538dc780f5240723233ff901e1054641ed318" },
{ name = "scap", git = "https://github.com/zed-industries/scap", rev = "808aa5c45b41e8f44729d02e38fd00a2fe2722e7" },
]
[final-excludes]

30
.github/actionlint.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
# Configuration related to self-hosted runner.
self-hosted-runner:
# Labels of self-hosted runner in array of strings.
labels:
# GitHub-hosted Runners
- github-8vcpu-ubuntu-2404
- github-16vcpu-ubuntu-2404
- windows-2025-16
- windows-2025-32
- windows-2025-64
# Buildjet Ubuntu 20.04 - AMD x86_64
- buildjet-2vcpu-ubuntu-2004
- buildjet-4vcpu-ubuntu-2004
- buildjet-8vcpu-ubuntu-2004
- buildjet-16vcpu-ubuntu-2004
- buildjet-32vcpu-ubuntu-2004
# Buildjet Ubuntu 22.04 - AMD x86_64
- buildjet-2vcpu-ubuntu-2204
- buildjet-4vcpu-ubuntu-2204
- buildjet-8vcpu-ubuntu-2204
- buildjet-16vcpu-ubuntu-2204
- buildjet-32vcpu-ubuntu-2204
# Buildjet Ubuntu 22.04 - Graviton aarch64
- buildjet-8vcpu-ubuntu-2204-arm
- buildjet-16vcpu-ubuntu-2204-arm
- buildjet-32vcpu-ubuntu-2204-arm
- buildjet-64vcpu-ubuntu-2204-arm
# Self Hosted Runners
- self-mini-macos
- self-32vcpu-windows-2022

View File

@@ -19,7 +19,7 @@ runs:
shell: bash -euxo pipefail {0}
run: ./script/linux
- name: Check for broken links
- name: Check for broken links (in MD)
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1
with:
args: --no-progress --exclude '^http' './docs/src/**/*'
@@ -30,3 +30,9 @@ runs:
run: |
mkdir -p target/deploy
mdbook build ./docs --dest-dir=../target/deploy/docs/
- name: Check for broken links (in HTML)
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1
with:
args: --no-progress --exclude '^http' 'target/deploy/docs/'
fail: true

View File

@@ -28,7 +28,7 @@ jobs:
run: |
set -eux
channel=$(cat crates/zed/RELEASE_CHANNEL)
channel="$(cat crates/zed/RELEASE_CHANNEL)"
tag_suffix=""
case $channel in
@@ -43,9 +43,9 @@ jobs:
;;
esac
which cargo-set-version > /dev/null || cargo install cargo-edit
output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')
output="$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')"
export GIT_COMMITTER_NAME="Zed Bot"
export GIT_COMMITTER_EMAIL="hi@zed.dev"
git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot <hi@zed.dev>"
git tag v${output}${tag_suffix}
git push origin HEAD v${output}${tag_suffix}
git tag "v${output}${tag_suffix}"
git push origin HEAD "v${output}${tag_suffix}"

View File

@@ -34,6 +34,7 @@ jobs:
run_license: ${{ steps.filter.outputs.run_license }}
run_docs: ${{ steps.filter.outputs.run_docs }}
run_nix: ${{ steps.filter.outputs.run_nix }}
run_actionlint: ${{ steps.filter.outputs.run_actionlint }}
runs-on:
- ubuntu-latest
steps:
@@ -47,39 +48,40 @@ jobs:
run: |
if [ -z "$GITHUB_BASE_REF" ]; then
echo "Not in a PR context (i.e., push to main/stable/preview)"
COMPARE_REV=$(git rev-parse HEAD~1)
COMPARE_REV="$(git rev-parse HEAD~1)"
else
echo "In a PR context comparing to pull_request.base.ref"
git fetch origin "$GITHUB_BASE_REF" --depth=350
COMPARE_REV=$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)
COMPARE_REV="$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)"
fi
# Specify anything which should skip full CI in this regex:
CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" ${{ github.sha }})"
# Specify anything which should potentially skip full test suite in this regex:
# - docs/
# - script/update_top_ranking_issues/
# - .github/ISSUE_TEMPLATE/
# - .github/workflows/ (except .github/workflows/ci.yml)
SKIP_REGEX='^(docs/|script/update_top_ranking_issues/|\.github/(ISSUE_TEMPLATE|workflows/(?!ci)))'
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep -vP "$SKIP_REGEX") ]]; then
echo "run_tests=true" >> $GITHUB_OUTPUT
else
echo "run_tests=false" >> $GITHUB_OUTPUT
fi
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep '^docs/') ]]; then
echo "run_docs=true" >> $GITHUB_OUTPUT
else
echo "run_docs=false" >> $GITHUB_OUTPUT
fi
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep -P '^(Cargo.lock|script/.*licenses)') ]]; then
echo "run_license=true" >> $GITHUB_OUTPUT
else
echo "run_license=false" >> $GITHUB_OUTPUT
fi
NIX_REGEX='^(nix/|flake\.|Cargo\.|rust-toolchain.toml|\.cargo/config.toml)'
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep -P "$NIX_REGEX") ]]; then
echo "run_nix=true" >> $GITHUB_OUTPUT
else
echo "run_nix=false" >> $GITHUB_OUTPUT
fi
echo "$CHANGED_FILES" | grep -qvP "$SKIP_REGEX" && \
echo "run_tests=true" >> "$GITHUB_OUTPUT" || \
echo "run_tests=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^docs/' && \
echo "run_docs=true" >> "$GITHUB_OUTPUT" || \
echo "run_docs=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^\.github/(workflows/|actions/|actionlint.yml)' && \
echo "run_actionlint=true" >> "$GITHUB_OUTPUT" || \
echo "run_actionlint=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^(Cargo.lock|script/.*licenses)' && \
echo "run_license=true" >> "$GITHUB_OUTPUT" || \
echo "run_license=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^(nix/|flake\.|Cargo\.|rust-toolchain.toml|\.cargo/config.toml)' && \
echo "run_nix=true" >> "$GITHUB_OUTPUT" || \
echo "run_nix=false" >> "$GITHUB_OUTPUT"
migration_checks:
name: Check Postgres and Protobuf migrations, mergability
@@ -89,8 +91,7 @@ jobs:
needs.job_spec.outputs.run_tests == 'true'
timeout-minutes: 60
runs-on:
- self-hosted
- macOS
- self-mini-macos
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -112,11 +113,11 @@ jobs:
run: |
if [ -z "$GITHUB_BASE_REF" ];
then
echo "BUF_BASE_BRANCH=$(git merge-base origin/main HEAD)" >> $GITHUB_ENV
echo "BUF_BASE_BRANCH=$(git merge-base origin/main HEAD)" >> "$GITHUB_ENV"
else
git checkout -B temp
git merge -q origin/$GITHUB_BASE_REF -m "merge main into temp"
echo "BUF_BASE_BRANCH=$GITHUB_BASE_REF" >> $GITHUB_ENV
git merge -q "origin/$GITHUB_BASE_REF" -m "merge main into temp"
echo "BUF_BASE_BRANCH=$GITHUB_BASE_REF" >> "$GITHUB_ENV"
fi
- uses: bufbuild/buf-setup-action@v1
@@ -140,7 +141,7 @@ jobs:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Install cargo-hakari
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386 # v2
with:
@@ -178,7 +179,7 @@ jobs:
- name: Prettier Check on /docs
working-directory: ./docs
run: |
pnpm dlx prettier@${PRETTIER_VERSION} . --check || {
pnpm dlx "prettier@${PRETTIER_VERSION}" . --check || {
echo "To fix, run from the root of the Zed repo:"
echo " cd docs && pnpm dlx prettier@${PRETTIER_VERSION} . --write && cd .."
false
@@ -188,7 +189,7 @@ jobs:
- name: Prettier Check on default.json
run: |
pnpm dlx prettier@${PRETTIER_VERSION} assets/settings/default.json --check || {
pnpm dlx "prettier@${PRETTIER_VERSION}" assets/settings/default.json --check || {
echo "To fix, run from the root of the Zed repo:"
echo " pnpm dlx prettier@${PRETTIER_VERSION} assets/settings/default.json --write"
false
@@ -234,6 +235,20 @@ jobs:
- name: Build docs
uses: ./.github/actions/build_docs
actionlint:
runs-on: ubuntu-latest
if: github.repository_owner == 'zed-industries' && needs.job_spec.outputs.run_actionlint == 'true'
needs: [job_spec]
steps:
- uses: actions/checkout@v4
- name: Download actionlint
id: get_actionlint
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
shell: bash
- name: Check workflow files
run: ${{ steps.get_actionlint.outputs.executable }} -color
shell: bash
macos_tests:
timeout-minutes: 60
name: (macOS) Run Clippy and tests
@@ -242,8 +257,7 @@ jobs:
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- self-hosted
- macOS
- self-mini-macos
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -255,6 +269,10 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Check that Cargo.lock is up to date
run: |
cargo update --locked --workspace
- name: cargo clippy
run: ./script/clippy
@@ -312,7 +330,7 @@ jobs:
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -364,7 +382,7 @@ jobs:
- buildjet-8vcpu-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -444,6 +462,7 @@ jobs:
- job_spec
- style
- check_docs
- actionlint
- migration_checks
# run_tests: If adding required tests, add them here and to script below.
- workspace_hack
@@ -465,6 +484,11 @@ jobs:
if [[ "${{ needs.job_spec.outputs.run_docs }}" == "true" ]]; then
[[ "${{ needs.check_docs.result }}" != 'success' ]] && { RET_CODE=1; echo "docs checks failed"; }
fi
if [[ "${{ needs.job_spec.outputs.run_actionlint }}" == "true" ]]; then
[[ "${{ needs.actionlint.result }}" != 'success' ]] && { RET_CODE=1; echo "actionlint checks failed"; }
fi
# Only check test jobs if they were supposed to run
if [[ "${{ needs.job_spec.outputs.run_tests }}" == "true" ]]; then
[[ "${{ needs.workspace_hack.result }}" != 'success' ]] && { RET_CODE=1; echo "Workspace Hack failed"; }
@@ -484,8 +508,7 @@ jobs:
timeout-minutes: 120
name: Create a macOS bundle
runs-on:
- self-hosted
- bundle
- self-mini-macos
if: |
startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
@@ -748,7 +771,8 @@ jobs:
timeout-minutes: 120
name: Create a Windows installer
runs-on: [self-hosted, Windows, X64]
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
# if: (startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling'))
needs: [windows_tests]
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
@@ -787,7 +811,7 @@ jobs:
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
# Re-enable when we are ready to publish windows preview releases
if: false && ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) && env.RELEASE_CHANNEL == 'preview' }} # upload only preview
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) && env.RELEASE_CHANNEL == 'preview' }} # upload only preview
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
@@ -802,10 +826,9 @@ jobs:
&& endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, bundle-windows-x64]
runs-on:
- self-hosted
- bundle
- self-mini-macos
steps:
- name: gh release
run: gh release edit $GITHUB_REF_NAME --draft=false
run: gh release edit "$GITHUB_REF_NAME" --draft=false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -18,7 +18,7 @@ jobs:
URL="https://zed.dev/releases/stable/latest"
fi
echo "URL=$URL" >> $GITHUB_OUTPUT
echo "URL=$URL" >> "$GITHUB_OUTPUT"
- name: Get content
uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757 # v1.4.1
id: get-content
@@ -50,9 +50,9 @@ jobs:
PREVIEW_TAG="${VERSION}-pre"
if git rev-parse "$PREVIEW_TAG" > /dev/null 2>&1; then
echo "was_promoted_from_preview=true" >> $GITHUB_OUTPUT
echo "was_promoted_from_preview=true" >> "$GITHUB_OUTPUT"
else
echo "was_promoted_from_preview=false" >> $GITHUB_OUTPUT
echo "was_promoted_from_preview=false" >> "$GITHUB_OUTPUT"
fi
- name: Send release notes email

View File

@@ -79,12 +79,12 @@ jobs:
- name: Build docker image
run: |
docker build -f Dockerfile-collab \
--build-arg GITHUB_SHA=$GITHUB_SHA \
--tag registry.digitalocean.com/zed/collab:$GITHUB_SHA \
--build-arg "GITHUB_SHA=$GITHUB_SHA" \
--tag "registry.digitalocean.com/zed/collab:$GITHUB_SHA" \
.
- name: Publish docker image
run: docker push registry.digitalocean.com/zed/collab:${GITHUB_SHA}
run: docker push "registry.digitalocean.com/zed/collab:${GITHUB_SHA}"
- name: Prune Docker system
run: docker system prune --filter 'until=72h' -f
@@ -131,7 +131,8 @@ jobs:
source script/lib/deploy-helpers.sh
export_vars_for_environment $ZED_KUBE_NAMESPACE
export ZED_DO_CERTIFICATE_ID=$(doctl compute certificate list --format ID --no-header)
ZED_DO_CERTIFICATE_ID="$(doctl compute certificate list --format ID --no-header)"
export ZED_DO_CERTIFICATE_ID
export ZED_IMAGE_ID="registry.digitalocean.com/zed/collab:${GITHUB_SHA}"
export ZED_SERVICE_NAME=collab

View File

@@ -35,7 +35,7 @@ jobs:
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

View File

@@ -43,8 +43,8 @@ jobs:
- name: Set path
if: ${{ ! matrix.system.install_nix }}
run: |
echo "/nix/var/nix/profiles/default/bin" >> $GITHUB_PATH
echo "/Users/administrator/.nix-profile/bin" >> $GITHUB_PATH
echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH"
echo "/Users/administrator/.nix-profile/bin" >> "$GITHUB_PATH"
- uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
if: ${{ matrix.system.install_nix }}
@@ -56,11 +56,13 @@ jobs:
name: zed
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
pushFilter: "${{ inputs.cachix-filter }}"
cachixArgs: '-v'
cachixArgs: "-v"
- run: nix build .#${{ inputs.flake-output }} -L --accept-flake-config
- name: Limit /nix/store to 50GB on macs
if: ${{ ! matrix.system.install_nix }}
run: |
[ $(du -sm /nix/store | cut -f1) -gt 50000 ] && nix-collect-garbage -d || :
if [ "$(du -sm /nix/store | cut -f1)" -gt 50000 ]; then
nix-collect-garbage -d || true
fi

View File

@@ -85,8 +85,7 @@ jobs:
name: Create a macOS bundle
if: github.repository_owner == 'zed-industries'
runs-on:
- self-hosted
- bundle
- self-mini-macos
needs: tests
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
@@ -112,6 +111,11 @@ jobs:
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Create macOS app bundle
run: script/bundle-mac
@@ -132,11 +136,16 @@ jobs:
clean: false
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Install Linux dependencies
run: ./script/linux && ./script/install-mold 2.34.0
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
@@ -169,6 +178,11 @@ jobs:
- name: Install Linux dependencies
run: ./script/linux
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
@@ -263,6 +277,11 @@ jobs:
Write-Host "Publishing version: $version on release channel nightly"
"nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Build Zed installer
working-directory: ${{ env.ZED_WORKSPACE }}
run: script/bundle-windows.ps1

View File

@@ -26,7 +26,7 @@ jobs:
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

645
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,13 @@
[workspace]
resolver = "2"
members = [
"crates/acp_thread",
"crates/activity_indicator",
"crates/acp",
"crates/agent_ui",
"crates/agent",
"crates/agent_settings",
"crates/agent_servers",
"crates/agent_settings",
"crates/agent_ui",
"crates/ai_onboarding",
"crates/anthropic",
"crates/askpass",
"crates/assets",
@@ -28,6 +29,9 @@ members = [
"crates/cli",
"crates/client",
"crates/clock",
"crates/cloud_api_client",
"crates/cloud_api_types",
"crates/cloud_llm_client",
"crates/collab",
"crates/collab_ui",
"crates/collections",
@@ -47,8 +51,8 @@ members = [
"crates/diagnostics",
"crates/docs_preprocessor",
"crates/editor",
"crates/explorer_command_injector",
"crates/eval",
"crates/explorer_command_injector",
"crates/extension",
"crates/extension_api",
"crates/extension_cli",
@@ -69,7 +73,6 @@ members = [
"crates/gpui",
"crates/gpui_macros",
"crates/gpui_tokio",
"crates/html_to_markdown",
"crates/http_client",
"crates/http_client_tls",
@@ -98,14 +101,15 @@ members = [
"crates/markdown_preview",
"crates/media",
"crates/menu",
"crates/svg_preview",
"crates/migrator",
"crates/mistral",
"crates/multi_buffer",
"crates/nc",
"crates/net",
"crates/node_runtime",
"crates/notifications",
"crates/ollama",
"crates/onboarding",
"crates/open_ai",
"crates/open_router",
"crates/outline",
@@ -137,6 +141,7 @@ members = [
"crates/semantic_version",
"crates/session",
"crates/settings",
"crates/settings_profile_selector",
"crates/settings_ui",
"crates/snippet",
"crates/snippet_provider",
@@ -149,6 +154,7 @@ members = [
"crates/sum_tree",
"crates/supermaven",
"crates/supermaven_api",
"crates/svg_preview",
"crates/tab_switcher",
"crates/task",
"crates/tasks_ui",
@@ -219,13 +225,14 @@ edition = "2024"
# Workspace member crates
#
acp = { path = "crates/acp" }
acp_thread = { path = "crates/acp_thread" }
agent = { path = "crates/agent" }
activity_indicator = { path = "crates/activity_indicator" }
agent_ui = { path = "crates/agent_ui" }
agent_settings = { path = "crates/agent_settings" }
agent_servers = { path = "crates/agent_servers" }
ai = { path = "crates/ai" }
ai_onboarding = { path = "crates/ai_onboarding" }
anthropic = { path = "crates/anthropic" }
askpass = { path = "crates/askpass" }
assets = { path = "crates/assets" }
@@ -247,6 +254,9 @@ channel = { path = "crates/channel" }
cli = { path = "crates/cli" }
client = { path = "crates/client" }
clock = { path = "crates/clock" }
cloud_api_client = { path = "crates/cloud_api_client" }
cloud_api_types = { path = "crates/cloud_api_types" }
cloud_llm_client = { path = "crates/cloud_llm_client" }
collab = { path = "crates/collab" }
collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections" }
@@ -317,10 +327,12 @@ menu = { path = "crates/menu" }
migrator = { path = "crates/migrator" }
mistral = { path = "crates/mistral" }
multi_buffer = { path = "crates/multi_buffer" }
nc = { path = "crates/nc" }
net = { path = "crates/net" }
node_runtime = { path = "crates/node_runtime" }
notifications = { path = "crates/notifications" }
ollama = { path = "crates/ollama" }
onboarding = { path = "crates/onboarding" }
open_ai = { path = "crates/open_ai" }
open_router = { path = "crates/open_router", features = ["schemars"] }
outline = { path = "crates/outline" }
@@ -331,6 +343,7 @@ picker = { path = "crates/picker" }
plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }
prettier = { path = "crates/prettier" }
settings_profile_selector = { path = "crates/settings_profile_selector" }
project = { path = "crates/project" }
project_panel = { path = "crates/project_panel" }
project_symbols = { path = "crates/project_symbols" }
@@ -406,7 +419,8 @@ zlog_settings = { path = "crates/zlog_settings" }
# External crates
#
agentic-coding-protocol = { version = "0.0.9" }
agentic-coding-protocol = "0.0.10"
agent-client-protocol = "0.0.11"
aho-corasick = "1.1"
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
any_vec = "0.14"
@@ -434,9 +448,9 @@ aws-smithy-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] }
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
base64 = "0.22"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blade-util = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
blade-util = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
blake3 = "1.5.3"
bytes = "1.0"
cargo_metadata = "0.19"
@@ -453,7 +467,7 @@ core-video = { version = "0.4.3", features = ["metal"] }
cpal = "0.16"
criterion = { version = "0.5", features = ["html_reports"] }
ctor = "0.4.0"
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "7f39295b441614ca9dbf44293e53c32f666897f9" }
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "1b461b310481d01e02b2603c16d7144b926339f8" }
dashmap = "6.0"
derive_more = "0.99.17"
dirs = "4.0"
@@ -476,6 +490,7 @@ heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
hex = "0.4.3"
html5ever = "0.27.0"
http = "1.1"
http-body = "1.0"
hyper = "0.14"
ignore = "0.4.22"
image = "0.25.1"
@@ -489,18 +504,18 @@ json_dotpath = "1.1"
jsonschema = "0.30.0"
jsonwebtoken = "9.3"
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed" ,rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "6add7052b598ea1f40f7e8913622c3958b009b60" }
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "39f629bdd03d59abd786ed9fc27e8bca02c0c0ec" }
markup5ever_rcdom = "0.3.0"
metal = "0.29"
moka = { version = "0.12.10", features = ["sync"] }
naga = { version = "25.0", features = ["wgsl-in"] }
nanoid = "0.4"
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
nix = "0.29"
num-format = "0.4.4"
objc = "0.2"
@@ -541,7 +556,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c77
"stream",
] }
rsa = "0.9.6"
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
"async-dispatcher-runtime",
] }
rust-embed = { version = "8.4", features = ["include-exclude"] }
@@ -549,8 +564,7 @@ rustc-demangle = "0.1.23"
rustc-hash = "2.1.0"
rustls = { version = "0.23.26" }
rustls-platform-verifier = "0.5.0"
# When updating scap rev, also update it in .config/hakari.toml
scap = { git = "https://github.com/zed-industries/scap", rev = "270538dc780f5240723233ff901e1054641ed318", default-features = false }
scap = { git = "https://github.com/zed-industries/scap", rev = "808aa5c45b41e8f44729d02e38fd00a2fe2722e7", default-features = false }
schemars = { version = "1.0", features = ["indexmap2"] }
semver = "1.0"
serde = { version = "1.0", features = ["derive", "rc"] }
@@ -638,7 +652,6 @@ which = "6.0.0"
windows-core = "0.61"
wit-component = "0.221"
workspace-hack = "0.1.0"
zed_llm_client = "= 0.8.6"
zstd = "0.11"
[workspace.dependencies.async-stripe]
@@ -665,14 +678,16 @@ features = [
"UI_ViewManagement",
"Wdk_System_SystemServices",
"Win32_Globalization",
"Win32_Graphics_Direct2D",
"Win32_Graphics_Direct2D_Common",
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D11",
"Win32_Graphics_Direct3D_Fxc",
"Win32_Graphics_DirectComposition",
"Win32_Graphics_DirectWrite",
"Win32_Graphics_Dwm",
"Win32_Graphics_Dxgi",
"Win32_Graphics_Dxgi_Common",
"Win32_Graphics_Gdi",
"Win32_Graphics_Imaging",
"Win32_Graphics_Imaging_D2D",
"Win32_Networking_WinSock",
"Win32_Security",
"Win32_Security_Credentials",
@@ -704,6 +719,7 @@ features = [
[patch.crates-io]
notify = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }
notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }
windows-capture = { git = "https://github.com/zed-industries/windows-capture.git", rev = "f0d6c1b6691db75461b732f6d5ff56eed002eeb9" }
# Makes the workspace hack crate refer to the local one, but only when you're building locally
workspace-hack = { path = "tooling/workspace-hack" }
@@ -712,6 +728,11 @@ workspace-hack = { path = "tooling/workspace-hack" }
split-debuginfo = "unpacked"
codegen-units = 16
# mirror configuration for crates compiled for the build platform
# (without this cargo will compile ~400 crates twice)
[profile.dev.build-override]
codegen-units = 16
[profile.dev.package]
taffy = { opt-level = 3 }
cranelift-codegen = { opt-level = 3 }

View File

@@ -1,5 +1,6 @@
# Zed
[![Zed](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/zed-industries/zed/main/assets/badge/v0.json)](https://zed.dev)
[![CI](https://github.com/zed-industries/zed/actions/workflows/ci.yml/badge.svg)](https://github.com/zed-industries/zed/actions/workflows/ci.yml)
Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom](https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter).

8
assets/badge/v0.json Normal file
View File

@@ -0,0 +1,8 @@
{
"label": "",
"message": "Zed",
"logoSvg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 96 96\"><rect width=\"96\" height=\"96\" fill=\"#000\"/><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M9 6C7.34315 6 6 7.34315 6 9V75H0V9C0 4.02944 4.02944 0 9 0H89.3787C93.3878 0 95.3955 4.84715 92.5607 7.68198L43.0551 57.1875H57V51H63V58.6875C63 61.1728 60.9853 63.1875 58.5 63.1875H37.0551L26.7426 73.5H73.5V36H79.5V73.5C79.5 76.8137 76.8137 79.5 73.5 79.5H20.7426L10.2426 90H87C88.6569 90 90 88.6569 90 87V21H96V87C96 91.9706 91.9706 96 87 96H6.62132C2.61224 96 0.604504 91.1529 3.43934 88.318L52.7574 39H39V45H33V37.5C33 35.0147 35.0147 33 37.5 33H58.7574L69.2574 22.5H22.5V60H16.5V22.5C16.5 19.1863 19.1863 16.5 22.5 16.5H75.2574L85.7574 6H9Z\" fill=\"#fff\"/></svg>",
"logoWidth": 16,
"labelColor": "black",
"color": "white"
}

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.35443 9.97775L6.71495 8.65418L6.75443 8.53883L6.71495 8.47508H6.59948L6.20456 8.45081L4.8557 8.41436L3.68608 8.36579L2.55291 8.30507L2.26734 8.24438L2 7.89224L2.02734 7.71617L2.26734 7.55528L2.61063 7.58564L3.37013 7.63724L4.50937 7.71617L5.3357 7.76474L6.56 7.89224H6.75443L6.78176 7.81331L6.71495 7.76474L6.66329 7.71617L5.48456 6.91778L4.20861 6.07388L3.54025 5.58815L3.17873 5.34227L2.99646 5.11157L2.91747 4.60764L3.24557 4.24639L3.68608 4.27675L3.79848 4.30711L4.24506 4.65014L5.19899 5.38781L6.44456 6.30458L6.62684 6.45635L6.69974 6.40475L6.70886 6.36833L6.62684 6.23171L5.94938 5.00834L5.22632 3.76372L4.9043 3.24766L4.81924 2.93802C4.78886 2.81053 4.7676 2.70427 4.7676 2.57374L5.14127 2.06678L5.34785 2L5.84609 2.06678L6.0557 2.24893L6.36557 2.95624L6.86684 4.07033L7.64456 5.58512L7.87241 6.0344L7.99391 6.45029L8.03948 6.57779H8.11847V6.50492L8.18228 5.6519L8.30075 4.6046L8.41619 3.25677L8.4557 2.87731L8.64404 2.42196L9.01772 2.17607L9.30938 2.31571L9.54938 2.65874L9.51596 2.88034L9.37316 3.80622L9.09368 5.25728L8.9114 6.22868H9.01772L9.13925 6.10727L9.6314 5.45459L10.4577 4.42246L10.8223 4.01265L11.2476 3.56033L11.521 3.3448H12.0375L12.4172 3.90944L12.2471 4.49228L11.7154 5.1662L11.275 5.73692L10.643 6.58691L10.2481 7.26689L10.2846 7.32152L10.3787 7.31243L11.8066 7.00886L12.5782 6.86921L13.4987 6.71135L13.915 6.90563L13.9605 7.10297L13.7965 7.50671L12.8122 7.74956L11.6577 7.98026L9.93824 8.38706L9.91697 8.40224L9.94127 8.43257L10.716 8.50544L11.0471 8.52365H11.8582L13.3681 8.63597L13.763 8.89703L14 9.21578L13.9605 9.45863L13.3529 9.76829L12.5327 9.57398L10.6187 9.11864L9.96254 8.95472H9.8714V9.00935L10.4182 9.54365L11.4208 10.4483L12.6754 11.614L12.7393 11.9023L12.5782 12.13L12.4081 12.1057L11.3053 11.277L10.88 10.9036L9.91697 10.0931H9.85316V10.1781L10.075 10.5029L11.2476 12.2636L11.3083 12.804L11.2233 12.98L10.9195 13.0863L10.5853 13.0255L9.89873 12.0632L9.19088 10.9795L8.61974 10.0081L8.54987 10.0476L8.21267 13.6752L8.05469 13.8604L7.69013 14L7.38632 13.7693L7.22531 13.3959L7.38632 12.6582L7.58075 11.6959L7.73873 10.9309L7.88153 9.98078L7.96658 9.66506L7.96052 9.64382L7.89062 9.65291L7.17368 10.6365L6.08303 12.1088L5.22026 13.0316L5.01368 13.1136L4.65519 12.9284L4.68861 12.5975L4.88911 12.303L6.08303 10.7852L6.80303 9.84416L7.26785 9.30077L7.26482 9.22187H7.23746L4.06582 11.2801L3.50076 11.3529L3.25772 11.1252L3.2881 10.7518L3.40354 10.6304L4.35747 9.97469L4.35443 9.97775Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1 +1,3 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Google Gemini</title><path d="M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.44 12.27C7.81333 13.1217 8 14.0317 8 15C8 14.0317 8.18083 13.1217 8.5425 12.27C8.91583 11.4183 9.4175 10.6775 10.0475 10.0475C10.6775 9.4175 11.4183 8.92167 12.27 8.56C13.1217 8.18667 14.0317 8 15 8C14.0317 8 13.1217 7.81917 12.27 7.4575C11.4411 7.1001 10.6871 6.5895 10.0475 5.9525C9.4105 5.31293 8.8999 4.55891 8.5425 3.73C8.18083 2.87833 8 1.96833 8 1C8 1.96833 7.81333 2.87833 7.44 3.73C7.07833 4.58167 6.5825 5.3225 5.9525 5.9525C5.31293 6.5895 4.55891 7.1001 3.73 7.4575C2.87833 7.81917 1.96833 8 1 8C1.96833 8 2.87833 8.18667 3.73 8.56C4.58167 8.92167 5.3225 9.4175 5.9525 10.0475C6.5825 10.6775 7.07833 11.4183 7.44 12.27Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 402 B

After

Width:  |  Height:  |  Size: 762 B

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.25669 0.999943C8.27509 0.993825 9.24655 1.42125 9.9227 2.17279C11.4427 1.85079 12.9991 2.53518 13.7733 3.86518C14.159 4.5149 14.3171 5.26409 14.2372 5.99994H13.2967C13.3789 5.42185 13.265 4.8321 12.9686 4.32514C12.2353 3.06961 10.6088 2.63919 9.33676 3.36322L6.48032 4.98822C6.46926 4.99697 6.46284 5.01135 6.46372 5.02533V6.38568L9.91294 4.42084C10.0565 4.33818 10.2336 4.33823 10.3768 4.42084L13.1502 5.99994H11.2948L9.88364 5.19623C9.87034 5.19054 9.85459 5.19128 9.84262 5.19916L8.64926 5.87983L8.8602 5.99994H7.99985C6.89539 6.00004 5.99988 6.89547 5.99985 7.99994V9.34955L3.90219 8.15522C3.75815 8.07431 3.66897 7.92228 3.66977 7.75873V4.53803C3.66977 4.50828 3.67172 4.4654 3.67172 4.44135C3.08836 4.65262 2.59832 5.0599 2.28794 5.59174C1.55635 6.84647 1.99122 8.44936 3.26059 9.17475L5.99985 10.7363V11.6162C5.87564 11.6568 5.73827 11.6456 5.6229 11.579L2.7977 9.96869C2.77156 9.95382 2.73449 9.9311 2.71372 9.91889C2.60687 10.5231 2.7194 11.1466 3.0311 11.6777C3.6435 12.7209 4.87159 13.1902 5.99985 12.9023V13.8398C4.50443 14.1233 2.98758 13.4424 2.22641 12.1347C1.71174 11.2677 1.60096 10.2237 1.9227 9.27045C0.880739 8.13295 0.703328 6.46023 1.48325 5.13373C1.98739 4.26024 2.84863 3.64401 3.84653 3.44233C4.3245 1.9837 5.70306 0.996447 7.25669 0.999943ZM7.25766 1.91498C5.78932 1.9143 4.59839 3.08914 4.59751 4.53803V7.79193C4.59926 7.80578 4.60735 7.81796 4.61997 7.82416L5.8143 8.50483L5.81626 4.57611C5.81537 4.41216 5.90431 4.2606 6.04868 4.17963L8.87387 2.56928C8.89868 2.55441 8.93612 2.53379 8.95786 2.5224C8.48035 2.13046 7.8788 1.91498 7.25766 1.91498Z" fill="black"/>
<path d="M13.5 6C14.6046 6 15.5 6.89543 15.5 8V13.5C15.5 14.6046 14.6046 15.5 13.5 15.5H8C6.89543 15.5 6 14.6046 6 13.5V8C6 6.89543 6.89543 6 8 6H13.5ZM10.8916 8.02539C10.0563 8.02539 9.33453 8.27982 8.81934 8.76562C8.30213 9.25335 8.02547 9.94371 8.02539 10.748C8.02539 11.557 8.29852 12.2492 8.81543 12.7373C9.33013 13.2232 10.0521 13.4746 10.8916 13.4746C11.9865 13.4745 12.8545 13.1022 13.3076 12.3525C13.3894 12.2176 13.4521 12.0693 13.4521 11.8857C13.4521 11.4795 13.0933 11.2773 12.7842 11.2773C12.6604 11.2774 12.5292 11.3025 12.4072 11.3779C12.2862 11.4529 12.2058 11.5586 12.1494 11.666L12.1475 11.6689C11.9677 12.0213 11.5535 12.246 10.8955 12.2461C10.4219 12.2461 10.0667 12.0932 9.83008 11.8506C9.59255 11.607 9.44141 11.2389 9.44141 10.748C9.44148 10.264 9.59319 9.89628 9.83203 9.65137C10.0702 9.40725 10.4255 9.25391 10.8916 9.25391C11.4912 9.25399 11.9415 9.50614 12.1289 9.8916V9.89062C12.1888 10.0157 12.276 10.1311 12.4023 10.2129C12.5303 10.2956 12.6724 10.3271 12.8115 10.3271C12.9661 10.3271 13.1303 10.2857 13.2627 10.1758C13.4018 10.0603 13.4746 9.89383 13.4746 9.71582C13.4746 9.61857 13.4542 9.52036 13.4199 9.42773L13.3818 9.33691C12.9749 8.49175 11.9927 8.02548 10.8916 8.02539ZM10.3203 8.97852L10.1494 9.03516C10.2095 9.01178 10.2716 8.99089 10.3359 8.97363C10.3307 8.97505 10.3256 8.97706 10.3203 8.97852ZM10.4814 8.94141C10.4969 8.9385 10.5126 8.93616 10.5283 8.93359C10.5126 8.93617 10.4969 8.9385 10.4814 8.94141ZM10.6709 8.91504C10.6819 8.91399 10.693 8.913 10.7041 8.91211C10.693 8.913 10.6819 8.91399 10.6709 8.91504Z" fill="black" fill-opacity="0.95"/>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor" stroke="currentColor">
<g clip-path="url(#clip0_205_3)">
<g>
<path d="M0.094 7.78c0.469 0 2.281 -0.405 3.219 -0.936s0.938 -0.531 2.875 -1.906c2.453 -1.741 4.188 -1.158 7.031 -1.158" stroke-width="2.8125" />
<path d="m15.969 3.797 -4.805 2.774V1.023z" />
<path d="M0 7.781c0.469 0 2.281 0.405 3.219 0.936s0.938 0.531 2.875 1.906C8.547 12.364 10.281 11.781 13.125 11.781" stroke-width="2.8125" />

Before

Width:  |  Height:  |  Size: 575 B

After

Width:  |  Height:  |  Size: 545 B

View File

@@ -1 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-volume-off"><path d="M16 9a5 5 0 0 1 .95 2.293"/><path d="M19.364 5.636a9 9 0 0 1 1.889 9.96"/><path d="m2 2 20 20"/><path d="m7 7-.587.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298V11"/><path d="M9.828 4.172A.686.686 0 0 1 11 4.657v.686"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.6667 6C11.003 6.44823 11.2208 6.97398 11.3001 7.52867" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.9094 3.75732C13.7621 4.6095 14.3383 5.69876 14.5629 6.88315C14.7875 8.06754 14.6502 9.29213 14.1688 10.3973" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.66675 2L13.6667 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.33333 4.66669L4.942 5.05802C4.85494 5.1456 4.75136 5.21504 4.63726 5.2623C4.52317 5.30957 4.40083 5.33372 4.27733 5.33335H2.66667C2.48986 5.33335 2.32029 5.40359 2.19526 5.52862C2.07024 5.65364 2 5.82321 2 6.00002V10C2 10.1768 2.07024 10.3464 2.19526 10.4714C2.32029 10.5964 2.48986 10.6667 2.66667 10.6667H4.27733C4.40083 10.6663 4.52317 10.6905 4.63726 10.7377C4.75136 10.785 4.85494 10.8544 4.942 10.942L7.19733 13.198C7.26307 13.2639 7.34687 13.3088 7.43813 13.3269C7.52939 13.3451 7.62399 13.3358 7.70995 13.3002C7.79591 13.2646 7.86936 13.2042 7.921 13.1268C7.97263 13.0494 8.00013 12.9584 8 12.8654V7.33335" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.21875 2.78136C7.28267 2.71719 7.36421 2.67345 7.45303 2.65568C7.54184 2.63791 7.63393 2.64691 7.71762 2.68154C7.80132 2.71618 7.87284 2.77488 7.92312 2.85022C7.97341 2.92555 8.0002 3.01412 8.00008 3.10469V3.56202" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 527 B

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-volume-2"><path d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"/><path d="M16 9a5 5 0 0 1 0 6"/><path d="M19.364 18.364a9 9 0 0 0 0-12.728"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 3.13467C7.99987 3.04181 7.97223 2.95107 7.92057 2.8739C7.86892 2.79674 7.79557 2.7366 7.70977 2.70108C7.62397 2.66557 7.52958 2.65626 7.43849 2.67434C7.34741 2.69242 7.26373 2.73707 7.198 2.80266L4.942 5.058C4.85494 5.14558 4.75136 5.21502 4.63726 5.26228C4.52317 5.30954 4.40083 5.33369 4.27733 5.33333H2.66667C2.48986 5.33333 2.32029 5.40357 2.19526 5.52859C2.07024 5.65362 2 5.82319 2 6V10C2 10.1768 2.07024 10.3464 2.19526 10.4714C2.32029 10.5964 2.48986 10.6667 2.66667 10.6667H4.27733C4.40083 10.6663 4.52317 10.6905 4.63726 10.7377C4.75136 10.785 4.85494 10.8544 4.942 10.942L7.19733 13.198C7.26307 13.2639 7.34687 13.3087 7.43813 13.3269C7.52939 13.3451 7.62399 13.3358 7.70995 13.3002C7.79591 13.2645 7.86936 13.2042 7.921 13.1268C7.97263 13.0494 8.00013 12.9584 8 12.8653V3.13467Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.6667 6C11.0995 6.57699 11.3334 7.27877 11.3334 8C11.3334 8.72123 11.0995 9.42301 10.6667 10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.9094 12.2427C13.4666 11.6855 13.9085 11.0241 14.2101 10.2961C14.5116 9.56815 14.6668 8.78793 14.6668 7.99999C14.6668 7.21205 14.5116 6.43183 14.2101 5.70387C13.9085 4.97591 13.4666 4.31448 12.9094 3.75732" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 475 B

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cloud-download-icon lucide-cloud-download"><path d="M12 13v8l-4-4"/><path d="m12 21 4-4"/><path d="M4.393 15.269A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.436 8.284"/></svg>

After

Width:  |  Height:  |  Size: 372 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -0,0 +1,9 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.6" d="M3.5 11V5.5L8.5 8L3.5 11Z" fill="black"/>
<path opacity="0.4" d="M8.5 14L3.5 11L8.5 8V14Z" fill="black"/>
<path opacity="0.6" d="M8.5 5.5H3.5L8.5 2.5L8.5 5.5Z" fill="black"/>
<path opacity="0.8" d="M8.5 5.5V2.5L13.5 5.5H8.5Z" fill="black"/>
<path opacity="0.2" d="M13.5 11L8.5 14L11 9.5L13.5 11Z" fill="black"/>
<path opacity="0.5" d="M13.5 11L11 9.5L13.5 5V11Z" fill="black"/>
<path d="M3.5 11V5L8.5 2.11325L13.5 5V11L8.5 13.8868L3.5 11Z" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 583 B

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2716_663)">
<path d="M8.47552 2.45453C11.5167 2.45457 13.9814 4.94501 13.9814 8.01623C13.9814 11.0875 11.5167 13.578 8.47552 13.5781C5.43427 13.5781 2.96948 11.0875 2.96948 8.01623C2.9695 4.94498 5.43429 2.45453 8.47552 2.45453ZM10.8795 4.70348C10.7605 4.16887 10.1328 3.85468 9.53627 3.96342C8.97622 4.06552 7.62871 4.45681 7.62057 4.45916C9.29414 4.44469 9.57429 4.4726 9.69939 4.64751C9.77324 4.7508 9.66576 4.89248 9.21944 4.96538C8.73515 5.04447 7.73014 5.13958 7.72343 5.14022C6.75441 5.19776 6.07177 5.20168 5.86705 5.63512C5.73334 5.91827 6.00968 6.16857 6.13082 6.32527C6.64271 6.89455 7.38215 7.20158 7.85809 7.42767C8.03716 7.51274 8.56257 7.67345 8.56257 7.67345C7.01855 7.58853 5.90474 8.06267 5.2514 8.60855C4.51246 9.29204 4.83937 10.1067 6.35327 10.6084C7.24742 10.9047 7.69094 11.0439 9.02473 10.9238C9.81031 10.8815 9.9342 10.9068 9.94203 10.9712C9.95275 11.062 9.06932 11.2874 8.82812 11.357C8.21455 11.534 6.60645 11.8913 6.59758 11.8932C6.60115 11.8935 7.06249 11.9257 7.65531 11.8735C7.89632 11.8522 8.81142 11.7624 9.49557 11.6123C9.49557 11.6123 10.3297 11.4338 10.7759 11.2693C11.2429 11.0973 11.497 10.9512 11.6113 10.7443C11.6063 10.7019 11.6465 10.5516 11.4313 10.4613C10.8807 10.2304 10.2423 10.2721 8.9789 10.2453C7.57789 10.1972 7.11184 9.9626 6.86356 9.77373C6.62548 9.58212 6.74518 9.05204 7.76528 8.5851C8.27917 8.33646 10.2935 7.87759 10.2935 7.87759C9.61511 7.54227 8.35014 6.95284 8.09005 6.82552C7.86199 6.71388 7.49701 6.54572 7.4179 6.34233C7.32824 6.14709 7.6297 5.97888 7.79813 5.9307C8.34057 5.77424 9.10635 5.67701 9.8033 5.66609C10.1536 5.66061 10.2105 5.63806 10.2105 5.63806C10.6939 5.55787 11.0121 5.22722 10.8795 4.70348Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_2716_663">
<rect width="12" height="12" fill="white" transform="translate(2.5 2)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.6725 13.9985C3.36161 13.9982 3.06354 13.8746 2.84371 13.6548C2.62388 13.435 2.50026 13.1369 2.5 12.826V7.494C2.5 6.8325 2.7675 6.185 3.2365 5.7165L6.219 2.736C6.45192 2.50247 6.72867 2.31724 7.03335 2.19094C7.33804 2.06464 7.66467 1.99975 7.9945 2H13.3275C13.6384 2.00027 13.9365 2.12388 14.1563 2.34371C14.3761 2.56354 14.4997 2.86162 14.5 3.1725V8.5045C14.4983 9.17074 14.2336 9.80936 13.7635 10.2815L10.781 13.264C10.5477 13.4976 10.2706 13.6829 9.96561 13.8092C9.66059 13.9355 9.33364 14.0003 9.0035 14V13.9985H3.6725ZM8.157 10.5715H5.243V11.257H8.157V10.5715ZM4.4815 5.257H11.243V12.0165L13.3715 9.888C13.7373 9.52036 13.9433 9.02316 13.9445 8.5045V3.1725C13.9445 2.8335 13.6685 2.5555 13.3275 2.5555H7.9945C7.73753 2.55499 7.483 2.6053 7.24556 2.70356C7.00813 2.80181 6.79246 2.94606 6.611 3.128L4.4815 5.257ZM4.3855 5.353L3.628 6.11C3.26258 6.47809 3.0569 6.97533 3.0555 7.494V12.826C3.0555 13.165 3.3315 13.443 3.6725 13.443H9.0055C9.26249 13.4434 9.51701 13.3929 9.75445 13.2946C9.99188 13.1963 10.2075 13.052 10.389 12.87L11.145 12.1145H4.3855V5.353Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.0945 8.01611C13.0945 7.87619 12.9911 7.79551 12.8642 7.8356L4.13456 10.6038C4.00742 10.6441 3.90427 10.7904 3.90427 10.9301V13.7593C3.90427 13.8992 4.00742 13.9801 4.13456 13.9398L12.8642 11.1719C12.9911 11.1315 13.0945 10.9852 13.0945 10.8453V8.01611Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.90427 7.92597C3.90427 8.06588 4.00742 8.21218 4.13456 8.25252L12.8655 11.0209C12.9926 11.0613 13.0958 10.9803 13.0958 10.8407V8.01124C13.0958 7.87158 12.9926 7.72529 12.8655 7.68494L4.13456 4.91652C4.00742 4.87618 3.90427 4.95686 3.90427 5.09677V7.92597Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.0945 2.20248C13.0945 2.06256 12.9911 1.98163 12.8642 2.02197L4.13456 4.78988C4.00742 4.83022 3.90427 4.97652 3.90427 5.11644V7.94563C3.90427 8.08554 4.00742 8.16622 4.13456 8.12614L12.8642 5.35797C12.9911 5.31763 13.0945 5.17133 13.0945 5.03167V2.20248Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0094 13.9181C11.1984 13.9917 11.4139 13.987 11.6047 13.8952L14.0753 12.7064C14.3349 12.5814 14.5 12.3187 14.5 12.0305V3.9696C14.5 3.68136 14.3349 3.41862 14.0753 3.2937L11.6047 2.10485C11.3543 1.98438 11.0614 2.01389 10.8416 2.17363C10.8102 2.19645 10.7803 2.22193 10.7523 2.25001L6.02261 6.56498L3.96246 5.00115C3.77068 4.85558 3.50244 4.86751 3.32432 5.02953L2.66356 5.63059C2.44569 5.82877 2.44544 6.17152 2.66302 6.37004L4.44965 8.00001L2.66302 9.62998C2.44544 9.82849 2.44569 10.1713 2.66356 10.3694L3.32432 10.9705C3.50244 11.1325 3.77068 11.1444 3.96246 10.9989L6.02261 9.43504L10.7523 13.75C10.8271 13.8249 10.915 13.8812 11.0094 13.9181ZM11.5018 5.27587L7.91309 8.00001L11.5018 10.7241V5.27587Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 876 B

View File

@@ -1,8 +1,5 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3 1C2.44771 1 2 1.44772 2 2V13C2 13.5523 2.44772 14 3 14H10.5C10.7761 14 11 13.7761 11 13.5C11 13.2239 10.7761 13 10.5 13H3V2L10.5 2C10.7761 2 11 1.77614 11 1.5C11 1.22386 10.7761 1 10.5 1H3ZM12.6036 4.89645C12.4083 4.70118 12.0917 4.70118 11.8964 4.89645C11.7012 5.09171 11.7012 5.40829 11.8964 5.60355L13.2929 7H6.5C6.22386 7 6 7.22386 6 7.5C6 7.77614 6.22386 8 6.5 8H13.2929L11.8964 9.39645C11.7012 9.59171 11.7012 9.90829 11.8964 10.1036C12.0917 10.2988 12.4083 10.2988 12.6036 10.1036L14.8536 7.85355C15.0488 7.65829 15.0488 7.34171 14.8536 7.14645L12.6036 4.89645Z"
fill="currentColor"
/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.437 11.0461L13.4831 8L10.437 4.95392" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 8L8 8" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.6553 13.4659H4.21843C3.89528 13.4659 3.58537 13.3375 3.35687 13.109C3.12837 12.8805 3 12.5706 3 12.2475V3.71843C3 3.39528 3.12837 3.08537 3.35687 2.85687C3.58537 2.62837 3.89528 2.5 4.21843 2.5H6.6553" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 768 B

After

Width:  |  Height:  |  Size: 637 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.628 11.0743V10.4575H8.45562L8.65084 10.2445C8.75911 10.1264 8.96952 9.79454 9.11862 9.50789C9.52153 8.73047 9.51798 7.25107 9.11862 6.43992C8.58614 5.35722 7.49453 4.56381 6.24942 4.35703C4.59252 4.08192 2.86196 5.00312 2.14045 6.54287C1.77038 7.33182 1.77038 8.64437 2.14045 9.43333C2.45905 10.1122 3.11309 10.8204 3.73609 11.1595C4.51439 11.5828 5.18264 11.676 7.51312 11.6848L9.62627 11.6928L9.628 11.0743ZM5.30605 10.169C4.24109 10.0111 3.45215 9.07124 3.45659 7.96813C3.45659 7.33004 3.70064 6.80022 4.18697 6.36182C4.67685 5.91986 5.1312 5.77344 5.86602 5.82048C7.00287 5.89236 7.82382 6.79845 7.82382 7.98056C7.82382 8.61332 7.71996 8.91682 7.33036 9.42534C6.90172 9.98444 6.08345 10.2853 5.30692 10.1699M15.1374 10.9802V10.2684H11.8138V4.47509H10.1986V11.6928H15.1374V10.9802Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 916 B

View File

@@ -1,3 +1,5 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.72742 8.83338C3.63539 8.57302 3.34973 8.43656 3.08937 8.52858C2.82901 8.6206 2.69255 8.90626 2.78458 9.16662C2.86101 9.38288 2.95188 9.59228 3.056 9.79364C3.81427 11.2601 5.27842 12.3044 7.00014 12.4753L7.00014 14L5.50014 14C5.22399 14 5.00014 14.2239 5.00014 14.5C5.00014 14.7761 5.22399 15 5.50014 15L7.50014 15L9.50014 15C9.77628 15 10.0001 14.7761 10.0001 14.5C10.0001 14.2239 9.77628 14 9.50014 14L8.00014 14L8.00014 12.4753C9.72168 12.3043 11.1857 11.26 11.9439 9.79364C12.048 9.59228 12.1389 9.38288 12.2153 9.16662C12.3073 8.90626 12.1709 8.6206 11.9105 8.52858C11.6501 8.43656 11.3645 8.57302 11.2725 8.83338C11.2114 9.00607 11.1388 9.17337 11.0556 9.33433C10.3899 10.6218 9.04706 11.5 7.49994 11.5C5.95282 11.5 4.60997 10.6218 3.94428 9.33433C3.86104 9.17337 3.78845 9.00607 3.72742 8.83338ZM5.5 3.5L5.5 7.5C5.5 8.60457 6.39543 9.5 7.5 9.5C8.60457 9.5 9.5 8.60457 9.5 7.5L9.5 3.5C9.5 2.39543 8.60457 1.5 7.5 1.5C6.39543 1.5 5.5 2.39543 5.5 3.5ZM4.5 7.5C4.5 9.15685 5.84315 10.5 7.5 10.5C9.15685 10.5 10.5 9.15685 10.5 7.5L10.5 3.5C10.5 1.84315 9.15685 0.5 7.5 0.5C5.84315 0.5 4.5 1.84315 4.5 3.5L4.5 7.5Z" fill="black"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 12.2028V14.3042" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.2027 6.94928V8.11672C12.2027 9.20041 11.7599 10.2397 10.9717 11.006C10.1836 11.7723 9.11457 12.2028 7.99992 12.2028C6.88527 12.2028 5.81627 11.7723 5.02809 11.006C4.23991 10.2397 3.79712 9.20041 3.79712 8.11672V6.94928" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.1015 3.63555C10.1015 2.56426 9.16065 1.6958 8.00008 1.6958C6.83951 1.6958 5.89868 2.56426 5.89868 3.63555V8.16165C5.89868 9.23294 6.83951 10.1014 8.00008 10.1014C9.16065 10.1014 10.1015 9.23294 10.1015 8.16165V3.63555Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 847 B

View File

@@ -1,3 +1,8 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.87 1.83637C13.0557 1.63204 13.0407 1.31581 12.8363 1.13006C12.632 0.944307 12.3158 0.959365 12.13 1.16369L10.4589 3.00199C10.2216 1.58215 8.98719 0.5 7.5 0.5C5.84315 0.5 4.5 1.84315 4.5 3.5L4.5 7.5C4.5 8.0754 4.66199 8.61297 4.94286 9.06958L4.24966 9.8321C4.1363 9.6744 4.03412 9.5081 3.94428 9.33433C3.86104 9.17337 3.78845 9.00607 3.72742 8.83338C3.63539 8.57302 3.34973 8.43656 3.08937 8.52858C2.82901 8.6206 2.69255 8.90626 2.78458 9.16662C2.86101 9.38288 2.95188 9.59228 3.056 9.79364C3.20094 10.074 3.37167 10.3388 3.56506 10.5852L2.13003 12.1637C1.94428 12.368 1.95933 12.6842 2.16366 12.87C2.36799 13.0558 2.68422 13.0407 2.86997 12.8364L4.25951 11.3079C5.01297 11.9497 5.95951 12.372 7.00014 12.4753L7.00014 14L5.50014 14C5.22399 14 5.00014 14.2239 5.00014 14.5C5.00014 14.7761 5.22399 15 5.50014 15L7.50014 15L9.50014 15C9.77628 15 10.0001 14.7761 10.0001 14.5C10.0001 14.2239 9.77628 14 9.50014 14L8.00014 14L8.00014 12.4753C9.72168 12.3043 11.1857 11.26 11.9439 9.79364C12.048 9.59228 12.1389 9.38288 12.2153 9.16662C12.3073 8.90626 12.1709 8.6206 11.9105 8.52858C11.6501 8.43656 11.3645 8.57302 11.2725 8.83338C11.2114 9.00607 11.1388 9.17337 11.0556 9.33433C10.3899 10.6218 9.04706 11.5 7.49994 11.5C6.523 11.5 5.62751 11.1498 4.93254 10.5675L5.60604 9.82669C6.12251 10.2476 6.78178 10.5 7.5 10.5C9.15685 10.5 10.5 9.15685 10.5 7.5L10.5 4.44333L12.87 1.83637ZM9.5 4.05673L9.5 3.5C9.5 2.39543 8.60457 1.5 7.5 1.5C6.39543 1.5 5.5 2.39543 5.5 3.5L5.5 7.5C5.5 7.77755 5.55653 8.04189 5.65872 8.28214L9.5 4.05673ZM6.28022 9.08509L9.5 5.54333L9.5 7.5C9.5 8.60457 8.60457 9.5 7.5 9.5C7.04083 9.5 6.6178 9.34527 6.28022 9.08509Z" fill="black"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 3L13 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 9C12 8.74858 12 8.49375 12 8.23839V7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.00043 7V8.09869C3.98856 8.86731 4.22157 9.62164 4.66938 10.2643C5.11718 10.907 5.75924 11.4085 6.51267 11.7042C7.2661 11.9999 8.09632 12.0761 8.89619 11.923C9.47851 11.8115 10.0253 11.5823 10.5 11.2539" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 6V3.62904C9.99714 3.26103 9.8347 2.90448 9.53885 2.6168C9.24299 2.32913 8.83093 2.12707 8.36903 2.04316C7.90713 1.95926 7.42226 1.9984 6.99252 2.15427C6.56278 2.31015 6.21317 2.57369 6 2.90245" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 6V8.00088C6.00031 8.39636 6.10356 8.78287 6.29674 9.11159C6.48991 9.44031 6.76433 9.69649 7.08534 9.84779C7.40634 9.99909 7.75954 10.0387 8.10032 9.96165C8.4411 9.88459 8.75417 9.69431 9 9.41483" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 12V14" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 4H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.66667 8H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.66667 12H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.00016 12C8.41993 12.5597 9.00515 12.9731 9.67294 13.1817C10.3407 13.3903 11.0572 13.3835 11.7209 13.1623C12.3846 12.941 12.9619 12.5166 13.371 11.949C13.78 11.3815 14.0002 10.6996 14.0002 10C14.0002 9.20435 13.6841 8.44129 13.1215 7.87868C12.5589 7.31607 11.7958 7 11.0002 7C10.1135 7 9.30683 7.36 8.72683 7.94L7.3335 9.33333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.3335 6.66669V9.33335H10.0002" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 964 B

View File

@@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.33333 8H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.6667 5H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 11H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 7V11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 9H10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 620 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.31254 12.549C7.3841 13.0987 8.61676 13.2476 9.78839 12.9688C10.96 12.6901 11.9936 12.0021 12.7028 11.0287C13.412 10.0554 13.7503 8.8607 13.6566 7.66002C13.5629 6.45934 13.0435 5.33159 12.1919 4.48C11.3403 3.62841 10.2126 3.10898 9.01188 3.01531C7.8112 2.92164 6.61655 3.2599 5.64319 3.96912C4.66984 4.67834 3.9818 5.71188 3.70306 6.88351C3.42432 8.05514 3.5732 9.2878 4.12289 10.3594L3 13.6719L6.31254 12.549Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 609 B

View File

@@ -1,8 +1,5 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 3.25C1 3.11193 1.11193 3 1.25 3H13.75C13.8881 3 14 3.11193 14 3.25V10.75C14 10.8881 13.8881 11 13.75 11H1.25C1.11193 11 1 10.8881 1 10.75V3.25ZM1.25 2C0.559643 2 0 2.55964 0 3.25V10.75C0 11.4404 0.559644 12 1.25 12H5.07341L4.82991 13.2986C4.76645 13.6371 5.02612 13.95 5.37049 13.95H9.62951C9.97389 13.95 10.2336 13.6371 10.1701 13.2986L9.92659 12H13.75C14.4404 12 15 11.4404 15 10.75V3.25C15 2.55964 14.4404 2 13.75 2H1.25ZM9.01091 12H5.98909L5.79222 13.05H9.20778L9.01091 12Z"
fill="currentColor"
/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.8 3H3.2C2.53726 3 2 3.51167 2 4.14286V9.85714C2 10.4883 2.53726 11 3.2 11H12.8C13.4627 11 14 10.4883 14 9.85714V4.14286C14 3.51167 13.4627 3 12.8 3Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.33325 14H10.6666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 11.3333V14" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 677 B

After

Width:  |  Height:  |  Size: 569 B

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.0001 8.62505C13.0001 11.75 10.8126 13.3125 8.21266 14.2187C8.07651 14.2648 7.92862 14.2626 7.79392 14.2125C5.18771 13.3125 3.00024 11.75 3.00024 8.62505V4.25012C3.00024 4.08436 3.06609 3.92539 3.1833 3.80818C3.30051 3.69098 3.45948 3.62513 3.62523 3.62513C4.87521 3.62513 6.43769 2.87514 7.52517 1.92516C7.65758 1.81203 7.82601 1.74988 8.00016 1.74988C8.17431 1.74988 8.34275 1.81203 8.47515 1.92516C9.56889 2.88139 11.1251 3.62513 12.3751 3.62513C12.5408 3.62513 12.6998 3.69098 12.817 3.80818C12.9342 3.92539 13.0001 4.08436 13.0001 4.25012V8.62505Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 8.00002L7.33333 9.33335L10 6.66669" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 883 B

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 8L7.33333 9L10 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 418 B

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 3C7.66045 3 8.33955 3 9 3" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 4C11.3949 4.26602 11.7345 4.60558 12 5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 7C13 7.66045 13 8.33955 13 9" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 11C11.734 11.3949 11.3944 11.7345 11 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 13C8.33954 13 7.66046 13 7 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 12C4.6051 11.734 4.26554 11.3944 4 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 9C3 8.33955 3 7.66045 3 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 5C4.26602 4.6051 4.60558 4.26554 5 4" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 3C7.66045 3 8.33955 3 9 3" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 4C11.3949 4.26602 11.7345 4.60558 12 5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 7C13 7.66045 13 8.33955 13 9" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 11C11.734 11.3949 11.3944 11.7345 11 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 13C8.33954 13 7.66046 13 7 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 12C4.6051 11.734 4.26554 11.3944 4 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 9C3 8.33955 3 7.66045 3 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 5C4.26602 4.6051 4.60558 4.26554 5 4" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.00016 8.66665C8.36835 8.66665 8.66683 8.36817 8.66683 7.99998C8.66683 7.63179 8.36835 7.33331 8.00016 7.33331C7.63197 7.33331 7.3335 7.63179 7.3335 7.99998C7.3335 8.36817 7.63197 8.66665 8.00016 8.66665Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -232,7 +232,7 @@
"ctrl-n": "agent::NewThread",
"ctrl-alt-n": "agent::NewTextThread",
"ctrl-shift-h": "agent::OpenHistory",
"ctrl-alt-c": "agent::OpenConfiguration",
"ctrl-alt-c": "agent::OpenSettings",
"ctrl-alt-p": "agent::OpenRulesLibrary",
"ctrl-i": "agent::ToggleProfileSelector",
"ctrl-alt-/": "agent::ToggleModelSelector",
@@ -269,15 +269,15 @@
}
},
{
"context": "AgentPanel && acp_thread",
"context": "AgentPanel && external_agent_thread",
"use_key_equivalents": true,
"bindings": {
"ctrl-n": "agent::NewAcpThread",
"ctrl-n": "agent::NewExternalAgentThread",
"ctrl-alt-t": "agent::NewThread"
}
},
{
"context": "MessageEditor > Editor",
"context": "MessageEditor && !Picker > Editor && !use_modifier_to_send",
"bindings": {
"enter": "agent::Chat",
"ctrl-enter": "agent::ChatWithFollow",
@@ -287,6 +287,17 @@
"ctrl-shift-n": "agent::RejectAll"
}
},
{
"context": "MessageEditor && !Picker > Editor && use_modifier_to_send",
"bindings": {
"ctrl-enter": "agent::Chat",
"enter": "editor::Newline",
"ctrl-i": "agent::ToggleProfileSelector",
"shift-ctrl-r": "agent::OpenAgentDiff",
"ctrl-shift-y": "agent::KeepAll",
"ctrl-shift-n": "agent::RejectAll"
}
},
{
"context": "EditMessageEditor > Editor",
"bindings": {
@@ -419,7 +430,7 @@
"ctrl-shift-pagedown": "pane::SwapItemRight",
"ctrl-f4": ["pane::CloseActiveItem", { "close_pinned": false }],
"ctrl-w": ["pane::CloseActiveItem", { "close_pinned": false }],
"alt-ctrl-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
"alt-ctrl-t": ["pane::CloseOtherItems", { "close_pinned": false }],
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
"ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
"ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
@@ -472,9 +483,8 @@
"ctrl-k ctrl-d": ["editor::SelectNext", { "replace_newest": true }], // editor.action.moveSelectionToNextFindMatch / find_under_expand_skip
"ctrl-k ctrl-shift-d": ["editor::SelectPrevious", { "replace_newest": true }], // editor.action.moveSelectionToPreviousFindMatch
"ctrl-k ctrl-i": "editor::Hover",
"ctrl-k ctrl-b": "editor::BlameHover",
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": false }],
"ctrl-u": "editor::UndoSelection",
"ctrl-shift-u": "editor::RedoSelection",
"f8": ["editor::GoToDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
"shift-f8": ["editor::GoToPreviousDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
"f2": "editor::Rename",
@@ -485,7 +495,7 @@
"shift-f12": "editor::GoToImplementation",
"alt-ctrl-f12": "editor::GoToTypeDefinitionSplit",
"alt-shift-f12": "editor::FindAllReferences",
"ctrl-m": "editor::MoveToEnclosingBracket",
"ctrl-m": "editor::MoveToEnclosingBracket", // from jetbrains
"ctrl-|": "editor::MoveToEnclosingBracket",
"ctrl-{": "editor::Fold",
"ctrl-}": "editor::UnfoldLines",
@@ -588,6 +598,7 @@
"ctrl-shift-t": "pane::ReopenClosedItem",
"ctrl-k ctrl-s": "zed::OpenKeymapEditor",
"ctrl-k ctrl-t": "theme_selector::Toggle",
"ctrl-alt-super-p": "settings_profile_selector::Toggle",
"ctrl-t": "project_symbols::Toggle",
"ctrl-p": "file_finder::Toggle",
"ctrl-tab": "tab_switcher::Toggle",
@@ -652,6 +663,8 @@
{
"context": "Editor",
"bindings": {
"ctrl-u": "editor::UndoSelection",
"ctrl-shift-u": "editor::RedoSelection",
"ctrl-shift-j": "editor::JoinLines",
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
@@ -860,8 +873,6 @@
"tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus",
"ctrl-enter": "git::Commit",
"ctrl-shift-enter": "git::Amend",
"alt-enter": "menu::SecondaryConfirm",
"delete": ["git::RestoreFile", { "skip_prompt": false }],
"backspace": ["git::RestoreFile", { "skip_prompt": false }],
@@ -898,7 +909,9 @@
"ctrl-g backspace": "git::RestoreTrackedFiles",
"ctrl-g shift-backspace": "git::TrashUntrackedFiles",
"ctrl-space": "git::StageAll",
"ctrl-shift-space": "git::UnstageAll"
"ctrl-shift-space": "git::UnstageAll",
"ctrl-enter": "git::Commit",
"ctrl-shift-enter": "git::Amend"
}
},
{
@@ -917,7 +930,7 @@
}
},
{
"context": "GitPanel > Editor",
"context": "CommitEditor > Editor",
"bindings": {
"escape": "git_panel::FocusChanges",
"tab": "git_panel::FocusChanges",
@@ -963,9 +976,14 @@
"context": "CollabPanel && not_editing",
"bindings": {
"ctrl-backspace": "collab_panel::Remove",
"space": "menu::Confirm",
"ctrl-up": "collab_panel::MoveChannelUp",
"ctrl-down": "collab_panel::MoveChannelDown"
"space": "menu::Confirm"
}
},
{
"context": "CollabPanel",
"bindings": {
"alt-up": "collab_panel::MoveChannelUp",
"alt-down": "collab_panel::MoveChannelDown"
}
},
{
@@ -1118,7 +1136,12 @@
"ctrl-f": "search::FocusSearch",
"alt-find": "keymap_editor::ToggleKeystrokeSearch",
"alt-ctrl-f": "keymap_editor::ToggleKeystrokeSearch",
"alt-c": "keymap_editor::ToggleConflictFilter"
"alt-c": "keymap_editor::ToggleConflictFilter",
"enter": "keymap_editor::EditBinding",
"alt-enter": "keymap_editor::CreateBinding",
"ctrl-c": "keymap_editor::CopyAction",
"ctrl-shift-c": "keymap_editor::CopyContext",
"ctrl-t": "keymap_editor::ShowMatchingKeybinds"
}
},
{

View File

@@ -272,7 +272,7 @@
"cmd-n": "agent::NewThread",
"cmd-alt-n": "agent::NewTextThread",
"cmd-shift-h": "agent::OpenHistory",
"cmd-alt-c": "agent::OpenConfiguration",
"cmd-alt-c": "agent::OpenSettings",
"cmd-alt-p": "agent::OpenRulesLibrary",
"cmd-i": "agent::ToggleProfileSelector",
"cmd-alt-/": "agent::ToggleModelSelector",
@@ -310,15 +310,15 @@
}
},
{
"context": "AgentPanel && acp_thread",
"context": "AgentPanel && external_agent_thread",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "agent::NewAcpThread",
"cmd-n": "agent::NewExternalAgentThread",
"cmd-alt-t": "agent::NewThread"
}
},
{
"context": "MessageEditor > Editor",
"context": "MessageEditor && !Picker > Editor && !use_modifier_to_send",
"use_key_equivalents": true,
"bindings": {
"enter": "agent::Chat",
@@ -329,6 +329,18 @@
"cmd-shift-n": "agent::RejectAll"
}
},
{
"context": "MessageEditor && !Picker > Editor && use_modifier_to_send",
"use_key_equivalents": true,
"bindings": {
"cmd-enter": "agent::Chat",
"enter": "editor::Newline",
"cmd-i": "agent::ToggleProfileSelector",
"shift-ctrl-r": "agent::OpenAgentDiff",
"cmd-shift-y": "agent::KeepAll",
"cmd-shift-n": "agent::RejectAll"
}
},
{
"context": "EditMessageEditor > Editor",
"use_key_equivalents": true,
@@ -477,7 +489,7 @@
"ctrl-shift-pageup": "pane::SwapItemLeft",
"ctrl-shift-pagedown": "pane::SwapItemRight",
"cmd-w": ["pane::CloseActiveItem", { "close_pinned": false }],
"alt-cmd-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
"alt-cmd-t": ["pane::CloseOtherItems", { "close_pinned": false }],
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
"cmd-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
"cmd-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
@@ -525,9 +537,8 @@
"ctrl-cmd-d": ["editor::SelectPrevious", { "replace_newest": false }], // editor.action.addSelectionToPreviousFindMatch
"cmd-k ctrl-cmd-d": ["editor::SelectPrevious", { "replace_newest": true }], // editor.action.moveSelectionToPreviousFindMatch
"cmd-k cmd-i": "editor::Hover",
"cmd-k cmd-b": "editor::BlameHover",
"cmd-/": ["editor::ToggleComments", { "advance_downwards": false }],
"cmd-u": "editor::UndoSelection",
"cmd-shift-u": "editor::RedoSelection",
"f8": ["editor::GoToDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
"shift-f8": ["editor::GoToPreviousDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
"f2": "editor::Rename",
@@ -538,7 +549,7 @@
"alt-cmd-f12": "editor::GoToTypeDefinitionSplit",
"alt-shift-f12": "editor::FindAllReferences",
"cmd-|": "editor::MoveToEnclosingBracket",
"ctrl-m": "editor::MoveToEnclosingBracket",
"ctrl-m": "editor::MoveToEnclosingBracket", // From Jetbrains
"alt-cmd-[": "editor::Fold",
"alt-cmd-]": "editor::UnfoldLines",
"cmd-k cmd-l": "editor::ToggleFold",
@@ -654,6 +665,7 @@
"cmd-shift-t": "pane::ReopenClosedItem",
"cmd-k cmd-s": "zed::OpenKeymapEditor",
"cmd-k cmd-t": "theme_selector::Toggle",
"ctrl-alt-cmd-p": "settings_profile_selector::Toggle",
"cmd-t": "project_symbols::Toggle",
"cmd-p": "file_finder::Toggle",
"ctrl-tab": "tab_switcher::Toggle",
@@ -714,6 +726,8 @@
"context": "Editor",
"use_key_equivalents": true,
"bindings": {
"cmd-u": "editor::UndoSelection",
"cmd-shift-u": "editor::RedoSelection",
"ctrl-j": "editor::JoinLines",
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
@@ -937,8 +951,6 @@
"tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus",
"cmd-enter": "git::Commit",
"cmd-shift-enter": "git::Amend",
"backspace": ["git::RestoreFile", { "skip_prompt": false }],
"delete": ["git::RestoreFile", { "skip_prompt": false }],
"cmd-backspace": ["git::RestoreFile", { "skip_prompt": true }],
@@ -963,7 +975,7 @@
}
},
{
"context": "GitPanel > Editor",
"context": "CommitEditor > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "editor::Newline",
@@ -988,7 +1000,9 @@
"ctrl-g backspace": "git::RestoreTrackedFiles",
"ctrl-g shift-backspace": "git::TrashUntrackedFiles",
"cmd-ctrl-y": "git::StageAll",
"cmd-ctrl-shift-y": "git::UnstageAll"
"cmd-ctrl-shift-y": "git::UnstageAll",
"cmd-enter": "git::Commit",
"cmd-shift-enter": "git::Amend"
}
},
{
@@ -1024,9 +1038,15 @@
"use_key_equivalents": true,
"bindings": {
"ctrl-backspace": "collab_panel::Remove",
"space": "menu::Confirm",
"cmd-up": "collab_panel::MoveChannelUp",
"cmd-down": "collab_panel::MoveChannelDown"
"space": "menu::Confirm"
}
},
{
"context": "CollabPanel",
"use_key_equivalents": true,
"bindings": {
"alt-up": "collab_panel::MoveChannelUp",
"alt-down": "collab_panel::MoveChannelDown"
}
},
{
@@ -1216,8 +1236,14 @@
"context": "KeymapEditor",
"use_key_equivalents": true,
"bindings": {
"cmd-f": "search::FocusSearch",
"cmd-alt-f": "keymap_editor::ToggleKeystrokeSearch",
"cmd-alt-c": "keymap_editor::ToggleConflictFilter"
"cmd-alt-c": "keymap_editor::ToggleConflictFilter",
"enter": "keymap_editor::EditBinding",
"alt-enter": "keymap_editor::CreateBinding",
"cmd-c": "keymap_editor::CopyAction",
"cmd-shift-c": "keymap_editor::CopyContext",
"cmd-t": "keymap_editor::ShowMatchingKeybinds"
}
},
{

View File

@@ -13,9 +13,9 @@
}
},
{
"context": "Editor && vim_mode == insert && !menu",
"context": "Editor && vim_mode == insert",
"bindings": {
// "j k": "vim::SwitchToNormalMode"
// "j k": "vim::NormalBefore"
}
}
]

View File

@@ -8,7 +8,7 @@
"ctrl-shift-i": "agent::ToggleFocus",
"ctrl-l": "agent::ToggleFocus",
"ctrl-shift-l": "agent::ToggleFocus",
"ctrl-shift-j": "agent::OpenConfiguration"
"ctrl-shift-j": "agent::OpenSettings"
}
},
{

View File

@@ -114,7 +114,7 @@
"ctrl-x o": "workspace::ActivateNextPane", // other-window
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
"ctrl-x 1": "pane::CloseInactiveItems", // delete-other-windows
"ctrl-x 1": "pane::CloseOtherItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file

View File

@@ -4,6 +4,7 @@
"ctrl-alt-s": "zed::OpenSettings",
"ctrl-{": "pane::ActivatePreviousItem",
"ctrl-}": "pane::ActivateNextItem",
"shift-escape": null, // Unmap workspace::zoom
"ctrl-f2": "debugger::Stop",
"f6": "debugger::Pause",
"f7": "debugger::StepInto",
@@ -44,8 +45,8 @@
"ctrl-alt-right": "pane::GoForward",
"alt-f7": "editor::FindAllReferences",
"ctrl-alt-f7": "editor::FindAllReferences",
// "ctrl-b": "editor::GoToDefinition", // Conflicts with workspace::ToggleLeftDock
// "ctrl-alt-b": "editor::GoToDefinitionSplit", // Conflicts with workspace::ToggleLeftDock
"ctrl-b": "editor::GoToDefinition", // Conflicts with workspace::ToggleLeftDock
"ctrl-alt-b": "editor::GoToDefinitionSplit", // Conflicts with workspace::ToggleRightDock
"ctrl-shift-b": "editor::GoToTypeDefinition",
"ctrl-alt-shift-b": "editor::GoToTypeDefinitionSplit",
"f2": "editor::GoToDiagnostic",
@@ -66,22 +67,66 @@
"context": "Editor && mode == full",
"bindings": {
"ctrl-f12": "outline::Toggle",
"alt-7": "outline::Toggle",
"ctrl-r": ["buffer_search::Deploy", { "replace_enabled": true }],
"ctrl-shift-n": "file_finder::Toggle",
"ctrl-g": "go_to_line::Toggle",
"alt-enter": "editor::ToggleCodeActions"
}
},
{
"context": "BufferSearchBar",
"bindings": {
"shift-enter": "search::SelectPreviousMatch"
}
},
{
"context": "BufferSearchBar || ProjectSearchBar",
"bindings": {
"alt-c": "search::ToggleCaseSensitive",
"alt-e": "search::ToggleSelection",
"alt-x": "search::ToggleRegex",
"alt-w": "search::ToggleWholeWord"
}
},
{
"context": "Workspace",
"bindings": {
"ctrl-shift-f12": "workspace::CloseAllDocks",
"ctrl-shift-r": ["pane::DeploySearch", { "replace_enabled": true }],
"alt-shift-f10": "task::Spawn",
"ctrl-e": "file_finder::Toggle",
// "ctrl-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
"ctrl-shift-n": "file_finder::Toggle",
"ctrl-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle",
"ctrl-alt-shift-n": "project_symbols::Toggle",
"alt-1": "workspace::ToggleLeftDock",
"ctrl-e": "tab_switcher::Toggle",
"alt-6": "diagnostics::Deploy"
"alt-0": "git_panel::ToggleFocus",
"alt-1": "project_panel::ToggleFocus",
"alt-5": "debug_panel::ToggleFocus",
"alt-6": "diagnostics::Deploy",
"alt-7": "outline_panel::ToggleFocus"
}
},
{
"context": "Pane", // this is to override the default Pane mappings to switch tabs
"bindings": {
"alt-1": "project_panel::ToggleFocus",
"alt-2": null, // Bookmarks (left dock)
"alt-3": null, // Find Panel (bottom dock)
"alt-4": null, // Run Panel (bottom dock)
"alt-5": "debug_panel::ToggleFocus",
"alt-6": "diagnostics::Deploy",
"alt-7": "outline_panel::ToggleFocus",
"alt-8": null, // Services (bottom dock)
"alt-9": null, // Git History (bottom dock)
"alt-0": "git_panel::ToggleFocus"
}
},
{
"context": "Workspace || Editor",
"bindings": {
"alt-f12": "terminal_panel::ToggleFocus",
"ctrl-shift-k": "git::Push"
}
},
{
@@ -95,10 +140,36 @@
"context": "ProjectPanel",
"bindings": {
"enter": "project_panel::Open",
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
"shift-f6": "project_panel::Rename"
}
},
{
"context": "Terminal",
"bindings": {
"ctrl-shift-t": "workspace::NewTerminal",
"alt-f12": "workspace::CloseActiveDock",
"alt-left": "pane::ActivatePreviousItem",
"alt-right": "pane::ActivateNextItem",
"ctrl-up": "terminal::ScrollLineUp",
"ctrl-down": "terminal::ScrollLineDown",
"shift-pageup": "terminal::ScrollPageUp",
"shift-pagedown": "terminal::ScrollPageDown"
}
},
{ "context": "GitPanel", "bindings": { "alt-0": "workspace::CloseActiveDock" } },
{ "context": "ProjectPanel", "bindings": { "alt-1": "workspace::CloseActiveDock" } },
{ "context": "DebugPanel", "bindings": { "alt-5": "workspace::CloseActiveDock" } },
{ "context": "Diagnostics > Editor", "bindings": { "alt-6": "pane::CloseActiveItem" } },
{ "context": "OutlinePanel", "bindings": { "alt-7": "workspace::CloseActiveDock" } },
{
"context": "Dock || Workspace || Terminal || OutlinePanel || ProjectPanel || CollabPanel || (Editor && mode == auto_height)",
"bindings": {
"escape": "editor::ToggleFocus",
"shift-escape": "workspace::CloseActiveDock"
}
}
]

View File

@@ -8,7 +8,7 @@
"cmd-shift-i": "agent::ToggleFocus",
"cmd-l": "agent::ToggleFocus",
"cmd-shift-l": "agent::ToggleFocus",
"cmd-shift-j": "agent::OpenConfiguration"
"cmd-shift-j": "agent::OpenSettings"
}
},
{

View File

@@ -114,7 +114,7 @@
"ctrl-x o": "workspace::ActivateNextPane", // other-window
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
"ctrl-x 1": "pane::CloseInactiveItems", // delete-other-windows
"ctrl-x 1": "pane::CloseOtherItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file

View File

@@ -3,6 +3,8 @@
"bindings": {
"cmd-{": "pane::ActivatePreviousItem",
"cmd-}": "pane::ActivateNextItem",
"cmd-0": "git_panel::ToggleFocus", // overrides `cmd-0` zoom reset
"shift-escape": null, // Unmap workspace::zoom
"ctrl-f2": "debugger::Stop",
"f6": "debugger::Pause",
"f7": "debugger::StepInto",
@@ -63,28 +65,70 @@
"context": "Editor && mode == full",
"bindings": {
"cmd-f12": "outline::Toggle",
"cmd-7": "outline::Toggle",
"cmd-r": ["buffer_search::Deploy", { "replace_enabled": true }],
"cmd-shift-o": "file_finder::Toggle",
"cmd-l": "go_to_line::Toggle",
"alt-enter": "editor::ToggleCodeActions"
}
},
{
"context": "BufferSearchBar > Editor",
"context": "BufferSearchBar",
"bindings": {
"shift-enter": "search::SelectPreviousMatch"
}
},
{
"context": "BufferSearchBar || ProjectSearchBar",
"bindings": {
"alt-c": "search::ToggleCaseSensitive",
"alt-e": "search::ToggleSelection",
"alt-x": "search::ToggleRegex",
"alt-w": "search::ToggleWholeWord",
"ctrl-alt-c": "search::ToggleCaseSensitive",
"ctrl-alt-e": "search::ToggleSelection",
"ctrl-alt-w": "search::ToggleWholeWord",
"ctrl-alt-x": "search::ToggleRegex"
}
},
{
"context": "Workspace",
"bindings": {
"cmd-shift-f12": "workspace::CloseAllDocks",
"cmd-shift-r": ["pane::DeploySearch", { "replace_enabled": true }],
"ctrl-alt-r": "task::Spawn",
"cmd-e": "file_finder::Toggle",
// "cmd-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
"cmd-shift-o": "file_finder::Toggle",
"cmd-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle",
"cmd-alt-o": "project_symbols::Toggle", // JetBrains: Go to Symbol
"cmd-o": "project_symbols::Toggle", // JetBrains: Go to Class
"cmd-1": "workspace::ToggleLeftDock",
"cmd-6": "diagnostics::Deploy"
"cmd-1": "project_panel::ToggleFocus",
"cmd-5": "debug_panel::ToggleFocus",
"cmd-6": "diagnostics::Deploy",
"cmd-7": "outline_panel::ToggleFocus"
}
},
{
"context": "Pane", // this is to override the default Pane mappings to switch tabs
"bindings": {
"cmd-1": "project_panel::ToggleFocus",
"cmd-2": null, // Bookmarks (left dock)
"cmd-3": null, // Find Panel (bottom dock)
"cmd-4": null, // Run Panel (bottom dock)
"cmd-5": "debug_panel::ToggleFocus",
"cmd-6": "diagnostics::Deploy",
"cmd-7": "outline_panel::ToggleFocus",
"cmd-8": null, // Services (bottom dock)
"cmd-9": null, // Git History (bottom dock)
"cmd-0": "git_panel::ToggleFocus"
}
},
{
"context": "Workspace || Editor",
"bindings": {
"alt-f12": "terminal_panel::ToggleFocus",
"cmd-shift-k": "git::Push"
}
},
{
@@ -98,11 +142,35 @@
"context": "ProjectPanel",
"bindings": {
"enter": "project_panel::Open",
"cmd-shift-f": "project_panel::NewSearchInDirectory",
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": false }],
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
"shift-f6": "project_panel::Rename"
}
},
{
"context": "Terminal",
"bindings": {
"cmd-t": "workspace::NewTerminal",
"alt-f12": "workspace::CloseActiveDock",
"cmd-up": "terminal::ScrollLineUp",
"cmd-down": "terminal::ScrollLineDown",
"shift-pageup": "terminal::ScrollPageUp",
"shift-pagedown": "terminal::ScrollPageDown"
}
},
{ "context": "GitPanel", "bindings": { "cmd-0": "workspace::CloseActiveDock" } },
{ "context": "ProjectPanel", "bindings": { "cmd-1": "workspace::CloseActiveDock" } },
{ "context": "DebugPanel", "bindings": { "cmd-5": "workspace::CloseActiveDock" } },
{ "context": "Diagnostics > Editor", "bindings": { "cmd-6": "pane::CloseActiveItem" } },
{ "context": "OutlinePanel", "bindings": { "cmd-7": "workspace::CloseActiveDock" } },
{
"context": "Dock || Workspace || Terminal || OutlinePanel || ProjectPanel || CollabPanel || (Editor && mode == auto_height)",
"bindings": {
"escape": "editor::ToggleFocus",
"shift-escape": "workspace::CloseActiveDock"
}
}
]

View File

@@ -6,7 +6,7 @@
}
},
{
"context": "Editor",
"context": "Editor && mode == full",
"bindings": {
"cmd-l": "go_to_line::Toggle",
"ctrl-shift-d": "editor::DuplicateLineDown",
@@ -15,7 +15,12 @@
"cmd-enter": "editor::NewlineBelow",
"cmd-alt-enter": "editor::NewlineAbove",
"cmd-shift-l": "editor::SelectLine",
"cmd-shift-t": "outline::Toggle",
"cmd-shift-t": "outline::Toggle"
}
},
{
"context": "Editor",
"bindings": {
"alt-backspace": "editor::DeleteToPreviousWordStart",
"alt-shift-backspace": "editor::DeleteToNextWordEnd",
"alt-delete": "editor::DeleteToNextWordEnd",
@@ -39,10 +44,6 @@
"ctrl-_": "editor::ConvertToSnakeCase"
}
},
{
"context": "Editor && mode == full",
"bindings": {}
},
{
"context": "BufferSearchBar",
"bindings": {

View File

@@ -124,6 +124,7 @@
"g r a": "editor::ToggleCodeActions",
"g g": "vim::StartOfDocument",
"g h": "editor::Hover",
"g B": "editor::BlameHover",
"g t": "pane::ActivateNextItem",
"g shift-t": "pane::ActivatePreviousItem",
"g d": "editor::GoToDefinition",
@@ -219,6 +220,8 @@
{
"context": "vim_mode == normal",
"bindings": {
"i": "vim::InsertBefore",
"a": "vim::InsertAfter",
"ctrl-[": "editor::Cancel",
":": "command_palette::Toggle",
"c": "vim::PushChange",
@@ -352,9 +355,7 @@
"shift-d": "vim::DeleteToEndOfLine",
"shift-j": "vim::JoinLines",
"shift-y": "vim::YankLine",
"i": "vim::InsertBefore",
"shift-i": "vim::InsertFirstNonWhitespace",
"a": "vim::InsertAfter",
"shift-a": "vim::InsertEndOfLine",
"o": "vim::InsertLineBelow",
"shift-o": "vim::InsertLineAbove",
@@ -376,7 +377,10 @@
{
"context": "vim_mode == helix_normal && !menu",
"bindings": {
"i": "vim::HelixInsert",
"a": "vim::HelixAppend",
"ctrl-[": "editor::Cancel",
";": "vim::HelixCollapseSelection",
":": "command_palette::Toggle",
"left": "vim::WrappingLeft",
"right": "vim::WrappingRight",
@@ -723,7 +727,7 @@
}
},
{
"context": "AgentPanel || GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || DebugPanel",
"context": "VimControl || !Editor && !Terminal",
"bindings": {
// window related commands (ctrl-w X)
"ctrl-w": null,
@@ -781,7 +785,7 @@
}
},
{
"context": "ChangesList || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
"context": "!Editor && !Terminal",
"bindings": {
":": "command_palette::Toggle",
"g /": "pane::DeploySearch"
@@ -857,6 +861,14 @@
"shift-n": null
}
},
{
"context": "Picker > Editor",
"bindings": {
"ctrl-h": "editor::Backspace",
"ctrl-u": "editor::DeleteToBeginningOfLine",
"ctrl-w": "editor::DeleteToPreviousWordStart"
}
},
{
"context": "GitCommit > Editor && VimControl && vim_mode == normal",
"bindings": {

View File

@@ -197,6 +197,8 @@
// "inline"
// 3. Place snippets at the bottom of the completion list:
// "bottom"
// 4. Do not show snippets in the completion list:
// "none"
"snippet_sort_order": "inline",
// How to highlight the current line in the editor.
//
@@ -689,7 +691,10 @@
// 5. Never show the scrollbar:
// "never"
"show": null
}
},
// Default depth to expand outline items in the current file.
// Set to 0 to collapse all items that have children, 1 or higher to collapse items at that depth or deeper.
"expand_outlines_with_depth": 100
},
"collaboration_panel": {
// Whether to show the collaboration panel button in the status bar.
@@ -817,7 +822,7 @@
"edit_file": true,
"fetch": true,
"list_directory": true,
"project_notifications": true,
"project_notifications": false,
"move_path": true,
"now": true,
"find_path": true,
@@ -837,7 +842,7 @@
"diagnostics": true,
"fetch": true,
"list_directory": true,
"project_notifications": true,
"project_notifications": false,
"now": true,
"find_path": true,
"read_file": true,
@@ -1074,6 +1079,10 @@
// Send anonymized usage data like what languages you're using Zed with.
"metrics": true
},
// Whether to disable all AI features in Zed.
//
// Default: false
"disable_ai": false,
// Automatically update Zed. This setting may be ignored on Linux if
// installed through a package manager.
"auto_update": true,
@@ -1710,6 +1719,7 @@
"openai": {
"api_url": "https://api.openai.com/v1"
},
"openai_compatible": {},
"open_router": {
"api_url": "https://openrouter.ai/api/v1"
},
@@ -1867,5 +1877,25 @@
"save_breakpoints": true,
"dock": "bottom",
"button": true
}
},
// Configures any number of settings profiles that are temporarily applied on
// top of your existing user settings when selected from
// `settings profile selector: toggle`.
// Examples:
// "profiles": {
// "Presenting": {
// "agent_font_size": 20.0,
// "buffer_font_size": 20.0,
// "theme": "One Light",
// "ui_font_size": 20.0
// },
// "Python (ty)": {
// "languages": {
// "Python": {
// "language_servers": ["ty"]
// }
// }
// }
// }
"profiles": []
}

View File

@@ -15,13 +15,15 @@
"adapter": "JavaScript",
"program": "$ZED_FILE",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT"
"cwd": "$ZED_WORKTREE_ROOT",
"type": "pwa-node"
},
{
"label": "JavaScript debug terminal",
"adapter": "JavaScript",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT",
"console": "integratedTerminal"
"console": "integratedTerminal",
"type": "pwa-node"
}
]

View File

@@ -8,7 +8,7 @@
// command palette (cmd-shift-p / ctrl-shift-p)
{
"ui_font_size": 16,
"buffer_font_size": 16,
"buffer_font_size": 15,
"theme": {
"mode": "system",
"light": "One Light",

View File

@@ -59,5 +59,11 @@ services:
depends_on:
- postgres
stripe-mock:
image: stripe/stripe-mock:v0.178.0
ports:
- 12111:12111
- 12112:12112
volumes:
postgres_data:

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
[package]
name = "acp"
name = "acp_thread"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
@@ -9,15 +9,14 @@ license = "GPL-3.0-or-later"
workspace = true
[lib]
path = "src/acp.rs"
path = "src/acp_thread.rs"
doctest = false
[features]
test-support = ["gpui/test-support", "project/test-support"]
gemini = []
[dependencies]
agent_servers.workspace = true
agent-client-protocol.workspace = true
agentic-coding-protocol.workspace = true
anyhow.workspace = true
assistant_tool.workspace = true
@@ -29,6 +28,8 @@ itertools.workspace = true
language.workspace = true
markdown.workspace = true
project.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
smol.workspace = true
ui.workspace = true
@@ -41,7 +42,6 @@ env_logger.workspace = true
gpui = { workspace = true, "features" = ["test-support"] }
indoc.workspace = true
project = { workspace = true, "features" = ["test-support"] }
serde_json.workspace = true
tempfile.workspace = true
util.workspace = true
settings.workspace = true

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
use std::{path::Path, rc::Rc};
use agent_client_protocol as acp;
use anyhow::Result;
use gpui::{AsyncApp, Entity, Task};
use project::Project;
use ui::App;
use crate::AcpThread;
pub trait AgentConnection {
fn name(&self) -> &'static str;
fn new_thread(
self: Rc<Self>,
project: Entity<Project>,
cwd: &Path,
cx: &mut AsyncApp,
) -> Task<Result<Entity<AcpThread>>>;
fn authenticate(&self, cx: &mut App) -> Task<Result<()>>;
fn prompt(&self, params: acp::PromptArguments, cx: &mut App) -> Task<Result<()>>;
fn cancel(&self, session_id: &acp::SessionId, cx: &mut App);
}

View File

@@ -0,0 +1,453 @@
// Translates old acp agents into the new schema
use agent_client_protocol as acp;
use agentic_coding_protocol::{self as acp_old, AgentRequest as _};
use anyhow::{Context as _, Result};
use futures::channel::oneshot;
use gpui::{AppContext as _, AsyncApp, Entity, Task, WeakEntity};
use project::Project;
use std::{cell::RefCell, error::Error, fmt, path::Path, rc::Rc};
use ui::App;
use util::ResultExt as _;
use crate::{AcpThread, AgentConnection};
#[derive(Clone)]
pub struct OldAcpClientDelegate {
thread: Rc<RefCell<WeakEntity<AcpThread>>>,
cx: AsyncApp,
next_tool_call_id: Rc<RefCell<u64>>,
// sent_buffer_versions: HashMap<Entity<Buffer>, HashMap<u64, BufferSnapshot>>,
}
impl OldAcpClientDelegate {
pub fn new(thread: Rc<RefCell<WeakEntity<AcpThread>>>, cx: AsyncApp) -> Self {
Self {
thread,
cx,
next_tool_call_id: Rc::new(RefCell::new(0)),
}
}
}
impl acp_old::Client for OldAcpClientDelegate {
async fn stream_assistant_message_chunk(
&self,
params: acp_old::StreamAssistantMessageChunkParams,
) -> Result<(), acp_old::Error> {
let cx = &mut self.cx.clone();
cx.update(|cx| {
self.thread
.borrow()
.update(cx, |thread, cx| match params.chunk {
acp_old::AssistantMessageChunk::Text { text } => {
thread.push_assistant_content_block(text.into(), false, cx)
}
acp_old::AssistantMessageChunk::Thought { thought } => {
thread.push_assistant_content_block(thought.into(), true, cx)
}
})
.log_err();
})?;
Ok(())
}
async fn request_tool_call_confirmation(
&self,
request: acp_old::RequestToolCallConfirmationParams,
) -> Result<acp_old::RequestToolCallConfirmationResponse, acp_old::Error> {
let cx = &mut self.cx.clone();
let old_acp_id = *self.next_tool_call_id.borrow() + 1;
self.next_tool_call_id.replace(old_acp_id);
let tool_call = into_new_tool_call(
acp::ToolCallId(old_acp_id.to_string().into()),
request.tool_call,
);
let mut options = match request.confirmation {
acp_old::ToolCallConfirmation::Edit { .. } => vec![(
acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
acp::PermissionOptionKind::AllowAlways,
"Always Allow Edits".to_string(),
)],
acp_old::ToolCallConfirmation::Execute { root_command, .. } => vec![(
acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
acp::PermissionOptionKind::AllowAlways,
format!("Always Allow {}", root_command),
)],
acp_old::ToolCallConfirmation::Mcp {
server_name,
tool_name,
..
} => vec![
(
acp_old::ToolCallConfirmationOutcome::AlwaysAllowMcpServer,
acp::PermissionOptionKind::AllowAlways,
format!("Always Allow {}", server_name),
),
(
acp_old::ToolCallConfirmationOutcome::AlwaysAllowTool,
acp::PermissionOptionKind::AllowAlways,
format!("Always Allow {}", tool_name),
),
],
acp_old::ToolCallConfirmation::Fetch { .. } => vec![(
acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
acp::PermissionOptionKind::AllowAlways,
"Always Allow".to_string(),
)],
acp_old::ToolCallConfirmation::Other { .. } => vec![(
acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
acp::PermissionOptionKind::AllowAlways,
"Always Allow".to_string(),
)],
};
options.extend([
(
acp_old::ToolCallConfirmationOutcome::Allow,
acp::PermissionOptionKind::AllowOnce,
"Allow".to_string(),
),
(
acp_old::ToolCallConfirmationOutcome::Reject,
acp::PermissionOptionKind::RejectOnce,
"Reject".to_string(),
),
]);
let mut outcomes = Vec::with_capacity(options.len());
let mut acp_options = Vec::with_capacity(options.len());
for (index, (outcome, kind, label)) in options.into_iter().enumerate() {
outcomes.push(outcome);
acp_options.push(acp::PermissionOption {
id: acp::PermissionOptionId(index.to_string().into()),
label,
kind,
})
}
let response = cx
.update(|cx| {
self.thread.borrow().update(cx, |thread, cx| {
thread.request_tool_call_permission(tool_call, acp_options, cx)
})
})?
.context("Failed to update thread")?
.await;
let outcome = match response {
Ok(option_id) => outcomes[option_id.0.parse::<usize>().unwrap_or(0)],
Err(oneshot::Canceled) => acp_old::ToolCallConfirmationOutcome::Cancel,
};
Ok(acp_old::RequestToolCallConfirmationResponse {
id: acp_old::ToolCallId(old_acp_id),
outcome: outcome,
})
}
async fn push_tool_call(
&self,
request: acp_old::PushToolCallParams,
) -> Result<acp_old::PushToolCallResponse, acp_old::Error> {
let cx = &mut self.cx.clone();
let old_acp_id = *self.next_tool_call_id.borrow() + 1;
self.next_tool_call_id.replace(old_acp_id);
cx.update(|cx| {
self.thread.borrow().update(cx, |thread, cx| {
thread.upsert_tool_call(
into_new_tool_call(acp::ToolCallId(old_acp_id.to_string().into()), request),
cx,
)
})
})?
.context("Failed to update thread")?;
Ok(acp_old::PushToolCallResponse {
id: acp_old::ToolCallId(old_acp_id),
})
}
async fn update_tool_call(
&self,
request: acp_old::UpdateToolCallParams,
) -> Result<(), acp_old::Error> {
let cx = &mut self.cx.clone();
cx.update(|cx| {
self.thread.borrow().update(cx, |thread, cx| {
thread.update_tool_call(
acp::ToolCallUpdate {
id: acp::ToolCallId(request.tool_call_id.0.to_string().into()),
fields: acp::ToolCallUpdateFields {
status: Some(into_new_tool_call_status(request.status)),
content: Some(
request
.content
.into_iter()
.map(into_new_tool_call_content)
.collect::<Vec<_>>(),
),
..Default::default()
},
},
cx,
)
})
})?
.context("Failed to update thread")??;
Ok(())
}
async fn update_plan(&self, request: acp_old::UpdatePlanParams) -> Result<(), acp_old::Error> {
let cx = &mut self.cx.clone();
cx.update(|cx| {
self.thread.borrow().update(cx, |thread, cx| {
thread.update_plan(
acp::Plan {
entries: request
.entries
.into_iter()
.map(into_new_plan_entry)
.collect(),
},
cx,
)
})
})?
.context("Failed to update thread")?;
Ok(())
}
async fn read_text_file(
&self,
acp_old::ReadTextFileParams { path, line, limit }: acp_old::ReadTextFileParams,
) -> Result<acp_old::ReadTextFileResponse, acp_old::Error> {
let content = self
.cx
.update(|cx| {
self.thread.borrow().update(cx, |thread, cx| {
thread.read_text_file(path, line, limit, false, cx)
})
})?
.context("Failed to update thread")?
.await?;
Ok(acp_old::ReadTextFileResponse { content })
}
async fn write_text_file(
&self,
acp_old::WriteTextFileParams { path, content }: acp_old::WriteTextFileParams,
) -> Result<(), acp_old::Error> {
self.cx
.update(|cx| {
self.thread
.borrow()
.update(cx, |thread, cx| thread.write_text_file(path, content, cx))
})?
.context("Failed to update thread")?
.await?;
Ok(())
}
}
fn into_new_tool_call(id: acp::ToolCallId, request: acp_old::PushToolCallParams) -> acp::ToolCall {
acp::ToolCall {
id: id,
label: request.label,
kind: acp_kind_from_old_icon(request.icon),
status: acp::ToolCallStatus::InProgress,
content: request
.content
.into_iter()
.map(into_new_tool_call_content)
.collect(),
locations: request
.locations
.into_iter()
.map(into_new_tool_call_location)
.collect(),
raw_input: None,
}
}
fn acp_kind_from_old_icon(icon: acp_old::Icon) -> acp::ToolKind {
match icon {
acp_old::Icon::FileSearch => acp::ToolKind::Search,
acp_old::Icon::Folder => acp::ToolKind::Search,
acp_old::Icon::Globe => acp::ToolKind::Search,
acp_old::Icon::Hammer => acp::ToolKind::Other,
acp_old::Icon::LightBulb => acp::ToolKind::Think,
acp_old::Icon::Pencil => acp::ToolKind::Edit,
acp_old::Icon::Regex => acp::ToolKind::Search,
acp_old::Icon::Terminal => acp::ToolKind::Execute,
}
}
fn into_new_tool_call_status(status: acp_old::ToolCallStatus) -> acp::ToolCallStatus {
match status {
acp_old::ToolCallStatus::Running => acp::ToolCallStatus::InProgress,
acp_old::ToolCallStatus::Finished => acp::ToolCallStatus::Completed,
acp_old::ToolCallStatus::Error => acp::ToolCallStatus::Failed,
}
}
fn into_new_tool_call_content(content: acp_old::ToolCallContent) -> acp::ToolCallContent {
match content {
acp_old::ToolCallContent::Markdown { markdown } => markdown.into(),
acp_old::ToolCallContent::Diff { diff } => acp::ToolCallContent::Diff {
diff: into_new_diff(diff),
},
}
}
fn into_new_diff(diff: acp_old::Diff) -> acp::Diff {
acp::Diff {
path: diff.path,
old_text: diff.old_text,
new_text: diff.new_text,
}
}
fn into_new_tool_call_location(location: acp_old::ToolCallLocation) -> acp::ToolCallLocation {
acp::ToolCallLocation {
path: location.path,
line: location.line,
}
}
fn into_new_plan_entry(entry: acp_old::PlanEntry) -> acp::PlanEntry {
acp::PlanEntry {
content: entry.content,
priority: into_new_plan_priority(entry.priority),
status: into_new_plan_status(entry.status),
}
}
fn into_new_plan_priority(priority: acp_old::PlanEntryPriority) -> acp::PlanEntryPriority {
match priority {
acp_old::PlanEntryPriority::Low => acp::PlanEntryPriority::Low,
acp_old::PlanEntryPriority::Medium => acp::PlanEntryPriority::Medium,
acp_old::PlanEntryPriority::High => acp::PlanEntryPriority::High,
}
}
fn into_new_plan_status(status: acp_old::PlanEntryStatus) -> acp::PlanEntryStatus {
match status {
acp_old::PlanEntryStatus::Pending => acp::PlanEntryStatus::Pending,
acp_old::PlanEntryStatus::InProgress => acp::PlanEntryStatus::InProgress,
acp_old::PlanEntryStatus::Completed => acp::PlanEntryStatus::Completed,
}
}
#[derive(Debug)]
pub struct Unauthenticated;
impl Error for Unauthenticated {}
impl fmt::Display for Unauthenticated {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Unauthenticated")
}
}
pub struct OldAcpAgentConnection {
pub name: &'static str,
pub connection: acp_old::AgentConnection,
pub child_status: Task<Result<()>>,
pub current_thread: Rc<RefCell<WeakEntity<AcpThread>>>,
}
impl AgentConnection for OldAcpAgentConnection {
fn name(&self) -> &'static str {
self.name
}
fn new_thread(
self: Rc<Self>,
project: Entity<Project>,
_cwd: &Path,
cx: &mut AsyncApp,
) -> Task<Result<Entity<AcpThread>>> {
let task = self.connection.request_any(
acp_old::InitializeParams {
protocol_version: acp_old::ProtocolVersion::latest(),
}
.into_any(),
);
let current_thread = self.current_thread.clone();
cx.spawn(async move |cx| {
let result = task.await?;
let result = acp_old::InitializeParams::response_from_any(result)?;
if !result.is_authenticated {
anyhow::bail!(Unauthenticated)
}
cx.update(|cx| {
let thread = cx.new(|cx| {
let session_id = acp::SessionId("acp-old-no-id".into());
AcpThread::new(self.clone(), project, session_id, cx)
});
current_thread.replace(thread.downgrade());
thread
})
})
}
fn authenticate(&self, cx: &mut App) -> Task<Result<()>> {
let task = self
.connection
.request_any(acp_old::AuthenticateParams.into_any());
cx.foreground_executor().spawn(async move {
task.await?;
Ok(())
})
}
fn prompt(&self, params: acp::PromptArguments, cx: &mut App) -> Task<Result<()>> {
let chunks = params
.prompt
.into_iter()
.filter_map(|block| match block {
acp::ContentBlock::Text(text) => {
Some(acp_old::UserMessageChunk::Text { text: text.text })
}
acp::ContentBlock::ResourceLink(link) => Some(acp_old::UserMessageChunk::Path {
path: link.uri.into(),
}),
_ => None,
})
.collect();
let task = self
.connection
.request_any(acp_old::SendUserMessageParams { chunks }.into_any());
cx.foreground_executor().spawn(async move {
task.await?;
anyhow::Ok(())
})
}
fn cancel(&self, _session_id: &acp::SessionId, cx: &mut App) {
let task = self
.connection
.request_any(acp_old::CancelSendMessageParams.into_any());
cx.foreground_executor()
.spawn(async move {
task.await?;
anyhow::Ok(())
})
.detach_and_log_err(cx)
}
}

View File

@@ -231,7 +231,6 @@ impl ActivityIndicator {
status,
} => {
let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
let project = project.clone();
let status = status.clone();
let server_name = server_name.clone();
cx.spawn_in(window, async move |workspace, cx| {
@@ -247,8 +246,7 @@ impl ActivityIndicator {
workspace.update_in(cx, |workspace, window, cx| {
workspace.add_item_to_active_pane(
Box::new(cx.new(|cx| {
let mut editor =
Editor::for_buffer(buffer, Some(project.clone()), window, cx);
let mut editor = Editor::for_buffer(buffer, None, window, cx);
editor.set_read_only(true);
editor
})),

View File

@@ -25,6 +25,7 @@ assistant_context.workspace = true
assistant_tool.workspace = true
chrono.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
component.workspace = true
context_server.workspace = true
@@ -35,9 +36,9 @@ futures.workspace = true
git.workspace = true
gpui.workspace = true
heed.workspace = true
http_client.workspace = true
icons.workspace = true
indoc.workspace = true
http_client.workspace = true
itertools.workspace = true
language.workspace = true
language_model.workspace = true
@@ -46,7 +47,6 @@ paths.workspace = true
postage.workspace = true
project.workspace = true
prompt_store.workspace = true
proto.workspace = true
ref-cast.workspace = true
rope.workspace = true
schemars.workspace = true
@@ -63,7 +63,6 @@ time.workspace = true
util.workspace = true
uuid.workspace = true
workspace-hack.workspace = true
zed_llm_client.workspace = true
zstd.workspace = true
[dev-dependencies]

View File

@@ -308,7 +308,12 @@ mod tests {
unimplemented!()
}
fn needs_confirmation(&self, _input: &serde_json::Value, _cx: &App) -> bool {
fn needs_confirmation(
&self,
_input: &serde_json::Value,
_project: &Entity<Project>,
_cx: &App,
) -> bool {
unimplemented!()
}

View File

@@ -38,7 +38,7 @@ impl Tool for ContextServerTool {
}
fn icon(&self) -> IconName {
IconName::Cog
IconName::ToolHammer
}
fn source(&self) -> ToolSource {
@@ -47,7 +47,7 @@ impl Tool for ContextServerTool {
}
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self, _: &serde_json::Value, _: &Entity<Project>, _: &App) -> bool {
true
}

View File

@@ -13,6 +13,7 @@ use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolWorkingSet};
use chrono::{DateTime, Utc};
use client::{ModelRequestUsage, RequestUsage};
use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, Plan, UsageLimit};
use collections::HashMap;
use feature_flags::{self, FeatureFlagAppExt};
use futures::{FutureExt, StreamExt as _, future::Shared};
@@ -36,7 +37,6 @@ use project::{
git_store::{GitStore, GitStoreCheckpoint, RepositoryState},
};
use prompt_store::{ModelContext, PromptBuilder};
use proto::Plan;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::Settings;
@@ -47,11 +47,10 @@ use std::{
time::{Duration, Instant},
};
use thiserror::Error;
use util::{ResultExt as _, debug_panic, post_inc};
use util::{ResultExt as _, post_inc};
use uuid::Uuid;
use zed_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
const MAX_RETRY_ATTEMPTS: u8 = 3;
const MAX_RETRY_ATTEMPTS: u8 = 4;
const BASE_RETRY_DELAY: Duration = Duration::from_secs(5);
#[derive(Debug, Clone)]
@@ -396,6 +395,7 @@ pub struct Thread {
remaining_turns: u32,
configured_model: Option<ConfiguredModel>,
profile: AgentProfile,
last_error_context: Option<(Arc<dyn LanguageModel>, CompletionIntent)>,
}
#[derive(Clone, Debug)]
@@ -489,10 +489,11 @@ impl Thread {
retry_state: None,
message_feedback: HashMap::default(),
last_auto_capture_at: None,
last_error_context: None,
last_received_chunk_at: None,
request_callback: None,
remaining_turns: u32::MAX,
configured_model,
configured_model: configured_model.clone(),
profile: AgentProfile::new(profile_id, tools),
}
}
@@ -613,6 +614,7 @@ impl Thread {
feedback: None,
message_feedback: HashMap::default(),
last_auto_capture_at: None,
last_error_context: None,
last_received_chunk_at: None,
request_callback: None,
remaining_turns: u32::MAX,
@@ -939,7 +941,7 @@ impl Thread {
}
pub fn tool_uses_for_message(&self, id: MessageId, cx: &App) -> Vec<ToolUse> {
self.tool_use.tool_uses_for_message(id, cx)
self.tool_use.tool_uses_for_message(id, &self.project, cx)
}
pub fn tool_results_for_message(
@@ -1264,9 +1266,58 @@ impl Thread {
self.flush_notifications(model.clone(), intent, cx);
let request = self.to_completion_request(model.clone(), intent, cx);
let _checkpoint = self.finalize_pending_checkpoint(cx);
self.stream_completion(
self.to_completion_request(model.clone(), intent, cx),
model,
intent,
window,
cx,
);
}
self.stream_completion(request, model, intent, window, cx);
pub fn retry_last_completion(
&mut self,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
// Clear any existing error state
self.retry_state = None;
// Use the last error context if available, otherwise fall back to configured model
let (model, intent) = if let Some((model, intent)) = self.last_error_context.take() {
(model, intent)
} else if let Some(configured_model) = self.configured_model.as_ref() {
let model = configured_model.model.clone();
let intent = if self.has_pending_tool_uses() {
CompletionIntent::ToolResults
} else {
CompletionIntent::UserPrompt
};
(model, intent)
} else if let Some(configured_model) = self.get_or_init_configured_model(cx) {
let model = configured_model.model.clone();
let intent = if self.has_pending_tool_uses() {
CompletionIntent::ToolResults
} else {
CompletionIntent::UserPrompt
};
(model, intent)
} else {
return;
};
self.send_to_model(model, intent, window, cx);
}
pub fn enable_burn_mode_and_retry(
&mut self,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
self.completion_mode = CompletionMode::Burn;
cx.emit(ThreadEvent::ProfileChanged);
self.retry_last_completion(window, cx);
}
pub fn used_tools_since_last_user_message(&self) -> bool {
@@ -1530,20 +1581,18 @@ impl Thread {
model: Arc<dyn LanguageModel>,
cx: &mut App,
) -> Option<PendingToolUse> {
let action_log = self.action_log.read(cx);
// Represent notification as a simulated `project_notifications` tool call
let tool_name = Arc::from("project_notifications");
let tool = self.tools.read(cx).tool(&tool_name, cx)?;
if !action_log.has_unnotified_user_edits() {
if !self.profile.is_tool_enabled(tool.source(), tool.name(), cx) {
return None;
}
// Represent notification as a simulated `project_notifications` tool call
let tool_name = Arc::from("project_notifications");
let Some(tool) = self.tools.read(cx).tool(&tool_name, cx) else {
debug_panic!("`project_notifications` tool not found");
return None;
};
if !self.profile.is_tool_enabled(tool.source(), tool.name(), cx) {
if self
.action_log
.update(cx, |log, cx| log.unnotified_user_edits(cx).is_none())
{
return None;
}
@@ -1631,7 +1680,7 @@ impl Thread {
let completion_mode = request
.mode
.unwrap_or(zed_llm_client::CompletionMode::Normal);
.unwrap_or(cloud_llm_client::CompletionMode::Normal);
self.last_received_chunk_at = Some(Instant::now());
@@ -1987,6 +2036,12 @@ impl Thread {
if let Some(retry_strategy) =
Thread::get_retry_strategy(completion_error)
{
log::info!(
"Retrying with {:?} for language model completion error {:?}",
retry_strategy,
completion_error
);
retry_scheduled = thread
.handle_retryable_error_with_delay(
&completion_error,
@@ -2130,8 +2185,8 @@ impl Thread {
// General strategy here:
// - If retrying won't help (e.g. invalid API key or payload too large), return None so we don't retry at all.
// - If it's a time-based issue (e.g. server overloaded, rate limit exceeded), try multiple times with exponential backoff.
// - If it's an issue that *might* be fixed by retrying (e.g. internal server error), just retry once.
// - If it's a time-based issue (e.g. server overloaded, rate limit exceeded), retry up to 4 times with exponential backoff.
// - If it's an issue that *might* be fixed by retrying (e.g. internal server error), retry up to 3 times.
match error {
HttpResponseError {
status_code: StatusCode::TOO_MANY_REQUESTS,
@@ -2146,16 +2201,48 @@ impl Thread {
max_attempts: MAX_RETRY_ATTEMPTS,
})
}
UpstreamProviderError {
status,
retry_after,
..
} => match *status {
StatusCode::TOO_MANY_REQUESTS | StatusCode::SERVICE_UNAVAILABLE => {
Some(RetryStrategy::Fixed {
delay: retry_after.unwrap_or(BASE_RETRY_DELAY),
max_attempts: MAX_RETRY_ATTEMPTS,
})
}
StatusCode::INTERNAL_SERVER_ERROR => Some(RetryStrategy::Fixed {
delay: retry_after.unwrap_or(BASE_RETRY_DELAY),
// Internal Server Error could be anything, retry up to 3 times.
max_attempts: 3,
}),
status => {
// There is no StatusCode variant for the unofficial HTTP 529 ("The service is overloaded"),
// but we frequently get them in practice. See https://http.dev/529
if status.as_u16() == 529 {
Some(RetryStrategy::Fixed {
delay: retry_after.unwrap_or(BASE_RETRY_DELAY),
max_attempts: MAX_RETRY_ATTEMPTS,
})
} else {
Some(RetryStrategy::Fixed {
delay: retry_after.unwrap_or(BASE_RETRY_DELAY),
max_attempts: 2,
})
}
}
},
ApiInternalServerError { .. } => Some(RetryStrategy::Fixed {
delay: BASE_RETRY_DELAY,
max_attempts: 1,
max_attempts: 3,
}),
ApiReadResponseError { .. }
| HttpSend { .. }
| DeserializeResponse { .. }
| BadRequestFormat { .. } => Some(RetryStrategy::Fixed {
delay: BASE_RETRY_DELAY,
max_attempts: 1,
max_attempts: 3,
}),
// Retrying these errors definitely shouldn't help.
HttpResponseError {
@@ -2163,24 +2250,30 @@ impl Thread {
StatusCode::PAYLOAD_TOO_LARGE | StatusCode::FORBIDDEN | StatusCode::UNAUTHORIZED,
..
}
| SerializeRequest { .. }
| BuildRequestBody { .. }
| PromptTooLarge { .. }
| AuthenticationError { .. }
| PermissionError { .. }
| NoApiKey { .. }
| ApiEndpointNotFound { .. }
| NoApiKey { .. } => None,
| PromptTooLarge { .. } => None,
// These errors might be transient, so retry them
SerializeRequest { .. } | BuildRequestBody { .. } => Some(RetryStrategy::Fixed {
delay: BASE_RETRY_DELAY,
max_attempts: 1,
}),
// Retry all other 4xx and 5xx errors once.
HttpResponseError { status_code, .. }
if status_code.is_client_error() || status_code.is_server_error() =>
{
Some(RetryStrategy::Fixed {
delay: BASE_RETRY_DELAY,
max_attempts: 1,
max_attempts: 3,
})
}
// Conservatively assume that any other errors are non-retryable
HttpResponseError { .. } | Other(..) => None,
HttpResponseError { .. } | Other(..) => Some(RetryStrategy::Fixed {
delay: BASE_RETRY_DELAY,
max_attempts: 2,
}),
}
}
@@ -2193,6 +2286,23 @@ impl Thread {
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) -> bool {
// Store context for the Retry button
self.last_error_context = Some((model.clone(), intent));
// Only auto-retry if Burn Mode is enabled
if self.completion_mode != CompletionMode::Burn {
// Show error with retry options
cx.emit(ThreadEvent::ShowError(ThreadError::RetryableError {
message: format!(
"{}\n\nTo automatically retry when similar errors happen, enable Burn Mode.",
error
)
.into(),
can_enable_burn_mode: true,
}));
return false;
}
let Some(strategy) = strategy.or_else(|| Self::get_retry_strategy(error)) else {
return false;
};
@@ -2273,6 +2383,13 @@ impl Thread {
// Stop generating since we're giving up on retrying.
self.pending_completions.clear();
// Show error alongside a Retry button, but no
// Enable Burn Mode button (since it's already enabled)
cx.emit(ThreadEvent::ShowError(ThreadError::RetryableError {
message: format!("Failed after retrying: {}", error).into(),
can_enable_burn_mode: false,
}));
false
}
}
@@ -2439,7 +2556,7 @@ impl Thread {
return self.handle_hallucinated_tool_use(tool_use.id, tool_use.name, window, cx);
}
if tool.needs_confirmation(&tool_use.input, cx)
if tool.needs_confirmation(&tool_use.input, &self.project, cx)
&& !AgentSettings::get_global(cx).always_allow_tool_actions
{
self.tool_use.confirm_tool_use(
@@ -3137,8 +3254,10 @@ impl Thread {
}
fn update_model_request_usage(&self, amount: u32, limit: UsageLimit, cx: &mut Context<Self>) {
self.project.update(cx, |project, cx| {
project.user_store().update(cx, |user_store, cx| {
self.project
.read(cx)
.user_store()
.update(cx, |user_store, cx| {
user_store.update_model_request_usage(
ModelRequestUsage(RequestUsage {
amount: amount as i32,
@@ -3146,8 +3265,7 @@ impl Thread {
}),
cx,
)
})
});
});
}
pub fn deny_tool_use(
@@ -3183,6 +3301,11 @@ pub enum ThreadError {
header: SharedString,
message: SharedString,
},
#[error("Retryable error: {message}")]
RetryableError {
message: SharedString,
can_enable_burn_mode: bool,
},
}
#[derive(Debug, Clone)]
@@ -3583,6 +3706,7 @@ fn main() {{
}
#[gpui::test]
#[ignore] // turn this test on when project_notifications tool is re-enabled
async fn test_stale_buffer_notification(cx: &mut TestAppContext) {
init_test_settings(cx);
@@ -4137,6 +4261,11 @@ fn main() {{
let project = create_test_project(cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
thread.update(cx, |thread, _| {
thread.set_completion_mode(CompletionMode::Burn);
});
// Create model that returns overloaded error
let model = Arc::new(ErrorInjector::new(TestError::Overloaded));
@@ -4210,6 +4339,11 @@ fn main() {{
let project = create_test_project(cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
thread.update(cx, |thread, _| {
thread.set_completion_mode(CompletionMode::Burn);
});
// Create model that returns internal server error
let model = Arc::new(ErrorInjector::new(TestError::InternalServerError));
@@ -4231,7 +4365,7 @@ fn main() {{
let retry_state = thread.retry_state.as_ref().unwrap();
assert_eq!(retry_state.attempt, 1, "Should be first retry attempt");
assert_eq!(
retry_state.max_attempts, 1,
retry_state.max_attempts, 3,
"Should have correct max attempts"
);
});
@@ -4247,8 +4381,9 @@ fn main() {{
if let MessageSegment::Text(text) = seg {
text.contains("internal")
&& text.contains("Fake")
&& text.contains("Retrying in")
&& !text.contains("attempt")
&& text.contains("Retrying")
&& text.contains("attempt 1 of 3")
&& text.contains("seconds")
} else {
false
}
@@ -4286,6 +4421,11 @@ fn main() {{
let project = create_test_project(cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
thread.update(cx, |thread, _| {
thread.set_completion_mode(CompletionMode::Burn);
});
// Create model that returns internal server error
let model = Arc::new(ErrorInjector::new(TestError::InternalServerError));
@@ -4338,8 +4478,8 @@ fn main() {{
let retry_state = thread.retry_state.as_ref().unwrap();
assert_eq!(retry_state.attempt, 1, "Should be first retry attempt");
assert_eq!(
retry_state.max_attempts, 1,
"Internal server errors should only retry once"
retry_state.max_attempts, 3,
"Internal server errors should retry up to 3 times"
);
});
@@ -4347,7 +4487,15 @@ fn main() {{
cx.executor().advance_clock(BASE_RETRY_DELAY);
cx.run_until_parked();
// Should have scheduled second retry - count retry messages
// Advance clock for second retry
cx.executor().advance_clock(BASE_RETRY_DELAY);
cx.run_until_parked();
// Advance clock for third retry
cx.executor().advance_clock(BASE_RETRY_DELAY);
cx.run_until_parked();
// Should have completed all retries - count retry messages
let retry_count = thread.update(cx, |thread, _| {
thread
.messages
@@ -4365,24 +4513,24 @@ fn main() {{
.count()
});
assert_eq!(
retry_count, 1,
"Should have only one retry for internal server errors"
retry_count, 3,
"Should have 3 retries for internal server errors"
);
// For internal server errors, we only retry once and then give up
// Check that retry_state is cleared after the single retry
// For internal server errors, we retry 3 times and then give up
// Check that retry_state is cleared after all retries
thread.read_with(cx, |thread, _| {
assert!(
thread.retry_state.is_none(),
"Retry state should be cleared after single retry"
"Retry state should be cleared after all retries"
);
});
// Verify total attempts (1 initial + 1 retry)
// Verify total attempts (1 initial + 3 retries)
assert_eq!(
*completion_count.lock(),
2,
"Should have attempted once plus 1 retry"
4,
"Should have attempted once plus 3 retries"
);
}
@@ -4393,6 +4541,11 @@ fn main() {{
let project = create_test_project(cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
thread.update(cx, |thread, _| {
thread.set_completion_mode(CompletionMode::Burn);
});
// Create model that returns overloaded error
let model = Arc::new(ErrorInjector::new(TestError::Overloaded));
@@ -4479,6 +4632,11 @@ fn main() {{
let project = create_test_project(cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
thread.update(cx, |thread, _| {
thread.set_completion_mode(CompletionMode::Burn);
});
// We'll use a wrapper to switch behavior after first failure
struct RetryTestModel {
inner: Arc<FakeLanguageModel>,
@@ -4647,6 +4805,11 @@ fn main() {{
let project = create_test_project(cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
thread.update(cx, |thread, _| {
thread.set_completion_mode(CompletionMode::Burn);
});
// Create a model that fails once then succeeds
struct FailOnceModel {
inner: Arc<FakeLanguageModel>,
@@ -4808,6 +4971,11 @@ fn main() {{
let project = create_test_project(cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
thread.update(cx, |thread, _| {
thread.set_completion_mode(CompletionMode::Burn);
});
// Create a model that returns rate limit error with retry_after
struct RateLimitModel {
inner: Arc<FakeLanguageModel>,
@@ -5081,6 +5249,79 @@ fn main() {{
);
}
#[gpui::test]
async fn test_no_retry_without_burn_mode(cx: &mut TestAppContext) {
init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Ensure we're in Normal mode (not Burn mode)
thread.update(cx, |thread, _| {
thread.set_completion_mode(CompletionMode::Normal);
});
// Track error events
let error_events = Arc::new(Mutex::new(Vec::new()));
let error_events_clone = error_events.clone();
let _subscription = thread.update(cx, |_, cx| {
cx.subscribe(&thread, move |_, _, event: &ThreadEvent, _| {
if let ThreadEvent::ShowError(error) = event {
error_events_clone.lock().push(error.clone());
}
})
});
// Create model that returns overloaded error
let model = Arc::new(ErrorInjector::new(TestError::Overloaded));
// Insert a user message
thread.update(cx, |thread, cx| {
thread.insert_user_message("Hello!", ContextLoadResult::default(), None, vec![], cx);
});
// Start completion
thread.update(cx, |thread, cx| {
thread.send_to_model(model.clone(), CompletionIntent::UserPrompt, None, cx);
});
cx.run_until_parked();
// Verify no retry state was created
thread.read_with(cx, |thread, _| {
assert!(
thread.retry_state.is_none(),
"Should not have retry state in Normal mode"
);
});
// Check that a retryable error was reported
let errors = error_events.lock();
assert!(!errors.is_empty(), "Should have received an error event");
if let ThreadError::RetryableError {
message: _,
can_enable_burn_mode,
} = &errors[0]
{
assert!(
*can_enable_burn_mode,
"Error should indicate burn mode can be enabled"
);
} else {
panic!("Expected RetryableError, got {:?}", errors[0]);
}
// Verify the thread is no longer generating
thread.read_with(cx, |thread, _| {
assert!(
!thread.is_generating(),
"Should not be generating after error without retry"
);
});
}
#[gpui::test]
async fn test_retry_cancelled_on_stop(cx: &mut TestAppContext) {
init_test_settings(cx);
@@ -5088,6 +5329,11 @@ fn main() {{
let project = create_test_project(cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
thread.update(cx, |thread, _| {
thread.set_completion_mode(CompletionMode::Burn);
});
// Create model that returns overloaded error
let model = Arc::new(ErrorInjector::new(TestError::Overloaded));
@@ -5249,7 +5495,7 @@ fn main() {{
let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
let context_store = cx.new(|_cx| ContextStore::new(project.downgrade(), None));
let provider = Arc::new(FakeLanguageModelProvider);
let provider = Arc::new(FakeLanguageModelProvider::default());
let model = provider.test_model();
let model: Arc<dyn LanguageModel> = Arc::new(model);

View File

@@ -41,6 +41,9 @@ use std::{
};
use util::ResultExt as _;
pub static ZED_STATELESS: std::sync::LazyLock<bool> =
std::sync::LazyLock::new(|| std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty()));
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DataType {
#[serde(rename = "json")]
@@ -874,7 +877,11 @@ impl ThreadsDatabase {
let needs_migration_from_heed = mdb_path.exists();
let connection = Connection::open_file(&sqlite_path.to_string_lossy());
let connection = if *ZED_STATELESS {
Connection::open_memory(Some("THREAD_FALLBACK_DB"))
} else {
Connection::open_file(&sqlite_path.to_string_lossy())
};
connection.exec(indoc! {"
CREATE TABLE IF NOT EXISTS threads (

View File

@@ -165,7 +165,12 @@ impl ToolUseState {
self.pending_tool_uses_by_id.values().collect()
}
pub fn tool_uses_for_message(&self, id: MessageId, cx: &App) -> Vec<ToolUse> {
pub fn tool_uses_for_message(
&self,
id: MessageId,
project: &Entity<Project>,
cx: &App,
) -> Vec<ToolUse> {
let Some(tool_uses_for_message) = &self.tool_uses_by_assistant_message.get(&id) else {
return Vec::new();
};
@@ -211,7 +216,10 @@ impl ToolUseState {
let (icon, needs_confirmation) =
if let Some(tool) = self.tools.read(cx).tool(&tool_use.name, cx) {
(tool.icon(), tool.needs_confirmation(&tool_use.input, cx))
(
tool.icon(),
tool.needs_confirmation(&tool_use.input, project, cx),
)
} else {
(IconName::Cog, false)
};

View File

@@ -5,6 +5,10 @@ edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[features]
test-support = ["acp_thread/test-support", "gpui/test-support", "project/test-support"]
e2e = []
[lints]
workspace = true
@@ -13,15 +17,41 @@ path = "src/agent_servers.rs"
doctest = false
[dependencies]
acp_thread.workspace = true
agent-client-protocol.workspace = true
agentic-coding-protocol.workspace = true
anyhow.workspace = true
collections.workspace = true
context_server.workspace = true
futures.workspace = true
gpui.workspace = true
itertools.workspace = true
log.workspace = true
paths.workspace = true
project.workspace = true
rand.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
smol.workspace = true
strum.workspace = true
tempfile.workspace = true
ui.workspace = true
util.workspace = true
uuid.workspace = true
watch.workspace = true
indoc.workspace = true
which.workspace = true
workspace-hack.workspace = true
[target.'cfg(unix)'.dependencies]
libc.workspace = true
nix.workspace = true
[dev-dependencies]
env_logger.workspace = true
language.workspace = true
indoc.workspace = true
acp_thread = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }

View File

@@ -1,30 +1,82 @@
use std::{
path::{Path, PathBuf},
sync::Arc,
};
mod claude;
mod codex;
mod gemini;
mod mcp_server;
mod settings;
use anyhow::{Context as _, Result};
#[cfg(test)]
mod e2e_tests;
pub use claude::*;
pub use codex::*;
pub use gemini::*;
pub use settings::*;
use acp_thread::AgentConnection;
use anyhow::Result;
use collections::HashMap;
use gpui::{App, AsyncApp, Entity, SharedString};
use gpui::{App, AsyncApp, Entity, SharedString, Task};
use project::Project;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsStore};
use util::{ResultExt, paths};
use std::{
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
};
use util::ResultExt as _;
pub fn init(cx: &mut App) {
AllAgentServersSettings::register(cx);
settings::init(cx);
}
#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug)]
pub struct AllAgentServersSettings {
gemini: Option<AgentServerSettings>,
pub trait AgentServer: Send {
fn logo(&self) -> ui::IconName;
fn name(&self) -> &'static str;
fn empty_state_headline(&self) -> &'static str;
fn empty_state_message(&self) -> &'static str;
fn connect(
&self,
// these will go away when old_acp is fully removed
root_dir: &Path,
project: &Entity<Project>,
cx: &mut App,
) -> Task<Result<Rc<dyn AgentConnection>>>;
}
#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug)]
pub struct AgentServerSettings {
#[serde(flatten)]
command: AgentServerCommand,
impl std::fmt::Debug for AgentServerCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let filtered_env = self.env.as_ref().map(|env| {
env.iter()
.map(|(k, v)| {
(
k,
if util::redact::should_redact(k) {
"[REDACTED]"
} else {
v
},
)
})
.collect::<Vec<_>>()
});
f.debug_struct("AgentServerCommand")
.field("path", &self.path)
.field("args", &self.args)
.field("env", &filtered_env)
.finish()
}
}
pub enum AgentServerVersion {
Supported,
Unsupported {
error_message: SharedString,
upgrade_message: SharedString,
upgrade_command: String,
},
}
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)]
@@ -36,105 +88,34 @@ pub struct AgentServerCommand {
pub env: Option<HashMap<String, String>>,
}
pub struct Gemini;
pub struct AgentServerVersion {
pub current_version: SharedString,
pub supported: bool,
}
pub trait AgentServer: Send {
fn command(
&self,
impl AgentServerCommand {
pub(crate) async fn resolve(
path_bin_name: &'static str,
extra_args: &[&'static str],
settings: Option<AgentServerSettings>,
project: &Entity<Project>,
cx: &mut AsyncApp,
) -> impl Future<Output = Result<AgentServerCommand>>;
fn version(
&self,
command: &AgentServerCommand,
) -> impl Future<Output = Result<AgentServerVersion>> + Send;
}
const GEMINI_ACP_ARG: &str = "--experimental-acp";
impl AgentServer for Gemini {
async fn command(
&self,
project: &Entity<Project>,
cx: &mut AsyncApp,
) -> Result<AgentServerCommand> {
let custom_command = cx.read_global(|settings: &SettingsStore, _| {
let settings = settings.get::<AllAgentServersSettings>(None);
settings
.gemini
.as_ref()
.map(|gemini_settings| AgentServerCommand {
path: gemini_settings.command.path.clone(),
args: gemini_settings
.command
.args
.iter()
.cloned()
.chain(std::iter::once(GEMINI_ACP_ARG.into()))
.collect(),
env: gemini_settings.command.env.clone(),
})
})?;
if let Some(custom_command) = custom_command {
return Ok(custom_command);
}
if let Some(path) = find_bin_in_path("gemini", project, cx).await {
return Ok(AgentServerCommand {
path,
args: vec![GEMINI_ACP_ARG.into()],
env: None,
) -> Option<Self> {
if let Some(agent_settings) = settings {
return Some(Self {
path: agent_settings.command.path,
args: agent_settings
.command
.args
.into_iter()
.chain(extra_args.iter().map(|arg| arg.to_string()))
.collect(),
env: agent_settings.command.env,
});
} else {
find_bin_in_path(path_bin_name, project, cx)
.await
.map(|path| Self {
path,
args: extra_args.iter().map(|arg| arg.to_string()).collect(),
env: None,
})
}
let (fs, node_runtime) = project.update(cx, |project, _| {
(project.fs().clone(), project.node_runtime().cloned())
})?;
let node_runtime = node_runtime.context("gemini not found on path")?;
let directory = ::paths::agent_servers_dir().join("gemini");
fs.create_dir(&directory).await?;
node_runtime
.npm_install_packages(&directory, &[("@google/gemini-cli", "latest")])
.await?;
let path = directory.join("node_modules/.bin/gemini");
Ok(AgentServerCommand {
path,
args: vec![GEMINI_ACP_ARG.into()],
env: None,
})
}
async fn version(&self, command: &AgentServerCommand) -> Result<AgentServerVersion> {
let version_fut = util::command::new_smol_command(&command.path)
.args(command.args.iter())
.arg("--version")
.kill_on_drop(true)
.output();
let help_fut = util::command::new_smol_command(&command.path)
.args(command.args.iter())
.arg("--help")
.kill_on_drop(true)
.output();
let (version_output, help_output) = futures::future::join(version_fut, help_fut).await;
let current_version = String::from_utf8(version_output?.stdout)?.into();
let supported = String::from_utf8(help_output?.stdout)?.contains(GEMINI_ACP_ARG);
Ok(AgentServerVersion {
current_version,
supported,
})
}
}
@@ -184,48 +165,3 @@ async fn find_bin_in_path(
})
.await
}
impl std::fmt::Debug for AgentServerCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let filtered_env = self.env.as_ref().map(|env| {
env.iter()
.map(|(k, v)| {
(
k,
if util::redact::should_redact(k) {
"[REDACTED]"
} else {
v
},
)
})
.collect::<Vec<_>>()
});
f.debug_struct("AgentServerCommand")
.field("path", &self.path)
.field("args", &self.args)
.field("env", &filtered_env)
.finish()
}
}
impl settings::Settings for AllAgentServersSettings {
const KEY: Option<&'static str> = Some("agent_servers");
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
let mut settings = AllAgentServersSettings::default();
for value in sources.defaults_and_customizations() {
if value.gemini.is_some() {
settings.gemini = value.gemini.clone();
}
}
Ok(settings)
}
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
}

View File

@@ -0,0 +1,839 @@
mod mcp_server;
pub mod tools;
use collections::HashMap;
use context_server::listener::McpServerTool;
use project::Project;
use settings::SettingsStore;
use smol::process::Child;
use std::cell::RefCell;
use std::fmt::Display;
use std::path::Path;
use std::rc::Rc;
use uuid::Uuid;
use agent_client_protocol as acp;
use anyhow::{Result, anyhow};
use futures::channel::oneshot;
use futures::{AsyncBufReadExt, AsyncWriteExt};
use futures::{
AsyncRead, AsyncWrite, FutureExt, StreamExt,
channel::mpsc::{self, UnboundedReceiver, UnboundedSender},
io::BufReader,
select_biased,
};
use gpui::{App, AppContext, AsyncApp, Entity, Task, WeakEntity};
use serde::{Deserialize, Serialize};
use util::ResultExt;
use crate::claude::mcp_server::{ClaudeZedMcpServer, McpConfig};
use crate::claude::tools::ClaudeTool;
use crate::{AgentServer, AgentServerCommand, AllAgentServersSettings};
use acp_thread::{AcpThread, AgentConnection};
#[derive(Clone)]
pub struct ClaudeCode;
impl AgentServer for ClaudeCode {
fn name(&self) -> &'static str {
"Claude Code"
}
fn empty_state_headline(&self) -> &'static str {
self.name()
}
fn empty_state_message(&self) -> &'static str {
"How can I help you today?"
}
fn logo(&self) -> ui::IconName {
ui::IconName::AiClaude
}
fn connect(
&self,
_root_dir: &Path,
_project: &Entity<Project>,
_cx: &mut App,
) -> Task<Result<Rc<dyn AgentConnection>>> {
let connection = ClaudeAgentConnection {
sessions: Default::default(),
};
Task::ready(Ok(Rc::new(connection) as _))
}
}
struct ClaudeAgentConnection {
sessions: Rc<RefCell<HashMap<acp::SessionId, ClaudeAgentSession>>>,
}
impl AgentConnection for ClaudeAgentConnection {
fn name(&self) -> &'static str {
ClaudeCode.name()
}
fn new_thread(
self: Rc<Self>,
project: Entity<Project>,
cwd: &Path,
cx: &mut AsyncApp,
) -> Task<Result<Entity<AcpThread>>> {
let cwd = cwd.to_owned();
cx.spawn(async move |cx| {
let (mut thread_tx, thread_rx) = watch::channel(WeakEntity::new_invalid());
let permission_mcp_server = ClaudeZedMcpServer::new(thread_rx.clone(), cx).await?;
let mut mcp_servers = HashMap::default();
mcp_servers.insert(
mcp_server::SERVER_NAME.to_string(),
permission_mcp_server.server_config()?,
);
let mcp_config = McpConfig { mcp_servers };
let mcp_config_file = tempfile::NamedTempFile::new()?;
let (mcp_config_file, mcp_config_path) = mcp_config_file.into_parts();
let mut mcp_config_file = smol::fs::File::from(mcp_config_file);
mcp_config_file
.write_all(serde_json::to_string(&mcp_config)?.as_bytes())
.await?;
mcp_config_file.flush().await?;
let settings = cx.read_global(|settings: &SettingsStore, _| {
settings.get::<AllAgentServersSettings>(None).claude.clone()
})?;
let Some(command) =
AgentServerCommand::resolve("claude", &[], settings, &project, cx).await
else {
anyhow::bail!("Failed to find claude binary");
};
let (incoming_message_tx, mut incoming_message_rx) = mpsc::unbounded();
let (outgoing_tx, outgoing_rx) = mpsc::unbounded();
let session_id = acp::SessionId(Uuid::new_v4().to_string().into());
log::trace!("Starting session with id: {}", session_id);
cx.background_spawn({
let session_id = session_id.clone();
async move {
let mut outgoing_rx = Some(outgoing_rx);
let mut child = spawn_claude(
&command,
ClaudeSessionMode::Start,
session_id.clone(),
&mcp_config_path,
&cwd,
)
.await?;
let pid = child.id();
log::trace!("Spawned (pid: {})", pid);
ClaudeAgentSession::handle_io(
outgoing_rx.take().unwrap(),
incoming_message_tx.clone(),
child.stdin.take().unwrap(),
child.stdout.take().unwrap(),
)
.await?;
log::trace!("Stopped (pid: {})", pid);
drop(mcp_config_path);
anyhow::Ok(())
}
})
.detach();
let end_turn_tx = Rc::new(RefCell::new(None));
let handler_task = cx.spawn({
let end_turn_tx = end_turn_tx.clone();
let thread_rx = thread_rx.clone();
async move |cx| {
while let Some(message) = incoming_message_rx.next().await {
ClaudeAgentSession::handle_message(
thread_rx.clone(),
message,
end_turn_tx.clone(),
cx,
)
.await
}
}
});
let thread =
cx.new(|cx| AcpThread::new(self.clone(), project, session_id.clone(), cx))?;
thread_tx.send(thread.downgrade())?;
let session = ClaudeAgentSession {
outgoing_tx,
end_turn_tx,
_handler_task: handler_task,
_mcp_server: Some(permission_mcp_server),
};
self.sessions.borrow_mut().insert(session_id, session);
Ok(thread)
})
}
fn authenticate(&self, _cx: &mut App) -> Task<Result<()>> {
Task::ready(Err(anyhow!("Authentication not supported")))
}
fn prompt(&self, params: acp::PromptArguments, cx: &mut App) -> Task<Result<()>> {
let sessions = self.sessions.borrow();
let Some(session) = sessions.get(&params.session_id) else {
return Task::ready(Err(anyhow!(
"Attempted to send message to nonexistent session {}",
params.session_id
)));
};
let (tx, rx) = oneshot::channel();
session.end_turn_tx.borrow_mut().replace(tx);
let mut content = String::new();
for chunk in params.prompt {
match chunk {
acp::ContentBlock::Text(text_content) => {
content.push_str(&text_content.text);
}
acp::ContentBlock::ResourceLink(resource_link) => {
content.push_str(&format!("@{}", resource_link.uri));
}
acp::ContentBlock::Audio(_)
| acp::ContentBlock::Image(_)
| acp::ContentBlock::Resource(_) => {
// TODO
}
}
}
if let Err(err) = session.outgoing_tx.unbounded_send(SdkMessage::User {
message: Message {
role: Role::User,
content: Content::UntaggedText(content),
id: None,
model: None,
stop_reason: None,
stop_sequence: None,
usage: None,
},
session_id: Some(params.session_id.to_string()),
}) {
return Task::ready(Err(anyhow!(err)));
}
cx.foreground_executor().spawn(async move {
rx.await??;
Ok(())
})
}
fn cancel(&self, session_id: &acp::SessionId, _cx: &mut App) {
let sessions = self.sessions.borrow();
let Some(session) = sessions.get(&session_id) else {
log::warn!("Attempted to cancel nonexistent session {}", session_id);
return;
};
session
.outgoing_tx
.unbounded_send(SdkMessage::new_interrupt_message())
.log_err();
}
}
#[derive(Clone, Copy)]
enum ClaudeSessionMode {
Start,
#[expect(dead_code)]
Resume,
}
async fn spawn_claude(
command: &AgentServerCommand,
mode: ClaudeSessionMode,
session_id: acp::SessionId,
mcp_config_path: &Path,
root_dir: &Path,
) -> Result<Child> {
let child = util::command::new_smol_command(&command.path)
.args([
"--input-format",
"stream-json",
"--output-format",
"stream-json",
"--print",
"--verbose",
"--mcp-config",
mcp_config_path.to_string_lossy().as_ref(),
"--permission-prompt-tool",
&format!(
"mcp__{}__{}",
mcp_server::SERVER_NAME,
mcp_server::PermissionTool::NAME,
),
"--allowedTools",
&format!(
"mcp__{}__{},mcp__{}__{}",
mcp_server::SERVER_NAME,
mcp_server::EditTool::NAME,
mcp_server::SERVER_NAME,
mcp_server::ReadTool::NAME
),
"--disallowedTools",
"Read,Edit",
])
.args(match mode {
ClaudeSessionMode::Start => ["--session-id".to_string(), session_id.to_string()],
ClaudeSessionMode::Resume => ["--resume".to_string(), session_id.to_string()],
})
.args(command.args.iter().map(|arg| arg.as_str()))
.current_dir(root_dir)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::inherit())
.kill_on_drop(true)
.spawn()?;
Ok(child)
}
struct ClaudeAgentSession {
outgoing_tx: UnboundedSender<SdkMessage>,
end_turn_tx: Rc<RefCell<Option<oneshot::Sender<Result<()>>>>>,
_mcp_server: Option<ClaudeZedMcpServer>,
_handler_task: Task<()>,
}
impl ClaudeAgentSession {
async fn handle_message(
mut thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
message: SdkMessage,
end_turn_tx: Rc<RefCell<Option<oneshot::Sender<Result<()>>>>>,
cx: &mut AsyncApp,
) {
match message {
// we should only be sending these out, they don't need to be in the thread
SdkMessage::ControlRequest { .. } => {}
SdkMessage::Assistant {
message,
session_id: _,
}
| SdkMessage::User {
message,
session_id: _,
} => {
let Some(thread) = thread_rx
.recv()
.await
.log_err()
.and_then(|entity| entity.upgrade())
else {
log::error!("Received an SDK message but thread is gone");
return;
};
for chunk in message.content.chunks() {
match chunk {
ContentChunk::Text { text } | ContentChunk::UntaggedText(text) => {
thread
.update(cx, |thread, cx| {
thread.push_assistant_content_block(text.into(), false, cx)
})
.log_err();
}
ContentChunk::ToolUse { id, name, input } => {
let claude_tool = ClaudeTool::infer(&name, input);
thread
.update(cx, |thread, cx| {
if let ClaudeTool::TodoWrite(Some(params)) = claude_tool {
thread.update_plan(
acp::Plan {
entries: params
.todos
.into_iter()
.map(Into::into)
.collect(),
},
cx,
)
} else {
thread.upsert_tool_call(
claude_tool.as_acp(acp::ToolCallId(id.into())),
cx,
);
}
})
.log_err();
}
ContentChunk::ToolResult {
content,
tool_use_id,
} => {
let content = content.to_string();
thread
.update(cx, |thread, cx| {
thread.update_tool_call(
acp::ToolCallUpdate {
id: acp::ToolCallId(tool_use_id.into()),
fields: acp::ToolCallUpdateFields {
status: Some(acp::ToolCallStatus::Completed),
content: (!content.is_empty())
.then(|| vec![content.into()]),
..Default::default()
},
},
cx,
)
})
.log_err();
}
ContentChunk::Image
| ContentChunk::Document
| ContentChunk::Thinking
| ContentChunk::RedactedThinking
| ContentChunk::WebSearchToolResult => {
thread
.update(cx, |thread, cx| {
thread.push_assistant_content_block(
format!("Unsupported content: {:?}", chunk).into(),
false,
cx,
)
})
.log_err();
}
}
}
}
SdkMessage::Result {
is_error,
subtype,
result,
..
} => {
if let Some(end_turn_tx) = end_turn_tx.borrow_mut().take() {
if is_error {
end_turn_tx
.send(Err(anyhow!(
"Error: {}",
result.unwrap_or_else(|| subtype.to_string())
)))
.ok();
} else {
end_turn_tx.send(Ok(())).ok();
}
}
}
SdkMessage::System { .. } | SdkMessage::ControlResponse { .. } => {}
}
}
async fn handle_io(
mut outgoing_rx: UnboundedReceiver<SdkMessage>,
incoming_tx: UnboundedSender<SdkMessage>,
mut outgoing_bytes: impl Unpin + AsyncWrite,
incoming_bytes: impl Unpin + AsyncRead,
) -> Result<UnboundedReceiver<SdkMessage>> {
let mut output_reader = BufReader::new(incoming_bytes);
let mut outgoing_line = Vec::new();
let mut incoming_line = String::new();
loop {
select_biased! {
message = outgoing_rx.next() => {
if let Some(message) = message {
outgoing_line.clear();
serde_json::to_writer(&mut outgoing_line, &message)?;
log::trace!("send: {}", String::from_utf8_lossy(&outgoing_line));
outgoing_line.push(b'\n');
outgoing_bytes.write_all(&outgoing_line).await.ok();
} else {
break;
}
}
bytes_read = output_reader.read_line(&mut incoming_line).fuse() => {
if bytes_read? == 0 {
break
}
log::trace!("recv: {}", &incoming_line);
match serde_json::from_str::<SdkMessage>(&incoming_line) {
Ok(message) => {
incoming_tx.unbounded_send(message).log_err();
}
Err(error) => {
log::error!("failed to parse incoming message: {error}. Raw: {incoming_line}");
}
}
incoming_line.clear();
}
}
}
Ok(outgoing_rx)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Message {
role: Role,
content: Content,
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
stop_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
stop_sequence: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
usage: Option<Usage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
enum Content {
UntaggedText(String),
Chunks(Vec<ContentChunk>),
}
impl Content {
pub fn chunks(self) -> impl Iterator<Item = ContentChunk> {
match self {
Self::Chunks(chunks) => chunks.into_iter(),
Self::UntaggedText(text) => vec![ContentChunk::Text { text: text.clone() }].into_iter(),
}
}
}
impl Display for Content {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Content::UntaggedText(txt) => write!(f, "{}", txt),
Content::Chunks(chunks) => {
for chunk in chunks {
write!(f, "{}", chunk)?;
}
Ok(())
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum ContentChunk {
Text {
text: String,
},
ToolUse {
id: String,
name: String,
input: serde_json::Value,
},
ToolResult {
content: Content,
tool_use_id: String,
},
// TODO
Image,
Document,
Thinking,
RedactedThinking,
WebSearchToolResult,
#[serde(untagged)]
UntaggedText(String),
}
impl Display for ContentChunk {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ContentChunk::Text { text } => write!(f, "{}", text),
ContentChunk::UntaggedText(text) => write!(f, "{}", text),
ContentChunk::ToolResult { content, .. } => write!(f, "{}", content),
ContentChunk::Image
| ContentChunk::Document
| ContentChunk::Thinking
| ContentChunk::RedactedThinking
| ContentChunk::ToolUse { .. }
| ContentChunk::WebSearchToolResult => {
write!(f, "\n{:?}\n", &self)
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Usage {
input_tokens: u32,
cache_creation_input_tokens: u32,
cache_read_input_tokens: u32,
output_tokens: u32,
service_tier: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum Role {
System,
Assistant,
User,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct MessageParam {
role: Role,
content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum SdkMessage {
// An assistant message
Assistant {
message: Message, // from Anthropic SDK
#[serde(skip_serializing_if = "Option::is_none")]
session_id: Option<String>,
},
// A user message
User {
message: Message, // from Anthropic SDK
#[serde(skip_serializing_if = "Option::is_none")]
session_id: Option<String>,
},
// Emitted as the last message in a conversation
Result {
subtype: ResultErrorType,
duration_ms: f64,
duration_api_ms: f64,
is_error: bool,
num_turns: i32,
#[serde(skip_serializing_if = "Option::is_none")]
result: Option<String>,
session_id: String,
total_cost_usd: f64,
},
// Emitted as the first message at the start of a conversation
System {
cwd: String,
session_id: String,
tools: Vec<String>,
model: String,
mcp_servers: Vec<McpServer>,
#[serde(rename = "apiKeySource")]
api_key_source: String,
#[serde(rename = "permissionMode")]
permission_mode: PermissionMode,
},
/// Messages used to control the conversation, outside of chat messages to the model
ControlRequest {
request_id: String,
request: ControlRequest,
},
/// Response to a control request
ControlResponse { response: ControlResponse },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "subtype", rename_all = "snake_case")]
enum ControlRequest {
/// Cancel the current conversation
Interrupt,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ControlResponse {
request_id: String,
subtype: ResultErrorType,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum ResultErrorType {
Success,
ErrorMaxTurns,
ErrorDuringExecution,
}
impl Display for ResultErrorType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResultErrorType::Success => write!(f, "success"),
ResultErrorType::ErrorMaxTurns => write!(f, "error_max_turns"),
ResultErrorType::ErrorDuringExecution => write!(f, "error_during_execution"),
}
}
}
impl SdkMessage {
fn new_interrupt_message() -> Self {
use rand::Rng;
// In the Claude Code TS SDK they just generate a random 12 character string,
// `Math.random().toString(36).substring(2, 15)`
let request_id = rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(12)
.map(char::from)
.collect();
Self::ControlRequest {
request_id,
request: ControlRequest::Interrupt,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct McpServer {
name: String,
status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum PermissionMode {
Default,
AcceptEdits,
BypassPermissions,
Plan,
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use serde_json::json;
crate::common_e2e_tests!(ClaudeCode, allow_option_id = "allow");
pub fn local_command() -> AgentServerCommand {
AgentServerCommand {
path: "claude".into(),
args: vec![],
env: None,
}
}
#[test]
fn test_deserialize_content_untagged_text() {
let json = json!("Hello, world!");
let content: Content = serde_json::from_value(json).unwrap();
match content {
Content::UntaggedText(text) => assert_eq!(text, "Hello, world!"),
_ => panic!("Expected UntaggedText variant"),
}
}
#[test]
fn test_deserialize_content_chunks() {
let json = json!([
{
"type": "text",
"text": "Hello"
},
{
"type": "tool_use",
"id": "tool_123",
"name": "calculator",
"input": {"operation": "add", "a": 1, "b": 2}
}
]);
let content: Content = serde_json::from_value(json).unwrap();
match content {
Content::Chunks(chunks) => {
assert_eq!(chunks.len(), 2);
match &chunks[0] {
ContentChunk::Text { text } => assert_eq!(text, "Hello"),
_ => panic!("Expected Text chunk"),
}
match &chunks[1] {
ContentChunk::ToolUse { id, name, input } => {
assert_eq!(id, "tool_123");
assert_eq!(name, "calculator");
assert_eq!(input["operation"], "add");
assert_eq!(input["a"], 1);
assert_eq!(input["b"], 2);
}
_ => panic!("Expected ToolUse chunk"),
}
}
_ => panic!("Expected Chunks variant"),
}
}
#[test]
fn test_deserialize_tool_result_untagged_text() {
let json = json!({
"type": "tool_result",
"content": "Result content",
"tool_use_id": "tool_456"
});
let chunk: ContentChunk = serde_json::from_value(json).unwrap();
match chunk {
ContentChunk::ToolResult {
content,
tool_use_id,
} => {
match content {
Content::UntaggedText(text) => assert_eq!(text, "Result content"),
_ => panic!("Expected UntaggedText content"),
}
assert_eq!(tool_use_id, "tool_456");
}
_ => panic!("Expected ToolResult variant"),
}
}
#[test]
fn test_deserialize_tool_result_chunks() {
let json = json!({
"type": "tool_result",
"content": [
{
"type": "text",
"text": "Processing complete"
},
{
"type": "text",
"text": "Result: 42"
}
],
"tool_use_id": "tool_789"
});
let chunk: ContentChunk = serde_json::from_value(json).unwrap();
match chunk {
ContentChunk::ToolResult {
content,
tool_use_id,
} => {
match content {
Content::Chunks(chunks) => {
assert_eq!(chunks.len(), 2);
match &chunks[0] {
ContentChunk::Text { text } => assert_eq!(text, "Processing complete"),
_ => panic!("Expected Text chunk"),
}
match &chunks[1] {
ContentChunk::Text { text } => assert_eq!(text, "Result: 42"),
_ => panic!("Expected Text chunk"),
}
}
_ => panic!("Expected Chunks content"),
}
assert_eq!(tool_use_id, "tool_789");
}
_ => panic!("Expected ToolResult variant"),
}
}
}

View File

@@ -0,0 +1,302 @@
use std::path::PathBuf;
use crate::claude::tools::{ClaudeTool, EditToolParams, ReadToolParams};
use acp_thread::AcpThread;
use agent_client_protocol as acp;
use anyhow::{Context, Result};
use collections::HashMap;
use context_server::listener::{McpServerTool, ToolResponse};
use context_server::types::{
Implementation, InitializeParams, InitializeResponse, ProtocolVersion, ServerCapabilities,
ToolAnnotations, ToolResponseContent, ToolsCapabilities, requests,
};
use gpui::{App, AsyncApp, Task, WeakEntity};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub struct ClaudeZedMcpServer {
server: context_server::listener::McpServer,
}
pub const SERVER_NAME: &str = "zed";
impl ClaudeZedMcpServer {
pub async fn new(
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
cx: &AsyncApp,
) -> Result<Self> {
let mut mcp_server = context_server::listener::McpServer::new(cx).await?;
mcp_server.handle_request::<requests::Initialize>(Self::handle_initialize);
mcp_server.add_tool(PermissionTool {
thread_rx: thread_rx.clone(),
});
mcp_server.add_tool(ReadTool {
thread_rx: thread_rx.clone(),
});
mcp_server.add_tool(EditTool {
thread_rx: thread_rx.clone(),
});
Ok(Self { server: mcp_server })
}
pub fn server_config(&self) -> Result<McpServerConfig> {
#[cfg(not(test))]
let zed_path = std::env::current_exe()
.context("finding current executable path for use in mcp_server")?;
#[cfg(test)]
let zed_path = crate::e2e_tests::get_zed_path();
Ok(McpServerConfig {
command: zed_path,
args: vec![
"--nc".into(),
self.server.socket_path().display().to_string(),
],
env: None,
})
}
fn handle_initialize(_: InitializeParams, cx: &App) -> Task<Result<InitializeResponse>> {
cx.foreground_executor().spawn(async move {
Ok(InitializeResponse {
protocol_version: ProtocolVersion("2025-06-18".into()),
capabilities: ServerCapabilities {
experimental: None,
logging: None,
completions: None,
prompts: None,
resources: None,
tools: Some(ToolsCapabilities {
list_changed: Some(false),
}),
},
server_info: Implementation {
name: SERVER_NAME.into(),
version: "0.1.0".into(),
},
meta: None,
})
})
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct McpConfig {
pub mcp_servers: HashMap<String, McpServerConfig>,
}
#[derive(Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct McpServerConfig {
pub command: PathBuf,
pub args: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub env: Option<HashMap<String, String>>,
}
// Tools
#[derive(Clone)]
pub struct PermissionTool {
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct PermissionToolParams {
tool_name: String,
input: serde_json::Value,
tool_use_id: Option<String>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PermissionToolResponse {
behavior: PermissionToolBehavior,
updated_input: serde_json::Value,
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
enum PermissionToolBehavior {
Allow,
Deny,
}
impl McpServerTool for PermissionTool {
type Input = PermissionToolParams;
type Output = ();
const NAME: &'static str = "Confirmation";
fn description(&self) -> &'static str {
"Request permission for tool calls"
}
async fn run(
&self,
input: Self::Input,
cx: &mut AsyncApp,
) -> Result<ToolResponse<Self::Output>> {
let mut thread_rx = self.thread_rx.clone();
let Some(thread) = thread_rx.recv().await?.upgrade() else {
anyhow::bail!("Thread closed");
};
let claude_tool = ClaudeTool::infer(&input.tool_name, input.input.clone());
let tool_call_id = acp::ToolCallId(input.tool_use_id.context("Tool ID required")?.into());
let allow_option_id = acp::PermissionOptionId("allow".into());
let reject_option_id = acp::PermissionOptionId("reject".into());
let chosen_option = thread
.update(cx, |thread, cx| {
thread.request_tool_call_permission(
claude_tool.as_acp(tool_call_id),
vec![
acp::PermissionOption {
id: allow_option_id.clone(),
label: "Allow".into(),
kind: acp::PermissionOptionKind::AllowOnce,
},
acp::PermissionOption {
id: reject_option_id.clone(),
label: "Reject".into(),
kind: acp::PermissionOptionKind::RejectOnce,
},
],
cx,
)
})?
.await?;
let response = if chosen_option == allow_option_id {
PermissionToolResponse {
behavior: PermissionToolBehavior::Allow,
updated_input: input.input,
}
} else {
debug_assert_eq!(chosen_option, reject_option_id);
PermissionToolResponse {
behavior: PermissionToolBehavior::Deny,
updated_input: input.input,
}
};
Ok(ToolResponse {
content: vec![ToolResponseContent::Text {
text: serde_json::to_string(&response)?,
}],
structured_content: (),
})
}
}
#[derive(Clone)]
pub struct ReadTool {
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
}
impl McpServerTool for ReadTool {
type Input = ReadToolParams;
type Output = ();
const NAME: &'static str = "Read";
fn description(&self) -> &'static str {
"Read the contents of a file. In sessions with mcp__zed__Read always use it instead of Read as it contains the most up-to-date contents."
}
fn annotations(&self) -> ToolAnnotations {
ToolAnnotations {
title: Some("Read file".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
open_world_hint: Some(false),
idempotent_hint: None,
}
}
async fn run(
&self,
input: Self::Input,
cx: &mut AsyncApp,
) -> Result<ToolResponse<Self::Output>> {
let mut thread_rx = self.thread_rx.clone();
let Some(thread) = thread_rx.recv().await?.upgrade() else {
anyhow::bail!("Thread closed");
};
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(input.abs_path, input.offset, input.limit, false, cx)
})?
.await?;
Ok(ToolResponse {
content: vec![ToolResponseContent::Text { text: content }],
structured_content: (),
})
}
}
#[derive(Clone)]
pub struct EditTool {
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
}
impl McpServerTool for EditTool {
type Input = EditToolParams;
type Output = ();
const NAME: &'static str = "Edit";
fn description(&self) -> &'static str {
"Edits a file. In sessions with mcp__zed__Edit always use it instead of Edit as it will show the diff to the user better."
}
fn annotations(&self) -> ToolAnnotations {
ToolAnnotations {
title: Some("Edit file".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(false),
open_world_hint: Some(false),
idempotent_hint: Some(false),
}
}
async fn run(
&self,
input: Self::Input,
cx: &mut AsyncApp,
) -> Result<ToolResponse<Self::Output>> {
let mut thread_rx = self.thread_rx.clone();
let Some(thread) = thread_rx.recv().await?.upgrade() else {
anyhow::bail!("Thread closed");
};
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(input.abs_path.clone(), None, None, true, cx)
})?
.await?;
let new_content = content.replace(&input.old_text, &input.new_text);
if new_content == content {
return Err(anyhow::anyhow!("The old_text was not found in the content"));
}
thread
.update(cx, |thread, cx| {
thread.write_text_file(input.abs_path, new_content, cx)
})?
.await?;
Ok(ToolResponse {
content: vec![],
structured_content: (),
})
}
}

View File

@@ -0,0 +1,675 @@
use std::path::PathBuf;
use agent_client_protocol as acp;
use itertools::Itertools;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use util::ResultExt;
pub enum ClaudeTool {
Task(Option<TaskToolParams>),
NotebookRead(Option<NotebookReadToolParams>),
NotebookEdit(Option<NotebookEditToolParams>),
Edit(Option<EditToolParams>),
MultiEdit(Option<MultiEditToolParams>),
ReadFile(Option<ReadToolParams>),
Write(Option<WriteToolParams>),
Ls(Option<LsToolParams>),
Glob(Option<GlobToolParams>),
Grep(Option<GrepToolParams>),
Terminal(Option<BashToolParams>),
WebFetch(Option<WebFetchToolParams>),
WebSearch(Option<WebSearchToolParams>),
TodoWrite(Option<TodoWriteToolParams>),
ExitPlanMode(Option<ExitPlanModeToolParams>),
Other {
name: String,
input: serde_json::Value,
},
}
impl ClaudeTool {
pub fn infer(tool_name: &str, input: serde_json::Value) -> Self {
match tool_name {
// Known tools
"mcp__zed__Read" => Self::ReadFile(serde_json::from_value(input).log_err()),
"mcp__zed__Edit" => Self::Edit(serde_json::from_value(input).log_err()),
"MultiEdit" => Self::MultiEdit(serde_json::from_value(input).log_err()),
"Write" => Self::Write(serde_json::from_value(input).log_err()),
"LS" => Self::Ls(serde_json::from_value(input).log_err()),
"Glob" => Self::Glob(serde_json::from_value(input).log_err()),
"Grep" => Self::Grep(serde_json::from_value(input).log_err()),
"Bash" => Self::Terminal(serde_json::from_value(input).log_err()),
"WebFetch" => Self::WebFetch(serde_json::from_value(input).log_err()),
"WebSearch" => Self::WebSearch(serde_json::from_value(input).log_err()),
"TodoWrite" => Self::TodoWrite(serde_json::from_value(input).log_err()),
"exit_plan_mode" => Self::ExitPlanMode(serde_json::from_value(input).log_err()),
"Task" => Self::Task(serde_json::from_value(input).log_err()),
"NotebookRead" => Self::NotebookRead(serde_json::from_value(input).log_err()),
"NotebookEdit" => Self::NotebookEdit(serde_json::from_value(input).log_err()),
// Inferred from name
_ => {
let tool_name = tool_name.to_lowercase();
if tool_name.contains("edit") || tool_name.contains("write") {
Self::Edit(None)
} else if tool_name.contains("terminal") {
Self::Terminal(None)
} else {
Self::Other {
name: tool_name.to_string(),
input,
}
}
}
}
}
pub fn label(&self) -> String {
match &self {
Self::Task(Some(params)) => params.description.clone(),
Self::Task(None) => "Task".into(),
Self::NotebookRead(Some(params)) => {
format!("Read Notebook {}", params.notebook_path.display())
}
Self::NotebookRead(None) => "Read Notebook".into(),
Self::NotebookEdit(Some(params)) => {
format!("Edit Notebook {}", params.notebook_path.display())
}
Self::NotebookEdit(None) => "Edit Notebook".into(),
Self::Terminal(Some(params)) => format!("`{}`", params.command),
Self::Terminal(None) => "Terminal".into(),
Self::ReadFile(_) => "Read File".into(),
Self::Ls(Some(params)) => {
format!("List Directory {}", params.path.display())
}
Self::Ls(None) => "List Directory".into(),
Self::Edit(Some(params)) => {
format!("Edit {}", params.abs_path.display())
}
Self::Edit(None) => "Edit".into(),
Self::MultiEdit(Some(params)) => {
format!("Multi Edit {}", params.file_path.display())
}
Self::MultiEdit(None) => "Multi Edit".into(),
Self::Write(Some(params)) => {
format!("Write {}", params.file_path.display())
}
Self::Write(None) => "Write".into(),
Self::Glob(Some(params)) => {
format!("Glob `{params}`")
}
Self::Glob(None) => "Glob".into(),
Self::Grep(Some(params)) => format!("`{params}`"),
Self::Grep(None) => "Grep".into(),
Self::WebFetch(Some(params)) => format!("Fetch {}", params.url),
Self::WebFetch(None) => "Fetch".into(),
Self::WebSearch(Some(params)) => format!("Web Search: {}", params),
Self::WebSearch(None) => "Web Search".into(),
Self::TodoWrite(Some(params)) => format!(
"Update TODOs: {}",
params.todos.iter().map(|todo| &todo.content).join(", ")
),
Self::TodoWrite(None) => "Update TODOs".into(),
Self::ExitPlanMode(_) => "Exit Plan Mode".into(),
Self::Other { name, .. } => name.clone(),
}
}
pub fn content(&self) -> Vec<acp::ToolCallContent> {
match &self {
Self::Other { input, .. } => vec![
format!(
"```json\n{}```",
serde_json::to_string_pretty(&input).unwrap_or("{}".to_string())
)
.into(),
],
Self::Task(Some(params)) => vec![params.prompt.clone().into()],
Self::NotebookRead(Some(params)) => {
vec![params.notebook_path.display().to_string().into()]
}
Self::NotebookEdit(Some(params)) => vec![params.new_source.clone().into()],
Self::Terminal(Some(params)) => vec![
format!(
"`{}`\n\n{}",
params.command,
params.description.as_deref().unwrap_or_default()
)
.into(),
],
Self::ReadFile(Some(params)) => vec![params.abs_path.display().to_string().into()],
Self::Ls(Some(params)) => vec![params.path.display().to_string().into()],
Self::Glob(Some(params)) => vec![params.to_string().into()],
Self::Grep(Some(params)) => vec![format!("`{params}`").into()],
Self::WebFetch(Some(params)) => vec![params.prompt.clone().into()],
Self::WebSearch(Some(params)) => vec![params.to_string().into()],
Self::TodoWrite(Some(params)) => vec![
params
.todos
.iter()
.map(|todo| {
format!(
"- {} {}: {}",
match todo.status {
TodoStatus::Completed => "",
TodoStatus::InProgress => "🚧",
TodoStatus::Pending => "",
},
todo.priority,
todo.content
)
})
.join("\n")
.into(),
],
Self::ExitPlanMode(Some(params)) => vec![params.plan.clone().into()],
Self::Edit(Some(params)) => vec![acp::ToolCallContent::Diff {
diff: acp::Diff {
path: params.abs_path.clone(),
old_text: Some(params.old_text.clone()),
new_text: params.new_text.clone(),
},
}],
Self::Write(Some(params)) => vec![acp::ToolCallContent::Diff {
diff: acp::Diff {
path: params.file_path.clone(),
old_text: None,
new_text: params.content.clone(),
},
}],
Self::MultiEdit(Some(params)) => {
// todo: show multiple edits in a multibuffer?
params
.edits
.first()
.map(|edit| {
vec![acp::ToolCallContent::Diff {
diff: acp::Diff {
path: params.file_path.clone(),
old_text: Some(edit.old_string.clone()),
new_text: edit.new_string.clone(),
},
}]
})
.unwrap_or_default()
}
Self::Task(None)
| Self::NotebookRead(None)
| Self::NotebookEdit(None)
| Self::Terminal(None)
| Self::ReadFile(None)
| Self::Ls(None)
| Self::Glob(None)
| Self::Grep(None)
| Self::WebFetch(None)
| Self::WebSearch(None)
| Self::TodoWrite(None)
| Self::ExitPlanMode(None)
| Self::Edit(None)
| Self::Write(None)
| Self::MultiEdit(None) => vec![],
}
}
pub fn kind(&self) -> acp::ToolKind {
match self {
Self::Task(_) => acp::ToolKind::Think,
Self::NotebookRead(_) => acp::ToolKind::Read,
Self::NotebookEdit(_) => acp::ToolKind::Edit,
Self::Edit(_) => acp::ToolKind::Edit,
Self::MultiEdit(_) => acp::ToolKind::Edit,
Self::Write(_) => acp::ToolKind::Edit,
Self::ReadFile(_) => acp::ToolKind::Read,
Self::Ls(_) => acp::ToolKind::Search,
Self::Glob(_) => acp::ToolKind::Search,
Self::Grep(_) => acp::ToolKind::Search,
Self::Terminal(_) => acp::ToolKind::Execute,
Self::WebSearch(_) => acp::ToolKind::Search,
Self::WebFetch(_) => acp::ToolKind::Fetch,
Self::TodoWrite(_) => acp::ToolKind::Think,
Self::ExitPlanMode(_) => acp::ToolKind::Think,
Self::Other { .. } => acp::ToolKind::Other,
}
}
pub fn locations(&self) -> Vec<acp::ToolCallLocation> {
match &self {
Self::Edit(Some(EditToolParams { abs_path, .. })) => vec![acp::ToolCallLocation {
path: abs_path.clone(),
line: None,
}],
Self::MultiEdit(Some(MultiEditToolParams { file_path, .. })) => {
vec![acp::ToolCallLocation {
path: file_path.clone(),
line: None,
}]
}
Self::Write(Some(WriteToolParams { file_path, .. })) => {
vec![acp::ToolCallLocation {
path: file_path.clone(),
line: None,
}]
}
Self::ReadFile(Some(ReadToolParams {
abs_path, offset, ..
})) => vec![acp::ToolCallLocation {
path: abs_path.clone(),
line: *offset,
}],
Self::NotebookRead(Some(NotebookReadToolParams { notebook_path, .. })) => {
vec![acp::ToolCallLocation {
path: notebook_path.clone(),
line: None,
}]
}
Self::NotebookEdit(Some(NotebookEditToolParams { notebook_path, .. })) => {
vec![acp::ToolCallLocation {
path: notebook_path.clone(),
line: None,
}]
}
Self::Glob(Some(GlobToolParams {
path: Some(path), ..
})) => vec![acp::ToolCallLocation {
path: path.clone(),
line: None,
}],
Self::Ls(Some(LsToolParams { path, .. })) => vec![acp::ToolCallLocation {
path: path.clone(),
line: None,
}],
Self::Grep(Some(GrepToolParams {
path: Some(path), ..
})) => vec![acp::ToolCallLocation {
path: PathBuf::from(path),
line: None,
}],
Self::Task(_)
| Self::NotebookRead(None)
| Self::NotebookEdit(None)
| Self::Edit(None)
| Self::MultiEdit(None)
| Self::Write(None)
| Self::ReadFile(None)
| Self::Ls(None)
| Self::Glob(_)
| Self::Grep(_)
| Self::Terminal(_)
| Self::WebFetch(_)
| Self::WebSearch(_)
| Self::TodoWrite(_)
| Self::ExitPlanMode(_)
| Self::Other { .. } => vec![],
}
}
pub fn as_acp(&self, id: acp::ToolCallId) -> acp::ToolCall {
acp::ToolCall {
id,
kind: self.kind(),
status: acp::ToolCallStatus::InProgress,
label: self.label(),
content: self.content(),
locations: self.locations(),
raw_input: None,
}
}
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct EditToolParams {
/// The absolute path to the file to read.
pub abs_path: PathBuf,
/// The old text to replace (must be unique in the file)
pub old_text: String,
/// The new text.
pub new_text: String,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct ReadToolParams {
/// The absolute path to the file to read.
pub abs_path: PathBuf,
/// Which line to start reading from. Omit to start from the beginning.
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<u32>,
/// How many lines to read. Omit for the whole file.
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct WriteToolParams {
/// Absolute path for new file
pub file_path: PathBuf,
/// File content
pub content: String,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct BashToolParams {
/// Shell command to execute
pub command: String,
/// 5-10 word description of what command does
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// Timeout in ms (max 600000ms/10min, default 120000ms)
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<u32>,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct GlobToolParams {
/// Glob pattern like **/*.js or src/**/*.ts
pub pattern: String,
/// Directory to search in (omit for current directory)
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<PathBuf>,
}
impl std::fmt::Display for GlobToolParams {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(path) = &self.path {
write!(f, "{}", path.display())?;
}
write!(f, "{}", self.pattern)
}
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct LsToolParams {
/// Absolute path to directory
pub path: PathBuf,
/// Array of glob patterns to ignore
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ignore: Vec<String>,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct GrepToolParams {
/// Regex pattern to search for
pub pattern: String,
/// File/directory to search (defaults to current directory)
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
/// "content" (shows lines), "files_with_matches" (default), "count"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_mode: Option<GrepOutputMode>,
/// Filter files with glob pattern like "*.js"
#[serde(skip_serializing_if = "Option::is_none")]
pub glob: Option<String>,
/// File type filter like "js", "py", "rust"
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub file_type: Option<String>,
/// Case insensitive search
#[serde(rename = "-i", default, skip_serializing_if = "is_false")]
pub case_insensitive: bool,
/// Show line numbers (content mode only)
#[serde(rename = "-n", default, skip_serializing_if = "is_false")]
pub line_numbers: bool,
/// Lines after match (content mode only)
#[serde(rename = "-A", skip_serializing_if = "Option::is_none")]
pub after_context: Option<u32>,
/// Lines before match (content mode only)
#[serde(rename = "-B", skip_serializing_if = "Option::is_none")]
pub before_context: Option<u32>,
/// Lines before and after match (content mode only)
#[serde(rename = "-C", skip_serializing_if = "Option::is_none")]
pub context: Option<u32>,
/// Enable multiline/cross-line matching
#[serde(default, skip_serializing_if = "is_false")]
pub multiline: bool,
/// Limit output to first N results
#[serde(skip_serializing_if = "Option::is_none")]
pub head_limit: Option<u32>,
}
impl std::fmt::Display for GrepToolParams {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "grep")?;
// Boolean flags
if self.case_insensitive {
write!(f, " -i")?;
}
if self.line_numbers {
write!(f, " -n")?;
}
// Context options
if let Some(after) = self.after_context {
write!(f, " -A {}", after)?;
}
if let Some(before) = self.before_context {
write!(f, " -B {}", before)?;
}
if let Some(context) = self.context {
write!(f, " -C {}", context)?;
}
// Output mode
if let Some(mode) = &self.output_mode {
match mode {
GrepOutputMode::FilesWithMatches => write!(f, " -l")?,
GrepOutputMode::Count => write!(f, " -c")?,
GrepOutputMode::Content => {} // Default mode
}
}
// Head limit
if let Some(limit) = self.head_limit {
write!(f, " | head -{}", limit)?;
}
// Glob pattern
if let Some(glob) = &self.glob {
write!(f, " --include=\"{}\"", glob)?;
}
// File type
if let Some(file_type) = &self.file_type {
write!(f, " --type={}", file_type)?;
}
// Multiline
if self.multiline {
write!(f, " -P")?; // Perl-compatible regex for multiline
}
// Pattern (escaped if contains special characters)
write!(f, " \"{}\"", self.pattern)?;
// Path
if let Some(path) = &self.path {
write!(f, " {}", path)?;
}
Ok(())
}
}
#[derive(Deserialize, Serialize, JsonSchema, strum::Display, Debug)]
#[serde(rename_all = "snake_case")]
pub enum TodoPriority {
High,
Medium,
Low,
}
impl Into<acp::PlanEntryPriority> for TodoPriority {
fn into(self) -> acp::PlanEntryPriority {
match self {
TodoPriority::High => acp::PlanEntryPriority::High,
TodoPriority::Medium => acp::PlanEntryPriority::Medium,
TodoPriority::Low => acp::PlanEntryPriority::Low,
}
}
}
#[derive(Deserialize, Serialize, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum TodoStatus {
Pending,
InProgress,
Completed,
}
impl Into<acp::PlanEntryStatus> for TodoStatus {
fn into(self) -> acp::PlanEntryStatus {
match self {
TodoStatus::Pending => acp::PlanEntryStatus::Pending,
TodoStatus::InProgress => acp::PlanEntryStatus::InProgress,
TodoStatus::Completed => acp::PlanEntryStatus::Completed,
}
}
}
#[derive(Deserialize, Serialize, JsonSchema, Debug)]
pub struct Todo {
/// Unique identifier
pub id: String,
/// Task description
pub content: String,
/// Priority level of the todo
pub priority: TodoPriority,
/// Current status of the todo
pub status: TodoStatus,
}
impl Into<acp::PlanEntry> for Todo {
fn into(self) -> acp::PlanEntry {
acp::PlanEntry {
content: self.content,
priority: self.priority.into(),
status: self.status.into(),
}
}
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct TodoWriteToolParams {
pub todos: Vec<Todo>,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct ExitPlanModeToolParams {
/// Implementation plan in markdown format
pub plan: String,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct TaskToolParams {
/// Short 3-5 word description of task
pub description: String,
/// Detailed task for agent to perform
pub prompt: String,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct NotebookReadToolParams {
/// Absolute path to .ipynb file
pub notebook_path: PathBuf,
/// Specific cell ID to read
#[serde(skip_serializing_if = "Option::is_none")]
pub cell_id: Option<String>,
}
#[derive(Deserialize, Serialize, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum CellType {
Code,
Markdown,
}
#[derive(Deserialize, Serialize, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum EditMode {
Replace,
Insert,
Delete,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct NotebookEditToolParams {
/// Absolute path to .ipynb file
pub notebook_path: PathBuf,
/// New cell content
pub new_source: String,
/// Cell ID to edit
#[serde(skip_serializing_if = "Option::is_none")]
pub cell_id: Option<String>,
/// Type of cell (code or markdown)
#[serde(skip_serializing_if = "Option::is_none")]
pub cell_type: Option<CellType>,
/// Edit operation mode
#[serde(skip_serializing_if = "Option::is_none")]
pub edit_mode: Option<EditMode>,
}
#[derive(Deserialize, Serialize, JsonSchema, Debug)]
pub struct MultiEditItem {
/// The text to search for and replace
pub old_string: String,
/// The replacement text
pub new_string: String,
/// Whether to replace all occurrences or just the first
#[serde(default, skip_serializing_if = "is_false")]
pub replace_all: bool,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct MultiEditToolParams {
/// Absolute path to file
pub file_path: PathBuf,
/// List of edits to apply
pub edits: Vec<MultiEditItem>,
}
fn is_false(v: &bool) -> bool {
!*v
}
#[derive(Deserialize, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum GrepOutputMode {
Content,
FilesWithMatches,
Count,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct WebFetchToolParams {
/// Valid URL to fetch
#[serde(rename = "url")]
pub url: String,
/// What to extract from content
pub prompt: String,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct WebSearchToolParams {
/// Search query (min 2 chars)
pub query: String,
/// Only include these domains
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_domains: Vec<String>,
/// Exclude these domains
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blocked_domains: Vec<String>,
}
impl std::fmt::Display for WebSearchToolParams {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{}\"", self.query)?;
if !self.allowed_domains.is_empty() {
write!(f, " (allowed: {})", self.allowed_domains.join(", "))?;
}
if !self.blocked_domains.is_empty() {
write!(f, " (blocked: {})", self.blocked_domains.join(", "))?;
}
Ok(())
}
}

View File

@@ -0,0 +1,319 @@
use agent_client_protocol as acp;
use anyhow::anyhow;
use collections::HashMap;
use context_server::listener::McpServerTool;
use context_server::types::requests;
use context_server::{ContextServer, ContextServerCommand, ContextServerId};
use futures::channel::{mpsc, oneshot};
use project::Project;
use settings::SettingsStore;
use smol::stream::StreamExt as _;
use std::cell::RefCell;
use std::rc::Rc;
use std::{path::Path, sync::Arc};
use util::ResultExt;
use anyhow::{Context, Result};
use gpui::{App, AppContext as _, AsyncApp, Entity, Task, WeakEntity};
use crate::mcp_server::ZedMcpServer;
use crate::{AgentServer, AgentServerCommand, AllAgentServersSettings, mcp_server};
use acp_thread::{AcpThread, AgentConnection};
#[derive(Clone)]
pub struct Codex;
impl AgentServer for Codex {
fn name(&self) -> &'static str {
"Codex"
}
fn empty_state_headline(&self) -> &'static str {
"Welcome to Codex"
}
fn empty_state_message(&self) -> &'static str {
"What can I help with?"
}
fn logo(&self) -> ui::IconName {
ui::IconName::AiOpenAi
}
fn connect(
&self,
_root_dir: &Path,
project: &Entity<Project>,
cx: &mut App,
) -> Task<Result<Rc<dyn AgentConnection>>> {
let project = project.clone();
let working_directory = project.read(cx).active_project_directory(cx);
cx.spawn(async move |cx| {
let settings = cx.read_global(|settings: &SettingsStore, _| {
settings.get::<AllAgentServersSettings>(None).codex.clone()
})?;
let Some(command) =
AgentServerCommand::resolve("codex", &["mcp"], settings, &project, cx).await
else {
anyhow::bail!("Failed to find codex binary");
};
let client: Arc<ContextServer> = ContextServer::stdio(
ContextServerId("codex-mcp-server".into()),
ContextServerCommand {
path: command.path,
args: command.args,
env: command.env,
},
working_directory,
)
.into();
ContextServer::start(client.clone(), cx).await?;
let (notification_tx, mut notification_rx) = mpsc::unbounded();
client
.client()
.context("Failed to subscribe")?
.on_notification(acp::SESSION_UPDATE_METHOD_NAME, {
move |notification, _cx| {
let notification_tx = notification_tx.clone();
log::trace!(
"ACP Notification: {}",
serde_json::to_string_pretty(&notification).unwrap()
);
if let Some(notification) =
serde_json::from_value::<acp::SessionNotification>(notification)
.log_err()
{
notification_tx.unbounded_send(notification).ok();
}
}
});
let sessions = Rc::new(RefCell::new(HashMap::default()));
let notification_handler_task = cx.spawn({
let sessions = sessions.clone();
async move |cx| {
while let Some(notification) = notification_rx.next().await {
CodexConnection::handle_session_notification(
notification,
sessions.clone(),
cx,
)
}
}
});
let connection = CodexConnection {
client,
sessions,
_notification_handler_task: notification_handler_task,
};
Ok(Rc::new(connection) as _)
})
}
}
struct CodexConnection {
client: Arc<context_server::ContextServer>,
sessions: Rc<RefCell<HashMap<acp::SessionId, CodexSession>>>,
_notification_handler_task: Task<()>,
}
struct CodexSession {
thread: WeakEntity<AcpThread>,
cancel_tx: Option<oneshot::Sender<()>>,
_mcp_server: ZedMcpServer,
}
impl AgentConnection for CodexConnection {
fn name(&self) -> &'static str {
"Codex"
}
fn new_thread(
self: Rc<Self>,
project: Entity<Project>,
cwd: &Path,
cx: &mut AsyncApp,
) -> Task<Result<Entity<AcpThread>>> {
let client = self.client.client();
let sessions = self.sessions.clone();
let cwd = cwd.to_path_buf();
cx.spawn(async move |cx| {
let client = client.context("MCP server is not initialized yet")?;
let (mut thread_tx, thread_rx) = watch::channel(WeakEntity::new_invalid());
let mcp_server = ZedMcpServer::new(thread_rx, cx).await?;
let response = client
.request::<requests::CallTool>(context_server::types::CallToolParams {
name: acp::NEW_SESSION_TOOL_NAME.into(),
arguments: Some(serde_json::to_value(acp::NewSessionArguments {
mcp_servers: [(
mcp_server::SERVER_NAME.to_string(),
mcp_server.server_config()?,
)]
.into(),
client_tools: acp::ClientTools {
request_permission: Some(acp::McpToolId {
mcp_server: mcp_server::SERVER_NAME.into(),
tool_name: mcp_server::RequestPermissionTool::NAME.into(),
}),
read_text_file: Some(acp::McpToolId {
mcp_server: mcp_server::SERVER_NAME.into(),
tool_name: mcp_server::ReadTextFileTool::NAME.into(),
}),
write_text_file: Some(acp::McpToolId {
mcp_server: mcp_server::SERVER_NAME.into(),
tool_name: mcp_server::WriteTextFileTool::NAME.into(),
}),
},
cwd,
})?),
meta: None,
})
.await?;
if response.is_error.unwrap_or_default() {
return Err(anyhow!(response.text_contents()));
}
let result = serde_json::from_value::<acp::NewSessionOutput>(
response.structured_content.context("Empty response")?,
)?;
let thread =
cx.new(|cx| AcpThread::new(self.clone(), project, result.session_id.clone(), cx))?;
thread_tx.send(thread.downgrade())?;
let session = CodexSession {
thread: thread.downgrade(),
cancel_tx: None,
_mcp_server: mcp_server,
};
sessions.borrow_mut().insert(result.session_id, session);
Ok(thread)
})
}
fn authenticate(&self, _cx: &mut App) -> Task<Result<()>> {
Task::ready(Err(anyhow!("Authentication not supported")))
}
fn prompt(
&self,
params: agent_client_protocol::PromptArguments,
cx: &mut App,
) -> Task<Result<()>> {
let client = self.client.client();
let sessions = self.sessions.clone();
cx.foreground_executor().spawn(async move {
let client = client.context("MCP server is not initialized yet")?;
let (new_cancel_tx, cancel_rx) = oneshot::channel();
{
let mut sessions = sessions.borrow_mut();
let session = sessions
.get_mut(&params.session_id)
.context("Session not found")?;
session.cancel_tx.replace(new_cancel_tx);
}
let result = client
.request_with::<requests::CallTool>(
context_server::types::CallToolParams {
name: acp::PROMPT_TOOL_NAME.into(),
arguments: Some(serde_json::to_value(params)?),
meta: None,
},
Some(cancel_rx),
None,
)
.await;
if let Err(err) = &result
&& err.is::<context_server::client::RequestCanceled>()
{
return Ok(());
}
let response = result?;
if response.is_error.unwrap_or_default() {
return Err(anyhow!(response.text_contents()));
}
Ok(())
})
}
fn cancel(&self, session_id: &agent_client_protocol::SessionId, _cx: &mut App) {
let mut sessions = self.sessions.borrow_mut();
if let Some(cancel_tx) = sessions
.get_mut(session_id)
.and_then(|session| session.cancel_tx.take())
{
cancel_tx.send(()).ok();
}
}
}
impl CodexConnection {
pub fn handle_session_notification(
notification: acp::SessionNotification,
threads: Rc<RefCell<HashMap<acp::SessionId, CodexSession>>>,
cx: &mut AsyncApp,
) {
let threads = threads.borrow();
let Some(thread) = threads
.get(&notification.session_id)
.and_then(|session| session.thread.upgrade())
else {
log::error!(
"Thread not found for session ID: {}",
notification.session_id
);
return;
};
thread
.update(cx, |thread, cx| {
thread.handle_session_update(notification.update, cx)
})
.log_err();
}
}
impl Drop for CodexConnection {
fn drop(&mut self) {
self.client.stop().log_err();
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::AgentServerCommand;
use std::path::Path;
crate::common_e2e_tests!(Codex, allow_option_id = "approve");
pub fn local_command() -> AgentServerCommand {
let cli_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../../codex/codex-rs/target/debug/codex");
AgentServerCommand {
path: cli_path,
args: vec![],
env: None,
}
}
}

View File

@@ -0,0 +1,459 @@
use std::{
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};
use crate::{AgentServer, AgentServerSettings, AllAgentServersSettings};
use acp_thread::{AcpThread, AgentThreadEntry, ToolCall, ToolCallStatus};
use agent_client_protocol as acp;
use futures::{FutureExt, StreamExt, channel::mpsc, select};
use gpui::{Entity, TestAppContext};
use indoc::indoc;
use project::{FakeFs, Project};
use settings::{Settings, SettingsStore};
use util::path;
pub async fn test_basic(server: impl AgentServer + 'static, cx: &mut TestAppContext) {
let fs = init_test(cx).await;
let project = Project::test(fs, [], cx).await;
let thread = new_test_thread(server, project.clone(), "/private/tmp", cx).await;
thread
.update(cx, |thread, cx| thread.send_raw("Hello from Zed!", cx))
.await
.unwrap();
thread.read_with(cx, |thread, _| {
assert!(
thread.entries().len() >= 2,
"Expected at least 2 entries. Got: {:?}",
thread.entries()
);
assert!(matches!(
thread.entries()[0],
AgentThreadEntry::UserMessage(_)
));
assert!(matches!(
thread.entries()[1],
AgentThreadEntry::AssistantMessage(_)
));
});
}
pub async fn test_path_mentions(server: impl AgentServer + 'static, cx: &mut TestAppContext) {
let _fs = init_test(cx).await;
let tempdir = tempfile::tempdir().unwrap();
std::fs::write(
tempdir.path().join("foo.rs"),
indoc! {"
fn main() {
println!(\"Hello, world!\");
}
"},
)
.expect("failed to write file");
let project = Project::example([tempdir.path()], &mut cx.to_async()).await;
let thread = new_test_thread(server, project.clone(), tempdir.path(), cx).await;
thread
.update(cx, |thread, cx| {
thread.send(
vec![
acp::ContentBlock::Text(acp::TextContent {
text: "Read the file ".into(),
annotations: None,
}),
acp::ContentBlock::ResourceLink(acp::ResourceLink {
uri: "foo.rs".into(),
name: "foo.rs".into(),
annotations: None,
description: None,
mime_type: None,
size: None,
title: None,
}),
acp::ContentBlock::Text(acp::TextContent {
text: " and tell me what the content of the println! is".into(),
annotations: None,
}),
],
cx,
)
})
.await
.unwrap();
thread.read_with(cx, |thread, cx| {
assert!(matches!(
thread.entries()[0],
AgentThreadEntry::UserMessage(_)
));
let assistant_message = &thread
.entries()
.iter()
.rev()
.find_map(|entry| match entry {
AgentThreadEntry::AssistantMessage(msg) => Some(msg),
_ => None,
})
.unwrap();
assert!(
assistant_message.to_markdown(cx).contains("Hello, world!"),
"unexpected assistant message: {:?}",
assistant_message.to_markdown(cx)
);
});
drop(tempdir);
}
pub async fn test_tool_call(server: impl AgentServer + 'static, cx: &mut TestAppContext) {
let _fs = init_test(cx).await;
let tempdir = tempfile::tempdir().unwrap();
let foo_path = tempdir.path().join("foo");
std::fs::write(&foo_path, "Lorem ipsum dolor").expect("failed to write file");
let project = Project::example([tempdir.path()], &mut cx.to_async()).await;
let thread = new_test_thread(server, project.clone(), "/private/tmp", cx).await;
thread
.update(cx, |thread, cx| {
thread.send_raw(
&format!("Read {} and tell me what you see.", foo_path.display()),
cx,
)
})
.await
.unwrap();
thread.read_with(cx, |thread, _cx| {
assert!(thread.entries().iter().any(|entry| {
matches!(
entry,
AgentThreadEntry::ToolCall(ToolCall {
status: ToolCallStatus::Allowed { .. },
..
})
)
}));
assert!(
thread
.entries()
.iter()
.any(|entry| { matches!(entry, AgentThreadEntry::AssistantMessage(_)) })
);
});
drop(tempdir);
}
pub async fn test_tool_call_with_confirmation(
server: impl AgentServer + 'static,
allow_option_id: acp::PermissionOptionId,
cx: &mut TestAppContext,
) {
let fs = init_test(cx).await;
let project = Project::test(fs, [path!("/private/tmp").as_ref()], cx).await;
let thread = new_test_thread(server, project.clone(), "/private/tmp", cx).await;
let full_turn = thread.update(cx, |thread, cx| {
thread.send_raw(
r#"Run exactly `touch hello.txt && echo "Hello, world!" | tee hello.txt` in the terminal."#,
cx,
)
});
run_until_first_tool_call(
&thread,
|entry| {
matches!(
entry,
AgentThreadEntry::ToolCall(ToolCall {
status: ToolCallStatus::WaitingForConfirmation { .. },
..
})
)
},
cx,
)
.await;
let tool_call_id = thread.read_with(cx, |thread, cx| {
let AgentThreadEntry::ToolCall(ToolCall {
id,
label,
status: ToolCallStatus::WaitingForConfirmation { .. },
..
}) = &thread
.entries()
.iter()
.find(|entry| matches!(entry, AgentThreadEntry::ToolCall(_)))
.unwrap()
else {
panic!();
};
let label = label.read(cx).source();
assert!(label.contains("touch"), "Got: {}", label);
id.clone()
});
thread.update(cx, |thread, cx| {
thread.authorize_tool_call(
tool_call_id,
allow_option_id,
acp::PermissionOptionKind::AllowOnce,
cx,
);
assert!(thread.entries().iter().any(|entry| matches!(
entry,
AgentThreadEntry::ToolCall(ToolCall {
status: ToolCallStatus::Allowed { .. },
..
})
)));
});
full_turn.await.unwrap();
thread.read_with(cx, |thread, cx| {
let AgentThreadEntry::ToolCall(ToolCall {
content,
status: ToolCallStatus::Allowed { .. },
..
}) = thread
.entries()
.iter()
.find(|entry| matches!(entry, AgentThreadEntry::ToolCall(_)))
.unwrap()
else {
panic!();
};
assert!(
content.iter().any(|c| c.to_markdown(cx).contains("Hello")),
"Expected content to contain 'Hello'"
);
});
}
pub async fn test_cancel(server: impl AgentServer + 'static, cx: &mut TestAppContext) {
let fs = init_test(cx).await;
let project = Project::test(fs, [path!("/private/tmp").as_ref()], cx).await;
let thread = new_test_thread(server, project.clone(), "/private/tmp", cx).await;
let full_turn = thread.update(cx, |thread, cx| {
thread.send_raw(
r#"Run exactly `touch hello.txt && echo "Hello, world!" | tee hello.txt` in the terminal."#,
cx,
)
});
let first_tool_call_ix = run_until_first_tool_call(
&thread,
|entry| {
matches!(
entry,
AgentThreadEntry::ToolCall(ToolCall {
status: ToolCallStatus::WaitingForConfirmation { .. },
..
})
)
},
cx,
)
.await;
thread.read_with(cx, |thread, cx| {
let AgentThreadEntry::ToolCall(ToolCall {
id,
label,
status: ToolCallStatus::WaitingForConfirmation { .. },
..
}) = &thread.entries()[first_tool_call_ix]
else {
panic!("{:?}", thread.entries()[1]);
};
let label = label.read(cx).source();
assert!(label.contains("touch"), "Got: {}", label);
id.clone()
});
let _ = thread.update(cx, |thread, cx| thread.cancel(cx));
full_turn.await.unwrap();
thread.read_with(cx, |thread, _| {
let AgentThreadEntry::ToolCall(ToolCall {
status: ToolCallStatus::Canceled,
..
}) = &thread.entries()[first_tool_call_ix]
else {
panic!();
};
});
thread
.update(cx, |thread, cx| {
thread.send_raw(r#"Stop running and say goodbye to me."#, cx)
})
.await
.unwrap();
thread.read_with(cx, |thread, _| {
assert!(matches!(
&thread.entries().last().unwrap(),
AgentThreadEntry::AssistantMessage(..),
))
});
}
#[macro_export]
macro_rules! common_e2e_tests {
($server:expr, allow_option_id = $allow_option_id:expr) => {
mod common_e2e {
use super::*;
#[::gpui::test]
#[cfg_attr(not(feature = "e2e"), ignore)]
async fn basic(cx: &mut ::gpui::TestAppContext) {
$crate::e2e_tests::test_basic($server, cx).await;
}
#[::gpui::test]
#[cfg_attr(not(feature = "e2e"), ignore)]
async fn path_mentions(cx: &mut ::gpui::TestAppContext) {
$crate::e2e_tests::test_path_mentions($server, cx).await;
}
#[::gpui::test]
#[cfg_attr(not(feature = "e2e"), ignore)]
async fn tool_call(cx: &mut ::gpui::TestAppContext) {
$crate::e2e_tests::test_tool_call($server, cx).await;
}
#[::gpui::test]
#[cfg_attr(not(feature = "e2e"), ignore)]
async fn tool_call_with_confirmation(cx: &mut ::gpui::TestAppContext) {
$crate::e2e_tests::test_tool_call_with_confirmation(
$server,
::agent_client_protocol::PermissionOptionId($allow_option_id.into()),
cx,
)
.await;
}
#[::gpui::test]
#[cfg_attr(not(feature = "e2e"), ignore)]
async fn cancel(cx: &mut ::gpui::TestAppContext) {
$crate::e2e_tests::test_cancel($server, cx).await;
}
}
};
}
// Helpers
pub async fn init_test(cx: &mut TestAppContext) -> Arc<FakeFs> {
env_logger::try_init().ok();
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
Project::init_settings(cx);
language::init(cx);
crate::settings::init(cx);
crate::AllAgentServersSettings::override_global(
AllAgentServersSettings {
claude: Some(AgentServerSettings {
command: crate::claude::tests::local_command(),
}),
gemini: Some(AgentServerSettings {
command: crate::gemini::tests::local_command(),
}),
codex: Some(AgentServerSettings {
command: crate::codex::tests::local_command(),
}),
},
cx,
);
});
cx.executor().allow_parking();
FakeFs::new(cx.executor())
}
pub async fn new_test_thread(
server: impl AgentServer + 'static,
project: Entity<Project>,
current_dir: impl AsRef<Path>,
cx: &mut TestAppContext,
) -> Entity<AcpThread> {
let connection = cx
.update(|cx| server.connect(current_dir.as_ref(), &project, cx))
.await
.unwrap();
let thread = connection
.new_thread(project.clone(), current_dir.as_ref(), &mut cx.to_async())
.await
.unwrap();
thread
}
pub async fn run_until_first_tool_call(
thread: &Entity<AcpThread>,
wait_until: impl Fn(&AgentThreadEntry) -> bool + 'static,
cx: &mut TestAppContext,
) -> usize {
let (mut tx, mut rx) = mpsc::channel::<usize>(1);
let subscription = cx.update(|cx| {
cx.subscribe(thread, move |thread, _, cx| {
for (ix, entry) in thread.read(cx).entries().iter().enumerate() {
if wait_until(entry) {
return tx.try_send(ix).unwrap();
}
}
})
});
select! {
// We have to use a smol timer here because
// cx.background_executor().timer isn't real in the test context
_ = futures::FutureExt::fuse(smol::Timer::after(Duration::from_secs(20))) => {
panic!("Timeout waiting for tool call")
}
ix = rx.next().fuse() => {
drop(subscription);
ix.unwrap()
}
}
}
pub fn get_zed_path() -> PathBuf {
let mut zed_path = std::env::current_exe().unwrap();
while zed_path
.file_name()
.map_or(true, |name| name.to_string_lossy() != "debug")
{
if !zed_path.pop() {
panic!("Could not find target directory");
}
}
zed_path.push("zed");
if !zed_path.exists() {
panic!("\n🚨 Run `cargo build` at least once before running e2e tests\n\n");
}
zed_path
}

View File

@@ -0,0 +1,206 @@
use anyhow::anyhow;
use std::cell::RefCell;
use std::path::Path;
use std::rc::Rc;
use util::ResultExt as _;
use crate::{AgentServer, AgentServerCommand, AgentServerVersion};
use acp_thread::{AgentConnection, LoadError, OldAcpAgentConnection, OldAcpClientDelegate};
use agentic_coding_protocol as acp_old;
use anyhow::{Context as _, Result};
use gpui::{AppContext as _, AsyncApp, Entity, Task, WeakEntity};
use project::Project;
use settings::SettingsStore;
use ui::App;
use crate::AllAgentServersSettings;
#[derive(Clone)]
pub struct Gemini;
const ACP_ARG: &str = "--experimental-acp";
impl AgentServer for Gemini {
fn name(&self) -> &'static str {
"Gemini"
}
fn empty_state_headline(&self) -> &'static str {
"Welcome to Gemini"
}
fn empty_state_message(&self) -> &'static str {
"Ask questions, edit files, run commands.\nBe specific for the best results."
}
fn logo(&self) -> ui::IconName {
ui::IconName::AiGemini
}
fn connect(
&self,
root_dir: &Path,
project: &Entity<Project>,
cx: &mut App,
) -> Task<Result<Rc<dyn AgentConnection>>> {
let root_dir = root_dir.to_path_buf();
let project = project.clone();
let this = self.clone();
let name = self.name();
cx.spawn(async move |cx| {
let command = this.command(&project, cx).await?;
let mut child = util::command::new_smol_command(&command.path)
.args(command.args.iter())
.current_dir(root_dir)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::inherit())
.kill_on_drop(true)
.spawn()?;
let stdin = child.stdin.take().unwrap();
let stdout = child.stdout.take().unwrap();
let foreground_executor = cx.foreground_executor().clone();
let thread_rc = Rc::new(RefCell::new(WeakEntity::new_invalid()));
let (connection, io_fut) = acp_old::AgentConnection::connect_to_agent(
OldAcpClientDelegate::new(thread_rc.clone(), cx.clone()),
stdin,
stdout,
move |fut| foreground_executor.spawn(fut).detach(),
);
let io_task = cx.background_spawn(async move {
io_fut.await.log_err();
});
let child_status = cx.background_spawn(async move {
let result = match child.status().await {
Err(e) => Err(anyhow!(e)),
Ok(result) if result.success() => Ok(()),
Ok(result) => {
if let Some(AgentServerVersion::Unsupported {
error_message,
upgrade_message,
upgrade_command,
}) = this.version(&command).await.log_err()
{
Err(anyhow!(LoadError::Unsupported {
error_message,
upgrade_message,
upgrade_command
}))
} else {
Err(anyhow!(LoadError::Exited(result.code().unwrap_or(-127))))
}
}
};
drop(io_task);
result
});
let connection: Rc<dyn AgentConnection> = Rc::new(OldAcpAgentConnection {
name,
connection,
child_status,
current_thread: thread_rc,
});
Ok(connection)
})
}
}
impl Gemini {
async fn command(
&self,
project: &Entity<Project>,
cx: &mut AsyncApp,
) -> Result<AgentServerCommand> {
let settings = cx.read_global(|settings: &SettingsStore, _| {
settings.get::<AllAgentServersSettings>(None).gemini.clone()
})?;
if let Some(command) =
AgentServerCommand::resolve("gemini", &[ACP_ARG], settings, &project, cx).await
{
return Ok(command);
};
let (fs, node_runtime) = project.update(cx, |project, _| {
(project.fs().clone(), project.node_runtime().cloned())
})?;
let node_runtime = node_runtime.context("gemini not found on path")?;
let directory = ::paths::agent_servers_dir().join("gemini");
fs.create_dir(&directory).await?;
node_runtime
.npm_install_packages(&directory, &[("@google/gemini-cli", "latest")])
.await?;
let path = directory.join("node_modules/.bin/gemini");
Ok(AgentServerCommand {
path,
args: vec![ACP_ARG.into()],
env: None,
})
}
async fn version(&self, command: &AgentServerCommand) -> Result<AgentServerVersion> {
let version_fut = util::command::new_smol_command(&command.path)
.args(command.args.iter())
.arg("--version")
.kill_on_drop(true)
.output();
let help_fut = util::command::new_smol_command(&command.path)
.args(command.args.iter())
.arg("--help")
.kill_on_drop(true)
.output();
let (version_output, help_output) = futures::future::join(version_fut, help_fut).await;
let current_version = String::from_utf8(version_output?.stdout)?;
let supported = String::from_utf8(help_output?.stdout)?.contains(ACP_ARG);
if supported {
Ok(AgentServerVersion::Supported)
} else {
Ok(AgentServerVersion::Unsupported {
error_message: format!(
"Your installed version of Gemini {} doesn't support the Agentic Coding Protocol (ACP).",
current_version
).into(),
upgrade_message: "Upgrade Gemini to Latest".into(),
upgrade_command: "npm install -g @google/gemini-cli@latest".into(),
})
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::AgentServerCommand;
use std::path::Path;
crate::common_e2e_tests!(Gemini, allow_option_id = "0");
pub fn local_command() -> AgentServerCommand {
let cli_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../../gemini-cli/packages/cli")
.to_string_lossy()
.to_string();
AgentServerCommand {
path: "node".into(),
args: vec![cli_path, ACP_ARG.into()],
env: None,
}
}
}

View File

@@ -0,0 +1,207 @@
use acp_thread::AcpThread;
use agent_client_protocol as acp;
use anyhow::Result;
use context_server::listener::{McpServerTool, ToolResponse};
use context_server::types::{
Implementation, InitializeParams, InitializeResponse, ProtocolVersion, ServerCapabilities,
ToolsCapabilities, requests,
};
use futures::channel::oneshot;
use gpui::{App, AsyncApp, Task, WeakEntity};
use indoc::indoc;
pub struct ZedMcpServer {
server: context_server::listener::McpServer,
}
pub const SERVER_NAME: &str = "zed";
impl ZedMcpServer {
pub async fn new(
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
cx: &AsyncApp,
) -> Result<Self> {
let mut mcp_server = context_server::listener::McpServer::new(cx).await?;
mcp_server.handle_request::<requests::Initialize>(Self::handle_initialize);
mcp_server.add_tool(RequestPermissionTool {
thread_rx: thread_rx.clone(),
});
mcp_server.add_tool(ReadTextFileTool {
thread_rx: thread_rx.clone(),
});
mcp_server.add_tool(WriteTextFileTool {
thread_rx: thread_rx.clone(),
});
Ok(Self { server: mcp_server })
}
pub fn server_config(&self) -> Result<acp::McpServerConfig> {
#[cfg(not(test))]
let zed_path = anyhow::Context::context(
std::env::current_exe(),
"finding current executable path for use in mcp_server",
)?;
#[cfg(test)]
let zed_path = crate::e2e_tests::get_zed_path();
Ok(acp::McpServerConfig {
command: zed_path,
args: vec![
"--nc".into(),
self.server.socket_path().display().to_string(),
],
env: None,
})
}
fn handle_initialize(_: InitializeParams, cx: &App) -> Task<Result<InitializeResponse>> {
cx.foreground_executor().spawn(async move {
Ok(InitializeResponse {
protocol_version: ProtocolVersion("2025-06-18".into()),
capabilities: ServerCapabilities {
experimental: None,
logging: None,
completions: None,
prompts: None,
resources: None,
tools: Some(ToolsCapabilities {
list_changed: Some(false),
}),
},
server_info: Implementation {
name: SERVER_NAME.into(),
version: "0.1.0".into(),
},
meta: None,
})
})
}
}
// Tools
#[derive(Clone)]
pub struct RequestPermissionTool {
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
}
impl McpServerTool for RequestPermissionTool {
type Input = acp::RequestPermissionArguments;
type Output = acp::RequestPermissionOutput;
const NAME: &'static str = "Confirmation";
fn description(&self) -> &'static str {
indoc! {"
Request permission for tool calls.
This tool is meant to be called programmatically by the agent loop, not the LLM.
"}
}
async fn run(
&self,
input: Self::Input,
cx: &mut AsyncApp,
) -> Result<ToolResponse<Self::Output>> {
let mut thread_rx = self.thread_rx.clone();
let Some(thread) = thread_rx.recv().await?.upgrade() else {
anyhow::bail!("Thread closed");
};
let result = thread
.update(cx, |thread, cx| {
thread.request_tool_call_permission(input.tool_call, input.options, cx)
})?
.await;
let outcome = match result {
Ok(option_id) => acp::RequestPermissionOutcome::Selected { option_id },
Err(oneshot::Canceled) => acp::RequestPermissionOutcome::Canceled,
};
Ok(ToolResponse {
content: vec![],
structured_content: acp::RequestPermissionOutput { outcome },
})
}
}
#[derive(Clone)]
pub struct ReadTextFileTool {
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
}
impl McpServerTool for ReadTextFileTool {
type Input = acp::ReadTextFileArguments;
type Output = acp::ReadTextFileOutput;
const NAME: &'static str = "Read";
fn description(&self) -> &'static str {
"Reads the content of the given file in the project including unsaved changes."
}
async fn run(
&self,
input: Self::Input,
cx: &mut AsyncApp,
) -> Result<ToolResponse<Self::Output>> {
let mut thread_rx = self.thread_rx.clone();
let Some(thread) = thread_rx.recv().await?.upgrade() else {
anyhow::bail!("Thread closed");
};
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(input.path, input.line, input.limit, false, cx)
})?
.await?;
Ok(ToolResponse {
content: vec![],
structured_content: acp::ReadTextFileOutput { content },
})
}
}
#[derive(Clone)]
pub struct WriteTextFileTool {
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
}
impl McpServerTool for WriteTextFileTool {
type Input = acp::WriteTextFileArguments;
type Output = ();
const NAME: &'static str = "Write";
fn description(&self) -> &'static str {
"Write to a file replacing its contents"
}
async fn run(
&self,
input: Self::Input,
cx: &mut AsyncApp,
) -> Result<ToolResponse<Self::Output>> {
let mut thread_rx = self.thread_rx.clone();
let Some(thread) = thread_rx.recv().await?.upgrade() else {
anyhow::bail!("Thread closed");
};
thread
.update(cx, |thread, cx| {
thread.write_text_file(input.path, input.content, cx)
})?
.await?;
Ok(ToolResponse {
content: vec![],
structured_content: (),
})
}
}

View File

@@ -0,0 +1,54 @@
use crate::AgentServerCommand;
use anyhow::Result;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
pub fn init(cx: &mut App) {
AllAgentServersSettings::register(cx);
}
#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug)]
pub struct AllAgentServersSettings {
pub gemini: Option<AgentServerSettings>,
pub claude: Option<AgentServerSettings>,
pub codex: Option<AgentServerSettings>,
}
#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug)]
pub struct AgentServerSettings {
#[serde(flatten)]
pub command: AgentServerCommand,
}
impl settings::Settings for AllAgentServersSettings {
const KEY: Option<&'static str> = Some("agent_servers");
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
let mut settings = AllAgentServersSettings::default();
for AllAgentServersSettings {
gemini,
claude,
codex,
} in sources.defaults_and_customizations()
{
if gemini.is_some() {
settings.gemini = gemini.clone();
}
if claude.is_some() {
settings.claude = claude.clone();
}
if codex.is_some() {
settings.codex = codex.clone();
}
}
Ok(settings)
}
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
}

View File

@@ -13,6 +13,7 @@ path = "src/agent_settings.rs"
[dependencies]
anyhow.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
gpui.workspace = true
language_model.workspace = true
@@ -20,7 +21,6 @@ schemars.workspace = true
serde.workspace = true
settings.workspace = true
workspace-hack.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
fs.workspace = true

View File

@@ -69,6 +69,7 @@ pub struct AgentSettings {
pub enable_feedback: bool,
pub expand_edit_card: bool,
pub expand_terminal_card: bool,
pub use_modifier_to_send: bool,
}
impl AgentSettings {
@@ -174,6 +175,10 @@ impl AgentSettingsContent {
self.single_file_review = Some(allow);
}
pub fn set_use_modifier_to_send(&mut self, always_use: bool) {
self.use_modifier_to_send = Some(always_use);
}
pub fn set_profile(&mut self, profile_id: AgentProfileId) {
self.default_profile = Some(profile_id);
}
@@ -301,6 +306,10 @@ pub struct AgentSettingsContent {
///
/// Default: true
expand_terminal_card: Option<bool>,
/// Whether to always use cmd-enter (or ctrl-enter on Linux) to send messages in the agent panel.
///
/// Default: false
use_modifier_to_send: Option<bool>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
@@ -312,11 +321,11 @@ pub enum CompletionMode {
Burn,
}
impl From<CompletionMode> for zed_llm_client::CompletionMode {
impl From<CompletionMode> for cloud_llm_client::CompletionMode {
fn from(value: CompletionMode) -> Self {
match value {
CompletionMode::Normal => zed_llm_client::CompletionMode::Normal,
CompletionMode::Burn => zed_llm_client::CompletionMode::Max,
CompletionMode::Normal => cloud_llm_client::CompletionMode::Normal,
CompletionMode::Burn => cloud_llm_client::CompletionMode::Max,
}
}
}
@@ -456,6 +465,10 @@ impl Settings for AgentSettings {
&mut settings.expand_terminal_card,
value.expand_terminal_card,
);
merge(
&mut settings.use_modifier_to_send,
value.use_modifier_to_send,
);
settings
.model_parameters

View File

@@ -16,11 +16,12 @@ doctest = false
test-support = ["gpui/test-support", "language/test-support"]
[dependencies]
acp.workspace = true
acp_thread.workspace = true
agent-client-protocol.workspace = true
agent.workspace = true
agentic-coding-protocol.workspace = true
agent_settings.workspace = true
agent_servers.workspace = true
agent_settings.workspace = true
ai_onboarding.workspace = true
anyhow.workspace = true
assistant_context.workspace = true
assistant_slash_command.workspace = true
@@ -30,7 +31,9 @@ audio.workspace = true
buffer_diff.workspace = true
chrono.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
component.workspace = true
context_server.workspace = true
db.workspace = true
@@ -44,14 +47,15 @@ futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
html_to_markdown.workspace = true
indoc.workspace = true
http_client.workspace = true
indexed_docs.workspace = true
indoc.workspace = true
inventory.workspace = true
itertools.workspace = true
jsonschema.workspace = true
language.workspace = true
language_model.workspace = true
language_models.workspace = true
log.workspace = true
lsp.workspace = true
markdown.workspace = true
@@ -86,6 +90,7 @@ theme.workspace = true
time.workspace = true
time_format.workspace = true
ui.workspace = true
ui_input.workspace = true
urlencoding.workspace = true
util.workspace = true
uuid.workspace = true
@@ -93,7 +98,6 @@ watch.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
assistant_tools.workspace = true

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,7 @@ use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};
use anyhow::Context as _;
use assistant_tool::ToolUseStatus;
use audio::{Audio, Sound};
use cloud_llm_client::CompletionIntent;
use collections::{HashMap, HashSet};
use editor::actions::{MoveUp, Paste};
use editor::scroll::Autoscroll;
@@ -52,7 +53,6 @@ use util::ResultExt as _;
use util::markdown::MarkdownCodeBlock;
use workspace::{CollaboratorId, Workspace};
use zed_actions::assistant::OpenRulesLibrary;
use zed_llm_client::CompletionIntent;
const CODEBLOCK_CONTAINER_GROUP: &str = "codeblock_container";
const EDIT_PREVIOUS_MESSAGE_MIN_LINES: usize = 1;
@@ -1036,7 +1036,7 @@ impl ActiveThread {
.collect::<Vec<_>>()
.join("\n");
self.last_error = Some(ThreadError::Message {
header: "Error interacting with language model".into(),
header: "Error".into(),
message: error_message.into(),
});
}
@@ -3202,7 +3202,10 @@ impl ActiveThread {
.border_color(self.tool_card_border_color(cx))
.rounded_b_lg()
.child(
LoadingLabel::new("Waiting for Confirmation").size(LabelSize::Small)
div()
.min_w(rems_from_px(145.))
.child(LoadingLabel::new("Waiting for Confirmation").size(LabelSize::Small)
)
)
.child(
h_flex()
@@ -3247,7 +3250,6 @@ impl ActiveThread {
},
))
})
.child(ui::Divider::vertical())
.child({
let tool_id = tool_use.id.clone();
Button::new("allow-tool-action", "Allow")
@@ -3722,8 +3724,11 @@ pub(crate) fn open_context(
AgentContextHandle::Thread(thread_context) => workspace.update(cx, |workspace, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
panel.update(cx, |panel, cx| {
panel.open_thread(thread_context.thread.clone(), window, cx);
let thread = thread_context.thread.clone();
window.defer(cx, move |window, cx| {
panel.update(cx, |panel, cx| {
panel.open_thread(thread, window, cx);
});
});
}
}),
@@ -3731,8 +3736,11 @@ pub(crate) fn open_context(
AgentContextHandle::TextThread(text_thread_context) => {
workspace.update(cx, |workspace, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
panel.update(cx, |panel, cx| {
panel.open_prompt_editor(text_thread_context.context.clone(), window, cx)
let context = text_thread_context.context.clone();
window.defer(cx, move |window, cx| {
panel.update(cx, |panel, cx| {
panel.open_prompt_editor(context, window, cx)
});
});
}
})
@@ -3887,7 +3895,7 @@ mod tests {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.set_default_model(
Some(ConfiguredModel {
provider: Arc::new(FakeLanguageModelProvider),
provider: Arc::new(FakeLanguageModelProvider::default()),
model,
}),
cx,
@@ -3971,7 +3979,7 @@ mod tests {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.set_default_model(
Some(ConfiguredModel {
provider: Arc::new(FakeLanguageModelProvider),
provider: Arc::new(FakeLanguageModelProvider::default()),
model: model.clone(),
}),
cx,

View File

@@ -1,3 +1,4 @@
mod add_llm_provider_modal;
mod configure_context_server_modal;
mod manage_profiles_modal;
mod tool_picker;
@@ -6,6 +7,7 @@ use std::{sync::Arc, time::Duration};
use agent_settings::AgentSettings;
use assistant_tool::{ToolSource, ToolWorkingSet};
use cloud_llm_client::Plan;
use collections::HashMap;
use context_server::ContextServerId;
use extension::ExtensionManifest;
@@ -24,11 +26,10 @@ use project::{
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
project_settings::{ContextServerSettings, ProjectSettings},
};
use proto::Plan;
use settings::{Settings, update_settings_file};
use ui::{
Chip, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, PopoverMenu,
Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip, prelude::*,
Scrollbar, ScrollbarState, Switch, SwitchColor, SwitchField, Tooltip, prelude::*,
};
use util::ResultExt as _;
use workspace::Workspace;
@@ -37,7 +38,10 @@ use zed_actions::ExtensionCategoryFilter;
pub(crate) use configure_context_server_modal::ConfigureContextServerModal;
pub(crate) use manage_profiles_modal::ManageProfilesModal;
use crate::AddContextServer;
use crate::{
AddContextServer,
agent_configuration::add_llm_provider_modal::{AddLlmProviderModal, LlmCompatibleProvider},
};
pub struct AgentConfiguration {
fs: Arc<dyn Fs>,
@@ -176,12 +180,20 @@ impl AgentConfiguration {
let current_plan = if is_zed_provider {
self.workspace
.upgrade()
.and_then(|workspace| workspace.read(cx).user_store().read(cx).current_plan())
.and_then(|workspace| workspace.read(cx).user_store().read(cx).plan())
} else {
None
};
let is_signed_in = self
.workspace
.read_with(cx, |workspace, _| {
workspace.client().status().borrow().is_connected()
})
.unwrap_or(false);
v_flex()
.w_full()
.when(is_expanded, |this| this.mb_2())
.child(
div()
@@ -212,6 +224,7 @@ impl AgentConfiguration {
.hover(|hover| hover.bg(cx.theme().colors().element_hover))
.child(
h_flex()
.w_full()
.gap_2()
.child(
Icon::new(provider.icon())
@@ -220,14 +233,15 @@ impl AgentConfiguration {
)
.child(
h_flex()
.w_full()
.gap_1()
.child(
Label::new(provider_name.clone())
.size(LabelSize::Large),
)
.map(|this| {
if is_zed_provider {
this.gap_2().child(
if is_zed_provider && is_signed_in {
this.child(
self.render_zed_plan_info(current_plan, cx),
)
} else {
@@ -303,21 +317,78 @@ impl AgentConfiguration {
let providers = LanguageModelRegistry::read_global(cx).providers();
v_flex()
.w_full()
.child(
v_flex()
h_flex()
.p(DynamicSpacing::Base16.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.pb_0()
.mb_2p5()
.gap_0p5()
.child(Headline::new("LLM Providers"))
.items_start()
.justify_between()
.child(
Label::new("Add at least one provider to use AI-powered features.")
.color(Color::Muted),
v_flex()
.w_full()
.gap_0p5()
.child(
h_flex()
.w_full()
.gap_2()
.justify_between()
.child(Headline::new("LLM Providers"))
.child(
PopoverMenu::new("add-provider-popover")
.trigger(
Button::new("add-provider", "Add Provider")
.icon_position(IconPosition::Start)
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.label_size(LabelSize::Small),
)
.anchor(gpui::Corner::TopRight)
.menu({
let workspace = self.workspace.clone();
move |window, cx| {
Some(ContextMenu::build(
window,
cx,
|menu, _window, _cx| {
menu.header("Compatible APIs").entry(
"OpenAI",
None,
{
let workspace =
workspace.clone();
move |window, cx| {
workspace
.update(cx, |workspace, cx| {
AddLlmProviderModal::toggle(
LlmCompatibleProvider::OpenAi,
workspace,
window,
cx,
);
})
.log_err();
}
},
)
},
))
}
}),
),
)
.child(
Label::new("Add at least one provider to use AI-powered features.")
.color(Color::Muted),
),
),
)
.child(
div()
.w_full()
.pl(DynamicSpacing::Base08.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.children(
@@ -330,119 +401,80 @@ impl AgentConfiguration {
fn render_command_permission(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let always_allow_tool_actions = AgentSettings::get_global(cx).always_allow_tool_actions;
let fs = self.fs.clone();
h_flex()
.gap_4()
.justify_between()
.flex_wrap()
.child(
v_flex()
.gap_0p5()
.max_w_5_6()
.child(Label::new("Allow running editing tools without asking for confirmation"))
.child(
Label::new(
"The agent can perform potentially destructive actions without asking for your confirmation.",
)
.color(Color::Muted),
),
)
.child(
Switch::new(
"always-allow-tool-actions-switch",
always_allow_tool_actions.into(),
)
.color(SwitchColor::Accent)
.on_click({
let fs = self.fs.clone();
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _| {
settings.set_always_allow_tool_actions(allow);
},
);
}
}),
)
SwitchField::new(
"always-allow-tool-actions-switch",
"Allow running commands without asking for confirmation",
Some(
"The agent can perform potentially destructive actions without asking for your confirmation.".into(),
),
always_allow_tool_actions,
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
settings.set_always_allow_tool_actions(allow);
});
},
)
}
fn render_single_file_review(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let single_file_review = AgentSettings::get_global(cx).single_file_review;
let fs = self.fs.clone();
h_flex()
.gap_4()
.justify_between()
.flex_wrap()
.child(
v_flex()
.gap_0p5()
.max_w_5_6()
.child(Label::new("Enable single-file agent reviews"))
.child(
Label::new(
"Agent edits are also displayed in single-file editors for review.",
)
.color(Color::Muted),
),
)
.child(
Switch::new("single-file-review-switch", single_file_review.into())
.color(SwitchColor::Accent)
.on_click({
let fs = self.fs.clone();
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _| {
settings.set_single_file_review(allow);
},
);
}
}),
)
SwitchField::new(
"single-file-review",
"Enable single-file agent reviews",
Some("Agent edits are also displayed in single-file editors for review.".into()),
single_file_review,
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
settings.set_single_file_review(allow);
});
},
)
}
fn render_sound_notification(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let play_sound_when_agent_done = AgentSettings::get_global(cx).play_sound_when_agent_done;
let fs = self.fs.clone();
h_flex()
.gap_4()
.justify_between()
.flex_wrap()
.child(
v_flex()
.gap_0p5()
.max_w_5_6()
.child(Label::new("Play sound when finished generating"))
.child(
Label::new(
"Hear a notification sound when the agent is done generating changes or needs your input.",
)
.color(Color::Muted),
),
)
.child(
Switch::new("play-sound-notification-switch", play_sound_when_agent_done.into())
.color(SwitchColor::Accent)
.on_click({
let fs = self.fs.clone();
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _| {
settings.set_play_sound_when_agent_done(allow);
},
);
}
}),
)
SwitchField::new(
"sound-notification",
"Play sound when finished generating",
Some(
"Hear a notification sound when the agent is done generating changes or needs your input.".into(),
),
play_sound_when_agent_done,
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
settings.set_play_sound_when_agent_done(allow);
});
},
)
}
fn render_modifier_to_send(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let use_modifier_to_send = AgentSettings::get_global(cx).use_modifier_to_send;
let fs = self.fs.clone();
SwitchField::new(
"modifier-send",
"Use modifier to submit a message",
Some(
"Make a modifier (cmd-enter on macOS, ctrl-enter on Linux) required to send messages.".into(),
),
use_modifier_to_send,
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
settings.set_use_modifier_to_send(allow);
});
},
)
}
fn render_general_settings_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
@@ -456,6 +488,7 @@ impl AgentConfiguration {
.child(self.render_command_permission(cx))
.child(self.render_single_file_review(cx))
.child(self.render_sound_notification(cx))
.child(self.render_modifier_to_send(cx))
}
fn render_zed_plan_info(&self, plan: Option<Plan>, cx: &mut Context<Self>) -> impl IntoElement {
@@ -475,7 +508,7 @@ impl AgentConfiguration {
.blend(cx.theme().colors().text_accent.opacity(0.2));
let (plan_name, label_color, bg_color) = match plan {
Plan::Free => ("Free", Color::Default, free_chip_bg),
Plan::ZedFree => ("Free", Color::Default, free_chip_bg),
Plan::ZedProTrial => ("Pro Trial", Color::Accent, pro_chip_bg),
Plan::ZedPro => ("Pro", Color::Accent, pro_chip_bg),
};

View File

@@ -0,0 +1,639 @@
use std::sync::Arc;
use anyhow::Result;
use collections::HashSet;
use fs::Fs;
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, Task};
use language_model::LanguageModelRegistry;
use language_models::{
AllLanguageModelSettings, OpenAiCompatibleSettingsContent,
provider::open_ai_compatible::AvailableModel,
};
use settings::update_settings_file;
use ui::{Banner, KeyBinding, Modal, ModalFooter, ModalHeader, Section, prelude::*};
use ui_input::SingleLineInput;
use workspace::{ModalView, Workspace};
#[derive(Clone, Copy)]
pub enum LlmCompatibleProvider {
OpenAi,
}
impl LlmCompatibleProvider {
fn name(&self) -> &'static str {
match self {
LlmCompatibleProvider::OpenAi => "OpenAI",
}
}
fn api_url(&self) -> &'static str {
match self {
LlmCompatibleProvider::OpenAi => "https://api.openai.com/v1",
}
}
}
struct AddLlmProviderInput {
provider_name: Entity<SingleLineInput>,
api_url: Entity<SingleLineInput>,
api_key: Entity<SingleLineInput>,
models: Vec<ModelInput>,
}
impl AddLlmProviderInput {
fn new(provider: LlmCompatibleProvider, window: &mut Window, cx: &mut App) -> Self {
let provider_name = single_line_input("Provider Name", provider.name(), None, window, cx);
let api_url = single_line_input("API URL", provider.api_url(), None, window, cx);
let api_key = single_line_input(
"API Key",
"000000000000000000000000000000000000000000000000",
None,
window,
cx,
);
Self {
provider_name,
api_url,
api_key,
models: vec![ModelInput::new(window, cx)],
}
}
fn add_model(&mut self, window: &mut Window, cx: &mut App) {
self.models.push(ModelInput::new(window, cx));
}
fn remove_model(&mut self, index: usize) {
self.models.remove(index);
}
}
struct ModelInput {
name: Entity<SingleLineInput>,
max_completion_tokens: Entity<SingleLineInput>,
max_output_tokens: Entity<SingleLineInput>,
max_tokens: Entity<SingleLineInput>,
}
impl ModelInput {
fn new(window: &mut Window, cx: &mut App) -> Self {
let model_name = single_line_input(
"Model Name",
"e.g. gpt-4o, claude-opus-4, gemini-2.5-pro",
None,
window,
cx,
);
let max_completion_tokens = single_line_input(
"Max Completion Tokens",
"200000",
Some("200000"),
window,
cx,
);
let max_output_tokens = single_line_input(
"Max Output Tokens",
"Max Output Tokens",
Some("32000"),
window,
cx,
);
let max_tokens = single_line_input("Max Tokens", "Max Tokens", Some("200000"), window, cx);
Self {
name: model_name,
max_completion_tokens,
max_output_tokens,
max_tokens,
}
}
fn parse(&self, cx: &App) -> Result<AvailableModel, SharedString> {
let name = self.name.read(cx).text(cx);
if name.is_empty() {
return Err(SharedString::from("Model Name cannot be empty"));
}
Ok(AvailableModel {
name,
display_name: None,
max_completion_tokens: Some(
self.max_completion_tokens
.read(cx)
.text(cx)
.parse::<u64>()
.map_err(|_| SharedString::from("Max Completion Tokens must be a number"))?,
),
max_output_tokens: Some(
self.max_output_tokens
.read(cx)
.text(cx)
.parse::<u64>()
.map_err(|_| SharedString::from("Max Output Tokens must be a number"))?,
),
max_tokens: self
.max_tokens
.read(cx)
.text(cx)
.parse::<u64>()
.map_err(|_| SharedString::from("Max Tokens must be a number"))?,
})
}
}
fn single_line_input(
label: impl Into<SharedString>,
placeholder: impl Into<SharedString>,
text: Option<&str>,
window: &mut Window,
cx: &mut App,
) -> Entity<SingleLineInput> {
cx.new(|cx| {
let input = SingleLineInput::new(window, cx, placeholder).label(label);
if let Some(text) = text {
input
.editor()
.update(cx, |editor, cx| editor.set_text(text, window, cx));
}
input
})
}
fn save_provider_to_settings(
input: &AddLlmProviderInput,
cx: &mut App,
) -> Task<Result<(), SharedString>> {
let provider_name: Arc<str> = input.provider_name.read(cx).text(cx).into();
if provider_name.is_empty() {
return Task::ready(Err("Provider Name cannot be empty".into()));
}
if LanguageModelRegistry::read_global(cx)
.providers()
.iter()
.any(|provider| {
provider.id().0.as_ref() == provider_name.as_ref()
|| provider.name().0.as_ref() == provider_name.as_ref()
})
{
return Task::ready(Err(
"Provider Name is already taken by another provider".into()
));
}
let api_url = input.api_url.read(cx).text(cx);
if api_url.is_empty() {
return Task::ready(Err("API URL cannot be empty".into()));
}
let api_key = input.api_key.read(cx).text(cx);
if api_key.is_empty() {
return Task::ready(Err("API Key cannot be empty".into()));
}
let mut models = Vec::new();
let mut model_names: HashSet<String> = HashSet::default();
for model in &input.models {
match model.parse(cx) {
Ok(model) => {
if !model_names.insert(model.name.clone()) {
return Task::ready(Err("Model Names must be unique".into()));
}
models.push(model)
}
Err(err) => return Task::ready(Err(err)),
}
}
let fs = <dyn Fs>::global(cx);
let task = cx.write_credentials(&api_url, "Bearer", api_key.as_bytes());
cx.spawn(async move |cx| {
task.await
.map_err(|_| "Failed to write API key to keychain")?;
cx.update(|cx| {
update_settings_file::<AllLanguageModelSettings>(fs, cx, |settings, _cx| {
settings.openai_compatible.get_or_insert_default().insert(
provider_name,
OpenAiCompatibleSettingsContent {
api_url,
available_models: models,
},
);
});
})
.ok();
Ok(())
})
}
pub struct AddLlmProviderModal {
provider: LlmCompatibleProvider,
input: AddLlmProviderInput,
focus_handle: FocusHandle,
last_error: Option<SharedString>,
}
impl AddLlmProviderModal {
pub fn toggle(
provider: LlmCompatibleProvider,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
workspace.toggle_modal(window, cx, |window, cx| Self::new(provider, window, cx));
}
fn new(provider: LlmCompatibleProvider, window: &mut Window, cx: &mut Context<Self>) -> Self {
Self {
input: AddLlmProviderInput::new(provider, window, cx),
provider,
last_error: None,
focus_handle: cx.focus_handle(),
}
}
fn confirm(&mut self, _: &menu::Confirm, _: &mut Window, cx: &mut Context<Self>) {
let task = save_provider_to_settings(&self.input, cx);
cx.spawn(async move |this, cx| {
let result = task.await;
this.update(cx, |this, cx| match result {
Ok(_) => {
cx.emit(DismissEvent);
}
Err(error) => {
this.last_error = Some(error);
cx.notify();
}
})
})
.detach_and_log_err(cx);
}
fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(DismissEvent);
}
fn render_section(&self) -> Section {
Section::new()
.child(self.input.provider_name.clone())
.child(self.input.api_url.clone())
.child(self.input.api_key.clone())
}
fn render_model_section(&self, cx: &mut Context<Self>) -> Section {
Section::new().child(
v_flex()
.gap_2()
.child(
h_flex()
.justify_between()
.child(Label::new("Models").size(LabelSize::Small))
.child(
Button::new("add-model", "Add Model")
.icon(IconName::Plus)
.icon_position(IconPosition::Start)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.label_size(LabelSize::Small)
.on_click(cx.listener(|this, _, window, cx| {
this.input.add_model(window, cx);
cx.notify();
})),
),
)
.children(
self.input
.models
.iter()
.enumerate()
.map(|(ix, _)| self.render_model(ix, cx)),
),
)
}
fn render_model(&self, ix: usize, cx: &mut Context<Self>) -> impl IntoElement + use<> {
let has_more_than_one_model = self.input.models.len() > 1;
let model = &self.input.models[ix];
v_flex()
.p_2()
.gap_2()
.rounded_sm()
.border_1()
.border_dashed()
.border_color(cx.theme().colors().border.opacity(0.6))
.bg(cx.theme().colors().element_active.opacity(0.15))
.child(model.name.clone())
.child(
h_flex()
.gap_2()
.child(model.max_completion_tokens.clone())
.child(model.max_output_tokens.clone()),
)
.child(model.max_tokens.clone())
.when(has_more_than_one_model, |this| {
this.child(
Button::new(("remove-model", ix), "Remove Model")
.icon(IconName::Trash)
.icon_position(IconPosition::Start)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.label_size(LabelSize::Small)
.style(ButtonStyle::Outlined)
.full_width()
.on_click(cx.listener(move |this, _, _window, cx| {
this.input.remove_model(ix);
cx.notify();
})),
)
})
}
}
impl EventEmitter<DismissEvent> for AddLlmProviderModal {}
impl Focusable for AddLlmProviderModal {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl ModalView for AddLlmProviderModal {}
impl Render for AddLlmProviderModal {
fn render(&mut self, window: &mut ui::Window, cx: &mut ui::Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
div()
.id("add-llm-provider-modal")
.key_context("AddLlmProviderModal")
.w(rems(34.))
.elevation_3(cx)
.on_action(cx.listener(Self::cancel))
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
this.focus_handle(cx).focus(window);
}))
.child(
Modal::new("configure-context-server", None)
.header(ModalHeader::new().headline("Add LLM Provider").description(
match self.provider {
LlmCompatibleProvider::OpenAi => {
"This provider will use an OpenAI compatible API."
}
},
))
.when_some(self.last_error.clone(), |this, error| {
this.section(
Section::new().child(
Banner::new()
.severity(ui::Severity::Warning)
.child(div().text_xs().child(error)),
),
)
})
.child(
v_flex()
.id("modal_content")
.max_h_128()
.overflow_y_scroll()
.gap_2()
.child(self.render_section())
.child(self.render_model_section(cx)),
)
.footer(
ModalFooter::new().end_slot(
h_flex()
.gap_1()
.child(
Button::new("cancel", "Cancel")
.key_binding(
KeyBinding::for_action_in(
&menu::Cancel,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(cx.listener(|this, _event, window, cx| {
this.cancel(&menu::Cancel, window, cx)
})),
)
.child(
Button::new("save-server", "Save Provider")
.key_binding(
KeyBinding::for_action_in(
&menu::Confirm,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(cx.listener(|this, _event, window, cx| {
this.confirm(&menu::Confirm, window, cx)
})),
),
),
),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use editor::EditorSettings;
use fs::FakeFs;
use gpui::{TestAppContext, VisualTestContext};
use language::language_settings;
use language_model::{
LanguageModelProviderId, LanguageModelProviderName,
fake_provider::FakeLanguageModelProvider,
};
use project::Project;
use settings::{Settings as _, SettingsStore};
use util::path;
#[gpui::test]
async fn test_save_provider_invalid_inputs(cx: &mut TestAppContext) {
let cx = setup_test(cx).await;
assert_eq!(
save_provider_validation_errors("", "someurl", "somekey", vec![], cx,).await,
Some("Provider Name cannot be empty".into())
);
assert_eq!(
save_provider_validation_errors("someprovider", "", "somekey", vec![], cx,).await,
Some("API URL cannot be empty".into())
);
assert_eq!(
save_provider_validation_errors("someprovider", "someurl", "", vec![], cx,).await,
Some("API Key cannot be empty".into())
);
assert_eq!(
save_provider_validation_errors(
"someprovider",
"someurl",
"somekey",
vec![("", "200000", "200000", "32000")],
cx,
)
.await,
Some("Model Name cannot be empty".into())
);
assert_eq!(
save_provider_validation_errors(
"someprovider",
"someurl",
"somekey",
vec![("somemodel", "abc", "200000", "32000")],
cx,
)
.await,
Some("Max Tokens must be a number".into())
);
assert_eq!(
save_provider_validation_errors(
"someprovider",
"someurl",
"somekey",
vec![("somemodel", "200000", "abc", "32000")],
cx,
)
.await,
Some("Max Completion Tokens must be a number".into())
);
assert_eq!(
save_provider_validation_errors(
"someprovider",
"someurl",
"somekey",
vec![("somemodel", "200000", "200000", "abc")],
cx,
)
.await,
Some("Max Output Tokens must be a number".into())
);
assert_eq!(
save_provider_validation_errors(
"someprovider",
"someurl",
"somekey",
vec![
("somemodel", "200000", "200000", "32000"),
("somemodel", "200000", "200000", "32000"),
],
cx,
)
.await,
Some("Model Names must be unique".into())
);
}
#[gpui::test]
async fn test_save_provider_name_conflict(cx: &mut TestAppContext) {
let cx = setup_test(cx).await;
cx.update(|_window, cx| {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.register_provider(
FakeLanguageModelProvider::new(
LanguageModelProviderId::new("someprovider"),
LanguageModelProviderName::new("Some Provider"),
),
cx,
);
});
});
assert_eq!(
save_provider_validation_errors(
"someprovider",
"someurl",
"someapikey",
vec![("somemodel", "200000", "200000", "32000")],
cx,
)
.await,
Some("Provider Name is already taken by another provider".into())
);
}
async fn setup_test(cx: &mut TestAppContext) -> &mut VisualTestContext {
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
workspace::init_settings(cx);
Project::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
language_settings::init(cx);
EditorSettings::register(cx);
language_model::init_settings(cx);
language_models::init_settings(cx);
});
let fs = FakeFs::new(cx.executor());
cx.update(|cx| <dyn Fs>::set_global(fs.clone(), cx));
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
let (_, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
cx
}
async fn save_provider_validation_errors(
provider_name: &str,
api_url: &str,
api_key: &str,
models: Vec<(&str, &str, &str, &str)>,
cx: &mut VisualTestContext,
) -> Option<SharedString> {
fn set_text(
input: &Entity<SingleLineInput>,
text: &str,
window: &mut Window,
cx: &mut App,
) {
input.update(cx, |input, cx| {
input.editor().update(cx, |editor, cx| {
editor.set_text(text, window, cx);
});
});
}
let task = cx.update(|window, cx| {
let mut input = AddLlmProviderInput::new(LlmCompatibleProvider::OpenAi, window, cx);
set_text(&input.provider_name, provider_name, window, cx);
set_text(&input.api_url, api_url, window, cx);
set_text(&input.api_key, api_key, window, cx);
for (i, (name, max_tokens, max_completion_tokens, max_output_tokens)) in
models.iter().enumerate()
{
if i >= input.models.len() {
input.models.push(ModelInput::new(window, cx));
}
let model = &mut input.models[i];
set_text(&model.name, name, window, cx);
set_text(&model.max_tokens, max_tokens, window, cx);
set_text(
&model.max_completion_tokens,
max_completion_tokens,
window,
cx,
);
set_text(&model.max_output_tokens, max_output_tokens, window, cx);
}
save_provider_to_settings(&input, cx)
});
task.await.err()
}
}

View File

@@ -1,4 +1,5 @@
use std::{
path::PathBuf,
sync::{Arc, Mutex},
time::Duration,
};
@@ -188,7 +189,7 @@ fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)
}
None => (
"some-mcp-server".to_string(),
"".to_string(),
PathBuf::new(),
"[]".to_string(),
"{}".to_string(),
),
@@ -199,13 +200,14 @@ fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)
/// The name of your MCP server
"{name}": {{
/// The command which runs the MCP server
"command": "{command}",
"command": "{}",
/// The arguments to pass to the MCP server
"args": {args},
/// The environment variables to set
"env": {env}
}}
}}"#
}}"#,
command.display()
)
}

View File

@@ -1,5 +1,5 @@
use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll};
use acp::{AcpThread, AcpThreadEvent};
use acp_thread::{AcpThread, AcpThreadEvent};
use agent::{Thread, ThreadEvent, ThreadSummary};
use agent_settings::AgentSettings;
use anyhow::Result;
@@ -30,7 +30,7 @@ use std::{
sync::Arc,
time::Duration,
};
use ui::{IconButtonShape, KeyBinding, Tooltip, prelude::*, vertical_divider};
use ui::{ButtonSize, IconButtonShape, KeyBinding, Tooltip, prelude::*, vertical_divider};
use util::ResultExt;
use workspace::{
Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
@@ -81,7 +81,7 @@ impl AgentDiffThread {
match self {
AgentDiffThread::Native(thread) => thread.read(cx).is_generating(),
AgentDiffThread::AcpThread(thread) => {
thread.read(cx).status() == acp::ThreadStatus::Generating
thread.read(cx).status() == acp_thread::ThreadStatus::Generating
}
}
}
@@ -791,22 +791,32 @@ fn render_diff_hunk_controls(
) -> AnyElement {
let editor = editor.clone();
h_flex()
// Get controls positioning from editor state
let controls_above = editor.read(cx).diff_hunk_controls_above();
let mut container = h_flex()
.h(line_height)
.mr_0p5()
.gap_1()
.px_0p5()
.pb_1()
.py_0p5()
.border_x_1()
.border_b_1()
.border_color(cx.theme().colors().border)
.rounded_b_md()
.bg(cx.theme().colors().editor_background)
.gap_1()
.block_mouse_except_scroll()
.shadow_md()
.shadow_md();
if controls_above {
container = container.border_t_1().rounded_t_md();
} else {
container = container.border_b_1().rounded_b_md();
}
container
.children(vec![
Button::new(("reject", row as u64), "Reject")
.size(ButtonSize::Compact)
.disabled(is_created_file)
.key_binding(
KeyBinding::for_action_in(
@@ -835,6 +845,7 @@ fn render_diff_hunk_controls(
}
}),
Button::new(("keep", row as u64), "Keep")
.size(ButtonSize::Compact)
.key_binding(
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
@@ -1506,8 +1517,7 @@ impl AgentDiff {
.read(cx)
.entries()
.last()
.and_then(|entry| entry.diff())
.is_some()
.map_or(false, |entry| entry.diffs().next().is_some())
{
self.update_reviewing_editors(workspace, window, cx);
}
@@ -1517,12 +1527,14 @@ impl AgentDiff {
.read(cx)
.entries()
.get(*ix)
.and_then(|entry| entry.diff())
.is_some()
.map_or(false, |entry| entry.diffs().next().is_some())
{
self.update_reviewing_editors(workspace, window, cx);
}
}
AcpThreadEvent::Stopped
| AcpThreadEvent::ToolAuthorizationRequired
| AcpThreadEvent::Error => {}
}
}

View File

@@ -1,8 +1,6 @@
use crate::{
ModelUsageContext,
language_model_selector::{
LanguageModelSelector, ToggleModelSelector, language_model_selector,
},
language_model_selector::{LanguageModelSelector, language_model_selector},
};
use agent_settings::AgentSettings;
use fs::Fs;
@@ -12,6 +10,7 @@ use picker::popover_menu::PickerPopoverMenu;
use settings::update_settings_file;
use std::sync::Arc;
use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*};
use zed_actions::agent::ToggleModelSelector;
pub struct AgentModelSelector {
selector: Entity<LanguageModelSelector>,
@@ -96,22 +95,18 @@ impl Render for AgentModelSelector {
let model_name = model
.as_ref()
.map(|model| model.model.name().0)
.unwrap_or_else(|| SharedString::from("No model selected"));
let provider_icon = model
.as_ref()
.map(|model| model.provider.icon())
.unwrap_or_else(|| IconName::Ai);
.unwrap_or_else(|| SharedString::from("Select a Model"));
let provider_icon = model.as_ref().map(|model| model.provider.icon());
let focus_handle = self.focus_handle.clone();
PickerPopoverMenu::new(
self.selector.clone(),
ButtonLike::new("active-model")
.child(
Icon::new(provider_icon)
.color(Color::Muted)
.size(IconSize::XSmall),
)
.when_some(provider_icon, |this, icon| {
this.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall))
})
.child(
Label::new(model_name)
.color(Color::Muted)

File diff suppressed because it is too large Load Diff

View File

@@ -25,12 +25,14 @@ mod thread_history;
mod tool_compatibility;
mod ui;
use std::rc::Rc;
use std::sync::Arc;
use agent::{Thread, ThreadId};
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
use assistant_slash_command::SlashCommandRegistry;
use client::Client;
use client::{Client, DisableAiSettings};
use command_palette_hooks::CommandPaletteFilter;
use feature_flags::FeatureFlagAppExt as _;
use fs::Fs;
use gpui::{Action, App, Entity, actions};
@@ -40,8 +42,9 @@ use language_model::{
};
use prompt_store::PromptBuilder;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use settings::{Settings as _, SettingsStore};
use std::any::TypeId;
pub use crate::active_thread::ActiveThread;
use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
@@ -51,14 +54,13 @@ use crate::slash_command_settings::SlashCommandSettings;
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor};
pub use ui::preview::{all_agent_previews, get_agent_preview};
use zed_actions;
actions!(
agent,
[
/// Creates a new text-based conversation thread.
NewTextThread,
/// Creates a new external agent conversation thread.
NewAcpThread,
/// Toggles the context picker interface for adding files, symbols, or other context.
ToggleContextPicker,
/// Toggles the navigation menu for switching between threads and views.
@@ -133,6 +135,34 @@ pub struct NewThread {
from_thread_id: Option<ThreadId>,
}
/// Creates a new external agent conversation thread.
#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
#[action(namespace = agent)]
#[serde(deny_unknown_fields)]
pub struct NewExternalAgentThread {
/// Which agent to use for the conversation.
agent: Option<ExternalAgent>,
}
#[derive(Default, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum ExternalAgent {
#[default]
Gemini,
ClaudeCode,
Codex,
}
impl ExternalAgent {
pub fn server(&self) -> Rc<dyn agent_servers::AgentServer> {
match self {
ExternalAgent::Gemini => Rc::new(agent_servers::Gemini),
ExternalAgent::ClaudeCode => Rc::new(agent_servers::ClaudeCode),
ExternalAgent::Codex => Rc::new(agent_servers::Codex),
}
}
}
/// Opens the profile management interface for configuring agent tools and settings.
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
#[action(namespace = agent)]
@@ -216,6 +246,69 @@ pub fn init(
})
.detach();
cx.observe_new(ManageProfilesModal::register).detach();
// Update command palette filter based on AI settings
update_command_palette_filter(cx);
// Watch for settings changes
cx.observe_global::<SettingsStore>(|app_cx| {
// When settings change, update the command palette filter
update_command_palette_filter(app_cx);
})
.detach();
}
fn update_command_palette_filter(cx: &mut App) {
let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
CommandPaletteFilter::update_global(cx, |filter, _| {
if disable_ai {
filter.hide_namespace("agent");
filter.hide_namespace("assistant");
filter.hide_namespace("copilot");
filter.hide_namespace("supermaven");
filter.hide_namespace("zed_predict_onboarding");
filter.hide_namespace("edit_prediction");
use editor::actions::{
AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
};
let edit_prediction_actions = [
TypeId::of::<AcceptEditPrediction>(),
TypeId::of::<AcceptPartialEditPrediction>(),
TypeId::of::<ShowEditPrediction>(),
TypeId::of::<NextEditPrediction>(),
TypeId::of::<PreviousEditPrediction>(),
TypeId::of::<ToggleEditPrediction>(),
];
filter.hide_action_types(&edit_prediction_actions);
filter.hide_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
} else {
filter.show_namespace("agent");
filter.show_namespace("assistant");
filter.show_namespace("copilot");
filter.show_namespace("zed_predict_onboarding");
filter.show_namespace("edit_prediction");
use editor::actions::{
AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
};
let edit_prediction_actions = [
TypeId::of::<AcceptEditPrediction>(),
TypeId::of::<AcceptPartialEditPrediction>(),
TypeId::of::<ShowEditPrediction>(),
TypeId::of::<NextEditPrediction>(),
TypeId::of::<PreviousEditPrediction>(),
TypeId::of::<ToggleEditPrediction>(),
];
filter.show_action_types(edit_prediction_actions.iter());
filter
.show_action_types([TypeId::of::<zed_actions::OpenZedPredictOnboarding>()].iter());
}
});
}
fn init_language_model_settings(cx: &mut App) {

View File

@@ -6,6 +6,7 @@ use agent::{
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result};
use client::telemetry::Telemetry;
use cloud_llm_client::CompletionIntent;
use collections::HashSet;
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
use futures::{
@@ -35,7 +36,6 @@ use std::{
};
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
use zed_llm_client::CompletionIntent;
pub struct BufferCodegen {
alternatives: Vec<Entity<CodegenAlternative>>,

View File

@@ -1,10 +1,10 @@
#![allow(unused, dead_code)]
use client::{ModelRequestUsage, RequestUsage};
use cloud_llm_client::{Plan, UsageLimit};
use gpui::Global;
use std::ops::{Deref, DerefMut};
use ui::prelude::*;
use zed_llm_client::{Plan, UsageLimit};
/// Debug only: Used for testing various account states
///

View File

@@ -16,7 +16,7 @@ use agent::{
};
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result};
use client::telemetry::Telemetry;
use client::{DisableAiSettings, telemetry::Telemetry};
use collections::{HashMap, HashSet, VecDeque, hash_map};
use editor::SelectionEffects;
use editor::{
@@ -48,7 +48,7 @@ use text::{OffsetRangeExt, ToPoint as _};
use ui::prelude::*;
use util::{RangeExt, ResultExt, maybe};
use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId};
use zed_actions::agent::OpenConfiguration;
use zed_actions::agent::OpenSettings;
pub fn init(
fs: Arc<dyn Fs>,
@@ -57,6 +57,17 @@ pub fn init(
cx: &mut App,
) {
cx.set_global(InlineAssistant::new(fs, prompt_builder, telemetry));
cx.observe_global::<SettingsStore>(|cx| {
if DisableAiSettings::get_global(cx).disable_ai {
// Hide any active inline assist UI when AI is disabled
InlineAssistant::update_global(cx, |assistant, cx| {
assistant.cancel_all_active_completions(cx);
});
}
})
.detach();
cx.observe_new(|_workspace: &mut Workspace, window, cx| {
let Some(window) = window else {
return;
@@ -141,6 +152,26 @@ impl InlineAssistant {
.detach();
}
/// Hides all active inline assists when AI is disabled
pub fn cancel_all_active_completions(&mut self, cx: &mut App) {
// Cancel all active completions in editors
for (editor_handle, _) in self.assists_by_editor.iter() {
if let Some(editor) = editor_handle.upgrade() {
let windows = cx.windows();
if !windows.is_empty() {
let window = windows[0];
let _ = window.update(cx, |_, window, cx| {
editor.update(cx, |editor, cx| {
if editor.has_active_inline_completion() {
editor.cancel(&Default::default(), window, cx);
}
});
});
}
}
}
}
fn handle_workspace_event(
&mut self,
workspace: Entity<Workspace>,
@@ -176,7 +207,7 @@ impl InlineAssistant {
window: &mut Window,
cx: &mut App,
) {
let is_assistant2_enabled = true;
let is_assistant2_enabled = !DisableAiSettings::get_global(cx).disable_ai;
if let Some(editor) = item.act_as::<Editor>(cx) {
editor.update(cx, |editor, cx| {
@@ -199,6 +230,13 @@ impl InlineAssistant {
cx,
);
if DisableAiSettings::get_global(cx).disable_ai {
// Cancel any active completions
if editor.has_active_inline_completion() {
editor.cancel(&Default::default(), window, cx);
}
}
// Remove the Assistant1 code action provider, as it still might be registered.
editor.remove_code_action_provider("assistant".into(), window, cx);
} else {
@@ -219,7 +257,7 @@ impl InlineAssistant {
cx: &mut Context<Workspace>,
) {
let settings = AgentSettings::get_global(cx);
if !settings.enabled {
if !settings.enabled || DisableAiSettings::get_global(cx).disable_ai {
return;
}
@@ -307,7 +345,7 @@ impl InlineAssistant {
if let Some(answer) = answer {
if answer == 0 {
cx.update(|window, cx| {
window.dispatch_action(Box::new(OpenConfiguration), cx)
window.dispatch_action(Box::new(OpenSettings), cx)
})
.ok();
}

View File

@@ -2,7 +2,6 @@ use crate::agent_model_selector::AgentModelSelector;
use crate::buffer_codegen::BufferCodegen;
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider};
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
use crate::language_model_selector::ToggleModelSelector;
use crate::message_editor::{ContextCreasesAddon, extract_message_creases, insert_message_creases};
use crate::terminal_codegen::TerminalCodegen;
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist, ModelUsageContext};
@@ -38,6 +37,7 @@ use ui::{
CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, PopoverMenuHandle, Tooltip, prelude::*,
};
use workspace::Workspace;
use zed_actions::agent::ToggleModelSelector;
pub struct PromptEditor<T> {
pub editor: Entity<Editor>,

View File

@@ -3,9 +3,7 @@ use std::{cmp::Reverse, sync::Arc};
use collections::{HashSet, IndexMap};
use feature_flags::ZedProFeatureFlag;
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
use gpui::{
Action, AnyElement, App, BackgroundExecutor, DismissEvent, Subscription, Task, actions,
};
use gpui::{Action, AnyElement, App, BackgroundExecutor, DismissEvent, Subscription, Task};
use language_model::{
AuthenticateError, ConfiguredModel, LanguageModel, LanguageModelProviderId,
LanguageModelRegistry,
@@ -15,15 +13,6 @@ use picker::{Picker, PickerDelegate};
use proto::Plan;
use ui::{ListItem, ListItemSpacing, prelude::*};
actions!(
agent,
[
/// Toggles the language model selector dropdown.
#[action(deprecated_aliases = ["assistant::ToggleModelSelector", "assistant2::ToggleModelSelector"])]
ToggleModelSelector
]
);
const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro";
type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &mut App) + 'static>;
@@ -587,7 +576,7 @@ impl PickerDelegate for LanguageModelPickerDelegate {
.icon_position(IconPosition::Start)
.on_click(|_, window, cx| {
window.dispatch_action(
zed_actions::agent::OpenConfiguration.boxed_clone(),
zed_actions::agent::OpenSettings.boxed_clone(),
cx,
);
}),

View File

@@ -4,19 +4,20 @@ use std::sync::Arc;
use crate::agent_diff::AgentDiffThread;
use crate::agent_model_selector::AgentModelSelector;
use crate::language_model_selector::ToggleModelSelector;
use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip};
use crate::ui::{
MaxModeTooltip,
preview::{AgentPreview, UsageCallout},
};
use agent::history_store::HistoryStore;
use agent::{
context::{AgentContextKey, ContextLoadResult, load_context},
context_store::ContextStoreEvent,
};
use agent_settings::{AgentSettings, CompletionMode};
use ai_onboarding::ApiKeysWithProviders;
use buffer_diff::BufferDiff;
use client::UserStore;
use cloud_llm_client::CompletionIntent;
use collections::{HashMap, HashSet};
use editor::actions::{MoveUp, Paste};
use editor::display_map::CreaseId;
@@ -29,17 +30,18 @@ use fs::Fs;
use futures::future::Shared;
use futures::{FutureExt as _, future};
use gpui::{
Animation, AnimationExt, App, Entity, EventEmitter, Focusable, Subscription, Task, TextStyle,
WeakEntity, linear_color_stop, linear_gradient, point, pulsating_between,
Animation, AnimationExt, App, Entity, EventEmitter, Focusable, IntoElement, KeyContext,
Subscription, Task, TextStyle, WeakEntity, linear_color_stop, linear_gradient, point,
pulsating_between,
};
use language::{Buffer, Language, Point};
use language_model::{
ConfiguredModel, LanguageModelRequestMessage, MessageContent, ZED_CLOUD_PROVIDER_ID,
ConfiguredModel, LanguageModelRegistry, LanguageModelRequestMessage, MessageContent,
ZED_CLOUD_PROVIDER_ID,
};
use multi_buffer;
use project::Project;
use prompt_store::PromptStore;
use proto::Plan;
use settings::Settings;
use std::time::Duration;
use theme::ThemeSettings;
@@ -49,7 +51,7 @@ use ui::{
use util::ResultExt as _;
use workspace::{CollaboratorId, Workspace};
use zed_actions::agent::Chat;
use zed_llm_client::CompletionIntent;
use zed_actions::agent::ToggleModelSelector;
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
@@ -65,6 +67,9 @@ use agent::{
thread_store::{TextThreadStore, ThreadStore},
};
pub const MIN_EDITOR_LINES: usize = 4;
pub const MAX_EDITOR_LINES: usize = 8;
#[derive(RegisterComponent)]
pub struct MessageEditor {
thread: Entity<Thread>,
@@ -72,9 +77,9 @@ pub struct MessageEditor {
editor: Entity<Editor>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
user_store: Entity<UserStore>,
context_store: Entity<ContextStore>,
prompt_store: Option<Entity<PromptStore>>,
history_store: Option<WeakEntity<HistoryStore>>,
context_strip: Entity<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: Entity<AgentModelSelector>,
@@ -88,9 +93,6 @@ pub struct MessageEditor {
_subscriptions: Vec<Subscription>,
}
const MIN_EDITOR_LINES: usize = 4;
const MAX_EDITOR_LINES: usize = 8;
pub(crate) fn create_editor(
workspace: WeakEntity<Workspace>,
context_store: WeakEntity<ContextStore>,
@@ -132,6 +134,7 @@ pub(crate) fn create_editor(
placement: Some(ContextMenuPlacement::Above),
});
editor.register_addon(ContextCreasesAddon::new());
editor.register_addon(MessageEditorAddon::new());
editor
});
@@ -153,11 +156,11 @@ impl MessageEditor {
pub fn new(
fs: Arc<dyn Fs>,
workspace: WeakEntity<Workspace>,
user_store: Entity<UserStore>,
context_store: Entity<ContextStore>,
prompt_store: Option<Entity<PromptStore>>,
thread_store: WeakEntity<ThreadStore>,
text_thread_store: WeakEntity<TextThreadStore>,
history_store: Option<WeakEntity<HistoryStore>>,
thread: Entity<Thread>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -224,12 +227,12 @@ impl MessageEditor {
Self {
editor: editor.clone(),
project: thread.read(cx).project().clone(),
user_store,
thread,
incompatible_tools_state: incompatible_tools.clone(),
workspace,
context_store,
prompt_store,
history_store,
context_strip,
context_picker_menu_handle,
load_context_task: None,
@@ -609,7 +612,11 @@ impl MessageEditor {
)
}
fn render_follow_toggle(&self, cx: &mut Context<Self>) -> impl IntoElement {
fn render_follow_toggle(
&self,
is_model_selected: bool,
cx: &mut Context<Self>,
) -> impl IntoElement {
let following = self
.workspace
.read_with(cx, |workspace, _| {
@@ -618,6 +625,7 @@ impl MessageEditor {
.unwrap_or(false);
IconButton::new("follow-agent", IconName::Crosshair)
.disabled(!is_model_selected)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.toggle_state(following)
@@ -705,11 +713,11 @@ impl MessageEditor {
cx.listener(|this, _: &RejectAll, window, cx| this.handle_reject_all(window, cx)),
)
.capture_action(cx.listener(Self::paste))
.gap_2()
.p_2()
.bg(editor_bg_color)
.gap_2()
.border_t_1()
.border_color(cx.theme().colors().border)
.bg(editor_bg_color)
.child(
h_flex()
.justify_between()
@@ -786,7 +794,7 @@ impl MessageEditor {
.justify_between()
.child(
h_flex()
.child(self.render_follow_toggle(cx))
.child(self.render_follow_toggle(is_model_selected, cx))
.children(self.render_burn_mode_toggle(cx)),
)
.child(
@@ -902,6 +910,10 @@ impl MessageEditor {
.on_click({
let focus_handle = focus_handle.clone();
move |_event, window, cx| {
telemetry::event!(
"Agent Message Sent",
agent = "zed",
);
focus_handle.dispatch_action(
&Chat, window, cx,
);
@@ -1270,24 +1282,12 @@ impl MessageEditor {
return None;
}
let user_store = self.user_store.read(cx);
let ubb_enable = user_store
.usage_based_billing_enabled()
.map_or(false, |enabled| enabled);
if ubb_enable {
let user_store = self.project.read(cx).user_store().read(cx);
if user_store.is_usage_based_billing_enabled() {
return None;
}
let plan = user_store
.current_plan()
.map(|plan| match plan {
Plan::Free => zed_llm_client::Plan::ZedFree,
Plan::ZedPro => zed_llm_client::Plan::ZedPro,
Plan::ZedProTrial => zed_llm_client::Plan::ZedProTrial,
})
.unwrap_or(zed_llm_client::Plan::ZedFree);
let plan = user_store.plan().unwrap_or(cloud_llm_client::Plan::ZedFree);
let usage = user_store.model_request_usage()?;
@@ -1489,6 +1489,31 @@ pub struct ContextCreasesAddon {
_subscription: Option<Subscription>,
}
pub struct MessageEditorAddon {}
impl MessageEditorAddon {
pub fn new() -> Self {
Self {}
}
}
impl Addon for MessageEditorAddon {
fn to_any(&self) -> &dyn std::any::Any {
self
}
fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
Some(self)
}
fn extend_key_context(&self, key_context: &mut KeyContext, cx: &App) {
let settings = agent_settings::AgentSettings::get_global(cx);
if settings.use_modifier_to_send {
key_context.add("use_modifier_to_send");
}
}
}
impl Addon for ContextCreasesAddon {
fn to_any(&self) -> &dyn std::any::Any {
self
@@ -1624,9 +1649,38 @@ impl Render for MessageEditor {
let line_height = TextSize::Small.rems(cx).to_pixels(window.rem_size()) * 1.5;
let has_configured_providers = LanguageModelRegistry::read_global(cx)
.providers()
.iter()
.filter(|provider| {
provider.is_authenticated(cx) && provider.id() != ZED_CLOUD_PROVIDER_ID
})
.count()
> 0;
let is_signed_out = self
.workspace
.read_with(cx, |workspace, _| {
workspace.client().status().borrow().is_signed_out()
})
.unwrap_or(true);
let has_history = self
.history_store
.as_ref()
.and_then(|hs| hs.update(cx, |hs, cx| hs.entries(cx).len() > 0).ok())
.unwrap_or(false)
|| self
.thread
.read_with(cx, |thread, _| thread.messages().len() > 0);
v_flex()
.size_full()
.bg(cx.theme().colors().panel_background)
.when(
!has_history && is_signed_out && has_configured_providers,
|this| this.child(cx.new(ApiKeysWithProviders::new)),
)
.when(changed_buffers.len() > 0, |parent| {
parent.child(self.render_edits_bar(&changed_buffers, window, cx))
})
@@ -1698,7 +1752,6 @@ impl AgentPreview for MessageEditor {
) -> Option<AnyElement> {
if let Some(workspace) = workspace.upgrade() {
let fs = workspace.read(cx).app_state().fs.clone();
let user_store = workspace.read(cx).app_state().user_store.clone();
let project = workspace.read(cx).project().clone();
let weak_project = project.downgrade();
let context_store = cx.new(|_cx| ContextStore::new(weak_project, None));
@@ -1711,11 +1764,11 @@ impl AgentPreview for MessageEditor {
MessageEditor::new(
fs,
workspace.downgrade(),
user_store,
context_store,
None,
thread_store.downgrade(),
text_thread_store.downgrade(),
None,
thread,
window,
cx,

View File

@@ -10,6 +10,7 @@ use agent::{
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result};
use client::telemetry::Telemetry;
use cloud_llm_client::CompletionIntent;
use collections::{HashMap, VecDeque};
use editor::{MultiBuffer, actions::SelectAll};
use fs::Fs;
@@ -27,7 +28,6 @@ use terminal_view::TerminalView;
use ui::prelude::*;
use util::ResultExt;
use workspace::{Toast, Workspace, notifications::NotificationId};
use zed_llm_client::CompletionIntent;
pub fn init(
fs: Arc<dyn Fs>,

View File

@@ -1,8 +1,6 @@
use crate::{
burn_mode_tooltip::BurnModeTooltip,
language_model_selector::{
LanguageModelSelector, ToggleModelSelector, language_model_selector,
},
language_model_selector::{LanguageModelSelector, language_model_selector},
};
use agent_settings::{AgentSettings, CompletionMode};
use anyhow::Result;
@@ -38,8 +36,7 @@ use language::{
language_settings::{SoftWrap, all_language_settings},
};
use language_model::{
ConfigurationError, LanguageModelExt, LanguageModelImage, LanguageModelProviderTosView,
LanguageModelRegistry, Role,
ConfigurationError, LanguageModelExt, LanguageModelImage, LanguageModelRegistry, Role,
};
use multi_buffer::MultiBufferRow;
use picker::{Picker, popover_menu::PickerPopoverMenu};
@@ -74,6 +71,7 @@ use workspace::{
pane,
searchable::{SearchEvent, SearchableItem},
};
use zed_actions::agent::ToggleModelSelector;
use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};
use assistant_context::{
@@ -1895,108 +1893,6 @@ impl TextThreadEditor {
.update(cx, |context, cx| context.summarize(true, cx));
}
fn render_notice(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
// This was previously gated behind the `zed-pro` feature flag. Since we
// aren't planning to ship that right now, we're just hard-coding this
// value to not show the nudge.
let nudge = Some(false);
let model_registry = LanguageModelRegistry::read_global(cx);
if nudge.map_or(false, |value| value) {
Some(
h_flex()
.p_3()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.bg(cx.theme().colors().editor_background)
.justify_between()
.child(
h_flex()
.gap_3()
.child(Icon::new(IconName::ZedAssistant).color(Color::Accent))
.child(Label::new("Zed AI is here! Get started by signing in →")),
)
.child(
Button::new("sign-in", "Sign in")
.size(ButtonSize::Compact)
.style(ButtonStyle::Filled)
.on_click(cx.listener(|this, _event, _window, cx| {
let client = this
.workspace
.read_with(cx, |workspace, _| workspace.client().clone())
.log_err();
if let Some(client) = client {
cx.spawn(async move |context_editor, cx| {
match client.authenticate_and_connect(true, cx).await {
util::ConnectionResult::Timeout => {
log::error!("Authentication timeout")
}
util::ConnectionResult::ConnectionReset => {
log::error!("Connection reset")
}
util::ConnectionResult::Result(r) => {
if r.log_err().is_some() {
context_editor
.update(cx, |_, cx| cx.notify())
.ok();
}
}
}
})
.detach()
}
})),
)
.into_any_element(),
)
} else if let Some(configuration_error) =
model_registry.configuration_error(model_registry.default_model(), cx)
{
Some(
h_flex()
.px_3()
.py_2()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.bg(cx.theme().colors().editor_background)
.justify_between()
.child(
h_flex()
.gap_3()
.child(
Icon::new(IconName::Warning)
.size(IconSize::Small)
.color(Color::Warning),
)
.child(Label::new(configuration_error.to_string())),
)
.child(
Button::new("open-configuration", "Configure Providers")
.size(ButtonSize::Compact)
.icon(Some(IconName::SlidersVertical))
.icon_size(IconSize::Small)
.icon_position(IconPosition::Start)
.style(ButtonStyle::Filled)
.on_click({
let focus_handle = self.focus_handle(cx).clone();
move |_event, window, cx| {
focus_handle.dispatch_action(
&zed_actions::agent::OpenConfiguration,
window,
cx,
);
}
}),
)
.into_any_element(),
)
} else {
None
}
}
fn render_send_button(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx).clone();
@@ -2128,12 +2024,13 @@ impl TextThreadEditor {
.map(|default| default.model);
let model_name = match active_model {
Some(model) => model.name().0,
None => SharedString::from("No model selected"),
None => SharedString::from("Select Model"),
};
let active_provider = LanguageModelRegistry::read_global(cx)
.default_model()
.map(|default| default.provider);
let provider_icon = match active_provider {
Some(provider) => provider.icon(),
None => IconName::Ai,
@@ -2581,20 +2478,7 @@ impl EventEmitter<SearchEvent> for TextThreadEditor {}
impl Render for TextThreadEditor {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let provider = LanguageModelRegistry::read_global(cx)
.default_model()
.map(|default| default.provider);
let accept_terms = if self.show_accept_terms {
provider.as_ref().and_then(|provider| {
provider.render_accept_terms(LanguageModelProviderTosView::PromptEditorPopup, cx)
})
} else {
None
};
let language_model_selector = self.language_model_selector_menu_handle.clone();
let burn_mode_toggle = self.render_burn_mode_toggle(cx);
v_flex()
.key_context("ContextEditor")
@@ -2611,28 +2495,12 @@ impl Render for TextThreadEditor {
language_model_selector.toggle(window, cx);
})
.size_full()
.children(self.render_notice(cx))
.child(
div()
.flex_grow()
.bg(cx.theme().colors().editor_background)
.child(self.editor.clone()),
)
.when_some(accept_terms, |this, element| {
this.child(
div()
.absolute()
.right_3()
.bottom_12()
.max_w_96()
.py_2()
.px_3()
.elevation_2(cx)
.bg(cx.theme().colors().surface_background)
.occlude()
.child(element),
)
})
.children(self.render_last_error(cx))
.child(
h_flex()
@@ -2649,7 +2517,7 @@ impl Render for TextThreadEditor {
h_flex()
.gap_0p5()
.child(self.render_inject_context_menu(cx))
.when_some(burn_mode_toggle, |this, element| this.child(element)),
.children(self.render_burn_mode_toggle(cx)),
)
.child(
h_flex()

View File

@@ -1,11 +1,14 @@
mod agent_notification;
mod burn_mode_tooltip;
mod context_pill;
mod end_trial_upsell;
mod new_thread_button;
mod onboarding_modal;
pub mod preview;
mod upsell;
pub use agent_notification::*;
pub use burn_mode_tooltip::*;
pub use context_pill::*;
pub use end_trial_upsell::*;
pub use new_thread_button::*;
pub use onboarding_modal::*;

View File

@@ -0,0 +1,123 @@
use std::sync::Arc;
use ai_onboarding::{AgentPanelOnboardingCard, BulletItem};
use client::zed_urls;
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
use ui::{Divider, List, Tooltip, prelude::*};
#[derive(IntoElement, RegisterComponent)]
pub struct EndTrialUpsell {
dismiss_upsell: Arc<dyn Fn(&mut Window, &mut App)>,
}
impl EndTrialUpsell {
pub fn new(dismiss_upsell: Arc<dyn Fn(&mut Window, &mut App)>) -> Self {
Self { dismiss_upsell }
}
}
impl RenderOnce for EndTrialUpsell {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let pro_section = v_flex()
.gap_1()
.child(
h_flex()
.gap_2()
.child(
Label::new("Pro")
.size(LabelSize::Small)
.color(Color::Accent)
.buffer_font(cx),
)
.child(Divider::horizontal()),
)
.child(
List::new()
.child(BulletItem::new("500 prompts with Claude models"))
.child(BulletItem::new(
"Unlimited edit predictions with Zeta, our open-source model",
)),
)
.child(
Button::new("cta-button", "Upgrade to Zed Pro")
.full_width()
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
.on_click(move |_, _window, cx| {
telemetry::event!("Upgrade To Pro Clicked", state = "end-of-trial");
cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx))
}),
);
let free_section = v_flex()
.mt_1p5()
.gap_1()
.child(
h_flex()
.gap_2()
.child(
Label::new("Free")
.size(LabelSize::Small)
.color(Color::Muted)
.buffer_font(cx),
)
.child(
Label::new("(Current Plan)")
.size(LabelSize::Small)
.color(Color::Custom(cx.theme().colors().text_muted.opacity(0.6)))
.buffer_font(cx),
)
.child(Divider::horizontal()),
)
.child(
List::new()
.child(BulletItem::new("50 prompts with the Claude models"))
.child(BulletItem::new("2,000 accepted edit predictions")),
);
AgentPanelOnboardingCard::new()
.child(Headline::new("Your Zed Pro Trial has expired"))
.child(
Label::new("You've been automatically reset to the Free plan.")
.color(Color::Muted)
.mb_2(),
)
.child(pro_section)
.child(free_section)
.child(
h_flex().absolute().top_4().right_4().child(
IconButton::new("dismiss_onboarding", IconName::Close)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Dismiss"))
.on_click({
let callback = self.dismiss_upsell.clone();
move |_, window, cx| {
telemetry::event!("Banner Dismissed", source = "AI Onboarding");
callback(window, cx)
}
}),
),
)
}
}
impl Component for EndTrialUpsell {
fn scope() -> ComponentScope {
ComponentScope::Agent
}
fn sort_name() -> &'static str {
"AgentEndTrialUpsell"
}
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
Some(
v_flex()
.p_4()
.gap_4()
.child(EndTrialUpsell {
dismiss_upsell: Arc::new(|_, _| {}),
})
.into_any_element(),
)
}
}

Some files were not shown because too many files have changed in this diff Show More